Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Umesh Yadav
a787992431 sibling of 875c86e3ef 2025-07-16 15:27:56 +00:00
286 changed files with 3675 additions and 16480 deletions

View file

@ -23,8 +23,6 @@ workspace-members = [
] ]
third-party = [ third-party = [
{ name = "reqwest", version = "0.11.27" }, { name = "reqwest", version = "0.11.27" },
# build of remote_server should not include scap / its x11 dependency
{ name = "scap", git = "https://github.com/zed-industries/scap", rev = "270538dc780f5240723233ff901e1054641ed318" },
] ]
[final-excludes] [final-excludes]

View file

@ -0,0 +1,64 @@
name: "Trusted Signing on Windows"
description: "Install trusted signing on Windows."
# Modified from https://github.com/Azure/trusted-signing-action
runs:
using: "composite"
steps:
- name: Set variables
id: set-variables
shell: "pwsh"
run: |
$defaultPath = $env:PSModulePath -split ';' | Select-Object -First 1
"PSMODULEPATH=$defaultPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"TRUSTED_SIGNING_MODULE_VERSION=0.5.3" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"BUILD_TOOLS_NUGET_VERSION=10.0.22621.3233" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"TRUSTED_SIGNING_NUGET_VERSION=1.0.53" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
"DOTNET_SIGNCLI_NUGET_VERSION=0.9.1-beta.24469.1" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
- name: Cache TrustedSigning PowerShell module
id: cache-module
uses: actions/cache@v4
env:
cache-name: cache-module
with:
path: ${{ steps.set-variables.outputs.PSMODULEPATH }}\TrustedSigning\${{ steps.set-variables.outputs.TRUSTED_SIGNING_MODULE_VERSION }}
key: TrustedSigning-${{ steps.set-variables.outputs.TRUSTED_SIGNING_MODULE_VERSION }}
if: ${{ inputs.cache-dependencies == 'true' }}
- name: Cache Microsoft.Windows.SDK.BuildTools NuGet package
id: cache-buildtools
uses: actions/cache@v4
env:
cache-name: cache-buildtools
with:
path: ~\AppData\Local\TrustedSigning\Microsoft.Windows.SDK.BuildTools\Microsoft.Windows.SDK.BuildTools.${{ steps.set-variables.outputs.BUILD_TOOLS_NUGET_VERSION }}
key: Microsoft.Windows.SDK.BuildTools-${{ steps.set-variables.outputs.BUILD_TOOLS_NUGET_VERSION }}
if: ${{ inputs.cache-dependencies == 'true' }}
- name: Cache Microsoft.Trusted.Signing.Client NuGet package
id: cache-tsclient
uses: actions/cache@v4
env:
cache-name: cache-tsclient
with:
path: ~\AppData\Local\TrustedSigning\Microsoft.Trusted.Signing.Client\Microsoft.Trusted.Signing.Client.${{ steps.set-variables.outputs.TRUSTED_SIGNING_NUGET_VERSION }}
key: Microsoft.Trusted.Signing.Client-${{ steps.set-variables.outputs.TRUSTED_SIGNING_NUGET_VERSION }}
if: ${{ inputs.cache-dependencies == 'true' }}
- name: Cache SignCli NuGet package
id: cache-signcli
uses: actions/cache@v4
env:
cache-name: cache-signcli
with:
path: ~\AppData\Local\TrustedSigning\sign\sign.${{ steps.set-variables.outputs.DOTNET_SIGNCLI_NUGET_VERSION }}
key: SignCli-${{ steps.set-variables.outputs.DOTNET_SIGNCLI_NUGET_VERSION }}
if: ${{ inputs.cache-dependencies == 'true' }}
- name: Install Trusted Signing module
shell: "pwsh"
run: |
Install-Module -Name TrustedSigning -RequiredVersion ${{ steps.set-variables.outputs.TRUSTED_SIGNING_MODULE_VERSION }} -Force -Repository PSGallery
if: ${{ inputs.cache-dependencies != 'true' || steps.cache-module.outputs.cache-hit != 'true' }}

View file

@ -21,9 +21,6 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
jobs: jobs:
job_spec: job_spec:
@ -55,10 +52,9 @@ jobs:
fi fi
# Specify anything which should skip full CI in this regex: # Specify anything which should skip full CI in this regex:
# - docs/ # - docs/
# - script/update_top_ranking_issues/
# - .github/ISSUE_TEMPLATE/ # - .github/ISSUE_TEMPLATE/
# - .github/workflows/ (except .github/workflows/ci.yml) # - .github/workflows/ (except .github/workflows/ci.yml)
SKIP_REGEX='^(docs/|script/update_top_ranking_issues/|\.github/(ISSUE_TEMPLATE|workflows/(?!ci)))' SKIP_REGEX='^(docs/|\.github/(ISSUE_TEMPLATE|workflows/(?!ci)))'
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep -vP "$SKIP_REGEX") ]]; then if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep -vP "$SKIP_REGEX") ]]; then
echo "run_tests=true" >> $GITHUB_OUTPUT echo "run_tests=true" >> $GITHUB_OUTPUT
else else
@ -75,7 +71,7 @@ jobs:
echo "run_license=false" >> $GITHUB_OUTPUT echo "run_license=false" >> $GITHUB_OUTPUT
fi fi
NIX_REGEX='^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)' NIX_REGEX='^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)'
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep -P "$NIX_REGEX") ]]; then if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep "$NIX_REGEX") ]]; then
echo "run_nix=true" >> $GITHUB_OUTPUT echo "run_nix=true" >> $GITHUB_OUTPUT
else else
echo "run_nix=false" >> $GITHUB_OUTPUT echo "run_nix=false" >> $GITHUB_OUTPUT
@ -394,7 +390,7 @@ jobs:
windows_tests: windows_tests:
timeout-minutes: 60 timeout-minutes: 60
name: (Windows) Run Clippy and tests name: (Windows) Run Tests
needs: [job_spec] needs: [job_spec]
if: | if: |
github.repository_owner == 'zed-industries' && github.repository_owner == 'zed-industries' &&
@ -496,6 +492,9 @@ jobs:
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }} APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }} APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps: steps:
- name: Install Node - name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
@ -578,6 +577,10 @@ jobs:
startsWith(github.ref, 'refs/tags/v') startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') || contains(github.event.pull_request.labels.*.name, 'run-bundling')
needs: [linux_tests] needs: [linux_tests]
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@ -631,6 +634,10 @@ jobs:
startsWith(github.ref, 'refs/tags/v') startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling') || contains(github.event.pull_request.labels.*.name, 'run-bundling')
needs: [linux_tests] needs: [linux_tests]
env:
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@ -685,12 +692,16 @@ jobs:
) )
needs: [linux_tests] needs: [linux_tests]
name: Build Zed on FreeBSD name: Build Zed on FreeBSD
# env:
# MYTOKEN : ${{ secrets.MYTOKEN }}
# MYTOKEN2: "value2"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Build FreeBSD remote-server - name: Build FreeBSD remote-server
id: freebsd-build id: freebsd-build
uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0 uses: vmactions/freebsd-vm@c3ae29a132c8ef1924775414107a97cac042aad5 # v1.2.0
with: with:
# envs: "MYTOKEN MYTOKEN2"
usesh: true usesh: true
release: 13.5 release: 13.5
copyback: true copyback: true
@ -757,6 +768,8 @@ jobs:
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }} ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }} CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }} ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
FILE_DIGEST: SHA256 FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256 TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com" TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
@ -773,6 +786,9 @@ jobs:
# This exports RELEASE_CHANNEL into env (GITHUB_ENV) # This exports RELEASE_CHANNEL into env (GITHUB_ENV)
script/determine-release-channel.ps1 script/determine-release-channel.ps1
- name: Install trusted signing
uses: ./.github/actions/install_trusted_signing
- name: Build Zed installer - name: Build Zed installer
working-directory: ${{ env.ZED_WORKSPACE }} working-directory: ${{ env.ZED_WORKSPACE }}
run: script/bundle-windows.ps1 run: script/bundle-windows.ps1

View file

@ -12,9 +12,6 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
jobs: jobs:
style: style:
@ -94,6 +91,9 @@ jobs:
APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }} APPLE_NOTARIZATION_KEY: ${{ secrets.APPLE_NOTARIZATION_KEY }}
APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }} APPLE_NOTARIZATION_KEY_ID: ${{ secrets.APPLE_NOTARIZATION_KEY_ID }}
APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }} APPLE_NOTARIZATION_ISSUER_ID: ${{ secrets.APPLE_NOTARIZATION_ISSUER_ID }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
steps: steps:
- name: Install Node - name: Install Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
@ -125,6 +125,10 @@ jobs:
runs-on: runs-on:
- buildjet-16vcpu-ubuntu-2004 - buildjet-16vcpu-ubuntu-2004
needs: tests needs: tests
env:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@ -160,6 +164,10 @@ jobs:
runs-on: runs-on:
- buildjet-16vcpu-ubuntu-2204-arm - buildjet-16vcpu-ubuntu-2204-arm
needs: tests needs: tests
env:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
ZED_CLIENT_CHECKSUM_SEED: ${{ secrets.ZED_CLIENT_CHECKSUM_SEED }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@ -190,6 +198,9 @@ jobs:
if: false && github.repository_owner == 'zed-industries' if: false && github.repository_owner == 'zed-industries'
runs-on: github-8vcpu-ubuntu-2404 runs-on: github-8vcpu-ubuntu-2404
needs: tests needs: tests
env:
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
name: Build Zed on FreeBSD name: Build Zed on FreeBSD
# env: # env:
# MYTOKEN : ${{ secrets.MYTOKEN }} # MYTOKEN : ${{ secrets.MYTOKEN }}
@ -246,6 +257,8 @@ jobs:
ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }} ACCOUNT_NAME: ${{ vars.AZURE_SIGNING_ACCOUNT_NAME }}
CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }} CERT_PROFILE_NAME: ${{ vars.AZURE_SIGNING_CERT_PROFILE_NAME }}
ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }} ENDPOINT: ${{ vars.AZURE_SIGNING_ENDPOINT }}
DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
FILE_DIGEST: SHA256 FILE_DIGEST: SHA256
TIMESTAMP_DIGEST: SHA256 TIMESTAMP_DIGEST: SHA256
TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com" TIMESTAMP_SERVER: "http://timestamp.acs.microsoft.com"
@ -263,6 +276,9 @@ jobs:
Write-Host "Publishing version: $version on release channel nightly" Write-Host "Publishing version: $version on release channel nightly"
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL" "nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
- name: Install trusted signing
uses: ./.github/actions/install_trusted_signing
- name: Build Zed installer - name: Build Zed installer
working-directory: ${{ env.ZED_WORKSPACE }} working-directory: ${{ env.ZED_WORKSPACE }}
run: script/bundle-windows.ps1 run: script/bundle-windows.ps1

View file

@ -40,7 +40,7 @@
}, },
"file_types": { "file_types": {
"Dockerfile": ["Dockerfile*[!dockerignore]"], "Dockerfile": ["Dockerfile*[!dockerignore]"],
"JSONC": ["**/assets/**/*.json", "renovate.json"], "JSONC": ["assets/**/*.json", "renovate.json"],
"Git Ignore": ["dockerignore"] "Git Ignore": ["dockerignore"]
}, },
"hard_tabs": false, "hard_tabs": false,

146
Cargo.lock generated
View file

