Compare commits

..

4 Commits

Author SHA1 Message Date
Julia Ryan
bded0e12f4 wip 2025-07-22 14:08:11 -07:00
Julia Ryan
124b4645c4 switch from action to event 2025-07-18 14:34:31 -07:00
Julia Ryan
b24a30916a wip: start adding action 2025-07-18 14:34:31 -07:00
Julia Ryan
05e0a812c4 Add gutter runnable edit buttons 2025-07-18 14:34:31 -07:00
659 changed files with 17114 additions and 50798 deletions

View File

@@ -23,8 +23,6 @@ workspace-members = [
]
third-party = [
{ 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 = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7" },
]
[final-excludes]

View File

@@ -1,30 +0,0 @@
# Configuration related to self-hosted runner.
self-hosted-runner:
# Labels of self-hosted runner in array of strings.
labels:
# GitHub-hosted Runners
- github-8vcpu-ubuntu-2404
- github-16vcpu-ubuntu-2404
- windows-2025-16
- windows-2025-32
- windows-2025-64
# Buildjet Ubuntu 20.04 - AMD x86_64
- buildjet-2vcpu-ubuntu-2004
- buildjet-4vcpu-ubuntu-2004
- buildjet-8vcpu-ubuntu-2004
- buildjet-16vcpu-ubuntu-2004
- buildjet-32vcpu-ubuntu-2004
# Buildjet Ubuntu 22.04 - AMD x86_64
- buildjet-2vcpu-ubuntu-2204
- buildjet-4vcpu-ubuntu-2204
- buildjet-8vcpu-ubuntu-2204
- buildjet-16vcpu-ubuntu-2204
- buildjet-32vcpu-ubuntu-2204
# Buildjet Ubuntu 22.04 - Graviton aarch64
- buildjet-8vcpu-ubuntu-2204-arm
- buildjet-16vcpu-ubuntu-2204-arm
- buildjet-32vcpu-ubuntu-2204-arm
- buildjet-64vcpu-ubuntu-2204-arm
# Self Hosted Runners
- self-mini-macos
- self-32vcpu-windows-2022

View File

@@ -19,7 +19,7 @@ runs:
shell: bash -euxo pipefail {0}
run: ./script/linux
- name: Check for broken links (in MD)
- name: Check for broken links
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
with:
args: --no-progress --exclude '^http' './docs/src/**/*'
@@ -30,9 +30,3 @@ runs:
run: |
mkdir -p target/deploy
mdbook build ./docs --dest-dir=../target/deploy/docs/
- name: Check for broken links (in HTML)
uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1
with:
args: --no-progress --exclude '^http' 'target/deploy/docs/'
fail: true

View File

@@ -28,7 +28,7 @@ jobs:
run: |
set -eux
channel="$(cat crates/zed/RELEASE_CHANNEL)"
channel=$(cat crates/zed/RELEASE_CHANNEL)
tag_suffix=""
case $channel in
@@ -43,9 +43,9 @@ jobs:
;;
esac
which cargo-set-version > /dev/null || cargo install cargo-edit
output="$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')"
output=$(cargo set-version -p zed --bump patch 2>&1 | sed 's/.* //')
export GIT_COMMITTER_NAME="Zed Bot"
export GIT_COMMITTER_EMAIL="hi@zed.dev"
git commit -am "Bump to $output for @$GITHUB_ACTOR" --author "Zed Bot <hi@zed.dev>"
git tag "v${output}${tag_suffix}"
git push origin HEAD "v${output}${tag_suffix}"
git tag v${output}${tag_suffix}
git push origin HEAD v${output}${tag_suffix}

View File

@@ -34,7 +34,6 @@ jobs:
run_license: ${{ steps.filter.outputs.run_license }}
run_docs: ${{ steps.filter.outputs.run_docs }}
run_nix: ${{ steps.filter.outputs.run_nix }}
run_actionlint: ${{ steps.filter.outputs.run_actionlint }}
runs-on:
- ubuntu-latest
steps:
@@ -48,40 +47,39 @@ jobs:
run: |
if [ -z "$GITHUB_BASE_REF" ]; then
echo "Not in a PR context (i.e., push to main/stable/preview)"
COMPARE_REV="$(git rev-parse HEAD~1)"
COMPARE_REV=$(git rev-parse HEAD~1)
else
echo "In a PR context comparing to pull_request.base.ref"
git fetch origin "$GITHUB_BASE_REF" --depth=350
COMPARE_REV="$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)"
COMPARE_REV=$(git merge-base "origin/${GITHUB_BASE_REF}" HEAD)
fi
CHANGED_FILES="$(git diff --name-only "$COMPARE_REV" ${{ github.sha }})"
# Specify anything which should potentially skip full test suite in this regex:
# Specify anything which should skip full CI in this regex:
# - docs/
# - script/update_top_ranking_issues/
# - .github/ISSUE_TEMPLATE/
# - .github/workflows/ (except .github/workflows/ci.yml)
SKIP_REGEX='^(docs/|script/update_top_ranking_issues/|\.github/(ISSUE_TEMPLATE|workflows/(?!ci)))'
echo "$CHANGED_FILES" | grep -qvP "$SKIP_REGEX" && \
echo "run_tests=true" >> "$GITHUB_OUTPUT" || \
echo "run_tests=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^docs/' && \
echo "run_docs=true" >> "$GITHUB_OUTPUT" || \
echo "run_docs=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^\.github/(workflows/|actions/|actionlint.yml)' && \
echo "run_actionlint=true" >> "$GITHUB_OUTPUT" || \
echo "run_actionlint=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^(Cargo.lock|script/.*licenses)' && \
echo "run_license=true" >> "$GITHUB_OUTPUT" || \
echo "run_license=false" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" | grep -qP '^(nix/|flake\.|Cargo\.|rust-toolchain.toml|\.cargo/config.toml)' && \
echo "run_nix=true" >> "$GITHUB_OUTPUT" || \
echo "run_nix=false" >> "$GITHUB_OUTPUT"
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep -vP "$SKIP_REGEX") ]]; then
echo "run_tests=true" >> $GITHUB_OUTPUT
else
echo "run_tests=false" >> $GITHUB_OUTPUT
fi
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep '^docs/') ]]; then
echo "run_docs=true" >> $GITHUB_OUTPUT
else
echo "run_docs=false" >> $GITHUB_OUTPUT
fi
if [[ $(git diff --name-only $COMPARE_REV ${{ github.sha }} | grep -P '^(Cargo.lock|script/.*licenses)') ]]; then
echo "run_license=true" >> $GITHUB_OUTPUT
else
echo "run_license=false" >> $GITHUB_OUTPUT
fi
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
echo "run_nix=true" >> $GITHUB_OUTPUT
else
echo "run_nix=false" >> $GITHUB_OUTPUT
fi
migration_checks:
name: Check Postgres and Protobuf migrations, mergability
@@ -91,7 +89,8 @@ jobs:
needs.job_spec.outputs.run_tests == 'true'
timeout-minutes: 60
runs-on:
- self-mini-macos
- self-hosted
- macOS
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -113,11 +112,11 @@ jobs:
run: |
if [ -z "$GITHUB_BASE_REF" ];
then
echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> "$GITHUB_ENV"
echo "BUF_BASE_BRANCH=$(git merge-base origin/main HEAD)" >> $GITHUB_ENV
else
git checkout -B temp
git merge -q "origin/$GITHUB_BASE_REF" -m "merge main into temp"
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> "$GITHUB_ENV"
git merge -q origin/$GITHUB_BASE_REF -m "merge main into temp"
echo "BUF_BASE_BRANCH=$GITHUB_BASE_REF" >> $GITHUB_ENV
fi
- uses: bufbuild/buf-setup-action@v1
@@ -141,7 +140,7 @@ jobs:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install cargo-hakari
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
with:
@@ -179,7 +178,7 @@ jobs:
- name: Prettier Check on /docs
working-directory: ./docs
run: |
pnpm dlx "prettier@${PRETTIER_VERSION}" . --check || {
pnpm dlx prettier@${PRETTIER_VERSION} . --check || {
echo "To fix, run from the root of the Zed repo:"
echo " cd docs && pnpm dlx prettier@${PRETTIER_VERSION} . --write && cd .."
false
@@ -189,7 +188,7 @@ jobs:
- name: Prettier Check on default.json
run: |
pnpm dlx "prettier@${PRETTIER_VERSION}" assets/settings/default.json --check || {
pnpm dlx prettier@${PRETTIER_VERSION} assets/settings/default.json --check || {
echo "To fix, run from the root of the Zed repo:"
echo " pnpm dlx prettier@${PRETTIER_VERSION} assets/settings/default.json --write"
false
@@ -235,20 +234,6 @@ jobs:
- name: Build docs
uses: ./.github/actions/build_docs
actionlint:
runs-on: ubuntu-latest
if: github.repository_owner == 'zed-industries' && needs.job_spec.outputs.run_actionlint == 'true'
needs: [job_spec]
steps:
- uses: actions/checkout@v4
- name: Download actionlint
id: get_actionlint
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
shell: bash
- name: Check workflow files
run: ${{ steps.get_actionlint.outputs.executable }} -color
shell: bash
macos_tests:
timeout-minutes: 60
name: (macOS) Run Clippy and tests
@@ -257,7 +242,8 @@ jobs:
github.repository_owner == 'zed-industries' &&
needs.job_spec.outputs.run_tests == 'true'
runs-on:
- self-mini-macos
- self-hosted
- macOS
steps:
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -269,10 +255,6 @@ jobs:
mkdir -p ./../.cargo
cp ./.cargo/ci-config.toml ./../.cargo/config.toml
- name: Check that Cargo.lock is up to date
run: |
cargo update --locked --workspace
- name: cargo clippy
run: ./script/clippy
@@ -330,7 +312,7 @@ jobs:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -382,7 +364,7 @@ jobs:
- buildjet-8vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
@@ -462,7 +444,6 @@ jobs:
- job_spec
- style
- check_docs
- actionlint
- migration_checks
# run_tests: If adding required tests, add them here and to script below.
- workspace_hack
@@ -484,11 +465,6 @@ jobs:
if [[ "${{ needs.job_spec.outputs.run_docs }}" == "true" ]]; then
[[ "${{ needs.check_docs.result }}" != 'success' ]] && { RET_CODE=1; echo "docs checks failed"; }
fi
if [[ "${{ needs.job_spec.outputs.run_actionlint }}" == "true" ]]; then
[[ "${{ needs.actionlint.result }}" != 'success' ]] && { RET_CODE=1; echo "actionlint checks failed"; }
fi
# Only check test jobs if they were supposed to run
if [[ "${{ needs.job_spec.outputs.run_tests }}" == "true" ]]; then
[[ "${{ needs.workspace_hack.result }}" != 'success' ]] && { RET_CODE=1; echo "Workspace Hack failed"; }
@@ -508,7 +484,8 @@ jobs:
timeout-minutes: 120
name: Create a macOS bundle
runs-on:
- self-mini-macos
- self-hosted
- bundle
if: |
startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
@@ -702,10 +679,8 @@ jobs:
timeout-minutes: 60
runs-on: github-8vcpu-ubuntu-2404
if: |
false && (
startsWith(github.ref, 'refs/tags/v')
|| contains(github.event.pull_request.labels.*.name, 'run-bundling')
)
needs: [linux_tests]
name: Build Zed on FreeBSD
steps:
@@ -771,8 +746,7 @@ jobs:
timeout-minutes: 120
name: Create a Windows installer
runs-on: [self-hosted, Windows, X64]
if: contains(github.event.pull_request.labels.*.name, 'run-bundling')
# if: (startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling'))
if: ${{ startsWith(github.ref, 'refs/tags/v') || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
needs: [windows_tests]
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_SIGNING_TENANT_ID }}
@@ -811,7 +785,7 @@ jobs:
- name: Upload Artifacts to release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
# Re-enable when we are ready to publish windows preview releases
if: ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
if: false && ${{ !(contains(github.event.pull_request.labels.*.name, 'run-bundling')) && env.RELEASE_CHANNEL == 'preview' }} # upload only preview
with:
draft: true
prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}
@@ -824,11 +798,12 @@ jobs:
if: |
startsWith(github.ref, 'refs/tags/v')
&& endsWith(github.ref, '-pre') && !endsWith(github.ref, '.0-pre')
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64]
needs: [bundle-mac, bundle-linux-x86_x64, bundle-linux-aarch64, bundle-windows-x64, freebsd]
runs-on:
- self-mini-macos
- self-hosted
- bundle
steps:
- name: gh release
run: gh release edit "$GITHUB_REF_NAME" --draft=false
run: gh release edit $GITHUB_REF_NAME --draft=false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -18,7 +18,7 @@ jobs:
URL="https://zed.dev/releases/stable/latest"
fi
echo "URL=$URL" >> "$GITHUB_OUTPUT"
echo "URL=$URL" >> $GITHUB_OUTPUT
- name: Get content
uses: 2428392/gh-truncate-string-action@b3ff790d21cf42af3ca7579146eedb93c8fb0757 # v1.4.1
id: get-content
@@ -50,9 +50,9 @@ jobs:
PREVIEW_TAG="${VERSION}-pre"
if git rev-parse "$PREVIEW_TAG" > /dev/null 2>&1; then
echo "was_promoted_from_preview=true" >> "$GITHUB_OUTPUT"
echo "was_promoted_from_preview=true" >> $GITHUB_OUTPUT
else
echo "was_promoted_from_preview=false" >> "$GITHUB_OUTPUT"
echo "was_promoted_from_preview=false" >> $GITHUB_OUTPUT
fi
- name: Send release notes email

View File

@@ -79,12 +79,12 @@ jobs:
- name: Build docker image
run: |
docker build -f Dockerfile-collab \
--build-arg "GITHUB_SHA=$GITHUB_SHA" \
--tag "registry.digitalocean.com/zed/collab:$GITHUB_SHA" \
--build-arg GITHUB_SHA=$GITHUB_SHA \
--tag registry.digitalocean.com/zed/collab:$GITHUB_SHA \
.
- name: Publish docker image
run: docker push "registry.digitalocean.com/zed/collab:${GITHUB_SHA}"
run: docker push registry.digitalocean.com/zed/collab:${GITHUB_SHA}
- name: Prune Docker system
run: docker system prune --filter 'until=72h' -f
@@ -131,8 +131,7 @@ jobs:
source script/lib/deploy-helpers.sh
export_vars_for_environment $ZED_KUBE_NAMESPACE
ZED_DO_CERTIFICATE_ID="$(doctl compute certificate list --format ID --no-header)"
export ZED_DO_CERTIFICATE_ID
export ZED_DO_CERTIFICATE_ID=$(doctl compute certificate list --format ID --no-header)
export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:${GITHUB_SHA}"
export ZED_SERVICE_NAME=collab

View File

@@ -35,7 +35,7 @@ jobs:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

View File

@@ -43,8 +43,8 @@ jobs:
- name: Set path
if: ${{ ! matrix.system.install_nix }}
run: |
echo "/nix/var/nix/profiles/default/bin" >> "$GITHUB_PATH"
echo "/Users/administrator/.nix-profile/bin" >> "$GITHUB_PATH"
echo "/nix/var/nix/profiles/default/bin" >> $GITHUB_PATH
echo "/Users/administrator/.nix-profile/bin" >> $GITHUB_PATH
- uses: cachix/install-nix-action@02a151ada4993995686f9ed4f1be7cfbb229e56f # v31
if: ${{ matrix.system.install_nix }}
@@ -56,13 +56,11 @@ jobs:
name: zed
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
pushFilter: "${{ inputs.cachix-filter }}"
cachixArgs: "-v"
cachixArgs: '-v'
- run: nix build .#${{ inputs.flake-output }} -L --accept-flake-config
- name: Limit /nix/store to 50GB on macs
if: ${{ ! matrix.system.install_nix }}
run: |
if [ "$(du -sm /nix/store | cut -f1)" -gt 50000 ]; then
nix-collect-garbage -d || true
fi
[ $(du -sm /nix/store | cut -f1) -gt 50000 ] && nix-collect-garbage -d || :

View File

@@ -85,7 +85,8 @@ jobs:
name: Create a macOS bundle
if: github.repository_owner == 'zed-industries'
runs-on:
- self-mini-macos
- self-hosted
- bundle
needs: tests
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
@@ -111,11 +112,6 @@ jobs:
echo "Publishing version: ${version} on release channel nightly"
echo "nightly" > crates/zed/RELEASE_CHANNEL
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Create macOS app bundle
run: script/bundle-mac
@@ -136,16 +132,11 @@ jobs:
clean: false
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Install Linux dependencies
run: ./script/linux && ./script/install-mold 2.34.0
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
@@ -178,11 +169,6 @@ jobs:
- name: Install Linux dependencies
run: ./script/linux
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Limit target directory size
run: script/clear-target-dir-if-larger-than 100
@@ -201,7 +187,7 @@ jobs:
freebsd:
timeout-minutes: 60
if: false && github.repository_owner == 'zed-industries'
if: github.repository_owner == 'zed-industries'
runs-on: github-8vcpu-ubuntu-2404
needs: tests
name: Build Zed on FreeBSD
@@ -277,11 +263,6 @@ jobs:
Write-Host "Publishing version: $version on release channel nightly"
"nightly" | Set-Content -Path "crates/zed/RELEASE_CHANNEL"
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@3e938c54b3018bdd019973689ef984e033b0454b #v2
with:
token: ${{ SECRETS.SENTRY_AUTH_TOKEN }}
- name: Build Zed installer
working-directory: ${{ env.ZED_WORKSPACE }}
run: script/bundle-windows.ps1

View File

@@ -26,7 +26,7 @@ jobs:
- buildjet-16vcpu-ubuntu-2204
steps:
- name: Add Rust to the PATH
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Checkout repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

732
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,12 @@
[workspace]
resolver = "2"
members = [
"crates/acp_thread",
"crates/activity_indicator",
"crates/agent",
"crates/agent_servers",
"crates/agent_settings",
"crates/acp",
"crates/agent_ui",
"crates/ai_onboarding",
"crates/agent",
"crates/agent_settings",
"crates/agent_servers",
"crates/anthropic",
"crates/askpass",
"crates/assets",
@@ -29,9 +28,6 @@ members = [
"crates/cli",
"crates/client",
"crates/clock",
"crates/cloud_api_client",
"crates/cloud_api_types",
"crates/cloud_llm_client",
"crates/collab",
"crates/collab_ui",
"crates/collections",
@@ -51,8 +47,8 @@ members = [
"crates/diagnostics",
"crates/docs_preprocessor",
"crates/editor",
"crates/eval",
"crates/explorer_command_injector",
"crates/eval",
"crates/extension",
"crates/extension_api",
"crates/extension_cli",
@@ -73,6 +69,7 @@ members = [
"crates/gpui",
"crates/gpui_macros",
"crates/gpui_tokio",
"crates/html_to_markdown",
"crates/http_client",
"crates/http_client_tls",
@@ -101,15 +98,14 @@ members = [
"crates/markdown_preview",
"crates/media",
"crates/menu",
"crates/svg_preview",
"crates/migrator",
"crates/mistral",
"crates/multi_buffer",
"crates/nc",
"crates/net",
"crates/node_runtime",
"crates/notifications",
"crates/ollama",
"crates/onboarding",
"crates/open_ai",
"crates/open_router",
"crates/outline",
@@ -141,7 +137,6 @@ members = [
"crates/semantic_version",
"crates/session",
"crates/settings",
"crates/settings_profile_selector",
"crates/settings_ui",
"crates/snippet",
"crates/snippet_provider",
@@ -154,7 +149,6 @@ members = [
"crates/sum_tree",
"crates/supermaven",
"crates/supermaven_api",
"crates/svg_preview",
"crates/tab_switcher",
"crates/task",
"crates/tasks_ui",
@@ -185,7 +179,6 @@ members = [
"crates/welcome",
"crates/workspace",
"crates/worktree",
"crates/x_ai",
"crates/zed",
"crates/zed_actions",
"crates/zeta",
@@ -225,14 +218,13 @@ edition = "2024"
# Workspace member crates
#
acp_thread = { path = "crates/acp_thread" }
acp = { path = "crates/acp" }
agent = { path = "crates/agent" }
activity_indicator = { path = "crates/activity_indicator" }
agent_ui = { path = "crates/agent_ui" }
agent_settings = { path = "crates/agent_settings" }
agent_servers = { path = "crates/agent_servers" }
ai = { path = "crates/ai" }
ai_onboarding = { path = "crates/ai_onboarding" }
anthropic = { path = "crates/anthropic" }
askpass = { path = "crates/askpass" }
assets = { path = "crates/assets" }
@@ -254,9 +246,6 @@ channel = { path = "crates/channel" }
cli = { path = "crates/cli" }
client = { path = "crates/client" }
clock = { path = "crates/clock" }
cloud_api_client = { path = "crates/cloud_api_client" }
cloud_api_types = { path = "crates/cloud_api_types" }
cloud_llm_client = { path = "crates/cloud_llm_client" }
collab = { path = "crates/collab" }
collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections" }
@@ -327,12 +316,10 @@ menu = { path = "crates/menu" }
migrator = { path = "crates/migrator" }
mistral = { path = "crates/mistral" }
multi_buffer = { path = "crates/multi_buffer" }
nc = { path = "crates/nc" }
net = { path = "crates/net" }
node_runtime = { path = "crates/node_runtime" }
notifications = { path = "crates/notifications" }
ollama = { path = "crates/ollama" }
onboarding = { path = "crates/onboarding" }
open_ai = { path = "crates/open_ai" }
open_router = { path = "crates/open_router", features = ["schemars"] }
outline = { path = "crates/outline" }
@@ -343,7 +330,6 @@ picker = { path = "crates/picker" }
plugin = { path = "crates/plugin" }
plugin_macros = { path = "crates/plugin_macros" }
prettier = { path = "crates/prettier" }
settings_profile_selector = { path = "crates/settings_profile_selector" }
project = { path = "crates/project" }
project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" }
@@ -408,7 +394,6 @@ web_search_providers = { path = "crates/web_search_providers" }
welcome = { path = "crates/welcome" }
workspace = { path = "crates/workspace" }
worktree = { path = "crates/worktree" }
x_ai = { path = "crates/x_ai" }
zed = { path = "crates/zed" }
zed_actions = { path = "crates/zed_actions" }
zeta = { path = "crates/zeta" }
@@ -419,8 +404,7 @@ zlog_settings = { path = "crates/zlog_settings" }
# External crates
#
agentic-coding-protocol = "0.0.10"
agent-client-protocol = "0.0.11"
agentic-coding-protocol = "0.0.7"
aho-corasick = "1.1"
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
any_vec = "0.14"
@@ -448,9 +432,9 @@ aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
base64 = "0.22"
bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blade-util = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blade-graphics = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blade-util = { git = "https://github.com/kvark/blade", rev = "416375211bb0b5826b3584dccdb6a43369e499ad" }
blake3 = "1.5.3"
bytes = "1.0"
cargo_metadata = "0.19"
@@ -467,7 +451,7 @@ core-video = { version = "0.4.3", features = ["metal"] }
cpal = "0.16"
criterion = { version = "0.5", features = ["html_reports"] }
ctor = "0.4.0"
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "1b461b310481d01e02b2603c16d7144b926339f8" }
dap-types = { git = "https://github.com/zed-industries/dap-types", rev = "7f39295b441614ca9dbf44293e53c32f666897f9" }
dashmap = "6.0"
derive_more = "0.99.17"
dirs = "4.0"
@@ -490,7 +474,6 @@ heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
hex = "0.4.3"
html5ever = "0.27.0"
http = "1.1"
http-body = "1.0"
hyper = "0.14"
ignore = "0.4.22"
image = "0.25.1"
@@ -504,18 +487,18 @@ json_dotpath = "1.1"
jsonschema = "0.30.0"
jsonwebtoken = "9.3"
jupyter-protocol = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed" ,rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
jupyter-websocket-client = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "39f629bdd03d59abd786ed9fc27e8bca02c0c0ec" }
lsp-types = { git = "https://github.com/zed-industries/lsp-types", rev = "c9c189f1c5dd53c624a419ce35bc77ad6a908d18" }
markup5ever_rcdom = "0.3.0"
metal = "0.29"
moka = { version = "0.12.10", features = ["sync"] }
naga = { version = "25.0", features = ["wgsl-in"] }
nanoid = "0.4"
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
nbformat = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734" }
nix = "0.29"
num-format = "0.4.4"
objc = "0.2"
@@ -524,7 +507,6 @@ ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
partial-json-fixer = "0.5.3"
parse_int = "0.9"
pathdiff = "0.2"
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" }
@@ -556,7 +538,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "951c77
"stream",
] }
rsa = "0.9.6"
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
runtimelib = { git = "https://github.com/ConradIrwin/runtimed", rev = "7130c804216b6914355d15d0b91ea91f6babd734", default-features = false, features = [
"async-dispatcher-runtime",
] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
@@ -564,7 +546,7 @@ rustc-demangle = "0.1.23"
rustc-hash = "2.1.0"
rustls = { version = "0.23.26" }
rustls-platform-verifier = "0.5.0"
scap = { git = "https://github.com/zed-industries/scap", rev = "808aa5c45b41e8f44729d02e38fd00a2fe2722e7", default-features = false }
scap = { git = "https://github.com/zed-industries/scap", rev = "28dd306ff2e3374404936dec778fc1e975b8dd12", default-features = false }
schemars = { version = "1.0", features = ["indexmap2"] }
semver = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }
@@ -652,6 +634,7 @@ which = "6.0.0"
windows-core = "0.61"
wit-component = "0.221"
workspace-hack = "0.1.0"
zed_llm_client = "= 0.8.6"
zstd = "0.11"
[workspace.dependencies.async-stripe]
@@ -678,16 +661,14 @@ features = [
"UI_ViewManagement",
"Wdk_System_SystemServices",
"Win32_Globalization",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D11",
"Win32_Graphics_Direct3D_Fxc",
"Win32_Graphics_DirectComposition",
"Win32_Graphics_Direct2D",
"Win32_Graphics_Direct2D_Common",
"Win32_Graphics_DirectWrite",
"Win32_Graphics_Dwm",
"Win32_Graphics_Dxgi",
"Win32_Graphics_Dxgi_Common",
"Win32_Graphics_Gdi",
"Win32_Graphics_Imaging",
"Win32_Graphics_Imaging_D2D",
"Win32_Networking_WinSock",
"Win32_Security",
"Win32_Security_Credentials",
@@ -719,7 +700,6 @@ features = [
[patch.crates-io]
notify = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
notify-types = { git = "https://github.com/zed-industries/notify.git", rev = "bbb9ea5ae52b253e095737847e367c30653a2e96" }
windows-capture = { git = "https://github.com/zed-industries/windows-capture.git", rev = "f0d6c1b6691db75461b732f6d5ff56eed002eeb9" }
# Makes the workspace hack crate refer to the local one, but only when you're building locally
workspace-hack = { path = "tooling/workspace-hack" }
@@ -728,11 +708,6 @@ workspace-hack = { path = "tooling/workspace-hack" }
split-debuginfo = "unpacked"
codegen-units = 16
# mirror configuration for crates compiled for the build platform
# (without this cargo will compile ~400 crates twice)
[profile.dev.build-override]
codegen-units = 16
[profile.dev.package]
taffy = { opt-level = 3 }
cranelift-codegen = { opt-level = 3 }

View File

@@ -1,6 +1,5 @@
# Zed
[![Zed](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/zed-industries/zed/main/assets/badge/v0.json)](https://zed.dev)
[![CI](https://github.com/zed-industries/zed/actions/workflows/ci.yml/badge.svg)](https://github.com/zed-industries/zed/actions/workflows/ci.yml)
Welcome to Zed, a high-performance, multiplayer code editor from the creators of [Atom](https://github.com/atom/atom) and [Tree-sitter](https://github.com/tree-sitter/tree-sitter).

View File

@@ -1,8 +0,0 @@
{
"label": "",
"message": "Zed",
"logoSvg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 96 96\"><rect width=\"96\" height=\"96\" fill=\"#000\"/><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M9 6C7.34315 6 6 7.34315 6 9V75H0V9C0 4.02944 4.02944 0 9 0H89.3787C93.3878 0 95.3955 4.84715 92.5607 7.68198L43.0551 57.1875H57V51H63V58.6875C63 61.1728 60.9853 63.1875 58.5 63.1875H37.0551L26.7426 73.5H73.5V36H79.5V73.5C79.5 76.8137 76.8137 79.5 73.5 79.5H20.7426L10.2426 90H87C88.6569 90 90 88.6569 90 87V21H96V87C96 91.9706 91.9706 96 87 96H6.62132C2.61224 96 0.604504 91.1529 3.43934 88.318L52.7574 39H39V45H33V37.5C33 35.0147 35.0147 33 37.5 33H58.7574L69.2574 22.5H22.5V60H16.5V22.5C16.5 19.1863 19.1863 16.5 22.5 16.5H75.2574L85.7574 6H9Z\" fill=\"#fff\"/></svg>",
"logoWidth": 16,
"labelColor": "black",
"color": "white"
}

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="M4.35443 9.97775L6.71495 8.65418L6.75443 8.53883L6.71495 8.47508H6.59948L6.20456 8.45081L4.8557 8.41436L3.68608 8.36579L2.55291 8.30507L2.26734 8.24438L2 7.89224L2.02734 7.71617L2.26734 7.55528L2.61063 7.58564L3.37013 7.63724L4.50937 7.71617L5.3357 7.76474L6.56 7.89224H6.75443L6.78176 7.81331L6.71495 7.76474L6.66329 7.71617L5.48456 6.91778L4.20861 6.07388L3.54025 5.58815L3.17873 5.34227L2.99646 5.11157L2.91747 4.60764L3.24557 4.24639L3.68608 4.27675L3.79848 4.30711L4.24506 4.65014L5.19899 5.38781L6.44456 6.30458L6.62684 6.45635L6.69974 6.40475L6.70886 6.36833L6.62684 6.23171L5.94938 5.00834L5.22632 3.76372L4.9043 3.24766L4.81924 2.93802C4.78886 2.81053 4.7676 2.70427 4.7676 2.57374L5.14127 2.06678L5.34785 2L5.84609 2.06678L6.0557 2.24893L6.36557 2.95624L6.86684 4.07033L7.64456 5.58512L7.87241 6.0344L7.99391 6.45029L8.03948 6.57779H8.11847V6.50492L8.18228 5.6519L8.30075 4.6046L8.41619 3.25677L8.4557 2.87731L8.64404 2.42196L9.01772 2.17607L9.30938 2.31571L9.54938 2.65874L9.51596 2.88034L9.37316 3.80622L9.09368 5.25728L8.9114 6.22868H9.01772L9.13925 6.10727L9.6314 5.45459L10.4577 4.42246L10.8223 4.01265L11.2476 3.56033L11.521 3.3448H12.0375L12.4172 3.90944L12.2471 4.49228L11.7154 5.1662L11.275 5.73692L10.643 6.58691L10.2481 7.26689L10.2846 7.32152L10.3787 7.31243L11.8066 7.00886L12.5782 6.86921L13.4987 6.71135L13.915 6.90563L13.9605 7.10297L13.7965 7.50671L12.8122 7.74956L11.6577 7.98026L9.93824 8.38706L9.91697 8.40224L9.94127 8.43257L10.716 8.50544L11.0471 8.52365H11.8582L13.3681 8.63597L13.763 8.89703L14 9.21578L13.9605 9.45863L13.3529 9.76829L12.5327 9.57398L10.6187 9.11864L9.96254 8.95472H9.8714V9.00935L10.4182 9.54365L11.4208 10.4483L12.6754 11.614L12.7393 11.9023L12.5782 12.13L12.4081 12.1057L11.3053 11.277L10.88 10.9036L9.91697 10.0931H9.85316V10.1781L10.075 10.5029L11.2476 12.2636L11.3083 12.804L11.2233 12.98L10.9195 13.0863L10.5853 13.0255L9.89873 12.0632L9.19088 10.9795L8.61974 10.0081L8.54987 10.0476L8.21267 13.6752L8.05469 13.8604L7.69013 14L7.38632 13.7693L7.22531 13.3959L7.38632 12.6582L7.58075 11.6959L7.73873 10.9309L7.88153 9.98078L7.96658 9.66506L7.96052 9.64382L7.89062 9.65291L7.17368 10.6365L6.08303 12.1088L5.22026 13.0316L5.01368 13.1136L4.65519 12.9284L4.68861 12.5975L4.88911 12.303L6.08303 10.7852L6.80303 9.84416L7.26785 9.30077L7.26482 9.22187H7.23746L4.06582 11.2801L3.50076 11.3529L3.25772 11.1252L3.2881 10.7518L3.40354 10.6304L4.35747 9.97469L4.35443 9.97775Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,3 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.44 12.27C7.81333 13.1217 8 14.0317 8 15C8 14.0317 8.18083 13.1217 8.5425 12.27C8.91583 11.4183 9.4175 10.6775 10.0475 10.0475C10.6775 9.4175 11.4183 8.92167 12.27 8.56C13.1217 8.18667 14.0317 8 15 8C14.0317 8 13.1217 7.81917 12.27 7.4575C11.4411 7.1001 10.6871 6.5895 10.0475 5.9525C9.4105 5.31293 8.8999 4.55891 8.5425 3.73C8.18083 2.87833 8 1.96833 8 1C8 1.96833 7.81333 2.87833 7.44 3.73C7.07833 4.58167 6.5825 5.3225 5.9525 5.9525C5.31293 6.5895 4.55891 7.1001 3.73 7.4575C2.87833 7.81917 1.96833 8 1 8C1.96833 8 2.87833 8.18667 3.73 8.56C4.58167 8.92167 5.3225 9.4175 5.9525 10.0475C6.5825 10.6775 7.07833 11.4183 7.44 12.27Z" fill="black"/>
</svg>
<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: 762 B

After

Width:  |  Height:  |  Size: 402 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="M7.25669 0.999943C8.27509 0.993825 9.24655 1.42125 9.9227 2.17279C11.4427 1.85079 12.9991 2.53518 13.7733 3.86518C14.159 4.5149 14.3171 5.26409 14.2372 5.99994H13.2967C13.3789 5.42185 13.265 4.8321 12.9686 4.32514C12.2353 3.06961 10.6088 2.63919 9.33676 3.36322L6.48032 4.98822C6.46926 4.99697 6.46284 5.01135 6.46372 5.02533V6.38568L9.91294 4.42084C10.0565 4.33818 10.2336 4.33823 10.3768 4.42084L13.1502 5.99994H11.2948L9.88364 5.19623C9.87034 5.19054 9.85459 5.19128 9.84262 5.19916L8.64926 5.87983L8.8602 5.99994H7.99985C6.89539 6.00004 5.99988 6.89547 5.99985 7.99994V9.34955L3.90219 8.15522C3.75815 8.07431 3.66897 7.92228 3.66977 7.75873V4.53803C3.66977 4.50828 3.67172 4.4654 3.67172 4.44135C3.08836 4.65262 2.59832 5.0599 2.28794 5.59174C1.55635 6.84647 1.99122 8.44936 3.26059 9.17475L5.99985 10.7363V11.6162C5.87564 11.6568 5.73827 11.6456 5.6229 11.579L2.7977 9.96869C2.77156 9.95382 2.73449 9.9311 2.71372 9.91889C2.60687 10.5231 2.7194 11.1466 3.0311 11.6777C3.6435 12.7209 4.87159 13.1902 5.99985 12.9023V13.8398C4.50443 14.1233 2.98758 13.4424 2.22641 12.1347C1.71174 11.2677 1.60096 10.2237 1.9227 9.27045C0.880739 8.13295 0.703328 6.46023 1.48325 5.13373C1.98739 4.26024 2.84863 3.64401 3.84653 3.44233C4.3245 1.9837 5.70306 0.996447 7.25669 0.999943ZM7.25766 1.91498C5.78932 1.9143 4.59839 3.08914 4.59751 4.53803V7.79193C4.59926 7.80578 4.60735 7.81796 4.61997 7.82416L5.8143 8.50483L5.81626 4.57611C5.81537 4.41216 5.90431 4.2606 6.04868 4.17963L8.87387 2.56928C8.89868 2.55441 8.93612 2.53379 8.95786 2.5224C8.48035 2.13046 7.8788 1.91498 7.25766 1.91498Z" fill="black"/>
<path d="M13.5 6C14.6046 6 15.5 6.89543 15.5 8V13.5C15.5 14.6046 14.6046 15.5 13.5 15.5H8C6.89543 15.5 6 14.6046 6 13.5V8C6 6.89543 6.89543 6 8 6H13.5ZM10.8916 8.02539C10.0563 8.02539 9.33453 8.27982 8.81934 8.76562C8.30213 9.25335 8.02547 9.94371 8.02539 10.748C8.02539 11.557 8.29852 12.2492 8.81543 12.7373C9.33013 13.2232 10.0521 13.4746 10.8916 13.4746C11.9865 13.4745 12.8545 13.1022 13.3076 12.3525C13.3894 12.2176 13.4521 12.0693 13.4521 11.8857C13.4521 11.4795 13.0933 11.2773 12.7842 11.2773C12.6604 11.2774 12.5292 11.3025 12.4072 11.3779C12.2862 11.4529 12.2058 11.5586 12.1494 11.666L12.1475 11.6689C11.9677 12.0213 11.5535 12.246 10.8955 12.2461C10.4219 12.2461 10.0667 12.0932 9.83008 11.8506C9.59255 11.607 9.44141 11.2389 9.44141 10.748C9.44148 10.264 9.59319 9.89628 9.83203 9.65137C10.0702 9.40725 10.4255 9.25391 10.8916 9.25391C11.4912 9.25399 11.9415 9.50614 12.1289 9.8916V9.89062C12.1888 10.0157 12.276 10.1311 12.4023 10.2129C12.5303 10.2956 12.6724 10.3271 12.8115 10.3271C12.9661 10.3271 13.1303 10.2857 13.2627 10.1758C13.4018 10.0603 13.4746 9.89383 13.4746 9.71582C13.4746 9.61857 13.4542 9.52036 13.4199 9.42773L13.3818 9.33691C12.9749 8.49175 11.9927 8.02548 10.8916 8.02539ZM10.3203 8.97852L10.1494 9.03516C10.2095 9.01178 10.2716 8.99089 10.3359 8.97363C10.3307 8.97505 10.3256 8.97706 10.3203 8.97852ZM10.4814 8.94141C10.4969 8.9385 10.5126 8.93616 10.5283 8.93359C10.5126 8.93617 10.4969 8.9385 10.4814 8.94141ZM10.6709 8.91504C10.6819 8.91399 10.693 8.913 10.7041 8.91211C10.693 8.913 10.6819 8.91399 10.6709 8.91504Z" fill="black" fill-opacity="0.95"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="currentColor" stroke="currentColor">
<g>
<g clip-path="url(#clip0_205_3)">
<path d="M0.094 7.78c0.469 0 2.281 -0.405 3.219 -0.936s0.938 -0.531 2.875 -1.906c2.453 -1.741 4.188 -1.158 7.031 -1.158" stroke-width="2.8125" />
<path d="m15.969 3.797 -4.805 2.774V1.023z" />
<path d="M0 7.781c0.469 0 2.281 0.405 3.219 0.936s0.938 0.531 2.875 1.906C8.547 12.364 10.281 11.781 13.125 11.781" stroke-width="2.8125" />

Before

Width:  |  Height:  |  Size: 545 B

After

Width:  |  Height:  |  Size: 575 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="m12.414 5.47.27 9.641h2.157l.27-13.15zM15.11.889h-3.293L6.651 7.613l1.647 2.142zM.889 15.11H4.18l1.647-2.142-1.647-2.143zm0-9.641 7.409 9.641h3.292L4.181 5.47z" fill="#000"/>
</svg>

Before

Width:  |  Height:  |  Size: 289 B

View File

@@ -1,7 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.6667 6C11.003 6.44823 11.2208 6.97398 11.3001 7.52867" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.9094 3.75732C13.7621 4.6095 14.3383 5.69876 14.5629 6.88315C14.7875 8.06754 14.6502 9.29213 14.1688 10.3973" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.66675 2L13.6667 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.33333 4.66669L4.942 5.05802C4.85494 5.1456 4.75136 5.21504 4.63726 5.2623C4.52317 5.30957 4.40083 5.33372 4.27733 5.33335H2.66667C2.48986 5.33335 2.32029 5.40359 2.19526 5.52862C2.07024 5.65364 2 5.82321 2 6.00002V10C2 10.1768 2.07024 10.3464 2.19526 10.4714C2.32029 10.5964 2.48986 10.6667 2.66667 10.6667H4.27733C4.40083 10.6663 4.52317 10.6905 4.63726 10.7377C4.75136 10.785 4.85494 10.8544 4.942 10.942L7.19733 13.198C7.26307 13.2639 7.34687 13.3088 7.43813 13.3269C7.52939 13.3451 7.62399 13.3358 7.70995 13.3002C7.79591 13.2646 7.86936 13.2042 7.921 13.1268C7.97263 13.0494 8.00013 12.9584 8 12.8654V7.33335" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.21875 2.78136C7.28267 2.71719 7.36421 2.67345 7.45303 2.65568C7.54184 2.63791 7.63393 2.64691 7.71762 2.68154C7.80132 2.71618 7.87284 2.77488 7.92312 2.85022C7.97341 2.92555 8.0002 3.01412 8.00008 3.10469V3.56202" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</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-volume-off"><path d="M16 9a5 5 0 0 1 .95 2.293"/><path d="M19.364 5.636a9 9 0 0 1 1.889 9.96"/><path d="m2 2 20 20"/><path d="m7 7-.587.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298V11"/><path d="M9.828 4.172A.686.686 0 0 1 11 4.657v.686"/></svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 527 B

View File

@@ -1,5 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3.13467C7.99987 3.04181 7.97223 2.95107 7.92057 2.8739C7.86892 2.79674 7.79557 2.7366 7.70977 2.70108C7.62397 2.66557 7.52958 2.65626 7.43849 2.67434C7.34741 2.69242 7.26373 2.73707 7.198 2.80266L4.942 5.058C4.85494 5.14558 4.75136 5.21502 4.63726 5.26228C4.52317 5.30954 4.40083 5.33369 4.27733 5.33333H2.66667C2.48986 5.33333 2.32029 5.40357 2.19526 5.52859C2.07024 5.65362 2 5.82319 2 6V10C2 10.1768 2.07024 10.3464 2.19526 10.4714C2.32029 10.5964 2.48986 10.6667 2.66667 10.6667H4.27733C4.40083 10.6663 4.52317 10.6905 4.63726 10.7377C4.75136 10.785 4.85494 10.8544 4.942 10.942L7.19733 13.198C7.26307 13.2639 7.34687 13.3087 7.43813 13.3269C7.52939 13.3451 7.62399 13.3358 7.70995 13.3002C7.79591 13.2645 7.86936 13.2042 7.921 13.1268C7.97263 13.0494 8.00013 12.9584 8 12.8653V3.13467Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.6667 6C11.0995 6.57699 11.3334 7.27877 11.3334 8C11.3334 8.72123 11.0995 9.42301 10.6667 10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.9094 12.2427C13.4666 11.6855 13.9085 11.0241 14.2101 10.2961C14.5116 9.56815 14.6668 8.78793 14.6668 7.99999C14.6668 7.21205 14.5116 6.43183 14.2101 5.70387C13.9085 4.97591 13.4666 4.31448 12.9094 3.75732" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</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-volume-2"><path d="M11 4.702a.705.705 0 0 0-1.203-.498L6.413 7.587A1.4 1.4 0 0 1 5.416 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.416a1.4 1.4 0 0 1 .997.413l3.383 3.384A.705.705 0 0 0 11 19.298z"/><path d="M16 9a5 5 0 0 1 0 6"/><path d="M19.364 18.364a9 9 0 0 0 0-12.728"/></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 475 B

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-cloud-download-icon lucide-cloud-download"><path d="M12 13v8l-4-4"/><path d="m12 21 4-4"/><path d="M4.393 15.269A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.436 8.284"/></svg>

Before

Width:  |  Height:  |  Size: 372 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">
<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>
<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>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 615 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -1,9 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.6" d="M3.5 11V5.5L8.5 8L3.5 11Z" fill="black"/>
<path opacity="0.4" d="M8.5 14L3.5 11L8.5 8V14Z" fill="black"/>
<path opacity="0.6" d="M8.5 5.5H3.5L8.5 2.5L8.5 5.5Z" fill="black"/>
<path opacity="0.8" d="M8.5 5.5V2.5L13.5 5.5H8.5Z" fill="black"/>
<path opacity="0.2" d="M13.5 11L8.5 14L11 9.5L13.5 11Z" fill="black"/>
<path opacity="0.5" d="M13.5 11L11 9.5L13.5 5V11Z" fill="black"/>
<path d="M3.5 11V5L8.5 2.11325L13.5 5V11L8.5 13.8868L3.5 11Z" stroke="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 583 B

View File

@@ -1,10 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2716_663)">
<path d="M8.47552 2.45453C11.5167 2.45457 13.9814 4.94501 13.9814 8.01623C13.9814 11.0875 11.5167 13.578 8.47552 13.5781C5.43427 13.5781 2.96948 11.0875 2.96948 8.01623C2.9695 4.94498 5.43429 2.45453 8.47552 2.45453ZM10.8795 4.70348C10.7605 4.16887 10.1328 3.85468 9.53627 3.96342C8.97622 4.06552 7.62871 4.45681 7.62057 4.45916C9.29414 4.44469 9.57429 4.4726 9.69939 4.64751C9.77324 4.7508 9.66576 4.89248 9.21944 4.96538C8.73515 5.04447 7.73014 5.13958 7.72343 5.14022C6.75441 5.19776 6.07177 5.20168 5.86705 5.63512C5.73334 5.91827 6.00968 6.16857 6.13082 6.32527C6.64271 6.89455 7.38215 7.20158 7.85809 7.42767C8.03716 7.51274 8.56257 7.67345 8.56257 7.67345C7.01855 7.58853 5.90474 8.06267 5.2514 8.60855C4.51246 9.29204 4.83937 10.1067 6.35327 10.6084C7.24742 10.9047 7.69094 11.0439 9.02473 10.9238C9.81031 10.8815 9.9342 10.9068 9.94203 10.9712C9.95275 11.062 9.06932 11.2874 8.82812 11.357C8.21455 11.534 6.60645 11.8913 6.59758 11.8932C6.60115 11.8935 7.06249 11.9257 7.65531 11.8735C7.89632 11.8522 8.81142 11.7624 9.49557 11.6123C9.49557 11.6123 10.3297 11.4338 10.7759 11.2693C11.2429 11.0973 11.497 10.9512 11.6113 10.7443C11.6063 10.7019 11.6465 10.5516 11.4313 10.4613C10.8807 10.2304 10.2423 10.2721 8.9789 10.2453C7.57789 10.1972 7.11184 9.9626 6.86356 9.77373C6.62548 9.58212 6.74518 9.05204 7.76528 8.5851C8.27917 8.33646 10.2935 7.87759 10.2935 7.87759C9.61511 7.54227 8.35014 6.95284 8.09005 6.82552C7.86199 6.71388 7.49701 6.54572 7.4179 6.34233C7.32824 6.14709 7.6297 5.97888 7.79813 5.9307C8.34057 5.77424 9.10635 5.67701 9.8033 5.66609C10.1536 5.66061 10.2105 5.63806 10.2105 5.63806C10.6939 5.55787 11.0121 5.22722 10.8795 4.70348Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0_2716_663">
<rect width="12" height="12" fill="white" transform="translate(2.5 2)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 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="M3.6725 13.9985C3.36161 13.9982 3.06354 13.8746 2.84371 13.6548C2.62388 13.435 2.50026 13.1369 2.5 12.826V7.494C2.5 6.8325 2.7675 6.185 3.2365 5.7165L6.219 2.736C6.45192 2.50247 6.72867 2.31724 7.03335 2.19094C7.33804 2.06464 7.66467 1.99975 7.9945 2H13.3275C13.6384 2.00027 13.9365 2.12388 14.1563 2.34371C14.3761 2.56354 14.4997 2.86162 14.5 3.1725V8.5045C14.4983 9.17074 14.2336 9.80936 13.7635 10.2815L10.781 13.264C10.5477 13.4976 10.2706 13.6829 9.96561 13.8092C9.66059 13.9355 9.33364 14.0003 9.0035 14V13.9985H3.6725ZM8.157 10.5715H5.243V11.257H8.157V10.5715ZM4.4815 5.257H11.243V12.0165L13.3715 9.888C13.7373 9.52036 13.9433 9.02316 13.9445 8.5045V3.1725C13.9445 2.8335 13.6685 2.5555 13.3275 2.5555H7.9945C7.73753 2.55499 7.483 2.6053 7.24556 2.70356C7.00813 2.80181 6.79246 2.94606 6.611 3.128L4.4815 5.257ZM4.3855 5.353L3.628 6.11C3.26258 6.47809 3.0569 6.97533 3.0555 7.494V12.826C3.0555 13.165 3.3315 13.443 3.6725 13.443H9.0055C9.26249 13.4434 9.51701 13.3929 9.75445 13.2946C9.99188 13.1963 10.2075 13.052 10.389 12.87L11.145 12.1145H4.3855V5.353Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

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 fill-rule="evenodd" clip-rule="evenodd" d="M13.0945 8.01611C13.0945 7.87619 12.9911 7.79551 12.8642 7.8356L4.13456 10.6038C4.00742 10.6441 3.90427 10.7904 3.90427 10.9301V13.7593C3.90427 13.8992 4.00742 13.9801 4.13456 13.9398L12.8642 11.1719C12.9911 11.1315 13.0945 10.9852 13.0945 10.8453V8.01611Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.90427 7.92597C3.90427 8.06588 4.00742 8.21218 4.13456 8.25252L12.8655 11.0209C12.9926 11.0613 13.0958 10.9803 13.0958 10.8407V8.01124C13.0958 7.87158 12.9926 7.72529 12.8655 7.68494L4.13456 4.91652C4.00742 4.87618 3.90427 4.95686 3.90427 5.09677V7.92597Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.0945 2.20248C13.0945 2.06256 12.9911 1.98163 12.8642 2.02197L4.13456 4.78988C4.00742 4.83022 3.90427 4.97652 3.90427 5.11644V7.94563C3.90427 8.08554 4.00742 8.16622 4.13456 8.12614L12.8642 5.35797C12.9911 5.31763 13.0945 5.17133 13.0945 5.03167V2.20248Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 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 fill-rule="evenodd" clip-rule="evenodd" d="M11.0094 13.9181C11.1984 13.9917 11.4139 13.987 11.6047 13.8952L14.0753 12.7064C14.3349 12.5814 14.5 12.3187 14.5 12.0305V3.9696C14.5 3.68136 14.3349 3.41862 14.0753 3.2937L11.6047 2.10485C11.3543 1.98438 11.0614 2.01389 10.8416 2.17363C10.8102 2.19645 10.7803 2.22193 10.7523 2.25001L6.02261 6.56498L3.96246 5.00115C3.77068 4.85558 3.50244 4.86751 3.32432 5.02953L2.66356 5.63059C2.44569 5.82877 2.44544 6.17152 2.66302 6.37004L4.44965 8.00001L2.66302 9.62998C2.44544 9.82849 2.44569 10.1713 2.66356 10.3694L3.32432 10.9705C3.50244 11.1325 3.77068 11.1444 3.96246 10.9989L6.02261 9.43504L10.7523 13.75C10.8271 13.8249 10.915 13.8812 11.0094 13.9181ZM11.5018 5.27587L7.91309 8.00001L11.5018 10.7241V5.27587Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 876 B

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-equal-icon lucide-equal"><line x1="5" x2="19" y1="9" y2="9"/><line x1="5" x2="19" y1="15" y2="15"/></svg>

Before

Width:  |  Height:  |  Size: 308 B

View File

@@ -1,5 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.437 11.0461L13.4831 8L10.437 4.95392" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 8L8 8" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.6553 13.4659H4.21843C3.89528 13.4659 3.58537 13.3375 3.35687 13.109C3.12837 12.8805 3 12.5706 3 12.2475V3.71843C3 3.39528 3.12837 3.08537 3.35687 2.85687C3.58537 2.62837 3.89528 2.5 4.21843 2.5H6.6553" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M3 1C2.44771 1 2 1.44772 2 2V13C2 13.5523 2.44772 14 3 14H10.5C10.7761 14 11 13.7761 11 13.5C11 13.2239 10.7761 13 10.5 13H3V2L10.5 2C10.7761 2 11 1.77614 11 1.5C11 1.22386 10.7761 1 10.5 1H3ZM12.6036 4.89645C12.4083 4.70118 12.0917 4.70118 11.8964 4.89645C11.7012 5.09171 11.7012 5.40829 11.8964 5.60355L13.2929 7H6.5C6.22386 7 6 7.22386 6 7.5C6 7.77614 6.22386 8 6.5 8H13.2929L11.8964 9.39645C11.7012 9.59171 11.7012 9.90829 11.8964 10.1036C12.0917 10.2988 12.4083 10.2988 12.6036 10.1036L14.8536 7.85355C15.0488 7.65829 15.0488 7.34171 14.8536 7.14645L12.6036 4.89645Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 637 B

After

Width:  |  Height:  |  Size: 768 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="M9.628 11.0743V10.4575H8.45562L8.65084 10.2445C8.75911 10.1264 8.96952 9.79454 9.11862 9.50789C9.52153 8.73047 9.51798 7.25107 9.11862 6.43992C8.58614 5.35722 7.49453 4.56381 6.24942 4.35703C4.59252 4.08192 2.86196 5.00312 2.14045 6.54287C1.77038 7.33182 1.77038 8.64437 2.14045 9.43333C2.45905 10.1122 3.11309 10.8204 3.73609 11.1595C4.51439 11.5828 5.18264 11.676 7.51312 11.6848L9.62627 11.6928L9.628 11.0743ZM5.30605 10.169C4.24109 10.0111 3.45215 9.07124 3.45659 7.96813C3.45659 7.33004 3.70064 6.80022 4.18697 6.36182C4.67685 5.91986 5.1312 5.77344 5.86602 5.82048C7.00287 5.89236 7.82382 6.79845 7.82382 7.98056C7.82382 8.61332 7.71996 8.91682 7.33036 9.42534C6.90172 9.98444 6.08345 10.2853 5.30692 10.1699M15.1374 10.9802V10.2684H11.8138V4.47509H10.1986V11.6928H15.1374V10.9802Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 916 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">
<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"/>
<rect x="8" y="3" width="5.5" height="4" rx="1.5" fill="black"/>
<rect x="8" y="9" width="5.5" height="4" rx="1.5" fill="black"/>
<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="2.5" width="6" height="5" rx="1.5" fill="black"/>
<rect x="8" y="8.46875" width="6" height="5.0625" rx="1.5" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 423 B

After

Width:  |  Height:  |  Size: 462 B

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">
<circle cx="5" cy="12" r="1.25" stroke="black" stroke-width="1.5"/>
<path d="M5 11V5" stroke="black" stroke-width="1.5"/>
<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"/>
<circle cx="5" cy="4" r="1.25" stroke="black" stroke-width="1.5"/>
<circle cx="11" cy="4" r="1.25" stroke="black" stroke-width="1.5"/>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<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 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 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"/>
<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"/>
</svg>

Before

Width:  |  Height:  |  Size: 487 B

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,7 +1 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/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>
<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>

Before

Width:  |  Height:  |  Size: 680 B

After

Width:  |  Height:  |  Size: 349 B

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,5 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 12.2028V14.3042" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.2027 6.94928V8.11672C12.2027 9.20041 11.7599 10.2397 10.9717 11.006C10.1836 11.7723 9.11457 12.2028 7.99992 12.2028C6.88527 12.2028 5.81627 11.7723 5.02809 11.006C4.23991 10.2397 3.79712 9.20041 3.79712 8.11672V6.94928" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.1015 3.63555C10.1015 2.56426 9.16065 1.6958 8.00008 1.6958C6.83951 1.6958 5.89868 2.56426 5.89868 3.63555V8.16165C5.89868 9.23294 6.83951 10.1014 8.00008 10.1014C9.16065 10.1014 10.1015 9.23294 10.1015 8.16165V3.63555Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.72742 8.83338C3.63539 8.57302 3.34973 8.43656 3.08937 8.52858C2.82901 8.6206 2.69255 8.90626 2.78458 9.16662C2.86101 9.38288 2.95188 9.59228 3.056 9.79364C3.81427 11.2601 5.27842 12.3044 7.00014 12.4753L7.00014 14L5.50014 14C5.22399 14 5.00014 14.2239 5.00014 14.5C5.00014 14.7761 5.22399 15 5.50014 15L7.50014 15L9.50014 15C9.77628 15 10.0001 14.7761 10.0001 14.5C10.0001 14.2239 9.77628 14 9.50014 14L8.00014 14L8.00014 12.4753C9.72168 12.3043 11.1857 11.26 11.9439 9.79364C12.048 9.59228 12.1389 9.38288 12.2153 9.16662C12.3073 8.90626 12.1709 8.6206 11.9105 8.52858C11.6501 8.43656 11.3645 8.57302 11.2725 8.83338C11.2114 9.00607 11.1388 9.17337 11.0556 9.33433C10.3899 10.6218 9.04706 11.5 7.49994 11.5C5.95282 11.5 4.60997 10.6218 3.94428 9.33433C3.86104 9.17337 3.78845 9.00607 3.72742 8.83338ZM5.5 3.5L5.5 7.5C5.5 8.60457 6.39543 9.5 7.5 9.5C8.60457 9.5 9.5 8.60457 9.5 7.5L9.5 3.5C9.5 2.39543 8.60457 1.5 7.5 1.5C6.39543 1.5 5.5 2.39543 5.5 3.5ZM4.5 7.5C4.5 9.15685 5.84315 10.5 7.5 10.5C9.15685 10.5 10.5 9.15685 10.5 7.5L10.5 3.5C10.5 1.84315 9.15685 0.5 7.5 0.5C5.84315 0.5 4.5 1.84315 4.5 3.5L4.5 7.5Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 847 B

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,8 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3L13 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 9C12 8.74858 12 8.49375 12 8.23839V7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.00043 7V8.09869C3.98856 8.86731 4.22157 9.62164 4.66938 10.2643C5.11718 10.907 5.75924 11.4085 6.51267 11.7042C7.2661 11.9999 8.09632 12.0761 8.89619 11.923C9.47851 11.8115 10.0253 11.5823 10.5 11.2539" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 6V3.62904C9.99714 3.26103 9.8347 2.90448 9.53885 2.6168C9.24299 2.32913 8.83093 2.12707 8.36903 2.04316C7.90713 1.95926 7.42226 1.9984 6.99252 2.15427C6.56278 2.31015 6.21317 2.57369 6 2.90245" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 6V8.00088C6.00031 8.39636 6.10356 8.78287 6.29674 9.11159C6.48991 9.44031 6.76433 9.69649 7.08534 9.84779C7.40634 9.99909 7.75954 10.0387 8.10032 9.96165C8.4411 9.88459 8.75417 9.69431 9 9.41483" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 12V14" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.87 1.83637C13.0557 1.63204 13.0407 1.31581 12.8363 1.13006C12.632 0.944307 12.3158 0.959365 12.13 1.16369L10.4589 3.00199C10.2216 1.58215 8.98719 0.5 7.5 0.5C5.84315 0.5 4.5 1.84315 4.5 3.5L4.5 7.5C4.5 8.0754 4.66199 8.61297 4.94286 9.06958L4.24966 9.8321C4.1363 9.6744 4.03412 9.5081 3.94428 9.33433C3.86104 9.17337 3.78845 9.00607 3.72742 8.83338C3.63539 8.57302 3.34973 8.43656 3.08937 8.52858C2.82901 8.6206 2.69255 8.90626 2.78458 9.16662C2.86101 9.38288 2.95188 9.59228 3.056 9.79364C3.20094 10.074 3.37167 10.3388 3.56506 10.5852L2.13003 12.1637C1.94428 12.368 1.95933 12.6842 2.16366 12.87C2.36799 13.0558 2.68422 13.0407 2.86997 12.8364L4.25951 11.3079C5.01297 11.9497 5.95951 12.372 7.00014 12.4753L7.00014 14L5.50014 14C5.22399 14 5.00014 14.2239 5.00014 14.5C5.00014 14.7761 5.22399 15 5.50014 15L7.50014 15L9.50014 15C9.77628 15 10.0001 14.7761 10.0001 14.5C10.0001 14.2239 9.77628 14 9.50014 14L8.00014 14L8.00014 12.4753C9.72168 12.3043 11.1857 11.26 11.9439 9.79364C12.048 9.59228 12.1389 9.38288 12.2153 9.16662C12.3073 8.90626 12.1709 8.6206 11.9105 8.52858C11.6501 8.43656 11.3645 8.57302 11.2725 8.83338C11.2114 9.00607 11.1388 9.17337 11.0556 9.33433C10.3899 10.6218 9.04706 11.5 7.49994 11.5C6.523 11.5 5.62751 11.1498 4.93254 10.5675L5.60604 9.82669C6.12251 10.2476 6.78178 10.5 7.5 10.5C9.15685 10.5 10.5 9.15685 10.5 7.5L10.5 4.44333L12.87 1.83637ZM9.5 4.05673L9.5 3.5C9.5 2.39543 8.60457 1.5 7.5 1.5C6.39543 1.5 5.5 2.39543 5.5 3.5L5.5 7.5C5.5 7.77755 5.55653 8.04189 5.65872 8.28214L9.5 4.05673ZM6.28022 9.08509L9.5 5.54333L9.5 7.5C9.5 8.60457 8.60457 9.5 7.5 9.5C7.04083 9.5 6.6178 9.34527 6.28022 9.08509Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

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="M14 4H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.66667 8H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.66667 12H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.00016 12C8.41993 12.5597 9.00515 12.9731 9.67294 13.1817C10.3407 13.3903 11.0572 13.3835 11.7209 13.1623C12.3846 12.941 12.9619 12.5166 13.371 11.949C13.78 11.3815 14.0002 10.6996 14.0002 10C14.0002 9.20435 13.6841 8.44129 13.1215 7.87868C12.5589 7.31607 11.7958 7 11.0002 7C10.1135 7 9.30683 7.36 8.72683 7.94L7.3335 9.33333" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.3335 6.66669V9.33335H10.0002" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 964 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="M7.33333 8H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.6667 5H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 11H2" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 7V11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 9H10" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 620 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="M6.31254 12.549C7.3841 13.0987 8.61676 13.2476 9.78839 12.9688C10.96 12.6901 11.9936 12.0021 12.7028 11.0287C13.412 10.0554 13.7503 8.8607 13.6566 7.66002C13.5629 6.45934 13.0435 5.33159 12.1919 4.48C11.3403 3.62841 10.2126 3.10898 9.01188 3.01531C7.8112 2.92164 6.61655 3.2599 5.64319 3.96912C4.66984 4.67834 3.9818 5.71188 3.70306 6.88351C3.42432 8.05514 3.5732 9.2878 4.12289 10.3594L3 13.6719L6.31254 12.549Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 609 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

@@ -1,5 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.8 3H3.2C2.53726 3 2 3.51167 2 4.14286V9.85714C2 10.4883 2.53726 11 3.2 11H12.8C13.4627 11 14 10.4883 14 9.85714V4.14286C14 3.51167 13.4627 3 12.8 3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.33325 14H10.6666" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 11.3333V14" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M1 3.25C1 3.11193 1.11193 3 1.25 3H13.75C13.8881 3 14 3.11193 14 3.25V10.75C14 10.8881 13.8881 11 13.75 11H1.25C1.11193 11 1 10.8881 1 10.75V3.25ZM1.25 2C0.559643 2 0 2.55964 0 3.25V10.75C0 11.4404 0.559644 12 1.25 12H5.07341L4.82991 13.2986C4.76645 13.6371 5.02612 13.95 5.37049 13.95H9.62951C9.97389 13.95 10.2336 13.6371 10.1701 13.2986L9.92659 12H13.75C14.4404 12 15 11.4404 15 10.75V3.25C15 2.55964 14.4404 2 13.75 2H1.25ZM9.01091 12H5.98909L5.79222 13.05H9.20778L9.01091 12Z"
fill="currentColor"
/>
</svg>

Before

Width:  |  Height:  |  Size: 569 B

After

Width:  |  Height:  |  Size: 677 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.0001 8.62505C13.0001 11.75 10.8126 13.3125 8.21266 14.2187C8.07651 14.2648 7.92862 14.2626 7.79392 14.2125C5.18771 13.3125 3.00024 11.75 3.00024 8.62505V4.25012C3.00024 4.08436 3.06609 3.92539 3.1833 3.80818C3.30051 3.69098 3.45948 3.62513 3.62523 3.62513C4.87521 3.62513 6.43769 2.87514 7.52517 1.92516C7.65758 1.81203 7.82601 1.74988 8.00016 1.74988C8.17431 1.74988 8.34275 1.81203 8.47515 1.92516C9.56889 2.88139 11.1251 3.62513 12.3751 3.62513C12.5408 3.62513 12.6998 3.69098 12.817 3.80818C12.9342 3.92539 13.0001 4.08436 13.0001 4.25012V8.62505Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 8.00002L7.33333 9.33335L10 6.66669" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 883 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,4 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 8L7.33333 9L10 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 418 B

View File

@@ -1,10 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 3C7.66045 3 8.33955 3 9 3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 4C11.3949 4.26602 11.7345 4.60558 12 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 7C13 7.66045 13 8.33955 13 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 11C11.734 11.3949 11.3944 11.7345 11 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 13C8.33954 13 7.66046 13 7 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 12C4.6051 11.734 4.26554 11.3944 4 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 9C3 8.33955 3 7.66045 3 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 5C4.26602 4.6051 4.60558 4.26554 5 4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,11 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 3C7.66045 3 8.33955 3 9 3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11 4C11.3949 4.26602 11.7345 4.60558 12 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 7C13 7.66045 13 8.33955 13 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 11C11.734 11.3949 11.3944 11.7345 11 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 13C8.33954 13 7.66046 13 7 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 12C4.6051 11.734 4.26554 11.3944 4 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 9C3 8.33955 3 7.66045 3 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 5C4.26602 4.6051 4.60558 4.26554 5 4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.00016 8.66665C8.36835 8.66665 8.66683 8.36817 8.66683 7.99998C8.66683 7.63179 8.36835 7.33331 8.00016 7.33331C7.63197 7.33331 7.3335 7.63179 7.3335 7.99998C7.3335 8.36817 7.63197 8.66665 8.00016 8.66665Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

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">
<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="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"/>
<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"/>
</svg>

Before

Width:  |  Height:  |  Size: 999 B

After

Width:  |  Height:  |  Size: 947 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">
<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="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>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 998 B

View File

@@ -232,7 +232,7 @@
"ctrl-n": "agent::NewThread",
"ctrl-alt-n": "agent::NewTextThread",
"ctrl-shift-h": "agent::OpenHistory",
"ctrl-alt-c": "agent::OpenSettings",
"ctrl-alt-c": "agent::OpenConfiguration",
"ctrl-alt-p": "agent::OpenRulesLibrary",
"ctrl-i": "agent::ToggleProfileSelector",
"ctrl-alt-/": "agent::ToggleModelSelector",
@@ -269,15 +269,15 @@
}
},
{
"context": "AgentPanel && external_agent_thread",
"context": "AgentPanel && acp_thread",
"use_key_equivalents": true,
"bindings": {
"ctrl-n": "agent::NewExternalAgentThread",
"ctrl-n": "agent::NewAcpThread",
"ctrl-alt-t": "agent::NewThread"
}
},
{
"context": "MessageEditor && !Picker > Editor && !use_modifier_to_send",
"context": "MessageEditor > Editor",
"bindings": {
"enter": "agent::Chat",
"ctrl-enter": "agent::ChatWithFollow",
@@ -287,17 +287,6 @@
"ctrl-shift-n": "agent::RejectAll"
}
},
{
"context": "MessageEditor && !Picker > Editor && use_modifier_to_send",
"bindings": {
"ctrl-enter": "agent::Chat",
"enter": "editor::Newline",
"ctrl-i": "agent::ToggleProfileSelector",
"shift-ctrl-r": "agent::OpenAgentDiff",
"ctrl-shift-y": "agent::KeepAll",
"ctrl-shift-n": "agent::RejectAll"
}
},
{
"context": "EditMessageEditor > Editor",
"bindings": {
@@ -430,7 +419,7 @@
"ctrl-shift-pagedown": "pane::SwapItemRight",
"ctrl-f4": ["pane::CloseActiveItem", { "close_pinned": false }],
"ctrl-w": ["pane::CloseActiveItem", { "close_pinned": false }],
"alt-ctrl-t": ["pane::CloseOtherItems", { "close_pinned": false }],
"alt-ctrl-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"alt-ctrl-shift-w": "workspace::CloseInactiveTabsAndPanes",
"ctrl-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
"ctrl-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
@@ -483,10 +472,11 @@
"ctrl-k ctrl-d": ["editor::SelectNext", { "replace_newest": true }], // editor.action.moveSelectionToNextFindMatch / find_under_expand_skip
"ctrl-k ctrl-shift-d": ["editor::SelectPrevious", { "replace_newest": true }], // editor.action.moveSelectionToPreviousFindMatch
"ctrl-k ctrl-i": "editor::Hover",
"ctrl-k ctrl-b": "editor::BlameHover",
"ctrl-/": ["editor::ToggleComments", { "advance_downwards": false }],
"f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
"shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
"ctrl-u": "editor::UndoSelection",
"ctrl-shift-u": "editor::RedoSelection",
"f8": "editor::GoToDiagnostic",
"shift-f8": "editor::GoToPreviousDiagnostic",
"f2": "editor::Rename",
"f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit",
@@ -495,7 +485,7 @@
"shift-f12": "editor::GoToImplementation",
"alt-ctrl-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"ctrl-m": "editor::MoveToEnclosingBracket", // from jetbrains
"ctrl-m": "editor::MoveToEnclosingBracket",
"ctrl-|": "editor::MoveToEnclosingBracket",
"ctrl-{": "editor::Fold",
"ctrl-}": "editor::UnfoldLines",
@@ -596,9 +586,8 @@
"ctrl-shift-f": "pane::DeploySearch",
"ctrl-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
"ctrl-shift-t": "pane::ReopenClosedItem",
"ctrl-k ctrl-s": "zed::OpenKeymapEditor",
"ctrl-k ctrl-s": "zed::OpenKeymap",
"ctrl-k ctrl-t": "theme_selector::Toggle",
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
"ctrl-t": "project_symbols::Toggle",
"ctrl-p": "file_finder::Toggle",
"ctrl-tab": "tab_switcher::Toggle",
@@ -663,8 +652,6 @@
{
"context": "Editor",
"bindings": {
"ctrl-u": "editor::UndoSelection",
"ctrl-shift-u": "editor::RedoSelection",
"ctrl-shift-j": "editor::JoinLines",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
@@ -869,10 +856,11 @@
"alt-shift-y": "git::UnstageFile",
"ctrl-alt-y": "git::ToggleStaged",
"space": "git::ToggleStaged",
"shift-space": "git::StageRange",
"tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus",
"ctrl-enter": "git::Commit",
"ctrl-shift-enter": "git::Amend",
"alt-enter": "menu::SecondaryConfirm",
"delete": ["git::RestoreFile", { "skip_prompt": false }],
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
@@ -909,9 +897,7 @@
"ctrl-g backspace": "git::RestoreTrackedFiles",
"ctrl-g shift-backspace": "git::TrashUntrackedFiles",
"ctrl-space": "git::StageAll",
"ctrl-shift-space": "git::UnstageAll",
"ctrl-enter": "git::Commit",
"ctrl-shift-enter": "git::Amend"
"ctrl-shift-space": "git::UnstageAll"
}
},
{
@@ -930,7 +916,7 @@
}
},
{
"context": "CommitEditor > Editor",
"context": "GitPanel > Editor",
"bindings": {
"escape": "git_panel::FocusChanges",
"tab": "git_panel::FocusChanges",
@@ -976,14 +962,9 @@
"context": "CollabPanel && not_editing",
"bindings": {
"ctrl-backspace": "collab_panel::Remove",
"space": "menu::Confirm"
}
},
{
"context": "CollabPanel",
"bindings": {
"alt-up": "collab_panel::MoveChannelUp",
"alt-down": "collab_panel::MoveChannelDown"
"space": "menu::Confirm",
"ctrl-up": "collab_panel::MoveChannelUp",
"ctrl-down": "collab_panel::MoveChannelDown"
}
},
{
@@ -1017,7 +998,6 @@
{
"context": "FileFinder || (FileFinder > Picker > Editor)",
"bindings": {
"ctrl-p": "file_finder::Toggle",
"ctrl-shift-a": "file_finder::ToggleSplitMenu",
"ctrl-shift-i": "file_finder::ToggleFilterMenu"
}
@@ -1136,37 +1116,7 @@
"ctrl-f": "search::FocusSearch",
"alt-find": "keymap_editor::ToggleKeystrokeSearch",
"alt-ctrl-f": "keymap_editor::ToggleKeystrokeSearch",
"alt-c": "keymap_editor::ToggleConflictFilter",
"enter": "keymap_editor::EditBinding",
"alt-enter": "keymap_editor::CreateBinding",
"ctrl-c": "keymap_editor::CopyAction",
"ctrl-shift-c": "keymap_editor::CopyContext",
"ctrl-t": "keymap_editor::ShowMatchingKeybinds"
}
},
{
"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"
"alt-c": "keymap_editor::ToggleConflictFilter"
}
}
]

View File

@@ -272,7 +272,7 @@
"cmd-n": "agent::NewThread",
"cmd-alt-n": "agent::NewTextThread",
"cmd-shift-h": "agent::OpenHistory",
"cmd-alt-c": "agent::OpenSettings",
"cmd-alt-c": "agent::OpenConfiguration",
"cmd-alt-p": "agent::OpenRulesLibrary",
"cmd-i": "agent::ToggleProfileSelector",
"cmd-alt-/": "agent::ToggleModelSelector",
@@ -310,15 +310,15 @@
}
},
{
"context": "AgentPanel && external_agent_thread",
"context": "AgentPanel && acp_thread",
"use_key_equivalents": true,
"bindings": {
"cmd-n": "agent::NewExternalAgentThread",
"cmd-n": "agent::NewAcpThread",
"cmd-alt-t": "agent::NewThread"
}
},
{
"context": "MessageEditor && !Picker > Editor && !use_modifier_to_send",
"context": "MessageEditor > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "agent::Chat",
@@ -329,18 +329,6 @@
"cmd-shift-n": "agent::RejectAll"
}
},
{
"context": "MessageEditor && !Picker > Editor && use_modifier_to_send",
"use_key_equivalents": true,
"bindings": {
"cmd-enter": "agent::Chat",
"enter": "editor::Newline",
"cmd-i": "agent::ToggleProfileSelector",
"shift-ctrl-r": "agent::OpenAgentDiff",
"cmd-shift-y": "agent::KeepAll",
"cmd-shift-n": "agent::RejectAll"
}
},
{
"context": "EditMessageEditor > Editor",
"use_key_equivalents": true,
@@ -489,7 +477,7 @@
"ctrl-shift-pageup": "pane::SwapItemLeft",
"ctrl-shift-pagedown": "pane::SwapItemRight",
"cmd-w": ["pane::CloseActiveItem", { "close_pinned": false }],
"alt-cmd-t": ["pane::CloseOtherItems", { "close_pinned": false }],
"alt-cmd-t": ["pane::CloseInactiveItems", { "close_pinned": false }],
"ctrl-alt-cmd-w": "workspace::CloseInactiveTabsAndPanes",
"cmd-k e": ["pane::CloseItemsToTheLeft", { "close_pinned": false }],
"cmd-k t": ["pane::CloseItemsToTheRight", { "close_pinned": false }],
@@ -537,10 +525,11 @@
"ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": false }], // editor.action.addSelectionToPreviousFindMatch
"cmd-k ctrl-cmd-d": ["editor::SelectPrevious", { "replace_newest": true }], // editor.action.moveSelectionToPreviousFindMatch
"cmd-k cmd-i": "editor::Hover",
"cmd-k cmd-b": "editor::BlameHover",
"cmd-/": ["editor::ToggleComments", { "advance_downwards": false }],
"f8": ["editor::GoToDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
"shift-f8": ["editor::GoToPreviousDiagnostic", { "severity": { "min": "hint", "max": "error" } }],
"cmd-u": "editor::UndoSelection",
"cmd-shift-u": "editor::RedoSelection",
"f8": "editor::GoToDiagnostic",
"shift-f8": "editor::GoToPreviousDiagnostic",
"f2": "editor::Rename",
"f12": "editor::GoToDefinition",
"alt-f12": "editor::GoToDefinitionSplit",
@@ -549,7 +538,7 @@
"alt-cmd-f12": "editor::GoToTypeDefinitionSplit",
"alt-shift-f12": "editor::FindAllReferences",
"cmd-|": "editor::MoveToEnclosingBracket",
"ctrl-m": "editor::MoveToEnclosingBracket", // From Jetbrains
"ctrl-m": "editor::MoveToEnclosingBracket",
"alt-cmd-[": "editor::Fold",
"alt-cmd-]": "editor::UnfoldLines",
"cmd-k cmd-l": "editor::ToggleFold",
@@ -663,9 +652,8 @@
"cmd-shift-f": "pane::DeploySearch",
"cmd-shift-h": ["pane::DeploySearch", { "replace_enabled": true }],
"cmd-shift-t": "pane::ReopenClosedItem",
"cmd-k cmd-s": "zed::OpenKeymapEditor",
"cmd-k cmd-s": "zed::OpenKeymap",
"cmd-k cmd-t": "theme_selector::Toggle",
"ctrl-alt-cmd-p": "settings_profile_selector::Toggle",
"cmd-t": "project_symbols::Toggle",
"cmd-p": "file_finder::Toggle",
"ctrl-tab": "tab_switcher::Toggle",
@@ -726,8 +714,6 @@
"context": "Editor",
"use_key_equivalents": true,
"bindings": {
"cmd-u": "editor::UndoSelection",
"cmd-shift-u": "editor::RedoSelection",
"ctrl-j": "editor::JoinLines",
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
"ctrl-alt-h": "editor::DeleteToPreviousSubwordStart",
@@ -944,13 +930,14 @@
"enter": "menu::Confirm",
"cmd-alt-y": "git::ToggleStaged",
"space": "git::ToggleStaged",
"shift-space": "git::StageRange",
"cmd-y": "git::StageFile",
"cmd-shift-y": "git::UnstageFile",
"alt-down": "git_panel::FocusEditor",
"tab": "git_panel::FocusEditor",
"shift-tab": "git_panel::FocusEditor",
"escape": "git_panel::ToggleFocus",
"cmd-enter": "git::Commit",
"cmd-shift-enter": "git::Amend",
"backspace": ["git::RestoreFile", { "skip_prompt": false }],
"delete": ["git::RestoreFile", { "skip_prompt": false }],
"cmd-backspace": ["git::RestoreFile", { "skip_prompt": true }],
@@ -975,7 +962,7 @@
}
},
{
"context": "CommitEditor > Editor",
"context": "GitPanel > Editor",
"use_key_equivalents": true,
"bindings": {
"enter": "editor::Newline",
@@ -1000,9 +987,7 @@
"ctrl-g backspace": "git::RestoreTrackedFiles",
"ctrl-g shift-backspace": "git::TrashUntrackedFiles",
"cmd-ctrl-y": "git::StageAll",
"cmd-ctrl-shift-y": "git::UnstageAll",
"cmd-enter": "git::Commit",
"cmd-shift-enter": "git::Amend"
"cmd-ctrl-shift-y": "git::UnstageAll"
}
},
{
@@ -1038,15 +1023,9 @@
"use_key_equivalents": true,
"bindings": {
"ctrl-backspace": "collab_panel::Remove",
"space": "menu::Confirm"
}
},
{
"context": "CollabPanel",
"use_key_equivalents": true,
"bindings": {
"alt-up": "collab_panel::MoveChannelUp",
"alt-down": "collab_panel::MoveChannelDown"
"space": "menu::Confirm",
"cmd-up": "collab_panel::MoveChannelUp",
"cmd-down": "collab_panel::MoveChannelDown"
}
},
{
@@ -1118,16 +1097,13 @@
"ctrl-cmd-space": "terminal::ShowCharacterPalette",
"cmd-c": "terminal::Copy",
"cmd-v": "terminal::Paste",
"cmd-f": "buffer_search::Deploy",
"cmd-a": "editor::SelectAll",
"cmd-k": "terminal::Clear",
"cmd-n": "workspace::NewTerminal",
"ctrl-enter": "assistant::InlineAssist",
"ctrl-_": null, // emacs undo
// Some nice conveniences
"cmd-backspace": ["terminal::SendText", "\u0015"], // ctrl-u: clear line
"alt-delete": ["terminal::SendText", "\u001bd"], // alt-d: delete word forward
"cmd-delete": ["terminal::SendText", "\u000b"], // ctrl-k: delete to end of line
"cmd-backspace": ["terminal::SendText", "\u0015"],
"cmd-right": ["terminal::SendText", "\u0005"],
"cmd-left": ["terminal::SendText", "\u0001"],
// Terminal.app compatibility
@@ -1236,39 +1212,8 @@
"context": "KeymapEditor",
"use_key_equivalents": true,
"bindings": {
"cmd-f": "search::FocusSearch",
"cmd-alt-f": "keymap_editor::ToggleKeystrokeSearch",
"cmd-alt-c": "keymap_editor::ToggleConflictFilter",
"enter": "keymap_editor::EditBinding",
"alt-enter": "keymap_editor::CreateBinding",
"cmd-c": "keymap_editor::CopyAction",
"cmd-shift-c": "keymap_editor::CopyContext",
"cmd-t": "keymap_editor::ShowMatchingKeybinds"
}
},
{
"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"
"cmd-alt-c": "keymap_editor::ToggleConflictFilter"
}
}
]

View File

@@ -13,9 +13,9 @@
}
},
{
"context": "Editor && vim_mode == insert",
"context": "Editor && vim_mode == insert && !menu",
"bindings": {
// "j k": "vim::NormalBefore"
// "j k": "vim::SwitchToNormalMode"
}
}
]

