ACP (#34030)
Implements an ACP client that can be used from the agent panel
68
Cargo.lock
generated
|
@ -2,6 +2,33 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "acp"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"agent_servers",
|
||||||
|
"agentic-coding-protocol",
|
||||||
|
"anyhow",
|
||||||
|
"async-pipe",
|
||||||
|
"buffer_diff",
|
||||||
|
"editor",
|
||||||
|
"env_logger 0.11.8",
|
||||||
|
"futures 0.3.31",
|
||||||
|
"gpui",
|
||||||
|
"indoc",
|
||||||
|
"itertools 0.14.0",
|
||||||
|
"language",
|
||||||
|
"markdown",
|
||||||
|
"project",
|
||||||
|
"serde_json",
|
||||||
|
"settings",
|
||||||
|
"smol",
|
||||||
|
"tempfile",
|
||||||
|
"ui",
|
||||||
|
"util",
|
||||||
|
"workspace-hack",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "activity_indicator"
|
name = "activity_indicator"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -107,6 +134,24 @@ dependencies = [
|
||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "agent_servers"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"collections",
|
||||||
|
"futures 0.3.31",
|
||||||
|
"gpui",
|
||||||
|
"paths",
|
||||||
|
"project",
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
|
"settings",
|
||||||
|
"util",
|
||||||
|
"which 6.0.3",
|
||||||
|
"workspace-hack",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "agent_settings"
|
name = "agent_settings"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -130,8 +175,11 @@ dependencies = [
|
||||||
name = "agent_ui"
|
name = "agent_ui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"acp",
|
||||||
"agent",
|
"agent",
|
||||||
|
"agent_servers",
|
||||||
"agent_settings",
|
"agent_settings",
|
||||||
|
"agentic-coding-protocol",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assistant_context",
|
"assistant_context",
|
||||||
"assistant_slash_command",
|
"assistant_slash_command",
|
||||||
|
@ -191,6 +239,7 @@ dependencies = [
|
||||||
"settings",
|
"settings",
|
||||||
"smol",
|
"smol",
|
||||||
"streaming_diff",
|
"streaming_diff",
|
||||||
|
"task",
|
||||||
"telemetry",
|
"telemetry",
|
||||||
"telemetry_events",
|
"telemetry_events",
|
||||||
"terminal",
|
"terminal",
|
||||||
|
@ -212,6 +261,22 @@ dependencies = [
|
||||||
"zed_llm_client",
|
"zed_llm_client",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "agentic-coding-protocol"
|
||||||
|
version = "0.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b962eee17ee3924870d9b9d28cc8b6dcb5421e4d4e81cd864226374a122ceed1"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"chrono",
|
||||||
|
"futures 0.3.31",
|
||||||
|
"log",
|
||||||
|
"parking_lot",
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.7.8"
|
version = "0.7.8"
|
||||||
|
@ -14078,6 +14143,7 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe8c9d1c68d67dd9f97ecbc6f932b60eb289c5dbddd8aa1405484a8fd2fcd984"
|
checksum = "fe8c9d1c68d67dd9f97ecbc6f932b60eb289c5dbddd8aa1405484a8fd2fcd984"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"ref-cast",
|
"ref-cast",
|
||||||
|
@ -19579,6 +19645,7 @@ dependencies = [
|
||||||
"rustix 1.0.7",
|
"rustix 1.0.7",
|
||||||
"rustls 0.23.26",
|
"rustls 0.23.26",
|
||||||
"rustls-webpki 0.103.1",
|
"rustls-webpki 0.103.1",
|
||||||
|
"schemars",
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
"sea-orm",
|
"sea-orm",
|
||||||
"sea-query-binder",
|
"sea-query-binder",
|
||||||
|
@ -19976,6 +20043,7 @@ version = "0.196.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activity_indicator",
|
"activity_indicator",
|
||||||
"agent",
|
"agent",
|
||||||
|
"agent_servers",
|
||||||
"agent_settings",
|
"agent_settings",
|
||||||
"agent_ui",
|
"agent_ui",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/activity_indicator",
|
"crates/activity_indicator",
|
||||||
|
"crates/acp",
|
||||||
"crates/agent_ui",
|
"crates/agent_ui",
|
||||||
"crates/agent",
|
"crates/agent",
|
||||||
"crates/agent_settings",
|
"crates/agent_settings",
|
||||||
|
"crates/agent_servers",
|
||||||
"crates/anthropic",
|
"crates/anthropic",
|
||||||
"crates/askpass",
|
"crates/askpass",
|
||||||
"crates/assets",
|
"crates/assets",
|
||||||
|
@ -216,10 +218,12 @@ edition = "2024"
|
||||||
# Workspace member crates
|
# Workspace member crates
|
||||||
#
|
#
|
||||||
|
|
||||||
activity_indicator = { path = "crates/activity_indicator" }
|
acp = { path = "crates/acp" }
|
||||||
agent = { path = "crates/agent" }
|
agent = { path = "crates/agent" }
|
||||||
|
activity_indicator = { path = "crates/activity_indicator" }
|
||||||
agent_ui = { path = "crates/agent_ui" }
|
agent_ui = { path = "crates/agent_ui" }
|
||||||
agent_settings = { path = "crates/agent_settings" }
|
agent_settings = { path = "crates/agent_settings" }
|
||||||
|
agent_servers = { path = "crates/agent_servers" }
|
||||||
ai = { path = "crates/ai" }
|
ai = { path = "crates/ai" }
|
||||||
anthropic = { path = "crates/anthropic" }
|
anthropic = { path = "crates/anthropic" }
|
||||||
askpass = { path = "crates/askpass" }
|
askpass = { path = "crates/askpass" }
|
||||||
|
@ -400,6 +404,7 @@ zlog_settings = { path = "crates/zlog_settings" }
|
||||||
# External crates
|
# External crates
|
||||||
#
|
#
|
||||||
|
|
||||||
|
agentic-coding-protocol = "0.0.5"
|
||||||
aho-corasick = "1.1"
|
aho-corasick = "1.1"
|
||||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
|
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
|
||||||
any_vec = "0.14"
|
any_vec = "0.14"
|
||||||
|
|
1
assets/icons/ai_gemini.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<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>
|
After Width: | Height: | Size: 402 B |
3
assets/icons/tool_bulb.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.4174 10.2159C10.5454 9.58974 10.4174 9.57261 11.3762 8.46959C11.9337 7.82822 12.335 7.09214 12.335 6.27818C12.335 5.28184 11.9309 4.32631 11.2118 3.62179C10.4926 2.91728 9.5171 2.52148 8.50001 2.52148C7.48291 2.52148 6.50748 2.91728 5.78828 3.62179C5.06909 4.32631 4.66504 5.28184 4.66504 6.27818C4.66504 6.9043 4.79288 7.65565 5.62379 8.46959C6.58253 9.59098 6.45474 9.58974 6.58257 10.2159M10.4174 10.2159L10.4174 12.2989C10.4174 12.9504 9.87836 13.4786 9.21329 13.4786H7.78674C7.12167 13.4786 6.58253 12.9504 6.58253 12.2989L6.58257 10.2159M10.4174 10.2159H8.50001H6.58257" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 776 B |
3
assets/icons/tool_folder.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12.4 12.5C12.6917 12.5 12.9715 12.3884 13.1778 12.1899C13.3841 11.9913 13.5 11.722 13.5 11.4412V6.14706C13.5 5.86624 13.3841 5.59693 13.1778 5.39836C12.9715 5.19979 12.6917 5.08824 12.4 5.08824H8.055C7.87103 5.08997 7.68955 5.04726 7.52717 4.96402C7.36478 4.88078 7.22668 4.75967 7.1255 4.61176L6.68 3.97647C6.57984 3.83007 6.44349 3.7099 6.28317 3.62674C6.12286 3.54358 5.94361 3.50003 5.7615 3.5H3.6C3.30826 3.5 3.02847 3.61155 2.82218 3.81012C2.61589 4.00869 2.5 4.27801 2.5 4.55882V11.4412C2.5 11.722 2.61589 11.9913 2.82218 12.1899C3.02847 12.3884 3.30826 12.5 3.6 12.5H12.4Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 778 B |
5
assets/icons/tool_hammer.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9 8.5L4.94864 12.6222C4.71647 12.8544 4.40157 12.9848 4.07323 12.9848C3.74488 12.9848 3.42999 12.8544 3.19781 12.6222C2.96564 12.39 2.83521 12.0751 2.83521 11.7468C2.83521 11.4185 2.96564 11.1036 3.19781 10.8714L7.5 6.5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M10.8352 9.98474L13.8352 6.98474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12.8352 7.42495L11.7634 6.4298C11.5533 6.23484 11.4353 5.97039 11.4352 5.69462V5.08526L10.1696 3.91022C9.54495 3.33059 8.69961 3.00261 7.81649 2.99722L5.83521 2.98474L6.35041 3.41108C6.71634 3.71233 7.00935 4.08216 7.21013 4.4962C7.4109 4.91024 7.51488 5.35909 7.51521 5.81316L7.5 6.5L9 8.5L9.5 8C9.5 8 9.87337 7.79457 10.0834 7.98959L11.1552 8.98474" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 988 B |
4
assets/icons/tool_pencil.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12.5871 5.40582C12.8514 5.14152 13 4.78304 13 4.40922C13 4.03541 12.8516 3.67688 12.5873 3.41252C12.323 3.14816 11.9645 2.99962 11.5907 2.99957C11.2169 2.99953 10.8584 3.14798 10.594 3.41227L3.92098 10.0869C3.80488 10.2027 3.71903 10.3452 3.67097 10.5019L3.01047 12.678C2.99754 12.7212 2.99657 12.7672 3.00764 12.8109C3.01872 12.8547 3.04143 12.8946 3.07337 12.9265C3.1053 12.9584 3.14528 12.981 3.18905 12.992C3.23282 13.003 3.27875 13.002 3.32197 12.989L5.49849 12.329C5.65508 12.2813 5.79758 12.196 5.91349 12.0805L12.5871 5.40582Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9 5L11 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 835 B |
4
assets/icons/tool_regex.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4.57132 13.7143C5.20251 13.7143 5.71418 13.2026 5.71418 12.5714C5.71418 11.9403 5.20251 11.4286 4.57132 11.4286C3.94014 11.4286 3.42847 11.9403 3.42847 12.5714C3.42847 13.2026 3.94014 13.7143 4.57132 13.7143Z" fill="black"/>
|
||||||
|
<path d="M10.2856 2.85712V5.71426M10.2856 5.71426V8.5714M10.2856 5.71426H13.1428M10.2856 5.71426H7.42847M10.2856 5.71426L12.1904 3.80949M10.2856 5.71426L8.38084 7.61906M10.2856 5.71426L12.1904 7.61906M10.2856 5.71426L8.38084 3.80949" stroke="black" stroke-width="1.5" stroke-linecap="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 631 B |
4
assets/icons/tool_search.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13 13L11 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.5 12C9.98528 12 12 9.98528 12 7.5C12 5.01472 9.98528 3 7.5 3C5.01472 3 3 5.01472 3 7.5C3 9.98528 5.01472 12 7.5 12Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 421 B |
5
assets/icons/tool_terminal.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5.99487 8.44023L7.32821 7.10689L5.99487 5.77356" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7.33838 10.2264H10.005" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.8889 3H4.11111C3.49746 3 3 3.49746 3 4.11111V11.8889C3 12.5025 3.49746 13 4.11111 13H11.8889C12.5025 13 13 12.5025 13 11.8889V4.11111C13 3.49746 12.5025 3 11.8889 3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 625 B |
17
assets/icons/tool_web.svg
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_2663_433)">
|
||||||
|
<mask id="mask0_2663_433" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
|
||||||
|
<path d="M16 0H0V16H16V0Z" fill="white"/>
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_2663_433)">
|
||||||
|
<path d="M8 13C10.7614 13 13 10.7614 13 7.99999C13 5.23857 10.7614 3 8 3C5.23857 3 3 5.23857 3 7.99999C3 10.7614 5.23857 13 8 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M8 3C6.71611 4.34807 6 6.13836 6 7.99999C6 9.86163 6.71611 11.6519 8 13C9.28387 11.6519 10 9.86163 10 7.99999C10 6.13836 9.28387 4.34807 8 3Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3 8H13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_2663_433">
|
||||||
|
<rect width="16" height="16" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1,001 B |
|
@ -306,6 +306,15 @@
|
||||||
"enter": "agent::AcceptSuggestedContext"
|
"enter": "agent::AcceptSuggestedContext"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "AcpThread > Editor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"enter": "agent::Chat",
|
||||||
|
"up": "agent::PreviousHistoryMessage",
|
||||||
|
"down": "agent::NextHistoryMessage"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ThreadHistory",
|
"context": "ThreadHistory",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|
|
@ -357,6 +357,15 @@
|
||||||
"ctrl--": "pane::GoBack"
|
"ctrl--": "pane::GoBack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "AcpThread > Editor",
|
||||||
|
"use_key_equivalents": true,
|
||||||
|
"bindings": {
|
||||||
|
"enter": "agent::Chat",
|
||||||
|
"up": "agent::PreviousHistoryMessage",
|
||||||
|
"down": "agent::NextHistoryMessage"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "ThreadHistory",
|
"context": "ThreadHistory",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
|
|
|
@ -1855,6 +1855,8 @@
|
||||||
"read_ssh_config": true,
|
"read_ssh_config": true,
|
||||||
// Configures context servers for use by the agent.
|
// Configures context servers for use by the agent.
|
||||||
"context_servers": {},
|
"context_servers": {},
|
||||||
|
// Configures agent servers available in the agent panel.
|
||||||
|
"agent_servers": {},
|
||||||
"debugger": {
|
"debugger": {
|
||||||
"stepping_granularity": "line",
|
"stepping_granularity": "line",
|
||||||
"save_breakpoints": true,
|
"save_breakpoints": true,
|
||||||
|
|
46
crates/acp/Cargo.toml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
[package]
|
||||||
|
name = "acp"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/acp.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[features]
|
||||||
|
test-support = ["gpui/test-support", "project/test-support"]
|
||||||
|
gemini = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
agent_servers.workspace = true
|
||||||
|
agentic-coding-protocol.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
buffer_diff.workspace = true
|
||||||
|
editor.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
|
gpui.workspace = true
|
||||||
|
itertools.workspace = true
|
||||||
|
language.workspace = true
|
||||||
|
markdown.workspace = true
|
||||||
|
project.workspace = true
|
||||||
|
settings.workspace = true
|
||||||
|
smol.workspace = true
|
||||||
|
ui.workspace = true
|
||||||
|
util.workspace = true
|
||||||
|
workspace-hack.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
async-pipe.workspace = true
|
||||||
|
env_logger.workspace = true
|
||||||
|
gpui = { workspace = true, "features" = ["test-support"] }
|
||||||
|
indoc.workspace = true
|
||||||
|
project = { workspace = true, "features" = ["test-support"] }
|
||||||
|
serde_json.workspace = true
|
||||||
|
tempfile.workspace = true
|
||||||
|
util.workspace = true
|
||||||
|
settings.workspace = true
|
1
crates/acp/LICENSE-GPL
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../LICENSE-GPL
|
1625
crates/acp/src/acp.rs
Normal file
27
crates/agent_servers/Cargo.toml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
[package]
|
||||||
|
name = "agent_servers"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/agent_servers.rs"
|
||||||
|
doctest = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
collections.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
|
gpui.workspace = true
|
||||||
|
paths.workspace = true
|
||||||
|
project.workspace = true
|
||||||
|
schemars.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
settings.workspace = true
|
||||||
|
util.workspace = true
|
||||||
|
which.workspace = true
|
||||||
|
workspace-hack.workspace = true
|
1
crates/agent_servers/LICENSE-GPL
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../LICENSE-GPL
|
231
crates/agent_servers/src/agent_servers.rs
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
use std::{
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context as _, Result};
|
||||||
|
use collections::HashMap;
|
||||||
|
use gpui::{App, AsyncApp, Entity, SharedString};
|
||||||
|
use project::Project;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use settings::{Settings, SettingsSources, SettingsStore};
|
||||||
|
use util::{ResultExt, paths};
|
||||||
|
|
||||||
|
pub fn init(cx: &mut App) {
|
||||||
|
AllAgentServersSettings::register(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug)]
|
||||||
|
pub struct AllAgentServersSettings {
|
||||||
|
gemini: Option<AgentServerSettings>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug)]
|
||||||
|
pub struct AgentServerSettings {
|
||||||
|
#[serde(flatten)]
|
||||||
|
command: AgentServerCommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)]
|
||||||
|
pub struct AgentServerCommand {
|
||||||
|
#[serde(rename = "command")]
|
||||||
|
pub path: PathBuf,
|
||||||
|
#[serde(default)]
|
||||||
|
pub args: Vec<String>,
|
||||||
|
pub env: Option<HashMap<String, String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Gemini;
|
||||||
|
|
||||||
|
pub struct AgentServerVersion {
|
||||||
|
pub current_version: SharedString,
|
||||||
|
pub supported: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AgentServer: Send {
|
||||||
|
fn command(
|
||||||
|
&self,
|
||||||
|
project: &Entity<Project>,
|
||||||
|
cx: &mut AsyncApp,
|
||||||
|
) -> impl Future<Output = Result<AgentServerCommand>>;
|
||||||
|
|
||||||
|
fn version(
|
||||||
|
&self,
|
||||||
|
command: &AgentServerCommand,
|
||||||
|
) -> impl Future<Output = Result<AgentServerVersion>> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GEMINI_ACP_ARG: &str = "--acp";
|
||||||
|
|
||||||
|
impl AgentServer for Gemini {
|
||||||
|
async fn command(
|
||||||
|
&self,
|
||||||
|
project: &Entity<Project>,
|
||||||
|
cx: &mut AsyncApp,
|
||||||
|
) -> Result<AgentServerCommand> {
|
||||||
|
let custom_command = cx.read_global(|settings: &SettingsStore, _| {
|
||||||
|
let settings = settings.get::<AllAgentServersSettings>(None);
|
||||||
|
settings
|
||||||
|
.gemini
|
||||||
|
.as_ref()
|
||||||
|
.map(|gemini_settings| AgentServerCommand {
|
||||||
|
path: gemini_settings.command.path.clone(),
|
||||||
|
args: gemini_settings
|
||||||
|
.command
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(std::iter::once(GEMINI_ACP_ARG.into()))
|
||||||
|
.collect(),
|
||||||
|
env: gemini_settings.command.env.clone(),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(custom_command) = custom_command {
|
||||||
|
return Ok(custom_command);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(path) = find_bin_in_path("gemini", project, cx).await {
|
||||||
|
return Ok(AgentServerCommand {
|
||||||
|
path,
|
||||||
|
args: vec![GEMINI_ACP_ARG.into()],
|
||||||
|
env: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let (fs, node_runtime) = project.update(cx, |project, _| {
|
||||||
|
(project.fs().clone(), project.node_runtime().cloned())
|
||||||
|
})?;
|
||||||
|
let node_runtime = node_runtime.context("gemini not found on path")?;
|
||||||
|
|
||||||
|
let directory = ::paths::agent_servers_dir().join("gemini");
|
||||||
|
fs.create_dir(&directory).await?;
|
||||||
|
node_runtime
|
||||||
|
.npm_install_packages(&directory, &[("@google/gemini-cli", "latest")])
|
||||||
|
.await?;
|
||||||
|
let path = directory.join("node_modules/.bin/gemini");
|
||||||
|
|
||||||
|
Ok(AgentServerCommand {
|
||||||
|
path,
|
||||||
|
args: vec![GEMINI_ACP_ARG.into()],
|
||||||
|
env: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn version(&self, command: &AgentServerCommand) -> Result<AgentServerVersion> {
|
||||||
|
let version_fut = util::command::new_smol_command(&command.path)
|
||||||
|
.args(command.args.iter())
|
||||||
|
.arg("--version")
|
||||||
|
.kill_on_drop(true)
|
||||||
|
.output();
|
||||||
|
|
||||||
|
let help_fut = util::command::new_smol_command(&command.path)
|
||||||
|
.args(command.args.iter())
|
||||||
|
.arg("--help")
|
||||||
|
.kill_on_drop(true)
|
||||||
|
.output();
|
||||||
|
|
||||||
|
let (version_output, help_output) = futures::future::join(version_fut, help_fut).await;
|
||||||
|
|
||||||
|
let current_version = String::from_utf8(version_output?.stdout)?.into();
|
||||||
|
let supported = String::from_utf8(help_output?.stdout)?.contains(GEMINI_ACP_ARG);
|
||||||
|
|
||||||
|
Ok(AgentServerVersion {
|
||||||
|
current_version,
|
||||||
|
supported,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_bin_in_path(
|
||||||
|
bin_name: &'static str,
|
||||||
|
project: &Entity<Project>,
|
||||||
|
cx: &mut AsyncApp,
|
||||||
|
) -> Option<PathBuf> {
|
||||||
|
let (env_task, root_dir) = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
let worktree = project.visible_worktrees(cx).next();
|
||||||
|
match worktree {
|
||||||
|
Some(worktree) => {
|
||||||
|
let env_task = project.environment().update(cx, |env, cx| {
|
||||||
|
env.get_worktree_environment(worktree.clone(), cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
let path = worktree.read(cx).abs_path();
|
||||||
|
(env_task, path)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let path: Arc<Path> = paths::home_dir().as_path().into();
|
||||||
|
let env_task = project.environment().update(cx, |env, cx| {
|
||||||
|
env.get_directory_environment(path.clone(), cx)
|
||||||
|
});
|
||||||
|
(env_task, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.log_err()?;
|
||||||
|
|
||||||
|
cx.background_executor()
|
||||||
|
.spawn(async move {
|
||||||
|
let which_result = if cfg!(windows) {
|
||||||
|
which::which(bin_name)
|
||||||
|
} else {
|
||||||
|
let env = env_task.await.unwrap_or_default();
|
||||||
|
let shell_path = env.get("PATH").cloned();
|
||||||
|
which::which_in(bin_name, shell_path.as_ref(), root_dir.as_ref())
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(which::Error::CannotFindBinaryPath) = which_result {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
which_result.log_err()
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for AgentServerCommand {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let filtered_env = self.env.as_ref().map(|env| {
|
||||||
|
env.iter()
|
||||||
|
.map(|(k, v)| {
|
||||||
|
(
|
||||||
|
k,
|
||||||
|
if util::redact::should_redact(k) {
|
||||||
|
"[REDACTED]"
|
||||||
|
} else {
|
||||||
|
v
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
|
f.debug_struct("AgentServerCommand")
|
||||||
|
.field("path", &self.path)
|
||||||
|
.field("args", &self.args)
|
||||||
|
.field("env", &filtered_env)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl settings::Settings for AllAgentServersSettings {
|
||||||
|
const KEY: Option<&'static str> = Some("agent_servers");
|
||||||
|
|
||||||
|
type FileContent = Self;
|
||||||
|
|
||||||
|
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||||
|
let mut settings = AllAgentServersSettings::default();
|
||||||
|
|
||||||
|
for value in sources.defaults_and_customizations() {
|
||||||
|
if value.gemini.is_some() {
|
||||||
|
settings.gemini = value.gemini.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||||
|
}
|
|
@ -13,14 +13,14 @@ path = "src/agent_ui.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
test-support = [
|
test-support = ["gpui/test-support", "language/test-support"]
|
||||||
"gpui/test-support",
|
|
||||||
"language/test-support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
acp.workspace = true
|
||||||
agent.workspace = true
|
agent.workspace = true
|
||||||
|
agentic-coding-protocol.workspace = true
|
||||||
agent_settings.workspace = true
|
agent_settings.workspace = true
|
||||||
|
agent_servers.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
assistant_context.workspace = true
|
assistant_context.workspace = true
|
||||||
assistant_slash_command.workspace = true
|
assistant_slash_command.workspace = true
|
||||||
|
@ -76,6 +76,7 @@ serde_json_lenient.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
smol.workspace = true
|
smol.workspace = true
|
||||||
streaming_diff.workspace = true
|
streaming_diff.workspace = true
|
||||||
|
task.workspace = true
|
||||||
telemetry.workspace = true
|
telemetry.workspace = true
|
||||||
telemetry_events.workspace = true
|
telemetry_events.workspace = true
|
||||||
terminal.workspace = true
|
terminal.workspace = true
|
||||||
|
|
5
crates/agent_ui/src/acp.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
mod completion_provider;
|
||||||
|
mod message_history;
|
||||||
|
mod thread_view;
|
||||||
|
|
||||||
|
pub use thread_view::AcpThreadView;
|
574
crates/agent_ui/src/acp/completion_provider.rs
Normal file
|
@ -0,0 +1,574 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use collections::HashMap;
|
||||||
|
use editor::display_map::CreaseId;
|
||||||
|
use editor::{CompletionProvider, Editor, ExcerptId};
|
||||||
|
use file_icons::FileIcons;
|
||||||
|
use gpui::{App, Entity, Task, WeakEntity};
|
||||||
|
use language::{Buffer, CodeLabel, HighlightId};
|
||||||
|
use lsp::CompletionContext;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use project::{Completion, CompletionIntent, CompletionResponse, ProjectPath, WorktreeId};
|
||||||
|
use rope::Point;
|
||||||
|
use text::{Anchor, ToPoint};
|
||||||
|
use ui::prelude::*;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
use crate::context_picker::MentionLink;
|
||||||
|
use crate::context_picker::file_context_picker::{extract_file_name_and_directory, search_files};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct MentionSet {
|
||||||
|
paths_by_crease_id: HashMap<CreaseId, ProjectPath>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MentionSet {
|
||||||
|
pub fn insert(&mut self, crease_id: CreaseId, path: ProjectPath) {
|
||||||
|
self.paths_by_crease_id.insert(crease_id, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path_for_crease_id(&self, crease_id: CreaseId) -> Option<ProjectPath> {
|
||||||
|
self.paths_by_crease_id.get(&crease_id).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drain(&mut self) -> impl Iterator<Item = CreaseId> {
|
||||||
|
self.paths_by_crease_id.drain().map(|(id, _)| id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ContextPickerCompletionProvider {
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
editor: WeakEntity<Editor>,
|
||||||
|
mention_set: Arc<Mutex<MentionSet>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextPickerCompletionProvider {
|
||||||
|
pub fn new(
|
||||||
|
mention_set: Arc<Mutex<MentionSet>>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
editor: WeakEntity<Editor>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
mention_set,
|
||||||
|
workspace,
|
||||||
|
editor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn completion_for_path(
|
||||||
|
project_path: ProjectPath,
|
||||||
|
path_prefix: &str,
|
||||||
|
is_recent: bool,
|
||||||
|
is_directory: bool,
|
||||||
|
excerpt_id: ExcerptId,
|
||||||
|
source_range: Range<Anchor>,
|
||||||
|
editor: Entity<Editor>,
|
||||||
|
mention_set: Arc<Mutex<MentionSet>>,
|
||||||
|
cx: &App,
|
||||||
|
) -> Completion {
|
||||||
|
let (file_name, directory) =
|
||||||
|
extract_file_name_and_directory(&project_path.path, path_prefix);
|
||||||
|
|
||||||
|
let label =
|
||||||
|
build_code_label_for_full_path(&file_name, directory.as_ref().map(|s| s.as_ref()), cx);
|
||||||
|
let full_path = if let Some(directory) = directory {
|
||||||
|
format!("{}{}", directory, file_name)
|
||||||
|
} else {
|
||||||
|
file_name.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let crease_icon_path = if is_directory {
|
||||||
|
FileIcons::get_folder_icon(false, cx).unwrap_or_else(|| IconName::Folder.path().into())
|
||||||
|
} else {
|
||||||
|
FileIcons::get_icon(Path::new(&full_path), cx)
|
||||||
|
.unwrap_or_else(|| IconName::File.path().into())
|
||||||
|
};
|
||||||
|
let completion_icon_path = if is_recent {
|
||||||
|
IconName::HistoryRerun.path().into()
|
||||||
|
} else {
|
||||||
|
crease_icon_path.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_text = format!("{} ", MentionLink::for_file(&file_name, &full_path));
|
||||||
|
let new_text_len = new_text.len();
|
||||||
|
Completion {
|
||||||
|
replace_range: source_range.clone(),
|
||||||
|
new_text,
|
||||||
|
label,
|
||||||
|
documentation: None,
|
||||||
|
source: project::CompletionSource::Custom,
|
||||||
|
icon_path: Some(completion_icon_path),
|
||||||
|
insert_text_mode: None,
|
||||||
|
confirm: Some(confirm_completion_callback(
|
||||||
|
crease_icon_path,
|
||||||
|
file_name,
|
||||||
|
project_path,
|
||||||
|
excerpt_id,
|
||||||
|
source_range.start,
|
||||||
|
new_text_len - 1,
|
||||||
|
editor,
|
||||||
|
mention_set,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_code_label_for_full_path(file_name: &str, directory: Option<&str>, cx: &App) -> CodeLabel {
|
||||||
|
let comment_id = cx.theme().syntax().highlight_id("comment").map(HighlightId);
|
||||||
|
let mut label = CodeLabel::default();
|
||||||
|
|
||||||
|
label.push_str(&file_name, None);
|
||||||
|
label.push_str(" ", None);
|
||||||
|
|
||||||
|
if let Some(directory) = directory {
|
||||||
|
label.push_str(&directory, comment_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
label.filter_range = 0..label.text().len();
|
||||||
|
|
||||||
|
label
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompletionProvider for ContextPickerCompletionProvider {
|
||||||
|
fn completions(
|
||||||
|
&self,
|
||||||
|
excerpt_id: ExcerptId,
|
||||||
|
buffer: &Entity<Buffer>,
|
||||||
|
buffer_position: Anchor,
|
||||||
|
_trigger: CompletionContext,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Editor>,
|
||||||
|
) -> Task<Result<Vec<CompletionResponse>>> {
|
||||||
|
let state = buffer.update(cx, |buffer, _cx| {
|
||||||
|
let position = buffer_position.to_point(buffer);
|
||||||
|
let line_start = Point::new(position.row, 0);
|
||||||
|
let offset_to_line = buffer.point_to_offset(line_start);
|
||||||
|
let mut lines = buffer.text_for_range(line_start..position).lines();
|
||||||
|
let line = lines.next()?;
|
||||||
|
MentionCompletion::try_parse(line, offset_to_line)
|
||||||
|
});
|
||||||
|
let Some(state) = state else {
|
||||||
|
return Task::ready(Ok(Vec::new()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return Task::ready(Ok(Vec::new()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
|
let source_range = snapshot.anchor_before(state.source_range.start)
|
||||||
|
..snapshot.anchor_after(state.source_range.end);
|
||||||
|
|
||||||
|
let editor = self.editor.clone();
|
||||||
|
let mention_set = self.mention_set.clone();
|
||||||
|
let MentionCompletion { argument, .. } = state;
|
||||||
|
let query = argument.unwrap_or_else(|| "".to_string());
|
||||||
|
|
||||||
|
let search_task = search_files(query.clone(), Arc::<AtomicBool>::default(), &workspace, cx);
|
||||||
|
|
||||||
|
cx.spawn(async move |_, cx| {
|
||||||
|
let matches = search_task.await;
|
||||||
|
let Some(editor) = editor.upgrade() else {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
};
|
||||||
|
|
||||||
|
let completions = cx.update(|cx| {
|
||||||
|
matches
|
||||||
|
.into_iter()
|
||||||
|
.map(|mat| {
|
||||||
|
let path_match = &mat.mat;
|
||||||
|
let project_path = ProjectPath {
|
||||||
|
worktree_id: WorktreeId::from_usize(path_match.worktree_id),
|
||||||
|
path: path_match.path.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::completion_for_path(
|
||||||
|
project_path,
|
||||||
|
&path_match.path_prefix,
|
||||||
|
mat.is_recent,
|
||||||
|
path_match.is_dir,
|
||||||
|
excerpt_id,
|
||||||
|
source_range.clone(),
|
||||||
|
editor.clone(),
|
||||||
|
mention_set.clone(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(vec![CompletionResponse {
|
||||||
|
completions,
|
||||||
|
// Since this does its own filtering (see `filter_completions()` returns false),
|
||||||
|
// there is no benefit to computing whether this set of completions is incomplete.
|
||||||
|
is_incomplete: true,
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_completion_trigger(
|
||||||
|
&self,
|
||||||
|
buffer: &Entity<language::Buffer>,
|
||||||
|
position: language::Anchor,
|
||||||
|
_text: &str,
|
||||||
|
_trigger_in_words: bool,
|
||||||
|
_menu_is_open: bool,
|
||||||
|
cx: &mut Context<Editor>,
|
||||||
|
) -> bool {
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
let position = position.to_point(buffer);
|
||||||
|
let line_start = Point::new(position.row, 0);
|
||||||
|
let offset_to_line = buffer.point_to_offset(line_start);
|
||||||
|
let mut lines = buffer.text_for_range(line_start..position).lines();
|
||||||
|
if let Some(line) = lines.next() {
|
||||||
|
MentionCompletion::try_parse(line, offset_to_line)
|
||||||
|
.map(|completion| {
|
||||||
|
completion.source_range.start <= offset_to_line + position.column as usize
|
||||||
|
&& completion.source_range.end >= offset_to_line + position.column as usize
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_completions(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_completions(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn confirm_completion_callback(
|
||||||
|
crease_icon_path: SharedString,
|
||||||
|
crease_text: SharedString,
|
||||||
|
project_path: ProjectPath,
|
||||||
|
excerpt_id: ExcerptId,
|
||||||
|
start: Anchor,
|
||||||
|
content_len: usize,
|
||||||
|
editor: Entity<Editor>,
|
||||||
|
mention_set: Arc<Mutex<MentionSet>>,
|
||||||
|
) -> Arc<dyn Fn(CompletionIntent, &mut Window, &mut App) -> bool + Send + Sync> {
|
||||||
|
Arc::new(move |_, window, cx| {
|
||||||
|
let crease_text = crease_text.clone();
|
||||||
|
let crease_icon_path = crease_icon_path.clone();
|
||||||
|
let editor = editor.clone();
|
||||||
|
let project_path = project_path.clone();
|
||||||
|
let mention_set = mention_set.clone();
|
||||||
|
window.defer(cx, move |window, cx| {
|
||||||
|
let crease_id = crate::context_picker::insert_crease_for_mention(
|
||||||
|
excerpt_id,
|
||||||
|
start,
|
||||||
|
content_len,
|
||||||
|
crease_text.clone(),
|
||||||
|
crease_icon_path,
|
||||||
|
editor.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
if let Some(crease_id) = crease_id {
|
||||||
|
mention_set.lock().insert(crease_id, project_path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
struct MentionCompletion {
|
||||||
|
source_range: Range<usize>,
|
||||||
|
argument: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MentionCompletion {
|
||||||
|
fn try_parse(line: &str, offset_to_line: usize) -> Option<Self> {
|
||||||
|
let last_mention_start = line.rfind('@')?;
|
||||||
|
if last_mention_start >= line.len() {
|
||||||
|
return Some(Self::default());
|
||||||
|
}
|
||||||
|
if last_mention_start > 0
|
||||||
|
&& line
|
||||||
|
.chars()
|
||||||
|
.nth(last_mention_start - 1)
|
||||||
|
.map_or(false, |c| !c.is_whitespace())
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rest_of_line = &line[last_mention_start + 1..];
|
||||||
|
let mut argument = None;
|
||||||
|
|
||||||
|
let mut parts = rest_of_line.split_whitespace();
|
||||||
|
let mut end = last_mention_start + 1;
|
||||||
|
if let Some(argument_text) = parts.next() {
|
||||||
|
end += argument_text.len();
|
||||||
|
argument = Some(argument_text.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
source_range: last_mention_start + offset_to_line..end + offset_to_line,
|
||||||
|
argument,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use gpui::{EventEmitter, FocusHandle, Focusable, TestAppContext, VisualTestContext};
|
||||||
|
use project::{Project, ProjectPath};
|
||||||
|
use serde_json::json;
|
||||||
|
use settings::SettingsStore;
|
||||||
|
use std::{ops::Deref, rc::Rc};
|
||||||
|
use util::path;
|
||||||
|
use workspace::{AppState, Item};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mention_completion_parse() {
|
||||||
|
assert_eq!(MentionCompletion::try_parse("Lorem Ipsum", 0), None);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MentionCompletion::try_parse("Lorem @", 0),
|
||||||
|
Some(MentionCompletion {
|
||||||
|
source_range: 6..7,
|
||||||
|
argument: None,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
MentionCompletion::try_parse("Lorem @main", 0),
|
||||||
|
Some(MentionCompletion {
|
||||||
|
source_range: 6..11,
|
||||||
|
argument: Some("main".to_string()),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(MentionCompletion::try_parse("test@", 0), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AtMentionEditor(Entity<Editor>);
|
||||||
|
|
||||||
|
impl Item for AtMentionEditor {
|
||||||
|
type Event = ();
|
||||||
|
|
||||||
|
fn include_in_nav_history() -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
|
"Test".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<()> for AtMentionEditor {}
|
||||||
|
|
||||||
|
impl Focusable for AtMentionEditor {
|
||||||
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
|
self.0.read(cx).focus_handle(cx).clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for AtMentionEditor {
|
||||||
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
self.0.clone().into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_context_completion_provider(cx: &mut TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let app_state = cx.update(AppState::test);
|
||||||
|
|
||||||
|
cx.update(|cx| {
|
||||||
|
language::init(cx);
|
||||||
|
editor::init(cx);
|
||||||
|
workspace::init(app_state.clone(), cx);
|
||||||
|
Project::init_settings(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
app_state
|
||||||
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_tree(
|
||||||
|
path!("/dir"),
|
||||||
|
json!({
|
||||||
|
"editor": "",
|
||||||
|
"a": {
|
||||||
|
"one.txt": "",
|
||||||
|
"two.txt": "",
|
||||||
|
"three.txt": "",
|
||||||
|
"four.txt": ""
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"five.txt": "",
|
||||||
|
"six.txt": "",
|
||||||
|
"seven.txt": "",
|
||||||
|
"eight.txt": "",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(app_state.fs.clone(), [path!("/dir").as_ref()], cx).await;
|
||||||
|
let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||||
|
let workspace = window.root(cx).unwrap();
|
||||||
|
|
||||||
|
let worktree = project.update(cx, |project, cx| {
|
||||||
|
let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
|
||||||
|
assert_eq!(worktrees.len(), 1);
|
||||||
|
worktrees.pop().unwrap()
|
||||||
|
});
|
||||||
|
let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
|
||||||
|
|
||||||
|
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||||
|
|
||||||
|
let paths = vec![
|
||||||
|
path!("a/one.txt"),
|
||||||
|
path!("a/two.txt"),
|
||||||
|
path!("a/three.txt"),
|
||||||
|
path!("a/four.txt"),
|
||||||
|
path!("b/five.txt"),
|
||||||
|
path!("b/six.txt"),
|
||||||
|
path!("b/seven.txt"),
|
||||||
|
path!("b/eight.txt"),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut opened_editors = Vec::new();
|
||||||
|
for path in paths {
|
||||||
|
let buffer = workspace
|
||||||
|
.update_in(&mut cx, |workspace, window, cx| {
|
||||||
|
workspace.open_path(
|
||||||
|
ProjectPath {
|
||||||
|
worktree_id,
|
||||||
|
path: Path::new(path).into(),
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
opened_editors.push(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
let editor = workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||||
|
let editor = cx.new(|cx| {
|
||||||
|
Editor::new(
|
||||||
|
editor::EditorMode::full(),
|
||||||
|
multi_buffer::MultiBuffer::build_simple("", cx),
|
||||||
|
None,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
workspace.active_pane().update(cx, |pane, cx| {
|
||||||
|
pane.add_item(
|
||||||
|
Box::new(cx.new(|_| AtMentionEditor(editor.clone()))),
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
editor
|
||||||
|
});
|
||||||
|
|
||||||
|
let mention_set = Arc::new(Mutex::new(MentionSet::default()));
|
||||||
|
|
||||||
|
let editor_entity = editor.downgrade();
|
||||||
|
editor.update_in(&mut cx, |editor, window, cx| {
|
||||||
|
window.focus(&editor.focus_handle(cx));
|
||||||
|
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
|
||||||
|
mention_set.clone(),
|
||||||
|
workspace.downgrade(),
|
||||||
|
editor_entity,
|
||||||
|
))));
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.simulate_input("Lorem ");
|
||||||
|
|
||||||
|
editor.update(&mut cx, |editor, cx| {
|
||||||
|
assert_eq!(editor.text(cx), "Lorem ");
|
||||||
|
assert!(!editor.has_visible_completions_menu());
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.simulate_input("@");
|
||||||
|
|
||||||
|
editor.update(&mut cx, |editor, cx| {
|
||||||
|
assert_eq!(editor.text(cx), "Lorem @");
|
||||||
|
assert!(editor.has_visible_completions_menu());
|
||||||
|
assert_eq!(
|
||||||
|
current_completion_labels(editor),
|
||||||
|
&[
|
||||||
|
"eight.txt dir/b/",
|
||||||
|
"seven.txt dir/b/",
|
||||||
|
"six.txt dir/b/",
|
||||||
|
"five.txt dir/b/",
|
||||||
|
"four.txt dir/a/",
|
||||||
|
"three.txt dir/a/",
|
||||||
|
"two.txt dir/a/",
|
||||||
|
"one.txt dir/a/",
|
||||||
|
"dir ",
|
||||||
|
"a dir/",
|
||||||
|
"four.txt dir/a/",
|
||||||
|
"one.txt dir/a/",
|
||||||
|
"three.txt dir/a/",
|
||||||
|
"two.txt dir/a/",
|
||||||
|
"b dir/",
|
||||||
|
"eight.txt dir/b/",
|
||||||
|
"five.txt dir/b/",
|
||||||
|
"seven.txt dir/b/",
|
||||||
|
"six.txt dir/b/",
|
||||||
|
"editor dir/"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Select and confirm "File"
|
||||||
|
editor.update_in(&mut cx, |editor, window, cx| {
|
||||||
|
assert!(editor.has_visible_completions_menu());
|
||||||
|
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||||
|
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||||
|
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||||
|
editor.context_menu_next(&editor::actions::ContextMenuNext, window, cx);
|
||||||
|
editor.confirm_completion(&editor::actions::ConfirmCompletion::default(), window, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
editor.update(&mut cx, |editor, cx| {
|
||||||
|
assert_eq!(editor.text(cx), "Lorem [@four.txt](@file:dir/a/four.txt) ");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_completion_labels(editor: &Editor) -> Vec<String> {
|
||||||
|
let completions = editor.current_completions().expect("Missing completions");
|
||||||
|
completions
|
||||||
|
.into_iter()
|
||||||
|
.map(|completion| completion.label.text.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init_test(cx: &mut TestAppContext) {
|
||||||
|
cx.update(|cx| {
|
||||||
|
let store = SettingsStore::test(cx);
|
||||||
|
cx.set_global(store);
|
||||||
|
theme::init(theme::LoadThemes::JustBase, cx);
|
||||||
|
client::init_settings(cx);
|
||||||
|
language::init(cx);
|
||||||
|
Project::init_settings(cx);
|
||||||
|
workspace::init_settings(cx);
|
||||||
|
editor::init_settings(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
81
crates/agent_ui/src/acp/message_history.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
pub struct MessageHistory<T> {
|
||||||
|
items: Vec<T>,
|
||||||
|
current: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> MessageHistory<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
MessageHistory {
|
||||||
|
items: Vec::new(),
|
||||||
|
current: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, message: T) {
|
||||||
|
self.current.take();
|
||||||
|
self.items.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prev(&mut self) -> Option<&T> {
|
||||||
|
if self.items.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_ix = self
|
||||||
|
.current
|
||||||
|
.get_or_insert(self.items.len())
|
||||||
|
.saturating_sub(1);
|
||||||
|
|
||||||
|
self.current = Some(new_ix);
|
||||||
|
self.items.get(new_ix)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) -> Option<&T> {
|
||||||
|
let current = self.current.as_mut()?;
|
||||||
|
*current += 1;
|
||||||
|
|
||||||
|
self.items.get(*current).or_else(|| {
|
||||||
|
self.current.take();
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prev_next() {
|
||||||
|
let mut history = MessageHistory::new();
|
||||||
|
|
||||||
|
// Test empty history
|
||||||
|
assert_eq!(history.prev(), None);
|
||||||
|
assert_eq!(history.next(), None);
|
||||||
|
|
||||||
|
// Add some messages
|
||||||
|
history.push("first");
|
||||||
|
history.push("second");
|
||||||
|
history.push("third");
|
||||||
|
|
||||||
|
// Test prev navigation
|
||||||
|
assert_eq!(history.prev(), Some(&"third"));
|
||||||
|
assert_eq!(history.prev(), Some(&"second"));
|
||||||
|
assert_eq!(history.prev(), Some(&"first"));
|
||||||
|
assert_eq!(history.prev(), Some(&"first"));
|
||||||
|
|
||||||
|
assert_eq!(history.next(), Some(&"second"));
|
||||||
|
|
||||||
|
// Test mixed navigation
|
||||||
|
history.push("fourth");
|
||||||
|
assert_eq!(history.prev(), Some(&"fourth"));
|
||||||
|
assert_eq!(history.prev(), Some(&"third"));
|
||||||
|
assert_eq!(history.next(), Some(&"fourth"));
|
||||||
|
assert_eq!(history.next(), None);
|
||||||
|
|
||||||
|
// Test that push resets navigation
|
||||||
|
history.prev();
|
||||||
|
history.prev();
|
||||||
|
history.push("fifth");
|
||||||
|
assert_eq!(history.prev(), Some(&"fifth"));
|
||||||
|
}
|
||||||
|
}
|
1972
crates/agent_ui/src/acp/thread_view.rs
Normal file
|
@ -7,12 +7,14 @@ use std::time::Duration;
|
||||||
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
use db::kvp::{Dismissable, KEY_VALUE_STORE};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::NewGeminiThread;
|
||||||
use crate::language_model_selector::ToggleModelSelector;
|
use crate::language_model_selector::ToggleModelSelector;
|
||||||
use crate::{
|
use crate::{
|
||||||
AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
|
AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
|
||||||
DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
|
DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
|
||||||
NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
|
NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
|
||||||
ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
|
ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
|
||||||
|
acp::AcpThreadView,
|
||||||
active_thread::{self, ActiveThread, ActiveThreadEvent},
|
active_thread::{self, ActiveThread, ActiveThreadEvent},
|
||||||
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
|
agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
|
||||||
agent_diff::AgentDiff,
|
agent_diff::AgentDiff,
|
||||||
|
@ -38,6 +40,7 @@ use assistant_slash_command::SlashCommandWorkingSet;
|
||||||
use assistant_tool::ToolWorkingSet;
|
use assistant_tool::ToolWorkingSet;
|
||||||
use client::{UserStore, zed_urls};
|
use client::{UserStore, zed_urls};
|
||||||
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
|
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
|
||||||
|
use feature_flags::{self, FeatureFlagAppExt};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
|
Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
|
||||||
|
@ -109,6 +112,12 @@ pub fn init(cx: &mut App) {
|
||||||
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
|
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.register_action(|workspace, _: &NewGeminiThread, window, cx| {
|
||||||
|
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||||
|
workspace.focus_panel::<AgentPanel>(window, cx);
|
||||||
|
panel.update(cx, |panel, cx| panel.new_gemini_thread(window, cx));
|
||||||
|
}
|
||||||
|
})
|
||||||
.register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
|
.register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
|
||||||
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
|
||||||
workspace.focus_panel::<AgentPanel>(window, cx);
|
workspace.focus_panel::<AgentPanel>(window, cx);
|
||||||
|
@ -125,7 +134,8 @@ pub fn init(cx: &mut App) {
|
||||||
let thread = thread.read(cx).thread().clone();
|
let thread = thread.read(cx).thread().clone();
|
||||||
AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
|
AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
|
||||||
}
|
}
|
||||||
ActiveView::TextThread { .. }
|
ActiveView::AcpThread { .. }
|
||||||
|
| ActiveView::TextThread { .. }
|
||||||
| ActiveView::History
|
| ActiveView::History
|
||||||
| ActiveView::Configuration => {}
|
| ActiveView::Configuration => {}
|
||||||
}
|
}
|
||||||
|
@ -188,6 +198,9 @@ enum ActiveView {
|
||||||
message_editor: Entity<MessageEditor>,
|
message_editor: Entity<MessageEditor>,
|
||||||
_subscriptions: Vec<gpui::Subscription>,
|
_subscriptions: Vec<gpui::Subscription>,
|
||||||
},
|
},
|
||||||
|
AcpThread {
|
||||||
|
thread_view: Entity<AcpThreadView>,
|
||||||
|
},
|
||||||
TextThread {
|
TextThread {
|
||||||
context_editor: Entity<TextThreadEditor>,
|
context_editor: Entity<TextThreadEditor>,
|
||||||
title_editor: Entity<Editor>,
|
title_editor: Entity<Editor>,
|
||||||
|
@ -207,7 +220,9 @@ enum WhichFontSize {
|
||||||
impl ActiveView {
|
impl ActiveView {
|
||||||
pub fn which_font_size_used(&self) -> WhichFontSize {
|
pub fn which_font_size_used(&self) -> WhichFontSize {
|
||||||
match self {
|
match self {
|
||||||
ActiveView::Thread { .. } | ActiveView::History => WhichFontSize::AgentFont,
|
ActiveView::Thread { .. } | ActiveView::AcpThread { .. } | ActiveView::History => {
|
||||||
|
WhichFontSize::AgentFont
|
||||||
|
}
|
||||||
ActiveView::TextThread { .. } => WhichFontSize::BufferFont,
|
ActiveView::TextThread { .. } => WhichFontSize::BufferFont,
|
||||||
ActiveView::Configuration => WhichFontSize::None,
|
ActiveView::Configuration => WhichFontSize::None,
|
||||||
}
|
}
|
||||||
|
@ -238,6 +253,7 @@ impl ActiveView {
|
||||||
thread.scroll_to_bottom(cx);
|
thread.scroll_to_bottom(cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
ActiveView::AcpThread { .. } => {}
|
||||||
ActiveView::TextThread { .. }
|
ActiveView::TextThread { .. }
|
||||||
| ActiveView::History
|
| ActiveView::History
|
||||||
| ActiveView::Configuration => {}
|
| ActiveView::Configuration => {}
|
||||||
|
@ -653,7 +669,8 @@ impl AgentPanel {
|
||||||
.clone()
|
.clone()
|
||||||
.update(cx, |thread, cx| thread.get_or_init_configured_model(cx));
|
.update(cx, |thread, cx| thread.get_or_init_configured_model(cx));
|
||||||
}
|
}
|
||||||
ActiveView::TextThread { .. }
|
ActiveView::AcpThread { .. }
|
||||||
|
| ActiveView::TextThread { .. }
|
||||||
| ActiveView::History
|
| ActiveView::History
|
||||||
| ActiveView::Configuration => {}
|
| ActiveView::Configuration => {}
|
||||||
},
|
},
|
||||||
|
@ -733,6 +750,9 @@ impl AgentPanel {
|
||||||
ActiveView::Thread { thread, .. } => {
|
ActiveView::Thread { thread, .. } => {
|
||||||
thread.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
|
thread.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
|
||||||
}
|
}
|
||||||
|
ActiveView::AcpThread { thread_view, .. } => {
|
||||||
|
thread_view.update(cx, |thread_element, cx| thread_element.cancel(cx));
|
||||||
|
}
|
||||||
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
|
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -740,7 +760,10 @@ impl AgentPanel {
|
||||||
fn active_message_editor(&self) -> Option<&Entity<MessageEditor>> {
|
fn active_message_editor(&self) -> Option<&Entity<MessageEditor>> {
|
||||||
match &self.active_view {
|
match &self.active_view {
|
||||||
ActiveView::Thread { message_editor, .. } => Some(message_editor),
|
ActiveView::Thread { message_editor, .. } => Some(message_editor),
|
||||||
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => None,
|
ActiveView::AcpThread { .. }
|
||||||
|
| ActiveView::TextThread { .. }
|
||||||
|
| ActiveView::History
|
||||||
|
| ActiveView::Configuration => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -862,6 +885,21 @@ impl AgentPanel {
|
||||||
context_editor.focus_handle(cx).focus(window);
|
context_editor.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_gemini_thread(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
let project = self.project.clone();
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let thread_view = cx.new_window_entity(|window, cx| {
|
||||||
|
crate::acp::AcpThreadView::new(workspace, project, window, cx)
|
||||||
|
})?;
|
||||||
|
this.update_in(cx, |this, window, cx| {
|
||||||
|
this.set_active_view(ActiveView::AcpThread { thread_view }, window, cx);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
fn deploy_rules_library(
|
fn deploy_rules_library(
|
||||||
&mut self,
|
&mut self,
|
||||||
action: &OpenRulesLibrary,
|
action: &OpenRulesLibrary,
|
||||||
|
@ -994,6 +1032,7 @@ impl AgentPanel {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let message_editor = cx.new(|cx| {
|
let message_editor = cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
self.fs.clone(),
|
self.fs.clone(),
|
||||||
|
@ -1025,6 +1064,9 @@ impl AgentPanel {
|
||||||
ActiveView::Thread { message_editor, .. } => {
|
ActiveView::Thread { message_editor, .. } => {
|
||||||
message_editor.focus_handle(cx).focus(window);
|
message_editor.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
ActiveView::AcpThread { thread_view } => {
|
||||||
|
thread_view.focus_handle(cx).focus(window);
|
||||||
|
}
|
||||||
ActiveView::TextThread { context_editor, .. } => {
|
ActiveView::TextThread { context_editor, .. } => {
|
||||||
context_editor.focus_handle(cx).focus(window);
|
context_editor.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
@ -1144,7 +1186,10 @@ impl AgentPanel {
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
|
ActiveView::AcpThread { .. }
|
||||||
|
| ActiveView::TextThread { .. }
|
||||||
|
| ActiveView::History
|
||||||
|
| ActiveView::Configuration => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1197,6 +1242,13 @@ impl AgentPanel {
|
||||||
)
|
)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
|
ActiveView::AcpThread { thread_view } => {
|
||||||
|
thread_view
|
||||||
|
.update(cx, |thread_view, cx| {
|
||||||
|
thread_view.open_thread_as_markdown(workspace, window, cx)
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
|
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1351,7 +1403,8 @@ impl AgentPanel {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => {}
|
ActiveView::AcpThread { .. } => {}
|
||||||
|
ActiveView::History | ActiveView::Configuration => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if current_is_special && !new_is_special {
|
if current_is_special && !new_is_special {
|
||||||
|
@ -1437,6 +1490,7 @@ impl Focusable for AgentPanel {
|
||||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||||
match &self.active_view {
|
match &self.active_view {
|
||||||
ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx),
|
ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx),
|
||||||
|
ActiveView::AcpThread { thread_view, .. } => thread_view.focus_handle(cx),
|
||||||
ActiveView::History => self.history.focus_handle(cx),
|
ActiveView::History => self.history.focus_handle(cx),
|
||||||
ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
|
ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
|
||||||
ActiveView::Configuration => {
|
ActiveView::Configuration => {
|
||||||
|
@ -1593,6 +1647,9 @@ impl AgentPanel {
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ActiveView::AcpThread { thread_view } => Label::new(thread_view.read(cx).title(cx))
|
||||||
|
.truncate()
|
||||||
|
.into_any_element(),
|
||||||
ActiveView::TextThread {
|
ActiveView::TextThread {
|
||||||
title_editor,
|
title_editor,
|
||||||
context_editor,
|
context_editor,
|
||||||
|
@ -1727,7 +1784,10 @@ impl AgentPanel {
|
||||||
|
|
||||||
let active_thread = match &self.active_view {
|
let active_thread = match &self.active_view {
|
||||||
ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
|
ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
|
||||||
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => None,
|
ActiveView::AcpThread { .. }
|
||||||
|
| ActiveView::TextThread { .. }
|
||||||
|
| ActiveView::History
|
||||||
|
| ActiveView::Configuration => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let agent_extra_menu = PopoverMenu::new("agent-options-menu")
|
let agent_extra_menu = PopoverMenu::new("agent-options-menu")
|
||||||
|
@ -1755,6 +1815,9 @@ impl AgentPanel {
|
||||||
menu = menu
|
menu = menu
|
||||||
.action("New Thread", NewThread::default().boxed_clone())
|
.action("New Thread", NewThread::default().boxed_clone())
|
||||||
.action("New Text Thread", NewTextThread.boxed_clone())
|
.action("New Text Thread", NewTextThread.boxed_clone())
|
||||||
|
.when(cx.has_flag::<feature_flags::AcpFeatureFlag>(), |this| {
|
||||||
|
this.action("New Gemini Thread", NewGeminiThread.boxed_clone())
|
||||||
|
})
|
||||||
.when_some(active_thread, |this, active_thread| {
|
.when_some(active_thread, |this, active_thread| {
|
||||||
let thread = active_thread.read(cx);
|
let thread = active_thread.read(cx);
|
||||||
if !thread.is_empty() {
|
if !thread.is_empty() {
|
||||||
|
@ -1893,6 +1956,9 @@ impl AgentPanel {
|
||||||
message_editor,
|
message_editor,
|
||||||
..
|
..
|
||||||
} => (thread.read(cx), message_editor.read(cx)),
|
} => (thread.read(cx), message_editor.read(cx)),
|
||||||
|
ActiveView::AcpThread { .. } => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
|
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -2031,6 +2097,9 @@ impl AgentPanel {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ActiveView::AcpThread { .. } => {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
|
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2615,6 +2684,9 @@ impl AgentPanel {
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
let active_thread = match &self.active_view {
|
let active_thread = match &self.active_view {
|
||||||
ActiveView::Thread { thread, .. } => thread,
|
ActiveView::Thread { thread, .. } => thread,
|
||||||
|
ActiveView::AcpThread { .. } => {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
|
ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -2961,6 +3033,9 @@ impl AgentPanel {
|
||||||
.detach();
|
.detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
ActiveView::AcpThread { .. } => {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
ActiveView::TextThread { context_editor, .. } => {
|
ActiveView::TextThread { context_editor, .. } => {
|
||||||
context_editor.update(cx, |context_editor, cx| {
|
context_editor.update(cx, |context_editor, cx| {
|
||||||
TextThreadEditor::insert_dragged_files(
|
TextThreadEditor::insert_dragged_files(
|
||||||
|
@ -3034,6 +3109,7 @@ impl Render for AgentPanel {
|
||||||
});
|
});
|
||||||
this.continue_conversation(window, cx);
|
this.continue_conversation(window, cx);
|
||||||
}
|
}
|
||||||
|
ActiveView::AcpThread { .. } => {}
|
||||||
ActiveView::TextThread { .. }
|
ActiveView::TextThread { .. }
|
||||||
| ActiveView::History
|
| ActiveView::History
|
||||||
| ActiveView::Configuration => {}
|
| ActiveView::Configuration => {}
|
||||||
|
@ -3075,6 +3151,10 @@ impl Render for AgentPanel {
|
||||||
})
|
})
|
||||||
.child(h_flex().child(message_editor.clone()))
|
.child(h_flex().child(message_editor.clone()))
|
||||||
.child(self.render_drag_target(cx)),
|
.child(self.render_drag_target(cx)),
|
||||||
|
ActiveView::AcpThread { thread_view, .. } => parent
|
||||||
|
.relative()
|
||||||
|
.child(thread_view.clone())
|
||||||
|
.child(self.render_drag_target(cx)),
|
||||||
ActiveView::History => parent.child(self.history.clone()),
|
ActiveView::History => parent.child(self.history.clone()),
|
||||||
ActiveView::TextThread {
|
ActiveView::TextThread {
|
||||||
context_editor,
|
context_editor,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod acp;
|
||||||
mod active_thread;
|
mod active_thread;
|
||||||
mod agent_configuration;
|
mod agent_configuration;
|
||||||
mod agent_diff;
|
mod agent_diff;
|
||||||
|
@ -56,6 +57,8 @@ actions!(
|
||||||
[
|
[
|
||||||
/// Creates a new text-based conversation thread.
|
/// Creates a new text-based conversation thread.
|
||||||
NewTextThread,
|
NewTextThread,
|
||||||
|
/// Creates a new Gemini CLI-based conversation thread.
|
||||||
|
NewGeminiThread,
|
||||||
/// Toggles the context picker interface for adding files, symbols, or other context.
|
/// Toggles the context picker interface for adding files, symbols, or other context.
|
||||||
ToggleContextPicker,
|
ToggleContextPicker,
|
||||||
/// Toggles the navigation menu for switching between threads and views.
|
/// Toggles the navigation menu for switching between threads and views.
|
||||||
|
@ -76,8 +79,6 @@ actions!(
|
||||||
AddContextServer,
|
AddContextServer,
|
||||||
/// Removes the currently selected thread.
|
/// Removes the currently selected thread.
|
||||||
RemoveSelectedThread,
|
RemoveSelectedThread,
|
||||||
/// Starts a chat conversation with the agent.
|
|
||||||
Chat,
|
|
||||||
/// Starts a chat conversation with follow-up enabled.
|
/// Starts a chat conversation with follow-up enabled.
|
||||||
ChatWithFollow,
|
ChatWithFollow,
|
||||||
/// Cycles to the next inline assist suggestion.
|
/// Cycles to the next inline assist suggestion.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod completion_provider;
|
mod completion_provider;
|
||||||
mod fetch_context_picker;
|
mod fetch_context_picker;
|
||||||
mod file_context_picker;
|
pub(crate) mod file_context_picker;
|
||||||
mod rules_context_picker;
|
mod rules_context_picker;
|
||||||
mod symbol_context_picker;
|
mod symbol_context_picker;
|
||||||
mod thread_context_picker;
|
mod thread_context_picker;
|
||||||
|
|
|
@ -47,13 +47,14 @@ use ui::{
|
||||||
};
|
};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::{CollaboratorId, Workspace};
|
use workspace::{CollaboratorId, Workspace};
|
||||||
|
use zed_actions::agent::Chat;
|
||||||
use zed_llm_client::CompletionIntent;
|
use zed_llm_client::CompletionIntent;
|
||||||
|
|
||||||
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
|
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
|
||||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||||
use crate::profile_selector::ProfileSelector;
|
use crate::profile_selector::ProfileSelector;
|
||||||
use crate::{
|
use crate::{
|
||||||
ActiveThread, AgentDiffPane, Chat, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll,
|
ActiveThread, AgentDiffPane, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll,
|
||||||
ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode,
|
ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode,
|
||||||
ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
|
ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
|
||||||
};
|
};
|
||||||
|
|
|
@ -92,6 +92,12 @@ impl FeatureFlag for JjUiFeatureFlag {
|
||||||
const NAME: &'static str = "jj-ui";
|
const NAME: &'static str = "jj-ui";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AcpFeatureFlag;
|
||||||
|
|
||||||
|
impl FeatureFlag for AcpFeatureFlag {
|
||||||
|
const NAME: &'static str = "acp";
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ZedCloudFeatureFlag {}
|
pub struct ZedCloudFeatureFlag {}
|
||||||
|
|
||||||
impl FeatureFlag for ZedCloudFeatureFlag {
|
impl FeatureFlag for ZedCloudFeatureFlag {
|
||||||
|
|
|
@ -13,6 +13,7 @@ pub enum IconName {
|
||||||
AiBedrock,
|
AiBedrock,
|
||||||
AiDeepSeek,
|
AiDeepSeek,
|
||||||
AiEdit,
|
AiEdit,
|
||||||
|
AiGemini,
|
||||||
AiGoogle,
|
AiGoogle,
|
||||||
AiLmStudio,
|
AiLmStudio,
|
||||||
AiMistral,
|
AiMistral,
|
||||||
|
@ -252,6 +253,14 @@ pub enum IconName {
|
||||||
TextSnippet,
|
TextSnippet,
|
||||||
ThumbsDown,
|
ThumbsDown,
|
||||||
ThumbsUp,
|
ThumbsUp,
|
||||||
|
ToolBulb,
|
||||||
|
ToolFolder,
|
||||||
|
ToolHammer,
|
||||||
|
ToolPencil,
|
||||||
|
ToolRegex,
|
||||||
|
ToolSearch,
|
||||||
|
ToolTerminal,
|
||||||
|
ToolWeb,
|
||||||
Trash,
|
Trash,
|
||||||
TrashAlt,
|
TrashAlt,
|
||||||
Triangle,
|
Triangle,
|
||||||
|
|
|
@ -352,6 +352,14 @@ pub fn debug_adapters_dir() -> &'static PathBuf {
|
||||||
DEBUG_ADAPTERS_DIR.get_or_init(|| data_dir().join("debug_adapters"))
|
DEBUG_ADAPTERS_DIR.get_or_init(|| data_dir().join("debug_adapters"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the path to the agent servers directory
|
||||||
|
///
|
||||||
|
/// This is where agent servers are downloaded to
|
||||||
|
pub fn agent_servers_dir() -> &'static PathBuf {
|
||||||
|
static AGENT_SERVERS_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||||
|
AGENT_SERVERS_DIR.get_or_init(|| data_dir().join("agent_servers"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the path to the Copilot directory.
|
/// Returns the path to the Copilot directory.
|
||||||
pub fn copilot_dir() -> &'static PathBuf {
|
pub fn copilot_dir() -> &'static PathBuf {
|
||||||
static COPILOT_DIR: OnceLock<PathBuf> = OnceLock::new();
|
static COPILOT_DIR: OnceLock<PathBuf> = OnceLock::new();
|
||||||
|
|
|
@ -84,7 +84,7 @@ impl ProjectEnvironment {
|
||||||
self.get_worktree_environment(worktree, cx)
|
self.get_worktree_environment(worktree, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_worktree_environment(
|
pub fn get_worktree_environment(
|
||||||
&mut self,
|
&mut self,
|
||||||
worktree: Entity<Worktree>,
|
worktree: Entity<Worktree>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
@ -118,7 +118,7 @@ impl ProjectEnvironment {
|
||||||
/// If the project was opened from the CLI, then the inherited CLI environment is returned.
|
/// If the project was opened from the CLI, then the inherited CLI environment is returned.
|
||||||
/// If it wasn't opened from the CLI, and an absolute path is given, then a shell is spawned in
|
/// If it wasn't opened from the CLI, and an absolute path is given, then a shell is spawned in
|
||||||
/// that directory, to get environment variables as if the user has `cd`'d there.
|
/// that directory, to get environment variables as if the user has `cd`'d there.
|
||||||
pub(crate) fn get_directory_environment(
|
pub fn get_directory_environment(
|
||||||
&mut self,
|
&mut self,
|
||||||
abs_path: Arc<Path>,
|
abs_path: Arc<Path>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
|
|
|
@ -39,7 +39,7 @@ pub trait StyledExt: Styled + Sized {
|
||||||
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
|
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
|
||||||
///
|
///
|
||||||
/// Example Elements: Title Bar, Panel, Tab Bar, Editor
|
/// Example Elements: Title Bar, Panel, Tab Bar, Editor
|
||||||
fn elevation_1(self, cx: &mut App) -> Self {
|
fn elevation_1(self, cx: &App) -> Self {
|
||||||
elevated(self, cx, ElevationIndex::Surface)
|
elevated(self, cx, ElevationIndex::Surface)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use editor::test::editor_lsp_test_context::EditorLspTestContext;
|
use editor::test::editor_lsp_test_context::EditorLspTestContext;
|
||||||
use gpui::{Context, Entity, SemanticVersion, UpdateGlobal, actions};
|
use gpui::{Context, Entity, SemanticVersion, UpdateGlobal};
|
||||||
use search::{BufferSearchBar, project_search::ProjectSearchBar};
|
use search::{BufferSearchBar, project_search::ProjectSearchBar};
|
||||||
|
|
||||||
use crate::{state::Operator, *};
|
use crate::{state::Operator, *};
|
||||||
|
|
||||||
actions!(agent, [Chat]);
|
|
||||||
|
|
||||||
pub struct VimTestContext {
|
pub struct VimTestContext {
|
||||||
cx: EditorLspTestContext,
|
cx: EditorLspTestContext,
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ activity_indicator.workspace = true
|
||||||
agent.workspace = true
|
agent.workspace = true
|
||||||
agent_ui.workspace = true
|
agent_ui.workspace = true
|
||||||
agent_settings.workspace = true
|
agent_settings.workspace = true
|
||||||
|
agent_servers.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
askpass.workspace = true
|
askpass.workspace = true
|
||||||
assets.workspace = true
|
assets.workspace = true
|
||||||
|
|
|
@ -520,6 +520,7 @@ pub fn main() {
|
||||||
supermaven::init(app_state.client.clone(), cx);
|
supermaven::init(app_state.client.clone(), cx);
|
||||||
language_model::init(app_state.client.clone(), cx);
|
language_model::init(app_state.client.clone(), cx);
|
||||||
language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx);
|
language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx);
|
||||||
|
agent_servers::init(cx);
|
||||||
web_search::init(cx);
|
web_search::init(cx);
|
||||||
web_search_providers::init(app_state.client.clone(), cx);
|
web_search_providers::init(app_state.client.clone(), cx);
|
||||||
snippet_provider::init(cx);
|
snippet_provider::init(cx);
|
||||||
|
|
|
@ -268,7 +268,13 @@ pub mod agent {
|
||||||
/// Opens the agent onboarding modal.
|
/// Opens the agent onboarding modal.
|
||||||
OpenOnboardingModal,
|
OpenOnboardingModal,
|
||||||
/// Resets the agent onboarding state.
|
/// Resets the agent onboarding state.
|
||||||
ResetOnboarding
|
ResetOnboarding,
|
||||||
|
/// Starts a chat conversation with the agent.
|
||||||
|
Chat,
|
||||||
|
/// Displays the previous message in the history.
|
||||||
|
PreviousHistoryMessage,
|
||||||
|
/// Displays the next message in the history.
|
||||||
|
NextHistoryMessage
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,7 @@ rustc-hash = { version = "1" }
|
||||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["fs", "net", "std"] }
|
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["fs", "net", "std"] }
|
||||||
rustls = { version = "0.23", features = ["ring"] }
|
rustls = { version = "0.23", features = ["ring"] }
|
||||||
rustls-webpki = { version = "0.103", default-features = false, features = ["aws-lc-rs", "ring", "std"] }
|
rustls-webpki = { version = "0.103", default-features = false, features = ["aws-lc-rs", "ring", "std"] }
|
||||||
|
schemars = { version = "1", features = ["chrono04", "indexmap2"] }
|
||||||
sea-orm = { version = "1", features = ["runtime-tokio-rustls", "sqlx-postgres", "sqlx-sqlite"] }
|
sea-orm = { version = "1", features = ["runtime-tokio-rustls", "sqlx-postgres", "sqlx-sqlite"] }
|
||||||
sea-query-binder = { version = "0.7", default-features = false, features = ["postgres-array", "sqlx-postgres", "sqlx-sqlite", "with-bigdecimal", "with-chrono", "with-json", "with-rust_decimal", "with-time", "with-uuid"] }
|
sea-query-binder = { version = "0.7", default-features = false, features = ["postgres-array", "sqlx-postgres", "sqlx-sqlite", "with-bigdecimal", "with-chrono", "with-json", "with-rust_decimal", "with-time", "with-uuid"] }
|
||||||
semver = { version = "1", features = ["serde"] }
|
semver = { version = "1", features = ["serde"] }
|
||||||
|
@ -239,6 +240,7 @@ rustc-hash = { version = "1" }
|
||||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["fs", "net", "std"] }
|
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["fs", "net", "std"] }
|
||||||
rustls = { version = "0.23", features = ["ring"] }
|
rustls = { version = "0.23", features = ["ring"] }
|
||||||
rustls-webpki = { version = "0.103", default-features = false, features = ["aws-lc-rs", "ring", "std"] }
|
rustls-webpki = { version = "0.103", default-features = false, features = ["aws-lc-rs", "ring", "std"] }
|
||||||
|
schemars = { version = "1", features = ["chrono04", "indexmap2"] }
|
||||||
sea-orm = { version = "1", features = ["runtime-tokio-rustls", "sqlx-postgres", "sqlx-sqlite"] }
|
sea-orm = { version = "1", features = ["runtime-tokio-rustls", "sqlx-postgres", "sqlx-sqlite"] }
|
||||||
sea-query-binder = { version = "0.7", default-features = false, features = ["postgres-array", "sqlx-postgres", "sqlx-sqlite", "with-bigdecimal", "with-chrono", "with-json", "with-rust_decimal", "with-time", "with-uuid"] }
|
sea-query-binder = { version = "0.7", default-features = false, features = ["postgres-array", "sqlx-postgres", "sqlx-sqlite", "with-bigdecimal", "with-chrono", "with-json", "with-rust_decimal", "with-time", "with-uuid"] }
|
||||||
semver = { version = "1", features = ["serde"] }
|
semver = { version = "1", features = ["serde"] }
|
||||||
|
|