@ -2,34 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "acp"
version = "0.1.0"
dependencies = [
"agent_servers",
"agentic-coding-protocol",
"anyhow",
"assistant_tool",
"async-pipe",
"buffer_diff",
"editor",
"env_logger 0.11.8",
"futures 0.3.31",
"gpui",
"indoc",
"itertools 0.14.0",
"language",
"markdown",
"project",
"serde_json",
"settings",
"smol",
"tempfile",
"ui",
"util",
"workspace-hack",
]
[[package]] [[package]]
name = "activity_indicator" name = "activity_indicator"
version = "0.1.0" version = "0.1.0"
@ -135,24 +107,6 @@ dependencies = [
"zstd", "zstd",
] ]
[[package]]
name = "agent_servers"
version = "0.1.0"
dependencies = [
"anyhow",
"collections",
"futures 0.3.31",
"gpui",
"paths",
"project",
"schemars",
"serde",
"settings",
"util",
"which 6.0.3",
"workspace-hack",
]
[[package]] [[package]]
name = "agent_settings" name = "agent_settings"
version = "0.1.0" version = "0.1.0"
@ -176,11 +130,8 @@ dependencies = [
name = "agent_ui" name = "agent_ui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"acp",
"agent", "agent",
"agent_servers",
"agent_settings", "agent_settings",
"agentic-coding-protocol",
"anyhow", "anyhow",
"assistant_context", "assistant_context",
"assistant_slash_command", "assistant_slash_command",
@ -240,7 +191,6 @@ dependencies = [
"settings", "settings",
"smol", "smol",
"streaming_diff", "streaming_diff",
"task",
"telemetry", "telemetry",
"telemetry_events", "telemetry_events",
"terminal", "terminal",
@ -262,24 +212,6 @@ dependencies = [
"zed_llm_client", "zed_llm_client",
] ]
[[package]]
name = "agentic-coding-protocol"
version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e276b798eddd02562a339340a96919d90bbfcf78de118fdddc932524646fac7"
dependencies = [
"anyhow",
"chrono",
"derive_more 2.0.1",
"futures 0.3.31",
"log",
"parking_lot",
"schemars",
"semver",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.7.8" version = "0.7.8"
@ -678,7 +610,7 @@ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"collections", "collections",
"derive_more 0.99.19", "derive_more",
"extension", "extension",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
@ -741,11 +673,10 @@ dependencies = [
"clock", "clock",
"collections", "collections",
"ctor", "ctor",
"derive_more 0.99.19", "derive_more",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
"icons", "icons",
"indoc",
"language", "language",
"language_model", "language_model",
"log", "log",
@ -778,7 +709,7 @@ dependencies = [
"clock", "clock",
"collections", "collections",
"component", "component",
"derive_more 0.99.19", "derive_more",
"editor", "editor",
"feature_flags", "feature_flags",
"fs", "fs",
@ -1235,7 +1166,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collections", "collections",
"derive_more 0.99.19", "derive_more",
"gpui", "gpui",
"parking_lot", "parking_lot",
"rodio", "rodio",
@ -2928,7 +2859,7 @@ dependencies = [
"cocoa 0.26.0", "cocoa 0.26.0",
"collections", "collections",
"credentials_provider", "credentials_provider",
"derive_more 0.99.19", "derive_more",
"feature_flags", "feature_flags",
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
@ -3116,7 +3047,7 @@ dependencies = [
"dap_adapters", "dap_adapters",
"dashmap 6.1.0", "dashmap 6.1.0",
"debugger_ui", "debugger_ui",
"derive_more 0.99.19", "derive_more",
"editor", "editor",
"envy", "envy",
"extension", "extension",
@ -3169,7 +3100,6 @@ dependencies = [
"session", "session",
"settings", "settings",
"sha2", "sha2",
"smol",
"sqlx", "sqlx",
"strum 0.27.1", "strum 0.27.1",
"subtle", "subtle",
@ -3322,7 +3252,7 @@ name = "command_palette_hooks"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"collections", "collections",
"derive_more 0.99.19", "derive_more",
"gpui", "gpui",
"workspace-hack", "workspace-hack",
] ]
@ -4397,15 +4327,12 @@ dependencies = [
"futures 0.3.31", "futures 0.3.31",
"fuzzy", "fuzzy",
"gpui", "gpui",
"hex",
"indoc", "indoc",
"itertools 0.14.0", "itertools 0.14.0",
"language", "language",
"log", "log",
"menu", "menu",
"notifications",
"parking_lot", "parking_lot",
"parse_int",
"paths", "paths",
"picker", "picker",
"pretty_assertions", "pretty_assertions",
@ -4529,27 +4456,6 @@ dependencies = [
"syn 2.0.101", "syn 2.0.101",
] ]
[[package]]
name = "derive_more"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678"
dependencies = [
"derive_more-impl",
]
[[package]]
name = "derive_more-impl"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
"unicode-xid",
]
[[package]] [[package]]
name = "derive_refineable" name = "derive_refineable"
version = "0.1.0" version = "0.1.0"
@ -6248,7 +6154,7 @@ dependencies = [
"askpass", "askpass",
"async-trait", "async-trait",
"collections", "collections",
"derive_more 0.99.19", "derive_more",
"futures 0.3.31", "futures 0.3.31",
"git2", "git2",
"gpui", "gpui",
@ -7265,7 +7171,7 @@ dependencies = [
"core-video", "core-video",
"cosmic-text", "cosmic-text",
"ctor", "ctor",
"derive_more 0.99.19", "derive_more",
"embed-resource", "embed-resource",
"env_logger 0.11.8", "env_logger 0.11.8",
"etagere", "etagere",
@ -7811,7 +7717,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes 1.10.1", "bytes 1.10.1",
"derive_more 0.99.19", "derive_more",
"futures 0.3.31", "futures 0.3.31",
"http 1.3.1", "http 1.3.1",
"log", "log",
@ -8249,7 +8155,7 @@ dependencies = [
"async-trait", "async-trait",
"cargo_metadata", "cargo_metadata",
"collections", "collections",
"derive_more 0.99.19", "derive_more",
"extension", "extension",
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
@ -9008,7 +8914,6 @@ dependencies = [
"gpui", "gpui",
"language", "language",
"lsp", "lsp",
"project",
"serde", "serde",
"serde_json", "serde_json",
"util", "util",
@ -9061,6 +8966,7 @@ dependencies = [
"credentials_provider", "credentials_provider",
"deepseek", "deepseek",
"editor", "editor",
"feature_flags",
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
"google_ai", "google_ai",
@ -9688,11 +9594,12 @@ dependencies = [
[[package]] [[package]]
name = "lsp-types" name = "lsp-types"
version = "0.95.1" version = "0.95.1"
source = "git+https://github.com/zed-industries/lsp-types?rev=6add7052b598ea1f40f7e8913622c3958b009b60#6add7052b598ea1f40f7e8913622c3958b009b60" source = "git+https://github.com/zed-industries/lsp-types?rev=c9c189f1c5dd53c624a419ce35bc77ad6a908d18#c9c189f1c5dd53c624a419ce35bc77ad6a908d18"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"serde", "serde",
"serde_json", "serde_json",
"serde_repr",
"url", "url",
] ]
@ -11304,15 +11211,6 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "parse_int"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c464266693329dd5a8715098c7f86e6c5fd5d985018b8318f53d9c6c2b21a31"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "partial-json-fixer" name = "partial-json-fixer"
version = "0.5.3" version = "0.5.3"
@ -12356,7 +12254,6 @@ dependencies = [
"anyhow", "anyhow",
"askpass", "askpass",
"async-trait", "async-trait",
"base64 0.22.1",
"buffer_diff", "buffer_diff",
"circular-buffer", "circular-buffer",
"client", "client",
@ -12402,7 +12299,6 @@ dependencies = [
"sha2", "sha2",
"shellexpand 2.1.2", "shellexpand 2.1.2",
"shlex", "shlex",
"smallvec",
"smol", "smol",
"snippet", "snippet",
"snippet_provider", "snippet_provider",
@ -14137,7 +14033,7 @@ dependencies = [
[[package]] [[package]]
name = "scap" name = "scap"
version = "0.0.8" version = "0.0.8"
source = "git+https://github.com/zed-industries/scap?rev=270538dc780f5240723233ff901e1054641ed318#270538dc780f5240723233ff901e1054641ed318" source = "git+https://github.com/zed-industries/scap?rev=08f0a01417505cc0990b9931a37e5120db92e0d0#08f0a01417505cc0990b9931a37e5120db92e0d0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cocoa 0.25.0", "cocoa 0.25.0",
@ -14184,12 +14080,10 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe8c9d1c68d67dd9f97ecbc6f932b60eb289c5dbddd8aa1405484a8fd2fcd984" checksum = "fe8c9d1c68d67dd9f97ecbc6f932b60eb289c5dbddd8aa1405484a8fd2fcd984"
dependencies = [ dependencies = [
"chrono",
"dyn-clone", "dyn-clone",
"indexmap", "indexmap",
"ref-cast", "ref-cast",
"schemars_derive", "schemars_derive",
"semver",
"serde", "serde",
"serde_json", "serde_json",
] ]
@ -14711,19 +14605,16 @@ dependencies = [
"language", "language",
"log", "log",
"menu", "menu",
"notifications",
"paths", "paths",
"project", "project",
"schemars", "schemars",
"search", "search",
"serde", "serde",
"serde_json",
"settings", "settings",
"theme", "theme",
"tree-sitter-json", "tree-sitter-json",
"tree-sitter-rust", "tree-sitter-rust",
"ui", "ui",
"ui_input",
"util", "util",
"workspace", "workspace",
"workspace-hack", "workspace-hack",
@ -16151,7 +16042,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collections", "collections",
"derive_more 0.99.19", "derive_more",
"fs", "fs",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
@ -18418,6 +18309,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"client", "client",
"feature_flags",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
"http_client", "http_client",
@ -19689,7 +19581,6 @@ dependencies = [
"rustix 1.0.7", "rustix 1.0.7",
"rustls 0.23.26", "rustls 0.23.26",
"rustls-webpki 0.103.1", "rustls-webpki 0.103.1",
"schemars",
"scopeguard", "scopeguard",
"sea-orm", "sea-orm",
"sea-query-binder", "sea-query-binder",
@ -20094,11 +19985,10 @@ dependencies = [
[[package]] [[package]]
name = "zed" name = "zed"
version = "0.196.0" version = "0.195.2"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"agent", "agent",
"agent_servers",
"agent_settings", "agent_settings",
"agent_ui", "agent_ui",
"anyhow", "anyhow",

View file

@ -2,11 +2,9 @@
resolver = "2" resolver = "2"
members = [ members = [
"crates/activity_indicator", "crates/activity_indicator",
"crates/acp",
"crates/agent_ui", "crates/agent_ui",
"crates/agent", "crates/agent",
"crates/agent_settings", "crates/agent_settings",
"crates/agent_servers",
"crates/anthropic", "crates/anthropic",
"crates/askpass", "crates/askpass",
"crates/assets", "crates/assets",
@ -219,12 +217,10 @@ edition = "2024"
# Workspace member crates # Workspace member crates
# #
acp = { path = "crates/acp" }
agent = { path = "crates/agent" }
activity_indicator = { path = "crates/activity_indicator" } activity_indicator = { path = "crates/activity_indicator" }
agent = { path = "crates/agent" }
agent_ui = { path = "crates/agent_ui" } agent_ui = { path = "crates/agent_ui" }
agent_settings = { path = "crates/agent_settings" } agent_settings = { path = "crates/agent_settings" }
agent_servers = { path = "crates/agent_servers" }
ai = { path = "crates/ai" } ai = { path = "crates/ai" }
anthropic = { path = "crates/anthropic" } anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" } askpass = { path = "crates/askpass" }
@ -406,7 +402,6 @@ zlog_settings = { path = "crates/zlog_settings" }
# External crates # External crates
# #
agentic-coding-protocol = { version = "0.0.9" }
aho-corasick = "1.1" aho-corasick = "1.1"
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" } alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
any_vec = "0.14" any_vec = "0.14"
@ -494,7 +489,7 @@ libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] } libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0" linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "6add7052b598ea1f40f7e8913622c3958b009b60" } lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "c9c189f1c5dd53c624a419ce35bc77ad6a908d18" }
markup5ever_rcdom = "0.3.0" markup5ever_rcdom = "0.3.0"
metal = "0.29" metal = "0.29"
moka = { version = "0.12.10", features = ["sync"] } moka = { version = "0.12.10", features = ["sync"] }
@ -509,7 +504,6 @@ ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] } palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1" parking_lot = "0.12.1"
partial-json-fixer = "0.5.3" partial-json-fixer = "0.5.3"
parse_int = "0.9"
pathdiff = "0.2" pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }
@ -549,8 +543,7 @@ rustc-demangle = "0.1.23"
rustc-hash = "2.1.0" rustc-hash = "2.1.0"
rustls = { version = "0.23.26" } rustls = { version = "0.23.26" }
rustls-platform-verifier = "0.5.0" rustls-platform-verifier = "0.5.0"
# When updating scap rev, also update it in .config/hakari.toml scap = { git = "https://github.com/zed-industries/scap", rev = "08f0a01417505cc0990b9931a37e5120db92e0d0", default-features = false }
scap = { git = "https://github.com/zed-industries/scap", rev = "270538dc780f5240723233ff901e1054641ed318", default-features = false }
schemars = { version = "1.0", features = ["indexmap2"] } schemars = { version = "1.0", features = ["indexmap2"] }
semver = "1.0" semver = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] } serde = { version = "1.0", features = ["derive", "rc"] }

View file

@ -1 +0,0 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Google Gemini</title><path d="M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81"/></svg>

Before

Width:  |  Height:  |  Size: 402 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clipboard"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>

After

Width:  |  Height:  |  Size: 358 B

View file

@ -1,12 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bug"><path d="m8 2 1.88 1.88"/><path d="M14.12 3.88 16 2"/><path d="M9 7.13v-1a3.003 3.003 0 1 1 6 0v1"/><path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6"/><path d="M12 20v-9"/><path d="M6.53 9C4.6 8.8 3 7.1 3 5"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="M22 13h-4"/><path d="M17.2 17c2.1.1 3.8 1.9 3.8 4"/></svg>
<path d="M5.49219 2.29071L6.41455 3.1933" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.61816 3.1933L10.508 2.29071" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.7042 5.89221V5.15749C5.69033 4.85975 5.73943 4.56239 5.84856 4.28336C5.95768 4.00434 6.12456 3.74943 6.33913 3.53402C6.55369 3.31862 6.81149 3.14718 7.09697 3.03005C7.38245 2.91292 7.68969 2.85254 8.00014 2.85254C8.3106 2.85254 8.61784 2.91292 8.90332 3.03005C9.18879 3.14718 9.44659 3.31862 9.66116 3.53402C9.87572 3.74943 10.0426 4.00434 10.1517 4.28336C10.2609 4.56239 10.31 4.85975 10.2961 5.15749V5.89221" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.00006 13.0426C6.13263 13.0426 4.60474 11.6005 4.60474 9.83792V8.23558C4.60474 7.66895 4.84322 7.12554 5.26772 6.72487C5.69221 6.32421 6.26796 6.09912 6.86829 6.09912H9.13184C9.73217 6.09912 10.3079 6.32421 10.7324 6.72487C11.1569 7.12554 11.3954 7.66895 11.3954 8.23558V9.83792C11.3954 11.6005 9.86749 13.0426 8.00006 13.0426Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.60452 6.25196C3.51235 6.13878 2.60693 5.17677 2.60693 3.9884" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.60462 8.81659H2.34106" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.4541 13.3186C2.4541 12.1302 3.41611 11.1116 4.60448 11.0551" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.0761 3.9884C13.0761 5.17677 12.1706 6.13878 11.0955 6.25196" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.6591 8.81659H11.3955" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.3955 11.0551C12.5839 11.1116 13.5459 12.1302 13.5459 13.3186" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 615 B

Before After
Before After

View file

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0001 1.33334H4.00008C3.64646 1.33334 3.30732 1.47382 3.05727 1.72387C2.80722 1.97392 2.66675 2.31305 2.66675 2.66668V13.3333C2.66675 13.687 2.80722 14.0261 3.05727 14.2762C3.30732 14.5262 3.64646 14.6667 4.00008 14.6667H12.0001C12.3537 14.6667 12.6928 14.5262 12.9429 14.2762C13.1929 14.0261 13.3334 13.687 13.3334 13.3333V4.66668L10.0001 1.33334Z" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.66659 6.5L6.33325 9.83333" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.33325 6.5L9.66659 9.83333" stroke="black" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 804 B

View file

@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3V3.03125M3 3.03125V9M3 3.03125C3 5 5.96875 5 5.96875 5M3 9C3 11 5.96875 11 5.96875 11M3 9V12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3.03125 3V3.03125M3.03125 3.03125V9M3.03125 3.03125C3.03125 5 6 5 6 5M3.03125 9C3.03125 11 6 11 6 11M3.03125 9V12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="8" y="3" width="5.5" height="4" rx="1.5" fill="black"/> <rect x="8" y="2.5" width="6" height="5" rx="1.5" fill="black"/>
<rect x="8" y="9" width="5.5" height="4" rx="1.5" fill="black"/> <rect x="8" y="8.46875" width="6" height="5.0625" rx="1.5" fill="black"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 423 B

After

Width:  |  Height:  |  Size: 462 B

Before After
Before After

View file

@ -1,7 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="5" cy="12" r="1.25" stroke="black" stroke-width="1.5"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M3.75 3.25C4.02614 3.25 4.25 3.02614 4.25 2.75C4.25 2.47386 4.02614 2.25 3.75 2.25C3.47386 2.25 3.25 2.47386 3.25 2.75C3.25 3.02614 3.47386 3.25 3.75 3.25ZM3.75 4.25C4.57843 4.25 5.25 3.57843 5.25 2.75C5.25 1.92157 4.57843 1.25 3.75 1.25C2.92157 1.25 2.25 1.92157 2.25 2.75C2.25 3.57843 2.92157 4.25 3.75 4.25Z" fill="black"/>
<path d="M5 11V5" stroke="black" stroke-width="1.5"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M8.25 3.25C8.52614 3.25 8.75 3.02614 8.75 2.75C8.75 2.47386 8.52614 2.25 8.25 2.25C7.97386 2.25 7.75 2.47386 7.75 2.75C7.75 3.02614 7.97386 3.25 8.25 3.25ZM8.25 4.25C9.07843 4.25 9.75 3.57843 9.75 2.75C9.75 1.92157 9.07843 1.25 8.25 1.25C7.42157 1.25 6.75 1.92157 6.75 2.75C6.75 3.57843 7.42157 4.25 8.25 4.25Z" fill="black"/>
<path d="M5 10C5 10 5.5 8 7 8C7.73103 8 8.69957 8 9.50049 8C10.3289 8 11 7.32843 11 6.5V5" stroke="black" stroke-width="1.5"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M3.75 9.75C4.02614 9.75 4.25 9.52614 4.25 9.25C4.25 8.97386 4.02614 8.75 3.75 8.75C3.47386 8.75 3.25 8.97386 3.25 9.25C3.25 9.52614 3.47386 9.75 3.75 9.75ZM3.75 10.75C4.57843 10.75 5.25 10.0784 5.25 9.25C5.25 8.42157 4.57843 7.75 3.75 7.75C2.92157 7.75 2.25 8.42157 2.25 9.25C2.25 10.0784 2.92157 10.75 3.75 10.75Z" fill="black"/>
<circle cx="5" cy="4" r="1.25" stroke="black" stroke-width="1.5"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M3.25 3.75H4.25V5.59609C4.67823 5.35824 5.24991 5.25 6 5.25H7.25017C7.5262 5.25 7.75 5.02625 7.75 4.75V3.75H8.75V4.75C8.75 5.57832 8.07871 6.25 7.25017 6.25H6C5.14559 6.25 4.77639 6.41132 4.59684 6.56615C4.42571 6.71373 4.33877 6.92604 4.25 7.30651V8.25H3.25V3.75Z" fill="black"/>
<circle cx="11" cy="4" r="1.25" stroke="black" stroke-width="1.5"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 487 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Before After
Before After

View file

@ -1,7 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-tree"><path d="M21 12h-8"/><path d="M21 6H8"/><path d="M21 18h-8"/><path d="M3 6v4c0 1.1.9 2 2 2h3"/><path d="M3 10v6c0 1.1.9 2 2 2h3"/></svg>
<path d="M13.5 8H9.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.5 4L6.5 4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.5 12H9.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 3.5V6.33333C3 7.25 3.72 8 4.6 8H7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 6V10.5C3 11.325 3.72 12 4.6 12H7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 680 B

After

Width:  |  Height:  |  Size: 349 B

Before After
Before After

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-location-edit-icon lucide-location-edit"><path d="M17.97 9.304A8 8 0 0 0 2 10c0 4.69 4.887 9.562 7.022 11.468"/><path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/><circle cx="10" cy="10" r="3"/></svg>

Before

Width:  |  Height:  |  Size: 491 B

View file

@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 4L10 7L5 10V4Z" fill="black" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 227 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-code"><path d="m13 13.5 2-2.5-2-2.5"/><path d="m21 21-4.3-4.3"/><path d="M9 8.5 7 11l2 2.5"/><circle cx="11" cy="11" r="8"/></svg>

After

Width:  |  Height:  |  Size: 340 B

View file

@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.8889 3H4.11111C3.49746 3 3 3.49746 3 4.11111V11.8889C3 12.5025 3.49746 13 4.11111 13H11.8889C12.5025 13 13 12.5025 13 11.8889V4.11111C13 3.49746 12.5025 3 11.8889 3Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.37939 10.3243H10.3794" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.64966 9.32837L7.64966 7.32837L5.64966 5.32837" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 659 B

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.4174 10.2159C10.5454 9.58974 10.4174 9.57261 11.3762 8.46959C11.9337 7.82822 12.335 7.09214 12.335 6.27818C12.335 5.28184 11.9309 4.32631 11.2118 3.62179C10.4926 2.91728 9.5171 2.52148 8.50001 2.52148C7.48291 2.52148 6.50748 2.91728 5.78828 3.62179C5.06909 4.32631 4.66504 5.28184 4.66504 6.27818C4.66504 6.9043 4.79288 7.65565 5.62379 8.46959C6.58253 9.59098 6.45474 9.58974 6.58257 10.2159M10.4174 10.2159L10.4174 12.2989C10.4174 12.9504 9.87836 13.4786 9.21329 13.4786H7.78674C7.12167 13.4786 6.58253 12.9504 6.58253 12.2989L6.58257 10.2159M10.4174 10.2159H8.50001H6.58257" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 776 B

View file

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 2.5H6.5C6.22386 2.5 6 2.83579 6 3.25V4.75C6 5.16421 6.22386 5.5 6.5 5.5H9.5C9.77614 5.5 10 5.16421 10 4.75V3.25C10 2.83579 9.77614 2.5 9.5 2.5Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 3.5H11C11.2652 3.5 11.5196 3.61706 11.7071 3.82544C11.8946 4.03381 12 4.31643 12 4.61111V12.3889C12 12.6836 11.8946 12.9662 11.7071 13.1746C11.5196 13.3829 11.2652 13.5 11 13.5H5C4.73478 13.5 4.48043 13.3829 4.29289 13.1746C4.10536 12.9662 4 12.6836 4 12.3889V4.61111C4 4.31643 4.10536 4.03381 4.29289 3.82544C4.48043 3.61706 4.73478 3.5 5 3.5H6" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 788 B

View file

@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.50002 2.5H5C4.73478 2.5 4.48043 2.6159 4.29289 2.82219C4.10535 3.02848 4 3.30826 4 3.6V12.3999C4 12.6917 4.10535 12.9715 4.29289 13.1778C4.48043 13.3841 4.73478 13.5 5 13.5H11C11.2652 13.5 11.5195 13.3841 11.7071 13.1778C11.8946 12.9715 12 12.6917 12 12.3999V5.25L9.50002 2.5Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.3427 6.82379L6.65698 9.5095" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.65698 6.82379L9.3427 9.5095" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 724 B

View file

@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.7244 11.5299L9.01711 3.2922C8.91447 3.11109 8.76562 2.96045 8.58576 2.85564C8.4059 2.75084 8.20145 2.69562 7.99328 2.69562C7.7851 2.69562 7.58066 2.75084 7.40079 2.85564C7.22093 2.96045 7.07209 3.11109 6.96945 3.2922L2.26218 11.5299C2.15844 11.7096 2.10404 11.9135 2.1045 12.121C2.10495 12.3285 2.16026 12.5321 2.2648 12.7113C2.36934 12.8905 2.5194 13.0389 2.69978 13.1415C2.88015 13.244 3.08443 13.297 3.2919 13.2951H12.7064C12.9129 13.2949 13.1157 13.2404 13.2944 13.137C13.4731 13.0336 13.6215 12.8851 13.7247 12.7062C13.8278 12.5273 13.8821 12.3245 13.882 12.118C13.882 11.9115 13.8276 11.7087 13.7244 11.5299Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.99927 6.23425V8.58788" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.99927 10.9415H8.00492" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.4 12.5C12.6917 12.5 12.9715 12.3884 13.1778 12.1899C13.3841 11.9913 13.5 11.722 13.5 11.4412V6.14706C13.5 5.86624 13.3841 5.59693 13.1778 5.39836C12.9715 5.19979 12.6917 5.08824 12.4 5.08824H8.055C7.87103 5.08997 7.68955 5.04726 7.52717 4.96402C7.36478 4.88078 7.22668 4.75967 7.1255 4.61176L6.68 3.97647C6.57984 3.83007 6.44349 3.7099 6.28317 3.62674C6.12286 3.54358 5.94361 3.50003 5.7615 3.5H3.6C3.30826 3.5 3.02847 3.61155 2.82218 3.81012C2.61589 4.00869 2.5 4.27801 2.5 4.55882V11.4412C2.5 11.722 2.61589 11.9913 2.82218 12.1899C3.02847 12.3884 3.30826 12.5 3.6 12.5H12.4Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 778 B

View file

@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 8.5L4.94864 12.6222C4.71647 12.8544 4.40157 12.9848 4.07323 12.9848C3.74488 12.9848 3.42999 12.8544 3.19781 12.6222C2.96564 12.39 2.83521 12.0751 2.83521 11.7468C2.83521 11.4185 2.96564 11.1036 3.19781 10.8714L7.5 6.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.8352 9.98474L13.8352 6.98474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.8352 7.42495L11.7634 6.4298C11.5533 6.23484 11.4353 5.97039 11.4352 5.69462V5.08526L10.1696 3.91022C9.54495 3.33059 8.69961 3.00261 7.81649 2.99722L5.83521 2.98474L6.35041 3.41108C6.71634 3.71233 7.00935 4.08216 7.21013 4.4962C7.4109 4.91024 7.51488 5.35909 7.51521 5.81316L7.5 6.5L9 8.5L9.5 8C9.5 8 9.87337 7.79457 10.0834 7.98959L11.1552 8.98474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 988 B

View file

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.5 12C6.65203 12.304 6.87068 12.5565 7.13399 12.7321C7.39729 12.9076 7.69597 13 8 13C8.30403 13 8.60271 12.9076 8.86601 12.7321C9.12932 12.5565 9.34797 12.304 9.5 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.63088 9.21874C3.56556 9.28556 3.52246 9.36865 3.50681 9.45791C3.49116 9.54718 3.50364 9.63876 3.54273 9.72152C3.58183 9.80429 3.64585 9.87467 3.72701 9.92409C3.80817 9.97352 3.90298 9.99987 3.99989 9.99994H12.0001C12.097 9.99997 12.1918 9.97372 12.273 9.92439C12.3542 9.87505 12.4183 9.80476 12.4575 9.72205C12.4967 9.63934 12.5093 9.54778 12.4938 9.45851C12.4783 9.36924 12.4353 9.2861 12.3701 9.21921C11.705 8.57941 11 7.89947 11 5.79994C11 5.05733 10.684 4.34514 10.1213 3.82004C9.55872 3.29494 8.79564 2.99994 7.99997 2.99994C7.20431 2.99994 6.44123 3.29494 5.87861 3.82004C5.31599 4.34514 4.99991 5.05733 4.99991 5.79994C4.99991 7.89947 4.2944 8.57941 3.63088 9.21874Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5871 5.40582C12.8514 5.14152 13 4.78304 13 4.40922C13 4.03541 12.8516 3.67688 12.5873 3.41252C12.323 3.14816 11.9645 2.99962 11.5907 2.99957C11.2169 2.99953 10.8584 3.14798 10.594 3.41227L3.92098 10.0869C3.80488 10.2027 3.71903 10.3452 3.67097 10.5019L3.01047 12.678C2.99754 12.7212 2.99657 12.7672 3.00764 12.8109C3.01872 12.8547 3.04143 12.8946 3.07337 12.9265C3.1053 12.9584 3.14528 12.981 3.18905 12.992C3.23282 13.003 3.27875 13.002 3.32197 12.989L5.49849 12.329C5.65508 12.2813 5.79758 12.196 5.91349 12.0805L12.5871 5.40582Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 5L11 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 835 B

View file

@ -1,7 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 5.66667V4.33333C3 3.97971 3.14048 3.64057 3.39052 3.39052C3.64057 3.14048 3.97971 3 4.33333 3H5.66667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.3333 3H11.6666C12.0202 3 12.3593 3.14048 12.6094 3.39052C12.8594 3.64057 12.9999 3.97971 12.9999 4.33333V5.66667" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.9999 10.3333V11.6666C12.9999 12.0203 12.8594 12.3594 12.6094 12.6095C12.3593 12.8595 12.0202 13 11.6666 13H10.3333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.66667 13H4.33333C3.97971 13 3.64057 12.8595 3.39052 12.6095C3.14048 12.3594 3 12.0203 3 11.6666V10.3333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.5 8H10.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.57132 13.7143C5.20251 13.7143 5.71418 13.2026 5.71418 12.5714C5.71418 11.9403 5.20251 11.4286 4.57132 11.4286C3.94014 11.4286 3.42847 11.9403 3.42847 12.5714C3.42847 13.2026 3.94014 13.7143 4.57132 13.7143Z" fill="black"/>
<path d="M10.2856 2.85712V5.71426M10.2856 5.71426V8.5714M10.2856 5.71426H13.1428M10.2856 5.71426H7.42847M10.2856 5.71426L12.1904 3.80949M10.2856 5.71426L8.38084 7.61906M10.2856 5.71426L12.1904 7.61906M10.2856 5.71426L8.38084 3.80949" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 631 B

View file

@ -1,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 13L11 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.5 12C9.98528 12 12 9.98528 12 7.5C12 5.01472 9.98528 3 7.5 3C5.01472 3 3 5.01472 3 7.5C3 9.98528 5.01472 12 7.5 12Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 421 B

View file

@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.99487 8.44023L7.32821 7.10689L5.99487 5.77356" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.33838 10.2264H10.005" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.8889 3H4.11111C3.49746 3 3 3.49746 3 4.11111V11.8889C3 12.5025 3.49746 13 4.11111 13H11.8889C12.5025 13 13 12.5025 13 11.8889V4.11111C13 3.49746 12.5025 3 11.8889 3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 625 B

View file

@ -1,5 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.99993 13.4804C11.0267 13.4804 13.4803 11.0267 13.4803 7.99999C13.4803 4.97325 11.0267 2.51959 7.99993 2.51959C4.97319 2.51959 2.51953 4.97325 2.51953 7.99999C2.51953 11.0267 4.97319 13.4804 7.99993 13.4804Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 3C6.71611 4.34807 6 6.13836 6 7.99999C6 9.86163 6.71611 11.6519 8 13C9.28387 11.6519 10 9.86163 10 7.99999C10 6.13836 9.28387 4.34807 8 3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.24121 7.04827C4.52425 8.27022 6.22817 8.95178 7.99999 8.95178C9.77182 8.95178 11.4757 8.27022 12.7588 7.04827" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 847 B

View file

@ -1,5 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.79118 8.27005C8.27568 8.27005 9.4791 7.06663 9.4791 5.58214C9.4791 4.09765 8.27568 2.89423 6.79118 2.89423C5.30669 2.89423 4.10327 4.09765 4.10327 5.58214C4.10327 7.06663 5.30669 8.27005 6.79118 8.27005Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M5.9 8.00002C7.44656 8.00002 8.7 6.74637 8.7 5.20002C8.7 3.65368 7.44656 2.40002 5.9 2.40002C4.35344 2.40002 3.1 3.65368 3.1 5.20002C3.1 6.74637 4.35344 8.00002 5.9 8.00002ZM7.00906 9.05002H4.79094C2.69684 9.05002 1 10.7475 1 12.841C1 13.261 1.3395 13.6 1.75819 13.6H10.0409C10.4609 13.6 10.8 13.261 10.8 12.841C10.8 10.7475 9.1025 9.05002 7.00906 9.05002ZM11.4803 9.40002H9.86484C10.87 10.2247 11.5 11.4585 11.5 12.841C11.5 13.121 11.4169 13.3791 11.2812 13.6H14.3C14.6872 13.6 15 13.285 15 12.8803C15 10.9663 13.4338 9.40002 11.4803 9.40002ZM10.45 8.00002C11.8041 8.00002 12.9 6.90409 12.9 5.55002C12.9 4.19596 11.8041 3.10002 10.45 3.10002C9.90072 3.10002 9.39913 3.28716 8.9905 3.59243C9.2425 4.07631 9.4 4.61815 9.4 5.20002C9.4 5.97702 9.13903 6.69059 8.70897 7.27181C9.15281 7.72002 9.7675 8.00002 10.45 8.00002Z" fill="white"/>
<path d="M6.79112 8.60443C4.19441 8.60443 2.08936 10.7095 2.08936 13.3062H11.4929C11.4929 10.7095 9.38784 8.60443 6.79112 8.60443Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.6984 12.9263C14.6984 10.8893 13.4895 8.99736 12.2806 8.09067C12.6779 7.79254 12.9957 7.40104 13.2057 6.95083C13.4157 6.50062 13.5115 6.00558 13.4846 5.50952C13.4577 5.01346 13.309 4.53168 13.0515 4.10681C12.7941 3.68194 12.4358 3.3271 12.0085 3.07367" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 999 B

After

Width:  |  Height:  |  Size: 947 B

Before After
Before After

View file

@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 2.93652L6.9243 6.20697C6.86924 6.37435 6.77565 6.52646 6.65105 6.65105C6.52646 6.77565 6.37435 6.86924 6.20697 6.9243L2.93652 8L6.20697 9.0757C6.37435 9.13076 6.52646 9.22435 6.65105 9.34895C6.77565 9.47354 6.86924 9.62565 6.9243 9.79306L8 13.0635L9.0757 9.79306C9.13076 9.62565 9.22435 9.47354 9.34895 9.34895C9.47354 9.22435 9.62565 9.13076 9.79306 9.0757L13.0635 8L9.79306 6.9243C9.62565 6.86924 9.47354 6.77565 9.34895 6.65105C9.22435 6.52646 9.13076 6.37435 9.0757 6.20697L8 2.93652Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> <path d="M8 2L6.72534 5.87534C6.6601 6.07367 6.5492 6.25392 6.40155 6.40155C6.25392 6.5492 6.07367 6.6601 5.87534 6.72534L2 8L5.87534 9.27466C6.07367 9.3399 6.25392 9.4508 6.40155 9.59845C6.5492 9.74608 6.6601 9.92633 6.72534 10.1247L8 14L9.27466 10.1247C9.3399 9.92633 9.4508 9.74608 9.59845 9.59845C9.74608 9.4508 9.92633 9.3399 10.1247 9.27466L14 8L10.1247 6.72534C9.92633 6.6601 9.74608 6.5492 9.59845 6.40155C9.4508 6.25392 9.3399 6.07367 9.27466 5.87534L8 2Z" fill="black" fill-opacity="0.15" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.33334 2V4.66666M2 3.33334H4.66666" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3.33334 2V4.66666M2 3.33334H4.66666" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.6665 11.3333V14M11.3333 12.6666H13.9999" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/> <path d="M12.6665 11.3333V14M11.3333 12.6666H13.9999" stroke="black" stroke-opacity="0.75" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 998 B

Before After
Before After

View file

@ -268,14 +268,6 @@
"ctrl-alt-t": "agent::NewThread" "ctrl-alt-t": "agent::NewThread"
} }
}, },
{
"context": "AgentPanel && acp_thread",
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "agent::NewAcpThread",
"ctrl-alt-t": "agent::NewThread"
}
},
{ {
"context": "MessageEditor > Editor", "context": "MessageEditor > Editor",
"bindings": { "bindings": {
@ -314,16 +306,6 @@
"enter": "agent::AcceptSuggestedContext" "enter": "agent::AcceptSuggestedContext"
} }
}, },
{
"context": "AcpThread > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "agent::Chat",
"up": "agent::PreviousHistoryMessage",
"down": "agent::NextHistoryMessage",
"shift-ctrl-r": "agent::OpenAgentDiff"
}
},
{ {
"context": "ThreadHistory", "context": "ThreadHistory",
"bindings": { "bindings": {
@ -475,8 +457,8 @@
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }], "ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }],
"ctrl-u": "editor::UndoSelection", "ctrl-u": "editor::UndoSelection",
"ctrl-shift-u": "editor::RedoSelection", "ctrl-shift-u": "editor::RedoSelection",
"f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }], "f8": "editor::GoToDiagnostic",
"shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }], "shift-f8": "editor::GoToPreviousDiagnostic",
"f2": "editor::Rename", "f2": "editor::Rename",
"f12": "editor::GoToDefinition", "f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit", "alt-f12": "editor::GoToDefinitionSplit",
@ -856,7 +838,6 @@
"alt-shift-y": "git::UnstageFile", "alt-shift-y": "git::UnstageFile",
"ctrl-alt-y": "git::ToggleStaged", "ctrl-alt-y": "git::ToggleStaged",
"space": "git::ToggleStaged", "space": "git::ToggleStaged",
"shift-space": "git::StageRange",
"tab": "git_panel::FocusEditor", "tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor", "shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus", "escape": "git_panel::ToggleFocus",
@ -999,7 +980,6 @@
{ {
"context": "FileFinder || (FileFinder > Picker > Editor)", "context": "FileFinder || (FileFinder > Picker > Editor)",
"bindings": { "bindings": {
"ctrl-p": "file_finder::Toggle",
"ctrl-shift-a": "file_finder::ToggleSplitMenu", "ctrl-shift-a": "file_finder::ToggleSplitMenu",
"ctrl-shift-i": "file_finder::ToggleFilterMenu" "ctrl-shift-i": "file_finder::ToggleFilterMenu"
} }
@ -1115,35 +1095,7 @@
"context": "KeymapEditor", "context": "KeymapEditor",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"ctrl-f": "search::FocusSearch", "ctrl-f": "search::FocusSearch"
"alt-find": "keymap_editor::ToggleKeystrokeSearch",
"alt-ctrl-f": "keymap_editor::ToggleKeystrokeSearch",
"alt-c": "keymap_editor::ToggleConflictFilter"
}
},
{
"context": "KeystrokeInput",
"use_key_equivalents": true,
"bindings": {
"enter": "keystroke_input::StartRecording",
"escape escape escape": "keystroke_input::StopRecording",
"delete": "keystroke_input::ClearKeystrokes"
}
},
{
"context": "KeybindEditorModal",
"use_key_equivalents": true,
"bindings": {
"ctrl-enter": "menu::Confirm",
"escape": "menu::Cancel"
}
},
{
"context": "KeybindEditorModal > Editor",
"use_key_equivalents": true,
"bindings": {
"up": "menu::SelectPrevious",
"down": "menu::SelectNext"
} }
} }
] ]

View file

@ -309,14 +309,6 @@
"cmd-alt-t": "agent::NewThread" "cmd-alt-t": "agent::NewThread"
} }
}, },
{
"context": "AgentPanel && acp_thread",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "agent::NewAcpThread",
"cmd-alt-t": "agent::NewThread"
}
},
{ {
"context": "MessageEditor > Editor", "context": "MessageEditor > Editor",
"use_key_equivalents": true, "use_key_equivalents": true,
@ -365,16 +357,6 @@
"ctrl--": "pane::GoBack" "ctrl--": "pane::GoBack"
} }
}, },
{
"context": "AcpThread > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "agent::Chat",
"up": "agent::PreviousHistoryMessage",
"down": "agent::NextHistoryMessage",
"shift-ctrl-r": "agent::OpenAgentDiff"
}
},
{ {
"context": "ThreadHistory", "context": "ThreadHistory",
"bindings": { "bindings": {
@ -528,8 +510,8 @@
"cmd-/": ["editor::ToggleComments", { "advance_downwards": false }], "cmd-/": ["editor::ToggleComments", { "advance_downwards": false }],
"cmd-u": "editor::UndoSelection", "cmd-u": "editor::UndoSelection",
"cmd-shift-u": "editor::RedoSelection", "cmd-shift-u": "editor::RedoSelection",
"f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }], "f8": "editor::GoToDiagnostic",
"shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }], "shift-f8": "editor::GoToPreviousDiagnostic",
"f2": "editor::Rename", "f2": "editor::Rename",
"f12": "editor::GoToDefinition", "f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit", "alt-f12": "editor::GoToDefinitionSplit",
@ -930,7 +912,6 @@
"enter": "menu::Confirm", "enter": "menu::Confirm",
"cmd-alt-y": "git::ToggleStaged", "cmd-alt-y": "git::ToggleStaged",
"space": "git::ToggleStaged", "space": "git::ToggleStaged",
"shift-space": "git::StageRange",
"cmd-y": "git::StageFile", "cmd-y": "git::StageFile",
"cmd-shift-y": "git::UnstageFile", "cmd-shift-y": "git::UnstageFile",
"alt-down": "git_panel::FocusEditor", "alt-down": "git_panel::FocusEditor",
@ -1098,7 +1079,6 @@
"ctrl-cmd-space": "terminal::ShowCharacterPalette", "ctrl-cmd-space": "terminal::ShowCharacterPalette",
"cmd-c": "terminal::Copy", "cmd-c": "terminal::Copy",
"cmd-v": "terminal::Paste", "cmd-v": "terminal::Paste",
"cmd-f": "buffer_search::Deploy",
"cmd-a": "editor::SelectAll", "cmd-a": "editor::SelectAll",
"cmd-k": "terminal::Clear", "cmd-k": "terminal::Clear",
"cmd-n": "workspace::NewTerminal", "cmd-n": "workspace::NewTerminal",
@ -1214,33 +1194,7 @@
"context": "KeymapEditor", "context": "KeymapEditor",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"cmd-alt-f": "keymap_editor::ToggleKeystrokeSearch", "cmd-f": "search::FocusSearch"
"cmd-alt-c": "keymap_editor::ToggleConflictFilter"
}
},
{
"context": "KeystrokeInput",
"use_key_equivalents": true,
"bindings": {
"enter": "keystroke_input::StartRecording",
"escape escape escape": "keystroke_input::StopRecording",
"delete": "keystroke_input::ClearKeystrokes"
}
},
{
"context": "KeybindEditorModal",
"use_key_equivalents": true,
"bindings": {
"cmd-enter": "menu::Confirm",
"escape": "menu::Cancel"
}
},
{
"context": "KeybindEditorModal > Editor",
"use_key_equivalents": true,
"bindings": {
"up": "menu::SelectPrevious",
"down": "menu::SelectNext"
} }
} }
] ]

View file

@ -466,7 +466,7 @@
} }
}, },
{ {
"context": "(vim_mode == insert || vim_mode == normal) && showing_signature_help && !showing_completions", "context": "vim_mode == insert && showing_signature_help && !showing_completions",
"bindings": { "bindings": {
"ctrl-p": "editor::SignatureHelpPrevious", "ctrl-p": "editor::SignatureHelpPrevious",
"ctrl-n": "editor::SignatureHelpNext" "ctrl-n": "editor::SignatureHelpNext"
@ -841,7 +841,6 @@
"i": "git_panel::FocusEditor", "i": "git_panel::FocusEditor",
"x": "git::ToggleStaged", "x": "git::ToggleStaged",
"shift-x": "git::StageAll", "shift-x": "git::StageAll",
"g x": "git::StageRange",
"shift-u": "git::UnstageAll" "shift-u": "git::UnstageAll"
} }
}, },

View file

@ -84,7 +84,7 @@
"bottom_dock_layout": "contained", "bottom_dock_layout": "contained",
// The direction that you want to split panes horizontally. Defaults to "up" // The direction that you want to split panes horizontally. Defaults to "up"
"pane_split_direction_horizontal": "up", "pane_split_direction_horizontal": "up",
// The direction that you want to split panes vertically. Defaults to "left" // The direction that you want to split panes horizontally. Defaults to "left"
"pane_split_direction_vertical": "left", "pane_split_direction_vertical": "left",
// Centered layout related settings. // Centered layout related settings.
"centered_layout": { "centered_layout": {
@ -362,9 +362,7 @@
// Whether to show user picture in the titlebar. // Whether to show user picture in the titlebar.
"show_user_picture": true, "show_user_picture": true,
// Whether to show the sign in button in the titlebar. // Whether to show the sign in button in the titlebar.
"show_sign_in": true, "show_sign_in": true
// Whether to show the menus in the titlebar.
"show_menus": false
}, },
// Scrollbar related settings // Scrollbar related settings
"scrollbar": { "scrollbar": {
@ -1135,7 +1133,6 @@
"**/.svn", "**/.svn",
"**/.hg", "**/.hg",
"**/.jj", "**/.jj",
"**/.repo",
"**/CVS", "**/CVS",
"**/.DS_Store", "**/.DS_Store",
"**/Thumbs.db", "**/Thumbs.db",
@ -1158,14 +1155,16 @@
// Control whether the git blame information is shown inline, // Control whether the git blame information is shown inline,
// in the currently focused line. // in the currently focused line.
"inline_blame": { "inline_blame": {
"enabled": true, "enabled": true
// Sets a delay after which the inline blame information is shown. // Sets a delay after which the inline blame information is shown.
// Delay is restarted with every cursor movement. // Delay is restarted with every cursor movement.
"delay_ms": 0, // "delay_ms": 600
//
// Whether or not to display the git commit summary on the same line. // Whether or not to display the git commit summary on the same line.
"show_commit_summary": false, // "show_commit_summary": false
//
// The minimum column number to show the inline blame information at // The minimum column number to show the inline blame information at
"min_column": 0 // "min_column": 0
}, },
// How git hunks are displayed visually in the editor. // How git hunks are displayed visually in the editor.
// This setting can take two values: // This setting can take two values:
@ -1378,11 +1377,11 @@
// This will be merged with the platform's default font fallbacks // This will be merged with the platform's default font fallbacks
// "font_fallbacks": ["FiraCode Nerd Fonts"], // "font_fallbacks": ["FiraCode Nerd Fonts"],
// The weight of the editor font in standard CSS units from 100 to 900. // The weight of the editor font in standard CSS units from 100 to 900.
"font_weight": 400, // "font_weight": 400
// Sets the maximum number of lines in the terminal's scrollback buffer. // Sets the maximum number of lines in the terminal's scrollback buffer.
// Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling. // Default: 10_000, maximum: 100_000 (all bigger values set will be treated as 100_000), 0 disables the scrolling.
// Existing terminals will not pick up this change until they are recreated. // Existing terminals will not pick up this change until they are recreated.
"max_scroll_history_lines": 10000, // "max_scroll_history_lines": 10000,
// The minimum APCA perceptual contrast between foreground and background colors. // The minimum APCA perceptual contrast between foreground and background colors.
// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x, // APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x,
// especially for dark mode. Values range from 0 to 106. // especially for dark mode. Values range from 0 to 106.
@ -1671,10 +1670,6 @@
"allowed": true "allowed": true
} }
}, },
"SystemVerilog": {
"format_on_save": "off",
"use_on_type_format": false
},
"Vue.js": { "Vue.js": {
"language_servers": ["vue-language-server", "..."], "language_servers": ["vue-language-server", "..."],
"prettier": { "prettier": {
@ -1860,8 +1855,6 @@
"read_ssh_config": true, "read_ssh_config": true,
// Configures context servers for use by the agent. // Configures context servers for use by the agent.
"context_servers": {}, "context_servers": {},
// Configures agent servers available in the agent panel.
"agent_servers": {},
"debugger": { "debugger": {
"stepping_granularity": "line", "stepping_granularity": "line",
"save_breakpoints": true, "save_breakpoints": true,

View file

@ -1,47 +0,0 @@
[package]
name = "acp"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/acp.rs"
doctest = false
[features]
test-support = ["gpui/test-support", "project/test-support"]
gemini = []
[dependencies]
agent_servers.workspace = true
agentic-coding-protocol.workspace = true
anyhow.workspace = true
assistant_tool.workspace = true
buffer_diff.workspace = true
editor.workspace = true
futures.workspace = true
gpui.workspace = true
itertools.workspace = true
language.workspace = true
markdown.workspace = true
project.workspace = true
settings.workspace = true
smol.workspace = true
ui.workspace = true
util.workspace = true
workspace-hack.workspace = true
[dev-dependencies]
async-pipe.workspace = true
env_logger.workspace = true
gpui = { workspace = true, "features" = ["test-support"] }
indoc.workspace = true
project = { workspace = true, "features" = ["test-support"] }
serde_json.workspace = true
tempfile.workspace = true
util.workspace = true
settings.workspace = true

View file

@ -1 +0,0 @@
../../LICENSE-GPL

File diff suppressed because it is too large Load diff

View file

@ -448,7 +448,7 @@ impl ActivityIndicator {
.into_any_element(), .into_any_element(),
), ),
message: format!("Debug: {}", session.read(cx).adapter()), message: format!("Debug: {}", session.read(cx).adapter()),
tooltip_message: session.read(cx).label().map(|label| label.to_string()), tooltip_message: Some(session.read(cx).label().to_string()),
on_click: None, on_click: None,
}); });
} }