View File

@@ -8,7 +8,7 @@
"ctrl-shift-i": "agent::ToggleFocus",
"ctrl-l": "agent::ToggleFocus",
"ctrl-shift-l": "agent::ToggleFocus",
"ctrl-shift-j": "agent::OpenSettings"
"ctrl-shift-j": "agent::OpenConfiguration"
}
},
{

View File

@@ -114,7 +114,7 @@
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 1": "pane::CloseInactiveItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file

View File

@@ -4,7 +4,6 @@
"ctrl-alt-s": "zed::OpenSettings",
"ctrl-{": "pane::ActivatePreviousItem",
"ctrl-}": "pane::ActivateNextItem",
"shift-escape": null, // Unmap workspace::zoom
"ctrl-f2": "debugger::Stop",
"f6": "debugger::Pause",
"f7": "debugger::StepInto",
@@ -45,8 +44,8 @@
"ctrl-alt-right": "pane::GoForward",
"alt-f7": "editor::FindAllReferences",
"ctrl-alt-f7": "editor::FindAllReferences",
"ctrl-b": "editor::GoToDefinition", // Conflicts with workspace::ToggleLeftDock
"ctrl-alt-b": "editor::GoToDefinitionSplit", // Conflicts with workspace::ToggleRightDock
// "ctrl-b": "editor::GoToDefinition", // Conflicts with workspace::ToggleLeftDock
// "ctrl-alt-b": "editor::GoToDefinitionSplit", // Conflicts with workspace::ToggleLeftDock
"ctrl-shift-b": "editor::GoToTypeDefinition",
"ctrl-alt-shift-b": "editor::GoToTypeDefinitionSplit",
"f2": "editor::GoToDiagnostic",
@@ -67,66 +66,22 @@
"context": "Editor && mode == full",
"bindings": {
"ctrl-f12": "outline::Toggle",
"ctrl-r": ["buffer_search::Deploy", { "replace_enabled": true }],
"alt-7": "outline::Toggle",
"ctrl-shift-n": "file_finder::Toggle",
"ctrl-g": "go_to_line::Toggle",
"alt-enter": "editor::ToggleCodeActions"
}
},
{
"context": "BufferSearchBar",
"bindings": {
"shift-enter": "search::SelectPreviousMatch"
}
},
{
"context": "BufferSearchBar || ProjectSearchBar",
"bindings": {
"alt-c": "search::ToggleCaseSensitive",
"alt-e": "search::ToggleSelection",
"alt-x": "search::ToggleRegex",
"alt-w": "search::ToggleWholeWord"
}
},
{
"context": "Workspace",
"bindings": {
"ctrl-shift-f12": "workspace::CloseAllDocks",
"ctrl-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
"alt-shift-f10": "task::Spawn",
"ctrl-e": "file_finder::Toggle",
// "ctrl-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
"ctrl-shift-n": "file_finder::Toggle",
"ctrl-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
"ctrl-alt-shift-n": "project_symbols::Toggle",
"alt-0": "git_panel::ToggleFocus",
"alt-1": "project_panel::ToggleFocus",
"alt-5": "debug_panel::ToggleFocus",
"alt-6": "diagnostics::Deploy",
"alt-7": "outline_panel::ToggleFocus"
}
},
{
"context": "Pane", // this is to override the default Pane mappings to switch tabs
"bindings": {
"alt-1": "project_panel::ToggleFocus",
"alt-2": null, // Bookmarks (left dock)
"alt-3": null, // Find Panel (bottom dock)
"alt-4": null, // Run Panel (bottom dock)
"alt-5": "debug_panel::ToggleFocus",
"alt-6": "diagnostics::Deploy",
"alt-7": "outline_panel::ToggleFocus",
"alt-8": null, // Services (bottom dock)
"alt-9": null, // Git History (bottom dock)
"alt-0": "git_panel::ToggleFocus"
}
},
{
"context": "Workspace || Editor",
"bindings": {
"alt-f12": "terminal_panel::ToggleFocus",
"ctrl-shift-k": "git::Push"
"alt-1": "workspace::ToggleLeftDock",
"ctrl-e": "tab_switcher::Toggle",
"alt-6": "diagnostics::Deploy"
}
},
{
@@ -140,36 +95,10 @@
"context": "ProjectPanel",
"bindings": {
"enter": "project_panel::Open",
"ctrl-shift-f": "project_panel::NewSearchInDirectory",
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"shift-f6": "project_panel::Rename"
}
},
{
"context": "Terminal",
"bindings": {
"ctrl-shift-t": "workspace::NewTerminal",
"alt-f12": "workspace::CloseActiveDock",
"alt-left": "pane::ActivatePreviousItem",
"alt-right": "pane::ActivateNextItem",
"ctrl-up": "terminal::ScrollLineUp",
"ctrl-down": "terminal::ScrollLineDown",
"shift-pageup": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown"
}
},
{ "context": "GitPanel", "bindings": { "alt-0": "workspace::CloseActiveDock" } },
{ "context": "ProjectPanel", "bindings": { "alt-1": "workspace::CloseActiveDock" } },
{ "context": "DebugPanel", "bindings": { "alt-5": "workspace::CloseActiveDock" } },
{ "context": "Diagnostics > Editor", "bindings": { "alt-6": "pane::CloseActiveItem" } },
{ "context": "OutlinePanel", "bindings": { "alt-7": "workspace::CloseActiveDock" } },
{
"context": "Dock || Workspace || Terminal || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
"bindings": {
"escape": "editor::ToggleFocus",
"shift-escape": "workspace::CloseActiveDock"
}
}
]