View file

@ -21,7 +21,6 @@ use gpui::{
AnyWindowHandle, App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, AnyWindowHandle, App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task,
WeakEntity, Window, WeakEntity, Window,
}; };
use http_client::StatusCode;
use language_model::{ use language_model::{
ConfiguredModel, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, ConfiguredModel, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
LanguageModelExt as _, LanguageModelId, LanguageModelRegistry, LanguageModelRequest, LanguageModelExt as _, LanguageModelId, LanguageModelRegistry, LanguageModelRequest,
@ -52,19 +51,7 @@ use uuid::Uuid;
use zed_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit}; use zed_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
const MAX_RETRY_ATTEMPTS: u8 = 3; const MAX_RETRY_ATTEMPTS: u8 = 3;
const BASE_RETRY_DELAY: Duration = Duration::from_secs(5); const BASE_RETRY_DELAY_SECS: u64 = 5;
#[derive(Debug, Clone)]
enum RetryStrategy {
ExponentialBackoff {
initial_delay: Duration,
max_attempts: u8,
},
Fixed {
delay: Duration,
max_attempts: u8,
},
}
#[derive( #[derive(
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, JsonSchema, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, JsonSchema,
@ -1297,7 +1284,6 @@ impl Thread {
tool_choice: None, tool_choice: None,
stop: Vec::new(), stop: Vec::new(),
temperature: AgentSettings::temperature_for_model(&model, cx), temperature: AgentSettings::temperature_for_model(&model, cx),
thinking_allowed: true,
}; };
let available_tools = self.available_tools(cx, model.clone()); let available_tools = self.available_tools(cx, model.clone());
@ -1463,7 +1449,6 @@ impl Thread {
tool_choice: None, tool_choice: None,
stop: Vec::new(), stop: Vec::new(),
temperature: AgentSettings::temperature_for_model(model, cx), temperature: AgentSettings::temperature_for_model(model, cx),
thinking_allowed: false,
}; };
for message in &self.messages { for message in &self.messages {
@ -1532,9 +1517,7 @@ impl Thread {
) -> Option<PendingToolUse> { ) -> Option<PendingToolUse> {
let action_log = self.action_log.read(cx); let action_log = self.action_log.read(cx);
if !action_log.has_unnotified_user_edits() { action_log.unnotified_stale_buffers(cx).next()?;
return None;
}
// Represent notification as a simulated `project_notifications` tool call // Represent notification as a simulated `project_notifications` tool call
let tool_name = Arc::from("project_notifications"); let tool_name = Arc::from("project_notifications");
@ -1948,6 +1931,18 @@ impl Thread {
project.set_agent_location(None, cx); project.set_agent_location(None, cx);
}); });
fn emit_generic_error(error: &anyhow::Error, cx: &mut Context<Thread>) {
let error_message = error
.chain()
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join("\n");
cx.emit(ThreadEvent::ShowError(ThreadError::Message {
header: "Error interacting with language model".into(),
message: SharedString::from(error_message.clone()),
}));
}
if error.is::<PaymentRequiredError>() { if error.is::<PaymentRequiredError>() {
cx.emit(ThreadEvent::ShowError(ThreadError::PaymentRequired)); cx.emit(ThreadEvent::ShowError(ThreadError::PaymentRequired));
} else if let Some(error) = } else if let Some(error) =
@ -1959,10 +1954,9 @@ impl Thread {
} else if let Some(completion_error) = } else if let Some(completion_error) =
error.downcast_ref::<LanguageModelCompletionError>() error.downcast_ref::<LanguageModelCompletionError>()
{ {
use LanguageModelCompletionError::*;
match &completion_error { match &completion_error {
LanguageModelCompletionError::PromptTooLarge { PromptTooLarge { tokens, .. } => {
tokens, ..
} => {
let tokens = tokens.unwrap_or_else(|| { let tokens = tokens.unwrap_or_else(|| {
// We didn't get an exact token count from the API, so fall back on our estimate. // We didn't get an exact token count from the API, so fall back on our estimate.
thread thread
@ -1983,22 +1977,63 @@ impl Thread {
}); });
cx.notify(); cx.notify();
} }
_ => { RateLimitExceeded {
if let Some(retry_strategy) = retry_after: Some(retry_after),
Thread::get_retry_strategy(completion_error) ..
{ }
retry_scheduled = thread | ServerOverloaded {
.handle_retryable_error_with_delay( retry_after: Some(retry_after),
&completion_error, ..
Some(retry_strategy), } => {
model.clone(), thread.handle_rate_limit_error(
intent, &completion_error,
window, *retry_after,
cx, model.clone(),
); intent,
window,
cx,
);
retry_scheduled = true;
}
RateLimitExceeded { .. } | ServerOverloaded { .. } => {
retry_scheduled = thread.handle_retryable_error(
&completion_error,
model.clone(),
intent,
window,
cx,
);
if !retry_scheduled {
emit_generic_error(error, cx);
} }
} }
ApiInternalServerError { .. }
| ApiReadResponseError { .. }
| HttpSend { .. } => {
retry_scheduled = thread.handle_retryable_error(
&completion_error,
model.clone(),
intent,
window,
cx,
);
if !retry_scheduled {
emit_generic_error(error, cx);
}
}
NoApiKey { .. }
| HttpResponseError { .. }
| BadRequestFormat { .. }
| AuthenticationError { .. }
| PermissionError { .. }
| ApiEndpointNotFound { .. }
| SerializeRequest { .. }
| BuildRequestBody { .. }
| DeserializeResponse { .. }
| Other { .. } => emit_generic_error(error, cx),
} }
} else {
emit_generic_error(error, cx);
} }
if !retry_scheduled { if !retry_scheduled {
@ -2125,86 +2160,73 @@ impl Thread {
}); });
} }
fn get_retry_strategy(error: &LanguageModelCompletionError) -> Option<RetryStrategy> { fn handle_rate_limit_error(
use LanguageModelCompletionError::*; &mut self,
error: &LanguageModelCompletionError,
// General strategy here: retry_after: Duration,
// - If retrying won't help (e.g. invalid API key or payload too large), return None so we don't retry at all. model: Arc<dyn LanguageModel>,
// - If it's a time-based issue (e.g. server overloaded, rate limit exceeded), try multiple times with exponential backoff. intent: CompletionIntent,
// - If it's an issue that *might* be fixed by retrying (e.g. internal server error), just retry once. window: Option<AnyWindowHandle>,
match error { cx: &mut Context<Self>,
HttpResponseError { ) {
status_code: StatusCode::TOO_MANY_REQUESTS, // For rate limit errors, we only retry once with the specified duration
.. let retry_message = format!("{error}. Retrying in {} seconds…", retry_after.as_secs());
} => Some(RetryStrategy::ExponentialBackoff { log::warn!(
initial_delay: BASE_RETRY_DELAY, "Retrying completion request in {} seconds: {error:?}",
max_attempts: MAX_RETRY_ATTEMPTS, retry_after.as_secs(),
}), );
ServerOverloaded { retry_after, .. } | RateLimitExceeded { retry_after, .. } => {
Some(RetryStrategy::Fixed { // Add a UI-only message instead of a regular message
delay: retry_after.unwrap_or(BASE_RETRY_DELAY), let id = self.next_message_id.post_inc();
max_attempts: MAX_RETRY_ATTEMPTS, self.messages.push(Message {
}) id,
} role: Role::System,
ApiInternalServerError { .. } => Some(RetryStrategy::Fixed { segments: vec![MessageSegment::Text(retry_message)],
delay: BASE_RETRY_DELAY, loaded_context: LoadedContext::default(),
max_attempts: 1, creases: Vec::new(),
}), is_hidden: false,
ApiReadResponseError { .. } ui_only: true,
| HttpSend { .. } });
| DeserializeResponse { .. } cx.emit(ThreadEvent::MessageAdded(id));
| BadRequestFormat { .. } => Some(RetryStrategy::Fixed { // Schedule the retry
delay: BASE_RETRY_DELAY, let thread_handle = cx.entity().downgrade();
max_attempts: 1,
}), cx.spawn(async move |_thread, cx| {
// Retrying these errors definitely shouldn't help. cx.background_executor().timer(retry_after).await;
HttpResponseError {
status_code: thread_handle
StatusCode::PAYLOAD_TOO_LARGE | StatusCode::FORBIDDEN | StatusCode::UNAUTHORIZED, .update(cx, |thread, cx| {
.. // Retry the completion
} thread.send_to_model(model, intent, window, cx);
| SerializeRequest { .. } })
| BuildRequestBody { .. } .log_err();
| PromptTooLarge { .. } })
| AuthenticationError { .. } .detach();
| PermissionError { .. } }
| ApiEndpointNotFound { .. }
| NoApiKey { .. } => None, fn handle_retryable_error(
// Retry all other 4xx and 5xx errors once.
HttpResponseError { status_code, .. }
if status_code.is_client_error() || status_code.is_server_error() =>
{
Some(RetryStrategy::Fixed {
delay: BASE_RETRY_DELAY,
max_attempts: 1,
})
}
// Conservatively assume that any other errors are non-retryable
HttpResponseError { .. } | Other(..) => None,
}
}
fn handle_retryable_error_with_delay(
&mut self, &mut self,
error: &LanguageModelCompletionError, error: &LanguageModelCompletionError,
strategy: Option<RetryStrategy>,
model: Arc<dyn LanguageModel>, model: Arc<dyn LanguageModel>,
intent: CompletionIntent, intent: CompletionIntent,
window: Option<AnyWindowHandle>, window: Option<AnyWindowHandle>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> bool { ) -> bool {
let Some(strategy) = strategy.or_else(|| Self::get_retry_strategy(error)) else { self.handle_retryable_error_with_delay(error, None, model, intent, window, cx)
return false; }
};
let max_attempts = match &strategy {
RetryStrategy::ExponentialBackoff { max_attempts, .. } => *max_attempts,
RetryStrategy::Fixed { max_attempts, .. } => *max_attempts,
};
fn handle_retryable_error_with_delay(
&mut self,
error: &LanguageModelCompletionError,
custom_delay: Option<Duration>,
model: Arc<dyn LanguageModel>,
intent: CompletionIntent,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) -> bool {
let retry_state = self.retry_state.get_or_insert(RetryState { let retry_state = self.retry_state.get_or_insert(RetryState {
attempt: 0, attempt: 0,
max_attempts, max_attempts: MAX_RETRY_ATTEMPTS,
intent, intent,
}); });
@ -2214,24 +2236,20 @@ impl Thread {
let intent = retry_state.intent; let intent = retry_state.intent;
if attempt <= max_attempts { if attempt <= max_attempts {
let delay = match &strategy { // Use custom delay if provided (e.g., from rate limit), otherwise exponential backoff
RetryStrategy::ExponentialBackoff { initial_delay, .. } => { let delay = if let Some(custom_delay) = custom_delay {
let delay_secs = initial_delay.as_secs() * 2u64.pow((attempt - 1) as u32); custom_delay
Duration::from_secs(delay_secs) } else {
} let delay_secs = BASE_RETRY_DELAY_SECS * 2u64.pow((attempt - 1) as u32);
RetryStrategy::Fixed { delay, .. } => *delay, Duration::from_secs(delay_secs)
}; };
// Add a transient message to inform the user // Add a transient message to inform the user
let delay_secs = delay.as_secs(); let delay_secs = delay.as_secs();
let retry_message = if max_attempts == 1 { let retry_message = format!(
format!("{error}. Retrying in {delay_secs} seconds...") "{error}. Retrying (attempt {attempt} of {max_attempts}) \
} else { in {delay_secs} seconds..."
format!( );
"{error}. Retrying (attempt {attempt} of {max_attempts}) \
in {delay_secs} seconds..."
)
};
log::warn!( log::warn!(
"Retrying completion request (attempt {attempt} of {max_attempts}) \ "Retrying completion request (attempt {attempt} of {max_attempts}) \
in {delay_secs} seconds: {error:?}", in {delay_secs} seconds: {error:?}",
@ -2270,9 +2288,19 @@ impl Thread {
// Max retries exceeded // Max retries exceeded
self.retry_state = None; self.retry_state = None;
let notification_text = if max_attempts == 1 {
"Failed after retrying.".into()
} else {
format!("Failed after retrying {} times.", max_attempts).into()
};
// Stop generating since we're giving up on retrying. // Stop generating since we're giving up on retrying.
self.pending_completions.clear(); self.pending_completions.clear();
cx.emit(ThreadEvent::RetriesFailed {
message: notification_text,
});
false false
} }
} }
@ -3228,6 +3256,9 @@ pub enum ThreadEvent {
CancelEditing, CancelEditing,
CompletionCanceled, CompletionCanceled,
ProfileChanged, ProfileChanged,
RetriesFailed {
message: SharedString,
},
} }
impl EventEmitter<ThreadEvent> for Thread {} impl EventEmitter<ThreadEvent> for Thread {}
@ -3255,6 +3286,7 @@ mod tests {
use futures::stream::BoxStream; use futures::stream::BoxStream;
use gpui::TestAppContext; use gpui::TestAppContext;
use http_client; use http_client;
use indoc::indoc;
use language_model::fake_provider::{FakeLanguageModel, FakeLanguageModelProvider}; use language_model::fake_provider::{FakeLanguageModel, FakeLanguageModelProvider};
use language_model::{ use language_model::{
LanguageModelCompletionError, LanguageModelName, LanguageModelProviderId, LanguageModelCompletionError, LanguageModelName, LanguageModelProviderId,
@ -3615,7 +3647,6 @@ fn main() {{
cx, cx,
); );
}); });
cx.run_until_parked();
// We shouldn't have a stale buffer notification yet // We shouldn't have a stale buffer notification yet
let notifications = thread.read_with(cx, |thread, _| { let notifications = thread.read_with(cx, |thread, _| {
@ -3645,13 +3676,11 @@ fn main() {{
cx, cx,
) )
}); });
cx.run_until_parked();
// Check for the stale buffer warning // Check for the stale buffer warning
thread.update(cx, |thread, cx| { thread.update(cx, |thread, cx| {
thread.flush_notifications(model.clone(), CompletionIntent::UserPrompt, cx) thread.flush_notifications(model.clone(), CompletionIntent::UserPrompt, cx)
}); });
cx.run_until_parked();
let notifications = thread.read_with(cx, |thread, _cx| { let notifications = thread.read_with(cx, |thread, _cx| {
find_tool_uses(thread, "project_notifications") find_tool_uses(thread, "project_notifications")
@ -3665,8 +3694,12 @@ fn main() {{
panic!("`project_notifications` should return text"); panic!("`project_notifications` should return text");
}; };
assert!(notification_content.contains("These files have changed since the last read:")); let expected_content = indoc! {"[The following is an auto-generated notification; do not reply]
assert!(notification_content.contains("code.rs"));
These files have changed since the last read:
- code.rs
"};
assert_eq!(notification_content, expected_content);
// Insert another user message and flush notifications again // Insert another user message and flush notifications again
thread.update(cx, |thread, cx| { thread.update(cx, |thread, cx| {
@ -3682,7 +3715,6 @@ fn main() {{
thread.update(cx, |thread, cx| { thread.update(cx, |thread, cx| {
thread.flush_notifications(model.clone(), CompletionIntent::UserPrompt, cx) thread.flush_notifications(model.clone(), CompletionIntent::UserPrompt, cx)
}); });
cx.run_until_parked();
// There should be no new notifications (we already flushed one) // There should be no new notifications (we already flushed one)
let notifications = thread.read_with(cx, |thread, _cx| { let notifications = thread.read_with(cx, |thread, _cx| {
@ -4158,7 +4190,7 @@ fn main() {{
assert_eq!(retry_state.attempt, 1, "Should be first retry attempt"); assert_eq!(retry_state.attempt, 1, "Should be first retry attempt");
assert_eq!( assert_eq!(
retry_state.max_attempts, MAX_RETRY_ATTEMPTS, retry_state.max_attempts, MAX_RETRY_ATTEMPTS,
"Should retry MAX_RETRY_ATTEMPTS times for overloaded errors" "Should have default max attempts"
); );
}); });
@ -4231,7 +4263,7 @@ fn main() {{
let retry_state = thread.retry_state.as_ref().unwrap(); let retry_state = thread.retry_state.as_ref().unwrap();
assert_eq!(retry_state.attempt, 1, "Should be first retry attempt"); assert_eq!(retry_state.attempt, 1, "Should be first retry attempt");
assert_eq!( assert_eq!(
retry_state.max_attempts, 1, retry_state.max_attempts, MAX_RETRY_ATTEMPTS,
"Should have correct max attempts" "Should have correct max attempts"
); );
}); });
@ -4247,8 +4279,8 @@ fn main() {{
if let MessageSegment::Text(text) = seg { if let MessageSegment::Text(text) = seg {
text.contains("internal") text.contains("internal")
&& text.contains("Fake") && text.contains("Fake")
&& text.contains("Retrying in") && text
&& !text.contains("attempt") .contains(&format!("attempt 1 of {}", MAX_RETRY_ATTEMPTS))
} else { } else {
false false
} }
@ -4286,8 +4318,8 @@ fn main() {{
let project = create_test_project(cx, json!({})).await; let project = create_test_project(cx, json!({})).await;
let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await; let (_, _, thread, _, _base_model) = setup_test_environment(cx, project.clone()).await;
// Create model that returns internal server error // Create model that returns overloaded error
let model = Arc::new(ErrorInjector::new(TestError::InternalServerError)); let model = Arc::new(ErrorInjector::new(TestError::Overloaded));
// Insert a user message // Insert a user message
thread.update(cx, |thread, cx| { thread.update(cx, |thread, cx| {
@ -4337,14 +4369,11 @@ fn main() {{
assert!(thread.retry_state.is_some(), "Should have retry state"); assert!(thread.retry_state.is_some(), "Should have retry state");
let retry_state = thread.retry_state.as_ref().unwrap(); let retry_state = thread.retry_state.as_ref().unwrap();
assert_eq!(retry_state.attempt, 1, "Should be first retry attempt"); assert_eq!(retry_state.attempt, 1, "Should be first retry attempt");
assert_eq!(
retry_state.max_attempts, 1,
"Internal server errors should only retry once"
);
}); });
// Advance clock for first retry // Advance clock for first retry
cx.executor().advance_clock(BASE_RETRY_DELAY); cx.executor()
.advance_clock(Duration::from_secs(BASE_RETRY_DELAY_SECS));
cx.run_until_parked(); cx.run_until_parked();
// Should have scheduled second retry - count retry messages // Should have scheduled second retry - count retry messages
@ -4364,25 +4393,93 @@ fn main() {{
}) })
.count() .count()
}); });
assert_eq!( assert_eq!(retry_count, 2, "Should have scheduled second retry");
retry_count, 1,
"Should have only one retry for internal server errors"
);
// For internal server errors, we only retry once and then give up // Check retry state updated
// Check that retry_state is cleared after the single retry
thread.read_with(cx, |thread, _| { thread.read_with(cx, |thread, _| {
assert!( assert!(thread.retry_state.is_some(), "Should have retry state");
thread.retry_state.is_none(), let retry_state = thread.retry_state.as_ref().unwrap();
"Retry state should be cleared after single retry" assert_eq!(retry_state.attempt, 2, "Should be second retry attempt");
assert_eq!(
retry_state.max_attempts, MAX_RETRY_ATTEMPTS,
"Should have correct max attempts"
); );
}); });
// Verify total attempts (1 initial + 1 retry) // Advance clock for second retry (exponential backoff)
cx.executor()
.advance_clock(Duration::from_secs(BASE_RETRY_DELAY_SECS * 2));
cx.run_until_parked();
// Should have scheduled third retry
// Count all retry messages now
let retry_count = thread.update(cx, |thread, _| {
thread
.messages
.iter()
.filter(|m| {
m.ui_only
&& m.segments.iter().any(|s| {
if let MessageSegment::Text(text) = s {
text.contains("Retrying") && text.contains("seconds")
} else {
false
}
})
})
.count()
});
assert_eq!(
retry_count, MAX_RETRY_ATTEMPTS as usize,
"Should have scheduled third retry"
);
// Check retry state updated
thread.read_with(cx, |thread, _| {
assert!(thread.retry_state.is_some(), "Should have retry state");
let retry_state = thread.retry_state.as_ref().unwrap();
assert_eq!(
retry_state.attempt, MAX_RETRY_ATTEMPTS,
"Should be at max retry attempt"
);
assert_eq!(
retry_state.max_attempts, MAX_RETRY_ATTEMPTS,
"Should have correct max attempts"
);
});
// Advance clock for third retry (exponential backoff)
cx.executor()
.advance_clock(Duration::from_secs(BASE_RETRY_DELAY_SECS * 4));
cx.run_until_parked();
// No more retries should be scheduled after clock was advanced.
let retry_count = thread.update(cx, |thread, _| {
thread
.messages
.iter()
.filter(|m| {
m.ui_only
&& m.segments.iter().any(|s| {
if let MessageSegment::Text(text) = s {
text.contains("Retrying") && text.contains("seconds")
} else {
false
}
})
})
.count()
});
assert_eq!(
retry_count, MAX_RETRY_ATTEMPTS as usize,
"Should not exceed max retries"
);
// Final completion count should be initial + max retries
assert_eq!( assert_eq!(
*completion_count.lock(), *completion_count.lock(),
2, (MAX_RETRY_ATTEMPTS + 1) as usize,
"Should have attempted once plus 1 retry" "Should have made initial + max retry attempts"
); );
} }
@ -4402,13 +4499,13 @@ fn main() {{
}); });
// Track events // Track events
let stopped_with_error = Arc::new(Mutex::new(false)); let retries_failed = Arc::new(Mutex::new(false));
let stopped_with_error_clone = stopped_with_error.clone(); let retries_failed_clone = retries_failed.clone();
let _subscription = thread.update(cx, |_, cx| { let _subscription = thread.update(cx, |_, cx| {
cx.subscribe(&thread, move |_, _, event: &ThreadEvent, _| { cx.subscribe(&thread, move |_, _, event: &ThreadEvent, _| {
if let ThreadEvent::Stopped(Err(_)) = event { if let ThreadEvent::RetriesFailed { .. } = event {
*stopped_with_error_clone.lock() = true; *retries_failed_clone.lock() = true;
} }
}) })
}); });
@ -4420,11 +4517,23 @@ fn main() {{
cx.run_until_parked(); cx.run_until_parked();
// Advance through all retries // Advance through all retries
for _ in 0..MAX_RETRY_ATTEMPTS { for i in 0..MAX_RETRY_ATTEMPTS {
cx.executor().advance_clock(BASE_RETRY_DELAY); let delay = if i == 0 {
BASE_RETRY_DELAY_SECS
} else {
BASE_RETRY_DELAY_SECS * 2u64.pow(i as u32 - 1)
};
cx.executor().advance_clock(Duration::from_secs(delay));
cx.run_until_parked(); cx.run_until_parked();
} }
// After the 3rd retry is scheduled, we need to wait for it to execute and fail
// The 3rd retry has a delay of BASE_RETRY_DELAY_SECS * 4 (20 seconds)
let final_delay = BASE_RETRY_DELAY_SECS * 2u64.pow((MAX_RETRY_ATTEMPTS - 1) as u32);
cx.executor()
.advance_clock(Duration::from_secs(final_delay));
cx.run_until_parked();
let retry_count = thread.update(cx, |thread, _| { let retry_count = thread.update(cx, |thread, _| {
thread thread
.messages .messages
@ -4442,14 +4551,14 @@ fn main() {{
.count() .count()
}); });
// After max retries, should emit Stopped(Err(...)) event // After max retries, should emit RetriesFailed event
assert_eq!( assert_eq!(
retry_count, MAX_RETRY_ATTEMPTS as usize, retry_count, MAX_RETRY_ATTEMPTS as usize,
"Should have attempted MAX_RETRY_ATTEMPTS retries for overloaded errors" "Should have attempted max retries"
); );
assert!( assert!(
*stopped_with_error.lock(), *retries_failed.lock(),
"Should emit Stopped(Err(...)) event after max retries exceeded" "Should emit RetriesFailed event after max retries exceeded"
); );
// Retry state should be cleared // Retry state should be cleared
@ -4467,7 +4576,7 @@ fn main() {{
.count(); .count();
assert_eq!( assert_eq!(
retry_messages, MAX_RETRY_ATTEMPTS as usize, retry_messages, MAX_RETRY_ATTEMPTS as usize,
"Should have MAX_RETRY_ATTEMPTS retry messages for overloaded errors" "Should have one retry message per attempt"
); );
}); });
} }
@ -4605,7 +4714,8 @@ fn main() {{
}); });
// Wait for retry // Wait for retry
cx.executor().advance_clock(BASE_RETRY_DELAY); cx.executor()
.advance_clock(Duration::from_secs(BASE_RETRY_DELAY_SECS));
cx.run_until_parked(); cx.run_until_parked();
// Stream some successful content // Stream some successful content
@ -4767,7 +4877,8 @@ fn main() {{
}); });
// Wait for retry delay // Wait for retry delay
cx.executor().advance_clock(BASE_RETRY_DELAY); cx.executor()
.advance_clock(Duration::from_secs(BASE_RETRY_DELAY_SECS));
cx.run_until_parked(); cx.run_until_parked();
// The retry should now use our FailOnceModel which should succeed // The retry should now use our FailOnceModel which should succeed
@ -4926,15 +5037,9 @@ fn main() {{
thread.read_with(cx, |thread, _| { thread.read_with(cx, |thread, _| {
assert!( assert!(
thread.retry_state.is_some(), thread.retry_state.is_none(),
"Rate limit errors should set retry_state" "Rate limit errors should not set retry_state"
); );
if let Some(retry_state) = &thread.retry_state {
assert_eq!(
retry_state.max_attempts, MAX_RETRY_ATTEMPTS,
"Rate limit errors should use MAX_RETRY_ATTEMPTS"
);
}
}); });
// Verify we have one retry message // Verify we have one retry message
@ -4967,15 +5072,18 @@ fn main() {{
.find(|msg| msg.role == Role::System && msg.ui_only) .find(|msg| msg.role == Role::System && msg.ui_only)
.expect("Should have a retry message"); .expect("Should have a retry message");
// Check that the message contains attempt count since we use retry_state // Check that the message doesn't contain attempt count
if let Some(MessageSegment::Text(text)) = retry_message.segments.first() { if let Some(MessageSegment::Text(text)) = retry_message.segments.first() {
assert!( assert!(
text.contains(&format!("attempt 1 of {}", MAX_RETRY_ATTEMPTS)), !text.contains("attempt"),
"Rate limit retry message should contain attempt count with MAX_RETRY_ATTEMPTS" "Rate limit retry message should not contain attempt count"
); );
assert!( assert!(
text.contains("Retrying"), text.contains(&format!(
"Rate limit retry message should contain retry text" "Retrying in {} seconds",
TEST_RATE_LIMIT_RETRY_SECS
)),
"Rate limit retry message should contain retry delay"
); );
} }
}); });

View file

@ -1,27 +0,0 @@
[package]
name = "agent_servers"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/agent_servers.rs"
doctest = false
[dependencies]
anyhow.workspace = true
collections.workspace = true
futures.workspace = true
gpui.workspace = true
paths.workspace = true
project.workspace = true
schemars.workspace = true
serde.workspace = true
settings.workspace = true
util.workspace = true
which.workspace = true
workspace-hack.workspace = true

View file

@ -1 +0,0 @@
../../LICENSE-GPL

View file

@ -1,231 +0,0 @@
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use anyhow::{Context as _, Result};
use collections::HashMap;
use gpui::{App, AsyncApp, Entity, SharedString};
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsStore};
use util::{ResultExt, paths};
pub fn init(cx: &mut App) {
AllAgentServersSettings::register(cx);
}
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug)]
pub struct AllAgentServersSettings {
gemini: Option<AgentServerSettings>,
}
#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug)]
pub struct AgentServerSettings {
#[serde(flatten)]
command: AgentServerCommand,
}
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)]
pub struct AgentServerCommand {
#[serde(rename = "command")]
pub path: PathBuf,
#[serde(default)]
pub args: Vec<String>,
pub env: Option<HashMap<String, String>>,
}
pub struct Gemini;
pub struct AgentServerVersion {
pub current_version: SharedString,
pub supported: bool,
}
pub trait AgentServer: Send {
fn command(
&self,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> impl Future<Output = Result<AgentServerCommand>>;
fn version(
&self,
command: &AgentServerCommand,
) -> impl Future<Output = Result<AgentServerVersion>> + Send;
}
const GEMINI_ACP_ARG: &str = "--experimental-acp";
impl AgentServer for Gemini {
async fn command(
&self,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> Result<AgentServerCommand> {
let custom_command = cx.read_global(|settings: &SettingsStore, _| {
let settings = settings.get::<AllAgentServersSettings>(None);
settings
.gemini
.as_ref()
.map(|gemini_settings| AgentServerCommand {
path: gemini_settings.command.path.clone(),
args: gemini_settings
.command
.args
.iter()
.cloned()
.chain(std::iter::once(GEMINI_ACP_ARG.into()))
.collect(),
env: gemini_settings.command.env.clone(),
})
})?;
if let Some(custom_command) = custom_command {
return Ok(custom_command);
}
if let Some(path) = find_bin_in_path("gemini", project, cx).await {
return Ok(AgentServerCommand {
path,
args: vec![GEMINI_ACP_ARG.into()],
env: None,
});
}
let (fs, node_runtime) = project.update(cx, |project, _| {
(project.fs().clone(), project.node_runtime().cloned())
})?;
let node_runtime = node_runtime.context("gemini not found on path")?;
let directory = ::paths::agent_servers_dir().join("gemini");
fs.create_dir(&directory).await?;
node_runtime
.npm_install_packages(&directory, &[("@google/gemini-cli", "latest")])
.await?;
let path = directory.join("node_modules/.bin/gemini");
Ok(AgentServerCommand {
path,
args: vec![GEMINI_ACP_ARG.into()],
env: None,
})
}
async fn version(&self, command: &AgentServerCommand) -> Result<AgentServerVersion> {
let version_fut = util::command::new_smol_command(&command.path)
.args(command.args.iter())
.arg("--version")
.kill_on_drop(true)
.output();
let help_fut = util::command::new_smol_command(&command.path)
.args(command.args.iter())
.arg("--help")
.kill_on_drop(true)
.output();
let (version_output, help_output) = futures::future::join(version_fut, help_fut).await;
let current_version = String::from_utf8(version_output?.stdout)?.into();
let supported = String::from_utf8(help_output?.stdout)?.contains(GEMINI_ACP_ARG);
Ok(AgentServerVersion {
current_version,
supported,
})
}
}
async fn find_bin_in_path(
bin_name: &'static str,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> Option<PathBuf> {
let (env_task, root_dir) = project
.update(cx, |project, cx| {
let worktree = project.visible_worktrees(cx).next();
match worktree {
Some(worktree) => {
let env_task = project.environment().update(cx, |env, cx| {
env.get_worktree_environment(worktree.clone(), cx)
});
let path = worktree.read(cx).abs_path();
(env_task, path)
}
None => {
let path: Arc<Path> = paths::home_dir().as_path().into();
let env_task = project.environment().update(cx, |env, cx| {
env.get_directory_environment(path.clone(), cx)
});
(env_task, path)
}
}
})
.log_err()?;
cx.background_executor()
.spawn(async move {
let which_result = if cfg!(windows) {
which::which(bin_name)
} else {
let env = env_task.await.unwrap_or_default();
let shell_path = env.get("PATH").cloned();
which::which_in(bin_name, shell_path.as_ref(), root_dir.as_ref())
};
if let Err(which::Error::CannotFindBinaryPath) = which_result {
return None;
}
which_result.log_err()
})
.await
}
impl std::fmt::Debug for AgentServerCommand {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let filtered_env = self.env.as_ref().map(|env| {
env.iter()
.map(|(k, v)| {
(
k,
if util::redact::should_redact(k) {
"[REDACTED]"
} else {
v
},
)
})
.collect::<Vec<_>>()
});
f.debug_struct("AgentServerCommand")
.field("path", &self.path)
.field("args", &self.args)
.field("env", &filtered_env)
.finish()
}
}
impl settings::Settings for AllAgentServersSettings {
const KEY: Option<&'static str> = Some("agent_servers");
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
let mut settings = AllAgentServersSettings::default();
for value in sources.defaults_and_customizations() {
if value.gemini.is_some() {
settings.gemini = value.gemini.clone();
}
}
Ok(settings)
}
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
}

View file

@ -13,14 +13,14 @@ path = "src/agent_ui.rs"
doctest = false doctest = false
[features] [features]
test-support = ["gpui/test-support", "language/test-support"] test-support = [
"gpui/test-support",
"language/test-support",
]
[dependencies] [dependencies]
acp.workspace = true
agent.workspace = true agent.workspace = true
agentic-coding-protocol.workspace = true
agent_settings.workspace = true agent_settings.workspace = true
agent_servers.workspace = true
anyhow.workspace = true anyhow.workspace = true
assistant_context.workspace = true assistant_context.workspace = true
assistant_slash_command.workspace = true assistant_slash_command.workspace = true
@ -76,7 +76,6 @@ serde_json_lenient.workspace = true
settings.workspace = true settings.workspace = true
smol.workspace = true smol.workspace = true
streaming_diff.workspace = true streaming_diff.workspace = true
task.workspace = true
telemetry.workspace = true telemetry.workspace = true
telemetry_events.workspace = true telemetry_events.workspace = true
terminal.workspace = true terminal.workspace = true

View file

@ -1,6 +0,0 @@
mod completion_provider;
mod message_history;
mod thread_view;
pub use message_history::MessageHistory;
pub use thread_view::AcpThreadView;

View file

@ -1,574 +0,0 @@
use std::ops::Range;
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use anyhow::Result;
use collections::HashMap;
use editor::display_map::CreaseId;
use editor::{CompletionProvider, Editor, ExcerptId};
use file_icons::FileIcons;
use gpui::{App, Entity, Task, WeakEntity};
use language::{Buffer, CodeLabel, HighlightId};
use lsp::CompletionContext;
use parking_lot::Mutex;
use project::{Completion, CompletionIntent, CompletionResponse, ProjectPath, WorktreeId};
use rope::Point;
use text::{Anchor, ToPoint};
use ui::prelude::*;
use workspace::Workspace;
use crate::context_picker::MentionLink;
use crate::context_picker::file_context_picker::{extract_file_name_and_directory, search_files};
#[derive(Default)]
pub struct MentionSet {
paths_by_crease_id: HashMap<CreaseId, ProjectPath>,
}
impl MentionSet {
pub fn insert(&mut self, crease_id: CreaseId, path: ProjectPath) {
self.paths_by_crease_id.insert(crease_id, path);
}
pub fn path_for_crease_id(&self, crease_id: CreaseId) -> Option<ProjectPath> {
self.paths_by_crease_id.get(&crease_id).cloned()
}
pub fn drain(&mut self) -> impl Iterator<Item = CreaseId> {
self.paths_by_crease_id.drain().map(|(id, _)| id)
}
}
pub struct ContextPickerCompletionProvider {
workspace: WeakEntity<Workspace>,
editor: WeakEntity<Editor>,
mention_set: Arc<Mutex<MentionSet>>,
}
impl ContextPickerCompletionProvider {
pub fn new(
mention_set: Arc<Mutex<MentionSet>>,
workspace: WeakEntity<Workspace>,
editor: WeakEntity<Editor>,
) -> Self {
Self {
mention_set,
workspace,
editor,
}
}
fn completion_for_path(
project_path: ProjectPath,
path_prefix: &str,
is_recent: bool,
is_directory: bool,
excerpt_id: ExcerptId,
source_range: Range<Anchor>,
editor: Entity<Editor>,
mention_set: Arc<Mutex<MentionSet>>,
cx: &App,
) -> Completion {
let (file_name, directory) =
extract_file_name_and_directory(&project_path.path, path_prefix);
let label =
build_code_label_for_full_path(&file_name, directory.as_ref().map(|s| s.as_ref()), cx);
let full_path = if let Some(directory) = directory {
format!("{}{}", directory, file_name)
} else {
file_name.to_string()
};
let crease_icon_path = if is_directory {
FileIcons::get_folder_icon(false, cx).unwrap_or_else(|| IconName::Folder.path().into())
} else {
FileIcons::get_icon(Path::new(&full_path), cx)
.unwrap_or_else(|| IconName::File.path().into())
};
let completion_icon_path = if is_recent {
IconName::HistoryRerun.path().into()
} else {
crease_icon_path.clone()
};
let new_text = format!("{} ", MentionLink::for_file(&file_name, &full_path));
let new_text_len = new_text.len();
Completion {
replace_range: source_range.clone(),
new_text,
label,
documentation: None,
source: project::CompletionSource::Custom,
icon_path: Some(completion_icon_path),
insert_text_mode: None,
confirm: Some(confirm_completion_callback(
crease_icon_path,
file_name,
project_path,
excerpt_id,
source_range.start,
new_text_len - 1,
editor,
mention_set,
)),
}
}
}
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
let mut label = CodeLabel::default();
label.push_str(&file_name, None);
label.push_str(" ", None);
if let Some(directory) = directory {
label.push_str(&directory, comment_id);
}
label.filter_range = 0..label.text().len();
label
}
impl CompletionProvider for ContextPickerCompletionProvider {
fn completions(
&self,
excerpt_id: ExcerptId,
buffer: &Entity<Buffer>,
buffer_position: Anchor,
_trigger: CompletionContext,
_window: &mut Window,
cx: &mut Context<Editor>,
) -> Task<Result<Vec<CompletionResponse>>> {
let state = buffer.update(cx, |buffer, _cx| {
let position = buffer_position.to_point(buffer);
let line_start = Point::new(position.row, 0);
let offset_to_line = buffer.point_to_offset(line_start);
let mut lines = buffer.text_for_range(line_start..position).lines();
let line = lines.next()?;
MentionCompletion::try_parse(line, offset_to_line)
});
let Some(state) = state else {
return Task::ready(Ok(Vec::new()));
};
let Some(workspace) = self.workspace.upgrade() else {
return Task::ready(Ok(Vec::new()));
};
let snapshot = buffer.read(cx).snapshot();
let source_range = snapshot.anchor_before(state.source_range.start)
..snapshot.anchor_after(state.source_range.end);
let editor = self.editor.clone();
let mention_set = self.mention_set.clone();
let MentionCompletion { argument, .. } = state;
let query = argument.unwrap_or_else(|| "".to_string());
let search_task = search_files(query.clone(), Arc::<AtomicBool>::default(), &workspace, cx);
cx.spawn(async move |_, cx| {
let matches = search_task.await;
let Some(editor) = editor.upgrade() else {
return Ok(Vec::new());
};
let completions = cx.update(|cx| {
matches
.into_iter()
.map(|mat| {
let path_match = &mat.mat;
let project_path = ProjectPath {
worktree_id: WorktreeId::from_usize(path_match.worktree_id),
path: path_match.path.clone(),
};
Self::completion_for_path(
project_path,
&path_match.path_prefix,
mat.is_recent,
path_match.is_dir,
excerpt_id,
source_range.clone(),
editor.clone(),
mention_set.clone(),
cx,
)
})
.collect()
})?;
Ok(vec![CompletionResponse {
completions,
// Since this does its own filtering (see `filter_completions()` returns false),
// there is no benefit to computing whether this set of completions is incomplete.
is_incomplete: true,
}])
})
}
fn is_completion_trigger(
&self,
buffer: &Entity<language::Buffer>,
position: language::Anchor,
_text: &str,
_trigger_in_words: bool,
_menu_is_open: bool,
cx: &mut Context<Editor>,
) -> bool {
let buffer = buffer.read(cx);
let position = position.to_point(buffer);
let line_start = Point::new(position.row, 0);
let offset_to_line = buffer.point_to_offset(line_start);
let mut lines = buffer.text_for_range(line_start..position).lines();
if let Some(line) = lines.next() {
MentionCompletion::try_parse(line, offset_to_line)
.map(|completion| {
completion.source_range.start <= offset_to_line + position.column as usize
&& completion.source_range.end >= offset_to_line + position.column as usize
})
.unwrap_or(false)
} else {
false
}
}
fn sort_completions(&self) -> bool {
false
}
fn filter_completions(&self) -> bool {
false
}
}
fn confirm_completion_callback(
crease_icon_path: SharedString,
crease_text: SharedString,
project_path: ProjectPath,
excerpt_id: ExcerptId,
start: Anchor,
content_len: usize,
editor: Entity<Editor>,
mention_set: Arc<Mutex<MentionSet>>,
) -> Arc<dyn Fn(CompletionIntent, &mut Window, &mut App) -> bool + Send + Sync> {
Arc::new(move |_, window, cx| {
let crease_text = crease_text.clone();
let crease_icon_path = crease_icon_path.clone();
let editor = editor.clone();
let project_path = project_path.clone();
let mention_set = mention_set.clone();
window.defer(cx, move |window, cx| {
let crease_id = crate::context_picker::insert_crease_for_mention(
excerpt_id,
start,
content_len,
crease_text.clone(),
crease_icon_path,
editor.clone(),
window,
cx,
);
if let Some(crease_id) = crease_id {
mention_set.lock().insert(crease_id, project_path);
}
});
false
})
}
#[derive(Debug, Default, PartialEq)]
struct MentionCompletion {
source_range: Range<usize>,
argument: Option<String>,
}
impl MentionCompletion {
fn try_parse(line: &str, offset_to_line: usize) -> Option<Self> {
let last_mention_start = line.rfind('@')?;
if last_mention_start >= line.len() {
return Some(Self::default());
}
if last_mention_start > 0
&& line
.chars()
.nth(last_mention_start - 1)
.map_or(false, |c| !c.is_whitespace())
{
return None;
}
let rest_of_line = &line[last_mention_start + 1..];
let mut argument = None;
let mut parts = rest_of_line.split_whitespace();
let mut end = last_mention_start + 1;
if let Some(argument_text) = parts.next() {
end += argument_text.len();
argument = Some(argument_text.to_string());
}
Some(Self {
source_range: last_mention_start + offset_to_line..end + offset_to_line,
argument,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use gpui::{EventEmitter, FocusHandle, Focusable, TestAppContext, VisualTestContext};
use project::{Project, ProjectPath};
use serde_json::json;
use settings::SettingsStore;
use std::{ops::Deref, rc::Rc};
use util::path;
use workspace::{AppState, Item};
#[test]
fn test_mention_completion_parse() {
assert_eq!(MentionCompletion::try_parse("Lorem Ipsum", 0), None);
assert_eq!(
MentionCompletion::try_parse("Lorem @", 0),
Some(MentionCompletion {
source_range: 6..7,
argument: None,
})
);
assert_eq!(
MentionCompletion::try_parse("Lorem @main", 0),
Some(MentionCompletion {
source_range: 6..11,
argument: Some("main".to_string()),
})
);
assert_eq!(MentionCompletion::try_parse("test@", 0), None);
}
struct AtMentionEditor(Entity<Editor>);
impl Item for AtMentionEditor {
type Event = ();
fn include_in_nav_history() -> bool {
false
}
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
"Test".into()
}
}
impl EventEmitter<()> for AtMentionEditor {}
impl Focusable for AtMentionEditor {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.0.read(cx).focus_handle(cx).clone()
}
}
impl Render for AtMentionEditor {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
self.0.clone().into_any_element()
}
}
#[gpui::test]
async fn test_context_completion_provider(cx: &mut TestAppContext) {
init_test(cx);
let app_state = cx.update(AppState::test);
cx.update(|cx| {
language::init(cx);
editor::init(cx);
workspace::init(app_state.clone(), cx);
Project::init_settings(cx);
});
app_state
.fs
.as_fake()
.insert_tree(
path!("/dir"),
json!({
"editor": "",
"a": {
"one.txt": "",
"two.txt": "",
"three.txt": "",
"four.txt": ""
},
"b": {
"five.txt": "",
"six.txt": "",
"seven.txt": "",
"eight.txt": "",
}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), [path!("/dir").as_ref()], cx).await;
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
let workspace = window.root(cx).unwrap();
let worktree = project.update(cx, |project, cx| {
let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
assert_eq!(worktrees.len(), 1);
worktrees.pop().unwrap()
});
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
let paths = vec![
path!("a/one.txt"),
path!("a/two.txt"),
path!("a/three.txt"),
path!("a/four.txt"),
path!("b/five.txt"),
path!("b/six.txt"),
path!("b/seven.txt"),
path!("b/eight.txt"),
];
let mut opened_editors = Vec::new();
for path in paths {
let buffer = workspace
.update_in(&mut cx, |workspace, window, cx| {
workspace.open_path(
ProjectPath {
worktree_id,
path: Path::new(path).into(),
},
None,
false,
window,
cx,
)
})
.await
.unwrap();
opened_editors.push(buffer);
}
let editor = workspace.update_in(&mut cx, |workspace, window, cx| {
let editor = cx.new(|cx| {
Editor::new(
editor::EditorMode::full(),
multi_buffer::MultiBuffer::build_simple("", cx),
None,
window,
cx,
)
});
workspace.active_pane().update(cx, |pane, cx| {
pane.add_item(
Box::new(cx.new(|_| AtMentionEditor(editor.clone()))),
true,
true,
None,
window,
cx,
);
});
editor
});
let mention_set = Arc::new(Mutex::new(MentionSet::default()));
let editor_entity = editor.downgrade();
editor.update_in(&mut cx, |editor, window, cx| {
window.focus(&editor.focus_handle(cx));
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
mention_set.clone(),
workspace.downgrade(),
editor_entity,
))));
});
cx.simulate_input("Lorem ");
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "Lorem ");
assert!(!editor.has_visible_completions_menu());
});
cx.simulate_input("@");
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "Lorem @");
assert!(editor.has_visible_completions_menu());
assert_eq!(
current_completion_labels(editor),
&[
"eight.txt dir/b/",
"seven.txt dir/b/",
"six.txt dir/b/",
"five.txt dir/b/",
"four.txt dir/a/",
"three.txt dir/a/",
"two.txt dir/a/",
"one.txt dir/a/",
"dir ",
"a dir/",
"four.txt dir/a/",
"one.txt dir/a/",
"three.txt dir/a/",
"two.txt dir/a/",
"b dir/",
"eight.txt dir/b/",
"five.txt dir/b/",
"seven.txt dir/b/",
"six.txt dir/b/",
"editor dir/"
]
);
});
// Select and confirm "File"
editor.update_in(&mut cx, |editor, window, cx| {
assert!(editor.has_visible_completions_menu());
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
});
cx.run_until_parked();
editor.update(&mut cx, |editor, cx| {
assert_eq!(editor.text(cx), "Lorem [@four.txt](@file:dir/a/four.txt) ");
});
}
fn current_completion_labels(editor: &Editor) -> Vec<String> {
let completions = editor.current_completions().expect("Missing completions");
completions
.into_iter()
.map(|completion| completion.label.text.to_string())
.collect::<Vec<_>>()
}
pub(crate) fn init_test(cx: &mut TestAppContext) {
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
theme::init(theme::LoadThemes::JustBase, cx);
client::init_settings(cx);
language::init(cx);
Project::init_settings(cx);
workspace::init_settings(cx);
editor::init_settings(cx);
});
}
}

View file

@ -1,87 +0,0 @@
pub struct MessageHistory<T> {
items: Vec<T>,
current: Option<usize>,
}
impl<T> Default for MessageHistory<T> {
fn default() -> Self {
MessageHistory {
items: Vec::new(),
current: None,
}
}
}
impl<T> MessageHistory<T> {
pub fn push(&mut self, message: T) {
self.current.take();
self.items.push(message);
}
pub fn reset_position(&mut self) {
self.current.take();
}
pub fn prev(&mut self) -> Option<&T> {
if self.items.is_empty() {
return None;
}
let new_ix = self
.current
.get_or_insert(self.items.len())
.saturating_sub(1);
self.current = Some(new_ix);
self.items.get(new_ix)
}
pub fn next(&mut self) -> Option<&T> {
let current = self.current.as_mut()?;
*current += 1;
self.items.get(*current).or_else(|| {
self.current.take();
None
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prev_next() {
let mut history = MessageHistory::default();
// Test empty history
assert_eq!(history.prev(), None);
assert_eq!(history.next(), None);
// Add some messages
history.push("first");
history.push("second");
history.push("third");
// Test prev navigation
assert_eq!(history.prev(), Some(&"third"));
assert_eq!(history.prev(), Some(&"second"));
assert_eq!(history.prev(), Some(&"first"));
assert_eq!(history.prev(), Some(&"first"));
assert_eq!(history.next(), Some(&"second"));
// Test mixed navigation
history.push("fourth");
assert_eq!(history.prev(), Some(&"fourth"));
assert_eq!(history.prev(), Some(&"third"));
assert_eq!(history.next(), Some(&"fourth"));
assert_eq!(history.next(), None);
// Test that push resets navigation
history.prev();
history.prev();
history.push("fifth");
assert_eq!(history.prev(), Some(&"fifth"));
}
}

File diff suppressed because it is too large Load diff

View file

@ -787,15 +787,6 @@ impl ActiveThread {
.unwrap() .unwrap()
} }
}); });
let workspace_subscription = if let Some(workspace) = workspace.upgrade() {
Some(cx.observe_release(&workspace, |this, _, cx| {
this.dismiss_notifications(cx);
}))
} else {
None
};
let mut this = Self { let mut this = Self {
language_registry, language_registry,
thread_store, thread_store,
@ -843,10 +834,6 @@ impl ActiveThread {
} }
} }
if let Some(subscription) = workspace_subscription {
this._subscriptions.push(subscription);
}
this this
} }
@ -996,57 +983,30 @@ impl ActiveThread {
| ThreadEvent::SummaryChanged => { | ThreadEvent::SummaryChanged => {
self.save_thread(cx); self.save_thread(cx);
} }
ThreadEvent::Stopped(reason) => { ThreadEvent::Stopped(reason) => match reason {
match reason { Ok(StopReason::EndTurn | StopReason::MaxTokens) => {
Ok(StopReason::EndTurn | StopReason::MaxTokens) => { let used_tools = self.thread.read(cx).used_tools_since_last_user_message();
let used_tools = self.thread.read(cx).used_tools_since_last_user_message(); self.play_notification_sound(window, cx);
self.notify_with_sound( self.show_notification(
if used_tools { if used_tools {
"Finished running tools" "Finished running tools"
} else { } else {
"New message" "New message"
}, },
IconName::ZedAssistant, IconName::ZedAssistant,
window, window,
cx, cx,
); );
}
Ok(StopReason::ToolUse) => {
// Don't notify for intermediate tool use
}
Ok(StopReason::Refusal) => {
self.notify_with_sound(
"Language model refused to respond",
IconName::Warning,
window,
cx,
);
}
Err(error) => {
self.notify_with_sound(
"Agent stopped due to an error",
IconName::Warning,
window,
cx,
);
let error_message = error
.chain()
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join("\n");
self.last_error = Some(ThreadError::Message {
header: "Error interacting with language model".into(),
message: error_message.into(),
});
}
} }
} _ => {}
},
ThreadEvent::ToolConfirmationNeeded => { ThreadEvent::ToolConfirmationNeeded => {
self.notify_with_sound("Waiting for tool confirmation", IconName::Info, window, cx); self.play_notification_sound(window, cx);
self.show_notification("Waiting for tool confirmation", IconName::Info, window, cx);
} }
ThreadEvent::ToolUseLimitReached => { ThreadEvent::ToolUseLimitReached => {
self.notify_with_sound( self.play_notification_sound(window, cx);
self.show_notification(
"Consecutive tool use limit reached.", "Consecutive tool use limit reached.",
IconName::Warning, IconName::Warning,
window, window,
@ -1189,6 +1149,9 @@ impl ActiveThread {
self.save_thread(cx); self.save_thread(cx);
cx.notify(); cx.notify();
} }
ThreadEvent::RetriesFailed { message } => {
self.show_notification(message, ui::IconName::Warning, window, cx);
}
} }
} }
@ -1243,17 +1206,6 @@ impl ActiveThread {
} }
} }
fn notify_with_sound(
&mut self,
caption: impl Into<SharedString>,
icon: IconName,
window: &mut Window,
cx: &mut Context<ActiveThread>,
) {
self.play_notification_sound(window, cx);
self.show_notification(caption, icon, window, cx);
}
fn pop_up( fn pop_up(
&mut self, &mut self,
icon: IconName, icon: IconName,
@ -1509,7 +1461,6 @@ impl ActiveThread {
&configured_model.model, &configured_model.model,
cx, cx,
), ),
thinking_allowed: true,
}; };
Some(configured_model.model.count_tokens(request, cx)) Some(configured_model.model.count_tokens(request, cx))
@ -2629,8 +2580,8 @@ impl ActiveThread {
h_flex() h_flex()
.gap_1p5() .gap_1p5()
.child( .child(
Icon::new(IconName::ToolBulb) Icon::new(IconName::LightBulb)
.size(IconSize::Small) .size(IconSize::XSmall)
.color(Color::Muted), .color(Color::Muted),
) )
.child(LoadingLabel::new("Thinking").size(LabelSize::Small)), .child(LoadingLabel::new("Thinking").size(LabelSize::Small)),
@ -3043,7 +2994,7 @@ impl ActiveThread {
.overflow_x_scroll() .overflow_x_scroll()
.child( .child(
Icon::new(tool_use.icon) Icon::new(tool_use.icon)
.size(IconSize::Small) .size(IconSize::XSmall)
.color(Color::Muted), .color(Color::Muted),
) )
.child( .child(

View file

@ -24,10 +24,9 @@ use project::{
context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore}, context_server_store::{ContextServerConfiguration, ContextServerStatus, ContextServerStore},
project_settings::{ContextServerSettings, ProjectSettings}, project_settings::{ContextServerSettings, ProjectSettings},
}; };
use proto::Plan;
use settings::{Settings, update_settings_file}; use settings::{Settings, update_settings_file};
use ui::{ use ui::{
Chip, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu,
Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip, prelude::*, Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip, prelude::*,
}; };
use util::ResultExt as _; use util::ResultExt as _;
@ -172,15 +171,6 @@ impl AgentConfiguration {
.copied() .copied()
.unwrap_or(false); .unwrap_or(false);
let is_zed_provider = provider.id() == ZED_CLOUD_PROVIDER_ID;
let current_plan = if is_zed_provider {
self.workspace
.upgrade()
.and_then(|workspace| workspace.read(cx).user_store().read(cx).current_plan())
} else {
None
};
v_flex() v_flex()
.when(is_expanded, |this| this.mb_2()) .when(is_expanded, |this| this.mb_2())
.child( .child(
@ -218,31 +208,14 @@ impl AgentConfiguration {
.size(IconSize::Small) .size(IconSize::Small)
.color(Color::Muted), .color(Color::Muted),
) )
.child( .child(Label::new(provider_name.clone()).size(LabelSize::Large))
h_flex() .when(
.gap_1() provider.is_authenticated(cx) && !is_expanded,
.child( |parent| {
Label::new(provider_name.clone()) parent.child(
.size(LabelSize::Large), Icon::new(IconName::Check).color(Color::Success),
) )
.map(|this| { },
if is_zed_provider {
this.gap_2().child(
self.render_zed_plan_info(current_plan, cx),
)
} else {
this.when(
provider.is_authenticated(cx)
&& !is_expanded,
|parent| {
parent.child(
Icon::new(IconName::Check)
.color(Color::Success),
)
},
)
}
}),
), ),
) )
.child( .child(
@ -458,37 +431,6 @@ impl AgentConfiguration {
.child(self.render_sound_notification(cx)) .child(self.render_sound_notification(cx))
} }
fn render_zed_plan_info(&self, plan: Option<Plan>, cx: &mut Context<Self>) -> impl IntoElement {
if let Some(plan) = plan {
let free_chip_bg = cx
.theme()
.colors()
.editor_background
.opacity(0.5)
.blend(cx.theme().colors().text_accent.opacity(0.05));
let pro_chip_bg = cx
.theme()
.colors()
.editor_background
.opacity(0.5)
.blend(cx.theme().colors().text_accent.opacity(0.2));
let (plan_name, label_color, bg_color) = match plan {
Plan::Free => ("Free", Color::Default, free_chip_bg),
Plan::ZedProTrial => ("Pro Trial", Color::Accent, pro_chip_bg),
Plan::ZedPro => ("Pro", Color::Accent, pro_chip_bg),
};
Chip::new(plan_name.to_string())
.bg_color(bg_color)
.label_color(label_color)
.into_any_element()
} else {
div().into_any_element()
}
}
fn render_context_servers_section( fn render_context_servers_section(
&mut self, &mut self,
window: &mut Window, window: &mut Window,

View file

@ -1,9 +1,7 @@
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll}; use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll};
use acp::{AcpThread, AcpThreadEvent}; use agent::{Thread, ThreadEvent};
use agent::{Thread, ThreadEvent, ThreadSummary};
use agent_settings::AgentSettings; use agent_settings::AgentSettings;
use anyhow::Result; use anyhow::Result;
use assistant_tool::ActionLog;
use buffer_diff::DiffHunkStatus; use buffer_diff::DiffHunkStatus;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use editor::{ use editor::{
@ -43,108 +41,16 @@ use zed_actions::assistant::ToggleFocus;
pub struct AgentDiffPane { pub struct AgentDiffPane {
multibuffer: Entity<MultiBuffer>, multibuffer: Entity<MultiBuffer>,
editor: Entity<Editor>, editor: Entity<Editor>,
thread: AgentDiffThread, thread: Entity<Thread>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
title: SharedString, title: SharedString,
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
#[derive(PartialEq, Eq, Clone)]
pub enum AgentDiffThread {
Native(Entity<Thread>),
AcpThread(Entity<AcpThread>),
}
impl AgentDiffThread {
fn project(&self, cx: &App) -> Entity<Project> {
match self {
AgentDiffThread::Native(thread) => thread.read(cx).project().clone(),
AgentDiffThread::AcpThread(thread) => thread.read(cx).project().clone(),
}
}
fn action_log(&self, cx: &App) -> Entity<ActionLog> {
match self {
AgentDiffThread::Native(thread) => thread.read(cx).action_log().clone(),
AgentDiffThread::AcpThread(thread) => thread.read(cx).action_log().clone(),
}
}
fn summary(&self, cx: &App) -> ThreadSummary {
match self {
AgentDiffThread::Native(thread) => thread.read(cx).summary().clone(),
AgentDiffThread::AcpThread(thread) => ThreadSummary::Ready(thread.read(cx).title()),
}
}
fn is_generating(&self, cx: &App) -> bool {
match self {
AgentDiffThread::Native(thread) => thread.read(cx).is_generating(),
AgentDiffThread::AcpThread(thread) => {
thread.read(cx).status() == acp::ThreadStatus::Generating
}
}
}
fn has_pending_edit_tool_uses(&self, cx: &App) -> bool {
match self {
AgentDiffThread::Native(thread) => thread.read(cx).has_pending_edit_tool_uses(),
AgentDiffThread::AcpThread(thread) => thread.read(cx).has_pending_edit_tool_calls(),
}
}
fn downgrade(&self) -> WeakAgentDiffThread {
match self {
AgentDiffThread::Native(thread) => WeakAgentDiffThread::Native(thread.downgrade()),
AgentDiffThread::AcpThread(thread) => {
WeakAgentDiffThread::AcpThread(thread.downgrade())
}
}
}
}
impl From<Entity<Thread>> for AgentDiffThread {
fn from(entity: Entity<Thread>) -> Self {
AgentDiffThread::Native(entity)
}
}
impl From<Entity<AcpThread>> for AgentDiffThread {
fn from(entity: Entity<AcpThread>) -> Self {
AgentDiffThread::AcpThread(entity)
}
}
#[derive(PartialEq, Eq, Clone)]
pub enum WeakAgentDiffThread {
Native(WeakEntity<Thread>),
AcpThread(WeakEntity<AcpThread>),
}
impl WeakAgentDiffThread {
pub fn upgrade(&self) -> Option<AgentDiffThread> {
match self {
WeakAgentDiffThread::Native(weak) => weak.upgrade().map(AgentDiffThread::Native),
WeakAgentDiffThread::AcpThread(weak) => weak.upgrade().map(AgentDiffThread::AcpThread),
}
}
}
impl From<WeakEntity<Thread>> for WeakAgentDiffThread {
fn from(entity: WeakEntity<Thread>) -> Self {
WeakAgentDiffThread::Native(entity)
}
}
impl From<WeakEntity<AcpThread>> for WeakAgentDiffThread {
fn from(entity: WeakEntity<AcpThread>) -> Self {
WeakAgentDiffThread::AcpThread(entity)
}
}
impl AgentDiffPane { impl AgentDiffPane {
pub fn deploy( pub fn deploy(
thread: impl Into<AgentDiffThread>, thread: Entity<Thread>,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
@ -155,16 +61,14 @@ impl AgentDiffPane {
} }
pub fn deploy_in_workspace( pub fn deploy_in_workspace(
thread: impl Into<AgentDiffThread>, thread: Entity<Thread>,
workspace: &mut Workspace, workspace: &mut Workspace,
window: &mut Window, window: &mut Window,
cx: &mut Context<Workspace>, cx: &mut Context<Workspace>,
) -> Entity<Self> { ) -> Entity<Self> {
let thread = thread.into();
let existing_diff = workspace let existing_diff = workspace
.items_of_type::<AgentDiffPane>(cx) .items_of_type::<AgentDiffPane>(cx)
.find(|diff| diff.read(cx).thread == thread); .find(|diff| diff.read(cx).thread == thread);
if let Some(existing_diff) = existing_diff { if let Some(existing_diff) = existing_diff {
workspace.activate_item(&existing_diff, true, true, window, cx); workspace.activate_item(&existing_diff, true, true, window, cx);
existing_diff existing_diff
@ -177,7 +81,7 @@ impl AgentDiffPane {
} }
pub fn new( pub fn new(
thread: AgentDiffThread, thread: Entity<Thread>,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
@ -185,7 +89,7 @@ impl AgentDiffPane {
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
let project = thread.project(cx).clone(); let project = thread.read(cx).project().clone();
let editor = cx.new(|cx| { let editor = cx.new(|cx| {
let mut editor = let mut editor =
Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx); Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);
@ -196,27 +100,16 @@ impl AgentDiffPane {
editor editor
}); });
let action_log = thread.action_log(cx).clone(); let action_log = thread.read(cx).action_log().clone();
let mut this = Self { let mut this = Self {
_subscriptions: [ _subscriptions: vec![
Some( cx.observe_in(&action_log, window, |this, _action_log, window, cx| {
cx.observe_in(&action_log, window, |this, _action_log, window, cx| { this.update_excerpts(window, cx)
this.update_excerpts(window, cx) }),
}), cx.subscribe(&thread, |this, _thread, event, cx| {
), this.handle_thread_event(event, cx)
match &thread { }),
AgentDiffThread::Native(thread) => { ],
Some(cx.subscribe(&thread, |this, _thread, event, cx| {
this.handle_thread_event(event, cx)
}))
}
AgentDiffThread::AcpThread(_) => None,
},
]
.into_iter()
.flatten()
.collect(),
title: SharedString::default(), title: SharedString::default(),
multibuffer, multibuffer,
editor, editor,
@ -230,7 +123,8 @@ impl AgentDiffPane {
} }
fn update_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn update_excerpts(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let changed_buffers = self.thread.action_log(cx).read(cx).changed_buffers(cx); let thread = self.thread.read(cx);
let changed_buffers = thread.action_log().read(cx).changed_buffers(cx);
let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>(); let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::<HashSet<_>>();
for (buffer, diff_handle) in changed_buffers { for (buffer, diff_handle) in changed_buffers {
@ -317,7 +211,7 @@ impl AgentDiffPane {
} }
fn update_title(&mut self, cx: &mut Context<Self>) { fn update_title(&mut self, cx: &mut Context<Self>) {
let new_title = self.thread.summary(cx).unwrap_or("Agent Changes"); let new_title = self.thread.read(cx).summary().unwrap_or("Agent Changes");
if new_title != self.title { if new_title != self.title {
self.title = new_title; self.title = new_title;
cx.emit(EditorEvent::TitleChanged); cx.emit(EditorEvent::TitleChanged);
@ -381,15 +275,14 @@ impl AgentDiffPane {
fn keep_all(&mut self, _: &KeepAll, _window: &mut Window, cx: &mut Context<Self>) { fn keep_all(&mut self, _: &KeepAll, _window: &mut Window, cx: &mut Context<Self>) {
self.thread self.thread
.action_log(cx) .update(cx, |thread, cx| thread.keep_all_edits(cx));
.update(cx, |action_log, cx| action_log.keep_all_edits(cx))
} }
} }
fn keep_edits_in_selection( fn keep_edits_in_selection(
editor: &mut Editor, editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot, buffer_snapshot: &MultiBufferSnapshot,
thread: &AgentDiffThread, thread: &Entity<Thread>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) { ) {
@ -404,7 +297,7 @@ fn keep_edits_in_selection(
fn reject_edits_in_selection( fn reject_edits_in_selection(
editor: &mut Editor, editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot, buffer_snapshot: &MultiBufferSnapshot,
thread: &AgentDiffThread, thread: &Entity<Thread>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
) { ) {
@ -418,7 +311,7 @@ fn reject_edits_in_selection(
fn keep_edits_in_ranges( fn keep_edits_in_ranges(
editor: &mut Editor, editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot, buffer_snapshot: &MultiBufferSnapshot,
thread: &AgentDiffThread, thread: &Entity<Thread>,
ranges: Vec<Range<editor::Anchor>>, ranges: Vec<Range<editor::Anchor>>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
@ -433,8 +326,8 @@ fn keep_edits_in_ranges(
for hunk in &diff_hunks_in_ranges { for hunk in &diff_hunks_in_ranges {
let buffer = multibuffer.read(cx).buffer(hunk.buffer_id); let buffer = multibuffer.read(cx).buffer(hunk.buffer_id);
if let Some(buffer) = buffer { if let Some(buffer) = buffer {
thread.action_log(cx).update(cx, |action_log, cx| { thread.update(cx, |thread, cx| {
action_log.keep_edits_in_range(buffer, hunk.buffer_range.clone(), cx) thread.keep_edits_in_range(buffer, hunk.buffer_range.clone(), cx)
}); });
} }
} }
@ -443,7 +336,7 @@ fn keep_edits_in_ranges(
fn reject_edits_in_ranges( fn reject_edits_in_ranges(
editor: &mut Editor, editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot, buffer_snapshot: &MultiBufferSnapshot,
thread: &AgentDiffThread, thread: &Entity<Thread>,
ranges: Vec<Range<editor::Anchor>>, ranges: Vec<Range<editor::Anchor>>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Editor>, cx: &mut Context<Editor>,
@ -469,9 +362,8 @@ fn reject_edits_in_ranges(
for (buffer, ranges) in ranges_by_buffer { for (buffer, ranges) in ranges_by_buffer {
thread thread
.action_log(cx) .update(cx, |thread, cx| {
.update(cx, |action_log, cx| { thread.reject_edits_in_ranges(buffer, ranges, cx)
action_log.reject_edits_in_ranges(buffer, ranges, cx)
}) })
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
@ -569,7 +461,7 @@ impl Item for AgentDiffPane {
} }
fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement { fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {
let summary = self.thread.summary(cx).unwrap_or("Agent Changes"); let summary = self.thread.read(cx).summary().unwrap_or("Agent Changes");
Label::new(format!("Review: {}", summary)) Label::new(format!("Review: {}", summary))
.color(if params.selected { .color(if params.selected {
Color::Default Color::Default
@ -749,7 +641,7 @@ impl Render for AgentDiffPane {
} }
} }
fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControlsFn { fn diff_hunk_controls(thread: &Entity<Thread>) -> editor::RenderDiffHunkControlsFn {
let thread = thread.clone(); let thread = thread.clone();
Arc::new( Arc::new(
@ -784,7 +676,7 @@ fn render_diff_hunk_controls(
hunk_range: Range<editor::Anchor>, hunk_range: Range<editor::Anchor>,
is_created_file: bool, is_created_file: bool,
line_height: Pixels, line_height: Pixels,
thread: &AgentDiffThread, thread: &Entity<Thread>,
editor: &Entity<Editor>, editor: &Entity<Editor>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
@ -1220,8 +1112,11 @@ impl Render for AgentDiffToolbar {
return Empty.into_any(); return Empty.into_any();
}; };
let has_pending_edit_tool_use = let has_pending_edit_tool_use = agent_diff
agent_diff.read(cx).thread.has_pending_edit_tool_uses(cx); .read(cx)
.thread
.read(cx)
.has_pending_edit_tool_uses();
if has_pending_edit_tool_use { if has_pending_edit_tool_use {
return div().px_2().child(spinner_icon).into_any(); return div().px_2().child(spinner_icon).into_any();
@ -1292,8 +1187,8 @@ pub enum EditorState {
} }
struct WorkspaceThread { struct WorkspaceThread {
thread: WeakAgentDiffThread, thread: WeakEntity<Thread>,
_thread_subscriptions: (Subscription, Subscription), _thread_subscriptions: [Subscription; 2],
singleton_editors: HashMap<WeakEntity<Buffer>, HashMap<WeakEntity<Editor>, Subscription>>, singleton_editors: HashMap<WeakEntity<Buffer>, HashMap<WeakEntity<Editor>, Subscription>>,
_settings_subscription: Subscription, _settings_subscription: Subscription,
_workspace_subscription: Option<Subscription>, _workspace_subscription: Option<Subscription>,
@ -1317,23 +1212,23 @@ impl AgentDiff {
pub fn set_active_thread( pub fn set_active_thread(
workspace: &WeakEntity<Workspace>, workspace: &WeakEntity<Workspace>,
thread: impl Into<AgentDiffThread>, thread: &Entity<Thread>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) { ) {
Self::global(cx).update(cx, |this, cx| { Self::global(cx).update(cx, |this, cx| {
this.register_active_thread_impl(workspace, thread.into(), window, cx); this.register_active_thread_impl(workspace, thread, window, cx);
}); });
} }
fn register_active_thread_impl( fn register_active_thread_impl(
&mut self, &mut self,
workspace: &WeakEntity<Workspace>, workspace: &WeakEntity<Workspace>,
thread: AgentDiffThread, thread: &Entity<Thread>,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let action_log = thread.action_log(cx).clone(); let action_log = thread.read(cx).action_log().clone();
let action_log_subscription = cx.observe_in(&action_log, window, { let action_log_subscription = cx.observe_in(&action_log, window, {
let workspace = workspace.clone(); let workspace = workspace.clone();
@ -1342,25 +1237,17 @@ impl AgentDiff {
} }
}); });
let thread_subscription = match &thread { let thread_subscription = cx.subscribe_in(&thread, window, {
AgentDiffThread::Native(thread) => cx.subscribe_in(&thread, window, { let workspace = workspace.clone();
let workspace = workspace.clone(); move |this, _thread, event, window, cx| {
move |this, _thread, event, window, cx| { this.handle_thread_event(&workspace, event, window, cx)
this.handle_native_thread_event(&workspace, event, window, cx) }
} });
}),
AgentDiffThread::AcpThread(thread) => cx.subscribe_in(&thread, window, {
let workspace = workspace.clone();
move |this, thread, event, window, cx| {
this.handle_acp_thread_event(&workspace, thread, event, window, cx)
}
}),
};
if let Some(workspace_thread) = self.workspace_threads.get_mut(&workspace) { if let Some(workspace_thread) = self.workspace_threads.get_mut(&workspace) {
// replace thread and action log subscription, but keep editors // replace thread and action log subscription, but keep editors
workspace_thread.thread = thread.downgrade(); workspace_thread.thread = thread.downgrade();
workspace_thread._thread_subscriptions = (action_log_subscription, thread_subscription); workspace_thread._thread_subscriptions = [action_log_subscription, thread_subscription];
self.update_reviewing_editors(&workspace, window, cx); self.update_reviewing_editors(&workspace, window, cx);
return; return;
} }
@ -1385,7 +1272,7 @@ impl AgentDiff {
workspace.clone(), workspace.clone(),
WorkspaceThread { WorkspaceThread {
thread: thread.downgrade(), thread: thread.downgrade(),
_thread_subscriptions: (action_log_subscription, thread_subscription), _thread_subscriptions: [action_log_subscription, thread_subscription],
singleton_editors: HashMap::default(), singleton_editors: HashMap::default(),
_settings_subscription: settings_subscription, _settings_subscription: settings_subscription,
_workspace_subscription: workspace_subscription, _workspace_subscription: workspace_subscription,
@ -1432,7 +1319,7 @@ impl AgentDiff {
fn register_review_action<T: Action>( fn register_review_action<T: Action>(
workspace: &mut Workspace, workspace: &mut Workspace,
review: impl Fn(&Entity<Editor>, &AgentDiffThread, &mut Window, &mut App) -> PostReviewState review: impl Fn(&Entity<Editor>, &Entity<Thread>, &mut Window, &mut App) -> PostReviewState
+ 'static, + 'static,
this: &Entity<AgentDiff>, this: &Entity<AgentDiff>,
) { ) {
@ -1451,7 +1338,7 @@ impl AgentDiff {
}); });
} }
fn handle_native_thread_event( fn handle_thread_event(
&mut self, &mut self,
workspace: &WeakEntity<Workspace>, workspace: &WeakEntity<Workspace>,
event: &ThreadEvent, event: &ThreadEvent,
@ -1488,44 +1375,11 @@ impl AgentDiff {
| ThreadEvent::ToolConfirmationNeeded | ThreadEvent::ToolConfirmationNeeded
| ThreadEvent::ToolUseLimitReached | ThreadEvent::ToolUseLimitReached
| ThreadEvent::CancelEditing | ThreadEvent::CancelEditing
| ThreadEvent::RetriesFailed { .. }
| ThreadEvent::ProfileChanged => {} | ThreadEvent::ProfileChanged => {}
} }
} }
fn handle_acp_thread_event(
&mut self,
workspace: &WeakEntity<Workspace>,
thread: &Entity<AcpThread>,
event: &AcpThreadEvent,
window: &mut Window,
cx: &mut Context<Self>,
) {
match event {
AcpThreadEvent::NewEntry => {
if thread
.read(cx)
.entries()
.last()
.and_then(|entry| entry.diff())
.is_some()
{
self.update_reviewing_editors(workspace, window, cx);
}
}
AcpThreadEvent::EntryUpdated(ix) => {
if thread
.read(cx)
.entries()
.get(*ix)
.and_then(|entry| entry.diff())
.is_some()
{
self.update_reviewing_editors(workspace, window, cx);
}
}
}
}
fn handle_workspace_event( fn handle_workspace_event(
&mut self, &mut self,
workspace: &Entity<Workspace>, workspace: &Entity<Workspace>,
@ -1631,7 +1485,7 @@ impl AgentDiff {
return; return;
}; };
let action_log = thread.action_log(cx); let action_log = thread.read(cx).action_log();
let changed_buffers = action_log.read(cx).changed_buffers(cx); let changed_buffers = action_log.read(cx).changed_buffers(cx);
let mut unaffected = self.reviewing_editors.clone(); let mut unaffected = self.reviewing_editors.clone();
@ -1656,7 +1510,7 @@ impl AgentDiff {
multibuffer.add_diff(diff_handle.clone(), cx); multibuffer.add_diff(diff_handle.clone(), cx);
}); });
let new_state = if thread.is_generating(cx) { let new_state = if thread.read(cx).is_generating() {
EditorState::Generating EditorState::Generating
} else { } else {
EditorState::Reviewing EditorState::Reviewing
@ -1752,7 +1606,7 @@ impl AgentDiff {
fn keep_all( fn keep_all(
editor: &Entity<Editor>, editor: &Entity<Editor>,
thread: &AgentDiffThread, thread: &Entity<Thread>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> PostReviewState { ) -> PostReviewState {
@ -1772,7 +1626,7 @@ impl AgentDiff {
fn reject_all( fn reject_all(
editor: &Entity<Editor>, editor: &Entity<Editor>,
thread: &AgentDiffThread, thread: &Entity<Thread>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> PostReviewState { ) -> PostReviewState {
@ -1792,7 +1646,7 @@ impl AgentDiff {
fn keep( fn keep(
editor: &Entity<Editor>, editor: &Entity<Editor>,
thread: &AgentDiffThread, thread: &Entity<Thread>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> PostReviewState { ) -> PostReviewState {
@ -1805,7 +1659,7 @@ impl AgentDiff {
fn reject( fn reject(
editor: &Entity<Editor>, editor: &Entity<Editor>,
thread: &AgentDiffThread, thread: &Entity<Thread>,
window: &mut Window, window: &mut Window,
cx: &mut App, cx: &mut App,
) -> PostReviewState { ) -> PostReviewState {
@ -1828,7 +1682,7 @@ impl AgentDiff {
fn review_in_active_editor( fn review_in_active_editor(
&mut self, &mut self,
workspace: &mut Workspace, workspace: &mut Workspace,
review: impl Fn(&Entity<Editor>, &AgentDiffThread, &mut Window, &mut App) -> PostReviewState, review: impl Fn(&Entity<Editor>, &Entity<Thread>, &mut Window, &mut App) -> PostReviewState,
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Option<Task<Result<()>>> { ) -> Option<Task<Result<()>>> {
@ -1849,7 +1703,7 @@ impl AgentDiff {
if let PostReviewState::AllReviewed = review(&editor, &thread, window, cx) { if let PostReviewState::AllReviewed = review(&editor, &thread, window, cx) {
if let Some(curr_buffer) = editor.read(cx).buffer().read(cx).as_singleton() { if let Some(curr_buffer) = editor.read(cx).buffer().read(cx).as_singleton() {
let changed_buffers = thread.action_log(cx).read(cx).changed_buffers(cx); let changed_buffers = thread.read(cx).action_log().read(cx).changed_buffers(cx);
let mut keys = changed_buffers.keys().cycle(); let mut keys = changed_buffers.keys().cycle();
keys.find(|k| *k == &curr_buffer); keys.find(|k| *k == &curr_buffer);
@ -1947,9 +1801,8 @@ mod tests {
}) })
.await .await
.unwrap(); .unwrap();
let thread = let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
AgentDiffThread::Native(thread_store.update(cx, |store, cx| store.create_thread(cx))); let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
let action_log = cx.read(|cx| thread.action_log(cx));
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
@ -2135,9 +1988,8 @@ mod tests {
}); });
// Set the active thread // Set the active thread
let thread = AgentDiffThread::Native(thread);
cx.update(|window, cx| { cx.update(|window, cx| {
AgentDiff::set_active_thread(&workspace.downgrade(), thread.clone(), window, cx) AgentDiff::set_active_thread(&workspace.downgrade(), &thread, window, cx)
}); });
let buffer1 = project let buffer1 = project

View file

@ -1,4 +1,3 @@
use std::cell::RefCell;
use std::ops::Range; use std::ops::Range;
use std::path::Path; use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
@ -8,15 +7,12 @@ use std::time::Duration;
use db::kvp::{Dismissable, KEY_VALUE_STORE}; use db::kvp::{Dismissable, KEY_VALUE_STORE};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::NewAcpThread;
use crate::agent_diff::AgentDiffThread;
use crate::language_model_selector::ToggleModelSelector; use crate::language_model_selector::ToggleModelSelector;
use crate::{ use crate::{
AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode, AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread, DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu, ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
acp::AcpThreadView,
active_thread::{self, ActiveThread, ActiveThreadEvent}, active_thread::{self, ActiveThread, ActiveThreadEvent},
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent}, agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
agent_diff::AgentDiff, agent_diff::AgentDiff,
@ -42,7 +38,6 @@ use assistant_slash_command::SlashCommandWorkingSet;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;
use client::{UserStore, zed_urls}; use client::{UserStore, zed_urls};
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer}; use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
use feature_flags::{self, FeatureFlagAppExt};
use fs::Fs; use fs::Fs;
use gpui::{ use gpui::{
Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem, Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
@ -114,12 +109,6 @@ pub fn init(cx: &mut App) {
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx)); panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
} }
}) })
.register_action(|workspace, _: &NewAcpThread, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
workspace.focus_panel::<AgentPanel>(window, cx);
panel.update(cx, |panel, cx| panel.new_gemini_thread(window, cx));
}
})
.register_action(|workspace, action: &OpenRulesLibrary, window, cx| { .register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) { if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
workspace.focus_panel::<AgentPanel>(window, cx); workspace.focus_panel::<AgentPanel>(window, cx);
@ -136,8 +125,7 @@ pub fn init(cx: &mut App) {
let thread = thread.read(cx).thread().clone(); let thread = thread.read(cx).thread().clone();
AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx); AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
} }
ActiveView::AcpThread { .. } ActiveView::TextThread { .. }
| ActiveView::TextThread { .. }
| ActiveView::History | ActiveView::History
| ActiveView::Configuration => {} | ActiveView::Configuration => {}
} }
@ -200,9 +188,6 @@ enum ActiveView {
message_editor: Entity<MessageEditor>, message_editor: Entity<MessageEditor>,
_subscriptions: Vec<gpui::Subscription>, _subscriptions: Vec<gpui::Subscription>,
}, },
AcpThread {
thread_view: Entity<AcpThreadView>,
},
TextThread { TextThread {
context_editor: Entity<TextThreadEditor>, context_editor: Entity<TextThreadEditor>,
title_editor: Entity<Editor>, title_editor: Entity<Editor>,
@ -222,9 +207,7 @@ enum WhichFontSize {
impl ActiveView { impl ActiveView {
pub fn which_font_size_used(&self) -> WhichFontSize { pub fn which_font_size_used(&self) -> WhichFontSize {
match self { match self {
ActiveView::Thread { .. } | ActiveView::AcpThread { .. } | ActiveView::History => { ActiveView::Thread { .. } | ActiveView::History => WhichFontSize::AgentFont,
WhichFontSize::AgentFont
}
ActiveView::TextThread { .. } => WhichFontSize::BufferFont, ActiveView::TextThread { .. } => WhichFontSize::BufferFont,
ActiveView::Configuration => WhichFontSize::None, ActiveView::Configuration => WhichFontSize::None,
} }
@ -255,7 +238,6 @@ impl ActiveView {
thread.scroll_to_bottom(cx); thread.scroll_to_bottom(cx);
}); });
} }
ActiveView::AcpThread { .. } => {}
ActiveView::TextThread { .. } ActiveView::TextThread { .. }
| ActiveView::History | ActiveView::History
| ActiveView::Configuration => {} | ActiveView::Configuration => {}
@ -434,14 +416,11 @@ pub struct AgentPanel {
configuration_subscription: Option<Subscription>, configuration_subscription: Option<Subscription>,
local_timezone: UtcOffset, local_timezone: UtcOffset,
active_view: ActiveView, active_view: ActiveView,
acp_message_history:
Rc<RefCell<crate::acp::MessageHistory<agentic_coding_protocol::SendUserMessageParams>>>,
previous_view: Option<ActiveView>, previous_view: Option<ActiveView>,
history_store: Entity<HistoryStore>, history_store: Entity<HistoryStore>,
history: Entity<ThreadHistory>, history: Entity<ThreadHistory>,
hovered_recent_history_item: Option<usize>, hovered_recent_history_item: Option<usize>,
new_thread_menu_handle: PopoverMenuHandle<ContextMenu>, assistant_dropdown_menu_handle: PopoverMenuHandle<ContextMenu>,
agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>, assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
assistant_navigation_menu: Option<Entity<ContextMenu>>, assistant_navigation_menu: Option<Entity<ContextMenu>>,
width: Option<Pixels>, width: Option<Pixels>,
@ -628,7 +607,7 @@ impl AgentPanel {
} }
}; };
AgentDiff::set_active_thread(&workspace, thread.clone(), window, cx); AgentDiff::set_active_thread(&workspace, &thread, window, cx);
let weak_panel = weak_self.clone(); let weak_panel = weak_self.clone();
@ -674,8 +653,7 @@ impl AgentPanel {
.clone() .clone()
.update(cx, |thread, cx| thread.get_or_init_configured_model(cx)); .update(cx, |thread, cx| thread.get_or_init_configured_model(cx));
} }
ActiveView::AcpThread { .. } ActiveView::TextThread { .. }
| ActiveView::TextThread { .. }
| ActiveView::History | ActiveView::History
| ActiveView::Configuration => {} | ActiveView::Configuration => {}
}, },
@ -702,12 +680,10 @@ impl AgentPanel {
.unwrap(), .unwrap(),
inline_assist_context_store, inline_assist_context_store,
previous_view: None, previous_view: None,
acp_message_history: Default::default(),
history_store: history_store.clone(), history_store: history_store.clone(),
history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)), history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)),
hovered_recent_history_item: None, hovered_recent_history_item: None,
new_thread_menu_handle: PopoverMenuHandle::default(), assistant_dropdown_menu_handle: PopoverMenuHandle::default(),
agent_panel_menu_handle: PopoverMenuHandle::default(),
assistant_navigation_menu_handle: PopoverMenuHandle::default(), assistant_navigation_menu_handle: PopoverMenuHandle::default(),
assistant_navigation_menu: None, assistant_navigation_menu: None,
width: None, width: None,
@ -757,9 +733,6 @@ impl AgentPanel {
ActiveView::Thread { thread, .. } => { ActiveView::Thread { thread, .. } => {
thread.update(cx, |thread, cx| thread.cancel_last_completion(window, cx)); thread.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
} }
ActiveView::AcpThread { thread_view, .. } => {
thread_view.update(cx, |thread_element, cx| thread_element.cancel(cx));
}
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {} ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
} }
} }
@ -767,18 +740,18 @@ impl AgentPanel {
fn active_message_editor(&self) -> Option<&Entity<MessageEditor>> { fn active_message_editor(&self) -> Option<&Entity<MessageEditor>> {
match &self.active_view { match &self.active_view {
ActiveView::Thread { message_editor, .. } => Some(message_editor), ActiveView::Thread { message_editor, .. } => Some(message_editor),
ActiveView::AcpThread { .. } ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => None,
| ActiveView::TextThread { .. }
| ActiveView::History
| ActiveView::Configuration => None,
} }
} }
fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) { fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
// Preserve chat box text when using creating new thread // Preserve chat box text when using creating new thread from summary'
let preserved_text = self let preserved_text = if action.from_thread_id.is_some() {
.active_message_editor() self.active_message_editor()
.map(|editor| editor.read(cx).get_text(cx).trim().to_string()); .map(|editor| editor.read(cx).get_text(cx).trim().to_string())
} else {
None
};
let thread = self let thread = self
.thread_store .thread_store
@ -850,7 +823,7 @@ impl AgentPanel {
let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx); let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx);
self.set_active_view(thread_view, window, cx); self.set_active_view(thread_view, window, cx);
AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx); AgentDiff::set_active_thread(&self.workspace, &thread, window, cx);
} }
fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) { fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
@ -889,37 +862,6 @@ impl AgentPanel {
context_editor.focus_handle(cx).focus(window); context_editor.focus_handle(cx).focus(window);
} }
fn new_gemini_thread(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let workspace = self.workspace.clone();
let project = self.project.clone();
let message_history = self.acp_message_history.clone();
cx.spawn_in(window, async move |this, cx| {
let thread_view = cx.new_window_entity(|window, cx| {
crate::acp::AcpThreadView::new(
workspace.clone(),
project,
message_history,
window,
cx,
)
})?;
this.update_in(cx, |this, window, cx| {
this.set_active_view(
ActiveView::AcpThread {
thread_view: thread_view.clone(),
},
window,
cx,
);
})
.log_err();
anyhow::Ok(())
})
.detach();
}
fn deploy_rules_library( fn deploy_rules_library(
&mut self, &mut self,
action: &OpenRulesLibrary, action: &OpenRulesLibrary,
@ -1052,7 +994,6 @@ impl AgentPanel {
cx, cx,
) )
}); });
let message_editor = cx.new(|cx| { let message_editor = cx.new(|cx| {
MessageEditor::new( MessageEditor::new(
self.fs.clone(), self.fs.clone(),
@ -1071,7 +1012,7 @@ impl AgentPanel {
let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx); let thread_view = ActiveView::thread(active_thread.clone(), message_editor, window, cx);
self.set_active_view(thread_view, window, cx); self.set_active_view(thread_view, window, cx);
AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx); AgentDiff::set_active_thread(&self.workspace, &thread, window, cx);
} }
pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) { pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
@ -1084,9 +1025,6 @@ impl AgentPanel {
ActiveView::Thread { message_editor, .. } => { ActiveView::Thread { message_editor, .. } => {
message_editor.focus_handle(cx).focus(window); message_editor.focus_handle(cx).focus(window);
} }
ActiveView::AcpThread { thread_view } => {
thread_view.focus_handle(cx).focus(window);
}
ActiveView::TextThread { context_editor, .. } => { ActiveView::TextThread { context_editor, .. } => {
context_editor.focus_handle(cx).focus(window); context_editor.focus_handle(cx).focus(window);
} }
@ -1114,7 +1052,7 @@ impl AgentPanel {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
self.agent_panel_menu_handle.toggle(window, cx); self.assistant_dropdown_menu_handle.toggle(window, cx);
} }
pub fn increase_font_size( pub fn increase_font_size(
@ -1202,19 +1140,11 @@ impl AgentPanel {
let thread = thread.read(cx).thread().clone(); let thread = thread.read(cx).thread().clone();
self.workspace self.workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {
AgentDiffPane::deploy_in_workspace( AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx)
AgentDiffThread::Native(thread),
workspace,
window,
cx,
)
}) })
.log_err(); .log_err();
} }
ActiveView::AcpThread { .. } ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
| ActiveView::TextThread { .. }
| ActiveView::History
| ActiveView::Configuration => {}
} }
} }
@ -1267,13 +1197,6 @@ impl AgentPanel {
) )
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
ActiveView::AcpThread { thread_view } => {
thread_view
.update(cx, |thread_view, cx| {
thread_view.open_thread_as_markdown(workspace, window, cx)
})
.detach_and_log_err(cx);
}
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {} ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
} }
} }
@ -1428,8 +1351,7 @@ impl AgentPanel {
} }
}) })
} }
ActiveView::AcpThread { .. } => {} _ => {}
ActiveView::History | ActiveView::Configuration => {}
} }
if current_is_special && !new_is_special { if current_is_special && !new_is_special {
@ -1443,8 +1365,6 @@ impl AgentPanel {
self.active_view = new_view; self.active_view = new_view;
} }
self.acp_message_history.borrow_mut().reset_position();
self.focus_handle(cx).focus(window); self.focus_handle(cx).focus(window);
} }
@ -1517,7 +1437,6 @@ impl Focusable for AgentPanel {
fn focus_handle(&self, cx: &App) -> FocusHandle { fn focus_handle(&self, cx: &App) -> FocusHandle {
match &self.active_view { match &self.active_view {
ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx), ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx),
ActiveView::AcpThread { thread_view, .. } => thread_view.focus_handle(cx),
ActiveView::History => self.history.focus_handle(cx), ActiveView::History => self.history.focus_handle(cx),
ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx), ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
ActiveView::Configuration => { ActiveView::Configuration => {
@ -1674,9 +1593,6 @@ impl AgentPanel {
.into_any_element(), .into_any_element(),
} }
} }
ActiveView::AcpThread { thread_view } => Label::new(thread_view.read(cx).title(cx))
.truncate()
.into_any_element(),
ActiveView::TextThread { ActiveView::TextThread {
title_editor, title_editor,
context_editor, context_editor,
@ -1811,51 +1727,10 @@ impl AgentPanel {
let active_thread = match &self.active_view { let active_thread = match &self.active_view {
ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()), ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
ActiveView::AcpThread { .. } ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => None,
| ActiveView::TextThread { .. }
| ActiveView::History
| ActiveView::Configuration => None,
}; };
let new_thread_menu = PopoverMenu::new("new_thread_menu") let agent_extra_menu = PopoverMenu::new("agent-options-menu")
.trigger_with_tooltip(
IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
Tooltip::text("New Thread…"),
)
.anchor(Corner::TopRight)
.with_handle(self.new_thread_menu_handle.clone())
.menu(move |window, cx| {
let active_thread = active_thread.clone();
Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
menu = menu
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
this.header("Zed Agent")
})
.action("New Thread", NewThread::default().boxed_clone())
.action("New Text Thread", NewTextThread.boxed_clone())
.when_some(active_thread, |this, active_thread| {
let thread = active_thread.read(cx);
if !thread.is_empty() {
this.action(
"New From Summary",
Box::new(NewThread {
from_thread_id: Some(thread.id().clone()),
}),
)
} else {
this
}
})
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
this.separator()
.header("External Agents")
.action("New Gemini Thread", NewAcpThread.boxed_clone())
});
menu
}))
});
let agent_panel_menu = PopoverMenu::new("agent-options-menu")
.trigger_with_tooltip( .trigger_with_tooltip(
IconButton::new("agent-options-menu", IconName::Ellipsis) IconButton::new("agent-options-menu", IconName::Ellipsis)
.icon_size(IconSize::Small), .icon_size(IconSize::Small),
@ -1873,9 +1748,42 @@ impl AgentPanel {
}, },
) )
.anchor(Corner::TopRight) .anchor(Corner::TopRight)
.with_handle(self.agent_panel_menu_handle.clone()) .with_handle(self.assistant_dropdown_menu_handle.clone())
.menu(move |window, cx| { .menu(move |window, cx| {
Some(ContextMenu::build(window, cx, |mut menu, _window, _| { let active_thread = active_thread.clone();
Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
menu = menu
.action("New Thread", NewThread::default().boxed_clone())
.action("New Text Thread", NewTextThread.boxed_clone())
.when_some(active_thread, |this, active_thread| {
let thread = active_thread.read(cx);
if !thread.is_empty() {
this.action(
"New From Summary",
Box::new(NewThread {
from_thread_id: Some(thread.id().clone()),
}),
)
} else {
this
}
})
.separator();
menu = menu
.header("MCP Servers")
.action(
"View Server Extensions",
Box::new(zed_actions::Extensions {
category_filter: Some(
zed_actions::ExtensionCategoryFilter::ContextServers,
),
id: None,
}),
)
.action("Add Custom Server…", Box::new(AddContextServer))
.separator();
if let Some(usage) = usage { if let Some(usage) = usage {
menu = menu menu = menu
.header_with_link("Prompt Usage", "Manage", account_url.clone()) .header_with_link("Prompt Usage", "Manage", account_url.clone())
@ -1913,20 +1821,6 @@ impl AgentPanel {
.separator() .separator()
} }
menu = menu
.header("MCP Servers")
.action(
"View Server Extensions",
Box::new(zed_actions::Extensions {
category_filter: Some(
zed_actions::ExtensionCategoryFilter::ContextServers,
),
id: None,
}),
)
.action("Add Custom Server…", Box::new(AddContextServer))
.separator();
menu = menu menu = menu
.action("Rules…", Box::new(OpenRulesLibrary::default())) .action("Rules…", Box::new(OpenRulesLibrary::default()))
.action("Settings", Box::new(OpenConfiguration)) .action("Settings", Box::new(OpenConfiguration))
@ -1968,8 +1862,27 @@ impl AgentPanel {
.px(DynamicSpacing::Base08.rems(cx)) .px(DynamicSpacing::Base08.rems(cx))
.border_l_1() .border_l_1()
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.child(new_thread_menu) .child(
.child(agent_panel_menu), IconButton::new("new", IconName::Plus)
.icon_size(IconSize::Small)
.style(ButtonStyle::Subtle)
.tooltip(move |window, cx| {
Tooltip::for_action_in(
"New Thread",
&NewThread::default(),
&focus_handle,
window,
cx,
)
})
.on_click(move |_event, window, cx| {
window.dispatch_action(
NewThread::default().boxed_clone(),
cx,
);
}),
)
.child(agent_extra_menu),
), ),
) )
} }
@ -1981,9 +1894,6 @@ impl AgentPanel {
message_editor, message_editor,
.. ..
} => (thread.read(cx), message_editor.read(cx)), } => (thread.read(cx), message_editor.read(cx)),
ActiveView::AcpThread { .. } => {
return None;
}
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => { ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
return None; return None;
} }
@ -2122,9 +2032,6 @@ impl AgentPanel {
return false; return false;
} }
} }
ActiveView::AcpThread { .. } => {
return false;
}
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => { ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
return false; return false;
} }
@ -2709,9 +2616,6 @@ impl AgentPanel {
) -> Option<AnyElement> { ) -> Option<AnyElement> {
let active_thread = match &self.active_view { let active_thread = match &self.active_view {
ActiveView::Thread { thread, .. } => thread, ActiveView::Thread { thread, .. } => thread,
ActiveView::AcpThread { .. } => {
return None;
}
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => { ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
return None; return None;
} }
@ -3058,9 +2962,6 @@ impl AgentPanel {
.detach(); .detach();
}); });
} }
ActiveView::AcpThread { .. } => {
unimplemented!()
}
ActiveView::TextThread { context_editor, .. } => { ActiveView::TextThread { context_editor, .. } => {
context_editor.update(cx, |context_editor, cx| { context_editor.update(cx, |context_editor, cx| {
TextThreadEditor::insert_dragged_files( TextThreadEditor::insert_dragged_files(
@ -3079,10 +2980,8 @@ impl AgentPanel {
fn key_context(&self) -> KeyContext { fn key_context(&self) -> KeyContext {
let mut key_context = KeyContext::new_with_defaults(); let mut key_context = KeyContext::new_with_defaults();
key_context.add("AgentPanel"); key_context.add("AgentPanel");
match &self.active_view { if matches!(self.active_view, ActiveView::TextThread { .. }) {
ActiveView::AcpThread { .. } => key_context.add("acp_thread"), key_context.add("prompt_editor");
ActiveView::TextThread { .. } => key_context.add("prompt_editor"),
ActiveView::Thread { .. } | ActiveView::History | ActiveView::Configuration => {}
} }
key_context key_context
} }
@ -3136,7 +3035,6 @@ impl Render for AgentPanel {
}); });
this.continue_conversation(window, cx); this.continue_conversation(window, cx);
} }
ActiveView::AcpThread { .. } => {}
ActiveView::TextThread { .. } ActiveView::TextThread { .. }
| ActiveView::History | ActiveView::History
| ActiveView::Configuration => {} | ActiveView::Configuration => {}
@ -3178,10 +3076,6 @@ impl Render for AgentPanel {
}) })
.child(h_flex().child(message_editor.clone())) .child(h_flex().child(message_editor.clone()))
.child(self.render_drag_target(cx)), .child(self.render_drag_target(cx)),
ActiveView::AcpThread { thread_view, .. } => parent
.relative()
.child(thread_view.clone())
.child(self.render_drag_target(cx)),
ActiveView::History => parent.child(self.history.clone()), ActiveView::History => parent.child(self.history.clone()),
ActiveView::TextThread { ActiveView::TextThread {
context_editor, context_editor,

View file

@ -1,4 +1,3 @@
mod acp;
mod active_thread; mod active_thread;
mod agent_configuration; mod agent_configuration;
mod agent_diff; mod agent_diff;
@ -57,8 +56,6 @@ actions!(
[ [
/// Creates a new text-based conversation thread. /// Creates a new text-based conversation thread.
NewTextThread, NewTextThread,
/// Creates a new external agent conversation thread.
NewAcpThread,
/// Toggles the context picker interface for adding files, symbols, or other context. /// Toggles the context picker interface for adding files, symbols, or other context.
ToggleContextPicker, ToggleContextPicker,
/// Toggles the navigation menu for switching between threads and views. /// Toggles the navigation menu for switching between threads and views.
@ -79,6 +76,8 @@ actions!(
AddContextServer, AddContextServer,
/// Removes the currently selected thread. /// Removes the currently selected thread.
RemoveSelectedThread, RemoveSelectedThread,
/// Starts a chat conversation with the agent.
Chat,
/// Starts a chat conversation with follow-up enabled. /// Starts a chat conversation with follow-up enabled.
ChatWithFollow, ChatWithFollow,
/// Cycles to the next inline assist suggestion. /// Cycles to the next inline assist suggestion.

View file

@ -475,7 +475,6 @@ impl CodegenAlternative {
stop: Vec::new(), stop: Vec::new(),
temperature, temperature,
messages: vec![request_message], messages: vec![request_message],
thinking_allowed: false,
} }
})) }))
} }

View file

@ -1,6 +1,6 @@
mod completion_provider; mod completion_provider;
mod fetch_context_picker; mod fetch_context_picker;
pub(crate) mod file_context_picker; mod file_context_picker;
mod rules_context_picker; mod rules_context_picker;
mod symbol_context_picker; mod symbol_context_picker;
mod thread_context_picker; mod thread_context_picker;

View file

@ -660,6 +660,7 @@ impl InlineAssistant {
height: Some(prompt_editor_height), height: Some(prompt_editor_height),
render: build_assist_editor_renderer(prompt_editor), render: build_assist_editor_renderer(prompt_editor),
priority: 0, priority: 0,
render_in_minimap: false,
}, },
BlockProperties { BlockProperties {
style: BlockStyle::Sticky, style: BlockStyle::Sticky,
@ -674,6 +675,7 @@ impl InlineAssistant {
.into_any_element() .into_any_element()
}), }),
priority: 0, priority: 0,
render_in_minimap: false,
}, },
]; ];
@ -1449,6 +1451,7 @@ impl InlineAssistant {
.into_any_element() .into_any_element()
}), }),
priority: 0, priority: 0,
render_in_minimap: false,
}); });
} }