View File

@@ -8,7 +8,7 @@
"cmd-shift-i": "agent::ToggleFocus",
"cmd-l": "agent::ToggleFocus",
"cmd-shift-l": "agent::ToggleFocus",
"cmd-shift-j": "agent::OpenSettings"
"cmd-shift-j": "agent::OpenConfiguration"
}
},
{

View File

@@ -114,7 +114,7 @@
"ctrl-x o": "workspace::ActivateNextPane", // other-window
"ctrl-x k": "pane::CloseActiveItem", // kill-buffer
"ctrl-x 0": "pane::CloseActiveItem", // delete-window
"ctrl-x 1": "pane::CloseOtherItems", // delete-other-windows
"ctrl-x 1": "pane::CloseInactiveItems", // delete-other-windows
"ctrl-x 2": "pane::SplitDown", // split-window-below
"ctrl-x 3": "pane::SplitRight", // split-window-right
"ctrl-x ctrl-f": "file_finder::Toggle", // find-file

View File

@@ -3,8 +3,6 @@
"bindings": {
"cmd-{": "pane::ActivatePreviousItem",
"cmd-}": "pane::ActivateNextItem",
"cmd-0": "git_panel::ToggleFocus", // overrides `cmd-0` zoom reset
"shift-escape": null, // Unmap workspace::zoom
"ctrl-f2": "debugger::Stop",
"f6": "debugger::Pause",
"f7": "debugger::StepInto",
@@ -65,70 +63,28 @@
"context": "Editor && mode == full",
"bindings": {
"cmd-f12": "outline::Toggle",
"cmd-r": ["buffer_search::Deploy", { "replace_enabled": true }],
"cmd-7": "outline::Toggle",
"cmd-shift-o": "file_finder::Toggle",
"cmd-l": "go_to_line::Toggle",
"alt-enter": "editor::ToggleCodeActions"
}
},
{
"context": "BufferSearchBar",
"context": "BufferSearchBar > Editor",
"bindings": {
"shift-enter": "search::SelectPreviousMatch"
}
},
{
"context": "BufferSearchBar || ProjectSearchBar",
"bindings": {
"alt-c": "search::ToggleCaseSensitive",
"alt-e": "search::ToggleSelection",
"alt-x": "search::ToggleRegex",
"alt-w": "search::ToggleWholeWord",
"ctrl-alt-c": "search::ToggleCaseSensitive",
"ctrl-alt-e": "search::ToggleSelection",
"ctrl-alt-w": "search::ToggleWholeWord",
"ctrl-alt-x": "search::ToggleRegex"
}
},
{
"context": "Workspace",
"bindings": {
"cmd-shift-f12": "workspace::CloseAllDocks",
"cmd-shift-r": ["pane::DeploySearch", { "replace_enabled": true }],
"ctrl-alt-r": "task::Spawn",
"cmd-e": "file_finder::Toggle",
// "cmd-k": "git_panel::ToggleFocus", // bug: This should also focus commit editor
"cmd-shift-o": "file_finder::Toggle",
"cmd-shift-a": "command_palette::Toggle",
"shift shift": "command_palette::Toggle",
"cmd-alt-o": "project_symbols::Toggle", // JetBrains: Go to Symbol
"cmd-o": "project_symbols::Toggle", // JetBrains: Go to Class
"cmd-1": "project_panel::ToggleFocus",
"cmd-5": "debug_panel::ToggleFocus",
"cmd-6": "diagnostics::Deploy",
"cmd-7": "outline_panel::ToggleFocus"
}
},
{
"context": "Pane", // this is to override the default Pane mappings to switch tabs
"bindings": {
"cmd-1": "project_panel::ToggleFocus",
"cmd-2": null, // Bookmarks (left dock)
"cmd-3": null, // Find Panel (bottom dock)
"cmd-4": null, // Run Panel (bottom dock)
"cmd-5": "debug_panel::ToggleFocus",
"cmd-6": "diagnostics::Deploy",
"cmd-7": "outline_panel::ToggleFocus",
"cmd-8": null, // Services (bottom dock)
"cmd-9": null, // Git History (bottom dock)
"cmd-0": "git_panel::ToggleFocus"
}
},
{
"context": "Workspace || Editor",
"bindings": {
"alt-f12": "terminal_panel::ToggleFocus",
"cmd-shift-k": "git::Push"
"cmd-1": "workspace::ToggleLeftDock",
"cmd-6": "diagnostics::Deploy"
}
},
{
@@ -142,35 +98,11 @@
"context": "ProjectPanel",
"bindings": {
"enter": "project_panel::Open",
"cmd-shift-f": "project_panel::NewSearchInDirectory",
"cmd-backspace": ["project_panel::Trash", { "skip_prompt": false }],
"backspace": ["project_panel::Trash", { "skip_prompt": false }],
"delete": ["project_panel::Trash", { "skip_prompt": false }],
"shift-delete": ["project_panel::Delete", { "skip_prompt": false }],
"shift-f6": "project_panel::Rename"
}
},
{
"context": "Terminal",
"bindings": {
"cmd-t": "workspace::NewTerminal",
"alt-f12": "workspace::CloseActiveDock",
"cmd-up": "terminal::ScrollLineUp",
"cmd-down": "terminal::ScrollLineDown",
"shift-pageup": "terminal::ScrollPageUp",
"shift-pagedown": "terminal::ScrollPageDown"
}
},
{ "context": "GitPanel", "bindings": { "cmd-0": "workspace::CloseActiveDock" } },
{ "context": "ProjectPanel", "bindings": { "cmd-1": "workspace::CloseActiveDock" } },
{ "context": "DebugPanel", "bindings": { "cmd-5": "workspace::CloseActiveDock" } },
{ "context": "Diagnostics > Editor", "bindings": { "cmd-6": "pane::CloseActiveItem" } },
{ "context": "OutlinePanel", "bindings": { "cmd-7": "workspace::CloseActiveDock" } },
{
"context": "Dock || Workspace || Terminal || OutlinePanel || ProjectPanel || CollabPanel || (Editor && mode == auto_height)",
"bindings": {
"escape": "editor::ToggleFocus",
"shift-escape": "workspace::CloseActiveDock"
}
}
]