View file

@ -2,7 +2,6 @@ use std::collections::BTreeMap;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use crate::agent_diff::AgentDiffThread;
use crate::agent_model_selector::AgentModelSelector; use crate::agent_model_selector::AgentModelSelector;
use crate::language_model_selector::ToggleModelSelector; use crate::language_model_selector::ToggleModelSelector;
use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip}; use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
@ -48,14 +47,13 @@ use ui::{
}; };
use util::ResultExt as _; use util::ResultExt as _;
use workspace::{CollaboratorId, Workspace}; use workspace::{CollaboratorId, Workspace};
use zed_actions::agent::Chat;
use zed_llm_client::CompletionIntent; use zed_llm_client::CompletionIntent;
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention}; use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind}; use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
use crate::profile_selector::ProfileSelector; use crate::profile_selector::ProfileSelector;
use crate::{ use crate::{
ActiveThread, AgentDiffPane, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll, ActiveThread, AgentDiffPane, Chat, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll,
ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode, ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode,
ToggleContextPicker, ToggleProfileSelector, register_agent_preview, ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
}; };
@ -476,12 +474,9 @@ impl MessageEditor {
window: &mut Window, window: &mut Window,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
if let Ok(diff) = AgentDiffPane::deploy( if let Ok(diff) =
AgentDiffThread::Native(self.thread.clone()), AgentDiffPane::deploy(self.thread.clone(), self.workspace.clone(), window, cx)
self.workspace.clone(), {
window,
cx,
) {
let path_key = multi_buffer::PathKey::for_buffer(&buffer, cx); let path_key = multi_buffer::PathKey::for_buffer(&buffer, cx);
diff.update(cx, |diff, cx| diff.move_to_path(path_key, window, cx)); diff.update(cx, |diff, cx| diff.move_to_path(path_key, window, cx));
} }
@ -1458,7 +1453,6 @@ impl MessageEditor {
tool_choice: None, tool_choice: None,
stop: vec![], stop: vec![],
temperature: AgentSettings::temperature_for_model(&model.model, cx), temperature: AgentSettings::temperature_for_model(&model.model, cx),
thinking_allowed: true,
}; };
Some(model.model.count_tokens(request, cx)) Some(model.model.count_tokens(request, cx))
@ -1626,7 +1620,6 @@ impl Render for MessageEditor {
v_flex() v_flex()
.size_full() .size_full()
.bg(cx.theme().colors().panel_background)
.when(changed_buffers.len() > 0, |parent| { .when(changed_buffers.len() > 0, |parent| {
parent.child(self.render_edits_bar(&changed_buffers, window, cx)) parent.child(self.render_edits_bar(&changed_buffers, window, cx))
}) })

View file

@ -297,7 +297,6 @@ impl TerminalInlineAssistant {
tool_choice: None, tool_choice: None,
stop: Vec::new(), stop: Vec::new(),
temperature, temperature,
thinking_allowed: false,
} }
})) }))
} }

View file

@ -1256,6 +1256,7 @@ impl TextThreadEditor {
), ),
priority: usize::MAX, priority: usize::MAX,
render: render_block(MessageMetadata::from(message)), render: render_block(MessageMetadata::from(message)),
render_in_minimap: false,
}; };
let mut new_blocks = vec![]; let mut new_blocks = vec![];
let mut block_index_to_message = vec![]; let mut block_index_to_message = vec![];
@ -1857,6 +1858,7 @@ impl TextThreadEditor {
.into_any_element() .into_any_element()
}), }),
priority: 0, priority: 0,
render_in_minimap: false,
}) })
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View file

@ -2293,7 +2293,6 @@ impl AssistantContext {
tool_choice: None, tool_choice: None,
stop: Vec::new(), stop: Vec::new(),
temperature: model.and_then(|model| AgentSettings::temperature_for_model(model, cx)), temperature: model.and_then(|model| AgentSettings::temperature_for_model(model, cx)),
thinking_allowed: true,
}; };
for message in self.messages(cx) { for message in self.messages(cx) {
if message.status != MessageStatus::Done { if message.status != MessageStatus::Done {

View file

@ -34,11 +34,6 @@ impl ExtensionSlashCommandProxy for SlashCommandRegistryProxy {
self.slash_command_registry self.slash_command_registry
.register_command(ExtensionSlashCommand::new(extension, command), false) .register_command(ExtensionSlashCommand::new(extension, command), false)
} }
fn unregister_slash_command(&self, command_name: Arc<str>) {
self.slash_command_registry
.unregister_command_by_name(&command_name)
}
} }
/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`]. /// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].

View file

@ -40,7 +40,6 @@ collections = { workspace = true, features = ["test-support"] }
clock = { workspace = true, features = ["test-support"] } clock = { workspace = true, features = ["test-support"] }
ctor.workspace = true ctor.workspace = true
gpui = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] }
indoc.workspace = true
language = { workspace = true, features = ["test-support"] } language = { workspace = true, features = ["test-support"] }
language_model = { workspace = true, features = ["test-support"] } language_model = { workspace = true, features = ["test-support"] }
log.workspace = true log.workspace = true

View file

@ -8,10 +8,7 @@ use language::{Anchor, Buffer, BufferEvent, DiskState, Point, ToPoint};
use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle}; use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle};
use std::{cmp, ops::Range, sync::Arc}; use std::{cmp, ops::Range, sync::Arc};
use text::{Edit, Patch, Rope}; use text::{Edit, Patch, Rope};
use util::{ use util::RangeExt;
RangeExt, ResultExt as _,
paths::{PathStyle, RemotePathBuf},
};
/// Tracks actions performed by tools in a thread /// Tracks actions performed by tools in a thread
pub struct ActionLog { pub struct ActionLog {
@ -21,6 +18,8 @@ pub struct ActionLog {
edited_since_project_diagnostics_check: bool, edited_since_project_diagnostics_check: bool,
/// The project this action log is associated with /// The project this action log is associated with
project: Entity<Project>, project: Entity<Project>,
/// Tracks which buffer versions have already been notified as changed externally
notified_versions: BTreeMap<Entity<Buffer>, clock::Global>,
} }
impl ActionLog { impl ActionLog {
@ -30,6 +29,7 @@ impl ActionLog {
tracked_buffers: BTreeMap::default(), tracked_buffers: BTreeMap::default(),
edited_since_project_diagnostics_check: false, edited_since_project_diagnostics_check: false,
project, project,
notified_versions: BTreeMap::default(),
} }
} }
@ -47,71 +47,6 @@ impl ActionLog {
self.edited_since_project_diagnostics_check self.edited_since_project_diagnostics_check
} }
pub fn latest_snapshot(&self, buffer: &Entity<Buffer>) -> Option<text::BufferSnapshot> {
Some(self.tracked_buffers.get(buffer)?.snapshot.clone())
}
pub fn has_unnotified_user_edits(&self) -> bool {
self.tracked_buffers
.values()
.any(|tracked| tracked.has_unnotified_user_edits)
}
/// Return a unified diff patch with user edits made since last read or notification
pub fn unnotified_user_edits(&self, cx: &Context<Self>) -> Option<String> {
if !self.has_unnotified_user_edits() {
return None;
}
let unified_diff = self
.tracked_buffers
.values()
.filter_map(|tracked| {
if !tracked.has_unnotified_user_edits {
return None;
}
let text_with_latest_user_edits = tracked.diff_base.to_string();
let text_with_last_seen_user_edits = tracked.last_seen_base.to_string();
if text_with_latest_user_edits == text_with_last_seen_user_edits {
return None;
}
let patch = language::unified_diff(
&text_with_last_seen_user_edits,
&text_with_latest_user_edits,
);
let buffer = tracked.buffer.clone();
let file_path = buffer
.read(cx)
.file()
.map(|file| RemotePathBuf::new(file.full_path(cx), PathStyle::Posix).to_proto())
.unwrap_or_else(|| format!("buffer_{}", buffer.entity_id()));
let mut result = String::new();
result.push_str(&format!("--- a/{}\n", file_path));
result.push_str(&format!("+++ b/{}\n", file_path));
result.push_str(&patch);
Some(result)
})
.collect::<Vec<_>>()
.join("\n\n");
Some(unified_diff)
}
/// Return a unified diff patch with user edits made since last read/notification
/// and mark them as notified
pub fn flush_unnotified_user_edits(&mut self, cx: &Context<Self>) -> Option<String> {
let patch = self.unnotified_user_edits(cx);
self.tracked_buffers.values_mut().for_each(|tracked| {
tracked.has_unnotified_user_edits = false;
tracked.last_seen_base = tracked.diff_base.clone();
});
patch
}
fn track_buffer_internal( fn track_buffer_internal(
&mut self, &mut self,
buffer: Entity<Buffer>, buffer: Entity<Buffer>,
@ -120,6 +55,7 @@ impl ActionLog {
) -> &mut TrackedBuffer { ) -> &mut TrackedBuffer {
let status = if is_created { let status = if is_created {
if let Some(tracked) = self.tracked_buffers.remove(&buffer) { if let Some(tracked) = self.tracked_buffers.remove(&buffer) {
self.notified_versions.remove(&buffer);
match tracked.status { match tracked.status {
TrackedBufferStatus::Created { TrackedBufferStatus::Created {
existing_file_content, existing_file_content,
@ -161,31 +97,26 @@ impl ActionLog {
let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx)); let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
let (diff_update_tx, diff_update_rx) = mpsc::unbounded(); let (diff_update_tx, diff_update_rx) = mpsc::unbounded();
let diff_base; let diff_base;
let last_seen_base;
let unreviewed_edits; let unreviewed_edits;
if is_created { if is_created {
diff_base = Rope::default(); diff_base = Rope::default();
last_seen_base = Rope::default();
unreviewed_edits = Patch::new(vec![Edit { unreviewed_edits = Patch::new(vec![Edit {
old: 0..1, old: 0..1,
new: 0..text_snapshot.max_point().row + 1, new: 0..text_snapshot.max_point().row + 1,
}]) }])
} else { } else {
diff_base = buffer.read(cx).as_rope().clone(); diff_base = buffer.read(cx).as_rope().clone();
last_seen_base = diff_base.clone();
unreviewed_edits = Patch::default(); unreviewed_edits = Patch::default();
} }
TrackedBuffer { TrackedBuffer {
buffer: buffer.clone(), buffer: buffer.clone(),
diff_base, diff_base,
last_seen_base,
unreviewed_edits, unreviewed_edits,
snapshot: text_snapshot.clone(), snapshot: text_snapshot.clone(),
status, status,
version: buffer.read(cx).version(), version: buffer.read(cx).version(),
diff, diff,
diff_update: diff_update_tx, diff_update: diff_update_tx,
has_unnotified_user_edits: false,
_open_lsp_handle: open_lsp_handle, _open_lsp_handle: open_lsp_handle,
_maintain_diff: cx.spawn({ _maintain_diff: cx.spawn({
let buffer = buffer.clone(); let buffer = buffer.clone();
@ -239,6 +170,7 @@ impl ActionLog {
// If the buffer had been edited by a tool, but it got // If the buffer had been edited by a tool, but it got
// deleted externally, we want to stop tracking it. // deleted externally, we want to stop tracking it.
self.tracked_buffers.remove(&buffer); self.tracked_buffers.remove(&buffer);
self.notified_versions.remove(&buffer);
} }
cx.notify(); cx.notify();
} }
@ -252,6 +184,7 @@ impl ActionLog {
// resurrected externally, we want to clear the edits we // resurrected externally, we want to clear the edits we
// were tracking and reset the buffer's state. // were tracking and reset the buffer's state.
self.tracked_buffers.remove(&buffer); self.tracked_buffers.remove(&buffer);
self.notified_versions.remove(&buffer);
self.track_buffer_internal(buffer, false, cx); self.track_buffer_internal(buffer, false, cx);
} }
cx.notify(); cx.notify();
@ -325,23 +258,19 @@ impl ActionLog {
buffer_snapshot: text::BufferSnapshot, buffer_snapshot: text::BufferSnapshot,
cx: &mut AsyncApp, cx: &mut AsyncApp,
) -> Result<()> { ) -> Result<()> {
let rebase = this.update(cx, |this, cx| { let rebase = this.read_with(cx, |this, cx| {
let tracked_buffer = this let tracked_buffer = this
.tracked_buffers .tracked_buffers
.get_mut(buffer) .get(buffer)
.context("buffer not tracked")?; .context("buffer not tracked")?;
if let ChangeAuthor::User = author {
tracked_buffer.has_unnotified_user_edits = true;
}
let rebase = cx.background_spawn({ let rebase = cx.background_spawn({
let mut base_text = tracked_buffer.diff_base.clone(); let mut base_text = tracked_buffer.diff_base.clone();
let old_snapshot = tracked_buffer.snapshot.clone(); let old_snapshot = tracked_buffer.snapshot.clone();
let new_snapshot = buffer_snapshot.clone(); let new_snapshot = buffer_snapshot.clone();
let unreviewed_edits = tracked_buffer.unreviewed_edits.clone(); let unreviewed_edits = tracked_buffer.unreviewed_edits.clone();
let edits = diff_snapshots(&old_snapshot, &new_snapshot);
async move { async move {
let edits = diff_snapshots(&old_snapshot, &new_snapshot);
if let ChangeAuthor::User = author { if let ChangeAuthor::User = author {
apply_non_conflicting_edits( apply_non_conflicting_edits(
&unreviewed_edits, &unreviewed_edits,
@ -561,6 +490,7 @@ impl ActionLog {
match tracked_buffer.status { match tracked_buffer.status {
TrackedBufferStatus::Created { .. } => { TrackedBufferStatus::Created { .. } => {
self.tracked_buffers.remove(&buffer); self.tracked_buffers.remove(&buffer);
self.notified_versions.remove(&buffer);
cx.notify(); cx.notify();
} }
TrackedBufferStatus::Modified => { TrackedBufferStatus::Modified => {
@ -586,6 +516,7 @@ impl ActionLog {
match tracked_buffer.status { match tracked_buffer.status {
TrackedBufferStatus::Deleted => { TrackedBufferStatus::Deleted => {
self.tracked_buffers.remove(&buffer); self.tracked_buffers.remove(&buffer);
self.notified_versions.remove(&buffer);
cx.notify(); cx.notify();
} }
_ => { _ => {
@ -694,6 +625,7 @@ impl ActionLog {
}; };
self.tracked_buffers.remove(&buffer); self.tracked_buffers.remove(&buffer);
self.notified_versions.remove(&buffer);
cx.notify(); cx.notify();
task task
} }
@ -707,6 +639,7 @@ impl ActionLog {
// Clear all tracked edits for this buffer and start over as if we just read it. // Clear all tracked edits for this buffer and start over as if we just read it.
self.tracked_buffers.remove(&buffer); self.tracked_buffers.remove(&buffer);
self.notified_versions.remove(&buffer);
self.buffer_read(buffer.clone(), cx); self.buffer_read(buffer.clone(), cx);
cx.notify(); cx.notify();
save save
@ -782,22 +715,6 @@ impl ActionLog {
cx.notify(); cx.notify();
} }
pub fn reject_all_edits(&mut self, cx: &mut Context<Self>) -> Task<()> {
let futures = self.changed_buffers(cx).into_keys().map(|buffer| {
let reject = self.reject_edits_in_ranges(buffer, vec![Anchor::MIN..Anchor::MAX], cx);
async move {
reject.await.log_err();
}
});
let task = futures::future::join_all(futures);
cx.spawn(async move |_, _| {
task.await;
})
}
/// Returns the set of buffers that contain edits that haven't been reviewed by the user. /// Returns the set of buffers that contain edits that haven't been reviewed by the user.
pub fn changed_buffers(&self, cx: &App) -> BTreeMap<Entity<Buffer>, Entity<BufferDiff>> { pub fn changed_buffers(&self, cx: &App) -> BTreeMap<Entity<Buffer>, Entity<BufferDiff>> {
self.tracked_buffers self.tracked_buffers
@ -807,6 +724,33 @@ impl ActionLog {
.collect() .collect()
} }
/// Returns stale buffers that haven't been notified yet
pub fn unnotified_stale_buffers<'a>(
&'a self,
cx: &'a App,
) -> impl Iterator<Item = &'a Entity<Buffer>> {
self.stale_buffers(cx).filter(|buffer| {
let buffer_entity = buffer.read(cx);
self.notified_versions
.get(buffer)
.map_or(true, |notified_version| {
*notified_version != buffer_entity.version
})
})
}
/// Marks the given buffers as notified at their current versions
pub fn mark_buffers_as_notified(
&mut self,
buffers: impl IntoIterator<Item = Entity<Buffer>>,
cx: &App,
) {
for buffer in buffers {
let version = buffer.read(cx).version.clone();
self.notified_versions.insert(buffer, version);
}
}
/// Iterate over buffers changed since last read or edited by the model /// Iterate over buffers changed since last read or edited by the model
pub fn stale_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = &'a Entity<Buffer>> { pub fn stale_buffers<'a>(&'a self, cx: &'a App) -> impl Iterator<Item = &'a Entity<Buffer>> {
self.tracked_buffers self.tracked_buffers
@ -950,14 +894,12 @@ enum TrackedBufferStatus {
struct TrackedBuffer { struct TrackedBuffer {
buffer: Entity<Buffer>, buffer: Entity<Buffer>,
diff_base: Rope, diff_base: Rope,
last_seen_base: Rope,
unreviewed_edits: Patch<u32>, unreviewed_edits: Patch<u32>,
status: TrackedBufferStatus, status: TrackedBufferStatus,
version: clock::Global, version: clock::Global,
diff: Entity<BufferDiff>, diff: Entity<BufferDiff>,
snapshot: text::BufferSnapshot, snapshot: text::BufferSnapshot,
diff_update: mpsc::UnboundedSender<(ChangeAuthor, text::BufferSnapshot)>, diff_update: mpsc::UnboundedSender<(ChangeAuthor, text::BufferSnapshot)>,
has_unnotified_user_edits: bool,
_open_lsp_handle: OpenLspBufferHandle, _open_lsp_handle: OpenLspBufferHandle,
_maintain_diff: Task<()>, _maintain_diff: Task<()>,
_subscription: Subscription, _subscription: Subscription,
@ -988,7 +930,6 @@ mod tests {
use super::*; use super::*;
use buffer_diff::DiffHunkStatusKind; use buffer_diff::DiffHunkStatusKind;
use gpui::TestAppContext; use gpui::TestAppContext;
use indoc::indoc;
use language::Point; use language::Point;
use project::{FakeFs, Fs, Project, RemoveOptions}; use project::{FakeFs, Fs, Project, RemoveOptions};
use rand::prelude::*; use rand::prelude::*;
@ -1271,110 +1212,6 @@ mod tests {
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]); assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
} }
#[gpui::test(iterations = 10)]
async fn test_user_edits_notifications(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/dir"),
json!({"file": indoc! {"
abc
def
ghi
jkl
mno"}}),
)
.await;
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let file_path = project
.read_with(cx, |project, cx| project.find_project_path("dir/file", cx))
.unwrap();
let buffer = project
.update(cx, |project, cx| project.open_buffer(file_path, cx))
.await
.unwrap();
// Agent edits
cx.update(|cx| {
action_log.update(cx, |log, cx| log.buffer_read(buffer.clone(), cx));
buffer.update(cx, |buffer, cx| {
buffer
.edit([(Point::new(1, 2)..Point::new(2, 3), "F\nGHI")], None, cx)
.unwrap()
});
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
});
cx.run_until_parked();
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.text()),
indoc! {"
abc
deF
GHI
jkl
mno"}
);
assert_eq!(
unreviewed_hunks(&action_log, cx),
vec![(
buffer.clone(),
vec![HunkStatus {
range: Point::new(1, 0)..Point::new(3, 0),
diff_status: DiffHunkStatusKind::Modified,
old_text: "def\nghi\n".into(),
}],
)]
);
// User edits
buffer.update(cx, |buffer, cx| {
buffer.edit(
[
(Point::new(0, 2)..Point::new(0, 2), "X"),
(Point::new(3, 0)..Point::new(3, 0), "Y"),
],
None,
cx,
)
});
cx.run_until_parked();
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.text()),
indoc! {"
abXc
deF
GHI
Yjkl
mno"}
);
// User edits should be stored separately from agent's
let user_edits = action_log.update(cx, |log, cx| log.unnotified_user_edits(cx));
assert_eq!(
user_edits.expect("should have some user edits"),
indoc! {"
--- a/dir/file
+++ b/dir/file
@@ -1,5 +1,5 @@
-abc
+abXc
def
ghi
-jkl
+Yjkl
mno
"}
);
action_log.update(cx, |log, cx| {
log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(1, 0), cx)
});
cx.run_until_parked();
assert_eq!(unreviewed_hunks(&action_log, cx), vec![]);
}
#[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
async fn test_creating_files(cx: &mut TestAppContext) { async fn test_creating_files(cx: &mut TestAppContext) {
init_test(cx); init_test(cx);
@ -2364,61 +2201,4 @@ mod tests {
.collect() .collect()
}) })
} }
#[gpui::test]
async fn test_format_patch(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
path!("/dir"),
json!({"test.txt": "line 1\nline 2\nline 3\n"}),
)
.await;
let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
let action_log = cx.new(|_| ActionLog::new(project.clone()));
let file_path = project
.read_with(cx, |project, cx| {
project.find_project_path("dir/test.txt", cx)
})
.unwrap();
let buffer = project
.update(cx, |project, cx| project.open_buffer(file_path, cx))
.await
.unwrap();
cx.update(|cx| {
// Track the buffer and mark it as read first
action_log.update(cx, |log, cx| {
log.buffer_read(buffer.clone(), cx);
});
// Make some edits to create a patch
buffer.update(cx, |buffer, cx| {
buffer
.edit([(Point::new(1, 0)..Point::new(1, 6), "CHANGED")], None, cx)
.unwrap(); // Replace "line2" with "CHANGED"
});
});
cx.run_until_parked();
// Get the patch
let patch = action_log.update(cx, |log, cx| log.unnotified_user_edits(cx));
// Verify the patch format contains expected unified diff elements
assert_eq!(
patch.unwrap(),
indoc! {"
--- a/dir/test.txt
+++ b/dir/test.txt
@@ -1,3 +1,3 @@
line 1
-line 2
+CHANGED
line 3
"}
);
}
} }

View file

@ -57,7 +57,7 @@ impl Tool for CopyPathTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolCopy IconName::Clipboard
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {

View file

@ -46,7 +46,7 @@ impl Tool for CreateDirectoryTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolFolder IconName::Folder
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {

View file

@ -46,7 +46,7 @@ impl Tool for DeletePathTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolDeleteFile IconName::FileDelete
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {

View file

@ -59,7 +59,7 @@ impl Tool for DiagnosticsTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolDiagnostics IconName::XCircle
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {

View file

@ -719,7 +719,6 @@ impl EditAgent {
tools, tools,
stop: Vec::new(), stop: Vec::new(),
temperature: None, temperature: None,
thinking_allowed: true,
}; };
Ok(self.model.stream_completion_text(request, cx).await?.stream) Ok(self.model.stream_completion_text(request, cx).await?.stream)

View file

@ -365,23 +365,17 @@ fn eval_disable_cursor_blinking() {
// Model | Pass rate // Model | Pass rate
// ============================================ // ============================================
// //
// claude-3.7-sonnet | 0.59 (2025-07-14) // claude-3.7-sonnet | 0.99 (2025-06-14)
// claude-sonnet-4 | 0.81 (2025-07-14) // claude-sonnet-4 | 0.85 (2025-06-14)
// gemini-2.5-pro | 0.95 (2025-07-14) // gemini-2.5-pro-preview-latest | 0.97 (2025-06-16)
// gemini-2.5-flash-preview-04-17 | 0.78 (2025-07-14) // gemini-2.5-flash-preview-04-17 |
// gpt-4.1 | 0.00 (2025-07-14) (follows edit_description too literally) // gpt-4.1 |
let input_file_path = "root/editor.rs"; let input_file_path = "root/editor.rs";
let input_file_content = include_str!("evals/fixtures/disable_cursor_blinking/before.rs"); let input_file_content = include_str!("evals/fixtures/disable_cursor_blinking/before.rs");
let edit_description = "Comment out the call to `BlinkManager::enable`"; let edit_description = "Comment out the call to `BlinkManager::enable`";
let possible_diffs = vec![
include_str!("evals/fixtures/disable_cursor_blinking/possible-01.diff"),
include_str!("evals/fixtures/disable_cursor_blinking/possible-02.diff"),
include_str!("evals/fixtures/disable_cursor_blinking/possible-03.diff"),
include_str!("evals/fixtures/disable_cursor_blinking/possible-04.diff"),
];
eval( eval(
100, 100,
0.51, 0.95,
0.05, 0.05,
EvalInput::from_conversation( EvalInput::from_conversation(
vec![ vec![
@ -439,7 +433,11 @@ fn eval_disable_cursor_blinking() {
), ),
], ],
Some(input_file_content.into()), Some(input_file_content.into()),
EvalAssertion::assert_diff_any(possible_diffs), EvalAssertion::judge_diff(indoc! {"
- Calls to BlinkManager in `observe_window_activation` were commented out
- The call to `blink_manager.enable` above the call to show_cursor_names was commented out
- All the edits have valid indentation
"}),
), ),
); );
} }
@ -1265,7 +1263,6 @@ impl EvalAssertion {
content: vec![prompt.into()], content: vec![prompt.into()],
cache: false, cache: false,
}], }],
thinking_allowed: true,
..Default::default() ..Default::default()
}; };
let mut response = retry_on_rate_limit(async || { let mut response = retry_on_rate_limit(async || {
@ -1602,7 +1599,6 @@ impl EditAgentTest {
let conversation = LanguageModelRequest { let conversation = LanguageModelRequest {
messages, messages,
tools, tools,
thinking_allowed: true,
..Default::default() ..Default::default()
}; };

View file

@ -1,28 +0,0 @@
--- before.rs 2025-07-07 11:37:48.434629001 +0300
+++ expected.rs 2025-07-14 10:33:53.346906775 +0300
@@ -1780,11 +1780,11 @@
cx.observe_window_activation(window, |editor, window, cx| {
let active = window.is_window_active();
editor.blink_manager.update(cx, |blink_manager, cx| {
- if active {
- blink_manager.enable(cx);
- } else {
- blink_manager.disable(cx);
- }
+ // if active {
+ // blink_manager.enable(cx);
+ // } else {
+ // blink_manager.disable(cx);
+ // }
});
}),
],
@@ -18463,7 +18463,7 @@
}
self.blink_manager.update(cx, |blink_manager, cx| {
- blink_manager.enable(cx);
+ // blink_manager.enable(cx);
});
self.show_cursor_names(window, cx);
self.buffer.update(cx, |buffer, cx| {

View file

@ -1,29 +0,0 @@
@@ -1778,13 +1778,13 @@
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
cx.observe_window_activation(window, |editor, window, cx| {
- let active = window.is_window_active();
+ // let active = window.is_window_active();
editor.blink_manager.update(cx, |blink_manager, cx| {
- if active {
- blink_manager.enable(cx);
- } else {
- blink_manager.disable(cx);
- }
+ // if active {
+ // blink_manager.enable(cx);
+ // } else {
+ // blink_manager.disable(cx);
+ // }
});
}),
],
@@ -18463,7 +18463,7 @@
}
self.blink_manager.update(cx, |blink_manager, cx| {
- blink_manager.enable(cx);
+ // blink_manager.enable(cx);
});
self.show_cursor_names(window, cx);
self.buffer.update(cx, |buffer, cx| {

View file

@ -1,34 +0,0 @@
@@ -1774,17 +1774,17 @@
cx.observe(&buffer, Self::on_buffer_changed),
cx.subscribe_in(&buffer, window, Self::on_buffer_event),
cx.observe_in(&display_map, window, Self::on_display_map_changed),
- cx.observe(&blink_manager, |_, _, cx| cx.notify()),
+ // cx.observe(&blink_manager, |_, _, cx| cx.notify()),
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
cx.observe_window_activation(window, |editor, window, cx| {
- let active = window.is_window_active();
+ // let active = window.is_window_active();
editor.blink_manager.update(cx, |blink_manager, cx| {
- if active {
- blink_manager.enable(cx);
- } else {
- blink_manager.disable(cx);
- }
+ // if active {
+ // blink_manager.enable(cx);
+ // } else {
+ // blink_manager.disable(cx);
+ // }
});
}),
],
@@ -18463,7 +18463,7 @@
}
self.blink_manager.update(cx, |blink_manager, cx| {
- blink_manager.enable(cx);
+ // blink_manager.enable(cx);
});
self.show_cursor_names(window, cx);
self.buffer.update(cx, |buffer, cx| {

View file

@ -1,33 +0,0 @@
@@ -1774,17 +1774,17 @@
cx.observe(&buffer, Self::on_buffer_changed),
cx.subscribe_in(&buffer, window, Self::on_buffer_event),
cx.observe_in(&display_map, window, Self::on_display_map_changed),
- cx.observe(&blink_manager, |_, _, cx| cx.notify()),
+ // cx.observe(&blink_manager, |_, _, cx| cx.notify()),
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
cx.observe_window_activation(window, |editor, window, cx| {
let active = window.is_window_active();
editor.blink_manager.update(cx, |blink_manager, cx| {
- if active {
- blink_manager.enable(cx);
- } else {
- blink_manager.disable(cx);
- }
+ // if active {
+ // blink_manager.enable(cx);
+ // } else {
+ // blink_manager.disable(cx);
+ // }
});
}),
],
@@ -18463,7 +18463,7 @@
}
self.blink_manager.update(cx, |blink_manager, cx| {
- blink_manager.enable(cx);
+ // blink_manager.enable(cx);
});
self.show_cursor_names(window, cx);
self.buffer.update(cx, |buffer, cx| {

View file

@ -139,7 +139,7 @@ impl Tool for EditFileTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolPencil IconName::Pencil
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
@ -783,8 +783,8 @@ impl ToolCard for EditFileToolCard {
.child( .child(
h_flex() h_flex()
.child( .child(
Icon::new(IconName::ToolPencil) Icon::new(IconName::Pencil)
.size(IconSize::Small) .size(IconSize::XSmall)
.color(Color::Muted), .color(Color::Muted),
) )
.child( .child(

View file

@ -69,9 +69,10 @@ impl FetchTool {
.to_str() .to_str()
.context("invalid Content-Type header")?; .context("invalid Content-Type header")?;
let content_type = match content_type { let content_type = match content_type {
"text/html" | "application/xhtml+xml" => ContentType::Html, "text/html" => ContentType::Html,
"text/plain" => ContentType::Plaintext,
"application/json" => ContentType::Json, "application/json" => ContentType::Json,
_ => ContentType::Plaintext, _ => ContentType::Html,
}; };
match content_type { match content_type {
@ -129,7 +130,7 @@ impl Tool for FetchTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolWeb IconName::Globe
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {

View file

@ -68,7 +68,7 @@ impl Tool for FindPathTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolSearch IconName::SearchCode
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
@ -313,7 +313,7 @@ impl ToolCard for FindPathToolCard {
.mb_2() .mb_2()
.gap_1() .gap_1()
.child( .child(
ToolCallCardHeader::new(IconName::ToolSearch, matches_label) ToolCallCardHeader::new(IconName::SearchCode, matches_label)
.with_code_path(&self.glob) .with_code_path(&self.glob)
.disclosure_slot( .disclosure_slot(
Disclosure::new("path-search-disclosure", self.expanded) Disclosure::new("path-search-disclosure", self.expanded)

View file

@ -70,7 +70,7 @@ impl Tool for GrepTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolRegex IconName::Regex
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {

View file

@ -58,7 +58,7 @@ impl Tool for ListDirectoryTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolFolder IconName::Folder
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {

View file

@ -6,6 +6,7 @@ use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchem
use project::Project; use project::Project;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Write as _;
use std::sync::Arc; use std::sync::Arc;
use ui::IconName; use ui::IconName;
@ -30,7 +31,7 @@ impl Tool for ProjectNotificationsTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolNotification IconName::Envelope
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
@ -51,22 +52,34 @@ impl Tool for ProjectNotificationsTool {
_window: Option<AnyWindowHandle>, _window: Option<AnyWindowHandle>,
cx: &mut App, cx: &mut App,
) -> ToolResult { ) -> ToolResult {
let Some(user_edits_diff) = let mut stale_files = String::new();
action_log.update(cx, |log, cx| log.flush_unnotified_user_edits(cx)) let mut notified_buffers = Vec::new();
else {
return result("No new notifications"); for stale_file in action_log.read(cx).unnotified_stale_buffers(cx) {
if let Some(file) = stale_file.read(cx).file() {
writeln!(&mut stale_files, "- {}", file.path().display()).ok();
notified_buffers.push(stale_file.clone());
}
}
if !notified_buffers.is_empty() {
action_log.update(cx, |log, cx| {
log.mark_buffers_as_notified(notified_buffers, cx);
});
}
let response = if stale_files.is_empty() {
"No new notifications".to_string()
} else {
// NOTE: Changes to this prompt require a symmetric update in the LLM Worker
const HEADER: &str = include_str!("./project_notifications_tool/prompt_header.txt");
format!("{HEADER}{stale_files}").replace("\r\n", "\n")
}; };
// NOTE: Changes to this prompt require a symmetric update in the LLM Worker Task::ready(Ok(response.into())).into()
const HEADER: &str = include_str!("./project_notifications_tool/prompt_header.txt");
result(&format!("{HEADER}\n\n```diff\n{user_edits_diff}\n```\n").replace("\r\n", "\n"))
} }
} }
fn result(response: &str) -> ToolResult {
Task::ready(Ok(response.to_string().into())).into()
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -110,7 +123,6 @@ mod tests {
action_log.update(cx, |log, cx| { action_log.update(cx, |log, cx| {
log.buffer_read(buffer.clone(), cx); log.buffer_read(buffer.clone(), cx);
}); });
cx.run_until_parked();
// Run the tool before any changes // Run the tool before any changes
let tool = Arc::new(ProjectNotificationsTool); let tool = Arc::new(ProjectNotificationsTool);
@ -130,7 +142,6 @@ mod tests {
cx, cx,
) )
}); });
cx.run_until_parked();
let response = result.output.await.unwrap(); let response = result.output.await.unwrap();
let response_text = match &response.content { let response_text = match &response.content {
@ -147,7 +158,6 @@ mod tests {
buffer.update(cx, |buffer, cx| { buffer.update(cx, |buffer, cx| {
buffer.edit([(1..1, "\nChange!\n")], None, cx); buffer.edit([(1..1, "\nChange!\n")], None, cx);
}); });
cx.run_until_parked();
// Run the tool again // Run the tool again
let result = cx.update(|cx| { let result = cx.update(|cx| {
@ -161,7 +171,6 @@ mod tests {
cx, cx,
) )
}); });
cx.run_until_parked();
// This time the buffer is stale, so the tool should return a notification // This time the buffer is stale, so the tool should return a notification
let response = result.output.await.unwrap(); let response = result.output.await.unwrap();
@ -170,12 +179,10 @@ mod tests {
_ => panic!("Expected text response"), _ => panic!("Expected text response"),
}; };
assert!( let expected_content = "[The following is an auto-generated notification; do not reply]\n\nThese files have changed since the last read:\n- code.rs\n";
response_text.contains("These files have changed"), assert_eq!(
"Tool should return the stale buffer notification" response_text.as_str(),
); expected_content,
assert!(
response_text.contains("test/code.rs"),
"Tool should return the stale buffer notification" "Tool should return the stale buffer notification"
); );
@ -191,7 +198,6 @@ mod tests {
cx, cx,
) )
}); });
cx.run_until_parked();
let response = result.output.await.unwrap(); let response = result.output.await.unwrap();
let response_text = match &response.content { let response_text = match &response.content {

View file

@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize};
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
use ui::IconName; use ui::IconName;
use util::markdown::MarkdownInlineCode;
/// If the model requests to read a file whose size exceeds this, then /// If the model requests to read a file whose size exceeds this, then
#[derive(Debug, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Serialize, Deserialize, JsonSchema)]
@ -67,7 +68,7 @@ impl Tool for ReadFileTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolRead IconName::FileSearch
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
@ -77,21 +78,11 @@ impl Tool for ReadFileTool {
fn ui_text(&self, input: &serde_json::Value) -> String { fn ui_text(&self, input: &serde_json::Value) -> String {
match serde_json::from_value::<ReadFileToolInput>(input.clone()) { match serde_json::from_value::<ReadFileToolInput>(input.clone()) {
Ok(input) => { Ok(input) => {
let path = &input.path; let path = MarkdownInlineCode(&input.path);
match (input.start_line, input.end_line) { match (input.start_line, input.end_line) {
(Some(start), Some(end)) => { (Some(start), None) => format!("Read file {path} (from line {start})"),
format!( (Some(start), Some(end)) => format!("Read file {path} (lines {start}-{end})"),
"[Read file `{}` (lines {}-{})](@selection:{}:({}-{}))", _ => format!("Read file {path}"),
path, start, end, path, start, end
)
}
(Some(start), None) => {
format!(
"[Read file `{}` (from line {})](@selection:{}:({}-{}))",
path, start, path, start, start
)
}
_ => format!("[Read file `{}`](@file:{})", path, path),
} }
} }
Err(_) => "Read file".to_string(), Err(_) => "Read file".to_string(),

View file

@ -90,7 +90,7 @@ impl Tool for TerminalTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolTerminal IconName::Terminal
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {

View file

@ -37,7 +37,7 @@ impl Tool for ThinkingTool {
} }
fn icon(&self) -> IconName { fn icon(&self) -> IconName {
IconName::ToolBulb IconName::LightBulb
} }
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> { fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {

View file

@ -82,7 +82,7 @@ impl RenderOnce for ToolCallCardHeader {
.child( .child(
h_flex().h(line_height).justify_center().child( h_flex().h(line_height).justify_center().child(
Icon::new(self.icon) Icon::new(self.icon)
.size(IconSize::Small) .size(IconSize::XSmall)
.color(Color::Muted), .color(Color::Muted),
), ),
) )

View file

@ -143,8 +143,6 @@ impl ToolCard for WebSearchToolCard {
_workspace: WeakEntity<Workspace>, _workspace: WeakEntity<Workspace>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement {
let icon = IconName::ToolWeb;
let header = match self.response.as_ref() { let header = match self.response.as_ref() {
Some(Ok(response)) => { Some(Ok(response)) => {
let text: SharedString = if response.results.len() == 1 { let text: SharedString = if response.results.len() == 1 {
@ -152,12 +150,13 @@ impl ToolCard for WebSearchToolCard {
} else { } else {
format!("{} results", response.results.len()).into() format!("{} results", response.results.len()).into()
}; };
ToolCallCardHeader::new(icon, "Searched the Web").with_secondary_text(text) ToolCallCardHeader::new(IconName::Globe, "Searched the Web")
.with_secondary_text(text)
} }
Some(Err(error)) => { Some(Err(error)) => {
ToolCallCardHeader::new(icon, "Web Search").with_error(error.to_string()) ToolCallCardHeader::new(IconName::Globe, "Web Search").with_error(error.to_string())
} }
None => ToolCallCardHeader::new(icon, "Searching the Web").loading(), None => ToolCallCardHeader::new(IconName::Globe, "Searching the Web").loading(),
}; };
let content = self.response.as_ref().and_then(|response| match response { let content = self.response.as_ref().and_then(|response| match response {

View file

@ -1389,17 +1389,10 @@ impl Room {
let sources = cx.screen_capture_sources(); let sources = cx.screen_capture_sources();
cx.spawn(async move |this, cx| { cx.spawn(async move |this, cx| {
let sources = sources let sources = sources.await??;
.await let source = sources.first().context("no display found")?;
.map_err(|error| error.into())
.and_then(|sources| sources);
let source =
sources.and_then(|sources| sources.into_iter().next().context("no display found"));
let publication = match source { let publication = participant.publish_screenshare_track(&**source, cx).await;
Ok(source) => participant.publish_screenshare_track(&*source, cx).await,
Err(error) => Err(error),
};
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
let live_kit = this let live_kit = this

View file

@ -315,19 +315,19 @@ fn main() -> Result<()> {
}); });
let stdin_pipe_handle: Option<JoinHandle<anyhow::Result<()>>> = let stdin_pipe_handle: Option<JoinHandle<anyhow::Result<()>>> =
stdin_tmp_file.map(|mut tmp_file| { stdin_tmp_file.map(|tmp_file| {
thread::spawn(move || { thread::spawn(move || {
let mut stdin = std::io::stdin().lock(); let stdin = std::io::stdin().lock();
if !io::IsTerminal::is_terminal(&stdin) { if io::IsTerminal::is_terminal(&stdin) {
io::copy(&mut stdin, &mut tmp_file)?; return Ok(());
} }
Ok(()) return pipe_to_tmp(stdin, tmp_file);
}) })
}); });
let anonymous_fd_pipe_handles: Vec<_> = anonymous_fd_tmp_files let anonymous_fd_pipe_handles: Vec<JoinHandle<anyhow::Result<()>>> = anonymous_fd_tmp_files
.into_iter() .into_iter()
.map(|(mut file, mut tmp_file)| thread::spawn(move || io::copy(&mut file, &mut tmp_file))) .map(|(file, tmp_file)| thread::spawn(move || pipe_to_tmp(file, tmp_file)))
.collect(); .collect();
if args.foreground { if args.foreground {
@ -349,6 +349,22 @@ fn main() -> Result<()> {
Ok(()) Ok(())
} }
fn pipe_to_tmp(mut src: impl io::Read, mut dest: fs::File) -> Result<()> {
let mut buffer = [0; 8 * 1024];
loop {
let bytes_read = match src.read(&mut buffer) {
Err(err) if err.kind() == io::ErrorKind::Interrupted => continue,
res => res?,
};
if bytes_read == 0 {
break;
}
io::Write::write_all(&mut dest, &buffer[..bytes_read])?;
}
io::Write::flush(&mut dest)?;
Ok(())
}
fn anonymous_fd(path: &str) -> Option<fs::File> { fn anonymous_fd(path: &str) -> Option<fs::File> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {

View file

@ -127,7 +127,6 @@ sea-orm = { version = "1.1.0-rc.1", features = ["sqlx-sqlite"] }
serde_json.workspace = true serde_json.workspace = true
session = { workspace = true, features = ["test-support"] } session = { workspace = true, features = ["test-support"] }
settings = { workspace = true, features = ["test-support"] } settings = { workspace = true, features = ["test-support"] }
smol.workspace = true
sqlx = { version = "0.8", features = ["sqlite"] } sqlx = { version = "0.8", features = ["sqlite"] }
task.workspace = true task.workspace = true
theme.workspace = true theme.workspace = true

View file

@ -1,33 +1,12 @@
{ {
"admins": [ "admins": [
"nathansobo", "nathansobo",
"maxbrunsfeld",
"as-cii", "as-cii",
"JosephTLyons", "maxbrunsfeld",
"maxdeviant", "iamnbutler",
"SomeoneToIgnore",
"mikayla-maki", "mikayla-maki",
"agu-z", "JosephTLyons",
"osiewicz", "rgbkrk"
"ConradIrwin",
"benbrandt",
"bennetbo",
"smitbarmase",
"notpeter",
"rgbkrk",
"JunkuiZhang",
"Anthony-Eid",
"rtfeldman",
"danilo-leal",
"MrSubidubi",
"cole-miller",
"osyvokon",
"probably-neb",
"mgsloan",
"P1n3appl3",
"mslzed",
"franciskafyi",
"katie-z-geer"
], ],
"channels": ["zed"] "channels": ["zed"]
} }

View file

@ -5,7 +5,7 @@ use axum::{
routing::{get, post}, routing::{get, post},
}; };
use chrono::{DateTime, SecondsFormat, Utc}; use chrono::{DateTime, SecondsFormat, Utc};
use collections::{HashMap, HashSet}; use collections::HashSet;
use reqwest::StatusCode; use reqwest::StatusCode;
use sea_orm::ActiveValue; use sea_orm::ActiveValue;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -21,13 +21,12 @@ use stripe::{
PaymentMethod, Subscription, SubscriptionId, SubscriptionStatus, PaymentMethod, Subscription, SubscriptionId, SubscriptionStatus,
}; };
use util::{ResultExt, maybe}; use util::{ResultExt, maybe};
use zed_llm_client::LanguageModelProvider;
use crate::api::events::SnowflakeRow; use crate::api::events::SnowflakeRow;
use crate::db::billing_subscription::{ use crate::db::billing_subscription::{
StripeCancellationReason, StripeSubscriptionStatus, SubscriptionKind, StripeCancellationReason, StripeSubscriptionStatus, SubscriptionKind,
}; };
use crate::llm::db::subscription_usage_meter::{self, CompletionMode}; use crate::llm::db::subscription_usage_meter::CompletionMode;
use crate::llm::{AGENT_EXTENDED_TRIAL_FEATURE_FLAG, DEFAULT_MAX_MONTHLY_SPEND}; use crate::llm::{AGENT_EXTENDED_TRIAL_FEATURE_FLAG, DEFAULT_MAX_MONTHLY_SPEND};
use crate::rpc::{ResultExt as _, Server}; use crate::rpc::{ResultExt as _, Server};
use crate::stripe_client::{ use crate::stripe_client::{
@ -1417,21 +1416,18 @@ async fn sync_model_request_usage_with_stripe(
let usage_meters = llm_db let usage_meters = llm_db
.get_current_subscription_usage_meters(Utc::now()) .get_current_subscription_usage_meters(Utc::now())
.await?; .await?;
let mut usage_meters_by_user_id = let usage_meters = usage_meters
HashMap::<UserId, Vec<subscription_usage_meter::Model>>::default(); .into_iter()
for (usage_meter, usage) in usage_meters { .filter(|(_, usage)| !staff_user_ids.contains(&usage.user_id))
let meters = usage_meters_by_user_id.entry(usage.user_id).or_default(); .collect::<Vec<_>>();
meters.push(usage_meter); let user_ids = usage_meters
} .iter()
.map(|(_, usage)| usage.user_id)
log::info!("Stripe usage sync: Retrieving Zed Pro subscriptions"); .collect::<HashSet<UserId>>();
let get_zed_pro_subscriptions_started_at = Utc::now(); let billing_subscriptions = app
let billing_subscriptions = app.db.get_active_zed_pro_billing_subscriptions().await?; .db
log::info!( .get_active_zed_pro_billing_subscriptions(user_ids)
"Stripe usage sync: Retrieved {} Zed Pro subscriptions in {}", .await?;
billing_subscriptions.len(),
Utc::now() - get_zed_pro_subscriptions_started_at
);
let claude_sonnet_4 = stripe_billing let claude_sonnet_4 = stripe_billing
.find_price_by_lookup_key("claude-sonnet-4-requests") .find_price_by_lookup_key("claude-sonnet-4-requests")
@ -1455,90 +1451,59 @@ async fn sync_model_request_usage_with_stripe(
.find_price_by_lookup_key("claude-3-7-sonnet-requests-max") .find_price_by_lookup_key("claude-3-7-sonnet-requests-max")
.await?; .await?;
let model_mode_combinations = [ let usage_meter_count = usage_meters.len();
("claude-opus-4", CompletionMode::Max),
("claude-opus-4", CompletionMode::Normal),
("claude-sonnet-4", CompletionMode::Max),
("claude-sonnet-4", CompletionMode::Normal),
("claude-3-7-sonnet", CompletionMode::Max),
("claude-3-7-sonnet", CompletionMode::Normal),
("claude-3-5-sonnet", CompletionMode::Normal),
];
let billing_subscription_count = billing_subscriptions.len(); log::info!("Stripe usage sync: Syncing {usage_meter_count} usage meters");
log::info!("Stripe usage sync: Syncing {billing_subscription_count} Zed Pro subscriptions"); for (usage_meter, usage) in usage_meters {
for (user_id, (billing_customer, billing_subscription)) in billing_subscriptions {
maybe!(async { maybe!(async {
if staff_user_ids.contains(&user_id) { let Some((billing_customer, billing_subscription)) =
return anyhow::Ok(()); billing_subscriptions.get(&usage.user_id)
} else {
bail!(
"Attempted to sync usage meter for user who is not a Stripe customer: {}",
usage.user_id
);
};
let stripe_customer_id = let stripe_customer_id =
StripeCustomerId(billing_customer.stripe_customer_id.clone().into()); StripeCustomerId(billing_customer.stripe_customer_id.clone().into());
let stripe_subscription_id = let stripe_subscription_id =
StripeSubscriptionId(billing_subscription.stripe_subscription_id.clone().into()); StripeSubscriptionId(billing_subscription.stripe_subscription_id.clone().into());
let usage_meters = usage_meters_by_user_id.get(&user_id); let model = llm_db.model_by_id(usage_meter.model_id)?;
for (model, mode) in &model_mode_combinations { let (price, meter_event_name) = match model.name.as_str() {
let Ok(model) = "claude-opus-4" => match usage_meter.mode {
llm_db.model(LanguageModelProvider::Anthropic, model) CompletionMode::Normal => (&claude_opus_4, "claude_opus_4/requests"),
else { CompletionMode::Max => (&claude_opus_4_max, "claude_opus_4/requests/max"),
log::warn!("Failed to load model for user {user_id}: {model}"); },
continue; "claude-sonnet-4" => match usage_meter.mode {
}; CompletionMode::Normal => (&claude_sonnet_4, "claude_sonnet_4/requests"),
CompletionMode::Max => (&claude_sonnet_4_max, "claude_sonnet_4/requests/max"),
let (price, meter_event_name) = match model.name.as_str() { },
"claude-opus-4" => match mode { "claude-3-5-sonnet" => (&claude_3_5_sonnet, "claude_3_5_sonnet/requests"),
CompletionMode::Normal => (&claude_opus_4, "claude_opus_4/requests"), "claude-3-7-sonnet" => match usage_meter.mode {
CompletionMode::Max => (&claude_opus_4_max, "claude_opus_4/requests/max"), CompletionMode::Normal => (&claude_3_7_sonnet, "claude_3_7_sonnet/requests"),
}, CompletionMode::Max => {
"claude-sonnet-4" => match mode { (&claude_3_7_sonnet_max, "claude_3_7_sonnet/requests/max")
CompletionMode::Normal => (&claude_sonnet_4, "claude_sonnet_4/requests"),
CompletionMode::Max => {
(&claude_sonnet_4_max, "claude_sonnet_4/requests/max")
}
},
"claude-3-5-sonnet" => (&claude_3_5_sonnet, "claude_3_5_sonnet/requests"),
"claude-3-7-sonnet" => match mode {
CompletionMode::Normal => {
(&claude_3_7_sonnet, "claude_3_7_sonnet/requests")
}
CompletionMode::Max => {
(&claude_3_7_sonnet_max, "claude_3_7_sonnet/requests/max")
}
},
model_name => {
bail!("Attempted to sync usage meter for unsupported model: {model_name:?}")
} }
}; },
model_name => {
let model_requests = usage_meters bail!("Attempted to sync usage meter for unsupported model: {model_name:?}")
.and_then(|usage_meters| {
usage_meters
.iter()
.find(|meter| meter.model_id == model.id && meter.mode == *mode)
})
.map(|usage_meter| usage_meter.requests)
.unwrap_or(0);
if model_requests > 0 {
stripe_billing
.subscribe_to_price(&stripe_subscription_id, price)
.await?;
} }
};
stripe_billing stripe_billing
.bill_model_request_usage(&stripe_customer_id, meter_event_name, model_requests) .subscribe_to_price(&stripe_subscription_id, price)
.await .await?;
.with_context(|| { stripe_billing
format!( .bill_model_request_usage(
"Failed to bill model request usage of {model_requests} for {stripe_customer_id}: {meter_event_name}", &stripe_customer_id,
) meter_event_name,
})?; usage_meter.requests,
} )
.await?;
Ok(()) Ok(())
}) })
@ -1547,7 +1512,7 @@ async fn sync_model_request_usage_with_stripe(
} }
log::info!( log::info!(
"Stripe usage sync: Synced {billing_subscription_count} Zed Pro subscriptions in {}", "Stripe usage sync: Synced {usage_meter_count} usage meters in {:?}",
Utc::now() - started_at Utc::now() - started_at
); );

View file

@ -199,33 +199,6 @@ impl Database {
pub async fn get_active_zed_pro_billing_subscriptions( pub async fn get_active_zed_pro_billing_subscriptions(
&self, &self,
) -> Result<HashMap<UserId, (billing_customer::Model, billing_subscription::Model)>> {
self.transaction(|tx| async move {
let mut rows = billing_subscription::Entity::find()
.inner_join(billing_customer::Entity)
.select_also(billing_customer::Entity)
.filter(
billing_subscription::Column::StripeSubscriptionStatus
.eq(StripeSubscriptionStatus::Active),
)
.filter(billing_subscription::Column::Kind.eq(SubscriptionKind::ZedPro))
.order_by_asc(billing_subscription::Column::Id)
.stream(&*tx)
.await?;
let mut subscriptions = HashMap::default();
while let Some(row) = rows.next().await {
if let (subscription, Some(customer)) = row? {
subscriptions.insert(customer.user_id, (customer, subscription));
}
}
Ok(subscriptions)
})
.await
}
pub async fn get_active_zed_pro_billing_subscriptions_for_users(
&self,
user_ids: HashSet<UserId>, user_ids: HashSet<UserId>,
) -> Result<HashMap<UserId, (billing_customer::Model, billing_subscription::Model)>> { ) -> Result<HashMap<UserId, (billing_customer::Model, billing_subscription::Model)>> {
self.transaction(|tx| { self.transaction(|tx| {

View file

@ -2836,115 +2836,60 @@ async fn make_update_user_plan_message(
account_too_young: Some(account_too_young), account_too_young: Some(account_too_young),
has_overdue_invoices: billing_customer has_overdue_invoices: billing_customer
.map(|billing_customer| billing_customer.has_overdue_invoices), .map(|billing_customer| billing_customer.has_overdue_invoices),
usage: Some( usage: usage.map(|usage| {
usage let plan = match plan {
.map(|usage| subscription_usage_to_proto(plan, usage, &feature_flags)) proto::Plan::Free => zed_llm_client::Plan::ZedFree,
.unwrap_or_else(|| make_default_subscription_usage(plan, &feature_flags)), proto::Plan::ZedPro => zed_llm_client::Plan::ZedPro,
), proto::Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
})
}
fn model_requests_limit(
plan: zed_llm_client::Plan,
feature_flags: &Vec<String>,
) -> zed_llm_client::UsageLimit {
match plan.model_requests_limit() {
zed_llm_client::UsageLimit::Limited(limit) => {
let limit = if plan == zed_llm_client::Plan::ZedProTrial
&& feature_flags
.iter()
.any(|flag| flag == AGENT_EXTENDED_TRIAL_FEATURE_FLAG)
{
1_000
} else {
limit
}; };
zed_llm_client::UsageLimit::Limited(limit) let model_requests_limit = match plan.model_requests_limit() {
}
zed_llm_client::UsageLimit::Unlimited => zed_llm_client::UsageLimit::Unlimited,
}
}
fn subscription_usage_to_proto(
plan: proto::Plan,
usage: crate::llm::db::subscription_usage::Model,
feature_flags: &Vec<String>,
) -> proto::SubscriptionUsage {
let plan = match plan {
proto::Plan::Free => zed_llm_client::Plan::ZedFree,
proto::Plan::ZedPro => zed_llm_client::Plan::ZedPro,
proto::Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
};
proto::SubscriptionUsage {
model_requests_usage_amount: usage.model_requests as u32,
model_requests_usage_limit: Some(proto::UsageLimit {
variant: Some(match model_requests_limit(plan, feature_flags) {
zed_llm_client::UsageLimit::Limited(limit) => { zed_llm_client::UsageLimit::Limited(limit) => {
proto::usage_limit::Variant::Limited(proto::usage_limit::Limited { let limit = if plan == zed_llm_client::Plan::ZedProTrial
limit: limit as u32, && feature_flags
}) .iter()
} .any(|flag| flag == AGENT_EXTENDED_TRIAL_FEATURE_FLAG)
zed_llm_client::UsageLimit::Unlimited => { {
proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {}) 1_000
} } else {
}), limit
}), };
edit_predictions_usage_amount: usage.edit_predictions as u32,
edit_predictions_usage_limit: Some(proto::UsageLimit {
variant: Some(match plan.edit_predictions_limit() {
zed_llm_client::UsageLimit::Limited(limit) => {
proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
limit: limit as u32,
})
}
zed_llm_client::UsageLimit::Unlimited => {
proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
}
}),
}),
}
}
fn make_default_subscription_usage( zed_llm_client::UsageLimit::Limited(limit)
plan: proto::Plan, }
feature_flags: &Vec<String>, zed_llm_client::UsageLimit::Unlimited => zed_llm_client::UsageLimit::Unlimited,
) -> proto::SubscriptionUsage { };
let plan = match plan {
proto::Plan::Free => zed_llm_client::Plan::ZedFree,
proto::Plan::ZedPro => zed_llm_client::Plan::ZedPro,
proto::Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
};
proto::SubscriptionUsage { proto::SubscriptionUsage {
model_requests_usage_amount: 0, model_requests_usage_amount: usage.model_requests as u32,
model_requests_usage_limit: Some(proto::UsageLimit { model_requests_usage_limit: Some(proto::UsageLimit {
variant: Some(match model_requests_limit(plan, feature_flags) { variant: Some(match model_requests_limit {
zed_llm_client::UsageLimit::Limited(limit) => { zed_llm_client::UsageLimit::Limited(limit) => {
proto::usage_limit::Variant::Limited(proto::usage_limit::Limited { proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
limit: limit as u32, limit: limit as u32,
}) })
} }
zed_llm_client::UsageLimit::Unlimited => { zed_llm_client::UsageLimit::Unlimited => {
proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {}) proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
} }
}), }),
}),
edit_predictions_usage_amount: usage.edit_predictions as u32,
edit_predictions_usage_limit: Some(proto::UsageLimit {
variant: Some(match plan.edit_predictions_limit() {
zed_llm_client::UsageLimit::Limited(limit) => {
proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
limit: limit as u32,
})
}
zed_llm_client::UsageLimit::Unlimited => {
proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
}
}),
}),
}
}), }),
edit_predictions_usage_amount: 0, })
edit_predictions_usage_limit: Some(proto::UsageLimit {
variant: Some(match plan.edit_predictions_limit() {
zed_llm_client::UsageLimit::Limited(limit) => {
proto::usage_limit::Variant::Limited(proto::usage_limit::Limited {
limit: limit as u32,
})
}
zed_llm_client::UsageLimit::Unlimited => {
proto::usage_limit::Variant::Unlimited(proto::usage_limit::Unlimited {})
}
}),
}),
}
} }
async fn update_user_plan(session: &Session) -> Result<()> { async fn update_user_plan(session: &Session) -> Result<()> {

View file

@ -19,8 +19,8 @@ use crate::stripe_client::{
StripeCustomerId, StripeCustomerUpdate, StripeCustomerUpdateAddress, StripeCustomerUpdateName, StripeCustomerId, StripeCustomerUpdate, StripeCustomerUpdateAddress, StripeCustomerUpdateName,
StripeMeter, StripePrice, StripePriceId, StripeSubscription, StripeSubscriptionId, StripeMeter, StripePrice, StripePriceId, StripeSubscription, StripeSubscriptionId,
StripeSubscriptionTrialSettings, StripeSubscriptionTrialSettingsEndBehavior, StripeSubscriptionTrialSettings, StripeSubscriptionTrialSettingsEndBehavior,
StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, StripeTaxIdCollection, StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, UpdateSubscriptionItems,
UpdateSubscriptionItems, UpdateSubscriptionParams, UpdateSubscriptionParams,
}; };
pub struct StripeBilling { pub struct StripeBilling {
@ -252,7 +252,6 @@ impl StripeBilling {
name: Some(StripeCustomerUpdateName::Auto), name: Some(StripeCustomerUpdateName::Auto),
shipping: None, shipping: None,
}); });
params.tax_id_collection = Some(StripeTaxIdCollection { enabled: true });
let session = self.client.create_checkout_session(params).await?; let session = self.client.create_checkout_session(params).await?;
Ok(session.url.context("no checkout session URL")?) Ok(session.url.context("no checkout session URL")?)
@ -312,7 +311,6 @@ impl StripeBilling {
name: Some(StripeCustomerUpdateName::Auto), name: Some(StripeCustomerUpdateName::Auto),
shipping: None, shipping: None,
}); });
params.tax_id_collection = Some(StripeTaxIdCollection { enabled: true });
let session = self.client.create_checkout_session(params).await?; let session = self.client.create_checkout_session(params).await?;
Ok(session.url.context("no checkout session URL")?) Ok(session.url.context("no checkout session URL")?)

View file

@ -190,7 +190,6 @@ pub struct StripeCreateCheckoutSessionParams<'a> {
pub success_url: Option<&'a str>, pub success_url: Option<&'a str>,
pub billing_address_collection: Option<StripeBillingAddressCollection>, pub billing_address_collection: Option<StripeBillingAddressCollection>,
pub customer_update: Option<StripeCustomerUpdate>, pub customer_update: Option<StripeCustomerUpdate>,
pub tax_id_collection: Option<StripeTaxIdCollection>,
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Clone, Copy)]
@ -219,11 +218,6 @@ pub struct StripeCreateCheckoutSessionSubscriptionData {
pub trial_settings: Option<StripeSubscriptionTrialSettings>, pub trial_settings: Option<StripeSubscriptionTrialSettings>,
} }
#[derive(Debug, PartialEq, Clone)]
pub struct StripeTaxIdCollection {
pub enabled: bool,
}
#[derive(Debug)] #[derive(Debug)]
pub struct StripeCheckoutSession { pub struct StripeCheckoutSession {
pub url: Option<String>, pub url: Option<String>,

View file

@ -14,8 +14,8 @@ use crate::stripe_client::{
StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams, StripeCreateCheckoutSessionSubscriptionData, StripeCreateMeterEventParams,
StripeCreateSubscriptionParams, StripeCustomer, StripeCustomerId, StripeCustomerUpdate, StripeCreateSubscriptionParams, StripeCustomer, StripeCustomerId, StripeCustomerUpdate,
StripeMeter, StripeMeterId, StripePrice, StripePriceId, StripeSubscription, StripeMeter, StripeMeterId, StripePrice, StripePriceId, StripeSubscription,
StripeSubscriptionId, StripeSubscriptionItem, StripeSubscriptionItemId, StripeTaxIdCollection, StripeSubscriptionId, StripeSubscriptionItem, StripeSubscriptionItemId, UpdateCustomerParams,
UpdateCustomerParams, UpdateSubscriptionParams, UpdateSubscriptionParams,
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -38,7 +38,6 @@ pub struct StripeCreateCheckoutSessionCall {
pub success_url: Option<String>, pub success_url: Option<String>,
pub billing_address_collection: Option<StripeBillingAddressCollection>, pub billing_address_collection: Option<StripeBillingAddressCollection>,
pub customer_update: Option<StripeCustomerUpdate>, pub customer_update: Option<StripeCustomerUpdate>,
pub tax_id_collection: Option<StripeTaxIdCollection>,
} }
pub struct FakeStripeClient { pub struct FakeStripeClient {
@ -237,7 +236,6 @@ impl StripeClient for FakeStripeClient {
success_url: params.success_url.map(|url| url.to_string()), success_url: params.success_url.map(|url| url.to_string()),
billing_address_collection: params.billing_address_collection, billing_address_collection: params.billing_address_collection,
customer_update: params.customer_update, customer_update: params.customer_update,
tax_id_collection: params.tax_id_collection,
}); });
Ok(StripeCheckoutSession { Ok(StripeCheckoutSession {

View file

@ -27,8 +27,8 @@ use crate::stripe_client::{
StripeMeter, StripePrice, StripePriceId, StripePriceRecurring, StripeSubscription, StripeMeter, StripePrice, StripePriceId, StripePriceRecurring, StripeSubscription,
StripeSubscriptionId, StripeSubscriptionItem, StripeSubscriptionItemId, StripeSubscriptionId, StripeSubscriptionItem, StripeSubscriptionItemId,
StripeSubscriptionTrialSettings, StripeSubscriptionTrialSettingsEndBehavior, StripeSubscriptionTrialSettings, StripeSubscriptionTrialSettingsEndBehavior,
StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, StripeTaxIdCollection, StripeSubscriptionTrialSettingsEndBehaviorMissingPaymentMethod, UpdateCustomerParams,
UpdateCustomerParams, UpdateSubscriptionParams, UpdateSubscriptionParams,
}; };
pub struct RealStripeClient { pub struct RealStripeClient {
@ -448,7 +448,6 @@ impl<'a> TryFrom<StripeCreateCheckoutSessionParams<'a>> for CreateCheckoutSessio
success_url: value.success_url, success_url: value.success_url,
billing_address_collection: value.billing_address_collection.map(Into::into), billing_address_collection: value.billing_address_collection.map(Into::into),
customer_update: value.customer_update.map(Into::into), customer_update: value.customer_update.map(Into::into),
tax_id_collection: value.tax_id_collection.map(Into::into),
..Default::default() ..Default::default()
}) })
} }
@ -591,11 +590,3 @@ impl From<StripeCustomerUpdate> for stripe::CreateCheckoutSessionCustomerUpdate
} }
} }
} }
impl From<StripeTaxIdCollection> for stripe::CreateCheckoutSessionTaxIdCollection {
fn from(value: StripeTaxIdCollection) -> Self {
stripe::CreateCheckoutSessionTaxIdCollection {
enabled: value.enabled,
}
}
}

View file

@ -2246,11 +2246,8 @@ async fn test_lsp_document_color(cx_a: &mut TestAppContext, cx_b: &mut TestAppCo
}); });
} }
async fn test_lsp_pull_diagnostics( #[gpui::test(iterations = 10)]
should_stream_workspace_diagnostic: bool, async fn test_lsp_pull_diagnostics(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
let mut server = TestServer::start(cx_a.executor()).await; let mut server = TestServer::start(cx_a.executor()).await;
let executor = cx_a.executor(); let executor = cx_a.executor();
let client_a = server.create_client(cx_a, "user_a").await; let client_a = server.create_client(cx_a, "user_a").await;
@ -2399,25 +2396,12 @@ async fn test_lsp_pull_diagnostics(
let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone(); let closure_workspace_diagnostics_pulls_made = workspace_diagnostics_pulls_made.clone();
let closure_workspace_diagnostics_pulls_result_ids = let closure_workspace_diagnostics_pulls_result_ids =
workspace_diagnostics_pulls_result_ids.clone(); workspace_diagnostics_pulls_result_ids.clone();
let (workspace_diagnostic_cancel_tx, closure_workspace_diagnostic_cancel_rx) =
smol::channel::bounded::<()>(1);
let (closure_workspace_diagnostic_received_tx, workspace_diagnostic_received_rx) =
smol::channel::bounded::<()>(1);
let expected_workspace_diagnostic_token = lsp::ProgressToken::String(format!(
"workspace/diagnostic-{}-1",
fake_language_server.server.server_id()
));
let closure_expected_workspace_diagnostic_token = expected_workspace_diagnostic_token.clone();
let mut workspace_diagnostics_pulls_handle = fake_language_server let mut workspace_diagnostics_pulls_handle = fake_language_server
.set_request_handler::<lsp::request::WorkspaceDiagnosticRequest, _, _>( .set_request_handler::<lsp::request::WorkspaceDiagnosticRequest, _, _>(
move |params, _| { move |params, _| {
let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone(); let workspace_requests_made = closure_workspace_diagnostics_pulls_made.clone();
let workspace_diagnostics_pulls_result_ids = let workspace_diagnostics_pulls_result_ids =
closure_workspace_diagnostics_pulls_result_ids.clone(); closure_workspace_diagnostics_pulls_result_ids.clone();
let workspace_diagnostic_cancel_rx = closure_workspace_diagnostic_cancel_rx.clone();
let workspace_diagnostic_received_tx = closure_workspace_diagnostic_received_tx.clone();
let expected_workspace_diagnostic_token =
closure_expected_workspace_diagnostic_token.clone();
async move { async move {
let workspace_request_count = let workspace_request_count =
workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1; workspace_requests_made.fetch_add(1, atomic::Ordering::Release) + 1;
@ -2427,21 +2411,6 @@ async fn test_lsp_pull_diagnostics(
.await .await
.extend(params.previous_result_ids.into_iter().map(|id| id.value)); .extend(params.previous_result_ids.into_iter().map(|id| id.value));
} }
if should_stream_workspace_diagnostic && !workspace_diagnostic_cancel_rx.is_closed()
{
assert_eq!(
params.partial_result_params.partial_result_token,
Some(expected_workspace_diagnostic_token)
);
workspace_diagnostic_received_tx.send(()).await.unwrap();
workspace_diagnostic_cancel_rx.recv().await.unwrap();
workspace_diagnostic_cancel_rx.close();
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#partialResults
// > The final response has to be empty in terms of result values.
return Ok(lsp::WorkspaceDiagnosticReportResult::Report(
lsp::WorkspaceDiagnosticReport { items: Vec::new() },
));
}
Ok(lsp::WorkspaceDiagnosticReportResult::Report( Ok(lsp::WorkspaceDiagnosticReportResult::Report(
lsp::WorkspaceDiagnosticReport { lsp::WorkspaceDiagnosticReport {
items: vec![ items: vec![
@ -2510,11 +2479,7 @@ async fn test_lsp_pull_diagnostics(
}, },
); );
if should_stream_workspace_diagnostic { workspace_diagnostics_pulls_handle.next().await.unwrap();
workspace_diagnostic_received_rx.recv().await.unwrap();
} else {
workspace_diagnostics_pulls_handle.next().await.unwrap();
}
assert_eq!( assert_eq!(
1, 1,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire), workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
@ -2538,10 +2503,10 @@ async fn test_lsp_pull_diagnostics(
"Expected single diagnostic, but got: {all_diagnostics:?}" "Expected single diagnostic, but got: {all_diagnostics:?}"
); );
let diagnostic = &all_diagnostics[0]; let diagnostic = &all_diagnostics[0];
let mut expected_messages = vec![expected_pull_diagnostic_main_message]; let expected_messages = [
if !should_stream_workspace_diagnostic { expected_workspace_pull_diagnostics_main_message,
expected_messages.push(expected_workspace_pull_diagnostics_main_message); expected_pull_diagnostic_main_message,
} ];
assert!( assert!(
expected_messages.contains(&diagnostic.diagnostic.message.as_str()), expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
"Expected {expected_messages:?} on the host, but got: {}", "Expected {expected_messages:?} on the host, but got: {}",
@ -2591,70 +2556,6 @@ async fn test_lsp_pull_diagnostics(
version: None, version: None,
}, },
); );
if should_stream_workspace_diagnostic {
fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
token: expected_workspace_diagnostic_token.clone(),
value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
items: vec![
lsp::WorkspaceDocumentDiagnosticReport::Full(
lsp::WorkspaceFullDocumentDiagnosticReport {
uri: lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
version: None,
full_document_diagnostic_report:
lsp::FullDocumentDiagnosticReport {
result_id: Some(format!(
"workspace_{}",
workspace_diagnostics_pulls_made
.fetch_add(1, atomic::Ordering::Release)
+ 1
)),
items: vec![lsp::Diagnostic {
range: lsp::Range {
start: lsp::Position {
line: 0,
character: 1,
},
end: lsp::Position {
line: 0,
character: 2,
},
},
severity: Some(lsp::DiagnosticSeverity::ERROR),
message:
expected_workspace_pull_diagnostics_main_message
.to_string(),
..lsp::Diagnostic::default()
}],
},
},
),
lsp::WorkspaceDocumentDiagnosticReport::Full(
lsp::WorkspaceFullDocumentDiagnosticReport {
uri: lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap(),
version: None,
full_document_diagnostic_report:
lsp::FullDocumentDiagnosticReport {
result_id: Some(format!(
"workspace_{}",
workspace_diagnostics_pulls_made
.fetch_add(1, atomic::Ordering::Release)
+ 1
)),
items: Vec::new(),
},
},
),
],
}),
),
});
};
let mut workspace_diagnostic_start_count =
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
executor.run_until_parked(); executor.run_until_parked();
editor_a_main.update(cx_a, |editor, cx| { editor_a_main.update(cx_a, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx); let snapshot = editor.buffer().read(cx).snapshot(cx);
@ -2698,7 +2599,7 @@ async fn test_lsp_pull_diagnostics(
); );
executor.run_until_parked(); executor.run_until_parked();
assert_eq!( assert_eq!(
workspace_diagnostic_start_count, 1,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire), workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"Workspace diagnostics should not be changed as the remote client does not initialize the workspace diagnostics pull" "Workspace diagnostics should not be changed as the remote client does not initialize the workspace diagnostics pull"
); );
@ -2745,7 +2646,7 @@ async fn test_lsp_pull_diagnostics(
); );
executor.run_until_parked(); executor.run_until_parked();
assert_eq!( assert_eq!(
workspace_diagnostic_start_count, 1,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire), workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"The remote client still did not anything to trigger the workspace diagnostics pull" "The remote client still did not anything to trigger the workspace diagnostics pull"
); );
@ -2772,75 +2673,6 @@ async fn test_lsp_pull_diagnostics(
); );
} }
}); });
if should_stream_workspace_diagnostic {
fake_language_server.notify::<lsp::notification::Progress>(&lsp::ProgressParams {
token: expected_workspace_diagnostic_token.clone(),
value: lsp::ProgressParamsValue::WorkspaceDiagnostic(
lsp::WorkspaceDiagnosticReportResult::Report(lsp::WorkspaceDiagnosticReport {
items: vec![lsp::WorkspaceDocumentDiagnosticReport::Full(
lsp::WorkspaceFullDocumentDiagnosticReport {
uri: lsp::Url::from_file_path(path!("/a/lib.rs")).unwrap(),
version: None,
full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
result_id: Some(format!(
"workspace_{}",
workspace_diagnostics_pulls_made
.fetch_add(1, atomic::Ordering::Release)
+ 1
)),
items: vec![lsp::Diagnostic {
range: lsp::Range {
start: lsp::Position {
line: 0,
character: 1,
},
end: lsp::Position {
line: 0,
character: 2,
},
},
severity: Some(lsp::DiagnosticSeverity::ERROR),
message: expected_workspace_pull_diagnostics_lib_message
.to_string(),
..lsp::Diagnostic::default()
}],
},
},
)],
}),
),
});
workspace_diagnostic_start_count =
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire);
workspace_diagnostic_cancel_tx.send(()).await.unwrap();
workspace_diagnostics_pulls_handle.next().await.unwrap();
executor.run_until_parked();
editor_b_lib.update(cx_b, |editor, cx| {
let snapshot = editor.buffer().read(cx).snapshot(cx);
let all_diagnostics = snapshot
.diagnostics_in_range(0..snapshot.len())
.collect::<Vec<_>>();
let expected_messages = [
expected_workspace_pull_diagnostics_lib_message,
// TODO bug: the pushed diagnostics are not being sent to the client when they open the corresponding buffer.
// expected_push_diagnostic_lib_message,
];
assert_eq!(
all_diagnostics.len(),
1,
"Expected pull diagnostics, but got: {all_diagnostics:?}"
);
for diagnostic in all_diagnostics {
assert!(
expected_messages.contains(&diagnostic.diagnostic.message.as_str()),
"The client should get both push and pull messages: {expected_messages:?}, but got: {}",
diagnostic.diagnostic.message
);
}
});
};
{ {
assert!( assert!(
diagnostics_pulls_result_ids.lock().await.len() > 0, diagnostics_pulls_result_ids.lock().await.len() > 0,
@ -2869,7 +2701,7 @@ async fn test_lsp_pull_diagnostics(
); );
workspace_diagnostics_pulls_handle.next().await.unwrap(); workspace_diagnostics_pulls_handle.next().await.unwrap();
assert_eq!( assert_eq!(
workspace_diagnostic_start_count + 1, 2,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire), workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"After client lib.rs edits, the workspace diagnostics request should follow" "After client lib.rs edits, the workspace diagnostics request should follow"
); );
@ -2888,7 +2720,7 @@ async fn test_lsp_pull_diagnostics(
); );
workspace_diagnostics_pulls_handle.next().await.unwrap(); workspace_diagnostics_pulls_handle.next().await.unwrap();
assert_eq!( assert_eq!(
workspace_diagnostic_start_count + 2, 3,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire), workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"After client main.rs edits, the workspace diagnostics pull should follow" "After client main.rs edits, the workspace diagnostics pull should follow"
); );
@ -2907,7 +2739,7 @@ async fn test_lsp_pull_diagnostics(
); );
workspace_diagnostics_pulls_handle.next().await.unwrap(); workspace_diagnostics_pulls_handle.next().await.unwrap();
assert_eq!( assert_eq!(
workspace_diagnostic_start_count + 3, 4,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire), workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"After host main.rs edits, the workspace diagnostics pull should follow" "After host main.rs edits, the workspace diagnostics pull should follow"
); );
@ -2937,7 +2769,7 @@ async fn test_lsp_pull_diagnostics(
); );
workspace_diagnostics_pulls_handle.next().await.unwrap(); workspace_diagnostics_pulls_handle.next().await.unwrap();
assert_eq!( assert_eq!(
workspace_diagnostic_start_count + 4, 5,
workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire), workspace_diagnostics_pulls_made.load(atomic::Ordering::Acquire),
"Another workspace diagnostics pull should happen after the diagnostics refresh server request" "Another workspace diagnostics pull should happen after the diagnostics refresh server request"
); );
@ -3008,19 +2840,6 @@ async fn test_lsp_pull_diagnostics(
}); });
} }
#[gpui::test(iterations = 10)]
async fn test_non_streamed_lsp_pull_diagnostics(
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
) {
test_lsp_pull_diagnostics(false, cx_a, cx_b).await;
}
#[gpui::test(iterations = 10)]
async fn test_streamed_lsp_pull_diagnostics(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
test_lsp_pull_diagnostics(true, cx_a, cx_b).await;
}
#[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
let mut server = TestServer::start(cx_a.executor()).await; let mut server = TestServer::start(cx_a.executor()).await;

View file

@ -1013,7 +1013,7 @@ async fn test_peers_following_each_other(cx_a: &mut TestAppContext, cx_b: &mut T
// and some of which were originally opened by client B. // and some of which were originally opened by client B.
workspace_b.update_in(cx_b, |workspace, window, cx| { workspace_b.update_in(cx_b, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| { workspace.active_pane().update(cx, |pane, cx| {
pane.close_inactive_items(&Default::default(), None, window, cx) pane.close_inactive_items(&Default::default(), window, cx)
.detach(); .detach();
}); });
}); });

View file

@ -48,20 +48,20 @@ impl RenderOnce for ComponentExample {
) )
.child( .child(
div() div()
.min_h(px(100.))
.w_full()
.p_8()
.flex() .flex()
.items_center() .w_full()
.justify_center()
.rounded_xl() .rounded_xl()
.min_h(px(100.))
.justify_center()
.p_8()
.border_1() .border_1()
.border_color(cx.theme().colors().border.opacity(0.5)) .border_color(cx.theme().colors().border.opacity(0.5))
.bg(pattern_slash( .bg(pattern_slash(
cx.theme().colors().surface_background.opacity(0.25), cx.theme().colors().surface_background.opacity(0.5),
12.0, 12.0,
12.0, 12.0,
)) ))
.shadow_xs()
.child(self.element), .child(self.element),
) )
.into_any_element() .into_any_element()

Some files were not shown because too many files have changed in this diff Show more