View File

@@ -6,7 +6,7 @@
}
},
{
"context": "Editor && mode == full",
"context": "Editor",
"bindings": {
"cmd-l": "go_to_line::Toggle",
"ctrl-shift-d": "editor::DuplicateLineDown",
@@ -15,12 +15,7 @@
"cmd-enter": "editor::NewlineBelow",
"cmd-alt-enter": "editor::NewlineAbove",
"cmd-shift-l": "editor::SelectLine",
"cmd-shift-t": "outline::Toggle"
}
},
{
"context": "Editor",
"bindings": {
"cmd-shift-t": "outline::Toggle",
"alt-backspace": "editor::DeleteToPreviousWordStart",
"alt-shift-backspace": "editor::DeleteToNextWordEnd",
"alt-delete": "editor::DeleteToNextWordEnd",
@@ -44,6 +39,10 @@
"ctrl-_": "editor::ConvertToSnakeCase"
}
},
{
"context": "Editor && mode == full",
"bindings": {}
},
{
"context": "BufferSearchBar",
"bindings": {

View File

@@ -124,7 +124,6 @@
"g r a": "editor::ToggleCodeActions",
"g g": "vim::StartOfDocument",
"g h": "editor::Hover",
"g B": "editor::BlameHover",
"g t": "pane::ActivateNextItem",
"g shift-t": "pane::ActivatePreviousItem",
"g d": "editor::GoToDefinition",
@@ -220,8 +219,6 @@
{
"context": "vim_mode == normal",
"bindings": {
"i": "vim::InsertBefore",
"a": "vim::InsertAfter",
"ctrl-[": "editor::Cancel",
":": "command_palette::Toggle",
"c": "vim::PushChange",
@@ -355,7 +352,9 @@
"shift-d": "vim::DeleteToEndOfLine",
"shift-j": "vim::JoinLines",
"shift-y": "vim::YankLine",
"i": "vim::InsertBefore",
"shift-i": "vim::InsertFirstNonWhitespace",
"a": "vim::InsertAfter",
"shift-a": "vim::InsertEndOfLine",
"o": "vim::InsertLineBelow",
"shift-o": "vim::InsertLineAbove",
@@ -377,10 +376,7 @@
{
"context": "vim_mode == helix_normal && !menu",
"bindings": {
"i": "vim::HelixInsert",
"a": "vim::HelixAppend",
"ctrl-[": "editor::Cancel",
";": "vim::HelixCollapseSelection",
":": "command_palette::Toggle",
"left": "vim::WrappingLeft",
"right": "vim::WrappingRight",
@@ -470,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": {
"ctrl-p": "editor::SignatureHelpPrevious",
"ctrl-n": "editor::SignatureHelpNext"
@@ -727,7 +723,7 @@
}
},
{
"context": "VimControl || !Editor && !Terminal",
"context": "AgentPanel || GitPanel || ProjectPanel || CollabPanel || OutlinePanel || ChatPanel || VimControl || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || DebugPanel",
"bindings": {
// window related commands (ctrl-w X)
"ctrl-w": null,
@@ -785,7 +781,7 @@
}
},
{
"context": "!Editor && !Terminal",
"context": "ChangesList || EmptyPane || SharedScreen || MarkdownPreview || KeyContextView || Welcome",
"bindings": {
":": "command_palette::Toggle",
"g /": "pane::DeploySearch"
@@ -845,7 +841,6 @@
"i": "git_panel::FocusEditor",
"x": "git::ToggleStaged",
"shift-x": "git::StageAll",
"g x": "git::StageRange",
"shift-u": "git::UnstageAll"
}
},
@@ -861,14 +856,6 @@
"shift-n": null
}
},
{
"context": "Picker > Editor",
"bindings": {
"ctrl-h": "editor::Backspace",
"ctrl-u": "editor::DeleteToBeginningOfLine",
"ctrl-w": "editor::DeleteToPreviousWordStart"
}
},
{
"context": "GitCommit > Editor && VimControl && vim_mode == normal",
"bindings": {

View File

@@ -84,7 +84,7 @@
"bottom_dock_layout": "contained",
// The direction that you want to split panes horizontally. Defaults to "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",
// Centered layout related settings.
"centered_layout": {
@@ -197,8 +197,6 @@
// "inline"
// 3. Place snippets at the bottom of the completion list:
// "bottom"
// 4. Do not show snippets in the completion list:
// "none"
"snippet_sort_order": "inline",
// How to highlight the current line in the editor.
//
@@ -691,10 +689,7 @@
// 5. Never show the scrollbar:
// "never"
"show": null
},
// Default depth to expand outline items in the current file.
// Set to 0 to collapse all items that have children, 1 or higher to collapse items at that depth or deeper.
"expand_outlines_with_depth": 100
}
},
"collaboration_panel": {
// Whether to show the collaboration panel button in the status bar.
@@ -822,7 +817,7 @@
"edit_file": true,
"fetch": true,
"list_directory": true,
"project_notifications": false,
"project_notifications": true,
"move_path": true,
"now": true,
"find_path": true,
@@ -842,7 +837,7 @@
"diagnostics": true,
"fetch": true,
"list_directory": true,
"project_notifications": false,
"project_notifications": true,
"now": true,
"find_path": true,
"read_file": true,
@@ -1079,10 +1074,6 @@
// Send anonymized usage data like what languages you're using Zed with.
"metrics": true
},
// Whether to disable all AI features in Zed.
//
// Default: false
"disable_ai": false,
// Automatically update Zed. This setting may be ignored on Linux if
// installed through a package manager.
"auto_update": true,
@@ -1144,7 +1135,6 @@
"**/.svn",
"**/.hg",
"**/.jj",
"**/.repo",
"**/CVS",
"**/.DS_Store",
"**/Thumbs.db",
@@ -1680,10 +1670,6 @@
"allowed": true
}
},
"SystemVerilog": {
"format_on_save": "off",
"use_on_type_format": false
},
"Vue.js": {
"language_servers": ["vue-language-server", "..."],
"prettier": {
@@ -1719,7 +1705,6 @@
"openai": {
"api_url": "https://api.openai.com/v1"
},
"openai_compatible": {},
"open_router": {
"api_url": "https://openrouter.ai/api/v1"
},
@@ -1877,25 +1862,5 @@
"save_breakpoints": true,
"dock": "bottom",
"button": true
},
// Configures any number of settings profiles that are temporarily applied on
// top of your existing user settings when selected from
// `settings profile selector: toggle`.
// Examples:
// "profiles": {
// "Presenting": {
// "agent_font_size": 20.0,
// "buffer_font_size": 20.0,
// "theme": "One Light",
// "ui_font_size": 20.0
// },
// "Python (ty)": {
// "languages": {
// "Python": {
// "language_servers": ["ty"]
// }
// }
// }
// }
"profiles": []
}
}

View File

@@ -15,15 +15,13 @@
"adapter": "JavaScript",
"program": "$ZED_FILE",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT",
"type": "pwa-node"
"cwd": "$ZED_WORKTREE_ROOT"
},
{
"label": "JavaScript debug terminal",
"adapter": "JavaScript",
"request": "launch",
"cwd": "$ZED_WORKTREE_ROOT",
"console": "integratedTerminal",
"type": "pwa-node"
"console": "integratedTerminal"
}
]

View File

@@ -8,7 +8,7 @@
// command palette (cmd-shift-p / ctrl-shift-p)
{
"ui_font_size": 16,
"buffer_font_size": 15,
"buffer_font_size": 16,
"theme": {
"mode": "system",
"light": "One Light",

View File

@@ -59,11 +59,5 @@ services:
depends_on:
- postgres
stripe-mock:
image: stripe/stripe-mock:v0.178.0
ports:
- 12111:12111
- 12112:12112
volumes:
postgres_data:

View File

@@ -1,5 +1,5 @@
[package]
name = "acp_thread"
name = "acp"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
@@ -9,14 +9,15 @@ license = "GPL-3.0-or-later"
workspace = true
[lib]
path = "src/acp_thread.rs"
path = "src/acp.rs"
doctest = false
[features]
test-support = ["gpui/test-support", "project/test-support"]
gemini = []
[dependencies]
agent-client-protocol.workspace = true
agent_servers.workspace = true
agentic-coding-protocol.workspace = true
anyhow.workspace = true
assistant_tool.workspace = true
@@ -28,8 +29,6 @@ itertools.workspace = true
language.workspace = true
markdown.workspace = true
project.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
ui.workspace = true
@@ -42,6 +41,7 @@ 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

1914
crates/acp/src/acp.rs Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +0,0 @@
use std::{path::Path, rc::Rc};
use agent_client_protocol as acp;
use anyhow::Result;
use gpui::{AsyncApp, Entity, Task};
use project::Project;
use ui::App;
use crate::AcpThread;
pub trait AgentConnection {
fn name(&self) -> &'static str;
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,
cwd: &Path,
cx: &mut AsyncApp,
) -> Task<Result<Entity<AcpThread>>>;
fn authenticate(&self, cx: &mut App) -> Task<Result<()>>;
fn prompt(&self, params: acp::PromptArguments, cx: &mut App) -> Task<Result<()>>;
fn cancel(&self, session_id: &acp::SessionId, cx: &mut App);
}

View File

@@ -1,453 +0,0 @@
// Translates old acp agents into the new schema
use agent_client_protocol as acp;
use agentic_coding_protocol::{self as acp_old, AgentRequest as _};
use anyhow::{Context as _, Result};
use futures::channel::oneshot;
use gpui::{AppContext as _, AsyncApp, Entity, Task, WeakEntity};
use project::Project;
use std::{cell::RefCell, error::Error, fmt, path::Path, rc::Rc};
use ui::App;
use util::ResultExt as _;
use crate::{AcpThread, AgentConnection};
#[derive(Clone)]
pub struct OldAcpClientDelegate {
thread: Rc<RefCell<WeakEntity<AcpThread>>>,
cx: AsyncApp,
next_tool_call_id: Rc<RefCell<u64>>,
// sent_buffer_versions: HashMap<Entity<Buffer>, HashMap<u64, BufferSnapshot>>,
}
impl OldAcpClientDelegate {
pub fn new(thread: Rc<RefCell<WeakEntity<AcpThread>>>, cx: AsyncApp) -> Self {
Self {
thread,
cx,
next_tool_call_id: Rc::new(RefCell::new(0)),
}
}
}
impl acp_old::Client for OldAcpClientDelegate {
async fn stream_assistant_message_chunk(
&self,
params: acp_old::StreamAssistantMessageChunkParams,
) -> Result<(), acp_old::Error> {
let cx = &mut self.cx.clone();
cx.update(|cx| {
self.thread
.borrow()
.update(cx, |thread, cx| match params.chunk {
acp_old::AssistantMessageChunk::Text { text } => {
thread.push_assistant_content_block(text.into(), false, cx)
}
acp_old::AssistantMessageChunk::Thought { thought } => {
thread.push_assistant_content_block(thought.into(), true, cx)
}
})
.log_err();
})?;
Ok(())
}
async fn request_tool_call_confirmation(
&self,
request: acp_old::RequestToolCallConfirmationParams,
) -> Result<acp_old::RequestToolCallConfirmationResponse, acp_old::Error> {
let cx = &mut self.cx.clone();
let old_acp_id = *self.next_tool_call_id.borrow() + 1;
self.next_tool_call_id.replace(old_acp_id);
let tool_call = into_new_tool_call(
acp::ToolCallId(old_acp_id.to_string().into()),
request.tool_call,
);
let mut options = match request.confirmation {
acp_old::ToolCallConfirmation::Edit { .. } => vec![(
acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
acp::PermissionOptionKind::AllowAlways,
"Always Allow Edits".to_string(),
)],
acp_old::ToolCallConfirmation::Execute { root_command, .. } => vec![(
acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
acp::PermissionOptionKind::AllowAlways,
format!("Always Allow {}", root_command),
)],
acp_old::ToolCallConfirmation::Mcp {
server_name,
tool_name,
..
} => vec![
(
acp_old::ToolCallConfirmationOutcome::AlwaysAllowMcpServer,
acp::PermissionOptionKind::AllowAlways,
format!("Always Allow {}", server_name),
),
(
acp_old::ToolCallConfirmationOutcome::AlwaysAllowTool,
acp::PermissionOptionKind::AllowAlways,
format!("Always Allow {}", tool_name),
),
],
acp_old::ToolCallConfirmation::Fetch { .. } => vec![(
acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
acp::PermissionOptionKind::AllowAlways,
"Always Allow".to_string(),
)],
acp_old::ToolCallConfirmation::Other { .. } => vec![(
acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
acp::PermissionOptionKind::AllowAlways,
"Always Allow".to_string(),
)],
};
options.extend([
(
acp_old::ToolCallConfirmationOutcome::Allow,
acp::PermissionOptionKind::AllowOnce,
"Allow".to_string(),
),
(
acp_old::ToolCallConfirmationOutcome::Reject,
acp::PermissionOptionKind::RejectOnce,
"Reject".to_string(),
),
]);
let mut outcomes = Vec::with_capacity(options.len());
let mut acp_options = Vec::with_capacity(options.len());
for (index, (outcome, kind, label)) in options.into_iter().enumerate() {
outcomes.push(outcome);
acp_options.push(acp::PermissionOption {
id: acp::PermissionOptionId(index.to_string().into()),
label,
kind,
})
}
let response = cx
.update(|cx| {
self.thread.borrow().update(cx, |thread, cx| {
thread.request_tool_call_permission(tool_call, acp_options, cx)
})
})?
.context("Failed to update thread")?
.await;
let outcome = match response {
Ok(option_id) => outcomes[option_id.0.parse::<usize>().unwrap_or(0)],
Err(oneshot::Canceled) => acp_old::ToolCallConfirmationOutcome::Cancel,
};
Ok(acp_old::RequestToolCallConfirmationResponse {
id: acp_old::ToolCallId(old_acp_id),
outcome: outcome,
})
}
async fn push_tool_call(
&self,
request: acp_old::PushToolCallParams,
) -> Result<acp_old::PushToolCallResponse, acp_old::Error> {
let cx = &mut self.cx.clone();
let old_acp_id = *self.next_tool_call_id.borrow() + 1;
self.next_tool_call_id.replace(old_acp_id);
cx.update(|cx| {
self.thread.borrow().update(cx, |thread, cx| {
thread.upsert_tool_call(
into_new_tool_call(acp::ToolCallId(old_acp_id.to_string().into()), request),
cx,
)
})
})?
.context("Failed to update thread")?;
Ok(acp_old::PushToolCallResponse {
id: acp_old::ToolCallId(old_acp_id),
})
}
async fn update_tool_call(
&self,
request: acp_old::UpdateToolCallParams,
) -> Result<(), acp_old::Error> {
let cx = &mut self.cx.clone();
cx.update(|cx| {
self.thread.borrow().update(cx, |thread, cx| {
thread.update_tool_call(
acp::ToolCallUpdate {
id: acp::ToolCallId(request.tool_call_id.0.to_string().into()),
fields: acp::ToolCallUpdateFields {
status: Some(into_new_tool_call_status(request.status)),
content: Some(
request
.content
.into_iter()
.map(into_new_tool_call_content)
.collect::<Vec<_>>(),
),
..Default::default()
},
},
cx,
)
})
})?
.context("Failed to update thread")??;
Ok(())
}
async fn update_plan(&self, request: acp_old::UpdatePlanParams) -> Result<(), acp_old::Error> {
let cx = &mut self.cx.clone();
cx.update(|cx| {
self.thread.borrow().update(cx, |thread, cx| {
thread.update_plan(
acp::Plan {
entries: request
.entries
.into_iter()
.map(into_new_plan_entry)
.collect(),
},
cx,
)
})
})?
.context("Failed to update thread")?;
Ok(())
}
async fn read_text_file(
&self,
acp_old::ReadTextFileParams { path, line, limit }: acp_old::ReadTextFileParams,
) -> Result<acp_old::ReadTextFileResponse, acp_old::Error> {
let content = self
.cx
.update(|cx| {
self.thread.borrow().update(cx, |thread, cx| {
thread.read_text_file(path, line, limit, false, cx)
})
})?
.context("Failed to update thread")?
.await?;
Ok(acp_old::ReadTextFileResponse { content })
}
async fn write_text_file(
&self,
acp_old::WriteTextFileParams { path, content }: acp_old::WriteTextFileParams,
) -> Result<(), acp_old::Error> {
self.cx
.update(|cx| {
self.thread
.borrow()
.update(cx, |thread, cx| thread.write_text_file(path, content, cx))
})?
.context("Failed to update thread")?
.await?;
Ok(())
}
}
fn into_new_tool_call(id: acp::ToolCallId, request: acp_old::PushToolCallParams) -> acp::ToolCall {
acp::ToolCall {
id: id,
label: request.label,
kind: acp_kind_from_old_icon(request.icon),
status: acp::ToolCallStatus::InProgress,
content: request
.content
.into_iter()
.map(into_new_tool_call_content)
.collect(),
locations: request
.locations
.into_iter()
.map(into_new_tool_call_location)
.collect(),
raw_input: None,
}
}
fn acp_kind_from_old_icon(icon: acp_old::Icon) -> acp::ToolKind {
match icon {
acp_old::Icon::FileSearch => acp::ToolKind::Search,
acp_old::Icon::Folder => acp::ToolKind::Search,
acp_old::Icon::Globe => acp::ToolKind::Search,
acp_old::Icon::Hammer => acp::ToolKind::Other,
acp_old::Icon::LightBulb => acp::ToolKind::Think,
acp_old::Icon::Pencil => acp::ToolKind::Edit,
acp_old::Icon::Regex => acp::ToolKind::Search,
acp_old::Icon::Terminal => acp::ToolKind::Execute,
}
}
fn into_new_tool_call_status(status: acp_old::ToolCallStatus) -> acp::ToolCallStatus {
match status {
acp_old::ToolCallStatus::Running => acp::ToolCallStatus::InProgress,
acp_old::ToolCallStatus::Finished => acp::ToolCallStatus::Completed,
acp_old::ToolCallStatus::Error => acp::ToolCallStatus::Failed,
}
}
fn into_new_tool_call_content(content: acp_old::ToolCallContent) -> acp::ToolCallContent {
match content {
acp_old::ToolCallContent::Markdown { markdown } => markdown.into(),
acp_old::ToolCallContent::Diff { diff } => acp::ToolCallContent::Diff {
diff: into_new_diff(diff),
},
}
}
fn into_new_diff(diff: acp_old::Diff) -> acp::Diff {
acp::Diff {
path: diff.path,
old_text: diff.old_text,
new_text: diff.new_text,
}
}
fn into_new_tool_call_location(location: acp_old::ToolCallLocation) -> acp::ToolCallLocation {
acp::ToolCallLocation {
path: location.path,
line: location.line,
}
}
fn into_new_plan_entry(entry: acp_old::PlanEntry) -> acp::PlanEntry {
acp::PlanEntry {
content: entry.content,
priority: into_new_plan_priority(entry.priority),
status: into_new_plan_status(entry.status),
}
}
fn into_new_plan_priority(priority: acp_old::PlanEntryPriority) -> acp::PlanEntryPriority {
match priority {
acp_old::PlanEntryPriority::Low => acp::PlanEntryPriority::Low,
acp_old::PlanEntryPriority::Medium => acp::PlanEntryPriority::Medium,
acp_old::PlanEntryPriority::High => acp::PlanEntryPriority::High,
}
}
fn into_new_plan_status(status: acp_old::PlanEntryStatus) -> acp::PlanEntryStatus {
match status {
acp_old::PlanEntryStatus::Pending => acp::PlanEntryStatus::Pending,
acp_old::PlanEntryStatus::InProgress => acp::PlanEntryStatus::InProgress,
acp_old::PlanEntryStatus::Completed => acp::PlanEntryStatus::Completed,
}
}
#[derive(Debug)]
pub struct Unauthenticated;
impl Error for Unauthenticated {}
impl fmt::Display for Unauthenticated {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Unauthenticated")
}
}
pub struct OldAcpAgentConnection {
pub name: &'static str,
pub connection: acp_old::AgentConnection,
pub child_status: Task<Result<()>>,
pub current_thread: Rc<RefCell<WeakEntity<AcpThread>>>,
}
impl AgentConnection for OldAcpAgentConnection {
fn name(&self) -> &'static str {
self.name
}
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,
_cwd: &Path,
cx: &mut AsyncApp,
) -> Task<Result<Entity<AcpThread>>> {
let task = self.connection.request_any(
acp_old::InitializeParams {
protocol_version: acp_old::ProtocolVersion::latest(),
}
.into_any(),
);
let current_thread = self.current_thread.clone();
cx.spawn(async move |cx| {
let result = task.await?;
let result = acp_old::InitializeParams::response_from_any(result)?;
if !result.is_authenticated {
anyhow::bail!(Unauthenticated)
}
cx.update(|cx| {
let thread = cx.new(|cx| {
let session_id = acp::SessionId("acp-old-no-id".into());
AcpThread::new(self.clone(), project, session_id, cx)
});
current_thread.replace(thread.downgrade());
thread
})
})
}
fn authenticate(&self, cx: &mut App) -> Task<Result<()>> {
let task = self
.connection
.request_any(acp_old::AuthenticateParams.into_any());
cx.foreground_executor().spawn(async move {
task.await?;
Ok(())
})
}
fn prompt(&self, params: acp::PromptArguments, cx: &mut App) -> Task<Result<()>> {
let chunks = params
.prompt
.into_iter()
.filter_map(|block| match block {
acp::ContentBlock::Text(text) => {
Some(acp_old::UserMessageChunk::Text { text: text.text })
}
acp::ContentBlock::ResourceLink(link) => Some(acp_old::UserMessageChunk::Path {
path: link.uri.into(),
}),
_ => None,
})
.collect();
let task = self
.connection
.request_any(acp_old::SendUserMessageParams { chunks }.into_any());
cx.foreground_executor().spawn(async move {
task.await?;
anyhow::Ok(())
})
}
fn cancel(&self, _session_id: &acp::SessionId, cx: &mut App) {
let task = self
.connection
.request_any(acp_old::CancelSendMessageParams.into_any());
cx.foreground_executor()
.spawn(async move {
task.await?;
anyhow::Ok(())
})
.detach_and_log_err(cx)
}
}

View File

@@ -231,6 +231,7 @@ impl ActivityIndicator {
status,
} => {
let create_buffer = project.update(cx, |project, cx| project.create_buffer(cx));
let project = project.clone();
let status = status.clone();
let server_name = server_name.clone();
cx.spawn_in(window, async move |workspace, cx| {
@@ -246,7 +247,8 @@ impl ActivityIndicator {
workspace.update_in(cx, |workspace, window, cx| {
workspace.add_item_to_active_pane(
Box::new(cx.new(|cx| {
let mut editor = Editor::for_buffer(buffer, None, window, cx);
let mut editor =
Editor::for_buffer(buffer, Some(project.clone()), window, cx);
editor.set_read_only(true);
editor
})),
@@ -446,7 +448,7 @@ impl ActivityIndicator {
.into_any_element(),
),
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,
});
}

View File

@@ -25,7 +25,6 @@ assistant_context.workspace = true
assistant_tool.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
component.workspace = true
context_server.workspace = true
@@ -36,9 +35,9 @@ futures.workspace = true
git.workspace = true
gpui.workspace = true
heed.workspace = true
http_client.workspace = true
icons.workspace = true
indoc.workspace = true
http_client.workspace = true
itertools.workspace = true
language.workspace = true
language_model.workspace = true
@@ -47,6 +46,7 @@ paths.workspace = true
postage.workspace = true
project.workspace = true
prompt_store.workspace = true
proto.workspace = true
ref-cast.workspace = true
rope.workspace = true
schemars.workspace = true
@@ -63,6 +63,7 @@ time.workspace = true
util.workspace = true
uuid.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true
zstd.workspace = true
[dev-dependencies]

View File

@@ -308,12 +308,7 @@ mod tests {
unimplemented!()
}
fn needs_confirmation(
&self,
_input: &serde_json::Value,
_project: &Entity<Project>,
_cx: &App,
) -> bool {
fn needs_confirmation(&self, _input: &serde_json::Value, _cx: &App) -> bool {
unimplemented!()
}

View File

@@ -38,7 +38,7 @@ impl Tool for ContextServerTool {
}
fn icon(&self) -> IconName {
IconName::ToolHammer
IconName::Cog
}
fn source(&self) -> ToolSource {
@@ -47,7 +47,7 @@ impl Tool for ContextServerTool {
}
}
fn needs_confirmation(&self, _: &serde_json::Value, _: &Entity<Project>, _: &App) -> bool {
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
true
}

File diff suppressed because it is too large Load Diff

View File

@@ -41,9 +41,6 @@ use std::{
};
use util::ResultExt as _;
pub static ZED_STATELESS: std::sync::LazyLock<bool> =
std::sync::LazyLock::new(|| std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty()));
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DataType {
#[serde(rename = "json")]
@@ -877,11 +874,7 @@ impl ThreadsDatabase {
let needs_migration_from_heed = mdb_path.exists();
let connection = if *ZED_STATELESS {
Connection::open_memory(Some("THREAD_FALLBACK_DB"))
} else {
Connection::open_file(&sqlite_path.to_string_lossy())
};
let connection = Connection::open_file(&sqlite_path.to_string_lossy());
connection.exec(indoc! {"
CREATE TABLE IF NOT EXISTS threads (

View File

@@ -165,12 +165,7 @@ impl ToolUseState {
self.pending_tool_uses_by_id.values().collect()
}
pub fn tool_uses_for_message(
&self,
id: MessageId,
project: &Entity<Project>,
cx: &App,
) -> Vec<ToolUse> {
pub fn tool_uses_for_message(&self, id: MessageId, cx: &App) -> Vec<ToolUse> {
let Some(tool_uses_for_message) = &self.tool_uses_by_assistant_message.get(&id) else {
return Vec::new();
};
@@ -216,10 +211,7 @@ impl ToolUseState {
let (icon, needs_confirmation) =
if let Some(tool) = self.tools.read(cx).tool(&tool_use.name, cx) {
(
tool.icon(),
tool.needs_confirmation(&tool_use.input, project, cx),
)
(tool.icon(), tool.needs_confirmation(&tool_use.input, cx))
} else {
(IconName::Cog, false)
};

View File

@@ -5,10 +5,6 @@ edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[features]
test-support = ["acp_thread/test-support", "gpui/test-support", "project/test-support"]
e2e = []
[lints]
workspace = true
@@ -17,41 +13,15 @@ path = "src/agent_servers.rs"
doctest = false
[dependencies]
acp_thread.workspace = true
agent-client-protocol.workspace = true
agentic-coding-protocol.workspace = true
anyhow.workspace = true
collections.workspace = true
context_server.workspace = true
futures.workspace = true
gpui.workspace = true
itertools.workspace = true
log.workspace = true
paths.workspace = true
project.workspace = true
rand.workspace = true
schemars.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smol.workspace = true
strum.workspace = true
tempfile.workspace = true
ui.workspace = true
util.workspace = true
uuid.workspace = true
watch.workspace = true
indoc.workspace = true
which.workspace = true
workspace-hack.workspace = true
[target.'cfg(unix)'.dependencies]
libc.workspace = true
nix.workspace = true
[dev-dependencies]
env_logger.workspace = true
language.workspace = true
indoc.workspace = true
acp_thread = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }

View File

@@ -1,82 +1,30 @@
mod claude;
mod codex;
mod gemini;
mod mcp_server;
mod settings;
use std::{
path::{Path, PathBuf},
sync::Arc,
};
#[cfg(test)]
mod e2e_tests;
pub use claude::*;
pub use codex::*;
pub use gemini::*;
pub use settings::*;
use acp_thread::AgentConnection;
use anyhow::Result;
use anyhow::{Context as _, Result};
use collections::HashMap;
use gpui::{App, AsyncApp, Entity, SharedString, Task};
use gpui::{App, AsyncApp, Entity, SharedString};
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
};
use util::ResultExt as _;
use settings::{Settings, SettingsSources, SettingsStore};
use util::{ResultExt, paths};
pub fn init(cx: &mut App) {
settings::init(cx);
AllAgentServersSettings::register(cx);
}
pub trait AgentServer: Send {
fn logo(&self) -> ui::IconName;
fn name(&self) -> &'static str;
fn empty_state_headline(&self) -> &'static str;
fn empty_state_message(&self) -> &'static str;
fn connect(
&self,
// these will go away when old_acp is fully removed
root_dir: &Path,
project: &Entity<Project>,
cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>>;
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug)]
pub struct AllAgentServersSettings {
gemini: Option<AgentServerSettings>,
}
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()
}
}
pub enum AgentServerVersion {
Supported,
Unsupported {
error_message: SharedString,
upgrade_message: SharedString,
upgrade_command: String,
},
#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug)]
pub struct AgentServerSettings {
#[serde(flatten)]
command: AgentServerCommand,
}
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)]
@@ -88,34 +36,105 @@ pub struct AgentServerCommand {
pub env: Option<HashMap<String, String>>,
}
impl AgentServerCommand {
pub(crate) async fn resolve(
path_bin_name: &'static str,
extra_args: &[&'static str],
settings: Option<AgentServerSettings>,
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,
) -> Option<Self> {
if let Some(agent_settings) = settings {
return Some(Self {
path: agent_settings.command.path,
args: agent_settings
.command
.args
.into_iter()
.chain(extra_args.iter().map(|arg| arg.to_string()))
.collect(),
env: agent_settings.command.env,
});
} else {
find_bin_in_path(path_bin_name, project, cx)
.await
.map(|path| Self {
path,
args: extra_args.iter().map(|arg| arg.to_string()).collect(),
env: None,
) -> impl Future<Output = Result<AgentServerCommand>>;
fn version(
&self,
command: &AgentServerCommand,
) -> impl Future<Output = Result<AgentServerVersion>> + Send;
}
const GEMINI_ACP_ARG: &str = "--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,
})
}
}
@@ -165,3 +184,48 @@ async fn find_bin_in_path(
})
.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

@@ -1,839 +0,0 @@
mod mcp_server;
pub mod tools;
use collections::HashMap;
use context_server::listener::McpServerTool;
use project::Project;
use settings::SettingsStore;
use smol::process::Child;
use std::cell::RefCell;
use std::fmt::Display;
use std::path::Path;
use std::rc::Rc;
use uuid::Uuid;
use agent_client_protocol as acp;
use anyhow::{Result, anyhow};
use futures::channel::oneshot;
use futures::{AsyncBufReadExt, AsyncWriteExt};
use futures::{
AsyncRead, AsyncWrite, FutureExt, StreamExt,
channel::mpsc::{self, UnboundedReceiver, UnboundedSender},
io::BufReader,
select_biased,
};
use gpui::{App, AppContext, AsyncApp, Entity, Task, WeakEntity};
use serde::{Deserialize, Serialize};
use util::ResultExt;
use crate::claude::mcp_server::{ClaudeZedMcpServer, McpConfig};
use crate::claude::tools::ClaudeTool;
use crate::{AgentServer, AgentServerCommand, AllAgentServersSettings};
use acp_thread::{AcpThread, AgentConnection};
#[derive(Clone)]
pub struct ClaudeCode;
impl AgentServer for ClaudeCode {
fn name(&self) -> &'static str {
"Claude Code"
}
fn empty_state_headline(&self) -> &'static str {
self.name()
}
fn empty_state_message(&self) -> &'static str {
"How can I help you today?"
}
fn logo(&self) -> ui::IconName {
ui::IconName::AiClaude
}
fn connect(
&self,
_root_dir: &Path,
_project: &Entity<Project>,
_cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>> {
let connection = ClaudeAgentConnection {
sessions: Default::default(),
};
Task::ready(Ok(Rc::new(connection) as _))
}
}
struct ClaudeAgentConnection {
sessions: Rc<RefCell<HashMap<acp::SessionId, ClaudeAgentSession>>>,
}
impl AgentConnection for ClaudeAgentConnection {
fn name(&self) -> &'static str {
ClaudeCode.name()
}
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,
cwd: &Path,
cx: &mut AsyncApp,
) -> Task<Result<Entity<AcpThread>>> {
let cwd = cwd.to_owned();
cx.spawn(async move |cx| {
let (mut thread_tx, thread_rx) = watch::channel(WeakEntity::new_invalid());
let permission_mcp_server = ClaudeZedMcpServer::new(thread_rx.clone(), cx).await?;
let mut mcp_servers = HashMap::default();
mcp_servers.insert(
mcp_server::SERVER_NAME.to_string(),
permission_mcp_server.server_config()?,
);
let mcp_config = McpConfig { mcp_servers };
let mcp_config_file = tempfile::NamedTempFile::new()?;
let (mcp_config_file, mcp_config_path) = mcp_config_file.into_parts();
let mut mcp_config_file = smol::fs::File::from(mcp_config_file);
mcp_config_file
.write_all(serde_json::to_string(&mcp_config)?.as_bytes())
.await?;
mcp_config_file.flush().await?;
let settings = cx.read_global(|settings: &SettingsStore, _| {
settings.get::<AllAgentServersSettings>(None).claude.clone()
})?;
let Some(command) =
AgentServerCommand::resolve("claude", &[], settings, &project, cx).await
else {
anyhow::bail!("Failed to find claude binary");
};
let (incoming_message_tx, mut incoming_message_rx) = mpsc::unbounded();
let (outgoing_tx, outgoing_rx) = mpsc::unbounded();
let session_id = acp::SessionId(Uuid::new_v4().to_string().into());
log::trace!("Starting session with id: {}", session_id);
cx.background_spawn({
let session_id = session_id.clone();
async move {
let mut outgoing_rx = Some(outgoing_rx);
let mut child = spawn_claude(
&command,
ClaudeSessionMode::Start,
session_id.clone(),
&mcp_config_path,
&cwd,
)
.await?;
let pid = child.id();
log::trace!("Spawned (pid: {})", pid);
ClaudeAgentSession::handle_io(
outgoing_rx.take().unwrap(),
incoming_message_tx.clone(),
child.stdin.take().unwrap(),
child.stdout.take().unwrap(),
)
.await?;
log::trace!("Stopped (pid: {})", pid);
drop(mcp_config_path);
anyhow::Ok(())
}
})
.detach();
let end_turn_tx = Rc::new(RefCell::new(None));
let handler_task = cx.spawn({
let end_turn_tx = end_turn_tx.clone();
let thread_rx = thread_rx.clone();
async move |cx| {
while let Some(message) = incoming_message_rx.next().await {
ClaudeAgentSession::handle_message(
thread_rx.clone(),
message,
end_turn_tx.clone(),
cx,
)
.await
}
}
});
let thread =
cx.new(|cx| AcpThread::new(self.clone(), project, session_id.clone(), cx))?;
thread_tx.send(thread.downgrade())?;
let session = ClaudeAgentSession {
outgoing_tx,
end_turn_tx,
_handler_task: handler_task,
_mcp_server: Some(permission_mcp_server),
};
self.sessions.borrow_mut().insert(session_id, session);
Ok(thread)
})
}
fn authenticate(&self, _cx: &mut App) -> Task<Result<()>> {
Task::ready(Err(anyhow!("Authentication not supported")))
}
fn prompt(&self, params: acp::PromptArguments, cx: &mut App) -> Task<Result<()>> {
let sessions = self.sessions.borrow();
let Some(session) = sessions.get(&params.session_id) else {
return Task::ready(Err(anyhow!(
"Attempted to send message to nonexistent session {}",
params.session_id
)));
};
let (tx, rx) = oneshot::channel();
session.end_turn_tx.borrow_mut().replace(tx);
let mut content = String::new();
for chunk in params.prompt {
match chunk {
acp::ContentBlock::Text(text_content) => {
content.push_str(&text_content.text);
}
acp::ContentBlock::ResourceLink(resource_link) => {
content.push_str(&format!("@{}", resource_link.uri));
}
acp::ContentBlock::Audio(_)
| acp::ContentBlock::Image(_)
| acp::ContentBlock::Resource(_) => {
// TODO
}
}
}
if let Err(err) = session.outgoing_tx.unbounded_send(SdkMessage::User {
message: Message {
role: Role::User,
content: Content::UntaggedText(content),
id: None,
model: None,
stop_reason: None,
stop_sequence: None,
usage: None,
},
session_id: Some(params.session_id.to_string()),
}) {
return Task::ready(Err(anyhow!(err)));
}
cx.foreground_executor().spawn(async move {
rx.await??;
Ok(())
})
}
fn cancel(&self, session_id: &acp::SessionId, _cx: &mut App) {
let sessions = self.sessions.borrow();
let Some(session) = sessions.get(&session_id) else {
log::warn!("Attempted to cancel nonexistent session {}", session_id);
return;
};
session
.outgoing_tx
.unbounded_send(SdkMessage::new_interrupt_message())
.log_err();
}
}
#[derive(Clone, Copy)]
enum ClaudeSessionMode {
Start,
#[expect(dead_code)]
Resume,
}
async fn spawn_claude(
command: &AgentServerCommand,
mode: ClaudeSessionMode,
session_id: acp::SessionId,
mcp_config_path: &Path,
root_dir: &Path,
) -> Result<Child> {
let child = util::command::new_smol_command(&command.path)
.args([
"--input-format",
"stream-json",
"--output-format",
"stream-json",
"--print",
"--verbose",
"--mcp-config",
mcp_config_path.to_string_lossy().as_ref(),
"--permission-prompt-tool",
&format!(
"mcp__{}__{}",
mcp_server::SERVER_NAME,
mcp_server::PermissionTool::NAME,
),
"--allowedTools",
&format!(
"mcp__{}__{},mcp__{}__{}",
mcp_server::SERVER_NAME,
mcp_server::EditTool::NAME,
mcp_server::SERVER_NAME,
mcp_server::ReadTool::NAME
),
"--disallowedTools",
"Read,Edit",
])
.args(match mode {
ClaudeSessionMode::Start => ["--session-id".to_string(), session_id.to_string()],
ClaudeSessionMode::Resume => ["--resume".to_string(), session_id.to_string()],
})
.args(command.args.iter().map(|arg| arg.as_str()))
.current_dir(root_dir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::inherit())
.kill_on_drop(true)
.spawn()?;
Ok(child)
}
struct ClaudeAgentSession {
outgoing_tx: UnboundedSender<SdkMessage>,
end_turn_tx: Rc<RefCell<Option<oneshot::Sender<Result<()>>>>>,
_mcp_server: Option<ClaudeZedMcpServer>,
_handler_task: Task<()>,
}
impl ClaudeAgentSession {
async fn handle_message(
mut thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
message: SdkMessage,
end_turn_tx: Rc<RefCell<Option<oneshot::Sender<Result<()>>>>>,
cx: &mut AsyncApp,
) {
match message {
// we should only be sending these out, they don't need to be in the thread
SdkMessage::ControlRequest { .. } => {}
SdkMessage::Assistant {
message,
session_id: _,
}
| SdkMessage::User {
message,
session_id: _,
} => {
let Some(thread) = thread_rx
.recv()
.await
.log_err()
.and_then(|entity| entity.upgrade())
else {
log::error!("Received an SDK message but thread is gone");
return;
};
for chunk in message.content.chunks() {
match chunk {
ContentChunk::Text { text } | ContentChunk::UntaggedText(text) => {
thread
.update(cx, |thread, cx| {
thread.push_assistant_content_block(text.into(), false, cx)
})
.log_err();
}
ContentChunk::ToolUse { id, name, input } => {
let claude_tool = ClaudeTool::infer(&name, input);
thread
.update(cx, |thread, cx| {
if let ClaudeTool::TodoWrite(Some(params)) = claude_tool {
thread.update_plan(
acp::Plan {
entries: params
.todos
.into_iter()
.map(Into::into)
.collect(),
},
cx,
)
} else {
thread.upsert_tool_call(
claude_tool.as_acp(acp::ToolCallId(id.into())),
cx,
);
}
})
.log_err();
}
ContentChunk::ToolResult {
content,
tool_use_id,
} => {
let content = content.to_string();
thread
.update(cx, |thread, cx| {
thread.update_tool_call(
acp::ToolCallUpdate {
id: acp::ToolCallId(tool_use_id.into()),
fields: acp::ToolCallUpdateFields {
status: Some(acp::ToolCallStatus::Completed),
content: (!content.is_empty())
.then(|| vec![content.into()]),
..Default::default()
},
},
cx,
)
})
.log_err();
}
ContentChunk::Image
| ContentChunk::Document
| ContentChunk::Thinking
| ContentChunk::RedactedThinking
| ContentChunk::WebSearchToolResult => {
thread
.update(cx, |thread, cx| {
thread.push_assistant_content_block(
format!("Unsupported content: {:?}", chunk).into(),
false,
cx,
)
})
.log_err();
}
}
}
}
SdkMessage::Result {
is_error,
subtype,
result,
..
} => {
if let Some(end_turn_tx) = end_turn_tx.borrow_mut().take() {
if is_error {
end_turn_tx
.send(Err(anyhow!(
"Error: {}",
result.unwrap_or_else(|| subtype.to_string())
)))
.ok();
} else {
end_turn_tx.send(Ok(())).ok();
}
}
}
SdkMessage::System { .. } | SdkMessage::ControlResponse { .. } => {}
}
}
async fn handle_io(
mut outgoing_rx: UnboundedReceiver<SdkMessage>,
incoming_tx: UnboundedSender<SdkMessage>,
mut outgoing_bytes: impl Unpin + AsyncWrite,
incoming_bytes: impl Unpin + AsyncRead,
) -> Result<UnboundedReceiver<SdkMessage>> {
let mut output_reader = BufReader::new(incoming_bytes);
let mut outgoing_line = Vec::new();
let mut incoming_line = String::new();
loop {
select_biased! {
message = outgoing_rx.next() => {
if let Some(message) = message {
outgoing_line.clear();
serde_json::to_writer(&mut outgoing_line, &message)?;
log::trace!("send: {}", String::from_utf8_lossy(&outgoing_line));
outgoing_line.push(b'\n');
outgoing_bytes.write_all(&outgoing_line).await.ok();
} else {
break;
}
}
bytes_read = output_reader.read_line(&mut incoming_line).fuse() => {
if bytes_read? == 0 {
break
}
log::trace!("recv: {}", &incoming_line);
match serde_json::from_str::<SdkMessage>(&incoming_line) {
Ok(message) => {
incoming_tx.unbounded_send(message).log_err();
}
Err(error) => {
log::error!("failed to parse incoming message: {error}. Raw: {incoming_line}");
}
}
incoming_line.clear();
}
}
}
Ok(outgoing_rx)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Message {
role: Role,
content: Content,
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
stop_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
stop_sequence: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
usage: Option<Usage>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
enum Content {
UntaggedText(String),
Chunks(Vec<ContentChunk>),
}
impl Content {
pub fn chunks(self) -> impl Iterator<Item = ContentChunk> {
match self {
Self::Chunks(chunks) => chunks.into_iter(),
Self::UntaggedText(text) => vec![ContentChunk::Text { text: text.clone() }].into_iter(),
}
}
}
impl Display for Content {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Content::UntaggedText(txt) => write!(f, "{}", txt),
Content::Chunks(chunks) => {
for chunk in chunks {
write!(f, "{}", chunk)?;
}
Ok(())
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum ContentChunk {
Text {
text: String,
},
ToolUse {
id: String,
name: String,
input: serde_json::Value,
},
ToolResult {
content: Content,
tool_use_id: String,
},
// TODO
Image,
Document,
Thinking,
RedactedThinking,
WebSearchToolResult,
#[serde(untagged)]
UntaggedText(String),
}
impl Display for ContentChunk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ContentChunk::Text { text } => write!(f, "{}", text),
ContentChunk::UntaggedText(text) => write!(f, "{}", text),
ContentChunk::ToolResult { content, .. } => write!(f, "{}", content),
ContentChunk::Image
| ContentChunk::Document
| ContentChunk::Thinking
| ContentChunk::RedactedThinking
| ContentChunk::ToolUse { .. }
| ContentChunk::WebSearchToolResult => {
write!(f, "\n{:?}\n", &self)
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Usage {
input_tokens: u32,
cache_creation_input_tokens: u32,
cache_read_input_tokens: u32,
output_tokens: u32,
service_tier: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum Role {
System,
Assistant,
User,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct MessageParam {
role: Role,
content: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum SdkMessage {
// An assistant message
Assistant {
message: Message, // from Anthropic SDK
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
},
// A user message
User {
message: Message, // from Anthropic SDK
#[serde(skip_serializing_if = "Option::is_none")]
session_id: Option<String>,
},
// Emitted as the last message in a conversation
Result {
subtype: ResultErrorType,
duration_ms: f64,
duration_api_ms: f64,
is_error: bool,
num_turns: i32,
#[serde(skip_serializing_if = "Option::is_none")]
result: Option<String>,
session_id: String,
total_cost_usd: f64,
},
// Emitted as the first message at the start of a conversation
System {
cwd: String,
session_id: String,
tools: Vec<String>,
model: String,
mcp_servers: Vec<McpServer>,
#[serde(rename = "apiKeySource")]
api_key_source: String,
#[serde(rename = "permissionMode")]
permission_mode: PermissionMode,
},
/// Messages used to control the conversation, outside of chat messages to the model
ControlRequest {
request_id: String,
request: ControlRequest,
},
/// Response to a control request
ControlResponse { response: ControlResponse },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "subtype", rename_all = "snake_case")]
enum ControlRequest {
/// Cancel the current conversation
Interrupt,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ControlResponse {
request_id: String,
subtype: ResultErrorType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum ResultErrorType {
Success,
ErrorMaxTurns,
ErrorDuringExecution,
}
impl Display for ResultErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResultErrorType::Success => write!(f, "success"),
ResultErrorType::ErrorMaxTurns => write!(f, "error_max_turns"),
ResultErrorType::ErrorDuringExecution => write!(f, "error_during_execution"),
}
}
}
impl SdkMessage {
fn new_interrupt_message() -> Self {
use rand::Rng;
// In the Claude Code TS SDK they just generate a random 12 character string,
// `Math.random().toString(36).substring(2, 15)`
let request_id = rand::thread_rng()
.sample_iter(&rand::distributions::Alphanumeric)
.take(12)
.map(char::from)
.collect();
Self::ControlRequest {
request_id,
request: ControlRequest::Interrupt,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct McpServer {
name: String,
status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum PermissionMode {
Default,
AcceptEdits,
BypassPermissions,
Plan,
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use serde_json::json;
crate::common_e2e_tests!(ClaudeCode, allow_option_id = "allow");
pub fn local_command() -> AgentServerCommand {
AgentServerCommand {
path: "claude".into(),
args: vec![],
env: None,
}
}
#[test]
fn test_deserialize_content_untagged_text() {
let json = json!("Hello, world!");
let content: Content = serde_json::from_value(json).unwrap();
match content {
Content::UntaggedText(text) => assert_eq!(text, "Hello, world!"),
_ => panic!("Expected UntaggedText variant"),
}
}
#[test]
fn test_deserialize_content_chunks() {
let json = json!([
{
"type": "text",
"text": "Hello"
},
{
"type": "tool_use",
"id": "tool_123",
"name": "calculator",
"input": {"operation": "add", "a": 1, "b": 2}
}
]);
let content: Content = serde_json::from_value(json).unwrap();
match content {
Content::Chunks(chunks) => {
assert_eq!(chunks.len(), 2);
match &chunks[0] {
ContentChunk::Text { text } => assert_eq!(text, "Hello"),
_ => panic!("Expected Text chunk"),
}
match &chunks[1] {
ContentChunk::ToolUse { id, name, input } => {
assert_eq!(id, "tool_123");
assert_eq!(name, "calculator");
assert_eq!(input["operation"], "add");
assert_eq!(input["a"], 1);
assert_eq!(input["b"], 2);
}
_ => panic!("Expected ToolUse chunk"),
}
}
_ => panic!("Expected Chunks variant"),
}
}
#[test]
fn test_deserialize_tool_result_untagged_text() {
let json = json!({
"type": "tool_result",
"content": "Result content",
"tool_use_id": "tool_456"
});
let chunk: ContentChunk = serde_json::from_value(json).unwrap();
match chunk {
ContentChunk::ToolResult {
content,
tool_use_id,
} => {
match content {
Content::UntaggedText(text) => assert_eq!(text, "Result content"),
_ => panic!("Expected UntaggedText content"),
}
assert_eq!(tool_use_id, "tool_456");
}
_ => panic!("Expected ToolResult variant"),
}
}
#[test]
fn test_deserialize_tool_result_chunks() {
let json = json!({
"type": "tool_result",
"content": [
{
"type": "text",
"text": "Processing complete"
},
{
"type": "text",
"text": "Result: 42"
}
],
"tool_use_id": "tool_789"
});
let chunk: ContentChunk = serde_json::from_value(json).unwrap();
match chunk {
ContentChunk::ToolResult {
content,
tool_use_id,
} => {
match content {
Content::Chunks(chunks) => {
assert_eq!(chunks.len(), 2);
match &chunks[0] {
ContentChunk::Text { text } => assert_eq!(text, "Processing complete"),
_ => panic!("Expected Text chunk"),
}
match &chunks[1] {
ContentChunk::Text { text } => assert_eq!(text, "Result: 42"),
_ => panic!("Expected Text chunk"),
}
}
_ => panic!("Expected Chunks content"),
}
assert_eq!(tool_use_id, "tool_789");
}
_ => panic!("Expected ToolResult variant"),
}
}
}

View File

@@ -1,302 +0,0 @@
use std::path::PathBuf;
use crate::claude::tools::{ClaudeTool, EditToolParams, ReadToolParams};
use acp_thread::AcpThread;
use agent_client_protocol as acp;
use anyhow::{Context, Result};
use collections::HashMap;
use context_server::listener::{McpServerTool, ToolResponse};
use context_server::types::{
Implementation, InitializeParams, InitializeResponse, ProtocolVersion, ServerCapabilities,
ToolAnnotations, ToolResponseContent, ToolsCapabilities, requests,
};
use gpui::{App, AsyncApp, Task, WeakEntity};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub struct ClaudeZedMcpServer {
server: context_server::listener::McpServer,
}
pub const SERVER_NAME: &str = "zed";
impl ClaudeZedMcpServer {
pub async fn new(
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
cx: &AsyncApp,
) -> Result<Self> {
let mut mcp_server = context_server::listener::McpServer::new(cx).await?;
mcp_server.handle_request::<requests::Initialize>(Self::handle_initialize);
mcp_server.add_tool(PermissionTool {
thread_rx: thread_rx.clone(),
});
mcp_server.add_tool(ReadTool {
thread_rx: thread_rx.clone(),
});
mcp_server.add_tool(EditTool {
thread_rx: thread_rx.clone(),
});
Ok(Self { server: mcp_server })
}
pub fn server_config(&self) -> Result<McpServerConfig> {
#[cfg(not(test))]
let zed_path = std::env::current_exe()
.context("finding current executable path for use in mcp_server")?;
#[cfg(test)]
let zed_path = crate::e2e_tests::get_zed_path();
Ok(McpServerConfig {
command: zed_path,
args: vec![
"--nc".into(),
self.server.socket_path().display().to_string(),
],
env: None,
})
}
fn handle_initialize(_: InitializeParams, cx: &App) -> Task<Result<InitializeResponse>> {
cx.foreground_executor().spawn(async move {
Ok(InitializeResponse {
protocol_version: ProtocolVersion("2025-06-18".into()),
capabilities: ServerCapabilities {
experimental: None,
logging: None,
completions: None,
prompts: None,
resources: None,
tools: Some(ToolsCapabilities {
list_changed: Some(false),
}),
},
server_info: Implementation {
name: SERVER_NAME.into(),
version: "0.1.0".into(),
},
meta: None,
})
})
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct McpConfig {
pub mcp_servers: HashMap<String, McpServerConfig>,
}
#[derive(Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct McpServerConfig {
pub command: PathBuf,
pub args: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub env: Option<HashMap<String, String>>,
}
// Tools
#[derive(Clone)]
pub struct PermissionTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct PermissionToolParams {
tool_name: String,
input: serde_json::Value,
tool_use_id: Option<String>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PermissionToolResponse {
behavior: PermissionToolBehavior,
updated_input: serde_json::Value,
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
enum PermissionToolBehavior {
Allow,
Deny,
}
impl McpServerTool for PermissionTool {
type Input = PermissionToolParams;
type Output = ();
const NAME: &'static str = "Confirmation";
fn description(&self) -> &'static str {
"Request permission for tool calls"
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
let claude_tool = ClaudeTool::infer(&input.tool_name, input.input.clone());
let tool_call_id = acp::ToolCallId(input.tool_use_id.context("Tool ID required")?.into());
let allow_option_id = acp::PermissionOptionId("allow".into());
let reject_option_id = acp::PermissionOptionId("reject".into());
let chosen_option = thread
.update(cx, |thread, cx| {
thread.request_tool_call_permission(
claude_tool.as_acp(tool_call_id),
vec![
acp::PermissionOption {
id: allow_option_id.clone(),
label: "Allow".into(),
kind: acp::PermissionOptionKind::AllowOnce,
},
acp::PermissionOption {
id: reject_option_id.clone(),
label: "Reject".into(),
kind: acp::PermissionOptionKind::RejectOnce,
},
],
cx,
)
})?
.await?;
let response = if chosen_option == allow_option_id {
PermissionToolResponse {
behavior: PermissionToolBehavior::Allow,
updated_input: input.input,
}
} else {
debug_assert_eq!(chosen_option, reject_option_id);
PermissionToolResponse {
behavior: PermissionToolBehavior::Deny,
updated_input: input.input,
}
};
Ok(ToolResponse {
content: vec![ToolResponseContent::Text {
text: serde_json::to_string(&response)?,
}],
structured_content: (),
})
}
}
#[derive(Clone)]
pub struct ReadTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
impl McpServerTool for ReadTool {
type Input = ReadToolParams;
type Output = ();
const NAME: &'static str = "Read";
fn description(&self) -> &'static str {
"Read the contents of a file. In sessions with mcp__zed__Read always use it instead of Read as it contains the most up-to-date contents."
}
fn annotations(&self) -> ToolAnnotations {
ToolAnnotations {
title: Some("Read file".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
open_world_hint: Some(false),
idempotent_hint: None,
}
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(input.abs_path, input.offset, input.limit, false, cx)
})?
.await?;
Ok(ToolResponse {
content: vec![ToolResponseContent::Text { text: content }],
structured_content: (),
})
}
}
#[derive(Clone)]
pub struct EditTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
impl McpServerTool for EditTool {
type Input = EditToolParams;
type Output = ();
const NAME: &'static str = "Edit";
fn description(&self) -> &'static str {
"Edits a file. In sessions with mcp__zed__Edit always use it instead of Edit as it will show the diff to the user better."
}
fn annotations(&self) -> ToolAnnotations {
ToolAnnotations {
title: Some("Edit file".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(false),
open_world_hint: Some(false),
idempotent_hint: Some(false),
}
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(input.abs_path.clone(), None, None, true, cx)
})?
.await?;
let new_content = content.replace(&input.old_text, &input.new_text);
if new_content == content {
return Err(anyhow::anyhow!("The old_text was not found in the content"));
}
thread
.update(cx, |thread, cx| {
thread.write_text_file(input.abs_path, new_content, cx)
})?
.await?;
Ok(ToolResponse {
content: vec![],
structured_content: (),
})
}
}

View File

@@ -1,675 +0,0 @@
use std::path::PathBuf;
use agent_client_protocol as acp;
use itertools::Itertools;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use util::ResultExt;
pub enum ClaudeTool {
Task(Option<TaskToolParams>),
NotebookRead(Option<NotebookReadToolParams>),
NotebookEdit(Option<NotebookEditToolParams>),
Edit(Option<EditToolParams>),
MultiEdit(Option<MultiEditToolParams>),
ReadFile(Option<ReadToolParams>),
Write(Option<WriteToolParams>),
Ls(Option<LsToolParams>),
Glob(Option<GlobToolParams>),
Grep(Option<GrepToolParams>),
Terminal(Option<BashToolParams>),
WebFetch(Option<WebFetchToolParams>),
WebSearch(Option<WebSearchToolParams>),
TodoWrite(Option<TodoWriteToolParams>),
ExitPlanMode(Option<ExitPlanModeToolParams>),
Other {
name: String,
input: serde_json::Value,
},
}
impl ClaudeTool {
pub fn infer(tool_name: &str, input: serde_json::Value) -> Self {
match tool_name {
// Known tools
"mcp__zed__Read" => Self::ReadFile(serde_json::from_value(input).log_err()),
"mcp__zed__Edit" => Self::Edit(serde_json::from_value(input).log_err()),
"MultiEdit" => Self::MultiEdit(serde_json::from_value(input).log_err()),
"Write" => Self::Write(serde_json::from_value(input).log_err()),
"LS" => Self::Ls(serde_json::from_value(input).log_err()),
"Glob" => Self::Glob(serde_json::from_value(input).log_err()),
"Grep" => Self::Grep(serde_json::from_value(input).log_err()),
"Bash" => Self::Terminal(serde_json::from_value(input).log_err()),
"WebFetch" => Self::WebFetch(serde_json::from_value(input).log_err()),
"WebSearch" => Self::WebSearch(serde_json::from_value(input).log_err()),
"TodoWrite" => Self::TodoWrite(serde_json::from_value(input).log_err()),
"exit_plan_mode" => Self::ExitPlanMode(serde_json::from_value(input).log_err()),
"Task" => Self::Task(serde_json::from_value(input).log_err()),
"NotebookRead" => Self::NotebookRead(serde_json::from_value(input).log_err()),
"NotebookEdit" => Self::NotebookEdit(serde_json::from_value(input).log_err()),
// Inferred from name
_ => {
let tool_name = tool_name.to_lowercase();
if tool_name.contains("edit") || tool_name.contains("write") {
Self::Edit(None)
} else if tool_name.contains("terminal") {
Self::Terminal(None)
} else {
Self::Other {
name: tool_name.to_string(),
input,
}
}
}
}
}
pub fn label(&self) -> String {
match &self {
Self::Task(Some(params)) => params.description.clone(),
Self::Task(None) => "Task".into(),
Self::NotebookRead(Some(params)) => {
format!("Read Notebook {}", params.notebook_path.display())
}
Self::NotebookRead(None) => "Read Notebook".into(),
Self::NotebookEdit(Some(params)) => {
format!("Edit Notebook {}", params.notebook_path.display())
}
Self::NotebookEdit(None) => "Edit Notebook".into(),
Self::Terminal(Some(params)) => format!("`{}`", params.command),
Self::Terminal(None) => "Terminal".into(),
Self::ReadFile(_) => "Read File".into(),
Self::Ls(Some(params)) => {
format!("List Directory {}", params.path.display())
}
Self::Ls(None) => "List Directory".into(),
Self::Edit(Some(params)) => {
format!("Edit {}", params.abs_path.display())
}
Self::Edit(None) => "Edit".into(),
Self::MultiEdit(Some(params)) => {
format!("Multi Edit {}", params.file_path.display())
}
Self::MultiEdit(None) => "Multi Edit".into(),
Self::Write(Some(params)) => {
format!("Write {}", params.file_path.display())
}
Self::Write(None) => "Write".into(),
Self::Glob(Some(params)) => {
format!("Glob `{params}`")
}
Self::Glob(None) => "Glob".into(),
Self::Grep(Some(params)) => format!("`{params}`"),
Self::Grep(None) => "Grep".into(),
Self::WebFetch(Some(params)) => format!("Fetch {}", params.url),
Self::WebFetch(None) => "Fetch".into(),
Self::WebSearch(Some(params)) => format!("Web Search: {}", params),
Self::WebSearch(None) => "Web Search".into(),
Self::TodoWrite(Some(params)) => format!(
"Update TODOs: {}",
params.todos.iter().map(|todo| &todo.content).join(", ")
),
Self::TodoWrite(None) => "Update TODOs".into(),
Self::ExitPlanMode(_) => "Exit Plan Mode".into(),
Self::Other { name, .. } => name.clone(),
}
}
pub fn content(&self) -> Vec<acp::ToolCallContent> {
match &self {
Self::Other { input, .. } => vec![
format!(
"```json\n{}```",
serde_json::to_string_pretty(&input).unwrap_or("{}".to_string())
)
.into(),
],
Self::Task(Some(params)) => vec![params.prompt.clone().into()],
Self::NotebookRead(Some(params)) => {
vec![params.notebook_path.display().to_string().into()]
}
Self::NotebookEdit(Some(params)) => vec![params.new_source.clone().into()],
Self::Terminal(Some(params)) => vec![
format!(
"`{}`\n\n{}",
params.command,
params.description.as_deref().unwrap_or_default()
)
.into(),
],
Self::ReadFile(Some(params)) => vec![params.abs_path.display().to_string().into()],
Self::Ls(Some(params)) => vec![params.path.display().to_string().into()],
Self::Glob(Some(params)) => vec![params.to_string().into()],
Self::Grep(Some(params)) => vec![format!("`{params}`").into()],
Self::WebFetch(Some(params)) => vec![params.prompt.clone().into()],
Self::WebSearch(Some(params)) => vec![params.to_string().into()],
Self::TodoWrite(Some(params)) => vec![
params
.todos
.iter()
.map(|todo| {
format!(
"- {} {}: {}",
match todo.status {
TodoStatus::Completed => "",
TodoStatus::InProgress => "🚧",
TodoStatus::Pending => "",
},
todo.priority,
todo.content
)
})
.join("\n")
.into(),
],
Self::ExitPlanMode(Some(params)) => vec![params.plan.clone().into()],
Self::Edit(Some(params)) => vec![acp::ToolCallContent::Diff {
diff: acp::Diff {
path: params.abs_path.clone(),
old_text: Some(params.old_text.clone()),
new_text: params.new_text.clone(),
},
}],
Self::Write(Some(params)) => vec![acp::ToolCallContent::Diff {
diff: acp::Diff {
path: params.file_path.clone(),
old_text: None,
new_text: params.content.clone(),
},
}],
Self::MultiEdit(Some(params)) => {
// todo: show multiple edits in a multibuffer?
params
.edits
.first()
.map(|edit| {
vec![acp::ToolCallContent::Diff {
diff: acp::Diff {
path: params.file_path.clone(),
old_text: Some(edit.old_string.clone()),
new_text: edit.new_string.clone(),
},
}]
})
.unwrap_or_default()
}
Self::Task(None)
| Self::NotebookRead(None)
| Self::NotebookEdit(None)
| Self::Terminal(None)
| Self::ReadFile(None)
| Self::Ls(None)
| Self::Glob(None)
| Self::Grep(None)
| Self::WebFetch(None)
| Self::WebSearch(None)
| Self::TodoWrite(None)
| Self::ExitPlanMode(None)
| Self::Edit(None)
| Self::Write(None)
| Self::MultiEdit(None) => vec![],
}
}
pub fn kind(&self) -> acp::ToolKind {
match self {
Self::Task(_) => acp::ToolKind::Think,
Self::NotebookRead(_) => acp::ToolKind::Read,
Self::NotebookEdit(_) => acp::ToolKind::Edit,
Self::Edit(_) => acp::ToolKind::Edit,
Self::MultiEdit(_) => acp::ToolKind::Edit,
Self::Write(_) => acp::ToolKind::Edit,
Self::ReadFile(_) => acp::ToolKind::Read,
Self::Ls(_) => acp::ToolKind::Search,
Self::Glob(_) => acp::ToolKind::Search,
Self::Grep(_) => acp::ToolKind::Search,
Self::Terminal(_) => acp::ToolKind::Execute,
Self::WebSearch(_) => acp::ToolKind::Search,
Self::WebFetch(_) => acp::ToolKind::Fetch,
Self::TodoWrite(_) => acp::ToolKind::Think,
Self::ExitPlanMode(_) => acp::ToolKind::Think,
Self::Other { .. } => acp::ToolKind::Other,
}
}
pub fn locations(&self) -> Vec<acp::ToolCallLocation> {
match &self {
Self::Edit(Some(EditToolParams { abs_path, .. })) => vec![acp::ToolCallLocation {
path: abs_path.clone(),
line: None,
}],
Self::MultiEdit(Some(MultiEditToolParams { file_path, .. })) => {
vec![acp::ToolCallLocation {
path: file_path.clone(),
line: None,
}]
}
Self::Write(Some(WriteToolParams { file_path, .. })) => {
vec![acp::ToolCallLocation {
path: file_path.clone(),
line: None,
}]
}
Self::ReadFile(Some(ReadToolParams {
abs_path, offset, ..
})) => vec![acp::ToolCallLocation {
path: abs_path.clone(),
line: *offset,
}],
Self::NotebookRead(Some(NotebookReadToolParams { notebook_path, .. })) => {
vec![acp::ToolCallLocation {
path: notebook_path.clone(),
line: None,
}]
}
Self::NotebookEdit(Some(NotebookEditToolParams { notebook_path, .. })) => {
vec![acp::ToolCallLocation {
path: notebook_path.clone(),
line: None,
}]
}
Self::Glob(Some(GlobToolParams {
path: Some(path), ..
})) => vec![acp::ToolCallLocation {
path: path.clone(),
line: None,
}],
Self::Ls(Some(LsToolParams { path, .. })) => vec![acp::ToolCallLocation {
path: path.clone(),
line: None,
}],
Self::Grep(Some(GrepToolParams {
path: Some(path), ..
})) => vec![acp::ToolCallLocation {
path: PathBuf::from(path),
line: None,
}],
Self::Task(_)
| Self::NotebookRead(None)
| Self::NotebookEdit(None)
| Self::Edit(None)
| Self::MultiEdit(None)
| Self::Write(None)
| Self::ReadFile(None)
| Self::Ls(None)
| Self::Glob(_)
| Self::Grep(_)
| Self::Terminal(_)
| Self::WebFetch(_)
| Self::WebSearch(_)
| Self::TodoWrite(_)
| Self::ExitPlanMode(_)
| Self::Other { .. } => vec![],
}
}
pub fn as_acp(&self, id: acp::ToolCallId) -> acp::ToolCall {
acp::ToolCall {
id,
kind: self.kind(),
status: acp::ToolCallStatus::InProgress,
label: self.label(),
content: self.content(),
locations: self.locations(),
raw_input: None,
}
}
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct EditToolParams {
/// The absolute path to the file to read.
pub abs_path: PathBuf,
/// The old text to replace (must be unique in the file)
pub old_text: String,
/// The new text.
pub new_text: String,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct ReadToolParams {
/// The absolute path to the file to read.
pub abs_path: PathBuf,
/// Which line to start reading from. Omit to start from the beginning.
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<u32>,
/// How many lines to read. Omit for the whole file.
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct WriteToolParams {
/// Absolute path for new file
pub file_path: PathBuf,
/// File content
pub content: String,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct BashToolParams {
/// Shell command to execute
pub command: String,
/// 5-10 word description of what command does
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
/// Timeout in ms (max 600000ms/10min, default 120000ms)
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option<u32>,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct GlobToolParams {
/// Glob pattern like **/*.js or src/**/*.ts
pub pattern: String,
/// Directory to search in (omit for current directory)
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<PathBuf>,
}
impl std::fmt::Display for GlobToolParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(path) = &self.path {
write!(f, "{}", path.display())?;
}
write!(f, "{}", self.pattern)
}
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct LsToolParams {
/// Absolute path to directory
pub path: PathBuf,
/// Array of glob patterns to ignore
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ignore: Vec<String>,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct GrepToolParams {
/// Regex pattern to search for
pub pattern: String,
/// File/directory to search (defaults to current directory)
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
/// "content" (shows lines), "files_with_matches" (default), "count"
#[serde(skip_serializing_if = "Option::is_none")]
pub output_mode: Option<GrepOutputMode>,
/// Filter files with glob pattern like "*.js"
#[serde(skip_serializing_if = "Option::is_none")]
pub glob: Option<String>,
/// File type filter like "js", "py", "rust"
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub file_type: Option<String>,
/// Case insensitive search
#[serde(rename = "-i", default, skip_serializing_if = "is_false")]
pub case_insensitive: bool,
/// Show line numbers (content mode only)
#[serde(rename = "-n", default, skip_serializing_if = "is_false")]
pub line_numbers: bool,
/// Lines after match (content mode only)
#[serde(rename = "-A", skip_serializing_if = "Option::is_none")]
pub after_context: Option<u32>,
/// Lines before match (content mode only)
#[serde(rename = "-B", skip_serializing_if = "Option::is_none")]
pub before_context: Option<u32>,
/// Lines before and after match (content mode only)
#[serde(rename = "-C", skip_serializing_if = "Option::is_none")]
pub context: Option<u32>,
/// Enable multiline/cross-line matching
#[serde(default, skip_serializing_if = "is_false")]
pub multiline: bool,
/// Limit output to first N results
#[serde(skip_serializing_if = "Option::is_none")]
pub head_limit: Option<u32>,
}
impl std::fmt::Display for GrepToolParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "grep")?;
// Boolean flags
if self.case_insensitive {
write!(f, " -i")?;
}
if self.line_numbers {
write!(f, " -n")?;
}
// Context options
if let Some(after) = self.after_context {
write!(f, " -A {}", after)?;
}
if let Some(before) = self.before_context {
write!(f, " -B {}", before)?;
}
if let Some(context) = self.context {
write!(f, " -C {}", context)?;
}
// Output mode
if let Some(mode) = &self.output_mode {
match mode {
GrepOutputMode::FilesWithMatches => write!(f, " -l")?,
GrepOutputMode::Count => write!(f, " -c")?,
GrepOutputMode::Content => {} // Default mode
}
}
// Head limit
if let Some(limit) = self.head_limit {
write!(f, " | head -{}", limit)?;
}
// Glob pattern
if let Some(glob) = &self.glob {
write!(f, " --include=\"{}\"", glob)?;
}
// File type
if let Some(file_type) = &self.file_type {
write!(f, " --type={}", file_type)?;
}
// Multiline
if self.multiline {
write!(f, " -P")?; // Perl-compatible regex for multiline
}
// Pattern (escaped if contains special characters)
write!(f, " \"{}\"", self.pattern)?;
// Path
if let Some(path) = &self.path {
write!(f, " {}", path)?;
}
Ok(())
}
}
#[derive(Deserialize, Serialize, JsonSchema, strum::Display, Debug)]
#[serde(rename_all = "snake_case")]
pub enum TodoPriority {
High,
Medium,
Low,
}
impl Into<acp::PlanEntryPriority> for TodoPriority {
fn into(self) -> acp::PlanEntryPriority {
match self {
TodoPriority::High => acp::PlanEntryPriority::High,
TodoPriority::Medium => acp::PlanEntryPriority::Medium,
TodoPriority::Low => acp::PlanEntryPriority::Low,
}
}
}
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum TodoStatus {
Pending,
InProgress,
Completed,
}
impl Into<acp::PlanEntryStatus> for TodoStatus {
fn into(self) -> acp::PlanEntryStatus {
match self {
TodoStatus::Pending => acp::PlanEntryStatus::Pending,
TodoStatus::InProgress => acp::PlanEntryStatus::InProgress,
TodoStatus::Completed => acp::PlanEntryStatus::Completed,
}
}
}
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
pub struct Todo {
/// Unique identifier
pub id: String,
/// Task description
pub content: String,
/// Priority level of the todo
pub priority: TodoPriority,
/// Current status of the todo
pub status: TodoStatus,
}
impl Into<acp::PlanEntry> for Todo {
fn into(self) -> acp::PlanEntry {
acp::PlanEntry {
content: self.content,
priority: self.priority.into(),
status: self.status.into(),
}
}
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct TodoWriteToolParams {
pub todos: Vec<Todo>,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct ExitPlanModeToolParams {
/// Implementation plan in markdown format
pub plan: String,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct TaskToolParams {
/// Short 3-5 word description of task
pub description: String,
/// Detailed task for agent to perform
pub prompt: String,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct NotebookReadToolParams {
/// Absolute path to .ipynb file
pub notebook_path: PathBuf,
/// Specific cell ID to read
#[serde(skip_serializing_if = "Option::is_none")]
pub cell_id: Option<String>,
}
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum CellType {
Code,
Markdown,
}
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum EditMode {
Replace,
Insert,
Delete,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct NotebookEditToolParams {
/// Absolute path to .ipynb file
pub notebook_path: PathBuf,
/// New cell content
pub new_source: String,
/// Cell ID to edit
#[serde(skip_serializing_if = "Option::is_none")]
pub cell_id: Option<String>,
/// Type of cell (code or markdown)
#[serde(skip_serializing_if = "Option::is_none")]
pub cell_type: Option<CellType>,
/// Edit operation mode
#[serde(skip_serializing_if = "Option::is_none")]
pub edit_mode: Option<EditMode>,
}
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
pub struct MultiEditItem {
/// The text to search for and replace
pub old_string: String,
/// The replacement text
pub new_string: String,
/// Whether to replace all occurrences or just the first
#[serde(default, skip_serializing_if = "is_false")]
pub replace_all: bool,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct MultiEditToolParams {
/// Absolute path to file
pub file_path: PathBuf,
/// List of edits to apply
pub edits: Vec<MultiEditItem>,
}
fn is_false(v: &bool) -> bool {
!*v
}
#[derive(Deserialize, JsonSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum GrepOutputMode {
Content,
FilesWithMatches,
Count,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct WebFetchToolParams {
/// Valid URL to fetch
#[serde(rename = "url")]
pub url: String,
/// What to extract from content
pub prompt: String,
}
#[derive(Deserialize, JsonSchema, Debug)]
pub struct WebSearchToolParams {
/// Search query (min 2 chars)
pub query: String,
/// Only include these domains
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub allowed_domains: Vec<String>,
/// Exclude these domains
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blocked_domains: Vec<String>,
}
impl std::fmt::Display for WebSearchToolParams {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\"", self.query)?;
if !self.allowed_domains.is_empty() {
write!(f, " (allowed: {})", self.allowed_domains.join(", "))?;
}
if !self.blocked_domains.is_empty() {
write!(f, " (blocked: {})", self.blocked_domains.join(", "))?;
}
Ok(())
}
}

View File

@@ -1,319 +0,0 @@
use agent_client_protocol as acp;
use anyhow::anyhow;
use collections::HashMap;
use context_server::listener::McpServerTool;
use context_server::types::requests;
use context_server::{ContextServer, ContextServerCommand, ContextServerId};
use futures::channel::{mpsc, oneshot};
use project::Project;
use settings::SettingsStore;
use smol::stream::StreamExt as _;
use std::cell::RefCell;
use std::rc::Rc;
use std::{path::Path, sync::Arc};
use util::ResultExt;
use anyhow::{Context, Result};
use gpui::{App, AppContext as _, AsyncApp, Entity, Task, WeakEntity};
use crate::mcp_server::ZedMcpServer;
use crate::{AgentServer, AgentServerCommand, AllAgentServersSettings, mcp_server};
use acp_thread::{AcpThread, AgentConnection};
#[derive(Clone)]
pub struct Codex;
impl AgentServer for Codex {
fn name(&self) -> &'static str {
"Codex"
}
fn empty_state_headline(&self) -> &'static str {
"Welcome to Codex"
}
fn empty_state_message(&self) -> &'static str {
"What can I help with?"
}
fn logo(&self) -> ui::IconName {
ui::IconName::AiOpenAi
}
fn connect(
&self,
_root_dir: &Path,
project: &Entity<Project>,
cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>> {
let project = project.clone();
let working_directory = project.read(cx).active_project_directory(cx);
cx.spawn(async move |cx| {
let settings = cx.read_global(|settings: &SettingsStore, _| {
settings.get::<AllAgentServersSettings>(None).codex.clone()
})?;
let Some(command) =
AgentServerCommand::resolve("codex", &["mcp"], settings, &project, cx).await
else {
anyhow::bail!("Failed to find codex binary");
};
let client: Arc<ContextServer> = ContextServer::stdio(
ContextServerId("codex-mcp-server".into()),
ContextServerCommand {
path: command.path,
args: command.args,
env: command.env,
},
working_directory,
)
.into();
ContextServer::start(client.clone(), cx).await?;
let (notification_tx, mut notification_rx) = mpsc::unbounded();
client
.client()
.context("Failed to subscribe")?
.on_notification(acp::SESSION_UPDATE_METHOD_NAME, {
move |notification, _cx| {
let notification_tx = notification_tx.clone();
log::trace!(
"ACP Notification: {}",
serde_json::to_string_pretty(&notification).unwrap()
);
if let Some(notification) =
serde_json::from_value::<acp::SessionNotification>(notification)
.log_err()
{
notification_tx.unbounded_send(notification).ok();
}
}
});
let sessions = Rc::new(RefCell::new(HashMap::default()));
let notification_handler_task = cx.spawn({
let sessions = sessions.clone();
async move |cx| {
while let Some(notification) = notification_rx.next().await {
CodexConnection::handle_session_notification(
notification,
sessions.clone(),
cx,
)
}
}
});
let connection = CodexConnection {
client,
sessions,
_notification_handler_task: notification_handler_task,
};
Ok(Rc::new(connection) as _)
})
}
}
struct CodexConnection {
client: Arc<context_server::ContextServer>,
sessions: Rc<RefCell<HashMap<acp::SessionId, CodexSession>>>,
_notification_handler_task: Task<()>,
}
struct CodexSession {
thread: WeakEntity<AcpThread>,
cancel_tx: Option<oneshot::Sender<()>>,
_mcp_server: ZedMcpServer,
}
impl AgentConnection for CodexConnection {
fn name(&self) -> &'static str {
"Codex"
}
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,
cwd: &Path,
cx: &mut AsyncApp,
) -> Task<Result<Entity<AcpThread>>> {
let client = self.client.client();
let sessions = self.sessions.clone();
let cwd = cwd.to_path_buf();
cx.spawn(async move |cx| {
let client = client.context("MCP server is not initialized yet")?;
let (mut thread_tx, thread_rx) = watch::channel(WeakEntity::new_invalid());
let mcp_server = ZedMcpServer::new(thread_rx, cx).await?;
let response = client
.request::<requests::CallTool>(context_server::types::CallToolParams {
name: acp::NEW_SESSION_TOOL_NAME.into(),
arguments: Some(serde_json::to_value(acp::NewSessionArguments {
mcp_servers: [(
mcp_server::SERVER_NAME.to_string(),
mcp_server.server_config()?,
)]
.into(),
client_tools: acp::ClientTools {
request_permission: Some(acp::McpToolId {
mcp_server: mcp_server::SERVER_NAME.into(),
tool_name: mcp_server::RequestPermissionTool::NAME.into(),
}),
read_text_file: Some(acp::McpToolId {
mcp_server: mcp_server::SERVER_NAME.into(),
tool_name: mcp_server::ReadTextFileTool::NAME.into(),
}),
write_text_file: Some(acp::McpToolId {
mcp_server: mcp_server::SERVER_NAME.into(),
tool_name: mcp_server::WriteTextFileTool::NAME.into(),
}),
},
cwd,
})?),
meta: None,
})
.await?;
if response.is_error.unwrap_or_default() {
return Err(anyhow!(response.text_contents()));
}
let result = serde_json::from_value::<acp::NewSessionOutput>(
response.structured_content.context("Empty response")?,
)?;
let thread =
cx.new(|cx| AcpThread::new(self.clone(), project, result.session_id.clone(), cx))?;
thread_tx.send(thread.downgrade())?;
let session = CodexSession {
thread: thread.downgrade(),
cancel_tx: None,
_mcp_server: mcp_server,
};
sessions.borrow_mut().insert(result.session_id, session);
Ok(thread)
})
}
fn authenticate(&self, _cx: &mut App) -> Task<Result<()>> {
Task::ready(Err(anyhow!("Authentication not supported")))
}
fn prompt(
&self,
params: agent_client_protocol::PromptArguments,
cx: &mut App,
) -> Task<Result<()>> {
let client = self.client.client();
let sessions = self.sessions.clone();
cx.foreground_executor().spawn(async move {
let client = client.context("MCP server is not initialized yet")?;
let (new_cancel_tx, cancel_rx) = oneshot::channel();
{
let mut sessions = sessions.borrow_mut();
let session = sessions
.get_mut(&params.session_id)
.context("Session not found")?;
session.cancel_tx.replace(new_cancel_tx);
}
let result = client
.request_with::<requests::CallTool>(
context_server::types::CallToolParams {
name: acp::PROMPT_TOOL_NAME.into(),
arguments: Some(serde_json::to_value(params)?),
meta: None,
},
Some(cancel_rx),
None,
)
.await;
if let Err(err) = &result
&& err.is::<context_server::client::RequestCanceled>()
{
return Ok(());
}
let response = result?;
if response.is_error.unwrap_or_default() {
return Err(anyhow!(response.text_contents()));
}
Ok(())
})
}
fn cancel(&self, session_id: &agent_client_protocol::SessionId, _cx: &mut App) {
let mut sessions = self.sessions.borrow_mut();
if let Some(cancel_tx) = sessions
.get_mut(session_id)
.and_then(|session| session.cancel_tx.take())
{
cancel_tx.send(()).ok();
}
}
}
impl CodexConnection {
pub fn handle_session_notification(
notification: acp::SessionNotification,
threads: Rc<RefCell<HashMap<acp::SessionId, CodexSession>>>,
cx: &mut AsyncApp,
) {
let threads = threads.borrow();
let Some(thread) = threads
.get(&notification.session_id)
.and_then(|session| session.thread.upgrade())
else {
log::error!(
"Thread not found for session ID: {}",
notification.session_id
);
return;
};
thread
.update(cx, |thread, cx| {
thread.handle_session_update(notification.update, cx)
})
.log_err();
}
}
impl Drop for CodexConnection {
fn drop(&mut self) {
self.client.stop().log_err();
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::AgentServerCommand;
use std::path::Path;
crate::common_e2e_tests!(Codex, allow_option_id = "approve");
pub fn local_command() -> AgentServerCommand {
let cli_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../../codex/codex-rs/target/debug/codex");
AgentServerCommand {
path: cli_path,
args: vec![],
env: None,
}
}
}

View File

@@ -1,459 +0,0 @@
use std::{
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use crate::{AgentServer, AgentServerSettings, AllAgentServersSettings};
use acp_thread::{AcpThread, AgentThreadEntry, ToolCall, ToolCallStatus};
use agent_client_protocol as acp;
use futures::{FutureExt, StreamExt, channel::mpsc, select};
use gpui::{Entity, TestAppContext};
use indoc::indoc;
use project::{FakeFs, Project};
use settings::{Settings, SettingsStore};
use util::path;
pub async fn test_basic(server: impl AgentServer + 'static, cx: &mut TestAppContext) {
let fs = init_test(cx).await;
let project = Project::test(fs, [], cx).await;
let thread = new_test_thread(server, project.clone(), "/private/tmp", cx).await;
thread
.update(cx, |thread, cx| thread.send_raw("Hello from Zed!", cx))
.await
.unwrap();
thread.read_with(cx, |thread, _| {
assert!(
thread.entries().len() >= 2,
"Expected at least 2 entries. Got: {:?}",
thread.entries()
);
assert!(matches!(
thread.entries()[0],
AgentThreadEntry::UserMessage(_)
));
assert!(matches!(
thread.entries()[1],
AgentThreadEntry::AssistantMessage(_)
));
});
}
pub async fn test_path_mentions(server: impl AgentServer + 'static, cx: &mut TestAppContext) {
let _fs = init_test(cx).await;
let tempdir = tempfile::tempdir().unwrap();
std::fs::write(
tempdir.path().join("foo.rs"),
indoc! {"
fn main() {
println!(\"Hello, world!\");
}
"},
)
.expect("failed to write file");
let project = Project::example([tempdir.path()], &mut cx.to_async()).await;
let thread = new_test_thread(server, project.clone(), tempdir.path(), cx).await;
thread
.update(cx, |thread, cx| {
thread.send(
vec![
acp::ContentBlock::Text(acp::TextContent {
text: "Read the file ".into(),
annotations: None,
}),
acp::ContentBlock::ResourceLink(acp::ResourceLink {
uri: "foo.rs".into(),
name: "foo.rs".into(),
annotations: None,
description: None,
mime_type: None,
size: None,
title: None,
}),
acp::ContentBlock::Text(acp::TextContent {
text: " and tell me what the content of the println! is".into(),
annotations: None,
}),
],
cx,
)
})
.await
.unwrap();
thread.read_with(cx, |thread, cx| {
assert!(matches!(
thread.entries()[0],
AgentThreadEntry::UserMessage(_)
));
let assistant_message = &thread
.entries()
.iter()
.rev()
.find_map(|entry| match entry {
AgentThreadEntry::AssistantMessage(msg) => Some(msg),
_ => None,
})
.unwrap();
assert!(
assistant_message.to_markdown(cx).contains("Hello, world!"),
"unexpected assistant message: {:?}",
assistant_message.to_markdown(cx)
);
});
drop(tempdir);
}
pub async fn test_tool_call(server: impl AgentServer + 'static, cx: &mut TestAppContext) {
let _fs = init_test(cx).await;
let tempdir = tempfile::tempdir().unwrap();
let foo_path = tempdir.path().join("foo");
std::fs::write(&foo_path, "Lorem ipsum dolor").expect("failed to write file");
let project = Project::example([tempdir.path()], &mut cx.to_async()).await;
let thread = new_test_thread(server, project.clone(), "/private/tmp", cx).await;
thread
.update(cx, |thread, cx| {
thread.send_raw(
&format!("Read {} and tell me what you see.", foo_path.display()),
cx,
)
})
.await
.unwrap();
thread.read_with(cx, |thread, _cx| {
assert!(thread.entries().iter().any(|entry| {
matches!(
entry,
AgentThreadEntry::ToolCall(ToolCall {
status: ToolCallStatus::Allowed { .. },
..
})
)
}));
assert!(
thread
.entries()
.iter()
.any(|entry| { matches!(entry, AgentThreadEntry::AssistantMessage(_)) })
);
});
drop(tempdir);
}
pub async fn test_tool_call_with_confirmation(
server: impl AgentServer + 'static,
allow_option_id: acp::PermissionOptionId,
cx: &mut TestAppContext,
) {
let fs = init_test(cx).await;
let project = Project::test(fs, [path!("/private/tmp").as_ref()], cx).await;
let thread = new_test_thread(server, project.clone(), "/private/tmp", cx).await;
let full_turn = thread.update(cx, |thread, cx| {
thread.send_raw(
r#"Run exactly `touch hello.txt && echo "Hello, world!" | tee hello.txt` in the terminal."#,
cx,
)
});
run_until_first_tool_call(
&thread,
|entry| {
matches!(
entry,
AgentThreadEntry::ToolCall(ToolCall {
status: ToolCallStatus::WaitingForConfirmation { .. },
..
})
)
},
cx,
)
.await;
let tool_call_id = thread.read_with(cx, |thread, cx| {
let AgentThreadEntry::ToolCall(ToolCall {
id,
label,
status: ToolCallStatus::WaitingForConfirmation { .. },
..
}) = &thread
.entries()
.iter()
.find(|entry| matches!(entry, AgentThreadEntry::ToolCall(_)))
.unwrap()
else {
panic!();
};
let label = label.read(cx).source();
assert!(label.contains("touch"), "Got: {}", label);
id.clone()
});
thread.update(cx, |thread, cx| {
thread.authorize_tool_call(
tool_call_id,
allow_option_id,
acp::PermissionOptionKind::AllowOnce,
cx,
);
assert!(thread.entries().iter().any(|entry| matches!(
entry,
AgentThreadEntry::ToolCall(ToolCall {
status: ToolCallStatus::Allowed { .. },
..
})
)));
});
full_turn.await.unwrap();
thread.read_with(cx, |thread, cx| {
let AgentThreadEntry::ToolCall(ToolCall {
content,
status: ToolCallStatus::Allowed { .. },
..
}) = thread
.entries()
.iter()
.find(|entry| matches!(entry, AgentThreadEntry::ToolCall(_)))
.unwrap()
else {
panic!();
};
assert!(
content.iter().any(|c| c.to_markdown(cx).contains("Hello")),
"Expected content to contain 'Hello'"
);
});
}
pub async fn test_cancel(server: impl AgentServer + 'static, cx: &mut TestAppContext) {
let fs = init_test(cx).await;
let project = Project::test(fs, [path!("/private/tmp").as_ref()], cx).await;
let thread = new_test_thread(server, project.clone(), "/private/tmp", cx).await;
let full_turn = thread.update(cx, |thread, cx| {
thread.send_raw(
r#"Run exactly `touch hello.txt && echo "Hello, world!" | tee hello.txt` in the terminal."#,
cx,
)
});
let first_tool_call_ix = run_until_first_tool_call(
&thread,
|entry| {
matches!(
entry,
AgentThreadEntry::ToolCall(ToolCall {
status: ToolCallStatus::WaitingForConfirmation { .. },
..
})
)
},
cx,
)
.await;
thread.read_with(cx, |thread, cx| {
let AgentThreadEntry::ToolCall(ToolCall {
id,
label,
status: ToolCallStatus::WaitingForConfirmation { .. },
..
}) = &thread.entries()[first_tool_call_ix]
else {
panic!("{:?}", thread.entries()[1]);
};
let label = label.read(cx).source();
assert!(label.contains("touch"), "Got: {}", label);
id.clone()
});
let _ = thread.update(cx, |thread, cx| thread.cancel(cx));
full_turn.await.unwrap();
thread.read_with(cx, |thread, _| {
let AgentThreadEntry::ToolCall(ToolCall {
status: ToolCallStatus::Canceled,
..
}) = &thread.entries()[first_tool_call_ix]
else {
panic!();
};
});
thread
.update(cx, |thread, cx| {
thread.send_raw(r#"Stop running and say goodbye to me."#, cx)
})
.await
.unwrap();
thread.read_with(cx, |thread, _| {
assert!(matches!(
&thread.entries().last().unwrap(),
AgentThreadEntry::AssistantMessage(..),
))
});
}
#[macro_export]
macro_rules! common_e2e_tests {
($server:expr, allow_option_id = $allow_option_id:expr) => {
mod common_e2e {
use super::*;
#[::gpui::test]
#[cfg_attr(not(feature = "e2e"), ignore)]
async fn basic(cx: &mut ::gpui::TestAppContext) {
$crate::e2e_tests::test_basic($server, cx).await;
}
#[::gpui::test]
#[cfg_attr(not(feature = "e2e"), ignore)]
async fn path_mentions(cx: &mut ::gpui::TestAppContext) {
$crate::e2e_tests::test_path_mentions($server, cx).await;
}
#[::gpui::test]
#[cfg_attr(not(feature = "e2e"), ignore)]
async fn tool_call(cx: &mut ::gpui::TestAppContext) {
$crate::e2e_tests::test_tool_call($server, cx).await;
}
#[::gpui::test]
#[cfg_attr(not(feature = "e2e"), ignore)]
async fn tool_call_with_confirmation(cx: &mut ::gpui::TestAppContext) {
$crate::e2e_tests::test_tool_call_with_confirmation(
$server,
::agent_client_protocol::PermissionOptionId($allow_option_id.into()),
cx,
)
.await;
}
#[::gpui::test]
#[cfg_attr(not(feature = "e2e"), ignore)]
async fn cancel(cx: &mut ::gpui::TestAppContext) {
$crate::e2e_tests::test_cancel($server, cx).await;
}
}
};
}
// Helpers
pub async fn init_test(cx: &mut TestAppContext) -> Arc<FakeFs> {
env_logger::try_init().ok();
cx.update(|cx| {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
Project::init_settings(cx);
language::init(cx);
crate::settings::init(cx);
crate::AllAgentServersSettings::override_global(
AllAgentServersSettings {
claude: Some(AgentServerSettings {
command: crate::claude::tests::local_command(),
}),
gemini: Some(AgentServerSettings {
command: crate::gemini::tests::local_command(),
}),
codex: Some(AgentServerSettings {
command: crate::codex::tests::local_command(),
}),
},
cx,
);
});
cx.executor().allow_parking();
FakeFs::new(cx.executor())
}
pub async fn new_test_thread(
server: impl AgentServer + 'static,
project: Entity<Project>,
current_dir: impl AsRef<Path>,
cx: &mut TestAppContext,
) -> Entity<AcpThread> {
let connection = cx
.update(|cx| server.connect(current_dir.as_ref(), &project, cx))
.await
.unwrap();
let thread = connection
.new_thread(project.clone(), current_dir.as_ref(), &mut cx.to_async())
.await
.unwrap();
thread
}
pub async fn run_until_first_tool_call(
thread: &Entity<AcpThread>,
wait_until: impl Fn(&AgentThreadEntry) -> bool + 'static,
cx: &mut TestAppContext,
) -> usize {
let (mut tx, mut rx) = mpsc::channel::<usize>(1);
let subscription = cx.update(|cx| {
cx.subscribe(thread, move |thread, _, cx| {
for (ix, entry) in thread.read(cx).entries().iter().enumerate() {
if wait_until(entry) {
return tx.try_send(ix).unwrap();
}
}
})
});
select! {
// We have to use a smol timer here because
// cx.background_executor().timer isn't real in the test context
_ = futures::FutureExt::fuse(smol::Timer::after(Duration::from_secs(20))) => {
panic!("Timeout waiting for tool call")
}
ix = rx.next().fuse() => {
drop(subscription);
ix.unwrap()
}
}
}
pub fn get_zed_path() -> PathBuf {
let mut zed_path = std::env::current_exe().unwrap();
while zed_path
.file_name()
.map_or(true, |name| name.to_string_lossy() != "debug")
{
if !zed_path.pop() {
panic!("Could not find target directory");
}
}
zed_path.push("zed");
if !zed_path.exists() {
panic!("\n🚨 Run `cargo build` at least once before running e2e tests\n\n");
}
zed_path
}

View File

@@ -1,206 +0,0 @@
use anyhow::anyhow;
use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;
use util::ResultExt as _;
use crate::{AgentServer, AgentServerCommand, AgentServerVersion};
use acp_thread::{AgentConnection, LoadError, OldAcpAgentConnection, OldAcpClientDelegate};
use agentic_coding_protocol as acp_old;
use anyhow::{Context as _, Result};
use gpui::{AppContext as _, AsyncApp, Entity, Task, WeakEntity};
use project::Project;
use settings::SettingsStore;
use ui::App;
use crate::AllAgentServersSettings;
#[derive(Clone)]
pub struct Gemini;
const ACP_ARG: &str = "--experimental-acp";
impl AgentServer for Gemini {
fn name(&self) -> &'static str {
"Gemini"
}
fn empty_state_headline(&self) -> &'static str {
"Welcome to Gemini"
}
fn empty_state_message(&self) -> &'static str {
"Ask questions, edit files, run commands.\nBe specific for the best results."
}
fn logo(&self) -> ui::IconName {
ui::IconName::AiGemini
}
fn connect(
&self,
root_dir: &Path,
project: &Entity<Project>,
cx: &mut App,
) -> Task<Result<Rc<dyn AgentConnection>>> {
let root_dir = root_dir.to_path_buf();
let project = project.clone();
let this = self.clone();
let name = self.name();
cx.spawn(async move |cx| {
let command = this.command(&project, cx).await?;
let mut child = util::command::new_smol_command(&command.path)
.args(command.args.iter())
.current_dir(root_dir)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::inherit())
.kill_on_drop(true)
.spawn()?;
let stdin = child.stdin.take().unwrap();
let stdout = child.stdout.take().unwrap();
let foreground_executor = cx.foreground_executor().clone();
let thread_rc = Rc::new(RefCell::new(WeakEntity::new_invalid()));
let (connection, io_fut) = acp_old::AgentConnection::connect_to_agent(
OldAcpClientDelegate::new(thread_rc.clone(), cx.clone()),
stdin,
stdout,
move |fut| foreground_executor.spawn(fut).detach(),
);
let io_task = cx.background_spawn(async move {
io_fut.await.log_err();
});
let child_status = cx.background_spawn(async move {
let result = match child.status().await {
Err(e) => Err(anyhow!(e)),
Ok(result) if result.success() => Ok(()),
Ok(result) => {
if let Some(AgentServerVersion::Unsupported {
error_message,
upgrade_message,
upgrade_command,
}) = this.version(&command).await.log_err()
{
Err(anyhow!(LoadError::Unsupported {
error_message,
upgrade_message,
upgrade_command
}))
} else {
Err(anyhow!(LoadError::Exited(result.code().unwrap_or(-127))))
}
}
};
drop(io_task);
result
});
let connection: Rc<dyn AgentConnection> = Rc::new(OldAcpAgentConnection {
name,
connection,
child_status,
current_thread: thread_rc,
});
Ok(connection)
})
}
}
impl Gemini {
async fn command(
&self,
project: &Entity<Project>,
cx: &mut AsyncApp,
) -> Result<AgentServerCommand> {
let settings = cx.read_global(|settings: &SettingsStore, _| {
settings.get::<AllAgentServersSettings>(None).gemini.clone()
})?;
if let Some(command) =
AgentServerCommand::resolve("gemini", &[ACP_ARG], settings, &project, cx).await
{
return Ok(command);
};
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![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)?;
let supported = String::from_utf8(help_output?.stdout)?.contains(ACP_ARG);
if supported {
Ok(AgentServerVersion::Supported)
} else {
Ok(AgentServerVersion::Unsupported {
error_message: format!(
"Your installed version of Gemini {} doesn't support the Agentic Coding Protocol (ACP).",
current_version
).into(),
upgrade_message: "Upgrade Gemini to Latest".into(),
upgrade_command: "npm install -g @google/gemini-cli@latest".into(),
})
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::AgentServerCommand;
use std::path::Path;
crate::common_e2e_tests!(Gemini, allow_option_id = "0");
pub fn local_command() -> AgentServerCommand {
let cli_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../../gemini-cli/packages/cli")
.to_string_lossy()
.to_string();
AgentServerCommand {
path: "node".into(),
args: vec![cli_path, ACP_ARG.into()],
env: None,
}
}
}

View File

@@ -1,207 +0,0 @@
use acp_thread::AcpThread;
use agent_client_protocol as acp;
use anyhow::Result;
use context_server::listener::{McpServerTool, ToolResponse};
use context_server::types::{
Implementation, InitializeParams, InitializeResponse, ProtocolVersion, ServerCapabilities,
ToolsCapabilities, requests,
};
use futures::channel::oneshot;
use gpui::{App, AsyncApp, Task, WeakEntity};
use indoc::indoc;
pub struct ZedMcpServer {
server: context_server::listener::McpServer,
}
pub const SERVER_NAME: &str = "zed";
impl ZedMcpServer {
pub async fn new(
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
cx: &AsyncApp,
) -> Result<Self> {
let mut mcp_server = context_server::listener::McpServer::new(cx).await?;
mcp_server.handle_request::<requests::Initialize>(Self::handle_initialize);
mcp_server.add_tool(RequestPermissionTool {
thread_rx: thread_rx.clone(),
});
mcp_server.add_tool(ReadTextFileTool {
thread_rx: thread_rx.clone(),
});
mcp_server.add_tool(WriteTextFileTool {
thread_rx: thread_rx.clone(),
});
Ok(Self { server: mcp_server })
}
pub fn server_config(&self) -> Result<acp::McpServerConfig> {
#[cfg(not(test))]
let zed_path = anyhow::Context::context(
std::env::current_exe(),
"finding current executable path for use in mcp_server",
)?;
#[cfg(test)]
let zed_path = crate::e2e_tests::get_zed_path();
Ok(acp::McpServerConfig {
command: zed_path,
args: vec![
"--nc".into(),
self.server.socket_path().display().to_string(),
],
env: None,
})
}
fn handle_initialize(_: InitializeParams, cx: &App) -> Task<Result<InitializeResponse>> {
cx.foreground_executor().spawn(async move {
Ok(InitializeResponse {
protocol_version: ProtocolVersion("2025-06-18".into()),
capabilities: ServerCapabilities {
experimental: None,
logging: None,
completions: None,
prompts: None,
resources: None,
tools: Some(ToolsCapabilities {
list_changed: Some(false),
}),
},
server_info: Implementation {
name: SERVER_NAME.into(),
version: "0.1.0".into(),
},
meta: None,
})
})
}
}
// Tools
#[derive(Clone)]
pub struct RequestPermissionTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
impl McpServerTool for RequestPermissionTool {
type Input = acp::RequestPermissionArguments;
type Output = acp::RequestPermissionOutput;
const NAME: &'static str = "Confirmation";
fn description(&self) -> &'static str {
indoc! {"
Request permission for tool calls.
This tool is meant to be called programmatically by the agent loop, not the LLM.
"}
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
let result = thread
.update(cx, |thread, cx| {
thread.request_tool_call_permission(input.tool_call, input.options, cx)
})?
.await;
let outcome = match result {
Ok(option_id) => acp::RequestPermissionOutcome::Selected { option_id },
Err(oneshot::Canceled) => acp::RequestPermissionOutcome::Canceled,
};
Ok(ToolResponse {
content: vec![],
structured_content: acp::RequestPermissionOutput { outcome },
})
}
}
#[derive(Clone)]
pub struct ReadTextFileTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
impl McpServerTool for ReadTextFileTool {
type Input = acp::ReadTextFileArguments;
type Output = acp::ReadTextFileOutput;
const NAME: &'static str = "Read";
fn description(&self) -> &'static str {
"Reads the content of the given file in the project including unsaved changes."
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
let content = thread
.update(cx, |thread, cx| {
thread.read_text_file(input.path, input.line, input.limit, false, cx)
})?
.await?;
Ok(ToolResponse {
content: vec![],
structured_content: acp::ReadTextFileOutput { content },
})
}
}
#[derive(Clone)]
pub struct WriteTextFileTool {
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
impl McpServerTool for WriteTextFileTool {
type Input = acp::WriteTextFileArguments;
type Output = ();
const NAME: &'static str = "Write";
fn description(&self) -> &'static str {
"Write to a file replacing its contents"
}
async fn run(
&self,
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
};
thread
.update(cx, |thread, cx| {
thread.write_text_file(input.path, input.content, cx)
})?
.await?;
Ok(ToolResponse {
content: vec![],
structured_content: (),
})
}
}

View File

@@ -1,54 +0,0 @@
use crate::AgentServerCommand;
use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
pub fn init(cx: &mut App) {
AllAgentServersSettings::register(cx);
}
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug)]
pub struct AllAgentServersSettings {
pub gemini: Option<AgentServerSettings>,
pub claude: Option<AgentServerSettings>,
pub codex: Option<AgentServerSettings>,
}
#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug)]
pub struct AgentServerSettings {
#[serde(flatten)]
pub command: AgentServerCommand,
}
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 AllAgentServersSettings {
gemini,
claude,
codex,
} in sources.defaults_and_customizations()
{
if gemini.is_some() {
settings.gemini = gemini.clone();
}
if claude.is_some() {
settings.claude = claude.clone();
}
if codex.is_some() {
settings.codex = codex.clone();
}
}
Ok(settings)
}
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
}

View File

@@ -13,7 +13,6 @@ path = "src/agent_settings.rs"
[dependencies]
anyhow.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
gpui.workspace = true
language_model.workspace = true
@@ -21,6 +20,7 @@ schemars.workspace = true
serde.workspace = true
settings.workspace = true
workspace-hack.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
fs.workspace = true

View File

@@ -69,7 +69,6 @@ pub struct AgentSettings {
pub enable_feedback: bool,
pub expand_edit_card: bool,
pub expand_terminal_card: bool,
pub use_modifier_to_send: bool,
}
impl AgentSettings {
@@ -175,10 +174,6 @@ impl AgentSettingsContent {
self.single_file_review = Some(allow);
}
pub fn set_use_modifier_to_send(&mut self, always_use: bool) {
self.use_modifier_to_send = Some(always_use);
}
pub fn set_profile(&mut self, profile_id: AgentProfileId) {
self.default_profile = Some(profile_id);
}
@@ -306,10 +301,6 @@ pub struct AgentSettingsContent {
///
/// Default: true
expand_terminal_card: Option<bool>,
/// Whether to always use cmd-enter (or ctrl-enter on Linux) to send messages in the agent panel.
///
/// Default: false
use_modifier_to_send: Option<bool>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)]
@@ -321,11 +312,11 @@ pub enum CompletionMode {
Burn,
}
impl From<CompletionMode> for cloud_llm_client::CompletionMode {
impl From<CompletionMode> for zed_llm_client::CompletionMode {
fn from(value: CompletionMode) -> Self {
match value {
CompletionMode::Normal => cloud_llm_client::CompletionMode::Normal,
CompletionMode::Burn => cloud_llm_client::CompletionMode::Max,
CompletionMode::Normal => zed_llm_client::CompletionMode::Normal,
CompletionMode::Burn => zed_llm_client::CompletionMode::Max,
}
}
}
@@ -465,10 +456,6 @@ impl Settings for AgentSettings {
&mut settings.expand_terminal_card,
value.expand_terminal_card,
);
merge(
&mut settings.use_modifier_to_send,
value.use_modifier_to_send,
);
settings
.model_parameters

View File

@@ -16,12 +16,11 @@ doctest = false
test-support = ["gpui/test-support", "language/test-support"]
[dependencies]
acp_thread.workspace = true
agent-client-protocol.workspace = true
acp.workspace = true
agent.workspace = true
agent_servers.workspace = true
agentic-coding-protocol.workspace = true
agent_settings.workspace = true
ai_onboarding.workspace = true
agent_servers.workspace = true
anyhow.workspace = true
assistant_context.workspace = true
assistant_slash_command.workspace = true
@@ -31,9 +30,7 @@ audio.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
client.workspace = true
cloud_llm_client.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
component.workspace = true
context_server.workspace = true
db.workspace = true
@@ -47,15 +44,14 @@ futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
html_to_markdown.workspace = true
indoc.workspace = true
http_client.workspace = true
indexed_docs.workspace = true
indoc.workspace = true
inventory.workspace = true
itertools.workspace = true
jsonschema.workspace = true
language.workspace = true
language_model.workspace = true
language_models.workspace = true
log.workspace = true
lsp.workspace = true
markdown.workspace = true
@@ -90,7 +86,6 @@ theme.workspace = true
time.workspace = true
time_format.workspace = true
ui.workspace = true
ui_input.workspace = true
urlencoding.workspace = true
util.workspace = true
uuid.workspace = true
@@ -98,6 +93,7 @@ watch.workspace = true
workspace-hack.workspace = true
workspace.workspace = true
zed_actions.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
assistant_tools.workspace = true

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,6 @@ use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
use anyhow::Context as _;
use assistant_tool::ToolUseStatus;
use audio::{Audio, Sound};
use cloud_llm_client::CompletionIntent;
use collections::{HashMap, HashSet};
use editor::actions::{MoveUp, Paste};
use editor::scroll::Autoscroll;
@@ -53,6 +52,7 @@ use util::ResultExt as _;
use util::markdown::MarkdownCodeBlock;
use workspace::{CollaboratorId, Workspace};
use zed_actions::assistant::OpenRulesLibrary;
use zed_llm_client::CompletionIntent;
const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
const EDIT_PREVIOUS_MESSAGE_MIN_LINES: usize = 1;
@@ -996,57 +996,30 @@ impl ActiveThread {
| ThreadEvent::SummaryChanged => {
self.save_thread(cx);
}
ThreadEvent::Stopped(reason) => {
match reason {
Ok(StopReason::EndTurn | StopReason::MaxTokens) => {
let used_tools = self.thread.read(cx).used_tools_since_last_user_message();
self.notify_with_sound(
if used_tools {
"Finished running tools"
} else {
"New message"
},
IconName::ZedAssistant,
window,
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".into(),
message: error_message.into(),
});
}
ThreadEvent::Stopped(reason) => match reason {
Ok(StopReason::EndTurn | StopReason::MaxTokens) => {
let used_tools = self.thread.read(cx).used_tools_since_last_user_message();
self.play_notification_sound(window, cx);
self.show_notification(
if used_tools {
"Finished running tools"
} else {
"New message"
},
IconName::ZedAssistant,
window,
cx,
);
}
}
_ => {}
},
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 => {
self.notify_with_sound(
self.play_notification_sound(window, cx);
self.show_notification(
"Consecutive tool use limit reached.",
IconName::Warning,
window,
@@ -1189,6 +1162,9 @@ impl ActiveThread {
self.save_thread(cx);
cx.notify();
}
ThreadEvent::RetriesFailed { message } => {
self.show_notification(message, ui::IconName::Warning, window, cx);
}
}
}
@@ -1243,17 +1219,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(
&mut self,
icon: IconName,
@@ -3202,10 +3167,7 @@ impl ActiveThread {
.border_color(self.tool_card_border_color(cx))
.rounded_b_lg()
.child(
div()
.min_w(rems_from_px(145.))
.child(LoadingLabel::new("Waiting for Confirmation").size(LabelSize::Small)
)
LoadingLabel::new("Waiting for Confirmation").size(LabelSize::Small)
)
.child(
h_flex()
@@ -3250,6 +3212,7 @@ impl ActiveThread {
},
))
})
.child(ui::Divider::vertical())
.child({
let tool_id = tool_use.id.clone();
Button::new("allow-tool-action", "Allow")
@@ -3724,11 +3687,8 @@ pub(crate) fn open_context(
AgentContextHandle::Thread(thread_context) => workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
let thread = thread_context.thread.clone();
window.defer(cx, move |window, cx| {
panel.update(cx, |panel, cx| {
panel.open_thread(thread, window, cx);
});
panel.update(cx, |panel, cx| {
panel.open_thread(thread_context.thread.clone(), window, cx);
});
}
}),
@@ -3736,11 +3696,8 @@ pub(crate) fn open_context(
AgentContextHandle::TextThread(text_thread_context) => {
workspace.update(cx, |workspace, cx| {
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
let context = text_thread_context.context.clone();
window.defer(cx, move |window, cx| {
panel.update(cx, |panel, cx| {
panel.open_prompt_editor(context, window, cx)
});
panel.update(cx, |panel, cx| {
panel.open_prompt_editor(text_thread_context.context.clone(), window, cx)
});
}
})
@@ -3895,7 +3852,7 @@ mod tests {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.set_default_model(
Some(ConfiguredModel {
provider: Arc::new(FakeLanguageModelProvider::default()),
provider: Arc::new(FakeLanguageModelProvider),
model,
}),
cx,
@@ -3979,7 +3936,7 @@ mod tests {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.set_default_model(
Some(ConfiguredModel {
provider: Arc::new(FakeLanguageModelProvider::default()),
provider: Arc::new(FakeLanguageModelProvider),
model: model.clone(),
}),
cx,

View File

@@ -1,4 +1,3 @@
mod add_llm_provider_modal;
mod configure_context_server_modal;
mod manage_profiles_modal;
mod tool_picker;
@@ -7,7 +6,6 @@ use std::{sync::Arc, time::Duration};
use agent_settings::AgentSettings;
use assistant_tool::{ToolSource, ToolWorkingSet};
use cloud_llm_client::Plan;
use collections::HashMap;
use context_server::ContextServerId;
use extension::ExtensionManifest;
@@ -28,8 +26,8 @@ use project::{
};
use settings::{Settings, update_settings_file};
use ui::{
Chip, ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu,
Scrollbar, ScrollbarState, Switch, SwitchColor, SwitchField, Tooltip, prelude::*,
ContextMenu, Disclosure, Divider, DividerColor, ElevationIndex, Indicator, PopoverMenu,
Scrollbar, ScrollbarState, Switch, SwitchColor, Tooltip, prelude::*,
};
use util::ResultExt as _;
use workspace::Workspace;
@@ -38,10 +36,7 @@ use zed_actions::ExtensionCategoryFilter;
pub(crate) use configure_context_server_modal::ConfigureContextServerModal;
pub(crate) use manage_profiles_modal::ManageProfilesModal;
use crate::{
AddContextServer,
agent_configuration::add_llm_provider_modal::{AddLlmProviderModal, LlmCompatibleProvider},
};
use crate::AddContextServer;
pub struct AgentConfiguration {
fs: Arc<dyn Fs>,
@@ -176,24 +171,7 @@ impl AgentConfiguration {
.copied()
.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).plan())
} else {
None
};
let is_signed_in = self
.workspace
.read_with(cx, |workspace, _| {
workspace.client().status().borrow().is_connected()
})
.unwrap_or(false);
v_flex()
.w_full()
.when(is_expanded, |this| this.mb_2())
.child(
div()
@@ -224,39 +202,20 @@ impl AgentConfiguration {
.hover(|hover| hover.bg(cx.theme().colors().element_hover))
.child(
h_flex()
.w_full()
.gap_2()
.child(
Icon::new(provider.icon())
.size(IconSize::Small)
.color(Color::Muted),
)
.child(
h_flex()
.w_full()
.gap_1()
.child(
Label::new(provider_name.clone())
.size(LabelSize::Large),
.child(Label::new(provider_name.clone()).size(LabelSize::Large))
.when(
provider.is_authenticated(cx) && !is_expanded,
|parent| {
parent.child(
Icon::new(IconName::Check).color(Color::Success),
)
.map(|this| {
if is_zed_provider && is_signed_in {
this.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(
@@ -317,78 +276,21 @@ impl AgentConfiguration {
let providers = LanguageModelRegistry::read_global(cx).providers();
v_flex()
.w_full()
.child(
h_flex()
v_flex()
.p(DynamicSpacing::Base16.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.pb_0()
.mb_2p5()
.items_start()
.justify_between()
.gap_0p5()
.child(Headline::new("LLM Providers"))
.child(
v_flex()
.w_full()
.gap_0p5()
.child(
h_flex()
.w_full()
.gap_2()
.justify_between()
.child(Headline::new("LLM Providers"))
.child(
PopoverMenu::new("add-provider-popover")
.trigger(
Button::new("add-provider", "Add Provider")
.icon_position(IconPosition::Start)
.icon(IconName::Plus)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.label_size(LabelSize::Small),
)
.anchor(gpui::Corner::TopRight)
.menu({
let workspace = self.workspace.clone();
move |window, cx| {
Some(ContextMenu::build(
window,
cx,
|menu, _window, _cx| {
menu.header("Compatible APIs").entry(
"OpenAI",
None,
{
let workspace =
workspace.clone();
move |window, cx| {
workspace
.update(cx, |workspace, cx| {
AddLlmProviderModal::toggle(
LlmCompatibleProvider::OpenAi,
workspace,
window,
cx,
);
})
.log_err();
}
},
)
},
))
}
}),
),
)
.child(
Label::new("Add at least one provider to use AI-powered features.")
.color(Color::Muted),
),
Label::new("Add at least one provider to use AI-powered features.")
.color(Color::Muted),
),
)
.child(
div()
.w_full()
.pl(DynamicSpacing::Base08.rems(cx))
.pr(DynamicSpacing::Base20.rems(cx))
.children(
@@ -401,80 +303,119 @@ impl AgentConfiguration {
fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let always_allow_tool_actions = AgentSettings::get_global(cx).always_allow_tool_actions;
let fs = self.fs.clone();
SwitchField::new(
"always-allow-tool-actions-switch",
"Allow running commands without asking for confirmation",
Some(
"The agent can perform potentially destructive actions without asking for your confirmation.".into(),
),
always_allow_tool_actions,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
settings.set_always_allow_tool_actions(allow);
});
},
)
h_flex()
.gap_4()
.justify_between()
.flex_wrap()
.child(
v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new("Allow running editing tools without asking for confirmation"))
.child(
Label::new(
"The agent can perform potentially destructive actions without asking for your confirmation.",
)
.color(Color::Muted),
),
)
.child(
Switch::new(
"always-allow-tool-actions-switch",
always_allow_tool_actions.into(),
)
.color(SwitchColor::Accent)
.on_click({
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_always_allow_tool_actions(allow);
},
);
}
}),
)
}
fn render_single_file_review(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let single_file_review = AgentSettings::get_global(cx).single_file_review;
let fs = self.fs.clone();
SwitchField::new(
"single-file-review",
"Enable single-file agent reviews",
Some("Agent edits are also displayed in single-file editors for review.".into()),
single_file_review,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
settings.set_single_file_review(allow);
});
},
)
h_flex()
.gap_4()
.justify_between()
.flex_wrap()
.child(
v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new("Enable single-file agent reviews"))
.child(
Label::new(
"Agent edits are also displayed in single-file editors for review.",
)
.color(Color::Muted),
),
)
.child(
Switch::new("single-file-review-switch", single_file_review.into())
.color(SwitchColor::Accent)
.on_click({
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_single_file_review(allow);
},
);
}
}),
)
}
fn render_sound_notification(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let play_sound_when_agent_done = AgentSettings::get_global(cx).play_sound_when_agent_done;
let fs = self.fs.clone();
SwitchField::new(
"sound-notification",
"Play sound when finished generating",
Some(
"Hear a notification sound when the agent is done generating changes or needs your input.".into(),
),
play_sound_when_agent_done,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
settings.set_play_sound_when_agent_done(allow);
});
},
)
}
fn render_modifier_to_send(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
let use_modifier_to_send = AgentSettings::get_global(cx).use_modifier_to_send;
let fs = self.fs.clone();
SwitchField::new(
"modifier-send",
"Use modifier to submit a message",
Some(
"Make a modifier (cmd-enter on macOS, ctrl-enter on Linux) required to send messages.".into(),
),
use_modifier_to_send,
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(fs.clone(), cx, move |settings, _| {
settings.set_use_modifier_to_send(allow);
});
},
)
h_flex()
.gap_4()
.justify_between()
.flex_wrap()
.child(
v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new("Play sound when finished generating"))
.child(
Label::new(
"Hear a notification sound when the agent is done generating changes or needs your input.",
)
.color(Color::Muted),
),
)
.child(
Switch::new("play-sound-notification-switch", play_sound_when_agent_done.into())
.color(SwitchColor::Accent)
.on_click({
let fs = self.fs.clone();
move |state, _window, cx| {
let allow = state == &ToggleState::Selected;
update_settings_file::<AgentSettings>(
fs.clone(),
cx,
move |settings, _| {
settings.set_play_sound_when_agent_done(allow);
},
);
}
}),
)
}
fn render_general_settings_section(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
@@ -488,38 +429,6 @@ impl AgentConfiguration {
.child(self.render_command_permission(cx))
.child(self.render_single_file_review(cx))
.child(self.render_sound_notification(cx))
.child(self.render_modifier_to_send(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::ZedFree => ("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(
@@ -582,7 +491,6 @@ impl AgentConfiguration {
category_filter: Some(
ExtensionCategoryFilter::ContextServers,
),
id: None,
}
.boxed_clone(),
cx,

View File

@@ -1,639 +0,0 @@
use std::sync::Arc;
use anyhow::Result;
use collections::HashSet;
use fs::Fs;
use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, Task};
use language_model::LanguageModelRegistry;
use language_models::{
AllLanguageModelSettings, OpenAiCompatibleSettingsContent,
provider::open_ai_compatible::AvailableModel,
};
use settings::update_settings_file;
use ui::{Banner, KeyBinding, Modal, ModalFooter, ModalHeader, Section, prelude::*};
use ui_input::SingleLineInput;
use workspace::{ModalView, Workspace};
#[derive(Clone, Copy)]
pub enum LlmCompatibleProvider {
OpenAi,
}
impl LlmCompatibleProvider {
fn name(&self) -> &'static str {
match self {
LlmCompatibleProvider::OpenAi => "OpenAI",
}
}
fn api_url(&self) -> &'static str {
match self {
LlmCompatibleProvider::OpenAi => "https://api.openai.com/v1",
}
}
}
struct AddLlmProviderInput {
provider_name: Entity<SingleLineInput>,
api_url: Entity<SingleLineInput>,
api_key: Entity<SingleLineInput>,
models: Vec<ModelInput>,
}
impl AddLlmProviderInput {
fn new(provider: LlmCompatibleProvider, window: &mut Window, cx: &mut App) -> Self {
let provider_name = single_line_input("Provider Name", provider.name(), None, window, cx);
let api_url = single_line_input("API URL", provider.api_url(), None, window, cx);
let api_key = single_line_input(
"API Key",
"000000000000000000000000000000000000000000000000",
None,
window,
cx,
);
Self {
provider_name,
api_url,
api_key,
models: vec![ModelInput::new(window, cx)],
}
}
fn add_model(&mut self, window: &mut Window, cx: &mut App) {
self.models.push(ModelInput::new(window, cx));
}
fn remove_model(&mut self, index: usize) {
self.models.remove(index);
}
}
struct ModelInput {
name: Entity<SingleLineInput>,
max_completion_tokens: Entity<SingleLineInput>,
max_output_tokens: Entity<SingleLineInput>,
max_tokens: Entity<SingleLineInput>,
}
impl ModelInput {
fn new(window: &mut Window, cx: &mut App) -> Self {
let model_name = single_line_input(
"Model Name",
"e.g. gpt-4o, claude-opus-4, gemini-2.5-pro",
None,
window,
cx,
);
let max_completion_tokens = single_line_input(
"Max Completion Tokens",
"200000",
Some("200000"),
window,
cx,
);
let max_output_tokens = single_line_input(
"Max Output Tokens",
"Max Output Tokens",
Some("32000"),
window,
cx,
);
let max_tokens = single_line_input("Max Tokens", "Max Tokens", Some("200000"), window, cx);
Self {
name: model_name,
max_completion_tokens,
max_output_tokens,
max_tokens,
}
}
fn parse(&self, cx: &App) -> Result<AvailableModel, SharedString> {
let name = self.name.read(cx).text(cx);
if name.is_empty() {
return Err(SharedString::from("Model Name cannot be empty"));
}
Ok(AvailableModel {
name,
display_name: None,
max_completion_tokens: Some(
self.max_completion_tokens
.read(cx)
.text(cx)
.parse::<u64>()
.map_err(|_| SharedString::from("Max Completion Tokens must be a number"))?,
),
max_output_tokens: Some(
self.max_output_tokens
.read(cx)
.text(cx)
.parse::<u64>()
.map_err(|_| SharedString::from("Max Output Tokens must be a number"))?,
),
max_tokens: self
.max_tokens
.read(cx)
.text(cx)
.parse::<u64>()
.map_err(|_| SharedString::from("Max Tokens must be a number"))?,
})
}
}
fn single_line_input(
label: impl Into<SharedString>,
placeholder: impl Into<SharedString>,
text: Option<&str>,
window: &mut Window,
cx: &mut App,
) -> Entity<SingleLineInput> {
cx.new(|cx| {
let input = SingleLineInput::new(window, cx, placeholder).label(label);
if let Some(text) = text {
input
.editor()
.update(cx, |editor, cx| editor.set_text(text, window, cx));
}
input
})
}
fn save_provider_to_settings(
input: &AddLlmProviderInput,
cx: &mut App,
) -> Task<Result<(), SharedString>> {
let provider_name: Arc<str> = input.provider_name.read(cx).text(cx).into();
if provider_name.is_empty() {
return Task::ready(Err("Provider Name cannot be empty".into()));
}
if LanguageModelRegistry::read_global(cx)
.providers()
.iter()
.any(|provider| {
provider.id().0.as_ref() == provider_name.as_ref()
|| provider.name().0.as_ref() == provider_name.as_ref()
})
{
return Task::ready(Err(
"Provider Name is already taken by another provider".into()
));
}
let api_url = input.api_url.read(cx).text(cx);
if api_url.is_empty() {
return Task::ready(Err("API URL cannot be empty".into()));
}
let api_key = input.api_key.read(cx).text(cx);
if api_key.is_empty() {
return Task::ready(Err("API Key cannot be empty".into()));
}
let mut models = Vec::new();
let mut model_names: HashSet<String> = HashSet::default();
for model in &input.models {
match model.parse(cx) {
Ok(model) => {
if !model_names.insert(model.name.clone()) {
return Task::ready(Err("Model Names must be unique".into()));
}
models.push(model)
}
Err(err) => return Task::ready(Err(err)),
}
}
let fs = <dyn Fs>::global(cx);
let task = cx.write_credentials(&api_url, "Bearer", api_key.as_bytes());
cx.spawn(async move |cx| {
task.await
.map_err(|_| "Failed to write API key to keychain")?;
cx.update(|cx| {
update_settings_file::<AllLanguageModelSettings>(fs, cx, |settings, _cx| {
settings.openai_compatible.get_or_insert_default().insert(
provider_name,
OpenAiCompatibleSettingsContent {
api_url,
available_models: models,
},
);
});
})
.ok();
Ok(())
})
}
pub struct AddLlmProviderModal {
provider: LlmCompatibleProvider,
input: AddLlmProviderInput,
focus_handle: FocusHandle,
last_error: Option<SharedString>,
}
impl AddLlmProviderModal {
pub fn toggle(
provider: LlmCompatibleProvider,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) {
workspace.toggle_modal(window, cx, |window, cx| Self::new(provider, window, cx));
}
fn new(provider: LlmCompatibleProvider, window: &mut Window, cx: &mut Context<Self>) -> Self {
Self {
input: AddLlmProviderInput::new(provider, window, cx),
provider,
last_error: None,
focus_handle: cx.focus_handle(),
}
}
fn confirm(&mut self, _: &menu::Confirm, _: &mut Window, cx: &mut Context<Self>) {
let task = save_provider_to_settings(&self.input, cx);
cx.spawn(async move |this, cx| {
let result = task.await;
this.update(cx, |this, cx| match result {
Ok(_) => {
cx.emit(DismissEvent);
}
Err(error) => {
this.last_error = Some(error);
cx.notify();
}
})
})
.detach_and_log_err(cx);
}
fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissEvent);
}
fn render_section(&self) -> Section {
Section::new()
.child(self.input.provider_name.clone())
.child(self.input.api_url.clone())
.child(self.input.api_key.clone())
}
fn render_model_section(&self, cx: &mut Context<Self>) -> Section {
Section::new().child(
v_flex()
.gap_2()
.child(
h_flex()
.justify_between()
.child(Label::new("Models").size(LabelSize::Small))
.child(
Button::new("add-model", "Add Model")
.icon(IconName::Plus)
.icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| {
this.input.add_model(window, cx);
cx.notify();
})),
),
)
.children(
self.input
.models
.iter()
.enumerate()
.map(|(ix, _)| self.render_model(ix, cx)),
),
)
}
fn render_model(&self, ix: usize, cx: &mut Context<Self>) -> impl IntoElement + use<> {
let has_more_than_one_model = self.input.models.len() > 1;
let model = &self.input.models[ix];
v_flex()
.p_2()
.gap_2()
.rounded_sm()
.border_1()
.border_dashed()
.border_color(cx.theme().colors().border.opacity(0.6))
.bg(cx.theme().colors().element_active.opacity(0.15))
.child(model.name.clone())
.child(
h_flex()
.gap_2()
.child(model.max_completion_tokens.clone())
.child(model.max_output_tokens.clone()),
)
.child(model.max_tokens.clone())
.when(has_more_than_one_model, |this| {
this.child(
Button::new(("remove-model", ix), "Remove Model")
.icon(IconName::Trash)
.icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
.label_size(LabelSize::Small)
.style(ButtonStyle::Outlined)
.full_width()
.on_click(cx.listener(move |this, _, _window, cx| {
this.input.remove_model(ix);
cx.notify();
})),
)
})
}
}
impl EventEmitter<DismissEvent> for AddLlmProviderModal {}
impl Focusable for AddLlmProviderModal {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl ModalView for AddLlmProviderModal {}
impl Render for AddLlmProviderModal {
fn render(&mut self, window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
div()
.id("add-llm-provider-modal")
.key_context("AddLlmProviderModal")
.w(rems(34.))
.elevation_3(cx)
.on_action(cx.listener(Self::cancel))
.capture_any_mouse_down(cx.listener(|this, _, window, cx| {
this.focus_handle(cx).focus(window);
}))
.child(
Modal::new("configure-context-server", None)
.header(ModalHeader::new().headline("Add LLM Provider").description(
match self.provider {
LlmCompatibleProvider::OpenAi => {
"This provider will use an OpenAI compatible API."
}
},
))
.when_some(self.last_error.clone(), |this, error| {
this.section(
Section::new().child(
Banner::new()
.severity(ui::Severity::Warning)
.child(div().text_xs().child(error)),
),
)
})
.child(
v_flex()
.id("modal_content")
.max_h_128()
.overflow_y_scroll()
.gap_2()
.child(self.render_section())
.child(self.render_model_section(cx)),
)
.footer(
ModalFooter::new().end_slot(
h_flex()
.gap_1()
.child(
Button::new("cancel", "Cancel")
.key_binding(
KeyBinding::for_action_in(
&menu::Cancel,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(cx.listener(|this, _event, window, cx| {
this.cancel(&menu::Cancel, window, cx)
})),
)
.child(
Button::new("save-server", "Save Provider")
.key_binding(
KeyBinding::for_action_in(
&menu::Confirm,
&focus_handle,
window,
cx,
)
.map(|kb| kb.size(rems_from_px(12.))),
)
.on_click(cx.listener(|this, _event, window, cx| {
this.confirm(&menu::Confirm, window, cx)
})),
),
),
),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use editor::EditorSettings;
use fs::FakeFs;
use gpui::{TestAppContext, VisualTestContext};
use language::language_settings;
use language_model::{
LanguageModelProviderId, LanguageModelProviderName,
fake_provider::FakeLanguageModelProvider,
};
use project::Project;
use settings::{Settings as _, SettingsStore};
use util::path;
#[gpui::test]
async fn test_save_provider_invalid_inputs(cx: &mut TestAppContext) {
let cx = setup_test(cx).await;
assert_eq!(
save_provider_validation_errors("", "someurl", "somekey", vec![], cx,).await,
Some("Provider Name cannot be empty".into())
);
assert_eq!(
save_provider_validation_errors("someprovider", "", "somekey", vec![], cx,).await,
Some("API URL cannot be empty".into())
);
assert_eq!(
save_provider_validation_errors("someprovider", "someurl", "", vec![], cx,).await,
Some("API Key cannot be empty".into())
);
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"somekey",
vec![("", "200000", "200000", "32000")],
cx,
)
.await,
Some("Model Name cannot be empty".into())
);
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"somekey",
vec![("somemodel", "abc", "200000", "32000")],
cx,
)
.await,
Some("Max Tokens must be a number".into())
);
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"somekey",
vec![("somemodel", "200000", "abc", "32000")],
cx,
)
.await,
Some("Max Completion Tokens must be a number".into())
);
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"somekey",
vec![("somemodel", "200000", "200000", "abc")],
cx,
)
.await,
Some("Max Output Tokens must be a number".into())
);
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"somekey",
vec![
("somemodel", "200000", "200000", "32000"),
("somemodel", "200000", "200000", "32000"),
],
cx,
)
.await,
Some("Model Names must be unique".into())
);
}
#[gpui::test]
async fn test_save_provider_name_conflict(cx: &mut TestAppContext) {
let cx = setup_test(cx).await;
cx.update(|_window, cx| {
LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
registry.register_provider(
FakeLanguageModelProvider::new(
LanguageModelProviderId::new("someprovider"),
LanguageModelProviderName::new("Some Provider"),
),
cx,
);
});
});
assert_eq!(
save_provider_validation_errors(
"someprovider",
"someurl",
"someapikey",
vec![("somemodel", "200000", "200000", "32000")],
cx,
)
.await,
Some("Provider Name is already taken by another provider".into())
);
}
async fn setup_test(cx: &mut TestAppContext) -> &mut VisualTestContext {
cx.update(|cx| {
let store = SettingsStore::test(cx);
cx.set_global(store);
workspace::init_settings(cx);
Project::init_settings(cx);
theme::init(theme::LoadThemes::JustBase, cx);
language_settings::init(cx);
EditorSettings::register(cx);
language_model::init_settings(cx);
language_models::init_settings(cx);
});
let fs = FakeFs::new(cx.executor());
cx.update(|cx| <dyn Fs>::set_global(fs.clone(), cx));
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
let (_, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
cx
}
async fn save_provider_validation_errors(
provider_name: &str,
api_url: &str,
api_key: &str,
models: Vec<(&str, &str, &str, &str)>,
cx: &mut VisualTestContext,
) -> Option<SharedString> {
fn set_text(
input: &Entity<SingleLineInput>,
text: &str,
window: &mut Window,
cx: &mut App,
) {
input.update(cx, |input, cx| {
input.editor().update(cx, |editor, cx| {
editor.set_text(text, window, cx);
});
});
}
let task = cx.update(|window, cx| {
let mut input = AddLlmProviderInput::new(LlmCompatibleProvider::OpenAi, window, cx);
set_text(&input.provider_name, provider_name, window, cx);
set_text(&input.api_url, api_url, window, cx);
set_text(&input.api_key, api_key, window, cx);
for (i, (name, max_tokens, max_completion_tokens, max_output_tokens)) in
models.iter().enumerate()
{
if i >= input.models.len() {
input.models.push(ModelInput::new(window, cx));
}
let model = &mut input.models[i];
set_text(&model.name, name, window, cx);
set_text(&model.max_tokens, max_tokens, window, cx);
set_text(
&model.max_completion_tokens,
max_completion_tokens,
window,
cx,
);
set_text(&model.max_output_tokens, max_output_tokens, window, cx);
}
save_provider_to_settings(&input, cx)
});
task.await.err()
}
}

View File

@@ -1,5 +1,4 @@
use std::{
path::PathBuf,
sync::{Arc, Mutex},
time::Duration,
};
@@ -189,7 +188,7 @@ fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)
}
None => (
"some-mcp-server".to_string(),
PathBuf::new(),
"".to_string(),
"[]".to_string(),
"{}".to_string(),
),
@@ -200,14 +199,13 @@ fn context_server_input(existing: Option<(ContextServerId, ContextServerCommand)
/// The name of your MCP server
"{name}": {{
/// The command which runs the MCP server
"command": "{}",
"command": "{command}",
/// The arguments to pass to the MCP server
"args": {args},
/// The environment variables to set
"env": {env}
}}
}}"#,
command.display()
}}"#
)
}

View File

@@ -1,5 +1,5 @@
use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll};
use acp_thread::{AcpThread, AcpThreadEvent};
use acp::{AcpThread, AcpThreadEvent};
use agent::{Thread, ThreadEvent, ThreadSummary};
use agent_settings::AgentSettings;
use anyhow::Result;
@@ -30,7 +30,7 @@ use std::{
sync::Arc,
time::Duration,
};
use ui::{ButtonSize, IconButtonShape, KeyBinding, Tooltip, prelude::*, vertical_divider};
use ui::{IconButtonShape, KeyBinding, Tooltip, prelude::*, vertical_divider};
use util::ResultExt;
use workspace::{
Item, ItemHandle, ItemNavHistory, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
@@ -81,7 +81,7 @@ impl AgentDiffThread {
match self {
AgentDiffThread::Native(thread) => thread.read(cx).is_generating(),
AgentDiffThread::AcpThread(thread) => {
thread.read(cx).status() == acp_thread::ThreadStatus::Generating
thread.read(cx).status() == acp::ThreadStatus::Generating
}
}
}
@@ -791,32 +791,22 @@ fn render_diff_hunk_controls(
) -> AnyElement {
let editor = editor.clone();
// Get controls positioning from editor state
let controls_above = editor.read(cx).diff_hunk_controls_above();
let mut container = h_flex()
h_flex()
.h(line_height)
.mr_0p5()
.gap_1()
.px_0p5()
.py_0p5()
.pb_1()
.border_x_1()
.border_b_1()
.border_color(cx.theme().colors().border)
.rounded_b_md()
.bg(cx.theme().colors().editor_background)
.gap_1()
.block_mouse_except_scroll()
.shadow_md();
if controls_above {
container = container.border_t_1().rounded_t_md();
} else {
container = container.border_b_1().rounded_b_md();
}
container
.shadow_md()
.children(vec![
Button::new(("reject", row as u64), "Reject")
.size(ButtonSize::Compact)
.disabled(is_created_file)
.key_binding(
KeyBinding::for_action_in(
@@ -845,7 +835,6 @@ fn render_diff_hunk_controls(
}
}),
Button::new(("keep", row as u64), "Keep")
.size(ButtonSize::Compact)
.key_binding(
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), window, cx)
.map(|kb| kb.size(rems_from_px(12.))),
@@ -1499,6 +1488,7 @@ impl AgentDiff {
| ThreadEvent::ToolConfirmationNeeded
| ThreadEvent::ToolUseLimitReached
| ThreadEvent::CancelEditing
| ThreadEvent::RetriesFailed { .. }
| ThreadEvent::ProfileChanged => {}
}
}
@@ -1517,7 +1507,8 @@ impl AgentDiff {
.read(cx)
.entries()
.last()
.map_or(false, |entry| entry.diffs().next().is_some())
.and_then(|entry| entry.diff())
.is_some()
{
self.update_reviewing_editors(workspace, window, cx);
}
@@ -1527,14 +1518,12 @@ impl AgentDiff {
.read(cx)
.entries()
.get(*ix)
.map_or(false, |entry| entry.diffs().next().is_some())
.and_then(|entry| entry.diff())
.is_some()
{
self.update_reviewing_editors(workspace, window, cx);
}
}
AcpThreadEvent::Stopped
| AcpThreadEvent::ToolAuthorizationRequired
| AcpThreadEvent::Error => {}
}
}

View File

@@ -1,6 +1,8 @@
use crate::{
ModelUsageContext,
language_model_selector::{LanguageModelSelector, language_model_selector},
language_model_selector::{
LanguageModelSelector, ToggleModelSelector, language_model_selector,
},
};
use agent_settings::AgentSettings;
use fs::Fs;
@@ -10,7 +12,6 @@ use picker::popover_menu::PickerPopoverMenu;
use settings::update_settings_file;
use std::sync::Arc;
use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
use zed_actions::agent::ToggleModelSelector;
pub struct AgentModelSelector {
selector: Entity<LanguageModelSelector>,
@@ -95,18 +96,22 @@ impl Render for AgentModelSelector {
let model_name = model
.as_ref()
.map(|model| model.model.name().0)
.unwrap_or_else(|| SharedString::from("Select a Model"));
let provider_icon = model.as_ref().map(|model| model.provider.icon());
.unwrap_or_else(|| SharedString::from("No model selected"));
let provider_icon = model
.as_ref()
.map(|model| model.provider.icon())
.unwrap_or_else(|| IconName::Ai);
let focus_handle = self.focus_handle.clone();
PickerPopoverMenu::new(
self.selector.clone(),
ButtonLike::new("active-model")
.when_some(provider_icon, |this, icon| {
this.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall))
})
.child(
Icon::new(provider_icon)
.color(Color::Muted)
.size(IconSize::XSmall),
)
.child(
Label::new(model_name)
.color(Color::Muted)

File diff suppressed because it is too large Load Diff

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