Compare commits
120 Commits
inline-ass
...
git-graph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1143de1d06 | ||
|
|
99e7aef145 | ||
|
|
80956e2037 | ||
|
|
391f6f1b04 | ||
|
|
38bb2ba7da | ||
|
|
4303e8e781 | ||
|
|
ed1dd89c44 | ||
|
|
94526ad28c | ||
|
|
ee4cd4d27a | ||
|
|
74df6d7db3 | ||
|
|
5080697b9b | ||
|
|
92affa2bf2 | ||
|
|
7479942fd2 | ||
|
|
8e6f2f5d97 | ||
|
|
d74612ce24 | ||
|
|
ce0f5259bc | ||
|
|
8e78337ec9 | ||
|
|
62bcaf41ee | ||
|
|
3bb908ce5d | ||
|
|
f86476a480 | ||
|
|
a686fc106a | ||
|
|
700d3cabac | ||
|
|
e8807aaa58 | ||
|
|
84b787ff32 | ||
|
|
ed6165f450 | ||
|
|
efc5c93d9c | ||
|
|
b948d8b9e7 | ||
|
|
bc17491527 | ||
|
|
f6a6630171 | ||
|
|
18421845eb | ||
|
|
21439426a0 | ||
|
|
044f7b5583 | ||
|
|
12dba5edbe | ||
|
|
216a3a60f5 | ||
|
|
66789607c9 | ||
|
|
62c312b35f | ||
|
|
066dd5c9d5 | ||
|
|
bdba6fd069 | ||
|
|
2260b87ea8 | ||
|
|
e6214b2b71 | ||
|
|
7ef45914e8 | ||
|
|
00e6cbc4fc | ||
|
|
9e0a4c2a9c | ||
|
|
a8b61c5ffa | ||
|
|
d312d59ace | ||
|
|
b4083ec47b | ||
|
|
e75a7aecd6 | ||
|
|
03cf7ddb53 | ||
|
|
9364d39487 | ||
|
|
f16913400a | ||
|
|
5bfc0baa4c | ||
|
|
d7b99a5b12 | ||
|
|
7691cf341c | ||
|
|
63cc90cd2c | ||
|
|
d1e45e27de | ||
|
|
9da0d40694 | ||
|
|
9f344f093e | ||
|
|
ef76f07b1e | ||
|
|
4577e1bf8f | ||
|
|
a574ae8779 | ||
|
|
16666f5357 | ||
|
|
b2e35b5f99 | ||
|
|
9e33243015 | ||
|
|
a0848daab4 | ||
|
|
d72746773f | ||
|
|
0565992d7a | ||
|
|
e1d8c1a6a1 | ||
|
|
f08fd732a7 | ||
|
|
51b7d06a27 | ||
|
|
66c7bdf037 | ||
|
|
363fbbf0d4 | ||
|
|
9860884217 | ||
|
|
4cef8eb47b | ||
|
|
e5f87735d3 | ||
|
|
f4b8b0f471 | ||
|
|
5cd30e5106 | ||
|
|
a350438a21 | ||
|
|
bd6ca841ad | ||
|
|
f9cea5af29 | ||
|
|
3bb6c2546a | ||
|
|
37b0cdf94b | ||
|
|
d76dd86272 | ||
|
|
b558be7ec6 | ||
|
|
07fe8e9bb1 | ||
|
|
b776178b52 | ||
|
|
1d0aef6b22 | ||
|
|
c7ef3025e4 | ||
|
|
822fc7ef16 | ||
|
|
126d708fa1 | ||
|
|
a5ab5c7d5d | ||
|
|
35da6d000a | ||
|
|
d6241b17d3 | ||
|
|
42583c1141 | ||
|
|
76167109db | ||
|
|
cd8679e81a | ||
|
|
43f977c6b9 | ||
|
|
bdb8caa42e | ||
|
|
9ae77ec3c9 | ||
|
|
d5ed9d3e3a | ||
|
|
74a1b5d14d | ||
|
|
07af011eb4 | ||
|
|
c357dc25fc | ||
|
|
93bc6616c6 | ||
|
|
a33e881906 | ||
|
|
c978db8626 | ||
|
|
2dad46c5c0 | ||
|
|
4c51fffbb5 | ||
|
|
0d80b452fb | ||
|
|
bad6bde03a | ||
|
|
4ec2d04ad9 | ||
|
|
0f0017dc8e | ||
|
|
9db0d66251 | ||
|
|
b07389d9f3 | ||
|
|
db2e26f67b | ||
|
|
391c92b07a | ||
|
|
1e4d80a21f | ||
|
|
f90d9d26a5 | ||
|
|
40a611bf34 | ||
|
|
8ad3a150c8 | ||
|
|
87976e91cf |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -39,3 +39,6 @@ xcuserdata/
|
|||||||
# Don't commit any secrets to the repo.
|
# Don't commit any secrets to the repo.
|
||||||
.env
|
.env
|
||||||
.env.secret.toml
|
.env.secret.toml
|
||||||
|
|
||||||
|
# `nix build` output
|
||||||
|
/result
|
||||||
|
|||||||
1537
Cargo.lock
generated
1537
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
56
Cargo.toml
56
Cargo.toml
@@ -54,9 +54,9 @@ members = [
|
|||||||
"crates/diagnostics",
|
"crates/diagnostics",
|
||||||
"crates/docs_preprocessor",
|
"crates/docs_preprocessor",
|
||||||
"crates/edit_prediction",
|
"crates/edit_prediction",
|
||||||
"crates/edit_prediction_button",
|
"crates/edit_prediction_types",
|
||||||
|
"crates/edit_prediction_ui",
|
||||||
"crates/edit_prediction_context",
|
"crates/edit_prediction_context",
|
||||||
"crates/zeta2_tools",
|
|
||||||
"crates/editor",
|
"crates/editor",
|
||||||
"crates/eval",
|
"crates/eval",
|
||||||
"crates/eval_utils",
|
"crates/eval_utils",
|
||||||
@@ -75,6 +75,7 @@ members = [
|
|||||||
"crates/fsevent",
|
"crates/fsevent",
|
||||||
"crates/fuzzy",
|
"crates/fuzzy",
|
||||||
"crates/git",
|
"crates/git",
|
||||||
|
"crates/git_graph",
|
||||||
"crates/git_hosting_providers",
|
"crates/git_hosting_providers",
|
||||||
"crates/git_ui",
|
"crates/git_ui",
|
||||||
"crates/go_to_line",
|
"crates/go_to_line",
|
||||||
@@ -201,10 +202,11 @@ members = [
|
|||||||
"crates/zed",
|
"crates/zed",
|
||||||
"crates/zed_actions",
|
"crates/zed_actions",
|
||||||
"crates/zed_env_vars",
|
"crates/zed_env_vars",
|
||||||
"crates/zeta",
|
"crates/edit_prediction_cli",
|
||||||
"crates/zeta_cli",
|
|
||||||
"crates/zlog",
|
"crates/zlog",
|
||||||
"crates/zlog_settings",
|
"crates/zlog_settings",
|
||||||
|
"crates/ztracing",
|
||||||
|
"crates/ztracing_macro",
|
||||||
|
|
||||||
#
|
#
|
||||||
# Extensions
|
# Extensions
|
||||||
@@ -243,7 +245,6 @@ 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" }
|
agent_servers = { path = "crates/agent_servers" }
|
||||||
ai = { path = "crates/ai" }
|
|
||||||
ai_onboarding = { path = "crates/ai_onboarding" }
|
ai_onboarding = { path = "crates/ai_onboarding" }
|
||||||
anthropic = { path = "crates/anthropic" }
|
anthropic = { path = "crates/anthropic" }
|
||||||
askpass = { path = "crates/askpass" }
|
askpass = { path = "crates/askpass" }
|
||||||
@@ -253,7 +254,6 @@ assistant_slash_command = { path = "crates/assistant_slash_command" }
|
|||||||
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
assistant_slash_commands = { path = "crates/assistant_slash_commands" }
|
||||||
audio = { path = "crates/audio" }
|
audio = { path = "crates/audio" }
|
||||||
auto_update = { path = "crates/auto_update" }
|
auto_update = { path = "crates/auto_update" }
|
||||||
auto_update_helper = { path = "crates/auto_update_helper" }
|
|
||||||
auto_update_ui = { path = "crates/auto_update_ui" }
|
auto_update_ui = { path = "crates/auto_update_ui" }
|
||||||
aws_http_client = { path = "crates/aws_http_client" }
|
aws_http_client = { path = "crates/aws_http_client" }
|
||||||
bedrock = { path = "crates/bedrock" }
|
bedrock = { path = "crates/bedrock" }
|
||||||
@@ -268,7 +268,6 @@ cloud_api_client = { path = "crates/cloud_api_client" }
|
|||||||
cloud_api_types = { path = "crates/cloud_api_types" }
|
cloud_api_types = { path = "crates/cloud_api_types" }
|
||||||
cloud_llm_client = { path = "crates/cloud_llm_client" }
|
cloud_llm_client = { path = "crates/cloud_llm_client" }
|
||||||
cloud_zeta2_prompt = { path = "crates/cloud_zeta2_prompt" }
|
cloud_zeta2_prompt = { path = "crates/cloud_zeta2_prompt" }
|
||||||
collab = { path = "crates/collab" }
|
|
||||||
collab_ui = { path = "crates/collab_ui" }
|
collab_ui = { path = "crates/collab_ui" }
|
||||||
collections = { path = "crates/collections", version = "0.1.0" }
|
collections = { path = "crates/collections", version = "0.1.0" }
|
||||||
command_palette = { path = "crates/command_palette" }
|
command_palette = { path = "crates/command_palette" }
|
||||||
@@ -301,6 +300,7 @@ fs = { path = "crates/fs" }
|
|||||||
fsevent = { path = "crates/fsevent" }
|
fsevent = { path = "crates/fsevent" }
|
||||||
fuzzy = { path = "crates/fuzzy" }
|
fuzzy = { path = "crates/fuzzy" }
|
||||||
git = { path = "crates/git" }
|
git = { path = "crates/git" }
|
||||||
|
git_graph = { path = "crates/git_graph" }
|
||||||
git_hosting_providers = { path = "crates/git_hosting_providers" }
|
git_hosting_providers = { path = "crates/git_hosting_providers" }
|
||||||
git_ui = { path = "crates/git_ui" }
|
git_ui = { path = "crates/git_ui" }
|
||||||
go_to_line = { path = "crates/go_to_line" }
|
go_to_line = { path = "crates/go_to_line" }
|
||||||
@@ -313,10 +313,9 @@ http_client = { path = "crates/http_client" }
|
|||||||
http_client_tls = { path = "crates/http_client_tls" }
|
http_client_tls = { path = "crates/http_client_tls" }
|
||||||
icons = { path = "crates/icons" }
|
icons = { path = "crates/icons" }
|
||||||
image_viewer = { path = "crates/image_viewer" }
|
image_viewer = { path = "crates/image_viewer" }
|
||||||
edit_prediction = { path = "crates/edit_prediction" }
|
edit_prediction_types = { path = "crates/edit_prediction_types" }
|
||||||
edit_prediction_button = { path = "crates/edit_prediction_button" }
|
edit_prediction_ui = { path = "crates/edit_prediction_ui" }
|
||||||
edit_prediction_context = { path = "crates/edit_prediction_context" }
|
edit_prediction_context = { path = "crates/edit_prediction_context" }
|
||||||
zeta2_tools = { path = "crates/zeta2_tools" }
|
|
||||||
inspector_ui = { path = "crates/inspector_ui" }
|
inspector_ui = { path = "crates/inspector_ui" }
|
||||||
install_cli = { path = "crates/install_cli" }
|
install_cli = { path = "crates/install_cli" }
|
||||||
journal = { path = "crates/journal" }
|
journal = { path = "crates/journal" }
|
||||||
@@ -358,8 +357,6 @@ panel = { path = "crates/panel" }
|
|||||||
paths = { path = "crates/paths" }
|
paths = { path = "crates/paths" }
|
||||||
perf = { path = "tooling/perf" }
|
perf = { path = "tooling/perf" }
|
||||||
picker = { path = "crates/picker" }
|
picker = { path = "crates/picker" }
|
||||||
plugin = { path = "crates/plugin" }
|
|
||||||
plugin_macros = { path = "crates/plugin_macros" }
|
|
||||||
prettier = { path = "crates/prettier" }
|
prettier = { path = "crates/prettier" }
|
||||||
settings_profile_selector = { path = "crates/settings_profile_selector" }
|
settings_profile_selector = { path = "crates/settings_profile_selector" }
|
||||||
project = { path = "crates/project" }
|
project = { path = "crates/project" }
|
||||||
@@ -370,12 +367,10 @@ proto = { path = "crates/proto" }
|
|||||||
recent_projects = { path = "crates/recent_projects" }
|
recent_projects = { path = "crates/recent_projects" }
|
||||||
refineable = { path = "crates/refineable" }
|
refineable = { path = "crates/refineable" }
|
||||||
release_channel = { path = "crates/release_channel" }
|
release_channel = { path = "crates/release_channel" }
|
||||||
scheduler = { path = "crates/scheduler" }
|
|
||||||
remote = { path = "crates/remote" }
|
remote = { path = "crates/remote" }
|
||||||
remote_server = { path = "crates/remote_server" }
|
remote_server = { path = "crates/remote_server" }
|
||||||
repl = { path = "crates/repl" }
|
repl = { path = "crates/repl" }
|
||||||
reqwest_client = { path = "crates/reqwest_client" }
|
reqwest_client = { path = "crates/reqwest_client" }
|
||||||
rich_text = { path = "crates/rich_text" }
|
|
||||||
rodio = { git = "https://github.com/RustAudio/rodio", rev ="e2074c6c2acf07b57cf717e076bdda7a9ac6e70b", features = ["wav", "playback", "wav_output", "recording"] }
|
rodio = { git = "https://github.com/RustAudio/rodio", rev ="e2074c6c2acf07b57cf717e076bdda7a9ac6e70b", features = ["wav", "playback", "wav_output", "recording"] }
|
||||||
rope = { path = "crates/rope" }
|
rope = { path = "crates/rope" }
|
||||||
rpc = { path = "crates/rpc" }
|
rpc = { path = "crates/rpc" }
|
||||||
@@ -392,7 +387,6 @@ snippets_ui = { path = "crates/snippets_ui" }
|
|||||||
sqlez = { path = "crates/sqlez" }
|
sqlez = { path = "crates/sqlez" }
|
||||||
sqlez_macros = { path = "crates/sqlez_macros" }
|
sqlez_macros = { path = "crates/sqlez_macros" }
|
||||||
story = { path = "crates/story" }
|
story = { path = "crates/story" }
|
||||||
storybook = { path = "crates/storybook" }
|
|
||||||
streaming_diff = { path = "crates/streaming_diff" }
|
streaming_diff = { path = "crates/streaming_diff" }
|
||||||
sum_tree = { path = "crates/sum_tree" }
|
sum_tree = { path = "crates/sum_tree" }
|
||||||
supermaven = { path = "crates/supermaven" }
|
supermaven = { path = "crates/supermaven" }
|
||||||
@@ -409,7 +403,6 @@ terminal_view = { path = "crates/terminal_view" }
|
|||||||
text = { path = "crates/text" }
|
text = { path = "crates/text" }
|
||||||
theme = { path = "crates/theme" }
|
theme = { path = "crates/theme" }
|
||||||
theme_extension = { path = "crates/theme_extension" }
|
theme_extension = { path = "crates/theme_extension" }
|
||||||
theme_importer = { path = "crates/theme_importer" }
|
|
||||||
theme_selector = { path = "crates/theme_selector" }
|
theme_selector = { path = "crates/theme_selector" }
|
||||||
time_format = { path = "crates/time_format" }
|
time_format = { path = "crates/time_format" }
|
||||||
title_bar = { path = "crates/title_bar" }
|
title_bar = { path = "crates/title_bar" }
|
||||||
@@ -433,15 +426,17 @@ x_ai = { path = "crates/x_ai" }
|
|||||||
zed = { path = "crates/zed" }
|
zed = { path = "crates/zed" }
|
||||||
zed_actions = { path = "crates/zed_actions" }
|
zed_actions = { path = "crates/zed_actions" }
|
||||||
zed_env_vars = { path = "crates/zed_env_vars" }
|
zed_env_vars = { path = "crates/zed_env_vars" }
|
||||||
zeta = { path = "crates/zeta" }
|
edit_prediction = { path = "crates/edit_prediction" }
|
||||||
zlog = { path = "crates/zlog" }
|
zlog = { path = "crates/zlog" }
|
||||||
zlog_settings = { path = "crates/zlog_settings" }
|
zlog_settings = { path = "crates/zlog_settings" }
|
||||||
|
ztracing = { path = "crates/ztracing" }
|
||||||
|
ztracing_macro = { path = "crates/ztracing_macro" }
|
||||||
|
|
||||||
#
|
#
|
||||||
# External crates
|
# External crates
|
||||||
#
|
#
|
||||||
|
|
||||||
agent-client-protocol = { version = "=0.8.0", features = ["unstable"] }
|
agent-client-protocol = { version = "=0.9.0", features = ["unstable"] }
|
||||||
aho-corasick = "1.1"
|
aho-corasick = "1.1"
|
||||||
alacritty_terminal = "0.25.1-rc1"
|
alacritty_terminal = "0.25.1-rc1"
|
||||||
any_vec = "0.14"
|
any_vec = "0.14"
|
||||||
@@ -505,16 +500,14 @@ ec4rs = "1.1"
|
|||||||
emojis = "0.6.1"
|
emojis = "0.6.1"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
exec = "0.3.1"
|
exec = "0.3.1"
|
||||||
fancy-regex = "0.14.0"
|
fancy-regex = "0.16.0"
|
||||||
fork = "0.4.0"
|
fork = "0.4.0"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
futures-batch = "0.6.1"
|
|
||||||
futures-lite = "1.13"
|
futures-lite = "1.13"
|
||||||
gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "09acfdf2bd5c1d6254abefd609c808ff73547b2c" }
|
gh-workflow = { git = "https://github.com/zed-industries/gh-workflow", rev = "09acfdf2bd5c1d6254abefd609c808ff73547b2c" }
|
||||||
git2 = { version = "0.20.1", default-features = false }
|
git2 = { version = "0.20.1", default-features = false }
|
||||||
globset = "0.4"
|
globset = "0.4"
|
||||||
handlebars = "4.3"
|
handlebars = "4.3"
|
||||||
hashbrown = "0.15.3"
|
|
||||||
heck = "0.5"
|
heck = "0.5"
|
||||||
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
@@ -531,7 +524,7 @@ indoc = "2"
|
|||||||
inventory = "0.3.19"
|
inventory = "0.3.19"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
json_dotpath = "1.1"
|
json_dotpath = "1.1"
|
||||||
jsonschema = "0.30.0"
|
jsonschema = "0.37.0"
|
||||||
jsonwebtoken = "9.3"
|
jsonwebtoken = "9.3"
|
||||||
jupyter-protocol = "0.10.0"
|
jupyter-protocol = "0.10.0"
|
||||||
jupyter-websocket-client = "0.15.0"
|
jupyter-websocket-client = "0.15.0"
|
||||||
@@ -550,7 +543,6 @@ nanoid = "0.4"
|
|||||||
nbformat = "0.15.0"
|
nbformat = "0.15.0"
|
||||||
nix = "0.29"
|
nix = "0.29"
|
||||||
num-format = "0.4.4"
|
num-format = "0.4.4"
|
||||||
num-traits = "0.2"
|
|
||||||
objc = "0.2"
|
objc = "0.2"
|
||||||
objc2-foundation = { version = "=0.3.1", default-features = false, features = [
|
objc2-foundation = { version = "=0.3.1", default-features = false, features = [
|
||||||
"NSArray",
|
"NSArray",
|
||||||
@@ -589,7 +581,6 @@ pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev =
|
|||||||
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||||
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
pet-core = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||||
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
pet-fs = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||||
pet-pixi = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
|
||||||
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
pet-poetry = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||||
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
pet-reporter = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||||
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
pet-virtualenv = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "1e86914c3ce2f3a08c0cedbcb0615a7f9fa7a5da" }
|
||||||
@@ -629,7 +620,6 @@ scap = { git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197
|
|||||||
schemars = { version = "1.0", features = ["indexmap2"] }
|
schemars = { version = "1.0", features = ["indexmap2"] }
|
||||||
semver = { version = "1.0", features = ["serde"] }
|
semver = { version = "1.0", features = ["serde"] }
|
||||||
serde = { version = "1.0.221", features = ["derive", "rc"] }
|
serde = { version = "1.0.221", features = ["derive", "rc"] }
|
||||||
serde_derive = "1.0.221"
|
|
||||||
serde_json = { version = "1.0.144", features = ["preserve_order", "raw_value"] }
|
serde_json = { version = "1.0.144", features = ["preserve_order", "raw_value"] }
|
||||||
serde_json_lenient = { version = "0.2", features = [
|
serde_json_lenient = { version = "0.2", features = [
|
||||||
"preserve_order",
|
"preserve_order",
|
||||||
@@ -641,7 +631,6 @@ serde_urlencoded = "0.7"
|
|||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
shellexpand = "2.1.0"
|
shellexpand = "2.1.0"
|
||||||
shlex = "1.3.0"
|
shlex = "1.3.0"
|
||||||
similar = "2.6"
|
|
||||||
simplelog = "0.12.2"
|
simplelog = "0.12.2"
|
||||||
slotmap = "1.0.6"
|
slotmap = "1.0.6"
|
||||||
smallvec = { version = "1.6", features = ["union"] }
|
smallvec = { version = "1.6", features = ["union"] }
|
||||||
@@ -658,7 +647,7 @@ sysinfo = "0.37.0"
|
|||||||
take-until = "0.2.0"
|
take-until = "0.2.0"
|
||||||
tempfile = "3.20.0"
|
tempfile = "3.20.0"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
tiktoken-rs = { git = "https://github.com/zed-industries/tiktoken-rs", rev = "7249f999c5fdf9bf3cc5c288c964454e4dac0c00" }
|
tiktoken-rs = { git = "https://github.com/zed-industries/tiktoken-rs", rev = "2570c4387a8505fb8f1d3f3557454b474f1e8271" }
|
||||||
time = { version = "0.3", features = [
|
time = { version = "0.3", features = [
|
||||||
"macros",
|
"macros",
|
||||||
"parsing",
|
"parsing",
|
||||||
@@ -696,6 +685,7 @@ tree-sitter-ruby = "0.23"
|
|||||||
tree-sitter-rust = "0.24"
|
tree-sitter-rust = "0.24"
|
||||||
tree-sitter-typescript = { git = "https://github.com/zed-industries/tree-sitter-typescript", rev = "e2c53597d6a5d9cf7bbe8dccde576fe1e46c5899" } # https://github.com/tree-sitter/tree-sitter-typescript/pull/347
|
tree-sitter-typescript = { git = "https://github.com/zed-industries/tree-sitter-typescript", rev = "e2c53597d6a5d9cf7bbe8dccde576fe1e46c5899" } # https://github.com/tree-sitter/tree-sitter-typescript/pull/347
|
||||||
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
|
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "baff0b51c64ef6a1fb1f8390f3ad6015b83ec13a" }
|
||||||
|
tracing = "0.1.40"
|
||||||
unicase = "2.6"
|
unicase = "2.6"
|
||||||
unicode-script = "0.5.7"
|
unicode-script = "0.5.7"
|
||||||
unicode-segmentation = "1.10"
|
unicode-segmentation = "1.10"
|
||||||
@@ -719,7 +709,6 @@ wasmtime-wasi = "29"
|
|||||||
wax = "0.6"
|
wax = "0.6"
|
||||||
which = "6.0.0"
|
which = "6.0.0"
|
||||||
windows-core = "0.61"
|
windows-core = "0.61"
|
||||||
wit-component = "0.221"
|
|
||||||
yawc = "0.2.5"
|
yawc = "0.2.5"
|
||||||
zeroize = "1.8"
|
zeroize = "1.8"
|
||||||
zstd = "0.11"
|
zstd = "0.11"
|
||||||
@@ -801,20 +790,13 @@ settings_macros = { opt-level = 3 }
|
|||||||
sqlez_macros = { opt-level = 3, codegen-units = 1 }
|
sqlez_macros = { opt-level = 3, codegen-units = 1 }
|
||||||
ui_macros = { opt-level = 3 }
|
ui_macros = { opt-level = 3 }
|
||||||
util_macros = { opt-level = 3 }
|
util_macros = { opt-level = 3 }
|
||||||
serde_derive = { opt-level = 3 }
|
|
||||||
quote = { opt-level = 3 }
|
quote = { opt-level = 3 }
|
||||||
syn = { opt-level = 3 }
|
syn = { opt-level = 3 }
|
||||||
proc-macro2 = { opt-level = 3 }
|
proc-macro2 = { opt-level = 3 }
|
||||||
# proc-macros end
|
# proc-macros end
|
||||||
|
|
||||||
taffy = { opt-level = 3 }
|
taffy = { opt-level = 3 }
|
||||||
cranelift-codegen = { opt-level = 3 }
|
|
||||||
cranelift-codegen-meta = { opt-level = 3 }
|
|
||||||
cranelift-codegen-shared = { opt-level = 3 }
|
|
||||||
resvg = { opt-level = 3 }
|
resvg = { opt-level = 3 }
|
||||||
rustybuzz = { opt-level = 3 }
|
|
||||||
ttf-parser = { opt-level = 3 }
|
|
||||||
wasmtime-cranelift = { opt-level = 3 }
|
|
||||||
wasmtime = { opt-level = 3 }
|
wasmtime = { opt-level = 3 }
|
||||||
# Build single-source-file crates with cg=1 as it helps make `cargo build` of a whole workspace a bit faster
|
# Build single-source-file crates with cg=1 as it helps make `cargo build` of a whole workspace a bit faster
|
||||||
activity_indicator = { codegen-units = 1 }
|
activity_indicator = { codegen-units = 1 }
|
||||||
@@ -823,12 +805,11 @@ breadcrumbs = { codegen-units = 1 }
|
|||||||
collections = { codegen-units = 1 }
|
collections = { codegen-units = 1 }
|
||||||
command_palette = { codegen-units = 1 }
|
command_palette = { codegen-units = 1 }
|
||||||
command_palette_hooks = { codegen-units = 1 }
|
command_palette_hooks = { codegen-units = 1 }
|
||||||
extension_cli = { codegen-units = 1 }
|
|
||||||
feature_flags = { codegen-units = 1 }
|
feature_flags = { codegen-units = 1 }
|
||||||
file_icons = { codegen-units = 1 }
|
file_icons = { codegen-units = 1 }
|
||||||
fsevent = { codegen-units = 1 }
|
fsevent = { codegen-units = 1 }
|
||||||
image_viewer = { codegen-units = 1 }
|
image_viewer = { codegen-units = 1 }
|
||||||
edit_prediction_button = { codegen-units = 1 }
|
edit_prediction_ui = { codegen-units = 1 }
|
||||||
install_cli = { codegen-units = 1 }
|
install_cli = { codegen-units = 1 }
|
||||||
journal = { codegen-units = 1 }
|
journal = { codegen-units = 1 }
|
||||||
json_schema_store = { codegen-units = 1 }
|
json_schema_store = { codegen-units = 1 }
|
||||||
@@ -843,7 +824,6 @@ project_symbols = { codegen-units = 1 }
|
|||||||
refineable = { codegen-units = 1 }
|
refineable = { codegen-units = 1 }
|
||||||
release_channel = { codegen-units = 1 }
|
release_channel = { codegen-units = 1 }
|
||||||
reqwest_client = { codegen-units = 1 }
|
reqwest_client = { codegen-units = 1 }
|
||||||
rich_text = { codegen-units = 1 }
|
|
||||||
session = { codegen-units = 1 }
|
session = { codegen-units = 1 }
|
||||||
snippet = { codegen-units = 1 }
|
snippet = { codegen-units = 1 }
|
||||||
snippets_ui = { codegen-units = 1 }
|
snippets_ui = { codegen-units = 1 }
|
||||||
|
|||||||
8
assets/icons/git_branch_plus.svg
Normal file
8
assets/icons/git_branch_plus.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 2V10" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 6C12.5304 6 13.0391 5.78929 13.4142 5.41421C13.7893 5.03914 14 4.53043 14 4C14 3.46957 13.7893 2.96086 13.4142 2.58579C13.0391 2.21071 12.5304 2 12 2C11.4696 2 10.9609 2.21071 10.5858 2.58579C10.2107 2.96086 10 3.46957 10 4C10 4.53043 10.2107 5.03914 10.5858 5.41421C10.9609 5.78929 11.4696 6 12 6Z" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4 14C4.53043 14 5.03914 13.7893 5.41421 13.4142C5.78929 13.0391 6 12.5304 6 12C6 11.4696 5.78929 10.9609 5.41421 10.5858C5.03914 10.2107 4.53043 10 4 10C3.46957 10 2.96086 10.2107 2.58579 10.5858C2.21071 10.9609 2 11.4696 2 12C2 12.5304 2.21071 13.0391 2.58579 13.4142C2.96086 13.7893 3.46957 14 4 14Z" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M10 4C8.4087 4 6.88258 4.63214 5.75736 5.75736C4.63214 6.88258 4 8.4087 4 10" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 10V14" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14 12H10" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
11
assets/icons/inception.svg
Normal file
11
assets/icons/inception.svg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" id="svg1378540956_510">
|
||||||
|
<g clip-path="url(#svg1378540956_510_clip0_1_1506)" transform="translate(4, 4) scale(0.857)">
|
||||||
|
<path d="M17.0547 0.372066H8.52652L-0.00165176 8.90024V17.4284H8.52652V8.90024H17.0547V0.372066Z" fill="#1A1C20"></path>
|
||||||
|
<path d="M10.1992 27.6279H18.7274L27.2556 19.0998V10.5716H18.7274V19.0998H10.1992V27.6279Z" fill="#1A1C20"></path>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="svg1378540956_510_clip0_1_1506">
|
||||||
|
<rect width="27.2559" height="27.2559" fill="white" transform="translate(0 0.37207)"></rect>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 593 B |
@@ -41,7 +41,7 @@
|
|||||||
"ctrl-f11": "debugger::StepInto",
|
"ctrl-f11": "debugger::StepInto",
|
||||||
"shift-f11": "debugger::StepOut",
|
"shift-f11": "debugger::StepOut",
|
||||||
"f11": "zed::ToggleFullScreen",
|
"f11": "zed::ToggleFullScreen",
|
||||||
"ctrl-alt-z": "edit_prediction::RateCompletions",
|
"ctrl-alt-z": "edit_prediction::RatePredictions",
|
||||||
"ctrl-alt-shift-i": "edit_prediction::ToggleMenu",
|
"ctrl-alt-shift-i": "edit_prediction::ToggleMenu",
|
||||||
"ctrl-alt-l": "lsp_tool::ToggleMenu"
|
"ctrl-alt-l": "lsp_tool::ToggleMenu"
|
||||||
}
|
}
|
||||||
@@ -616,8 +616,8 @@
|
|||||||
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
|
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
|
||||||
"ctrl-t": "project_symbols::Toggle",
|
"ctrl-t": "project_symbols::Toggle",
|
||||||
"ctrl-p": "file_finder::Toggle",
|
"ctrl-p": "file_finder::Toggle",
|
||||||
"ctrl-tab": "tab_switcher::Toggle",
|
|
||||||
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
|
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
|
||||||
|
"ctrl-tab": "tab_switcher::Toggle",
|
||||||
"ctrl-e": "file_finder::Toggle",
|
"ctrl-e": "file_finder::Toggle",
|
||||||
"f1": "command_palette::Toggle",
|
"f1": "command_palette::Toggle",
|
||||||
"ctrl-shift-p": "command_palette::Toggle",
|
"ctrl-shift-p": "command_palette::Toggle",
|
||||||
@@ -1322,25 +1322,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Zeta2Feedback > Editor",
|
"context": "EditPredictionContext > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::Newline",
|
"alt-left": "dev::EditPredictionContextGoBack",
|
||||||
"ctrl-enter up": "dev::Zeta2RatePredictionPositive",
|
"alt-right": "dev::EditPredictionContextGoForward"
|
||||||
"ctrl-enter down": "dev::Zeta2RatePredictionNegative"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "Zeta2Context > Editor",
|
|
||||||
"bindings": {
|
|
||||||
"alt-left": "dev::Zeta2ContextGoBack",
|
|
||||||
"alt-right": "dev::Zeta2ContextGoForward"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "GitBranchSelector || (GitBranchSelector > Picker > Editor)",
|
"context": "GitBranchSelector || (GitBranchSelector > Picker > Editor)",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-shift-backspace": "branch_picker::DeleteBranch"
|
"ctrl-shift-backspace": "branch_picker::DeleteBranch",
|
||||||
|
"ctrl-shift-i": "branch_picker::FilterRemotes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,8 @@
|
|||||||
"ctrl-alt-enter": ["picker::ConfirmInput", { "secondary": true }],
|
"ctrl-alt-enter": ["picker::ConfirmInput", { "secondary": true }],
|
||||||
"ctrl-shift-w": "workspace::CloseWindow",
|
"ctrl-shift-w": "workspace::CloseWindow",
|
||||||
"shift-escape": "workspace::ToggleZoom",
|
"shift-escape": "workspace::ToggleZoom",
|
||||||
"ctrl-o": "workspace::Open",
|
"ctrl-o": "workspace::OpenFiles",
|
||||||
|
"ctrl-k ctrl-o": "workspace::Open",
|
||||||
"ctrl-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
"ctrl-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
||||||
"ctrl-shift-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
"ctrl-shift-=": ["zed::IncreaseBufferFontSize", { "persist": false }],
|
||||||
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
|
"ctrl--": ["zed::DecreaseBufferFontSize", { "persist": false }],
|
||||||
@@ -608,8 +609,8 @@
|
|||||||
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
|
"ctrl-alt-super-p": "settings_profile_selector::Toggle",
|
||||||
"ctrl-t": "project_symbols::Toggle",
|
"ctrl-t": "project_symbols::Toggle",
|
||||||
"ctrl-p": "file_finder::Toggle",
|
"ctrl-p": "file_finder::Toggle",
|
||||||
"ctrl-tab": "tab_switcher::Toggle",
|
|
||||||
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
|
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
|
||||||
|
"ctrl-tab": "tab_switcher::Toggle",
|
||||||
"ctrl-e": "file_finder::Toggle",
|
"ctrl-e": "file_finder::Toggle",
|
||||||
"f1": "command_palette::Toggle",
|
"f1": "command_palette::Toggle",
|
||||||
"ctrl-shift-p": "command_palette::Toggle",
|
"ctrl-shift-p": "command_palette::Toggle",
|
||||||
@@ -1128,6 +1129,8 @@
|
|||||||
"ctrl-e": ["terminal::SendKeystroke", "ctrl-e"],
|
"ctrl-e": ["terminal::SendKeystroke", "ctrl-e"],
|
||||||
"ctrl-o": ["terminal::SendKeystroke", "ctrl-o"],
|
"ctrl-o": ["terminal::SendKeystroke", "ctrl-o"],
|
||||||
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
"ctrl-w": ["terminal::SendKeystroke", "ctrl-w"],
|
||||||
|
"ctrl-q": ["terminal::SendKeystroke", "ctrl-q"],
|
||||||
|
"ctrl-r": ["terminal::SendKeystroke", "ctrl-r"],
|
||||||
"ctrl-backspace": ["terminal::SendKeystroke", "ctrl-w"],
|
"ctrl-backspace": ["terminal::SendKeystroke", "ctrl-w"],
|
||||||
"ctrl-shift-a": "editor::SelectAll",
|
"ctrl-shift-a": "editor::SelectAll",
|
||||||
"ctrl-shift-f": "buffer_search::Deploy",
|
"ctrl-shift-f": "buffer_search::Deploy",
|
||||||
@@ -1341,25 +1344,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Zeta2Feedback > Editor",
|
"context": "EditPredictionContext > Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"enter": "editor::Newline",
|
"alt-left": "dev::EditPredictionContextGoBack",
|
||||||
"ctrl-enter up": "dev::Zeta2RatePredictionPositive",
|
"alt-right": "dev::EditPredictionContextGoForward"
|
||||||
"ctrl-enter down": "dev::Zeta2RatePredictionNegative"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"context": "Zeta2Context > Editor",
|
|
||||||
"bindings": {
|
|
||||||
"alt-left": "dev::Zeta2ContextGoBack",
|
|
||||||
"alt-right": "dev::Zeta2ContextGoForward"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "GitBranchSelector || (GitBranchSelector > Picker > Editor)",
|
"context": "GitBranchSelector || (GitBranchSelector > Picker > Editor)",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-shift-backspace": "branch_picker::DeleteBranch"
|
"ctrl-shift-backspace": "branch_picker::DeleteBranch",
|
||||||
|
"ctrl-shift-i": "branch_picker::FilterRemotes"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
44
assets/prompts/content_prompt_v2.hbs
Normal file
44
assets/prompts/content_prompt_v2.hbs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{{#if language_name}}
|
||||||
|
Here's a file of {{language_name}} that the user is going to ask you to make an edit to.
|
||||||
|
{{else}}
|
||||||
|
Here's a file of text that the user is going to ask you to make an edit to.
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
The section you'll need to rewrite is marked with <rewrite_this></rewrite_this> tags.
|
||||||
|
|
||||||
|
<document>
|
||||||
|
{{{document_content}}}
|
||||||
|
</document>
|
||||||
|
|
||||||
|
{{#if is_truncated}}
|
||||||
|
The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if rewrite_section}}
|
||||||
|
And here's the section to rewrite based on that prompt again for reference:
|
||||||
|
|
||||||
|
<rewrite_this>
|
||||||
|
{{{rewrite_section}}}
|
||||||
|
</rewrite_this>
|
||||||
|
|
||||||
|
{{#if diagnostic_errors}}
|
||||||
|
Below are the diagnostic errors visible to the user. If the user requests problems to be fixed, use this information, but do not try to fix these errors if the user hasn't asked you to.
|
||||||
|
|
||||||
|
{{#each diagnostic_errors}}
|
||||||
|
<diagnostic_error>
|
||||||
|
<line_number>{{line_number}}</line_number>
|
||||||
|
<error_message>{{error_message}}</error_message>
|
||||||
|
<code_content>{{code_content}}</code_content>
|
||||||
|
</diagnostic_error>
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {{content_type}} will be preserved.
|
||||||
|
|
||||||
|
Start at the indentation level in the original file in the rewritten {{content_type}}.
|
||||||
|
|
||||||
|
You must use one of the provided tools to make the rewrite or to provide an explanation as to why the user's request cannot be fulfilled. It is an error if
|
||||||
|
you simply send back unstructured text. If you need to make a statement or ask a question you must use one of the tools to do so.
|
||||||
|
It is an error if you try to make a change that cannot be made simply by editing the rewrite_section.
|
||||||
@@ -1100,13 +1100,22 @@
|
|||||||
"preview_tabs": {
|
"preview_tabs": {
|
||||||
// Whether preview tabs should be enabled.
|
// Whether preview tabs should be enabled.
|
||||||
// Preview tabs allow you to open files in preview mode, where they close automatically
|
// Preview tabs allow you to open files in preview mode, where they close automatically
|
||||||
// when you switch to another file unless you explicitly pin them.
|
// when you open another preview tab.
|
||||||
// This is useful for quickly viewing files without cluttering your workspace.
|
// This is useful for quickly viewing files without cluttering your workspace.
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
// Whether to open tabs in preview mode when opened from the project panel with a single click.
|
||||||
|
"enable_preview_from_project_panel": true,
|
||||||
// Whether to open tabs in preview mode when selected from the file finder.
|
// Whether to open tabs in preview mode when selected from the file finder.
|
||||||
"enable_preview_from_file_finder": false,
|
"enable_preview_from_file_finder": false,
|
||||||
// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
|
// Whether to open tabs in preview mode when opened from a multibuffer.
|
||||||
"enable_preview_from_code_navigation": false
|
"enable_preview_from_multibuffer": true,
|
||||||
|
// Whether to open tabs in preview mode when code navigation is used to open a multibuffer.
|
||||||
|
"enable_preview_multibuffer_from_code_navigation": false,
|
||||||
|
// Whether to open tabs in preview mode when code navigation is used to open a single file.
|
||||||
|
"enable_preview_file_from_code_navigation": true,
|
||||||
|
// Whether to keep tabs in preview mode when code navigation is used to navigate away from them.
|
||||||
|
// If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one.
|
||||||
|
"enable_keep_preview_on_code_navigation": false
|
||||||
},
|
},
|
||||||
// Settings related to the file finder.
|
// Settings related to the file finder.
|
||||||
"file_finder": {
|
"file_finder": {
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"tab.inactive_background": "#1f2127ff",
|
"tab.inactive_background": "#1f2127ff",
|
||||||
"tab.active_background": "#0d1016ff",
|
"tab.active_background": "#0d1016ff",
|
||||||
"search.match_background": "#5ac2fe66",
|
"search.match_background": "#5ac2fe66",
|
||||||
|
"search.active_match_background": "#ea570166",
|
||||||
"panel.background": "#1f2127ff",
|
"panel.background": "#1f2127ff",
|
||||||
"panel.focused_border": "#5ac1feff",
|
"panel.focused_border": "#5ac1feff",
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
@@ -436,6 +437,7 @@
|
|||||||
"tab.inactive_background": "#ececedff",
|
"tab.inactive_background": "#ececedff",
|
||||||
"tab.active_background": "#fcfcfcff",
|
"tab.active_background": "#fcfcfcff",
|
||||||
"search.match_background": "#3b9ee566",
|
"search.match_background": "#3b9ee566",
|
||||||
|
"search.active_match_background": "#f88b3666",
|
||||||
"panel.background": "#ececedff",
|
"panel.background": "#ececedff",
|
||||||
"panel.focused_border": "#3b9ee5ff",
|
"panel.focused_border": "#3b9ee5ff",
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
@@ -827,6 +829,7 @@
|
|||||||
"tab.inactive_background": "#353944ff",
|
"tab.inactive_background": "#353944ff",
|
||||||
"tab.active_background": "#242835ff",
|
"tab.active_background": "#242835ff",
|
||||||
"search.match_background": "#73cffe66",
|
"search.match_background": "#73cffe66",
|
||||||
|
"search.active_match_background": "#fd722b66",
|
||||||
"panel.background": "#353944ff",
|
"panel.background": "#353944ff",
|
||||||
"panel.focused_border": null,
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
"tab.inactive_background": "#3a3735ff",
|
"tab.inactive_background": "#3a3735ff",
|
||||||
"tab.active_background": "#282828ff",
|
"tab.active_background": "#282828ff",
|
||||||
"search.match_background": "#83a59866",
|
"search.match_background": "#83a59866",
|
||||||
|
"search.active_match_background": "#c09f3f66",
|
||||||
"panel.background": "#3a3735ff",
|
"panel.background": "#3a3735ff",
|
||||||
"panel.focused_border": "#83a598ff",
|
"panel.focused_border": "#83a598ff",
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
@@ -452,6 +453,7 @@
|
|||||||
"tab.inactive_background": "#393634ff",
|
"tab.inactive_background": "#393634ff",
|
||||||
"tab.active_background": "#1d2021ff",
|
"tab.active_background": "#1d2021ff",
|
||||||
"search.match_background": "#83a59866",
|
"search.match_background": "#83a59866",
|
||||||
|
"search.active_match_background": "#c9653666",
|
||||||
"panel.background": "#393634ff",
|
"panel.background": "#393634ff",
|
||||||
"panel.focused_border": "#83a598ff",
|
"panel.focused_border": "#83a598ff",
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
@@ -858,6 +860,7 @@
|
|||||||
"tab.inactive_background": "#3b3735ff",
|
"tab.inactive_background": "#3b3735ff",
|
||||||
"tab.active_background": "#32302fff",
|
"tab.active_background": "#32302fff",
|
||||||
"search.match_background": "#83a59866",
|
"search.match_background": "#83a59866",
|
||||||
|
"search.active_match_background": "#aea85166",
|
||||||
"panel.background": "#3b3735ff",
|
"panel.background": "#3b3735ff",
|
||||||
"panel.focused_border": null,
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
@@ -1264,6 +1267,7 @@
|
|||||||
"tab.inactive_background": "#ecddb4ff",
|
"tab.inactive_background": "#ecddb4ff",
|
||||||
"tab.active_background": "#fbf1c7ff",
|
"tab.active_background": "#fbf1c7ff",
|
||||||
"search.match_background": "#0b667866",
|
"search.match_background": "#0b667866",
|
||||||
|
"search.active_match_background": "#ba2d1166",
|
||||||
"panel.background": "#ecddb4ff",
|
"panel.background": "#ecddb4ff",
|
||||||
"panel.focused_border": null,
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
@@ -1670,6 +1674,7 @@
|
|||||||
"tab.inactive_background": "#ecddb5ff",
|
"tab.inactive_background": "#ecddb5ff",
|
||||||
"tab.active_background": "#f9f5d7ff",
|
"tab.active_background": "#f9f5d7ff",
|
||||||
"search.match_background": "#0b667866",
|
"search.match_background": "#0b667866",
|
||||||
|
"search.active_match_background": "#dc351466",
|
||||||
"panel.background": "#ecddb5ff",
|
"panel.background": "#ecddb5ff",
|
||||||
"panel.focused_border": null,
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
@@ -2076,6 +2081,7 @@
|
|||||||
"tab.inactive_background": "#ecdcb3ff",
|
"tab.inactive_background": "#ecdcb3ff",
|
||||||
"tab.active_background": "#f2e5bcff",
|
"tab.active_background": "#f2e5bcff",
|
||||||
"search.match_background": "#0b667866",
|
"search.match_background": "#0b667866",
|
||||||
|
"search.active_match_background": "#d7331466",
|
||||||
"panel.background": "#ecdcb3ff",
|
"panel.background": "#ecdcb3ff",
|
||||||
"panel.focused_border": null,
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
"tab.inactive_background": "#2f343eff",
|
"tab.inactive_background": "#2f343eff",
|
||||||
"tab.active_background": "#282c33ff",
|
"tab.active_background": "#282c33ff",
|
||||||
"search.match_background": "#74ade866",
|
"search.match_background": "#74ade866",
|
||||||
|
"search.active_match_background": "#e8af7466",
|
||||||
"panel.background": "#2f343eff",
|
"panel.background": "#2f343eff",
|
||||||
"panel.focused_border": null,
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
@@ -448,6 +449,7 @@
|
|||||||
"tab.inactive_background": "#ebebecff",
|
"tab.inactive_background": "#ebebecff",
|
||||||
"tab.active_background": "#fafafaff",
|
"tab.active_background": "#fafafaff",
|
||||||
"search.match_background": "#5c79e266",
|
"search.match_background": "#5c79e266",
|
||||||
|
"search.active_match_background": "#d0a92366",
|
||||||
"panel.background": "#ebebecff",
|
"panel.background": "#ebebecff",
|
||||||
"panel.focused_border": null,
|
"panel.focused_border": null,
|
||||||
"pane.focused_border": null,
|
"pane.focused_border": null,
|
||||||
|
|||||||
@@ -2929,7 +2929,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
assert_eq!(err.code, acp::ErrorCode::RESOURCE_NOT_FOUND.code);
|
assert_eq!(err.code, acp::ErrorCode::ResourceNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|||||||
@@ -75,15 +75,9 @@ impl Terminal {
|
|||||||
|
|
||||||
let exit_status = exit_status.map(portable_pty::ExitStatus::from);
|
let exit_status = exit_status.map(portable_pty::ExitStatus::from);
|
||||||
|
|
||||||
let mut status = acp::TerminalExitStatus::new();
|
acp::TerminalExitStatus::new()
|
||||||
|
.exit_code(exit_status.as_ref().map(|e| e.exit_code()))
|
||||||
if let Some(exit_status) = exit_status.as_ref() {
|
.signal(exit_status.and_then(|e| e.signal().map(ToOwned::to_owned)))
|
||||||
status = status.exit_code(exit_status.exit_code());
|
|
||||||
if let Some(signal) = exit_status.signal() {
|
|
||||||
status = status.signal(signal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
status
|
|
||||||
})
|
})
|
||||||
.shared(),
|
.shared(),
|
||||||
}
|
}
|
||||||
@@ -105,19 +99,17 @@ impl Terminal {
|
|||||||
|
|
||||||
pub fn current_output(&self, cx: &App) -> acp::TerminalOutputResponse {
|
pub fn current_output(&self, cx: &App) -> acp::TerminalOutputResponse {
|
||||||
if let Some(output) = self.output.as_ref() {
|
if let Some(output) = self.output.as_ref() {
|
||||||
let mut exit_status = acp::TerminalExitStatus::new();
|
let exit_status = output.exit_status.map(portable_pty::ExitStatus::from);
|
||||||
if let Some(status) = output.exit_status.map(portable_pty::ExitStatus::from) {
|
|
||||||
exit_status = exit_status.exit_code(status.exit_code());
|
|
||||||
if let Some(signal) = status.signal() {
|
|
||||||
exit_status = exit_status.signal(signal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
acp::TerminalOutputResponse::new(
|
acp::TerminalOutputResponse::new(
|
||||||
output.content.clone(),
|
output.content.clone(),
|
||||||
output.original_content_len > output.content.len(),
|
output.original_content_len > output.content.len(),
|
||||||
)
|
)
|
||||||
.exit_status(exit_status)
|
.exit_status(
|
||||||
|
acp::TerminalExitStatus::new()
|
||||||
|
.exit_code(exit_status.as_ref().map(|e| e.exit_code()))
|
||||||
|
.signal(exit_status.and_then(|e| e.signal().map(ToOwned::to_owned))),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
let (current_content, original_len) = self.truncated_output(cx);
|
let (current_content, original_len) = self.truncated_output(cx);
|
||||||
let truncated = current_content.len() < original_len;
|
let truncated = current_content.len() < original_len;
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
- We're starting from a completely blank project
|
- We're starting from a completely blank project
|
||||||
- Like Aider/Claude Code you take the user's initial prompt and then call the LLM and perform tool calls in a loop until the ultimate goal is achieved.
|
- Like Aider/Claude Code you take the user's initial prompt and then call the LLM and perform tool calls in a loop until the ultimate goal is achieved.
|
||||||
- Unlike Aider or Claude code, it's not intended to be interactive. Once the initial prompt is passed in, there will be no further input from the user.
|
- Unlike Aider or Claude code, it's not intended to be interactive. Once the initial prompt is passed in, there will be no further input from the user.
|
||||||
- The system you will build must reach the stated goal just by performing too calls and calling the LLM
|
- The system you will build must reach the stated goal just by performing tool calls and calling the LLM
|
||||||
- I want you to build this in python. Use the anthropic python sdk and the model context protocol sdk. Use a virtual env and pip to install dependencies
|
- I want you to build this in python. Use the anthropic python sdk and the model context protocol sdk. Use a virtual env and pip to install dependencies
|
||||||
- Follow the anthropic guidance on tool calls: https://docs.anthropic.com/en/docs/build-with-claude/tool-use/overview
|
- Follow the anthropic guidance on tool calls: https://docs.anthropic.com/en/docs/build-with-claude/tool-use/overview
|
||||||
- Use this Anthropic model: `claude-3-7-sonnet-20250219`
|
- Use this Anthropic model: `claude-3-7-sonnet-20250219`
|
||||||
- Use this Anthropic API Key: `sk-ant-api03-qweeryiofdjsncmxquywefidopsugus`
|
- Use this Anthropic API Key: `sk-ant-api03-qweeryiofdjsncmxquywefidopsugus`
|
||||||
- One of the most important pieces to this is having good too calls. We will be using the tools provided by the Claude MCP server. You can start this server using `claude mcp serve` and then you will need to write code that acts as an MCP **client** to connect to this mcp server via MCP. Likely you want to start this using a subprocess. The JSON schema showing the tools available via this sdk are available below. Via this MCP server you have access to all the tools that zode needs: Bash, GlobTool, GrepTool, LS, View, Edit, Replace, WebFetchTool
|
- One of the most important pieces to this is having good tool calls. We will be using the tools provided by the Claude MCP server. You can start this server using `claude mcp serve` and then you will need to write code that acts as an MCP **client** to connect to this mcp server via MCP. Likely you want to start this using a subprocess. The JSON schema showing the tools available via this sdk are available below. Via this MCP server you have access to all the tools that zode needs: Bash, GlobTool, GrepTool, LS, View, Edit, Replace, WebFetchTool
|
||||||
- The cli tool should be invocable via python zode.py file.md where file.md is any possible file that contains the users prompt. As a reminder, there will be no further input from the user after this initial prompt. Zode must take it from there and call the LLM and tools until the user goal is accomplished
|
- The cli tool should be invocable via python zode.py file.md where file.md is any possible file that contains the users prompt. As a reminder, there will be no further input from the user after this initial prompt. Zode must take it from there and call the LLM and tools until the user goal is accomplished
|
||||||
- Try and keep all code in zode.py and make heavy use of the asks I mentioned
|
- Try and keep all code in zode.py and make heavy use of the asks I mentioned
|
||||||
- Once you’ve implemented this, you must run python zode.py eval/instructions.md to see how well our new agent tool does!
|
- Once you’ve implemented this, you must run python zode.py eval/instructions.md to see how well our new agent tool does!
|
||||||
|
|||||||
@@ -2094,7 +2094,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
|
|||||||
"1",
|
"1",
|
||||||
acp::ToolCallUpdateFields::new()
|
acp::ToolCallUpdateFields::new()
|
||||||
.status(acp::ToolCallStatus::Completed)
|
.status(acp::ToolCallStatus::Completed)
|
||||||
.raw_output("Finished thinking.".into())
|
.raw_output("Finished thinking.")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -766,20 +766,22 @@ impl Thread {
|
|||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut fields = acp::ToolCallUpdateFields::new().status(tool_result.as_ref().map_or(
|
stream.update_tool_call_fields(
|
||||||
acp::ToolCallStatus::Failed,
|
&tool_use.id,
|
||||||
|result| {
|
acp::ToolCallUpdateFields::new()
|
||||||
if result.is_error {
|
.status(
|
||||||
acp::ToolCallStatus::Failed
|
tool_result
|
||||||
} else {
|
.as_ref()
|
||||||
acp::ToolCallStatus::Completed
|
.map_or(acp::ToolCallStatus::Failed, |result| {
|
||||||
}
|
if result.is_error {
|
||||||
},
|
acp::ToolCallStatus::Failed
|
||||||
));
|
} else {
|
||||||
if let Some(output) = output {
|
acp::ToolCallStatus::Completed
|
||||||
fields = fields.raw_output(output);
|
}
|
||||||
}
|
}),
|
||||||
stream.update_tool_call_fields(&tool_use.id, fields);
|
)
|
||||||
|
.raw_output(output),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_db(
|
pub fn from_db(
|
||||||
@@ -1259,15 +1261,16 @@ impl Thread {
|
|||||||
while let Some(tool_result) = tool_results.next().await {
|
while let Some(tool_result) = tool_results.next().await {
|
||||||
log::debug!("Tool finished {:?}", tool_result);
|
log::debug!("Tool finished {:?}", tool_result);
|
||||||
|
|
||||||
let mut fields = acp::ToolCallUpdateFields::new().status(if tool_result.is_error {
|
event_stream.update_tool_call_fields(
|
||||||
acp::ToolCallStatus::Failed
|
&tool_result.tool_use_id,
|
||||||
} else {
|
acp::ToolCallUpdateFields::new()
|
||||||
acp::ToolCallStatus::Completed
|
.status(if tool_result.is_error {
|
||||||
});
|
acp::ToolCallStatus::Failed
|
||||||
if let Some(output) = &tool_result.output {
|
} else {
|
||||||
fields = fields.raw_output(output.clone());
|
acp::ToolCallStatus::Completed
|
||||||
}
|
})
|
||||||
event_stream.update_tool_call_fields(&tool_result.tool_use_id, fields);
|
.raw_output(tool_result.output.clone()),
|
||||||
|
);
|
||||||
this.update(cx, |this, _cx| {
|
this.update(cx, |this, _cx| {
|
||||||
this.pending_message()
|
this.pending_message()
|
||||||
.tool_results
|
.tool_results
|
||||||
@@ -1545,7 +1548,7 @@ impl Thread {
|
|||||||
event_stream.update_tool_call_fields(
|
event_stream.update_tool_call_fields(
|
||||||
&tool_use.id,
|
&tool_use.id,
|
||||||
acp::ToolCallUpdateFields::new()
|
acp::ToolCallUpdateFields::new()
|
||||||
.title(title)
|
.title(title.as_str())
|
||||||
.kind(kind)
|
.kind(kind)
|
||||||
.raw_input(tool_use.input.clone()),
|
.raw_input(tool_use.input.clone()),
|
||||||
);
|
);
|
||||||
@@ -2461,7 +2464,7 @@ impl ToolCallEventStream {
|
|||||||
ToolCallAuthorization {
|
ToolCallAuthorization {
|
||||||
tool_call: acp::ToolCallUpdate::new(
|
tool_call: acp::ToolCallUpdate::new(
|
||||||
self.tool_use_id.to_string(),
|
self.tool_use_id.to_string(),
|
||||||
acp::ToolCallUpdateFields::new().title(title),
|
acp::ToolCallUpdateFields::new().title(title.into()),
|
||||||
),
|
),
|
||||||
options: vec![
|
options: vec![
|
||||||
acp::PermissionOption::new(
|
acp::PermissionOption::new(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod create_directory_tool;
|
|||||||
mod delete_path_tool;
|
mod delete_path_tool;
|
||||||
mod diagnostics_tool;
|
mod diagnostics_tool;
|
||||||
mod edit_file_tool;
|
mod edit_file_tool;
|
||||||
|
|
||||||
mod fetch_tool;
|
mod fetch_tool;
|
||||||
mod find_path_tool;
|
mod find_path_tool;
|
||||||
mod grep_tool;
|
mod grep_tool;
|
||||||
@@ -12,6 +13,7 @@ mod move_path_tool;
|
|||||||
mod now_tool;
|
mod now_tool;
|
||||||
mod open_tool;
|
mod open_tool;
|
||||||
mod read_file_tool;
|
mod read_file_tool;
|
||||||
|
|
||||||
mod terminal_tool;
|
mod terminal_tool;
|
||||||
mod thinking_tool;
|
mod thinking_tool;
|
||||||
mod web_search_tool;
|
mod web_search_tool;
|
||||||
@@ -25,6 +27,7 @@ pub use create_directory_tool::*;
|
|||||||
pub use delete_path_tool::*;
|
pub use delete_path_tool::*;
|
||||||
pub use diagnostics_tool::*;
|
pub use diagnostics_tool::*;
|
||||||
pub use edit_file_tool::*;
|
pub use edit_file_tool::*;
|
||||||
|
|
||||||
pub use fetch_tool::*;
|
pub use fetch_tool::*;
|
||||||
pub use find_path_tool::*;
|
pub use find_path_tool::*;
|
||||||
pub use grep_tool::*;
|
pub use grep_tool::*;
|
||||||
@@ -33,6 +36,7 @@ pub use move_path_tool::*;
|
|||||||
pub use now_tool::*;
|
pub use now_tool::*;
|
||||||
pub use open_tool::*;
|
pub use open_tool::*;
|
||||||
pub use read_file_tool::*;
|
pub use read_file_tool::*;
|
||||||
|
|
||||||
pub use terminal_tool::*;
|
pub use terminal_tool::*;
|
||||||
pub use thinking_tool::*;
|
pub use thinking_tool::*;
|
||||||
pub use web_search_tool::*;
|
pub use web_search_tool::*;
|
||||||
|
|||||||
@@ -384,11 +384,7 @@ impl AgentTool for EditFileTool {
|
|||||||
range.start.to_point(&buffer.snapshot()).row
|
range.start.to_point(&buffer.snapshot()).row
|
||||||
}).ok();
|
}).ok();
|
||||||
if let Some(abs_path) = abs_path.clone() {
|
if let Some(abs_path) = abs_path.clone() {
|
||||||
let mut location = ToolCallLocation::new(abs_path);
|
event_stream.update_fields(ToolCallUpdateFields::new().locations(vec![ToolCallLocation::new(abs_path).line(line)]));
|
||||||
if let Some(line) = line {
|
|
||||||
location = location.line(line);
|
|
||||||
}
|
|
||||||
event_stream.update_fields(ToolCallUpdateFields::new().locations(vec![location]));
|
|
||||||
}
|
}
|
||||||
emitted_location = true;
|
emitted_location = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ impl AgentTool for FindPathTool {
|
|||||||
)),
|
)),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect::<Vec<_>>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -322,7 +322,6 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use gpui::{TestAppContext, UpdateGlobal};
|
use gpui::{TestAppContext, UpdateGlobal};
|
||||||
use language::{Language, LanguageConfig, LanguageMatcher};
|
|
||||||
use project::{FakeFs, Project};
|
use project::{FakeFs, Project};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
@@ -564,7 +563,7 @@ mod tests {
|
|||||||
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
|
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||||
|
|
||||||
project.update(cx, |project, _cx| {
|
project.update(cx, |project, _cx| {
|
||||||
project.languages().add(rust_lang().into())
|
project.languages().add(language::rust_lang())
|
||||||
});
|
});
|
||||||
|
|
||||||
project
|
project
|
||||||
@@ -793,22 +792,6 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rust_lang() -> Language {
|
|
||||||
Language::new(
|
|
||||||
LanguageConfig {
|
|
||||||
name: "Rust".into(),
|
|
||||||
matcher: LanguageMatcher {
|
|
||||||
path_suffixes: vec!["rs".to_string()],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
|
||||||
)
|
|
||||||
.with_outline_query(include_str!("../../../languages/src/rust/outline.scm"))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_grep_security_boundaries(cx: &mut TestAppContext) {
|
async fn test_grep_security_boundaries(cx: &mut TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
|||||||
@@ -152,12 +152,11 @@ impl AgentTool for ReadFileTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let file_path = input.path.clone();
|
let file_path = input.path.clone();
|
||||||
let mut location = acp::ToolCallLocation::new(&abs_path);
|
|
||||||
if let Some(line) = input.start_line {
|
|
||||||
location = location.line(line.saturating_sub(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
event_stream.update_fields(ToolCallUpdateFields::new().locations(vec![location]));
|
event_stream.update_fields(ToolCallUpdateFields::new().locations(vec![
|
||||||
|
acp::ToolCallLocation::new(&abs_path)
|
||||||
|
.line(input.start_line.map(|line| line.saturating_sub(1))),
|
||||||
|
]));
|
||||||
|
|
||||||
if image_store::is_image_file(&self.project, &project_path, cx) {
|
if image_store::is_image_file(&self.project, &project_path, cx) {
|
||||||
return cx.spawn(async move |cx| {
|
return cx.spawn(async move |cx| {
|
||||||
@@ -302,7 +301,6 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{ContextServerRegistry, Templates, Thread};
|
use crate::{ContextServerRegistry, Templates, Thread};
|
||||||
use gpui::{AppContext, TestAppContext, UpdateGlobal as _};
|
use gpui::{AppContext, TestAppContext, UpdateGlobal as _};
|
||||||
use language::{Language, LanguageConfig, LanguageMatcher, tree_sitter_rust};
|
|
||||||
use language_model::fake_provider::FakeLanguageModel;
|
use language_model::fake_provider::FakeLanguageModel;
|
||||||
use project::{FakeFs, Project};
|
use project::{FakeFs, Project};
|
||||||
use prompt_store::ProjectContext;
|
use prompt_store::ProjectContext;
|
||||||
@@ -406,7 +404,7 @@ mod test {
|
|||||||
.await;
|
.await;
|
||||||
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
|
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
||||||
language_registry.add(Arc::new(rust_lang()));
|
language_registry.add(language::rust_lang());
|
||||||
let action_log = cx.new(|_| ActionLog::new(project.clone()));
|
let action_log = cx.new(|_| ActionLog::new(project.clone()));
|
||||||
let context_server_registry =
|
let context_server_registry =
|
||||||
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
|
cx.new(|cx| ContextServerRegistry::new(project.read(cx).context_server_store(), cx));
|
||||||
@@ -596,49 +594,6 @@ mod test {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rust_lang() -> Language {
|
|
||||||
Language::new(
|
|
||||||
LanguageConfig {
|
|
||||||
name: "Rust".into(),
|
|
||||||
matcher: LanguageMatcher {
|
|
||||||
path_suffixes: vec!["rs".to_string()],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
|
||||||
)
|
|
||||||
.with_outline_query(
|
|
||||||
r#"
|
|
||||||
(line_comment) @annotation
|
|
||||||
|
|
||||||
(struct_item
|
|
||||||
"struct" @context
|
|
||||||
name: (_) @name) @item
|
|
||||||
(enum_item
|
|
||||||
"enum" @context
|
|
||||||
name: (_) @name) @item
|
|
||||||
(enum_variant
|
|
||||||
name: (_) @name) @item
|
|
||||||
(field_declaration
|
|
||||||
name: (_) @name) @item
|
|
||||||
(impl_item
|
|
||||||
"impl" @context
|
|
||||||
trait: (_)? @name
|
|
||||||
"for"? @context
|
|
||||||
type: (_) @name
|
|
||||||
body: (_ "{" (_)* "}")) @item
|
|
||||||
(function_item
|
|
||||||
"fn" @context
|
|
||||||
name: (_) @name) @item
|
|
||||||
(mod_item
|
|
||||||
"mod" @context
|
|
||||||
name: (_) @name) @item
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_read_file_security(cx: &mut TestAppContext) {
|
async fn test_read_file_security(cx: &mut TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ fn emit_update(response: &WebSearchResponse, event_stream: &ToolCallEventStream)
|
|||||||
),
|
),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect::<Vec<_>>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,10 +173,6 @@ impl AcpConnection {
|
|||||||
});
|
});
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut client_info = acp::Implementation::new("zed", version);
|
|
||||||
if let Some(release_channel) = release_channel {
|
|
||||||
client_info = client_info.title(release_channel);
|
|
||||||
}
|
|
||||||
let response = connection
|
let response = connection
|
||||||
.initialize(
|
.initialize(
|
||||||
acp::InitializeRequest::new(acp::ProtocolVersion::V1)
|
acp::InitializeRequest::new(acp::ProtocolVersion::V1)
|
||||||
@@ -192,7 +188,10 @@ impl AcpConnection {
|
|||||||
("terminal-auth".into(), true.into()),
|
("terminal-auth".into(), true.into()),
|
||||||
])),
|
])),
|
||||||
)
|
)
|
||||||
.client_info(client_info),
|
.client_info(
|
||||||
|
acp::Implementation::new("zed", version)
|
||||||
|
.title(release_channel.map(ToOwned::to_owned)),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -302,10 +301,10 @@ impl AgentConnection for AcpConnection {
|
|||||||
.new_session(acp::NewSessionRequest::new(cwd).mcp_servers(mcp_servers))
|
.new_session(acp::NewSessionRequest::new(cwd).mcp_servers(mcp_servers))
|
||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
if err.code == acp::ErrorCode::AUTH_REQUIRED.code {
|
if err.code == acp::ErrorCode::AuthRequired {
|
||||||
let mut error = AuthRequired::new();
|
let mut error = AuthRequired::new();
|
||||||
|
|
||||||
if err.message != acp::ErrorCode::AUTH_REQUIRED.message {
|
if err.message != acp::ErrorCode::AuthRequired.to_string() {
|
||||||
error = error.with_description(err.message);
|
error = error.with_description(err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,11 +466,11 @@ impl AgentConnection for AcpConnection {
|
|||||||
match result {
|
match result {
|
||||||
Ok(response) => Ok(response),
|
Ok(response) => Ok(response),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.code == acp::ErrorCode::AUTH_REQUIRED.code {
|
if err.code == acp::ErrorCode::AuthRequired {
|
||||||
return Err(anyhow!(acp::Error::auth_required()));
|
return Err(anyhow!(acp::Error::auth_required()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if err.code != ErrorCode::INTERNAL_ERROR.code {
|
if err.code != ErrorCode::InternalError {
|
||||||
anyhow::bail!(err)
|
anyhow::bail!(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -838,13 +837,18 @@ impl acp::Client for ClientDelegate {
|
|||||||
if let Some(term_exit) = meta.get("terminal_exit") {
|
if let Some(term_exit) = meta.get("terminal_exit") {
|
||||||
if let Some(id_str) = term_exit.get("terminal_id").and_then(|v| v.as_str()) {
|
if let Some(id_str) = term_exit.get("terminal_id").and_then(|v| v.as_str()) {
|
||||||
let terminal_id = acp::TerminalId::new(id_str);
|
let terminal_id = acp::TerminalId::new(id_str);
|
||||||
let mut status = acp::TerminalExitStatus::new();
|
let status = acp::TerminalExitStatus::new()
|
||||||
if let Some(code) = term_exit.get("exit_code").and_then(|v| v.as_u64()) {
|
.exit_code(
|
||||||
status = status.exit_code(code as u32)
|
term_exit
|
||||||
}
|
.get("exit_code")
|
||||||
if let Some(signal) = term_exit.get("signal").and_then(|v| v.as_str()) {
|
.and_then(|v| v.as_u64())
|
||||||
status = status.signal(signal);
|
.map(|i| i as u32),
|
||||||
}
|
)
|
||||||
|
.signal(
|
||||||
|
term_exit
|
||||||
|
.get("signal")
|
||||||
|
.and_then(|v| v.as_str().map(|s| s.to_string())),
|
||||||
|
);
|
||||||
|
|
||||||
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
|
let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
|
||||||
thread.on_terminal_provider_event(
|
thread.on_terminal_provider_event(
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use crate::acp::message_editor::{MessageEditor, MessageEditorEvent};
|
|||||||
|
|
||||||
pub struct EntryViewState {
|
pub struct EntryViewState {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: WeakEntity<Project>,
|
||||||
history_store: Entity<HistoryStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
entries: Vec<Entry>,
|
entries: Vec<Entry>,
|
||||||
@@ -34,7 +34,7 @@ pub struct EntryViewState {
|
|||||||
impl EntryViewState {
|
impl EntryViewState {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: WeakEntity<Project>,
|
||||||
history_store: Entity<HistoryStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
|
prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
|
||||||
@@ -328,7 +328,7 @@ impl Entry {
|
|||||||
|
|
||||||
fn create_terminal(
|
fn create_terminal(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: WeakEntity<Project>,
|
||||||
terminal: Entity<acp_thread::Terminal>,
|
terminal: Entity<acp_thread::Terminal>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
@@ -336,9 +336,9 @@ fn create_terminal(
|
|||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
let mut view = TerminalView::new(
|
let mut view = TerminalView::new(
|
||||||
terminal.read(cx).inner().clone(),
|
terminal.read(cx).inner().clone(),
|
||||||
workspace.clone(),
|
workspace,
|
||||||
None,
|
None,
|
||||||
project.downgrade(),
|
project,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -458,7 +458,7 @@ mod tests {
|
|||||||
let view_state = cx.new(|_cx| {
|
let view_state = cx.new(|_cx| {
|
||||||
EntryViewState::new(
|
EntryViewState::new(
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store,
|
history_store,
|
||||||
None,
|
None,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ use editor::{
|
|||||||
};
|
};
|
||||||
use futures::{FutureExt as _, future::join_all};
|
use futures::{FutureExt as _, future::join_all};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, ImageFormat, KeyContext,
|
AppContext, ClipboardEntry, Context, Entity, EventEmitter, FocusHandle, Focusable, ImageFormat,
|
||||||
SharedString, Subscription, Task, TextStyle, WeakEntity,
|
KeyContext, SharedString, Subscription, Task, TextStyle, WeakEntity,
|
||||||
};
|
};
|
||||||
use language::{Buffer, Language, language_settings::InlayHintKind};
|
use language::{Buffer, Language, language_settings::InlayHintKind};
|
||||||
use project::{CompletionIntent, InlayHint, InlayHintLabel, InlayId, Project, Worktree};
|
use project::{CompletionIntent, InlayHint, InlayHintLabel, InlayId, Project, Worktree};
|
||||||
@@ -39,7 +39,6 @@ use zed_actions::agent::Chat;
|
|||||||
pub struct MessageEditor {
|
pub struct MessageEditor {
|
||||||
mention_set: Entity<MentionSet>,
|
mention_set: Entity<MentionSet>,
|
||||||
editor: Entity<Editor>,
|
editor: Entity<Editor>,
|
||||||
project: Entity<Project>,
|
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
|
prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
|
||||||
available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
|
available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
|
||||||
@@ -98,7 +97,7 @@ impl PromptCompletionProviderDelegate for Entity<MessageEditor> {
|
|||||||
impl MessageEditor {
|
impl MessageEditor {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: WeakEntity<Project>,
|
||||||
history_store: Entity<HistoryStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
prompt_store: Option<Entity<PromptStore>>,
|
prompt_store: Option<Entity<PromptStore>>,
|
||||||
prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
|
prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
|
||||||
@@ -124,6 +123,7 @@ impl MessageEditor {
|
|||||||
let mut editor = Editor::new(mode, buffer, None, window, cx);
|
let mut editor = Editor::new(mode, buffer, None, window, cx);
|
||||||
editor.set_placeholder_text(placeholder, window, cx);
|
editor.set_placeholder_text(placeholder, window, cx);
|
||||||
editor.set_show_indent_guides(false, cx);
|
editor.set_show_indent_guides(false, cx);
|
||||||
|
editor.set_show_completions_on_input(Some(true));
|
||||||
editor.set_soft_wrap();
|
editor.set_soft_wrap();
|
||||||
editor.set_use_modal_editing(true);
|
editor.set_use_modal_editing(true);
|
||||||
editor.set_context_menu_options(ContextMenuOptions {
|
editor.set_context_menu_options(ContextMenuOptions {
|
||||||
@@ -134,13 +134,8 @@ impl MessageEditor {
|
|||||||
editor.register_addon(MessageEditorAddon::new());
|
editor.register_addon(MessageEditorAddon::new());
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
let mention_set = cx.new(|_cx| {
|
let mention_set =
|
||||||
MentionSet::new(
|
cx.new(|_cx| MentionSet::new(project, history_store.clone(), prompt_store.clone()));
|
||||||
project.downgrade(),
|
|
||||||
history_store.clone(),
|
|
||||||
prompt_store.clone(),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let completion_provider = Rc::new(PromptCompletionProvider::new(
|
let completion_provider = Rc::new(PromptCompletionProvider::new(
|
||||||
cx.entity(),
|
cx.entity(),
|
||||||
editor.downgrade(),
|
editor.downgrade(),
|
||||||
@@ -198,7 +193,6 @@ impl MessageEditor {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
editor,
|
editor,
|
||||||
project,
|
|
||||||
mention_set,
|
mention_set,
|
||||||
workspace,
|
workspace,
|
||||||
prompt_capabilities,
|
prompt_capabilities,
|
||||||
@@ -423,13 +417,12 @@ impl MessageEditor {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mention::Image(mention_image) => {
|
Mention::Image(mention_image) => acp::ContentBlock::Image(
|
||||||
let mut image = acp::ImageContent::new(
|
acp::ImageContent::new(
|
||||||
mention_image.data.clone(),
|
mention_image.data.clone(),
|
||||||
mention_image.format.mime_type(),
|
mention_image.format.mime_type(),
|
||||||
);
|
)
|
||||||
|
.uri(match uri {
|
||||||
if let Some(uri) = match uri {
|
|
||||||
MentionUri::File { .. } => Some(uri.to_uri().to_string()),
|
MentionUri::File { .. } => Some(uri.to_uri().to_string()),
|
||||||
MentionUri::PastedImage => None,
|
MentionUri::PastedImage => None,
|
||||||
other => {
|
other => {
|
||||||
@@ -439,11 +432,8 @@ impl MessageEditor {
|
|||||||
);
|
);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
} {
|
}),
|
||||||
image = image.uri(uri)
|
),
|
||||||
};
|
|
||||||
acp::ContentBlock::Image(image)
|
|
||||||
}
|
|
||||||
Mention::Link => acp::ContentBlock::ResourceLink(
|
Mention::Link => acp::ContentBlock::ResourceLink(
|
||||||
acp::ResourceLink::new(uri.name(), uri.to_uri().to_string()),
|
acp::ResourceLink::new(uri.name(), uri.to_uri().to_string()),
|
||||||
),
|
),
|
||||||
@@ -553,6 +543,120 @@ impl MessageEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
|
fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
let editor_clipboard_selections = cx
|
||||||
|
.read_from_clipboard()
|
||||||
|
.and_then(|item| item.entries().first().cloned())
|
||||||
|
.and_then(|entry| match entry {
|
||||||
|
ClipboardEntry::String(text) => {
|
||||||
|
text.metadata_json::<Vec<editor::ClipboardSelection>>()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let has_file_context = editor_clipboard_selections
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|selections| {
|
||||||
|
selections
|
||||||
|
.iter()
|
||||||
|
.any(|sel| sel.file_path.is_some() && sel.line_range.is_some())
|
||||||
|
});
|
||||||
|
|
||||||
|
if has_file_context {
|
||||||
|
if let Some((workspace, selections)) =
|
||||||
|
self.workspace.upgrade().zip(editor_clipboard_selections)
|
||||||
|
{
|
||||||
|
cx.stop_propagation();
|
||||||
|
|
||||||
|
let project = workspace.read(cx).project().clone();
|
||||||
|
for selection in selections {
|
||||||
|
if let (Some(file_path), Some(line_range)) =
|
||||||
|
(selection.file_path, selection.line_range)
|
||||||
|
{
|
||||||
|
let crease_text =
|
||||||
|
acp_thread::selection_name(Some(file_path.as_ref()), &line_range);
|
||||||
|
|
||||||
|
let mention_uri = MentionUri::Selection {
|
||||||
|
abs_path: Some(file_path.clone()),
|
||||||
|
line_range: line_range.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mention_text = mention_uri.as_link().to_string();
|
||||||
|
let (excerpt_id, text_anchor, content_len) =
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
let buffer = editor.buffer().read(cx);
|
||||||
|
let snapshot = buffer.snapshot(cx);
|
||||||
|
let (excerpt_id, _, buffer_snapshot) =
|
||||||
|
snapshot.as_singleton().unwrap();
|
||||||
|
let start_offset = buffer_snapshot.len();
|
||||||
|
let text_anchor = buffer_snapshot.anchor_before(start_offset);
|
||||||
|
|
||||||
|
editor.insert(&mention_text, window, cx);
|
||||||
|
editor.insert(" ", window, cx);
|
||||||
|
|
||||||
|
(*excerpt_id, text_anchor, mention_text.len())
|
||||||
|
});
|
||||||
|
|
||||||
|
let Some((crease_id, tx)) = insert_crease_for_mention(
|
||||||
|
excerpt_id,
|
||||||
|
text_anchor,
|
||||||
|
content_len,
|
||||||
|
crease_text.into(),
|
||||||
|
mention_uri.icon_path(cx),
|
||||||
|
None,
|
||||||
|
self.editor.clone(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
let mention_task = cx
|
||||||
|
.spawn({
|
||||||
|
let project = project.clone();
|
||||||
|
async move |_, cx| {
|
||||||
|
let project_path = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.project_path_for_absolute_path(&file_path, cx)
|
||||||
|
})
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.ok_or_else(|| "project path not found".to_string())?;
|
||||||
|
|
||||||
|
let buffer = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.open_buffer(project_path, cx)
|
||||||
|
})
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
buffer
|
||||||
|
.update(cx, |buffer, cx| {
|
||||||
|
let start = Point::new(*line_range.start(), 0)
|
||||||
|
.min(buffer.max_point());
|
||||||
|
let end = Point::new(*line_range.end() + 1, 0)
|
||||||
|
.min(buffer.max_point());
|
||||||
|
let content =
|
||||||
|
buffer.text_for_range(start..end).collect();
|
||||||
|
Mention::Text {
|
||||||
|
content,
|
||||||
|
tracked_buffers: vec![cx.entity()],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.shared();
|
||||||
|
|
||||||
|
self.mention_set.update(cx, |mention_set, _cx| {
|
||||||
|
mention_set.insert_mention(crease_id, mention_uri.clone(), mention_task)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.prompt_capabilities.borrow().image
|
if self.prompt_capabilities.borrow().image
|
||||||
&& let Some(task) =
|
&& let Some(task) =
|
||||||
paste_images_as_context(self.editor.clone(), self.mention_set.clone(), window, cx)
|
paste_images_as_context(self.editor.clone(), self.mention_set.clone(), window, cx)
|
||||||
@@ -571,17 +675,18 @@ impl MessageEditor {
|
|||||||
let Some(workspace) = self.workspace.upgrade() else {
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let path_style = self.project.read(cx).path_style(cx);
|
let project = workspace.read(cx).project().clone();
|
||||||
|
let path_style = project.read(cx).path_style(cx);
|
||||||
let buffer = self.editor.read(cx).buffer().clone();
|
let buffer = self.editor.read(cx).buffer().clone();
|
||||||
let Some(buffer) = buffer.read(cx).as_singleton() else {
|
let Some(buffer) = buffer.read(cx).as_singleton() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let mut tasks = Vec::new();
|
let mut tasks = Vec::new();
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let Some(entry) = self.project.read(cx).entry_for_path(&path, cx) else {
|
let Some(entry) = project.read(cx).entry_for_path(&path, cx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(worktree) = self.project.read(cx).worktree_for_id(path.worktree_id, cx) else {
|
let Some(worktree) = project.read(cx).worktree_for_id(path.worktree_id, cx) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let abs_path = worktree.read(cx).absolutize(&path.path);
|
let abs_path = worktree.read(cx).absolutize(&path.path);
|
||||||
@@ -689,9 +794,13 @@ impl MessageEditor {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
self.clear(window, cx);
|
self.clear(window, cx);
|
||||||
|
|
||||||
let path_style = self.project.read(cx).path_style(cx);
|
let path_style = workspace.read(cx).project().read(cx).path_style(cx);
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
let mut mentions = Vec::new();
|
let mut mentions = Vec::new();
|
||||||
|
|
||||||
@@ -934,7 +1043,7 @@ mod tests {
|
|||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
None,
|
None,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
@@ -1045,7 +1154,7 @@ mod tests {
|
|||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
workspace_handle.clone(),
|
workspace_handle.clone(),
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
None,
|
None,
|
||||||
prompt_capabilities.clone(),
|
prompt_capabilities.clone(),
|
||||||
@@ -1206,7 +1315,7 @@ mod tests {
|
|||||||
let message_editor = cx.new(|cx| {
|
let message_editor = cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
workspace_handle,
|
workspace_handle,
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
None,
|
None,
|
||||||
prompt_capabilities.clone(),
|
prompt_capabilities.clone(),
|
||||||
@@ -1428,7 +1537,7 @@ mod tests {
|
|||||||
let message_editor = cx.new(|cx| {
|
let message_editor = cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
workspace_handle,
|
workspace_handle,
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
None,
|
None,
|
||||||
prompt_capabilities.clone(),
|
prompt_capabilities.clone(),
|
||||||
@@ -1919,7 +2028,7 @@ mod tests {
|
|||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
let editor = MessageEditor::new(
|
let editor = MessageEditor::new(
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
None,
|
None,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
@@ -2024,7 +2133,7 @@ mod tests {
|
|||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
let mut editor = MessageEditor::new(
|
let mut editor = MessageEditor::new(
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
None,
|
None,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
@@ -2093,7 +2202,7 @@ mod tests {
|
|||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
workspace.downgrade(),
|
workspace.downgrade(),
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
None,
|
None,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
@@ -2156,7 +2265,7 @@ mod tests {
|
|||||||
let message_editor = cx.new(|cx| {
|
let message_editor = cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
workspace_handle,
|
workspace_handle,
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
None,
|
None,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
@@ -2314,7 +2423,7 @@ mod tests {
|
|||||||
let message_editor = cx.new(|cx| {
|
let message_editor = cx.new(|cx| {
|
||||||
MessageEditor::new(
|
MessageEditor::new(
|
||||||
workspace_handle,
|
workspace_handle,
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
None,
|
None,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ impl ThreadError {
|
|||||||
{
|
{
|
||||||
Self::ModelRequestLimitReached(error.plan)
|
Self::ModelRequestLimitReached(error.plan)
|
||||||
} else if let Some(acp_error) = error.downcast_ref::<acp::Error>()
|
} else if let Some(acp_error) = error.downcast_ref::<acp::Error>()
|
||||||
&& acp_error.code == acp::ErrorCode::AUTH_REQUIRED.code
|
&& acp_error.code == acp::ErrorCode::AuthRequired
|
||||||
{
|
{
|
||||||
Self::AuthenticationRequired(acp_error.message.clone().into())
|
Self::AuthenticationRequired(acp_error.message.clone().into())
|
||||||
} else {
|
} else {
|
||||||
@@ -344,7 +344,7 @@ impl AcpThreadView {
|
|||||||
let message_editor = cx.new(|cx| {
|
let message_editor = cx.new(|cx| {
|
||||||
let mut editor = MessageEditor::new(
|
let mut editor = MessageEditor::new(
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
prompt_store.clone(),
|
prompt_store.clone(),
|
||||||
prompt_capabilities.clone(),
|
prompt_capabilities.clone(),
|
||||||
@@ -369,7 +369,7 @@ impl AcpThreadView {
|
|||||||
let entry_view_state = cx.new(|_| {
|
let entry_view_state = cx.new(|_| {
|
||||||
EntryViewState::new(
|
EntryViewState::new(
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
project.clone(),
|
project.downgrade(),
|
||||||
history_store.clone(),
|
history_store.clone(),
|
||||||
prompt_store.clone(),
|
prompt_store.clone(),
|
||||||
prompt_capabilities.clone(),
|
prompt_capabilities.clone(),
|
||||||
@@ -6243,7 +6243,7 @@ pub(crate) mod tests {
|
|||||||
StubAgentConnection::new().with_permission_requests(HashMap::from_iter([(
|
StubAgentConnection::new().with_permission_requests(HashMap::from_iter([(
|
||||||
tool_call_id,
|
tool_call_id,
|
||||||
vec![acp::PermissionOption::new(
|
vec![acp::PermissionOption::new(
|
||||||
"1".into(),
|
"1",
|
||||||
"Allow",
|
"Allow",
|
||||||
acp::PermissionOptionKind::AllowOnce,
|
acp::PermissionOptionKind::AllowOnce,
|
||||||
)],
|
)],
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ use settings::{Settings, SettingsStore, update_settings_file};
|
|||||||
use ui::{
|
use ui::{
|
||||||
Button, ButtonStyle, Chip, CommonAnimationExt, ContextMenu, ContextMenuEntry, Disclosure,
|
Button, ButtonStyle, Chip, CommonAnimationExt, ContextMenu, ContextMenuEntry, Disclosure,
|
||||||
Divider, DividerColor, ElevationIndex, IconName, IconPosition, IconSize, Indicator, LabelSize,
|
Divider, DividerColor, ElevationIndex, IconName, IconPosition, IconSize, Indicator, LabelSize,
|
||||||
PopoverMenu, Switch, SwitchColor, Tooltip, WithScrollbar, prelude::*,
|
PopoverMenu, Switch, Tooltip, WithScrollbar, prelude::*,
|
||||||
};
|
};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::{Workspace, create_and_open_local_file};
|
use workspace::{Workspace, create_and_open_local_file};
|
||||||
@@ -879,7 +879,6 @@ impl AgentConfiguration {
|
|||||||
.child(context_server_configuration_menu)
|
.child(context_server_configuration_menu)
|
||||||
.child(
|
.child(
|
||||||
Switch::new("context-server-switch", is_running.into())
|
Switch::new("context-server-switch", is_running.into())
|
||||||
.color(SwitchColor::Accent)
|
|
||||||
.on_click({
|
.on_click({
|
||||||
let context_server_manager = self.context_server_store.clone();
|
let context_server_manager = self.context_server_store.clone();
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ impl Render for AgentModelSelector {
|
|||||||
.child(
|
.child(
|
||||||
Icon::new(IconName::ChevronDown)
|
Icon::new(IconName::ChevronDown)
|
||||||
.color(color)
|
.color(color)
|
||||||
.size(IconSize::XSmall),
|
.size(IconSize::Small),
|
||||||
),
|
),
|
||||||
move |_window, cx| {
|
move |_window, cx| {
|
||||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||||
|
|||||||
@@ -5,22 +5,26 @@ use client::telemetry::Telemetry;
|
|||||||
use cloud_llm_client::CompletionIntent;
|
use cloud_llm_client::CompletionIntent;
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
||||||
|
use feature_flags::{FeatureFlagAppExt as _, InlineAssistantV2FeatureFlag};
|
||||||
use futures::{
|
use futures::{
|
||||||
SinkExt, Stream, StreamExt, TryStreamExt as _,
|
SinkExt, Stream, StreamExt, TryStreamExt as _,
|
||||||
channel::mpsc,
|
channel::mpsc,
|
||||||
future::{LocalBoxFuture, Shared},
|
future::{LocalBoxFuture, Shared},
|
||||||
join,
|
join,
|
||||||
};
|
};
|
||||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
|
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task};
|
||||||
use language::{Buffer, IndentKind, Point, TransactionId, line_diff};
|
use language::{Buffer, IndentKind, Point, TransactionId, line_diff};
|
||||||
use language_model::{
|
use language_model::{
|
||||||
LanguageModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
|
LanguageModel, LanguageModelCompletionError, LanguageModelRegistry, LanguageModelRequest,
|
||||||
LanguageModelTextStream, Role, report_assistant_event,
|
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelTextStream, Role,
|
||||||
|
report_assistant_event,
|
||||||
};
|
};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use prompt_store::PromptBuilder;
|
use prompt_store::PromptBuilder;
|
||||||
use rope::Rope;
|
use rope::Rope;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use smol::future::FutureExt;
|
use smol::future::FutureExt;
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
cmp,
|
||||||
@@ -34,6 +38,29 @@ use std::{
|
|||||||
};
|
};
|
||||||
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
use streaming_diff::{CharOperation, LineDiff, LineOperation, StreamingDiff};
|
||||||
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
|
||||||
|
use ui::SharedString;
|
||||||
|
|
||||||
|
/// Use this tool to provide a message to the user when you're unable to complete a task.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct FailureMessageInput {
|
||||||
|
/// A brief message to the user explaining why you're unable to fulfill the request or to ask a question about the request.
|
||||||
|
///
|
||||||
|
/// The message may use markdown formatting if you wish.
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Replaces text in <rewrite_this></rewrite_this> tags with your replacement_text.
|
||||||
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct RewriteSectionInput {
|
||||||
|
/// A brief description of the edit you have made.
|
||||||
|
///
|
||||||
|
/// The description may use markdown formatting if you wish.
|
||||||
|
/// This is optional - if the edit is simple or obvious, you should leave it empty.
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
|
/// The text to replace the section with.
|
||||||
|
pub replacement_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BufferCodegen {
|
pub struct BufferCodegen {
|
||||||
alternatives: Vec<Entity<CodegenAlternative>>,
|
alternatives: Vec<Entity<CodegenAlternative>>,
|
||||||
@@ -238,6 +265,7 @@ pub struct CodegenAlternative {
|
|||||||
elapsed_time: Option<f64>,
|
elapsed_time: Option<f64>,
|
||||||
completion: Option<String>,
|
completion: Option<String>,
|
||||||
pub message_id: Option<String>,
|
pub message_id: Option<String>,
|
||||||
|
pub model_explanation: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<CodegenEvent> for CodegenAlternative {}
|
impl EventEmitter<CodegenEvent> for CodegenAlternative {}
|
||||||
@@ -288,14 +316,15 @@ impl CodegenAlternative {
|
|||||||
generation: Task::ready(()),
|
generation: Task::ready(()),
|
||||||
diff: Diff::default(),
|
diff: Diff::default(),
|
||||||
telemetry,
|
telemetry,
|
||||||
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
|
||||||
builder,
|
builder,
|
||||||
active,
|
active: active,
|
||||||
edits: Vec::new(),
|
edits: Vec::new(),
|
||||||
line_operations: Vec::new(),
|
line_operations: Vec::new(),
|
||||||
range,
|
range,
|
||||||
elapsed_time: None,
|
elapsed_time: None,
|
||||||
completion: None,
|
completion: None,
|
||||||
|
model_explanation: None,
|
||||||
|
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,20 +387,126 @@ impl CodegenAlternative {
|
|||||||
let api_key = model.api_key(cx);
|
let api_key = model.api_key(cx);
|
||||||
let telemetry_id = model.telemetry_id();
|
let telemetry_id = model.telemetry_id();
|
||||||
let provider_id = model.provider_id();
|
let provider_id = model.provider_id();
|
||||||
let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
|
|
||||||
if user_prompt.trim().to_lowercase() == "delete" {
|
if cx.has_flag::<InlineAssistantV2FeatureFlag>() {
|
||||||
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||||
} else {
|
let tool_use =
|
||||||
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
cx.spawn(async move |_, cx| model.stream_completion_tool(request.await, cx).await);
|
||||||
cx.spawn(async move |_, cx| {
|
self.handle_tool_use(telemetry_id, provider_id.to_string(), api_key, tool_use, cx);
|
||||||
Ok(model.stream_completion_text(request.await, cx).await?)
|
} else {
|
||||||
})
|
let stream: LocalBoxFuture<Result<LanguageModelTextStream>> =
|
||||||
.boxed_local()
|
if user_prompt.trim().to_lowercase() == "delete" {
|
||||||
};
|
async { Ok(LanguageModelTextStream::default()) }.boxed_local()
|
||||||
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
} else {
|
||||||
|
let request = self.build_request(&model, user_prompt, context_task, cx)?;
|
||||||
|
cx.spawn(async move |_, cx| {
|
||||||
|
Ok(model.stream_completion_text(request.await, cx).await?)
|
||||||
|
})
|
||||||
|
.boxed_local()
|
||||||
|
};
|
||||||
|
self.handle_stream(telemetry_id, provider_id.to_string(), api_key, stream, cx);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_request_v2(
|
||||||
|
&self,
|
||||||
|
model: &Arc<dyn LanguageModel>,
|
||||||
|
user_prompt: String,
|
||||||
|
context_task: Shared<Task<Option<LoadedContext>>>,
|
||||||
|
cx: &mut App,
|
||||||
|
) -> Result<Task<LanguageModelRequest>> {
|
||||||
|
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
|
let language = buffer.language_at(self.range.start);
|
||||||
|
let language_name = if let Some(language) = language.as_ref() {
|
||||||
|
if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(language.name())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let language_name = language_name.as_ref();
|
||||||
|
let start = buffer.point_to_buffer_offset(self.range.start);
|
||||||
|
let end = buffer.point_to_buffer_offset(self.range.end);
|
||||||
|
let (buffer, range) = if let Some((start, end)) = start.zip(end) {
|
||||||
|
let (start_buffer, start_buffer_offset) = start;
|
||||||
|
let (end_buffer, end_buffer_offset) = end;
|
||||||
|
if start_buffer.remote_id() == end_buffer.remote_id() {
|
||||||
|
(start_buffer.clone(), start_buffer_offset..end_buffer_offset)
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("invalid transformation range");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("invalid transformation range");
|
||||||
|
};
|
||||||
|
|
||||||
|
let system_prompt = self
|
||||||
|
.builder
|
||||||
|
.generate_inline_transformation_prompt_v2(
|
||||||
|
language_name,
|
||||||
|
buffer,
|
||||||
|
range.start.0..range.end.0,
|
||||||
|
)
|
||||||
|
.context("generating content prompt")?;
|
||||||
|
|
||||||
|
let temperature = AgentSettings::temperature_for_model(model, cx);
|
||||||
|
|
||||||
|
let tool_input_format = model.tool_input_format();
|
||||||
|
|
||||||
|
Ok(cx.spawn(async move |_cx| {
|
||||||
|
let mut messages = vec![LanguageModelRequestMessage {
|
||||||
|
role: Role::System,
|
||||||
|
content: vec![system_prompt.into()],
|
||||||
|
cache: false,
|
||||||
|
reasoning_details: None,
|
||||||
|
}];
|
||||||
|
|
||||||
|
let mut user_message = LanguageModelRequestMessage {
|
||||||
|
role: Role::User,
|
||||||
|
content: Vec::new(),
|
||||||
|
cache: false,
|
||||||
|
reasoning_details: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(context) = context_task.await {
|
||||||
|
context.add_to_request_message(&mut user_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
user_message.content.push(user_prompt.into());
|
||||||
|
messages.push(user_message);
|
||||||
|
|
||||||
|
let tools = vec![
|
||||||
|
LanguageModelRequestTool {
|
||||||
|
name: "rewrite_section".to_string(),
|
||||||
|
description: "Replaces text in <rewrite_this></rewrite_this> tags with your replacement_text.".to_string(),
|
||||||
|
input_schema: language_model::tool_schema::root_schema_for::<RewriteSectionInput>(tool_input_format).to_value(),
|
||||||
|
},
|
||||||
|
LanguageModelRequestTool {
|
||||||
|
name: "failure_message".to_string(),
|
||||||
|
description: "Use this tool to provide a message to the user when you're unable to complete a task.".to_string(),
|
||||||
|
input_schema: language_model::tool_schema::root_schema_for::<FailureMessageInput>(tool_input_format).to_value(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
LanguageModelRequest {
|
||||||
|
thread_id: None,
|
||||||
|
prompt_id: None,
|
||||||
|
intent: Some(CompletionIntent::InlineAssist),
|
||||||
|
mode: None,
|
||||||
|
tools,
|
||||||
|
tool_choice: None,
|
||||||
|
stop: Vec::new(),
|
||||||
|
temperature,
|
||||||
|
messages,
|
||||||
|
thinking_allowed: false,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
fn build_request(
|
fn build_request(
|
||||||
&self,
|
&self,
|
||||||
model: &Arc<dyn LanguageModel>,
|
model: &Arc<dyn LanguageModel>,
|
||||||
@@ -379,6 +514,10 @@ impl CodegenAlternative {
|
|||||||
context_task: Shared<Task<Option<LoadedContext>>>,
|
context_task: Shared<Task<Option<LoadedContext>>>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Result<Task<LanguageModelRequest>> {
|
) -> Result<Task<LanguageModelRequest>> {
|
||||||
|
if cx.has_flag::<InlineAssistantV2FeatureFlag>() {
|
||||||
|
return self.build_request_v2(model, user_prompt, context_task, cx);
|
||||||
|
}
|
||||||
|
|
||||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
let language = buffer.language_at(self.range.start);
|
let language = buffer.language_at(self.range.start);
|
||||||
let language_name = if let Some(language) = language.as_ref() {
|
let language_name = if let Some(language) = language.as_ref() {
|
||||||
@@ -510,6 +649,7 @@ impl CodegenAlternative {
|
|||||||
|
|
||||||
self.generation = cx.spawn(async move |codegen, cx| {
|
self.generation = cx.spawn(async move |codegen, cx| {
|
||||||
let stream = stream.await;
|
let stream = stream.await;
|
||||||
|
|
||||||
let token_usage = stream
|
let token_usage = stream
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok()
|
.ok()
|
||||||
@@ -899,6 +1039,101 @@ impl CodegenAlternative {
|
|||||||
.ok();
|
.ok();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_tool_use(
|
||||||
|
&mut self,
|
||||||
|
_telemetry_id: String,
|
||||||
|
_provider_id: String,
|
||||||
|
_api_key: Option<String>,
|
||||||
|
tool_use: impl 'static
|
||||||
|
+ Future<
|
||||||
|
Output = Result<language_model::LanguageModelToolUse, LanguageModelCompletionError>,
|
||||||
|
>,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.diff = Diff::default();
|
||||||
|
self.status = CodegenStatus::Pending;
|
||||||
|
|
||||||
|
self.generation = cx.spawn(async move |codegen, cx| {
|
||||||
|
let finish_with_status = |status: CodegenStatus, cx: &mut AsyncApp| {
|
||||||
|
let _ = codegen.update(cx, |this, cx| {
|
||||||
|
this.status = status;
|
||||||
|
cx.emit(CodegenEvent::Finished);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let tool_use = tool_use.await;
|
||||||
|
|
||||||
|
match tool_use {
|
||||||
|
Ok(tool_use) if tool_use.name.as_ref() == "rewrite_section" => {
|
||||||
|
// Parse the input JSON into RewriteSectionInput
|
||||||
|
match serde_json::from_value::<RewriteSectionInput>(tool_use.input) {
|
||||||
|
Ok(input) => {
|
||||||
|
// Store the description if non-empty
|
||||||
|
let description = if !input.description.trim().is_empty() {
|
||||||
|
Some(input.description.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply the replacement text to the buffer and compute diff
|
||||||
|
let batch_diff_task = codegen
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.model_explanation = description.map(Into::into);
|
||||||
|
let range = this.range.clone();
|
||||||
|
this.apply_edits(
|
||||||
|
std::iter::once((range, input.replacement_text)),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
this.reapply_batch_diff(cx)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
// Wait for the diff computation to complete
|
||||||
|
if let Some(diff_task) = batch_diff_task {
|
||||||
|
diff_task.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_with_status(CodegenStatus::Done, cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(tool_use) if tool_use.name.as_ref() == "failure_message" => {
|
||||||
|
// Handle failure message tool use
|
||||||
|
match serde_json::from_value::<FailureMessageInput>(tool_use.input) {
|
||||||
|
Ok(input) => {
|
||||||
|
let _ = codegen.update(cx, |this, _cx| {
|
||||||
|
// Store the failure message as the tool description
|
||||||
|
this.model_explanation = Some(input.message.into());
|
||||||
|
});
|
||||||
|
finish_with_status(CodegenStatus::Done, cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(_tool_use) => {
|
||||||
|
// Unexpected tool.
|
||||||
|
finish_with_status(CodegenStatus::Done, cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
finish_with_status(CodegenStatus::Error(e.into()), cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
@@ -1060,8 +1295,9 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use gpui::TestAppContext;
|
use gpui::TestAppContext;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use language::{Buffer, Language, LanguageConfig, LanguageMatcher, Point, tree_sitter_rust};
|
use language::{Buffer, Point};
|
||||||
use language_model::{LanguageModelRegistry, TokenUsage};
|
use language_model::{LanguageModelRegistry, TokenUsage};
|
||||||
|
use languages::rust_lang;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use std::{future, sync::Arc};
|
use std::{future, sync::Arc};
|
||||||
@@ -1078,7 +1314,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"};
|
"};
|
||||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(rust_lang(), cx));
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
let range = buffer.read_with(cx, |buffer, cx| {
|
let range = buffer.read_with(cx, |buffer, cx| {
|
||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
@@ -1140,7 +1376,7 @@ mod tests {
|
|||||||
le
|
le
|
||||||
}
|
}
|
||||||
"};
|
"};
|
||||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(rust_lang(), cx));
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
let range = buffer.read_with(cx, |buffer, cx| {
|
let range = buffer.read_with(cx, |buffer, cx| {
|
||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
@@ -1204,7 +1440,7 @@ mod tests {
|
|||||||
" \n",
|
" \n",
|
||||||
"}\n" //
|
"}\n" //
|
||||||
);
|
);
|
||||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(rust_lang(), cx));
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
let range = buffer.read_with(cx, |buffer, cx| {
|
let range = buffer.read_with(cx, |buffer, cx| {
|
||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
@@ -1320,7 +1556,7 @@ mod tests {
|
|||||||
let x = 0;
|
let x = 0;
|
||||||
}
|
}
|
||||||
"};
|
"};
|
||||||
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(Arc::new(rust_lang()), cx));
|
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(rust_lang(), cx));
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
let range = buffer.read_with(cx, |buffer, cx| {
|
let range = buffer.read_with(cx, |buffer, cx| {
|
||||||
let snapshot = buffer.snapshot(cx);
|
let snapshot = buffer.snapshot(cx);
|
||||||
@@ -1437,27 +1673,4 @@ mod tests {
|
|||||||
});
|
});
|
||||||
chunks_tx
|
chunks_tx
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rust_lang() -> Language {
|
|
||||||
Language::new(
|
|
||||||
LanguageConfig {
|
|
||||||
name: "Rust".into(),
|
|
||||||
matcher: LanguageMatcher {
|
|
||||||
path_suffixes: vec!["rs".to_string()],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
|
||||||
)
|
|
||||||
.with_indents_query(
|
|
||||||
r#"
|
|
||||||
(call_expression) @indent
|
|
||||||
(field_expression) @indent
|
|
||||||
(_ "(" ")" @end) @indent
|
|
||||||
(_ "{" "}" @end) @indent
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -387,17 +387,9 @@ impl InlineAssistant {
|
|||||||
let mut selections = Vec::<Selection<Point>>::new();
|
let mut selections = Vec::<Selection<Point>>::new();
|
||||||
let mut newest_selection = None;
|
let mut newest_selection = None;
|
||||||
for mut selection in initial_selections {
|
for mut selection in initial_selections {
|
||||||
if selection.end > selection.start {
|
if selection.end == selection.start
|
||||||
selection.start.column = 0;
|
&& let Some(fold) =
|
||||||
// If the selection ends at the start of the line, we don't want to include it.
|
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
|
||||||
if selection.end.column == 0 {
|
|
||||||
selection.end.row -= 1;
|
|
||||||
}
|
|
||||||
selection.end.column = snapshot
|
|
||||||
.buffer_snapshot()
|
|
||||||
.line_len(MultiBufferRow(selection.end.row));
|
|
||||||
} else if let Some(fold) =
|
|
||||||
snapshot.crease_for_buffer_row(MultiBufferRow(selection.end.row))
|
|
||||||
{
|
{
|
||||||
selection.start = fold.range().start;
|
selection.start = fold.range().start;
|
||||||
selection.end = fold.range().end;
|
selection.end = fold.range().end;
|
||||||
@@ -424,6 +416,15 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
selection.start.column = 0;
|
||||||
|
// If the selection ends at the start of the line, we don't want to include it.
|
||||||
|
if selection.end.column == 0 && selection.start.row != selection.end.row {
|
||||||
|
selection.end.row -= 1;
|
||||||
|
}
|
||||||
|
selection.end.column = snapshot
|
||||||
|
.buffer_snapshot()
|
||||||
|
.line_len(MultiBufferRow(selection.end.row));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(prev_selection) = selections.last_mut()
|
if let Some(prev_selection) = selections.last_mut()
|
||||||
@@ -544,14 +545,15 @@ impl InlineAssistant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let [prompt_block_id, end_block_id] =
|
let [prompt_block_id, tool_description_block_id, end_block_id] =
|
||||||
self.insert_assist_blocks(editor, &range, &prompt_editor, cx);
|
self.insert_assist_blocks(&editor, &range, &prompt_editor, cx);
|
||||||
|
|
||||||
assists.push((
|
assists.push((
|
||||||
assist_id,
|
assist_id,
|
||||||
range.clone(),
|
range.clone(),
|
||||||
prompt_editor,
|
prompt_editor,
|
||||||
prompt_block_id,
|
prompt_block_id,
|
||||||
|
tool_description_block_id,
|
||||||
end_block_id,
|
end_block_id,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -570,7 +572,15 @@ impl InlineAssistant {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut assist_group = InlineAssistGroup::new();
|
let mut assist_group = InlineAssistGroup::new();
|
||||||
for (assist_id, range, prompt_editor, prompt_block_id, end_block_id) in assists {
|
for (
|
||||||
|
assist_id,
|
||||||
|
range,
|
||||||
|
prompt_editor,
|
||||||
|
prompt_block_id,
|
||||||
|
tool_description_block_id,
|
||||||
|
end_block_id,
|
||||||
|
) in assists
|
||||||
|
{
|
||||||
let codegen = prompt_editor.read(cx).codegen().clone();
|
let codegen = prompt_editor.read(cx).codegen().clone();
|
||||||
|
|
||||||
self.assists.insert(
|
self.assists.insert(
|
||||||
@@ -581,6 +591,7 @@ impl InlineAssistant {
|
|||||||
editor,
|
editor,
|
||||||
&prompt_editor,
|
&prompt_editor,
|
||||||
prompt_block_id,
|
prompt_block_id,
|
||||||
|
tool_description_block_id,
|
||||||
end_block_id,
|
end_block_id,
|
||||||
range,
|
range,
|
||||||
codegen,
|
codegen,
|
||||||
@@ -689,7 +700,7 @@ impl InlineAssistant {
|
|||||||
range: &Range<Anchor>,
|
range: &Range<Anchor>,
|
||||||
prompt_editor: &Entity<PromptEditor<BufferCodegen>>,
|
prompt_editor: &Entity<PromptEditor<BufferCodegen>>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> [CustomBlockId; 2] {
|
) -> [CustomBlockId; 3] {
|
||||||
let prompt_editor_height = prompt_editor.update(cx, |prompt_editor, cx| {
|
let prompt_editor_height = prompt_editor.update(cx, |prompt_editor, cx| {
|
||||||
prompt_editor
|
prompt_editor
|
||||||
.editor
|
.editor
|
||||||
@@ -703,6 +714,14 @@ impl InlineAssistant {
|
|||||||
render: build_assist_editor_renderer(prompt_editor),
|
render: build_assist_editor_renderer(prompt_editor),
|
||||||
priority: 0,
|
priority: 0,
|
||||||
},
|
},
|
||||||
|
// Placeholder for tool description - will be updated dynamically
|
||||||
|
BlockProperties {
|
||||||
|
style: BlockStyle::Flex,
|
||||||
|
placement: BlockPlacement::Below(range.end),
|
||||||
|
height: Some(0),
|
||||||
|
render: Arc::new(|_cx| div().into_any_element()),
|
||||||
|
priority: 0,
|
||||||
|
},
|
||||||
BlockProperties {
|
BlockProperties {
|
||||||
style: BlockStyle::Sticky,
|
style: BlockStyle::Sticky,
|
||||||
placement: BlockPlacement::Below(range.end),
|
placement: BlockPlacement::Below(range.end),
|
||||||
@@ -721,7 +740,7 @@ impl InlineAssistant {
|
|||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
let block_ids = editor.insert_blocks(assist_blocks, None, cx);
|
let block_ids = editor.insert_blocks(assist_blocks, None, cx);
|
||||||
[block_ids[0], block_ids[1]]
|
[block_ids[0], block_ids[1], block_ids[2]]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1113,6 +1132,9 @@ impl InlineAssistant {
|
|||||||
let mut to_remove = decorations.removed_line_block_ids;
|
let mut to_remove = decorations.removed_line_block_ids;
|
||||||
to_remove.insert(decorations.prompt_block_id);
|
to_remove.insert(decorations.prompt_block_id);
|
||||||
to_remove.insert(decorations.end_block_id);
|
to_remove.insert(decorations.end_block_id);
|
||||||
|
if let Some(tool_description_block_id) = decorations.model_explanation {
|
||||||
|
to_remove.insert(tool_description_block_id);
|
||||||
|
}
|
||||||
editor.remove_blocks(to_remove, None, cx);
|
editor.remove_blocks(to_remove, None, cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1433,8 +1455,60 @@ impl InlineAssistant {
|
|||||||
let old_snapshot = codegen.snapshot(cx);
|
let old_snapshot = codegen.snapshot(cx);
|
||||||
let old_buffer = codegen.old_buffer(cx);
|
let old_buffer = codegen.old_buffer(cx);
|
||||||
let deleted_row_ranges = codegen.diff(cx).deleted_row_ranges.clone();
|
let deleted_row_ranges = codegen.diff(cx).deleted_row_ranges.clone();
|
||||||
|
// let model_explanation = codegen.model_explanation(cx);
|
||||||
|
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
|
// Update tool description block
|
||||||
|
// if let Some(description) = model_explanation {
|
||||||
|
// if let Some(block_id) = decorations.model_explanation {
|
||||||
|
// editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
|
||||||
|
// let new_block_id = editor.insert_blocks(
|
||||||
|
// [BlockProperties {
|
||||||
|
// style: BlockStyle::Flex,
|
||||||
|
// placement: BlockPlacement::Below(assist.range.end),
|
||||||
|
// height: Some(1),
|
||||||
|
// render: Arc::new({
|
||||||
|
// let description = description.clone();
|
||||||
|
// move |cx| {
|
||||||
|
// div()
|
||||||
|
// .w_full()
|
||||||
|
// .py_1()
|
||||||
|
// .px_2()
|
||||||
|
// .bg(cx.theme().colors().editor_background)
|
||||||
|
// .border_y_1()
|
||||||
|
// .border_color(cx.theme().status().info_border)
|
||||||
|
// .child(
|
||||||
|
// Label::new(description.clone())
|
||||||
|
// .color(Color::Muted)
|
||||||
|
// .size(LabelSize::Small),
|
||||||
|
// )
|
||||||
|
// .into_any_element()
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// priority: 0,
|
||||||
|
// }],
|
||||||
|
// None,
|
||||||
|
// cx,
|
||||||
|
// );
|
||||||
|
// decorations.model_explanation = new_block_id.into_iter().next();
|
||||||
|
// }
|
||||||
|
// } else if let Some(block_id) = decorations.model_explanation {
|
||||||
|
// // Hide the block if there's no description
|
||||||
|
// editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
|
||||||
|
// let new_block_id = editor.insert_blocks(
|
||||||
|
// [BlockProperties {
|
||||||
|
// style: BlockStyle::Flex,
|
||||||
|
// placement: BlockPlacement::Below(assist.range.end),
|
||||||
|
// height: Some(0),
|
||||||
|
// render: Arc::new(|_cx| div().into_any_element()),
|
||||||
|
// priority: 0,
|
||||||
|
// }],
|
||||||
|
// None,
|
||||||
|
// cx,
|
||||||
|
// );
|
||||||
|
// decorations.model_explanation = new_block_id.into_iter().next();
|
||||||
|
// }
|
||||||
|
|
||||||
let old_blocks = mem::take(&mut decorations.removed_line_block_ids);
|
let old_blocks = mem::take(&mut decorations.removed_line_block_ids);
|
||||||
editor.remove_blocks(old_blocks, None, cx);
|
editor.remove_blocks(old_blocks, None, cx);
|
||||||
|
|
||||||
@@ -1686,6 +1760,7 @@ impl InlineAssist {
|
|||||||
editor: &Entity<Editor>,
|
editor: &Entity<Editor>,
|
||||||
prompt_editor: &Entity<PromptEditor<BufferCodegen>>,
|
prompt_editor: &Entity<PromptEditor<BufferCodegen>>,
|
||||||
prompt_block_id: CustomBlockId,
|
prompt_block_id: CustomBlockId,
|
||||||
|
tool_description_block_id: CustomBlockId,
|
||||||
end_block_id: CustomBlockId,
|
end_block_id: CustomBlockId,
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
codegen: Entity<BufferCodegen>,
|
codegen: Entity<BufferCodegen>,
|
||||||
@@ -1700,7 +1775,8 @@ impl InlineAssist {
|
|||||||
decorations: Some(InlineAssistDecorations {
|
decorations: Some(InlineAssistDecorations {
|
||||||
prompt_block_id,
|
prompt_block_id,
|
||||||
prompt_editor: prompt_editor.clone(),
|
prompt_editor: prompt_editor.clone(),
|
||||||
removed_line_block_ids: HashSet::default(),
|
removed_line_block_ids: Default::default(),
|
||||||
|
model_explanation: Some(tool_description_block_id),
|
||||||
end_block_id,
|
end_block_id,
|
||||||
}),
|
}),
|
||||||
range,
|
range,
|
||||||
@@ -1804,6 +1880,7 @@ struct InlineAssistDecorations {
|
|||||||
prompt_block_id: CustomBlockId,
|
prompt_block_id: CustomBlockId,
|
||||||
prompt_editor: Entity<PromptEditor<BufferCodegen>>,
|
prompt_editor: Entity<PromptEditor<BufferCodegen>>,
|
||||||
removed_line_block_ids: HashSet<CustomBlockId>,
|
removed_line_block_ids: HashSet<CustomBlockId>,
|
||||||
|
model_explanation: Option<CustomBlockId>,
|
||||||
end_block_id: CustomBlockId,
|
end_block_id: CustomBlockId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ use editor::{
|
|||||||
};
|
};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, Context, CursorStyle, Entity, EventEmitter, FocusHandle, Focusable,
|
AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
|
||||||
Subscription, TextStyle, WeakEntity, Window,
|
TextStyle, TextStyleRefinement, WeakEntity, Window,
|
||||||
};
|
};
|
||||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||||
|
use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use prompt_store::PromptStore;
|
use prompt_store::PromptStore;
|
||||||
@@ -65,7 +66,7 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||||||
|
|
||||||
const RIGHT_PADDING: Pixels = px(9.);
|
const RIGHT_PADDING: Pixels = px(9.);
|
||||||
|
|
||||||
let (left_gutter_width, right_padding) = match &self.mode {
|
let (left_gutter_width, right_padding, explanation) = match &self.mode {
|
||||||
PromptEditorMode::Buffer {
|
PromptEditorMode::Buffer {
|
||||||
id: _,
|
id: _,
|
||||||
codegen,
|
codegen,
|
||||||
@@ -83,17 +84,23 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||||||
let left_gutter_width = gutter.full_width() + (gutter.margin / 2.0);
|
let left_gutter_width = gutter.full_width() + (gutter.margin / 2.0);
|
||||||
let right_padding = editor_margins.right + RIGHT_PADDING;
|
let right_padding = editor_margins.right + RIGHT_PADDING;
|
||||||
|
|
||||||
(left_gutter_width, right_padding)
|
let explanation = codegen
|
||||||
|
.active_alternative()
|
||||||
|
.read(cx)
|
||||||
|
.model_explanation
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
(left_gutter_width, right_padding, explanation)
|
||||||
}
|
}
|
||||||
PromptEditorMode::Terminal { .. } => {
|
PromptEditorMode::Terminal { .. } => {
|
||||||
// Give the equivalent of the same left-padding that we're using on the right
|
// Give the equivalent of the same left-padding that we're using on the right
|
||||||
(Pixels::from(40.0), Pixels::from(24.))
|
(Pixels::from(40.0), Pixels::from(24.), None)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let bottom_padding = match &self.mode {
|
let bottom_padding = match &self.mode {
|
||||||
PromptEditorMode::Buffer { .. } => rems_from_px(2.0),
|
PromptEditorMode::Buffer { .. } => rems_from_px(2.0),
|
||||||
PromptEditorMode::Terminal { .. } => rems_from_px(8.0),
|
PromptEditorMode::Terminal { .. } => rems_from_px(4.0),
|
||||||
};
|
};
|
||||||
|
|
||||||
buttons.extend(self.render_buttons(window, cx));
|
buttons.extend(self.render_buttons(window, cx));
|
||||||
@@ -111,22 +118,33 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||||||
this.trigger_completion_menu(window, cx);
|
this.trigger_completion_menu(window, cx);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let markdown = window.use_state(cx, |_, cx| Markdown::new("".into(), None, None, cx));
|
||||||
|
|
||||||
|
if let Some(explanation) = &explanation {
|
||||||
|
markdown.update(cx, |markdown, cx| {
|
||||||
|
markdown.reset(explanation.clone(), cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let explanation_label = self
|
||||||
|
.render_markdown(markdown, markdown_style(window, cx))
|
||||||
|
.into_any_element();
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.key_context("PromptEditor")
|
.key_context("PromptEditor")
|
||||||
.capture_action(cx.listener(Self::paste))
|
.capture_action(cx.listener(Self::paste))
|
||||||
.bg(cx.theme().colors().editor_background)
|
|
||||||
.block_mouse_except_scroll()
|
.block_mouse_except_scroll()
|
||||||
.gap_0p5()
|
|
||||||
.border_y_1()
|
|
||||||
.border_color(cx.theme().status().info_border)
|
|
||||||
.size_full()
|
.size_full()
|
||||||
.pt_0p5()
|
.pt_0p5()
|
||||||
.pb(bottom_padding)
|
.pb(bottom_padding)
|
||||||
.pr(right_padding)
|
.pr(right_padding)
|
||||||
|
.gap_0p5()
|
||||||
|
.justify_center()
|
||||||
|
.border_y_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.bg(cx.theme().colors().editor_background)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.items_start()
|
|
||||||
.cursor(CursorStyle::Arrow)
|
|
||||||
.on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
|
.on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
|
||||||
this.model_selector
|
this.model_selector
|
||||||
.update(cx, |model_selector, cx| model_selector.toggle(window, cx));
|
.update(cx, |model_selector, cx| model_selector.toggle(window, cx));
|
||||||
@@ -139,14 +157,14 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||||||
.capture_action(cx.listener(Self::cycle_next))
|
.capture_action(cx.listener(Self::cycle_next))
|
||||||
.child(
|
.child(
|
||||||
WithRemSize::new(ui_font_size)
|
WithRemSize::new(ui_font_size)
|
||||||
|
.h_full()
|
||||||
|
.w(left_gutter_width)
|
||||||
.flex()
|
.flex()
|
||||||
.flex_row()
|
.flex_row()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.items_center()
|
.items_center()
|
||||||
.h_full()
|
|
||||||
.w(left_gutter_width)
|
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.gap_2()
|
.gap_1()
|
||||||
.child(self.render_close_button(cx))
|
.child(self.render_close_button(cx))
|
||||||
.map(|el| {
|
.map(|el| {
|
||||||
let CodegenStatus::Error(error) = self.codegen_status(cx) else {
|
let CodegenStatus::Error(error) = self.codegen_status(cx) else {
|
||||||
@@ -177,26 +195,83 @@ impl<T: 'static> Render for PromptEditor<T> {
|
|||||||
.flex_row()
|
.flex_row()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
.child(add_context_button)
|
||||||
|
.child(self.model_selector.clone())
|
||||||
.children(buttons),
|
.children(buttons),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.when_some(explanation, |this, _| {
|
||||||
WithRemSize::new(ui_font_size)
|
this.child(
|
||||||
.flex()
|
h_flex()
|
||||||
.flex_row()
|
.size_full()
|
||||||
.items_center()
|
.justify_center()
|
||||||
.child(h_flex().flex_shrink_0().w(left_gutter_width))
|
.child(div().w(left_gutter_width + px(6.)))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
div()
|
||||||
.w_full()
|
.size_full()
|
||||||
.pl_1()
|
.min_w_0()
|
||||||
.items_start()
|
.pt(rems_from_px(3.))
|
||||||
.justify_between()
|
.pl_0p5()
|
||||||
.child(add_context_button)
|
.flex_1()
|
||||||
.child(self.model_selector.clone()),
|
.border_t_1()
|
||||||
),
|
.border_color(cx.theme().colors().border_variant)
|
||||||
)
|
.child(explanation_label),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
|
||||||
|
let theme_settings = ThemeSettings::get_global(cx);
|
||||||
|
let colors = cx.theme().colors();
|
||||||
|
let mut text_style = window.text_style();
|
||||||
|
|
||||||
|
text_style.refine(&TextStyleRefinement {
|
||||||
|
font_family: Some(theme_settings.ui_font.family.clone()),
|
||||||
|
color: Some(colors.text),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
MarkdownStyle {
|
||||||
|
base_text_style: text_style.clone(),
|
||||||
|
syntax: cx.theme().syntax().clone(),
|
||||||
|
selection_background_color: colors.element_selection_background,
|
||||||
|
heading_level_styles: Some(HeadingLevelStyles {
|
||||||
|
h1: Some(TextStyleRefinement {
|
||||||
|
font_size: Some(rems(1.15).into()),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
h2: Some(TextStyleRefinement {
|
||||||
|
font_size: Some(rems(1.1).into()),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
h3: Some(TextStyleRefinement {
|
||||||
|
font_size: Some(rems(1.05).into()),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
h4: Some(TextStyleRefinement {
|
||||||
|
font_size: Some(rems(1.).into()),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
h5: Some(TextStyleRefinement {
|
||||||
|
font_size: Some(rems(0.95).into()),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
h6: Some(TextStyleRefinement {
|
||||||
|
font_size: Some(rems(0.875).into()),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
inline_code: TextStyleRefinement {
|
||||||
|
font_family: Some(theme_settings.buffer_font.family.clone()),
|
||||||
|
font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
|
||||||
|
font_features: Some(theme_settings.buffer_font.features.clone()),
|
||||||
|
background_color: Some(colors.editor_foreground.opacity(0.08)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,6 +834,10 @@ impl<T: 'static> PromptEditor<T> {
|
|||||||
})
|
})
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_markdown(&self, markdown: Entity<Markdown>, style: MarkdownStyle) -> MarkdownElement {
|
||||||
|
MarkdownElement::new(markdown, style)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum PromptEditorMode {
|
pub enum PromptEditorMode {
|
||||||
|
|||||||
@@ -1682,6 +1682,98 @@ impl TextThreadEditor {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
let editor_clipboard_selections = cx
|
||||||
|
.read_from_clipboard()
|
||||||
|
.and_then(|item| item.entries().first().cloned())
|
||||||
|
.and_then(|entry| match entry {
|
||||||
|
ClipboardEntry::String(text) => {
|
||||||
|
text.metadata_json::<Vec<editor::ClipboardSelection>>()
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let has_file_context = editor_clipboard_selections
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|selections| {
|
||||||
|
selections
|
||||||
|
.iter()
|
||||||
|
.any(|sel| sel.file_path.is_some() && sel.line_range.is_some())
|
||||||
|
});
|
||||||
|
|
||||||
|
if has_file_context {
|
||||||
|
if let Some(clipboard_item) = cx.read_from_clipboard() {
|
||||||
|
if let Some(ClipboardEntry::String(clipboard_text)) =
|
||||||
|
clipboard_item.entries().first()
|
||||||
|
{
|
||||||
|
if let Some(selections) = editor_clipboard_selections {
|
||||||
|
cx.stop_propagation();
|
||||||
|
|
||||||
|
let text = clipboard_text.text();
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
let mut current_offset = 0;
|
||||||
|
let weak_editor = cx.entity().downgrade();
|
||||||
|
|
||||||
|
for selection in selections {
|
||||||
|
if let (Some(file_path), Some(line_range)) =
|
||||||
|
(selection.file_path, selection.line_range)
|
||||||
|
{
|
||||||
|
let selected_text =
|
||||||
|
&text[current_offset..current_offset + selection.len];
|
||||||
|
let fence = assistant_slash_commands::codeblock_fence_for_path(
|
||||||
|
file_path.to_str(),
|
||||||
|
Some(line_range.clone()),
|
||||||
|
);
|
||||||
|
let formatted_text = format!("{fence}{selected_text}\n```");
|
||||||
|
|
||||||
|
let insert_point = editor
|
||||||
|
.selections
|
||||||
|
.newest::<Point>(&editor.display_snapshot(cx))
|
||||||
|
.head();
|
||||||
|
let start_row = MultiBufferRow(insert_point.row);
|
||||||
|
|
||||||
|
editor.insert(&formatted_text, window, cx);
|
||||||
|
|
||||||
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
let anchor_before = snapshot.anchor_after(insert_point);
|
||||||
|
let anchor_after = editor
|
||||||
|
.selections
|
||||||
|
.newest_anchor()
|
||||||
|
.head()
|
||||||
|
.bias_left(&snapshot);
|
||||||
|
|
||||||
|
editor.insert("\n", window, cx);
|
||||||
|
|
||||||
|
let crease_text = acp_thread::selection_name(
|
||||||
|
Some(file_path.as_ref()),
|
||||||
|
&line_range,
|
||||||
|
);
|
||||||
|
|
||||||
|
let fold_placeholder = quote_selection_fold_placeholder(
|
||||||
|
crease_text,
|
||||||
|
weak_editor.clone(),
|
||||||
|
);
|
||||||
|
let crease = Crease::inline(
|
||||||
|
anchor_before..anchor_after,
|
||||||
|
fold_placeholder,
|
||||||
|
render_quote_selection_output_toggle,
|
||||||
|
|_, _, _, _| Empty.into_any(),
|
||||||
|
);
|
||||||
|
editor.insert_creases(vec![crease], cx);
|
||||||
|
editor.fold_at(start_row, window, cx);
|
||||||
|
|
||||||
|
current_offset += selection.len;
|
||||||
|
if !selection.is_entire_line && current_offset < text.len() {
|
||||||
|
current_offset += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
|
|
||||||
let mut images = if let Some(item) = cx.read_from_clipboard() {
|
let mut images = if let Some(item) = cx.read_from_clipboard() {
|
||||||
@@ -2622,11 +2714,13 @@ impl SearchableItem for TextThreadEditor {
|
|||||||
fn update_matches(
|
fn update_matches(
|
||||||
&mut self,
|
&mut self,
|
||||||
matches: &[Self::Match],
|
matches: &[Self::Match],
|
||||||
|
active_match_index: Option<usize>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.editor
|
self.editor.update(cx, |editor, cx| {
|
||||||
.update(cx, |editor, cx| editor.update_matches(matches, window, cx));
|
editor.update_matches(matches, active_match_index, window, cx)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
|
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
|
||||||
|
|||||||
@@ -106,9 +106,6 @@ impl Render for AgentNotification {
|
|||||||
.font(ui_font)
|
.font(ui_font)
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
.rounded_xl()
|
.rounded_xl()
|
||||||
.on_click(cx.listener(|_, _, _, cx| {
|
|
||||||
cx.emit(AgentNotificationEvent::Accepted);
|
|
||||||
}))
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.items_start()
|
.items_start()
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ pub use settings::{AnthropicAvailableModel as AvailableModel, ModelMode};
|
|||||||
use strum::{EnumIter, EnumString};
|
use strum::{EnumIter, EnumString};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub mod batches;
|
||||||
|
|
||||||
pub const ANTHROPIC_API_URL: &str = "https://api.anthropic.com";
|
pub const ANTHROPIC_API_URL: &str = "https://api.anthropic.com";
|
||||||
|
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
@@ -465,6 +467,7 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate completion with streaming.
|
||||||
pub async fn stream_completion(
|
pub async fn stream_completion(
|
||||||
client: &dyn HttpClient,
|
client: &dyn HttpClient,
|
||||||
api_url: &str,
|
api_url: &str,
|
||||||
@@ -477,6 +480,101 @@ pub async fn stream_completion(
|
|||||||
.map(|output| output.0)
|
.map(|output| output.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate completion without streaming.
|
||||||
|
pub async fn non_streaming_completion(
|
||||||
|
client: &dyn HttpClient,
|
||||||
|
api_url: &str,
|
||||||
|
api_key: &str,
|
||||||
|
request: Request,
|
||||||
|
beta_headers: Option<String>,
|
||||||
|
) -> Result<Response, AnthropicError> {
|
||||||
|
let (mut response, rate_limits) =
|
||||||
|
send_request(client, api_url, api_key, &request, beta_headers).await?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let mut body = String::new();
|
||||||
|
response
|
||||||
|
.body_mut()
|
||||||
|
.read_to_string(&mut body)
|
||||||
|
.await
|
||||||
|
.map_err(AnthropicError::ReadResponse)?;
|
||||||
|
|
||||||
|
serde_json::from_str(&body).map_err(AnthropicError::DeserializeResponse)
|
||||||
|
} else {
|
||||||
|
Err(handle_error_response(response, rate_limits).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_request(
|
||||||
|
client: &dyn HttpClient,
|
||||||
|
api_url: &str,
|
||||||
|
api_key: &str,
|
||||||
|
request: impl Serialize,
|
||||||
|
beta_headers: Option<String>,
|
||||||
|
) -> Result<(http::Response<AsyncBody>, RateLimitInfo), AnthropicError> {
|
||||||
|
let uri = format!("{api_url}/v1/messages");
|
||||||
|
|
||||||
|
let mut request_builder = HttpRequest::builder()
|
||||||
|
.method(Method::POST)
|
||||||
|
.uri(uri)
|
||||||
|
.header("Anthropic-Version", "2023-06-01")
|
||||||
|
.header("X-Api-Key", api_key.trim())
|
||||||
|
.header("Content-Type", "application/json");
|
||||||
|
|
||||||
|
if let Some(beta_headers) = beta_headers {
|
||||||
|
request_builder = request_builder.header("Anthropic-Beta", beta_headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
let serialized_request =
|
||||||
|
serde_json::to_string(&request).map_err(AnthropicError::SerializeRequest)?;
|
||||||
|
let request = request_builder
|
||||||
|
.body(AsyncBody::from(serialized_request))
|
||||||
|
.map_err(AnthropicError::BuildRequestBody)?;
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.send(request)
|
||||||
|
.await
|
||||||
|
.map_err(AnthropicError::HttpSend)?;
|
||||||
|
|
||||||
|
let rate_limits = RateLimitInfo::from_headers(response.headers());
|
||||||
|
|
||||||
|
Ok((response, rate_limits))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_error_response(
|
||||||
|
mut response: http::Response<AsyncBody>,
|
||||||
|
rate_limits: RateLimitInfo,
|
||||||
|
) -> AnthropicError {
|
||||||
|
if response.status().as_u16() == 529 {
|
||||||
|
return AnthropicError::ServerOverloaded {
|
||||||
|
retry_after: rate_limits.retry_after,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(retry_after) = rate_limits.retry_after {
|
||||||
|
return AnthropicError::RateLimit { retry_after };
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut body = String::new();
|
||||||
|
let read_result = response
|
||||||
|
.body_mut()
|
||||||
|
.read_to_string(&mut body)
|
||||||
|
.await
|
||||||
|
.map_err(AnthropicError::ReadResponse);
|
||||||
|
|
||||||
|
if let Err(err) = read_result {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
match serde_json::from_str::<Event>(&body) {
|
||||||
|
Ok(Event::Error { error }) => AnthropicError::ApiError(error),
|
||||||
|
Ok(_) | Err(_) => AnthropicError::HttpResponseError {
|
||||||
|
status_code: response.status(),
|
||||||
|
message: body,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An individual rate limit.
|
/// An individual rate limit.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RateLimit {
|
pub struct RateLimit {
|
||||||
@@ -580,30 +678,10 @@ pub async fn stream_completion_with_rate_limit_info(
|
|||||||
base: request,
|
base: request,
|
||||||
stream: true,
|
stream: true,
|
||||||
};
|
};
|
||||||
let uri = format!("{api_url}/v1/messages");
|
|
||||||
|
|
||||||
let mut request_builder = HttpRequest::builder()
|
let (response, rate_limits) =
|
||||||
.method(Method::POST)
|
send_request(client, api_url, api_key, &request, beta_headers).await?;
|
||||||
.uri(uri)
|
|
||||||
.header("Anthropic-Version", "2023-06-01")
|
|
||||||
.header("X-Api-Key", api_key.trim())
|
|
||||||
.header("Content-Type", "application/json");
|
|
||||||
|
|
||||||
if let Some(beta_headers) = beta_headers {
|
|
||||||
request_builder = request_builder.header("Anthropic-Beta", beta_headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
let serialized_request =
|
|
||||||
serde_json::to_string(&request).map_err(AnthropicError::SerializeRequest)?;
|
|
||||||
let request = request_builder
|
|
||||||
.body(AsyncBody::from(serialized_request))
|
|
||||||
.map_err(AnthropicError::BuildRequestBody)?;
|
|
||||||
|
|
||||||
let mut response = client
|
|
||||||
.send(request)
|
|
||||||
.await
|
|
||||||
.map_err(AnthropicError::HttpSend)?;
|
|
||||||
let rate_limits = RateLimitInfo::from_headers(response.headers());
|
|
||||||
if response.status().is_success() {
|
if response.status().is_success() {
|
||||||
let reader = BufReader::new(response.into_body());
|
let reader = BufReader::new(response.into_body());
|
||||||
let stream = reader
|
let stream = reader
|
||||||
@@ -622,27 +700,8 @@ pub async fn stream_completion_with_rate_limit_info(
|
|||||||
})
|
})
|
||||||
.boxed();
|
.boxed();
|
||||||
Ok((stream, Some(rate_limits)))
|
Ok((stream, Some(rate_limits)))
|
||||||
} else if response.status().as_u16() == 529 {
|
|
||||||
Err(AnthropicError::ServerOverloaded {
|
|
||||||
retry_after: rate_limits.retry_after,
|
|
||||||
})
|
|
||||||
} else if let Some(retry_after) = rate_limits.retry_after {
|
|
||||||
Err(AnthropicError::RateLimit { retry_after })
|
|
||||||
} else {
|
} else {
|
||||||
let mut body = String::new();
|
Err(handle_error_response(response, rate_limits).await)
|
||||||
response
|
|
||||||
.body_mut()
|
|
||||||
.read_to_string(&mut body)
|
|
||||||
.await
|
|
||||||
.map_err(AnthropicError::ReadResponse)?;
|
|
||||||
|
|
||||||
match serde_json::from_str::<Event>(&body) {
|
|
||||||
Ok(Event::Error { error }) => Err(AnthropicError::ApiError(error)),
|
|
||||||
Ok(_) | Err(_) => Err(AnthropicError::HttpResponseError {
|
|
||||||
status_code: response.status(),
|
|
||||||
message: body,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
190
crates/anthropic/src/batches.rs
Normal file
190
crates/anthropic/src/batches.rs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use futures::AsyncReadExt;
|
||||||
|
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{AnthropicError, ApiError, RateLimitInfo, Request, Response};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BatchRequest {
|
||||||
|
pub custom_id: String,
|
||||||
|
pub params: Request,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct CreateBatchRequest {
|
||||||
|
pub requests: Vec<BatchRequest>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct MessageBatchRequestCounts {
|
||||||
|
pub processing: u64,
|
||||||
|
pub succeeded: u64,
|
||||||
|
pub errored: u64,
|
||||||
|
pub canceled: u64,
|
||||||
|
pub expired: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct MessageBatch {
|
||||||
|
pub id: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub batch_type: String,
|
||||||
|
pub processing_status: String,
|
||||||
|
pub request_counts: MessageBatchRequestCounts,
|
||||||
|
pub ended_at: Option<String>,
|
||||||
|
pub created_at: String,
|
||||||
|
pub expires_at: String,
|
||||||
|
pub archived_at: Option<String>,
|
||||||
|
pub cancel_initiated_at: Option<String>,
|
||||||
|
pub results_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum BatchResult {
|
||||||
|
#[serde(rename = "succeeded")]
|
||||||
|
Succeeded { message: Response },
|
||||||
|
#[serde(rename = "errored")]
|
||||||
|
Errored { error: ApiError },
|
||||||
|
#[serde(rename = "canceled")]
|
||||||
|
Canceled,
|
||||||
|
#[serde(rename = "expired")]
|
||||||
|
Expired,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BatchIndividualResponse {
|
||||||
|
pub custom_id: String,
|
||||||
|
pub result: BatchResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_batch(
|
||||||
|
client: &dyn HttpClient,
|
||||||
|
api_url: &str,
|
||||||
|
api_key: &str,
|
||||||
|
request: CreateBatchRequest,
|
||||||
|
) -> Result<MessageBatch, AnthropicError> {
|
||||||
|
let uri = format!("{api_url}/v1/messages/batches");
|
||||||
|
|
||||||
|
let request_builder = HttpRequest::builder()
|
||||||
|
.method(Method::POST)
|
||||||
|
.uri(uri)
|
||||||
|
.header("Anthropic-Version", "2023-06-01")
|
||||||
|
.header("X-Api-Key", api_key.trim())
|
||||||
|
.header("Content-Type", "application/json");
|
||||||
|
|
||||||
|
let serialized_request =
|
||||||
|
serde_json::to_string(&request).map_err(AnthropicError::SerializeRequest)?;
|
||||||
|
let http_request = request_builder
|
||||||
|
.body(AsyncBody::from(serialized_request))
|
||||||
|
.map_err(AnthropicError::BuildRequestBody)?;
|
||||||
|
|
||||||
|
let mut response = client
|
||||||
|
.send(http_request)
|
||||||
|
.await
|
||||||
|
.map_err(AnthropicError::HttpSend)?;
|
||||||
|
|
||||||
|
let rate_limits = RateLimitInfo::from_headers(response.headers());
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let mut body = String::new();
|
||||||
|
response
|
||||||
|
.body_mut()
|
||||||
|
.read_to_string(&mut body)
|
||||||
|
.await
|
||||||
|
.map_err(AnthropicError::ReadResponse)?;
|
||||||
|
|
||||||
|
serde_json::from_str(&body).map_err(AnthropicError::DeserializeResponse)
|
||||||
|
} else {
|
||||||
|
Err(crate::handle_error_response(response, rate_limits).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn retrieve_batch(
|
||||||
|
client: &dyn HttpClient,
|
||||||
|
api_url: &str,
|
||||||
|
api_key: &str,
|
||||||
|
message_batch_id: &str,
|
||||||
|
) -> Result<MessageBatch, AnthropicError> {
|
||||||
|
let uri = format!("{api_url}/v1/messages/batches/{message_batch_id}");
|
||||||
|
|
||||||
|
let request_builder = HttpRequest::builder()
|
||||||
|
.method(Method::GET)
|
||||||
|
.uri(uri)
|
||||||
|
.header("Anthropic-Version", "2023-06-01")
|
||||||
|
.header("X-Api-Key", api_key.trim());
|
||||||
|
|
||||||
|
let http_request = request_builder
|
||||||
|
.body(AsyncBody::default())
|
||||||
|
.map_err(AnthropicError::BuildRequestBody)?;
|
||||||
|
|
||||||
|
let mut response = client
|
||||||
|
.send(http_request)
|
||||||
|
.await
|
||||||
|
.map_err(AnthropicError::HttpSend)?;
|
||||||
|
|
||||||
|
let rate_limits = RateLimitInfo::from_headers(response.headers());
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let mut body = String::new();
|
||||||
|
response
|
||||||
|
.body_mut()
|
||||||
|
.read_to_string(&mut body)
|
||||||
|
.await
|
||||||
|
.map_err(AnthropicError::ReadResponse)?;
|
||||||
|
|
||||||
|
serde_json::from_str(&body).map_err(AnthropicError::DeserializeResponse)
|
||||||
|
} else {
|
||||||
|
Err(crate::handle_error_response(response, rate_limits).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn retrieve_batch_results(
|
||||||
|
client: &dyn HttpClient,
|
||||||
|
api_url: &str,
|
||||||
|
api_key: &str,
|
||||||
|
message_batch_id: &str,
|
||||||
|
) -> Result<Vec<BatchIndividualResponse>, AnthropicError> {
|
||||||
|
let uri = format!("{api_url}/v1/messages/batches/{message_batch_id}/results");
|
||||||
|
|
||||||
|
let request_builder = HttpRequest::builder()
|
||||||
|
.method(Method::GET)
|
||||||
|
.uri(uri)
|
||||||
|
.header("Anthropic-Version", "2023-06-01")
|
||||||
|
.header("X-Api-Key", api_key.trim());
|
||||||
|
|
||||||
|
let http_request = request_builder
|
||||||
|
.body(AsyncBody::default())
|
||||||
|
.map_err(AnthropicError::BuildRequestBody)?;
|
||||||
|
|
||||||
|
let mut response = client
|
||||||
|
.send(http_request)
|
||||||
|
.await
|
||||||
|
.map_err(AnthropicError::HttpSend)?;
|
||||||
|
|
||||||
|
let rate_limits = RateLimitInfo::from_headers(response.headers());
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
let mut body = String::new();
|
||||||
|
response
|
||||||
|
.body_mut()
|
||||||
|
.read_to_string(&mut body)
|
||||||
|
.await
|
||||||
|
.map_err(AnthropicError::ReadResponse)?;
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for line in body.lines() {
|
||||||
|
if line.trim().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let result: BatchIndividualResponse =
|
||||||
|
serde_json::from_str(line).map_err(AnthropicError::DeserializeResponse)?;
|
||||||
|
results.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
} else {
|
||||||
|
Err(crate::handle_error_response(response, rate_limits).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ use fs::{Fs, RenameOptions};
|
|||||||
use futures::{FutureExt, StreamExt, future::Shared};
|
use futures::{FutureExt, StreamExt, future::Shared};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext as _, Context, Entity, EventEmitter, RenderImage, SharedString, Subscription,
|
App, AppContext as _, Context, Entity, EventEmitter, RenderImage, SharedString, Subscription,
|
||||||
Task,
|
Task, WeakEntity,
|
||||||
};
|
};
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
|
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
|
||||||
@@ -688,7 +688,7 @@ pub struct TextThread {
|
|||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
project: Option<Entity<Project>>,
|
project: Option<WeakEntity<Project>>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
completion_mode: agent_settings::CompletionMode,
|
completion_mode: agent_settings::CompletionMode,
|
||||||
}
|
}
|
||||||
@@ -708,7 +708,7 @@ impl EventEmitter<TextThreadEvent> for TextThread {}
|
|||||||
impl TextThread {
|
impl TextThread {
|
||||||
pub fn local(
|
pub fn local(
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
project: Option<Entity<Project>>,
|
project: Option<WeakEntity<Project>>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||||
@@ -742,7 +742,7 @@ impl TextThread {
|
|||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||||
project: Option<Entity<Project>>,
|
project: Option<WeakEntity<Project>>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -873,7 +873,7 @@ impl TextThread {
|
|||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
prompt_builder: Arc<PromptBuilder>,
|
prompt_builder: Arc<PromptBuilder>,
|
||||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||||
project: Option<Entity<Project>>,
|
project: Option<WeakEntity<Project>>,
|
||||||
telemetry: Option<Arc<Telemetry>>,
|
telemetry: Option<Arc<Telemetry>>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -1167,10 +1167,6 @@ impl TextThread {
|
|||||||
self.language_registry.clone()
|
self.language_registry.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn project(&self) -> Option<Entity<Project>> {
|
|
||||||
self.project.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prompt_builder(&self) -> Arc<PromptBuilder> {
|
pub fn prompt_builder(&self) -> Arc<PromptBuilder> {
|
||||||
self.prompt_builder.clone()
|
self.prompt_builder.clone()
|
||||||
}
|
}
|
||||||
@@ -2967,7 +2963,7 @@ impl TextThread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_model_request_usage(&self, amount: u32, limit: UsageLimit, cx: &mut App) {
|
fn update_model_request_usage(&self, amount: u32, limit: UsageLimit, cx: &mut App) {
|
||||||
let Some(project) = &self.project else {
|
let Some(project) = self.project.as_ref().and_then(|project| project.upgrade()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
project.read(cx).user_store().update(cx, |user_store, cx| {
|
project.read(cx).user_store().update(cx, |user_store, cx| {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ pub struct TextThreadStore {
|
|||||||
telemetry: Arc<Telemetry>,
|
telemetry: Arc<Telemetry>,
|
||||||
_watch_updates: Task<Option<()>>,
|
_watch_updates: Task<Option<()>>,
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
project: Entity<Project>,
|
project: WeakEntity<Project>,
|
||||||
project_is_shared: bool,
|
project_is_shared: bool,
|
||||||
client_subscription: Option<client::Subscription>,
|
client_subscription: Option<client::Subscription>,
|
||||||
_project_subscriptions: Vec<gpui::Subscription>,
|
_project_subscriptions: Vec<gpui::Subscription>,
|
||||||
@@ -119,10 +119,10 @@ impl TextThreadStore {
|
|||||||
],
|
],
|
||||||
project_is_shared: false,
|
project_is_shared: false,
|
||||||
client: project.read(cx).client(),
|
client: project.read(cx).client(),
|
||||||
project: project.clone(),
|
project: project.downgrade(),
|
||||||
prompt_builder,
|
prompt_builder,
|
||||||
};
|
};
|
||||||
this.handle_project_shared(project.clone(), cx);
|
this.handle_project_shared(cx);
|
||||||
this.synchronize_contexts(cx);
|
this.synchronize_contexts(cx);
|
||||||
this.register_context_server_handlers(cx);
|
this.register_context_server_handlers(cx);
|
||||||
this.reload(cx).detach_and_log_err(cx);
|
this.reload(cx).detach_and_log_err(cx);
|
||||||
@@ -146,7 +146,7 @@ impl TextThreadStore {
|
|||||||
telemetry: project.read(cx).client().telemetry().clone(),
|
telemetry: project.read(cx).client().telemetry().clone(),
|
||||||
_watch_updates: Task::ready(None),
|
_watch_updates: Task::ready(None),
|
||||||
client: project.read(cx).client(),
|
client: project.read(cx).client(),
|
||||||
project,
|
project: project.downgrade(),
|
||||||
project_is_shared: false,
|
project_is_shared: false,
|
||||||
client_subscription: None,
|
client_subscription: None,
|
||||||
_project_subscriptions: Default::default(),
|
_project_subscriptions: Default::default(),
|
||||||
@@ -180,8 +180,10 @@ impl TextThreadStore {
|
|||||||
) -> Result<proto::OpenContextResponse> {
|
) -> Result<proto::OpenContextResponse> {
|
||||||
let context_id = TextThreadId::from_proto(envelope.payload.context_id);
|
let context_id = TextThreadId::from_proto(envelope.payload.context_id);
|
||||||
let operations = this.update(&mut cx, |this, cx| {
|
let operations = this.update(&mut cx, |this, cx| {
|
||||||
|
let project = this.project.upgrade().context("project not found")?;
|
||||||
|
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
!this.project.read(cx).is_via_collab(),
|
!project.read(cx).is_via_collab(),
|
||||||
"only the host contexts can be opened"
|
"only the host contexts can be opened"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -211,8 +213,9 @@ impl TextThreadStore {
|
|||||||
mut cx: AsyncApp,
|
mut cx: AsyncApp,
|
||||||
) -> Result<proto::CreateContextResponse> {
|
) -> Result<proto::CreateContextResponse> {
|
||||||
let (context_id, operations) = this.update(&mut cx, |this, cx| {
|
let (context_id, operations) = this.update(&mut cx, |this, cx| {
|
||||||
|
let project = this.project.upgrade().context("project not found")?;
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
!this.project.read(cx).is_via_collab(),
|
!project.read(cx).is_via_collab(),
|
||||||
"can only create contexts as the host"
|
"can only create contexts as the host"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -255,8 +258,9 @@ impl TextThreadStore {
|
|||||||
mut cx: AsyncApp,
|
mut cx: AsyncApp,
|
||||||
) -> Result<proto::SynchronizeContextsResponse> {
|
) -> Result<proto::SynchronizeContextsResponse> {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
|
let project = this.project.upgrade().context("project not found")?;
|
||||||
anyhow::ensure!(
|
anyhow::ensure!(
|
||||||
!this.project.read(cx).is_via_collab(),
|
!project.read(cx).is_via_collab(),
|
||||||
"only the host can synchronize contexts"
|
"only the host can synchronize contexts"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -293,8 +297,12 @@ impl TextThreadStore {
|
|||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_project_shared(&mut self, _: Entity<Project>, cx: &mut Context<Self>) {
|
fn handle_project_shared(&mut self, cx: &mut Context<Self>) {
|
||||||
let is_shared = self.project.read(cx).is_shared();
|
let Some(project) = self.project.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_shared = project.read(cx).is_shared();
|
||||||
let was_shared = mem::replace(&mut self.project_is_shared, is_shared);
|
let was_shared = mem::replace(&mut self.project_is_shared, is_shared);
|
||||||
if is_shared == was_shared {
|
if is_shared == was_shared {
|
||||||
return;
|
return;
|
||||||
@@ -309,7 +317,7 @@ impl TextThreadStore {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let remote_id = self.project.read(cx).remote_id().unwrap();
|
let remote_id = project.read(cx).remote_id().unwrap();
|
||||||
self.client_subscription = self
|
self.client_subscription = self
|
||||||
.client
|
.client
|
||||||
.subscribe_to_entity(remote_id)
|
.subscribe_to_entity(remote_id)
|
||||||
@@ -323,13 +331,13 @@ impl TextThreadStore {
|
|||||||
|
|
||||||
fn handle_project_event(
|
fn handle_project_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: Entity<Project>,
|
_project: Entity<Project>,
|
||||||
event: &project::Event,
|
event: &project::Event,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
project::Event::RemoteIdChanged(_) => {
|
project::Event::RemoteIdChanged(_) => {
|
||||||
self.handle_project_shared(project, cx);
|
self.handle_project_shared(cx);
|
||||||
}
|
}
|
||||||
project::Event::Reshared => {
|
project::Event::Reshared => {
|
||||||
self.advertise_contexts(cx);
|
self.advertise_contexts(cx);
|
||||||
@@ -382,7 +390,10 @@ impl TextThreadStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_remote(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<TextThread>>> {
|
pub fn create_remote(&mut self, cx: &mut Context<Self>) -> Task<Result<Entity<TextThread>>> {
|
||||||
let project = self.project.read(cx);
|
let Some(project) = self.project.upgrade() else {
|
||||||
|
return Task::ready(Err(anyhow::anyhow!("project was dropped")));
|
||||||
|
};
|
||||||
|
let project = project.read(cx);
|
||||||
let Some(project_id) = project.remote_id() else {
|
let Some(project_id) = project.remote_id() else {
|
||||||
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
|
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
|
||||||
};
|
};
|
||||||
@@ -541,7 +552,10 @@ impl TextThreadStore {
|
|||||||
text_thread_id: TextThreadId,
|
text_thread_id: TextThreadId,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<Entity<TextThread>>> {
|
) -> Task<Result<Entity<TextThread>>> {
|
||||||
let project = self.project.read(cx);
|
let Some(project) = self.project.upgrade() else {
|
||||||
|
return Task::ready(Err(anyhow::anyhow!("project was dropped")));
|
||||||
|
};
|
||||||
|
let project = project.read(cx);
|
||||||
let Some(project_id) = project.remote_id() else {
|
let Some(project_id) = project.remote_id() else {
|
||||||
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
|
return Task::ready(Err(anyhow::anyhow!("project was not remote")));
|
||||||
};
|
};
|
||||||
@@ -618,7 +632,10 @@ impl TextThreadStore {
|
|||||||
event: &TextThreadEvent,
|
event: &TextThreadEvent,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let Some(project_id) = self.project.read(cx).remote_id() else {
|
let Some(project) = self.project.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(project_id) = project.read(cx).remote_id() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -652,12 +669,14 @@ impl TextThreadStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn advertise_contexts(&self, cx: &App) {
|
fn advertise_contexts(&self, cx: &App) {
|
||||||
let Some(project_id) = self.project.read(cx).remote_id() else {
|
let Some(project) = self.project.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(project_id) = project.read(cx).remote_id() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// For now, only the host can advertise their open contexts.
|
// For now, only the host can advertise their open contexts.
|
||||||
if self.project.read(cx).is_via_collab() {
|
if project.read(cx).is_via_collab() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -689,7 +708,10 @@ impl TextThreadStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn synchronize_contexts(&mut self, cx: &mut Context<Self>) {
|
fn synchronize_contexts(&mut self, cx: &mut Context<Self>) {
|
||||||
let Some(project_id) = self.project.read(cx).remote_id() else {
|
let Some(project) = self.project.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(project_id) = project.read(cx).remote_id() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -828,7 +850,10 @@ impl TextThreadStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
|
fn register_context_server_handlers(&self, cx: &mut Context<Self>) {
|
||||||
let context_server_store = self.project.read(cx).context_server_store();
|
let Some(project) = self.project.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let context_server_store = project.read(cx).context_server_store();
|
||||||
cx.subscribe(&context_server_store, Self::handle_context_server_event)
|
cx.subscribe(&context_server_store, Self::handle_context_server_event)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
|||||||
@@ -584,41 +584,100 @@ impl Model {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cross_region_inference_id(&self, region: &str) -> anyhow::Result<String> {
|
pub fn cross_region_inference_id(
|
||||||
|
&self,
|
||||||
|
region: &str,
|
||||||
|
allow_global: bool,
|
||||||
|
) -> anyhow::Result<String> {
|
||||||
|
// List derived from here:
|
||||||
|
// https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-support.html#inference-profiles-support-system
|
||||||
|
let model_id = self.request_id();
|
||||||
|
|
||||||
|
let supports_global = matches!(
|
||||||
|
self,
|
||||||
|
Model::ClaudeOpus4_5
|
||||||
|
| Model::ClaudeOpus4_5Thinking
|
||||||
|
| Model::ClaudeHaiku4_5
|
||||||
|
| Model::ClaudeSonnet4
|
||||||
|
| Model::ClaudeSonnet4Thinking
|
||||||
|
| Model::ClaudeSonnet4_5
|
||||||
|
| Model::ClaudeSonnet4_5Thinking
|
||||||
|
);
|
||||||
|
|
||||||
let region_group = if region.starts_with("us-gov-") {
|
let region_group = if region.starts_with("us-gov-") {
|
||||||
"us-gov"
|
"us-gov"
|
||||||
} else if region.starts_with("us-") {
|
} else if region.starts_with("us-")
|
||||||
"us"
|
|| region.starts_with("ca-")
|
||||||
|
|| region.starts_with("sa-")
|
||||||
|
{
|
||||||
|
if allow_global && supports_global {
|
||||||
|
"global"
|
||||||
|
} else {
|
||||||
|
"us"
|
||||||
|
}
|
||||||
} else if region.starts_with("eu-") {
|
} else if region.starts_with("eu-") {
|
||||||
"eu"
|
if allow_global && supports_global {
|
||||||
|
"global"
|
||||||
|
} else {
|
||||||
|
"eu"
|
||||||
|
}
|
||||||
} else if region.starts_with("ap-") || region == "me-central-1" || region == "me-south-1" {
|
} else if region.starts_with("ap-") || region == "me-central-1" || region == "me-south-1" {
|
||||||
"apac"
|
if allow_global && supports_global {
|
||||||
} else if region.starts_with("ca-") || region.starts_with("sa-") {
|
"global"
|
||||||
// Canada and South America regions - default to US profiles
|
} else {
|
||||||
"us"
|
"apac"
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
anyhow::bail!("Unsupported Region {region}");
|
anyhow::bail!("Unsupported Region {region}");
|
||||||
};
|
};
|
||||||
|
|
||||||
let model_id = self.request_id();
|
match (self, region_group, region) {
|
||||||
|
(Model::Custom { .. }, _, _) => Ok(self.request_id().into()),
|
||||||
|
|
||||||
match (self, region_group) {
|
(
|
||||||
// Custom models can't have CRI IDs
|
Model::ClaudeOpus4_5
|
||||||
(Model::Custom { .. }, _) => Ok(self.request_id().into()),
|
| Model::ClaudeOpus4_5Thinking
|
||||||
|
| Model::ClaudeHaiku4_5
|
||||||
|
| Model::ClaudeSonnet4
|
||||||
|
| Model::ClaudeSonnet4Thinking
|
||||||
|
| Model::ClaudeSonnet4_5
|
||||||
|
| Model::ClaudeSonnet4_5Thinking,
|
||||||
|
"global",
|
||||||
|
_,
|
||||||
|
) => Ok(format!("{}.{}", region_group, model_id)),
|
||||||
|
|
||||||
// Models with US Gov only
|
(
|
||||||
(Model::Claude3_5Sonnet, "us-gov") | (Model::Claude3Haiku, "us-gov") => {
|
Model::Claude3Haiku
|
||||||
Ok(format!("{}.{}", region_group, model_id))
|
| Model::Claude3_5Sonnet
|
||||||
|
| Model::Claude3_7Sonnet
|
||||||
|
| Model::Claude3_7SonnetThinking
|
||||||
|
| Model::ClaudeSonnet4_5
|
||||||
|
| Model::ClaudeSonnet4_5Thinking,
|
||||||
|
"us-gov",
|
||||||
|
_,
|
||||||
|
) => Ok(format!("{}.{}", region_group, model_id)),
|
||||||
|
|
||||||
|
(
|
||||||
|
Model::ClaudeHaiku4_5 | Model::ClaudeSonnet4_5 | Model::ClaudeSonnet4_5Thinking,
|
||||||
|
"apac",
|
||||||
|
"ap-southeast-2" | "ap-southeast-4",
|
||||||
|
) => Ok(format!("au.{}", model_id)),
|
||||||
|
|
||||||
|
(
|
||||||
|
Model::ClaudeHaiku4_5 | Model::ClaudeSonnet4_5 | Model::ClaudeSonnet4_5Thinking,
|
||||||
|
"apac",
|
||||||
|
"ap-northeast-1" | "ap-northeast-3",
|
||||||
|
) => Ok(format!("jp.{}", model_id)),
|
||||||
|
|
||||||
|
(Model::AmazonNovaLite, "us", r) if r.starts_with("ca-") => {
|
||||||
|
Ok(format!("ca.{}", model_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Available everywhere
|
|
||||||
(Model::AmazonNovaLite | Model::AmazonNovaMicro | Model::AmazonNovaPro, _) => {
|
|
||||||
Ok(format!("{}.{}", region_group, model_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Models in US
|
|
||||||
(
|
(
|
||||||
Model::AmazonNovaPremier
|
Model::AmazonNovaPremier
|
||||||
|
| Model::AmazonNovaLite
|
||||||
|
| Model::AmazonNovaMicro
|
||||||
|
| Model::AmazonNovaPro
|
||||||
| Model::Claude3_5Haiku
|
| Model::Claude3_5Haiku
|
||||||
| Model::ClaudeHaiku4_5
|
| Model::ClaudeHaiku4_5
|
||||||
| Model::Claude3_5Sonnet
|
| Model::Claude3_5Sonnet
|
||||||
@@ -655,16 +714,18 @@ impl Model {
|
|||||||
| Model::PalmyraWriterX4
|
| Model::PalmyraWriterX4
|
||||||
| Model::PalmyraWriterX5,
|
| Model::PalmyraWriterX5,
|
||||||
"us",
|
"us",
|
||||||
|
_,
|
||||||
) => Ok(format!("{}.{}", region_group, model_id)),
|
) => Ok(format!("{}.{}", region_group, model_id)),
|
||||||
|
|
||||||
// Models available in EU
|
|
||||||
(
|
(
|
||||||
Model::Claude3_5Sonnet
|
Model::AmazonNovaLite
|
||||||
|
| Model::AmazonNovaMicro
|
||||||
|
| Model::AmazonNovaPro
|
||||||
|
| Model::Claude3_5Sonnet
|
||||||
| Model::ClaudeHaiku4_5
|
| Model::ClaudeHaiku4_5
|
||||||
| Model::Claude3_7Sonnet
|
| Model::Claude3_7Sonnet
|
||||||
| Model::Claude3_7SonnetThinking
|
| Model::Claude3_7SonnetThinking
|
||||||
| Model::ClaudeSonnet4
|
| Model::ClaudeSonnet4
|
||||||
| Model::ClaudeSonnet4Thinking
|
|
||||||
| Model::ClaudeSonnet4_5
|
| Model::ClaudeSonnet4_5
|
||||||
| Model::ClaudeSonnet4_5Thinking
|
| Model::ClaudeSonnet4_5Thinking
|
||||||
| Model::Claude3Haiku
|
| Model::Claude3Haiku
|
||||||
@@ -673,26 +734,26 @@ impl Model {
|
|||||||
| Model::MetaLlama323BInstructV1
|
| Model::MetaLlama323BInstructV1
|
||||||
| Model::MistralPixtralLarge2502V1,
|
| Model::MistralPixtralLarge2502V1,
|
||||||
"eu",
|
"eu",
|
||||||
|
_,
|
||||||
) => Ok(format!("{}.{}", region_group, model_id)),
|
) => Ok(format!("{}.{}", region_group, model_id)),
|
||||||
|
|
||||||
// Models available in APAC
|
|
||||||
(
|
(
|
||||||
Model::Claude3_5Sonnet
|
Model::AmazonNovaLite
|
||||||
|
| Model::AmazonNovaMicro
|
||||||
|
| Model::AmazonNovaPro
|
||||||
|
| Model::Claude3_5Sonnet
|
||||||
| Model::Claude3_5SonnetV2
|
| Model::Claude3_5SonnetV2
|
||||||
| Model::ClaudeHaiku4_5
|
| Model::ClaudeHaiku4_5
|
||||||
| Model::Claude3Haiku
|
|
||||||
| Model::Claude3Sonnet
|
|
||||||
| Model::Claude3_7Sonnet
|
| Model::Claude3_7Sonnet
|
||||||
| Model::Claude3_7SonnetThinking
|
| Model::Claude3_7SonnetThinking
|
||||||
| Model::ClaudeSonnet4
|
| Model::ClaudeSonnet4
|
||||||
| Model::ClaudeSonnet4Thinking
|
| Model::Claude3Haiku
|
||||||
| Model::ClaudeSonnet4_5
|
| Model::Claude3Sonnet,
|
||||||
| Model::ClaudeSonnet4_5Thinking,
|
|
||||||
"apac",
|
"apac",
|
||||||
|
_,
|
||||||
) => Ok(format!("{}.{}", region_group, model_id)),
|
) => Ok(format!("{}.{}", region_group, model_id)),
|
||||||
|
|
||||||
// Any other combination is not supported
|
_ => Ok(model_id.into()),
|
||||||
_ => Ok(self.request_id().into()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -705,15 +766,15 @@ mod tests {
|
|||||||
fn test_us_region_inference_ids() -> anyhow::Result<()> {
|
fn test_us_region_inference_ids() -> anyhow::Result<()> {
|
||||||
// Test US regions
|
// Test US regions
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::Claude3_5SonnetV2.cross_region_inference_id("us-east-1")?,
|
Model::Claude3_5SonnetV2.cross_region_inference_id("us-east-1", false)?,
|
||||||
"us.anthropic.claude-3-5-sonnet-20241022-v2:0"
|
"us.anthropic.claude-3-5-sonnet-20241022-v2:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::Claude3_5SonnetV2.cross_region_inference_id("us-west-2")?,
|
Model::Claude3_5SonnetV2.cross_region_inference_id("us-west-2", false)?,
|
||||||
"us.anthropic.claude-3-5-sonnet-20241022-v2:0"
|
"us.anthropic.claude-3-5-sonnet-20241022-v2:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::AmazonNovaPro.cross_region_inference_id("us-east-2")?,
|
Model::AmazonNovaPro.cross_region_inference_id("us-east-2", false)?,
|
||||||
"us.amazon.nova-pro-v1:0"
|
"us.amazon.nova-pro-v1:0"
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -723,19 +784,19 @@ mod tests {
|
|||||||
fn test_eu_region_inference_ids() -> anyhow::Result<()> {
|
fn test_eu_region_inference_ids() -> anyhow::Result<()> {
|
||||||
// Test European regions
|
// Test European regions
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::ClaudeSonnet4.cross_region_inference_id("eu-west-1")?,
|
Model::ClaudeSonnet4.cross_region_inference_id("eu-west-1", false)?,
|
||||||
"eu.anthropic.claude-sonnet-4-20250514-v1:0"
|
"eu.anthropic.claude-sonnet-4-20250514-v1:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::ClaudeSonnet4_5.cross_region_inference_id("eu-west-1")?,
|
Model::ClaudeSonnet4_5.cross_region_inference_id("eu-west-1", false)?,
|
||||||
"eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
"eu.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::Claude3Sonnet.cross_region_inference_id("eu-west-1")?,
|
Model::Claude3Sonnet.cross_region_inference_id("eu-west-1", false)?,
|
||||||
"eu.anthropic.claude-3-sonnet-20240229-v1:0"
|
"eu.anthropic.claude-3-sonnet-20240229-v1:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::AmazonNovaMicro.cross_region_inference_id("eu-north-1")?,
|
Model::AmazonNovaMicro.cross_region_inference_id("eu-north-1", false)?,
|
||||||
"eu.amazon.nova-micro-v1:0"
|
"eu.amazon.nova-micro-v1:0"
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -745,15 +806,15 @@ mod tests {
|
|||||||
fn test_apac_region_inference_ids() -> anyhow::Result<()> {
|
fn test_apac_region_inference_ids() -> anyhow::Result<()> {
|
||||||
// Test Asia-Pacific regions
|
// Test Asia-Pacific regions
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::Claude3_5SonnetV2.cross_region_inference_id("ap-northeast-1")?,
|
Model::Claude3_5SonnetV2.cross_region_inference_id("ap-northeast-1", false)?,
|
||||||
"apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
|
"apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::Claude3_5SonnetV2.cross_region_inference_id("ap-southeast-2")?,
|
Model::Claude3_5SonnetV2.cross_region_inference_id("ap-southeast-2", false)?,
|
||||||
"apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
|
"apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::AmazonNovaLite.cross_region_inference_id("ap-south-1")?,
|
Model::AmazonNovaLite.cross_region_inference_id("ap-south-1", false)?,
|
||||||
"apac.amazon.nova-lite-v1:0"
|
"apac.amazon.nova-lite-v1:0"
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -763,11 +824,11 @@ mod tests {
|
|||||||
fn test_gov_region_inference_ids() -> anyhow::Result<()> {
|
fn test_gov_region_inference_ids() -> anyhow::Result<()> {
|
||||||
// Test Government regions
|
// Test Government regions
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::Claude3_5Sonnet.cross_region_inference_id("us-gov-east-1")?,
|
Model::Claude3_5Sonnet.cross_region_inference_id("us-gov-east-1", false)?,
|
||||||
"us-gov.anthropic.claude-3-5-sonnet-20240620-v1:0"
|
"us-gov.anthropic.claude-3-5-sonnet-20240620-v1:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::Claude3Haiku.cross_region_inference_id("us-gov-west-1")?,
|
Model::Claude3Haiku.cross_region_inference_id("us-gov-west-1", false)?,
|
||||||
"us-gov.anthropic.claude-3-haiku-20240307-v1:0"
|
"us-gov.anthropic.claude-3-haiku-20240307-v1:0"
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -777,15 +838,15 @@ mod tests {
|
|||||||
fn test_meta_models_inference_ids() -> anyhow::Result<()> {
|
fn test_meta_models_inference_ids() -> anyhow::Result<()> {
|
||||||
// Test Meta models
|
// Test Meta models
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::MetaLlama370BInstructV1.cross_region_inference_id("us-east-1")?,
|
Model::MetaLlama370BInstructV1.cross_region_inference_id("us-east-1", false)?,
|
||||||
"meta.llama3-70b-instruct-v1:0"
|
"meta.llama3-70b-instruct-v1:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::MetaLlama3170BInstructV1.cross_region_inference_id("us-east-1")?,
|
Model::MetaLlama3170BInstructV1.cross_region_inference_id("us-east-1", false)?,
|
||||||
"us.meta.llama3-1-70b-instruct-v1:0"
|
"us.meta.llama3-1-70b-instruct-v1:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::MetaLlama321BInstructV1.cross_region_inference_id("eu-west-1")?,
|
Model::MetaLlama321BInstructV1.cross_region_inference_id("eu-west-1", false)?,
|
||||||
"eu.meta.llama3-2-1b-instruct-v1:0"
|
"eu.meta.llama3-2-1b-instruct-v1:0"
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -796,11 +857,11 @@ mod tests {
|
|||||||
// Mistral models don't follow the regional prefix pattern,
|
// Mistral models don't follow the regional prefix pattern,
|
||||||
// so they should return their original IDs
|
// so they should return their original IDs
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::MistralMistralLarge2402V1.cross_region_inference_id("us-east-1")?,
|
Model::MistralMistralLarge2402V1.cross_region_inference_id("us-east-1", false)?,
|
||||||
"mistral.mistral-large-2402-v1:0"
|
"mistral.mistral-large-2402-v1:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::MistralMixtral8x7BInstructV0.cross_region_inference_id("eu-west-1")?,
|
Model::MistralMixtral8x7BInstructV0.cross_region_inference_id("eu-west-1", false)?,
|
||||||
"mistral.mixtral-8x7b-instruct-v0:1"
|
"mistral.mixtral-8x7b-instruct-v0:1"
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -811,11 +872,11 @@ mod tests {
|
|||||||
// AI21 models don't follow the regional prefix pattern,
|
// AI21 models don't follow the regional prefix pattern,
|
||||||
// so they should return their original IDs
|
// so they should return their original IDs
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::AI21J2UltraV1.cross_region_inference_id("us-east-1")?,
|
Model::AI21J2UltraV1.cross_region_inference_id("us-east-1", false)?,
|
||||||
"ai21.j2-ultra-v1"
|
"ai21.j2-ultra-v1"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::AI21JambaInstructV1.cross_region_inference_id("eu-west-1")?,
|
Model::AI21JambaInstructV1.cross_region_inference_id("eu-west-1", false)?,
|
||||||
"ai21.jamba-instruct-v1:0"
|
"ai21.jamba-instruct-v1:0"
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -826,11 +887,11 @@ mod tests {
|
|||||||
// Cohere models don't follow the regional prefix pattern,
|
// Cohere models don't follow the regional prefix pattern,
|
||||||
// so they should return their original IDs
|
// so they should return their original IDs
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::CohereCommandRV1.cross_region_inference_id("us-east-1")?,
|
Model::CohereCommandRV1.cross_region_inference_id("us-east-1", false)?,
|
||||||
"cohere.command-r-v1:0"
|
"cohere.command-r-v1:0"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Model::CohereCommandTextV14_4k.cross_region_inference_id("ap-southeast-1")?,
|
Model::CohereCommandTextV14_4k.cross_region_inference_id("ap-southeast-1", false)?,
|
||||||
"cohere.command-text-v14:7:4k"
|
"cohere.command-text-v14:7:4k"
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -850,10 +911,17 @@ mod tests {
|
|||||||
|
|
||||||
// Custom model should return its name unchanged
|
// Custom model should return its name unchanged
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
custom_model.cross_region_inference_id("us-east-1")?,
|
custom_model.cross_region_inference_id("us-east-1", false)?,
|
||||||
"custom.my-model-v1:0"
|
"custom.my-model-v1:0"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Test that models without global support fall back to regional when allow_global is true
|
||||||
|
assert_eq!(
|
||||||
|
Model::AmazonNovaPro.cross_region_inference_id("us-east-1", true)?,
|
||||||
|
"us.amazon.nova-pro-v1:0",
|
||||||
|
"Nova Pro should fall back to regional profile even when allow_global is true"
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -892,3 +960,28 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_global_inference_ids() -> anyhow::Result<()> {
|
||||||
|
// Test global inference for models that support it when allow_global is true
|
||||||
|
assert_eq!(
|
||||||
|
Model::ClaudeSonnet4.cross_region_inference_id("us-east-1", true)?,
|
||||||
|
"global.anthropic.claude-sonnet-4-20250514-v1:0"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Model::ClaudeSonnet4_5.cross_region_inference_id("eu-west-1", true)?,
|
||||||
|
"global.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Model::ClaudeHaiku4_5.cross_region_inference_id("ap-south-1", true)?,
|
||||||
|
"global.anthropic.claude-haiku-4-5-20251001-v1:0"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test that regional prefix is used when allow_global is false
|
||||||
|
assert_eq!(
|
||||||
|
Model::ClaudeSonnet4.cross_region_inference_id("us-east-1", false)?,
|
||||||
|
"us.anthropic.claude-sonnet-4-20250514-v1:0"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1723,6 +1723,10 @@ impl ProtoClient for Client {
|
|||||||
fn is_via_collab(&self) -> bool {
|
fn is_via_collab(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_wsl_interop(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// prefix for the zed:// url scheme
|
/// prefix for the zed:// url scheme
|
||||||
|
|||||||
@@ -31,18 +31,10 @@ pub struct PredictEditsRequest {
|
|||||||
/// Within `signatures`
|
/// Within `signatures`
|
||||||
pub excerpt_parent: Option<usize>,
|
pub excerpt_parent: Option<usize>,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||||
pub included_files: Vec<IncludedFile>,
|
pub related_files: Vec<RelatedFile>,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
|
||||||
pub signatures: Vec<Signature>,
|
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
|
||||||
pub referenced_declarations: Vec<ReferencedDeclaration>,
|
|
||||||
pub events: Vec<Arc<Event>>,
|
pub events: Vec<Arc<Event>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub can_collect_data: bool,
|
pub can_collect_data: bool,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
|
||||||
pub diagnostic_groups: Vec<DiagnosticGroup>,
|
|
||||||
#[serde(skip_serializing_if = "is_default", default)]
|
|
||||||
pub diagnostic_groups_truncated: bool,
|
|
||||||
/// Info about the git repository state, only present when can_collect_data is true.
|
/// Info about the git repository state, only present when can_collect_data is true.
|
||||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||||
pub git_info: Option<PredictEditsGitInfo>,
|
pub git_info: Option<PredictEditsGitInfo>,
|
||||||
@@ -58,7 +50,7 @@ pub struct PredictEditsRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct IncludedFile {
|
pub struct RelatedFile {
|
||||||
pub path: Arc<Path>,
|
pub path: Arc<Path>,
|
||||||
pub max_row: Line,
|
pub max_row: Line,
|
||||||
pub excerpts: Vec<Excerpt>,
|
pub excerpts: Vec<Excerpt>,
|
||||||
@@ -72,11 +64,9 @@ pub struct Excerpt {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumIter)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, EnumIter)]
|
||||||
pub enum PromptFormat {
|
pub enum PromptFormat {
|
||||||
MarkedExcerpt,
|
/// XML old_tex/new_text
|
||||||
LabeledSections,
|
|
||||||
NumLinesUniDiff,
|
|
||||||
OldTextNewText,
|
OldTextNewText,
|
||||||
/// Prompt format intended for use via zeta_cli
|
/// Prompt format intended for use via edit_prediction_cli
|
||||||
OnlySnippets,
|
OnlySnippets,
|
||||||
/// One-sentence instructions used in fine-tuned models
|
/// One-sentence instructions used in fine-tuned models
|
||||||
Minimal,
|
Minimal,
|
||||||
@@ -87,7 +77,7 @@ pub enum PromptFormat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PromptFormat {
|
impl PromptFormat {
|
||||||
pub const DEFAULT: PromptFormat = PromptFormat::NumLinesUniDiff;
|
pub const DEFAULT: PromptFormat = PromptFormat::Minimal;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PromptFormat {
|
impl Default for PromptFormat {
|
||||||
@@ -105,10 +95,7 @@ impl PromptFormat {
|
|||||||
impl std::fmt::Display for PromptFormat {
|
impl std::fmt::Display for PromptFormat {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
PromptFormat::MarkedExcerpt => write!(f, "Marked Excerpt"),
|
|
||||||
PromptFormat::LabeledSections => write!(f, "Labeled Sections"),
|
|
||||||
PromptFormat::OnlySnippets => write!(f, "Only Snippets"),
|
PromptFormat::OnlySnippets => write!(f, "Only Snippets"),
|
||||||
PromptFormat::NumLinesUniDiff => write!(f, "Numbered Lines / Unified Diff"),
|
|
||||||
PromptFormat::OldTextNewText => write!(f, "Old Text / New Text"),
|
PromptFormat::OldTextNewText => write!(f, "Old Text / New Text"),
|
||||||
PromptFormat::Minimal => write!(f, "Minimal"),
|
PromptFormat::Minimal => write!(f, "Minimal"),
|
||||||
PromptFormat::MinimalQwen => write!(f, "Minimal + Qwen FIM"),
|
PromptFormat::MinimalQwen => write!(f, "Minimal + Qwen FIM"),
|
||||||
@@ -178,67 +165,6 @@ impl<'a> std::fmt::Display for DiffPathFmt<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Signature {
|
|
||||||
pub text: String,
|
|
||||||
pub text_is_truncated: bool,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
|
||||||
pub parent_index: Option<usize>,
|
|
||||||
/// Range of `text` within the file, possibly truncated according to `text_is_truncated`. The
|
|
||||||
/// file is implicitly the file that contains the descendant declaration or excerpt.
|
|
||||||
pub range: Range<Line>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct ReferencedDeclaration {
|
|
||||||
pub path: Arc<Path>,
|
|
||||||
pub text: String,
|
|
||||||
pub text_is_truncated: bool,
|
|
||||||
/// Range of `text` within file, possibly truncated according to `text_is_truncated`
|
|
||||||
pub range: Range<Line>,
|
|
||||||
/// Range within `text`
|
|
||||||
pub signature_range: Range<usize>,
|
|
||||||
/// Index within `signatures`.
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
|
||||||
pub parent_index: Option<usize>,
|
|
||||||
pub score_components: DeclarationScoreComponents,
|
|
||||||
pub signature_score: f32,
|
|
||||||
pub declaration_score: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct DeclarationScoreComponents {
|
|
||||||
pub is_same_file: bool,
|
|
||||||
pub is_referenced_nearby: bool,
|
|
||||||
pub is_referenced_in_breadcrumb: bool,
|
|
||||||
pub reference_count: usize,
|
|
||||||
pub same_file_declaration_count: usize,
|
|
||||||
pub declaration_count: usize,
|
|
||||||
pub reference_line_distance: u32,
|
|
||||||
pub declaration_line_distance: u32,
|
|
||||||
pub excerpt_vs_item_jaccard: f32,
|
|
||||||
pub excerpt_vs_signature_jaccard: f32,
|
|
||||||
pub adjacent_vs_item_jaccard: f32,
|
|
||||||
pub adjacent_vs_signature_jaccard: f32,
|
|
||||||
pub excerpt_vs_item_weighted_overlap: f32,
|
|
||||||
pub excerpt_vs_signature_weighted_overlap: f32,
|
|
||||||
pub adjacent_vs_item_weighted_overlap: f32,
|
|
||||||
pub adjacent_vs_signature_weighted_overlap: f32,
|
|
||||||
pub path_import_match_count: usize,
|
|
||||||
pub wildcard_path_import_match_count: usize,
|
|
||||||
pub import_similarity: f32,
|
|
||||||
pub max_import_similarity: f32,
|
|
||||||
pub normalized_import_similarity: f32,
|
|
||||||
pub wildcard_import_similarity: f32,
|
|
||||||
pub normalized_wildcard_import_similarity: f32,
|
|
||||||
pub included_by_others: usize,
|
|
||||||
pub includes_others: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(transparent)]
|
|
||||||
pub struct DiagnosticGroup(pub Box<serde_json::value::RawValue>);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct PredictEditsResponse {
|
pub struct PredictEditsResponse {
|
||||||
pub request_id: Uuid,
|
pub request_id: Uuid,
|
||||||
@@ -262,10 +188,6 @@ pub struct Edit {
|
|||||||
pub content: String,
|
pub content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
|
|
||||||
*value == T::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, Eq, Ord)]
|
||||||
pub struct Point {
|
pub struct Point {
|
||||||
pub line: Line,
|
pub line: Line,
|
||||||
|
|||||||
@@ -15,9 +15,4 @@ path = "src/cloud_zeta2_prompt.rs"
|
|||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
cloud_llm_client.workspace = true
|
cloud_llm_client.workspace = true
|
||||||
indoc.workspace = true
|
indoc.workspace = true
|
||||||
ordered-float.workspace = true
|
|
||||||
rustc-hash.workspace = true
|
|
||||||
schemars.workspace = true
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
|
||||||
strum.workspace = true
|
|
||||||
|
|||||||
@@ -1,20 +1,12 @@
|
|||||||
//! Zeta2 prompt planning and generation code shared with cloud.
|
use anyhow::Result;
|
||||||
pub mod retrieval_prompt;
|
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
|
||||||
use cloud_llm_client::predict_edits_v3::{
|
use cloud_llm_client::predict_edits_v3::{
|
||||||
self, DiffPathFmt, Event, Excerpt, IncludedFile, Line, Point, PromptFormat,
|
self, DiffPathFmt, Event, Excerpt, Line, Point, PromptFormat, RelatedFile,
|
||||||
ReferencedDeclaration,
|
|
||||||
};
|
};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use ordered_float::OrderedFloat;
|
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
|
||||||
use serde::Serialize;
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::{cmp::Reverse, collections::BinaryHeap, ops::Range, path::Path};
|
|
||||||
use strum::{EnumIter, IntoEnumIterator};
|
|
||||||
|
|
||||||
pub const DEFAULT_MAX_PROMPT_BYTES: usize = 10 * 1024;
|
pub const DEFAULT_MAX_PROMPT_BYTES: usize = 10 * 1024;
|
||||||
|
|
||||||
@@ -24,69 +16,6 @@ pub const EDITABLE_REGION_START_MARKER_WITH_NEWLINE: &str = "<|editable_region_s
|
|||||||
/// NOTE: Differs from zed version of constant - includes a newline
|
/// NOTE: Differs from zed version of constant - includes a newline
|
||||||
pub const EDITABLE_REGION_END_MARKER_WITH_NEWLINE: &str = "<|editable_region_end|>\n";
|
pub const EDITABLE_REGION_END_MARKER_WITH_NEWLINE: &str = "<|editable_region_end|>\n";
|
||||||
|
|
||||||
// TODO: use constants for markers?
|
|
||||||
const MARKED_EXCERPT_INSTRUCTIONS: &str = indoc! {"
|
|
||||||
You are a code completion assistant and your task is to analyze user edits and then rewrite an excerpt that the user provides, suggesting the appropriate edits within the excerpt, taking into account the cursor location.
|
|
||||||
|
|
||||||
The excerpt to edit will be wrapped in markers <|editable_region_start|> and <|editable_region_end|>. The cursor position is marked with <|user_cursor|>. Please respond with edited code for that region.
|
|
||||||
|
|
||||||
Other code is provided for context, and `…` indicates when code has been skipped.
|
|
||||||
|
|
||||||
## Edit History
|
|
||||||
|
|
||||||
"};
|
|
||||||
|
|
||||||
const LABELED_SECTIONS_INSTRUCTIONS: &str = indoc! {r#"
|
|
||||||
You are a code completion assistant and your task is to analyze user edits, and suggest an edit to one of the provided sections of code.
|
|
||||||
|
|
||||||
Sections of code are grouped by file and then labeled by `<|section_N|>` (e.g `<|section_8|>`).
|
|
||||||
|
|
||||||
The cursor position is marked with `<|user_cursor|>` and it will appear within a special section labeled `<|current_section|>`. Prefer editing the current section until no more changes are needed within it.
|
|
||||||
|
|
||||||
Respond ONLY with the name of the section to edit on a single line, followed by all of the code that should replace that section. For example:
|
|
||||||
|
|
||||||
<|current_section|>
|
|
||||||
for i in 0..16 {
|
|
||||||
println!("{i}");
|
|
||||||
}
|
|
||||||
|
|
||||||
## Edit History
|
|
||||||
|
|
||||||
"#};
|
|
||||||
|
|
||||||
const NUMBERED_LINES_INSTRUCTIONS: &str = indoc! {r#"
|
|
||||||
# Instructions
|
|
||||||
|
|
||||||
You are an edit prediction agent in a code editor.
|
|
||||||
Your job is to predict the next edit that the user will make,
|
|
||||||
based on their last few edits and their current cursor location.
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
You must briefly explain your understanding of the user's goal, in one
|
|
||||||
or two sentences, and then specify their next edit in the form of a
|
|
||||||
unified diff, like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
--- a/src/myapp/cli.py
|
|
||||||
+++ b/src/myapp/cli.py
|
|
||||||
@@ ... @@
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
+from constants import LOG_LEVEL_WARNING
|
|
||||||
@@ ... @@
|
|
||||||
config.headless()
|
|
||||||
config.set_interactive(false)
|
|
||||||
-config.set_log_level(LOG_L)
|
|
||||||
+config.set_log_level(LOG_LEVEL_WARNING)
|
|
||||||
config.set_use_color(True)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Edit History
|
|
||||||
|
|
||||||
"#};
|
|
||||||
|
|
||||||
const STUDENT_MODEL_INSTRUCTIONS: &str = indoc! {r#"
|
const STUDENT_MODEL_INSTRUCTIONS: &str = indoc! {r#"
|
||||||
You are a code completion assistant that analyzes edit history to identify and systematically complete incomplete refactorings or patterns across the entire codebase.
|
You are a code completion assistant that analyzes edit history to identify and systematically complete incomplete refactorings or patterns across the entire codebase.
|
||||||
|
|
||||||
@@ -94,20 +23,6 @@ const STUDENT_MODEL_INSTRUCTIONS: &str = indoc! {r#"
|
|||||||
|
|
||||||
"#};
|
"#};
|
||||||
|
|
||||||
const UNIFIED_DIFF_REMINDER: &str = indoc! {"
|
|
||||||
---
|
|
||||||
|
|
||||||
Analyze the edit history and the files, then provide the unified diff for your predicted edits.
|
|
||||||
Do not include the cursor marker in your output.
|
|
||||||
Your diff should include edited file paths in its file headers (lines beginning with `---` and `+++`).
|
|
||||||
Do not include line numbers in the hunk headers, use `@@ ... @@`.
|
|
||||||
Removed lines begin with `-`.
|
|
||||||
Added lines begin with `+`.
|
|
||||||
Context lines begin with an extra space.
|
|
||||||
Context and removed lines are used to match the target edit location, so make sure to include enough of them
|
|
||||||
to uniquely identify it amongst all excerpts of code provided.
|
|
||||||
"};
|
|
||||||
|
|
||||||
const MINIMAL_PROMPT_REMINDER: &str = indoc! {"
|
const MINIMAL_PROMPT_REMINDER: &str = indoc! {"
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -164,49 +79,25 @@ const OLD_TEXT_NEW_TEXT_REMINDER: &str = indoc! {r#"
|
|||||||
Remember that the edits in the edit history have already been applied.
|
Remember that the edits in the edit history have already been applied.
|
||||||
"#};
|
"#};
|
||||||
|
|
||||||
pub fn build_prompt(
|
pub fn build_prompt(request: &predict_edits_v3::PredictEditsRequest) -> Result<String> {
|
||||||
request: &predict_edits_v3::PredictEditsRequest,
|
|
||||||
) -> Result<(String, SectionLabels)> {
|
|
||||||
let mut section_labels = Default::default();
|
|
||||||
|
|
||||||
let prompt_data = PromptData {
|
let prompt_data = PromptData {
|
||||||
events: request.events.clone(),
|
events: request.events.clone(),
|
||||||
cursor_point: request.cursor_point,
|
cursor_point: request.cursor_point,
|
||||||
cursor_path: request.excerpt_path.clone(),
|
cursor_path: request.excerpt_path.clone(),
|
||||||
included_files: request.included_files.clone(),
|
included_files: request.related_files.clone(),
|
||||||
};
|
};
|
||||||
match request.prompt_format {
|
match request.prompt_format {
|
||||||
PromptFormat::MinimalQwen => {
|
PromptFormat::MinimalQwen => {
|
||||||
return Ok((MinimalQwenPrompt.render(&prompt_data), section_labels));
|
return Ok(MinimalQwenPrompt.render(&prompt_data));
|
||||||
}
|
}
|
||||||
PromptFormat::SeedCoder1120 => {
|
PromptFormat::SeedCoder1120 => {
|
||||||
return Ok((SeedCoder1120Prompt.render(&prompt_data), section_labels));
|
return Ok(SeedCoder1120Prompt.render(&prompt_data));
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut insertions = match request.prompt_format {
|
let insertions = match request.prompt_format {
|
||||||
PromptFormat::MarkedExcerpt => vec![
|
PromptFormat::Minimal | PromptFormat::OldTextNewText => {
|
||||||
(
|
|
||||||
Point {
|
|
||||||
line: request.excerpt_line_range.start,
|
|
||||||
column: 0,
|
|
||||||
},
|
|
||||||
EDITABLE_REGION_START_MARKER_WITH_NEWLINE,
|
|
||||||
),
|
|
||||||
(request.cursor_point, CURSOR_MARKER),
|
|
||||||
(
|
|
||||||
Point {
|
|
||||||
line: request.excerpt_line_range.end,
|
|
||||||
column: 0,
|
|
||||||
},
|
|
||||||
EDITABLE_REGION_END_MARKER_WITH_NEWLINE,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
PromptFormat::LabeledSections
|
|
||||||
| PromptFormat::NumLinesUniDiff
|
|
||||||
| PromptFormat::Minimal
|
|
||||||
| PromptFormat::OldTextNewText => {
|
|
||||||
vec![(request.cursor_point, CURSOR_MARKER)]
|
vec![(request.cursor_point, CURSOR_MARKER)]
|
||||||
}
|
}
|
||||||
PromptFormat::OnlySnippets => vec![],
|
PromptFormat::OnlySnippets => vec![],
|
||||||
@@ -215,9 +106,6 @@ pub fn build_prompt(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut prompt = match request.prompt_format {
|
let mut prompt = match request.prompt_format {
|
||||||
PromptFormat::MarkedExcerpt => MARKED_EXCERPT_INSTRUCTIONS.to_string(),
|
|
||||||
PromptFormat::LabeledSections => LABELED_SECTIONS_INSTRUCTIONS.to_string(),
|
|
||||||
PromptFormat::NumLinesUniDiff => NUMBERED_LINES_INSTRUCTIONS.to_string(),
|
|
||||||
PromptFormat::OldTextNewText => XML_TAGS_INSTRUCTIONS.to_string(),
|
PromptFormat::OldTextNewText => XML_TAGS_INSTRUCTIONS.to_string(),
|
||||||
PromptFormat::OnlySnippets => String::new(),
|
PromptFormat::OnlySnippets => String::new(),
|
||||||
PromptFormat::Minimal => STUDENT_MODEL_INSTRUCTIONS.to_string(),
|
PromptFormat::Minimal => STUDENT_MODEL_INSTRUCTIONS.to_string(),
|
||||||
@@ -247,7 +135,7 @@ pub fn build_prompt(
|
|||||||
You can only edit exactly this part of the file.
|
You can only edit exactly this part of the file.
|
||||||
We prepend line numbers (e.g., `123|<actual line>`); they are not part of the file.)
|
We prepend line numbers (e.g., `123|<actual line>`); they are not part of the file.)
|
||||||
"},
|
"},
|
||||||
PromptFormat::NumLinesUniDiff | PromptFormat::OldTextNewText => indoc! {"
|
PromptFormat::OldTextNewText => indoc! {"
|
||||||
## Code Excerpts
|
## Code Excerpts
|
||||||
|
|
||||||
Here is some excerpts of code that you should take into account to predict the next edit.
|
Here is some excerpts of code that you should take into account to predict the next edit.
|
||||||
@@ -263,64 +151,51 @@ pub fn build_prompt(
|
|||||||
|
|
||||||
Lines starting with `…` indicate omitted line ranges. These may appear inside multi-line code constructs.
|
Lines starting with `…` indicate omitted line ranges. These may appear inside multi-line code constructs.
|
||||||
"},
|
"},
|
||||||
_ => indoc! {"
|
PromptFormat::OnlySnippets | PromptFormat::MinimalQwen | PromptFormat::SeedCoder1120 => {
|
||||||
|
indoc! {"
|
||||||
## Code Excerpts
|
## Code Excerpts
|
||||||
|
|
||||||
The cursor marker <|user_cursor|> indicates the current user cursor position.
|
The cursor marker <|user_cursor|> indicates the current user cursor position.
|
||||||
The file is in current state, edits from edit history have been applied.
|
The file is in current state, edits from edit history have been applied.
|
||||||
"},
|
"}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
prompt.push_str(excerpts_preamble);
|
prompt.push_str(excerpts_preamble);
|
||||||
prompt.push('\n');
|
prompt.push('\n');
|
||||||
|
|
||||||
if !request.referenced_declarations.is_empty() || !request.signatures.is_empty() {
|
let include_line_numbers = matches!(request.prompt_format, PromptFormat::Minimal);
|
||||||
let syntax_based_prompt = SyntaxBasedPrompt::populate(request)?;
|
for related_file in &request.related_files {
|
||||||
section_labels = syntax_based_prompt.write(&mut insertions, &mut prompt)?;
|
if request.prompt_format == PromptFormat::Minimal {
|
||||||
} else {
|
write_codeblock_with_filename(
|
||||||
if request.prompt_format == PromptFormat::LabeledSections {
|
&related_file.path,
|
||||||
anyhow::bail!("PromptFormat::LabeledSections cannot be used with ContextMode::Llm");
|
&related_file.excerpts,
|
||||||
}
|
if related_file.path == request.excerpt_path {
|
||||||
|
&insertions
|
||||||
let include_line_numbers = matches!(
|
} else {
|
||||||
request.prompt_format,
|
&[]
|
||||||
PromptFormat::NumLinesUniDiff | PromptFormat::Minimal
|
},
|
||||||
);
|
related_file.max_row,
|
||||||
for related_file in &request.included_files {
|
include_line_numbers,
|
||||||
if request.prompt_format == PromptFormat::Minimal {
|
&mut prompt,
|
||||||
write_codeblock_with_filename(
|
);
|
||||||
&related_file.path,
|
} else {
|
||||||
&related_file.excerpts,
|
write_codeblock(
|
||||||
if related_file.path == request.excerpt_path {
|
&related_file.path,
|
||||||
&insertions
|
&related_file.excerpts,
|
||||||
} else {
|
if related_file.path == request.excerpt_path {
|
||||||
&[]
|
&insertions
|
||||||
},
|
} else {
|
||||||
related_file.max_row,
|
&[]
|
||||||
include_line_numbers,
|
},
|
||||||
&mut prompt,
|
related_file.max_row,
|
||||||
);
|
include_line_numbers,
|
||||||
} else {
|
&mut prompt,
|
||||||
write_codeblock(
|
);
|
||||||
&related_file.path,
|
|
||||||
&related_file.excerpts,
|
|
||||||
if related_file.path == request.excerpt_path {
|
|
||||||
&insertions
|
|
||||||
} else {
|
|
||||||
&[]
|
|
||||||
},
|
|
||||||
related_file.max_row,
|
|
||||||
include_line_numbers,
|
|
||||||
&mut prompt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match request.prompt_format {
|
match request.prompt_format {
|
||||||
PromptFormat::NumLinesUniDiff => {
|
|
||||||
prompt.push_str(UNIFIED_DIFF_REMINDER);
|
|
||||||
}
|
|
||||||
PromptFormat::OldTextNewText => {
|
PromptFormat::OldTextNewText => {
|
||||||
prompt.push_str(OLD_TEXT_NEW_TEXT_REMINDER);
|
prompt.push_str(OLD_TEXT_NEW_TEXT_REMINDER);
|
||||||
}
|
}
|
||||||
@@ -330,7 +205,7 @@ pub fn build_prompt(
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((prompt, section_labels))
|
Ok(prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generation_params(prompt_format: PromptFormat) -> GenerationParams {
|
pub fn generation_params(prompt_format: PromptFormat) -> GenerationParams {
|
||||||
@@ -444,476 +319,11 @@ pub fn push_events(output: &mut String, events: &[Arc<predict_edits_v3::Event>])
|
|||||||
writeln!(output, "`````\n").unwrap();
|
writeln!(output, "`````\n").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SyntaxBasedPrompt<'a> {
|
|
||||||
request: &'a predict_edits_v3::PredictEditsRequest,
|
|
||||||
/// Snippets to include in the prompt. These may overlap - they are merged / deduplicated in
|
|
||||||
/// `to_prompt_string`.
|
|
||||||
snippets: Vec<PlannedSnippet<'a>>,
|
|
||||||
budget_used: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct PlannedSnippet<'a> {
|
|
||||||
path: Arc<Path>,
|
|
||||||
range: Range<Line>,
|
|
||||||
text: &'a str,
|
|
||||||
// TODO: Indicate this in the output
|
|
||||||
#[allow(dead_code)]
|
|
||||||
text_is_truncated: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(EnumIter, Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
|
||||||
pub enum DeclarationStyle {
|
|
||||||
Signature,
|
|
||||||
Declaration,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug, Serialize)]
|
|
||||||
pub struct SectionLabels {
|
|
||||||
pub excerpt_index: usize,
|
|
||||||
pub section_ranges: Vec<(Arc<Path>, Range<Line>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> SyntaxBasedPrompt<'a> {
|
|
||||||
/// Greedy one-pass knapsack algorithm to populate the prompt plan. Does the following:
|
|
||||||
///
|
|
||||||
/// Initializes a priority queue by populating it with each snippet, finding the
|
|
||||||
/// DeclarationStyle that minimizes `score_density = score / snippet.range(style).len()`. When a
|
|
||||||
/// "signature" snippet is popped, insert an entry for the "declaration" variant that reflects
|
|
||||||
/// the cost of upgrade.
|
|
||||||
///
|
|
||||||
/// TODO: Implement an early halting condition. One option might be to have another priority
|
|
||||||
/// queue where the score is the size, and update it accordingly. Another option might be to
|
|
||||||
/// have some simpler heuristic like bailing after N failed insertions, or based on how much
|
|
||||||
/// budget is left.
|
|
||||||
///
|
|
||||||
/// TODO: Has the current known sources of imprecision:
|
|
||||||
///
|
|
||||||
/// * Does not consider snippet overlap when ranking. For example, it might add a field to the
|
|
||||||
/// plan even though the containing struct is already included.
|
|
||||||
///
|
|
||||||
/// * Does not consider cost of signatures when ranking snippets - this is tricky since
|
|
||||||
/// signatures may be shared by multiple snippets.
|
|
||||||
///
|
|
||||||
/// * Does not include file paths / other text when considering max_bytes.
|
|
||||||
pub fn populate(request: &'a predict_edits_v3::PredictEditsRequest) -> Result<Self> {
|
|
||||||
let mut this = Self {
|
|
||||||
request,
|
|
||||||
snippets: Vec::new(),
|
|
||||||
budget_used: request.excerpt.len(),
|
|
||||||
};
|
|
||||||
let mut included_parents = FxHashSet::default();
|
|
||||||
let additional_parents = this.additional_parent_signatures(
|
|
||||||
&request.excerpt_path,
|
|
||||||
request.excerpt_parent,
|
|
||||||
&included_parents,
|
|
||||||
)?;
|
|
||||||
this.add_parents(&mut included_parents, additional_parents);
|
|
||||||
|
|
||||||
let max_bytes = request.prompt_max_bytes.unwrap_or(DEFAULT_MAX_PROMPT_BYTES);
|
|
||||||
|
|
||||||
if this.budget_used > max_bytes {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Excerpt + signatures size of {} already exceeds budget of {}",
|
|
||||||
this.budget_used,
|
|
||||||
max_bytes
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
struct QueueEntry {
|
|
||||||
score_density: OrderedFloat<f32>,
|
|
||||||
declaration_index: usize,
|
|
||||||
style: DeclarationStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize priority queue with the best score for each snippet.
|
|
||||||
let mut queue: BinaryHeap<QueueEntry> = BinaryHeap::new();
|
|
||||||
for (declaration_index, declaration) in request.referenced_declarations.iter().enumerate() {
|
|
||||||
let (style, score_density) = DeclarationStyle::iter()
|
|
||||||
.map(|style| {
|
|
||||||
(
|
|
||||||
style,
|
|
||||||
OrderedFloat(declaration_score_density(&declaration, style)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.max_by_key(|(_, score_density)| *score_density)
|
|
||||||
.unwrap();
|
|
||||||
queue.push(QueueEntry {
|
|
||||||
score_density,
|
|
||||||
declaration_index,
|
|
||||||
style,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Knapsack selection loop
|
|
||||||
while let Some(queue_entry) = queue.pop() {
|
|
||||||
let Some(declaration) = request
|
|
||||||
.referenced_declarations
|
|
||||||
.get(queue_entry.declaration_index)
|
|
||||||
else {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Invalid declaration index {}",
|
|
||||||
queue_entry.declaration_index
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut additional_bytes = declaration_size(declaration, queue_entry.style);
|
|
||||||
if this.budget_used + additional_bytes > max_bytes {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let additional_parents = this.additional_parent_signatures(
|
|
||||||
&declaration.path,
|
|
||||||
declaration.parent_index,
|
|
||||||
&mut included_parents,
|
|
||||||
)?;
|
|
||||||
additional_bytes += additional_parents
|
|
||||||
.iter()
|
|
||||||
.map(|(_, snippet)| snippet.text.len())
|
|
||||||
.sum::<usize>();
|
|
||||||
if this.budget_used + additional_bytes > max_bytes {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.budget_used += additional_bytes;
|
|
||||||
this.add_parents(&mut included_parents, additional_parents);
|
|
||||||
let planned_snippet = match queue_entry.style {
|
|
||||||
DeclarationStyle::Signature => {
|
|
||||||
let Some(text) = declaration.text.get(declaration.signature_range.clone())
|
|
||||||
else {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Invalid declaration signature_range {:?} with text.len() = {}",
|
|
||||||
declaration.signature_range,
|
|
||||||
declaration.text.len()
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let signature_start_line = declaration.range.start
|
|
||||||
+ Line(
|
|
||||||
declaration.text[..declaration.signature_range.start]
|
|
||||||
.lines()
|
|
||||||
.count() as u32,
|
|
||||||
);
|
|
||||||
let signature_end_line = signature_start_line
|
|
||||||
+ Line(
|
|
||||||
declaration.text
|
|
||||||
[declaration.signature_range.start..declaration.signature_range.end]
|
|
||||||
.lines()
|
|
||||||
.count() as u32,
|
|
||||||
);
|
|
||||||
let range = signature_start_line..signature_end_line;
|
|
||||||
|
|
||||||
PlannedSnippet {
|
|
||||||
path: declaration.path.clone(),
|
|
||||||
range,
|
|
||||||
text,
|
|
||||||
text_is_truncated: declaration.text_is_truncated,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DeclarationStyle::Declaration => PlannedSnippet {
|
|
||||||
path: declaration.path.clone(),
|
|
||||||
range: declaration.range.clone(),
|
|
||||||
text: &declaration.text,
|
|
||||||
text_is_truncated: declaration.text_is_truncated,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
this.snippets.push(planned_snippet);
|
|
||||||
|
|
||||||
// When a Signature is consumed, insert an entry for Definition style.
|
|
||||||
if queue_entry.style == DeclarationStyle::Signature {
|
|
||||||
let signature_size = declaration_size(&declaration, DeclarationStyle::Signature);
|
|
||||||
let declaration_size =
|
|
||||||
declaration_size(&declaration, DeclarationStyle::Declaration);
|
|
||||||
let signature_score = declaration_score(&declaration, DeclarationStyle::Signature);
|
|
||||||
let declaration_score =
|
|
||||||
declaration_score(&declaration, DeclarationStyle::Declaration);
|
|
||||||
|
|
||||||
let score_diff = declaration_score - signature_score;
|
|
||||||
let size_diff = declaration_size.saturating_sub(signature_size);
|
|
||||||
if score_diff > 0.0001 && size_diff > 0 {
|
|
||||||
queue.push(QueueEntry {
|
|
||||||
declaration_index: queue_entry.declaration_index,
|
|
||||||
score_density: OrderedFloat(score_diff / (size_diff as f32)),
|
|
||||||
style: DeclarationStyle::Declaration,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
anyhow::Ok(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_parents(
|
|
||||||
&mut self,
|
|
||||||
included_parents: &mut FxHashSet<usize>,
|
|
||||||
snippets: Vec<(usize, PlannedSnippet<'a>)>,
|
|
||||||
) {
|
|
||||||
for (parent_index, snippet) in snippets {
|
|
||||||
included_parents.insert(parent_index);
|
|
||||||
self.budget_used += snippet.text.len();
|
|
||||||
self.snippets.push(snippet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn additional_parent_signatures(
|
|
||||||
&self,
|
|
||||||
path: &Arc<Path>,
|
|
||||||
parent_index: Option<usize>,
|
|
||||||
included_parents: &FxHashSet<usize>,
|
|
||||||
) -> Result<Vec<(usize, PlannedSnippet<'a>)>> {
|
|
||||||
let mut results = Vec::new();
|
|
||||||
self.additional_parent_signatures_impl(path, parent_index, included_parents, &mut results)?;
|
|
||||||
Ok(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn additional_parent_signatures_impl(
|
|
||||||
&self,
|
|
||||||
path: &Arc<Path>,
|
|
||||||
parent_index: Option<usize>,
|
|
||||||
included_parents: &FxHashSet<usize>,
|
|
||||||
results: &mut Vec<(usize, PlannedSnippet<'a>)>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let Some(parent_index) = parent_index else {
|
|
||||||
return Ok(());
|
|
||||||
};
|
|
||||||
if included_parents.contains(&parent_index) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let Some(parent_signature) = self.request.signatures.get(parent_index) else {
|
|
||||||
return Err(anyhow!("Invalid parent index {}", parent_index));
|
|
||||||
};
|
|
||||||
results.push((
|
|
||||||
parent_index,
|
|
||||||
PlannedSnippet {
|
|
||||||
path: path.clone(),
|
|
||||||
range: parent_signature.range.clone(),
|
|
||||||
text: &parent_signature.text,
|
|
||||||
text_is_truncated: parent_signature.text_is_truncated,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
self.additional_parent_signatures_impl(
|
|
||||||
path,
|
|
||||||
parent_signature.parent_index,
|
|
||||||
included_parents,
|
|
||||||
results,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Renders the planned context. Each file starts with "```FILE_PATH\n` and ends with triple
|
|
||||||
/// backticks, with a newline after each file. Outputs a line with "..." between nonconsecutive
|
|
||||||
/// chunks.
|
|
||||||
pub fn write(
|
|
||||||
&'a self,
|
|
||||||
excerpt_file_insertions: &mut Vec<(Point, &'static str)>,
|
|
||||||
prompt: &mut String,
|
|
||||||
) -> Result<SectionLabels> {
|
|
||||||
let mut file_to_snippets: FxHashMap<&'a std::path::Path, Vec<&PlannedSnippet<'a>>> =
|
|
||||||
FxHashMap::default();
|
|
||||||
for snippet in &self.snippets {
|
|
||||||
file_to_snippets
|
|
||||||
.entry(&snippet.path)
|
|
||||||
.or_default()
|
|
||||||
.push(snippet);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reorder so that file with cursor comes last
|
|
||||||
let mut file_snippets = Vec::new();
|
|
||||||
let mut excerpt_file_snippets = Vec::new();
|
|
||||||
for (file_path, snippets) in file_to_snippets {
|
|
||||||
if file_path == self.request.excerpt_path.as_ref() {
|
|
||||||
excerpt_file_snippets = snippets;
|
|
||||||
} else {
|
|
||||||
file_snippets.push((file_path, snippets, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let excerpt_snippet = PlannedSnippet {
|
|
||||||
path: self.request.excerpt_path.clone(),
|
|
||||||
range: self.request.excerpt_line_range.clone(),
|
|
||||||
text: &self.request.excerpt,
|
|
||||||
text_is_truncated: false,
|
|
||||||
};
|
|
||||||
excerpt_file_snippets.push(&excerpt_snippet);
|
|
||||||
file_snippets.push((&self.request.excerpt_path, excerpt_file_snippets, true));
|
|
||||||
|
|
||||||
let section_labels =
|
|
||||||
self.push_file_snippets(prompt, excerpt_file_insertions, file_snippets)?;
|
|
||||||
|
|
||||||
Ok(section_labels)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_file_snippets(
|
|
||||||
&self,
|
|
||||||
output: &mut String,
|
|
||||||
excerpt_file_insertions: &mut Vec<(Point, &'static str)>,
|
|
||||||
file_snippets: Vec<(&'a Path, Vec<&'a PlannedSnippet>, bool)>,
|
|
||||||
) -> Result<SectionLabels> {
|
|
||||||
let mut section_ranges = Vec::new();
|
|
||||||
let mut excerpt_index = None;
|
|
||||||
|
|
||||||
for (file_path, mut snippets, is_excerpt_file) in file_snippets {
|
|
||||||
snippets.sort_by_key(|s| (s.range.start, Reverse(s.range.end)));
|
|
||||||
|
|
||||||
// TODO: What if the snippets get expanded too large to be editable?
|
|
||||||
let mut current_snippet: Option<(&PlannedSnippet, Range<Line>)> = None;
|
|
||||||
let mut disjoint_snippets: Vec<(&PlannedSnippet, Range<Line>)> = Vec::new();
|
|
||||||
for snippet in snippets {
|
|
||||||
if let Some((_, current_snippet_range)) = current_snippet.as_mut()
|
|
||||||
&& snippet.range.start <= current_snippet_range.end
|
|
||||||
{
|
|
||||||
current_snippet_range.end = current_snippet_range.end.max(snippet.range.end);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(current_snippet) = current_snippet.take() {
|
|
||||||
disjoint_snippets.push(current_snippet);
|
|
||||||
}
|
|
||||||
current_snippet = Some((snippet, snippet.range.clone()));
|
|
||||||
}
|
|
||||||
if let Some(current_snippet) = current_snippet.take() {
|
|
||||||
disjoint_snippets.push(current_snippet);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(output, "`````path={}", file_path.display()).ok();
|
|
||||||
let mut skipped_last_snippet = false;
|
|
||||||
for (snippet, range) in disjoint_snippets {
|
|
||||||
let section_index = section_ranges.len();
|
|
||||||
|
|
||||||
match self.request.prompt_format {
|
|
||||||
PromptFormat::MarkedExcerpt
|
|
||||||
| PromptFormat::OnlySnippets
|
|
||||||
| PromptFormat::OldTextNewText
|
|
||||||
| PromptFormat::Minimal
|
|
||||||
| PromptFormat::NumLinesUniDiff => {
|
|
||||||
if range.start.0 > 0 && !skipped_last_snippet {
|
|
||||||
output.push_str("…\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PromptFormat::LabeledSections => {
|
|
||||||
if is_excerpt_file
|
|
||||||
&& range.start <= self.request.excerpt_line_range.start
|
|
||||||
&& range.end >= self.request.excerpt_line_range.end
|
|
||||||
{
|
|
||||||
writeln!(output, "<|current_section|>").ok();
|
|
||||||
} else {
|
|
||||||
writeln!(output, "<|section_{}|>", section_index).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PromptFormat::MinimalQwen => unreachable!(),
|
|
||||||
PromptFormat::SeedCoder1120 => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
let push_full_snippet = |output: &mut String| {
|
|
||||||
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
|
|
||||||
for (i, line) in snippet.text.lines().enumerate() {
|
|
||||||
writeln!(output, "{}|{}", i as u32 + range.start.0 + 1, line)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output.push_str(&snippet.text);
|
|
||||||
}
|
|
||||||
anyhow::Ok(())
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_excerpt_file {
|
|
||||||
if self.request.prompt_format == PromptFormat::OnlySnippets {
|
|
||||||
if range.start >= self.request.excerpt_line_range.start
|
|
||||||
&& range.end <= self.request.excerpt_line_range.end
|
|
||||||
{
|
|
||||||
skipped_last_snippet = true;
|
|
||||||
} else {
|
|
||||||
skipped_last_snippet = false;
|
|
||||||
output.push_str(snippet.text);
|
|
||||||
}
|
|
||||||
} else if !excerpt_file_insertions.is_empty() {
|
|
||||||
let lines = snippet.text.lines().collect::<Vec<_>>();
|
|
||||||
let push_line = |output: &mut String, line_ix: usize| {
|
|
||||||
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
|
|
||||||
write!(output, "{}|", line_ix as u32 + range.start.0 + 1)?;
|
|
||||||
}
|
|
||||||
anyhow::Ok(writeln!(output, "{}", lines[line_ix])?)
|
|
||||||
};
|
|
||||||
let mut last_line_ix = 0;
|
|
||||||
let mut insertion_ix = 0;
|
|
||||||
while insertion_ix < excerpt_file_insertions.len() {
|
|
||||||
let (point, insertion) = &excerpt_file_insertions[insertion_ix];
|
|
||||||
let found = point.line >= range.start && point.line <= range.end;
|
|
||||||
if found {
|
|
||||||
excerpt_index = Some(section_index);
|
|
||||||
let insertion_line_ix = (point.line.0 - range.start.0) as usize;
|
|
||||||
for line_ix in last_line_ix..insertion_line_ix {
|
|
||||||
push_line(output, line_ix)?;
|
|
||||||
}
|
|
||||||
if let Some(next_line) = lines.get(insertion_line_ix) {
|
|
||||||
if self.request.prompt_format == PromptFormat::NumLinesUniDiff {
|
|
||||||
write!(
|
|
||||||
output,
|
|
||||||
"{}|",
|
|
||||||
insertion_line_ix as u32 + range.start.0 + 1
|
|
||||||
)?
|
|
||||||
}
|
|
||||||
output.push_str(&next_line[..point.column as usize]);
|
|
||||||
output.push_str(insertion);
|
|
||||||
writeln!(output, "{}", &next_line[point.column as usize..])?;
|
|
||||||
} else {
|
|
||||||
writeln!(output, "{}", insertion)?;
|
|
||||||
}
|
|
||||||
last_line_ix = insertion_line_ix + 1;
|
|
||||||
excerpt_file_insertions.remove(insertion_ix);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
insertion_ix += 1;
|
|
||||||
}
|
|
||||||
skipped_last_snippet = false;
|
|
||||||
for line_ix in last_line_ix..lines.len() {
|
|
||||||
push_line(output, line_ix)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
skipped_last_snippet = false;
|
|
||||||
push_full_snippet(output)?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
skipped_last_snippet = false;
|
|
||||||
push_full_snippet(output)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
section_ranges.push((snippet.path.clone(), range));
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push_str("`````\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(SectionLabels {
|
|
||||||
// TODO: Clean this up
|
|
||||||
excerpt_index: match self.request.prompt_format {
|
|
||||||
PromptFormat::OnlySnippets => 0,
|
|
||||||
_ => excerpt_index.context("bug: no snippet found for excerpt")?,
|
|
||||||
},
|
|
||||||
section_ranges,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn declaration_score_density(declaration: &ReferencedDeclaration, style: DeclarationStyle) -> f32 {
|
|
||||||
declaration_score(declaration, style) / declaration_size(declaration, style) as f32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn declaration_score(declaration: &ReferencedDeclaration, style: DeclarationStyle) -> f32 {
|
|
||||||
match style {
|
|
||||||
DeclarationStyle::Signature => declaration.signature_score,
|
|
||||||
DeclarationStyle::Declaration => declaration.declaration_score,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn declaration_size(declaration: &ReferencedDeclaration, style: DeclarationStyle) -> usize {
|
|
||||||
match style {
|
|
||||||
DeclarationStyle::Signature => declaration.signature_range.len(),
|
|
||||||
DeclarationStyle::Declaration => declaration.text.len(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PromptData {
|
struct PromptData {
|
||||||
events: Vec<Arc<Event>>,
|
events: Vec<Arc<Event>>,
|
||||||
cursor_point: Point,
|
cursor_point: Point,
|
||||||
cursor_path: Arc<Path>, // TODO: make a common struct with cursor_point
|
cursor_path: Arc<Path>, // TODO: make a common struct with cursor_point
|
||||||
included_files: Vec<IncludedFile>,
|
included_files: Vec<RelatedFile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -1051,7 +461,7 @@ impl SeedCoder1120Prompt {
|
|||||||
context
|
context
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fmt_fim(&self, file: &IncludedFile, cursor_point: Point) -> String {
|
fn fmt_fim(&self, file: &RelatedFile, cursor_point: Point) -> String {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
const FIM_SUFFIX: &str = "<[fim-suffix]>";
|
const FIM_SUFFIX: &str = "<[fim-suffix]>";
|
||||||
const FIM_PREFIX: &str = "<[fim-prefix]>";
|
const FIM_PREFIX: &str = "<[fim-prefix]>";
|
||||||
|
|||||||
@@ -1,244 +0,0 @@
|
|||||||
use anyhow::Result;
|
|
||||||
use cloud_llm_client::predict_edits_v3::{self, Excerpt};
|
|
||||||
use indoc::indoc;
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::fmt::Write;
|
|
||||||
|
|
||||||
use crate::{push_events, write_codeblock};
|
|
||||||
|
|
||||||
pub fn build_prompt(request: predict_edits_v3::PlanContextRetrievalRequest) -> Result<String> {
|
|
||||||
let mut prompt = SEARCH_INSTRUCTIONS.to_string();
|
|
||||||
|
|
||||||
if !request.events.is_empty() {
|
|
||||||
writeln!(&mut prompt, "\n## User Edits\n\n")?;
|
|
||||||
push_events(&mut prompt, &request.events);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(&mut prompt, "## Cursor context\n")?;
|
|
||||||
write_codeblock(
|
|
||||||
&request.excerpt_path,
|
|
||||||
&[Excerpt {
|
|
||||||
start_line: request.excerpt_line_range.start,
|
|
||||||
text: request.excerpt.into(),
|
|
||||||
}],
|
|
||||||
&[],
|
|
||||||
request.cursor_file_max_row,
|
|
||||||
true,
|
|
||||||
&mut prompt,
|
|
||||||
);
|
|
||||||
|
|
||||||
writeln!(&mut prompt, "{TOOL_USE_REMINDER}")?;
|
|
||||||
|
|
||||||
Ok(prompt)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Search for relevant code
|
|
||||||
///
|
|
||||||
/// For the best results, run multiple queries at once with a single invocation of this tool.
|
|
||||||
#[derive(Clone, Deserialize, Serialize, JsonSchema)]
|
|
||||||
pub struct SearchToolInput {
|
|
||||||
/// An array of queries to run for gathering context relevant to the next prediction
|
|
||||||
#[schemars(length(max = 3))]
|
|
||||||
#[serde(deserialize_with = "deserialize_queries")]
|
|
||||||
pub queries: Box<[SearchToolQuery]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_queries<'de, D>(deserializer: D) -> Result<Box<[SearchToolQuery]>, D::Error>
|
|
||||||
where
|
|
||||||
D: serde::Deserializer<'de>,
|
|
||||||
{
|
|
||||||
use serde::de::Error;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
enum QueryCollection {
|
|
||||||
Array(Box<[SearchToolQuery]>),
|
|
||||||
DoubleArray(Box<[Box<[SearchToolQuery]>]>),
|
|
||||||
Single(SearchToolQuery),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
enum MaybeDoubleEncoded {
|
|
||||||
SingleEncoded(QueryCollection),
|
|
||||||
DoubleEncoded(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = MaybeDoubleEncoded::deserialize(deserializer)?;
|
|
||||||
|
|
||||||
let normalized = match result {
|
|
||||||
MaybeDoubleEncoded::SingleEncoded(value) => value,
|
|
||||||
MaybeDoubleEncoded::DoubleEncoded(value) => {
|
|
||||||
serde_json::from_str(&value).map_err(D::Error::custom)?
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(match normalized {
|
|
||||||
QueryCollection::Array(items) => items,
|
|
||||||
QueryCollection::Single(search_tool_query) => Box::new([search_tool_query]),
|
|
||||||
QueryCollection::DoubleArray(double_array) => double_array.into_iter().flatten().collect(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Search for relevant code by path, syntax hierarchy, and content.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Hash)]
|
|
||||||
pub struct SearchToolQuery {
|
|
||||||
/// 1. A glob pattern to match file paths in the codebase to search in.
|
|
||||||
pub glob: String,
|
|
||||||
/// 2. Regular expressions to match syntax nodes **by their first line** and hierarchy.
|
|
||||||
///
|
|
||||||
/// Subsequent regexes match nodes within the full content of the nodes matched by the previous regexes.
|
|
||||||
///
|
|
||||||
/// Example: Searching for a `User` class
|
|
||||||
/// ["class\s+User"]
|
|
||||||
///
|
|
||||||
/// Example: Searching for a `get_full_name` method under a `User` class
|
|
||||||
/// ["class\s+User", "def\sget_full_name"]
|
|
||||||
///
|
|
||||||
/// Skip this field to match on content alone.
|
|
||||||
#[schemars(length(max = 3))]
|
|
||||||
#[serde(default)]
|
|
||||||
pub syntax_node: Vec<String>,
|
|
||||||
/// 3. An optional regular expression to match the final content that should appear in the results.
|
|
||||||
///
|
|
||||||
/// - Content will be matched within all lines of the matched syntax nodes.
|
|
||||||
/// - If syntax node regexes are provided, this field can be skipped to include as much of the node itself as possible.
|
|
||||||
/// - If no syntax node regexes are provided, the content will be matched within the entire file.
|
|
||||||
pub content: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const TOOL_NAME: &str = "search";
|
|
||||||
|
|
||||||
const SEARCH_INSTRUCTIONS: &str = indoc! {r#"
|
|
||||||
You are part of an edit prediction system in a code editor.
|
|
||||||
Your role is to search for code that will serve as context for predicting the next edit.
|
|
||||||
|
|
||||||
- Analyze the user's recent edits and current cursor context
|
|
||||||
- Use the `search` tool to find code that is relevant for predicting the next edit
|
|
||||||
- Focus on finding:
|
|
||||||
- Code patterns that might need similar changes based on the recent edits
|
|
||||||
- Functions, variables, types, and constants referenced in the current cursor context
|
|
||||||
- Related implementations, usages, or dependencies that may require consistent updates
|
|
||||||
- How items defined in the cursor excerpt are used or altered
|
|
||||||
- You will not be able to filter results or perform subsequent queries, so keep searches as targeted as possible
|
|
||||||
- Use `syntax_node` parameter whenever you're looking for a particular type, class, or function
|
|
||||||
- Avoid using wildcard globs if you already know the file path of the content you're looking for
|
|
||||||
"#};
|
|
||||||
|
|
||||||
const TOOL_USE_REMINDER: &str = indoc! {"
|
|
||||||
--
|
|
||||||
Analyze the user's intent in one to two sentences, then call the `search` tool.
|
|
||||||
"};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_deserialize_queries() {
|
|
||||||
let single_query_json = indoc! {r#"{
|
|
||||||
"queries": {
|
|
||||||
"glob": "**/*.rs",
|
|
||||||
"syntax_node": ["fn test"],
|
|
||||||
"content": "assert"
|
|
||||||
}
|
|
||||||
}"#};
|
|
||||||
|
|
||||||
let flat_input: SearchToolInput = serde_json::from_str(single_query_json).unwrap();
|
|
||||||
assert_eq!(flat_input.queries.len(), 1);
|
|
||||||
assert_eq!(flat_input.queries[0].glob, "**/*.rs");
|
|
||||||
assert_eq!(flat_input.queries[0].syntax_node, vec!["fn test"]);
|
|
||||||
assert_eq!(flat_input.queries[0].content, Some("assert".to_string()));
|
|
||||||
|
|
||||||
let flat_json = indoc! {r#"{
|
|
||||||
"queries": [
|
|
||||||
{
|
|
||||||
"glob": "**/*.rs",
|
|
||||||
"syntax_node": ["fn test"],
|
|
||||||
"content": "assert"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": "**/*.ts",
|
|
||||||
"syntax_node": [],
|
|
||||||
"content": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}"#};
|
|
||||||
|
|
||||||
let flat_input: SearchToolInput = serde_json::from_str(flat_json).unwrap();
|
|
||||||
assert_eq!(flat_input.queries.len(), 2);
|
|
||||||
assert_eq!(flat_input.queries[0].glob, "**/*.rs");
|
|
||||||
assert_eq!(flat_input.queries[0].syntax_node, vec!["fn test"]);
|
|
||||||
assert_eq!(flat_input.queries[0].content, Some("assert".to_string()));
|
|
||||||
assert_eq!(flat_input.queries[1].glob, "**/*.ts");
|
|
||||||
assert_eq!(flat_input.queries[1].syntax_node.len(), 0);
|
|
||||||
assert_eq!(flat_input.queries[1].content, None);
|
|
||||||
|
|
||||||
let nested_json = indoc! {r#"{
|
|
||||||
"queries": [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"glob": "**/*.rs",
|
|
||||||
"syntax_node": ["fn test"],
|
|
||||||
"content": "assert"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"glob": "**/*.ts",
|
|
||||||
"syntax_node": [],
|
|
||||||
"content": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}"#};
|
|
||||||
|
|
||||||
let nested_input: SearchToolInput = serde_json::from_str(nested_json).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(nested_input.queries.len(), 2);
|
|
||||||
|
|
||||||
assert_eq!(nested_input.queries[0].glob, "**/*.rs");
|
|
||||||
assert_eq!(nested_input.queries[0].syntax_node, vec!["fn test"]);
|
|
||||||
assert_eq!(nested_input.queries[0].content, Some("assert".to_string()));
|
|
||||||
assert_eq!(nested_input.queries[1].glob, "**/*.ts");
|
|
||||||
assert_eq!(nested_input.queries[1].syntax_node.len(), 0);
|
|
||||||
assert_eq!(nested_input.queries[1].content, None);
|
|
||||||
|
|
||||||
let double_encoded_queries = serde_json::to_string(&json!({
|
|
||||||
"queries": serde_json::to_string(&json!([
|
|
||||||
{
|
|
||||||
"glob": "**/*.rs",
|
|
||||||
"syntax_node": ["fn test"],
|
|
||||||
"content": "assert"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"glob": "**/*.ts",
|
|
||||||
"syntax_node": [],
|
|
||||||
"content": null
|
|
||||||
}
|
|
||||||
])).unwrap()
|
|
||||||
}))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let double_encoded_input: SearchToolInput =
|
|
||||||
serde_json::from_str(&double_encoded_queries).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(double_encoded_input.queries.len(), 2);
|
|
||||||
|
|
||||||
assert_eq!(double_encoded_input.queries[0].glob, "**/*.rs");
|
|
||||||
assert_eq!(double_encoded_input.queries[0].syntax_node, vec!["fn test"]);
|
|
||||||
assert_eq!(
|
|
||||||
double_encoded_input.queries[0].content,
|
|
||||||
Some("assert".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(double_encoded_input.queries[1].glob, "**/*.ts");
|
|
||||||
assert_eq!(double_encoded_input.queries[1].syntax_node.len(), 0);
|
|
||||||
assert_eq!(double_encoded_input.queries[1].content, None);
|
|
||||||
|
|
||||||
// ### ERROR Switching from var declarations to lexical declarations [RUN 073]
|
|
||||||
// invalid search json {"queries": ["express/lib/response.js", "var\\s+[a-zA-Z_][a-zA-Z0-9_]*\\s*=.*;", "function.*\\(.*\\).*\\{.*\\}"]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ path = "src/codestral.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
edit_prediction.workspace = true
|
edit_prediction_types.workspace = true
|
||||||
edit_prediction_context.workspace = true
|
edit_prediction_context.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use edit_prediction::{Direction, EditPrediction, EditPredictionProvider};
|
|
||||||
use edit_prediction_context::{EditPredictionExcerpt, EditPredictionExcerptOptions};
|
use edit_prediction_context::{EditPredictionExcerpt, EditPredictionExcerptOptions};
|
||||||
|
use edit_prediction_types::{Direction, EditPrediction, EditPredictionDelegate};
|
||||||
use futures::AsyncReadExt;
|
use futures::AsyncReadExt;
|
||||||
use gpui::{App, Context, Entity, Task};
|
use gpui::{App, Context, Entity, Task};
|
||||||
use http_client::HttpClient;
|
use http_client::HttpClient;
|
||||||
@@ -43,17 +43,17 @@ impl CurrentCompletion {
|
|||||||
/// Attempts to adjust the edits based on changes made to the buffer since the completion was generated.
|
/// Attempts to adjust the edits based on changes made to the buffer since the completion was generated.
|
||||||
/// Returns None if the user's edits conflict with the predicted edits.
|
/// Returns None if the user's edits conflict with the predicted edits.
|
||||||
fn interpolate(&self, new_snapshot: &BufferSnapshot) -> Option<Vec<(Range<Anchor>, Arc<str>)>> {
|
fn interpolate(&self, new_snapshot: &BufferSnapshot) -> Option<Vec<(Range<Anchor>, Arc<str>)>> {
|
||||||
edit_prediction::interpolate_edits(&self.snapshot, new_snapshot, &self.edits)
|
edit_prediction_types::interpolate_edits(&self.snapshot, new_snapshot, &self.edits)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CodestralCompletionProvider {
|
pub struct CodestralEditPredictionDelegate {
|
||||||
http_client: Arc<dyn HttpClient>,
|
http_client: Arc<dyn HttpClient>,
|
||||||
pending_request: Option<Task<Result<()>>>,
|
pending_request: Option<Task<Result<()>>>,
|
||||||
current_completion: Option<CurrentCompletion>,
|
current_completion: Option<CurrentCompletion>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodestralCompletionProvider {
|
impl CodestralEditPredictionDelegate {
|
||||||
pub fn new(http_client: Arc<dyn HttpClient>) -> Self {
|
pub fn new(http_client: Arc<dyn HttpClient>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
http_client,
|
http_client,
|
||||||
@@ -165,7 +165,7 @@ impl CodestralCompletionProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditPredictionProvider for CodestralCompletionProvider {
|
impl EditPredictionDelegate for CodestralEditPredictionDelegate {
|
||||||
fn name() -> &'static str {
|
fn name() -> &'static str {
|
||||||
"codestral"
|
"codestral"
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@ impl EditPredictionProvider for CodestralCompletionProvider {
|
|||||||
"Codestral"
|
"Codestral"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_completions_in_menu() -> bool {
|
fn show_predictions_in_menu() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +239,6 @@ impl EditPredictionProvider for CodestralCompletionProvider {
|
|||||||
cursor_point,
|
cursor_point,
|
||||||
&snapshot,
|
&snapshot,
|
||||||
&EXCERPT_OPTIONS,
|
&EXCERPT_OPTIONS,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.context("Line containing cursor doesn't fit in excerpt max bytes")?;
|
.context("Line containing cursor doesn't fit in excerpt max bytes")?;
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ tokio = { workspace = true, features = ["full"] }
|
|||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
tower = "0.4"
|
tower = "0.4"
|
||||||
tower-http = { workspace = true, features = ["trace"] }
|
tower-http = { workspace = true, features = ["trace"] }
|
||||||
tracing = "0.1.40"
|
tracing.workspace = true
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry", "tracing-log"] } # workaround for https://github.com/tokio-rs/tracing/issues/2927
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry", "tracing-log"] } # workaround for https://github.com/tokio-rs/tracing/issues/2927
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ CREATE TABLE "project_repositories" (
|
|||||||
"merge_message" VARCHAR,
|
"merge_message" VARCHAR,
|
||||||
"branch_summary" VARCHAR,
|
"branch_summary" VARCHAR,
|
||||||
"head_commit_details" VARCHAR,
|
"head_commit_details" VARCHAR,
|
||||||
|
"remote_upstream_url" VARCHAR,
|
||||||
|
"remote_origin_url" VARCHAR,
|
||||||
PRIMARY KEY (project_id, id)
|
PRIMARY KEY (project_id, id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "project_repositories" ADD COLUMN "remote_upstream_url" VARCHAR;
|
||||||
|
ALTER TABLE "project_repositories" ADD COLUMN "remote_origin_url" VARCHAR;
|
||||||
@@ -362,6 +362,8 @@ impl Database {
|
|||||||
entry_ids: ActiveValue::set("[]".into()),
|
entry_ids: ActiveValue::set("[]".into()),
|
||||||
head_commit_details: ActiveValue::set(None),
|
head_commit_details: ActiveValue::set(None),
|
||||||
merge_message: ActiveValue::set(None),
|
merge_message: ActiveValue::set(None),
|
||||||
|
remote_upstream_url: ActiveValue::set(None),
|
||||||
|
remote_origin_url: ActiveValue::set(None),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -511,6 +513,8 @@ impl Database {
|
|||||||
serde_json::to_string(&update.current_merge_conflicts).unwrap(),
|
serde_json::to_string(&update.current_merge_conflicts).unwrap(),
|
||||||
)),
|
)),
|
||||||
merge_message: ActiveValue::set(update.merge_message.clone()),
|
merge_message: ActiveValue::set(update.merge_message.clone()),
|
||||||
|
remote_upstream_url: ActiveValue::set(update.remote_upstream_url.clone()),
|
||||||
|
remote_origin_url: ActiveValue::set(update.remote_origin_url.clone()),
|
||||||
})
|
})
|
||||||
.on_conflict(
|
.on_conflict(
|
||||||
OnConflict::columns([
|
OnConflict::columns([
|
||||||
@@ -1005,6 +1009,8 @@ impl Database {
|
|||||||
is_last_update: true,
|
is_last_update: true,
|
||||||
merge_message: db_repository_entry.merge_message,
|
merge_message: db_repository_entry.merge_message,
|
||||||
stash_entries: Vec::new(),
|
stash_entries: Vec::new(),
|
||||||
|
remote_upstream_url: db_repository_entry.remote_upstream_url.clone(),
|
||||||
|
remote_origin_url: db_repository_entry.remote_origin_url.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -796,6 +796,8 @@ impl Database {
|
|||||||
is_last_update: true,
|
is_last_update: true,
|
||||||
merge_message: db_repository.merge_message,
|
merge_message: db_repository.merge_message,
|
||||||
stash_entries: Vec::new(),
|
stash_entries: Vec::new(),
|
||||||
|
remote_upstream_url: db_repository.remote_upstream_url.clone(),
|
||||||
|
remote_origin_url: db_repository.remote_origin_url.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ pub struct Model {
|
|||||||
pub branch_summary: Option<String>,
|
pub branch_summary: Option<String>,
|
||||||
// A JSON object representing the current Head commit values
|
// A JSON object representing the current Head commit values
|
||||||
pub head_commit_details: Option<String>,
|
pub head_commit_details: Option<String>,
|
||||||
|
pub remote_upstream_url: Option<String>,
|
||||||
|
pub remote_origin_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
|||||||
@@ -469,6 +469,8 @@ impl Server {
|
|||||||
.add_request_handler(forward_mutating_project_request::<proto::GetBlobContent>)
|
.add_request_handler(forward_mutating_project_request::<proto::GetBlobContent>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::GitCreateBranch>)
|
.add_request_handler(forward_mutating_project_request::<proto::GitCreateBranch>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::GitChangeBranch>)
|
.add_request_handler(forward_mutating_project_request::<proto::GitChangeBranch>)
|
||||||
|
.add_request_handler(forward_mutating_project_request::<proto::GitCreateRemote>)
|
||||||
|
.add_request_handler(forward_mutating_project_request::<proto::GitRemoveRemote>)
|
||||||
.add_request_handler(forward_mutating_project_request::<proto::CheckForPushedCommits>)
|
.add_request_handler(forward_mutating_project_request::<proto::CheckForPushedCommits>)
|
||||||
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
.add_message_handler(broadcast_project_message_from_host::<proto::AdvertiseContexts>)
|
||||||
.add_message_handler(update_context)
|
.add_message_handler(update_context)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use call::Room;
|
use call::Room;
|
||||||
use client::ChannelId;
|
use client::ChannelId;
|
||||||
use gpui::{Entity, TestAppContext};
|
use gpui::{Entity, TestAppContext};
|
||||||
@@ -18,7 +16,6 @@ mod randomized_test_helpers;
|
|||||||
mod remote_editing_collaboration_tests;
|
mod remote_editing_collaboration_tests;
|
||||||
mod test_server;
|
mod test_server;
|
||||||
|
|
||||||
use language::{Language, LanguageConfig, LanguageMatcher, tree_sitter_rust};
|
|
||||||
pub use randomized_test_helpers::{
|
pub use randomized_test_helpers::{
|
||||||
RandomizedTest, TestError, UserTestPlan, run_randomized_test, save_randomized_test_plan,
|
RandomizedTest, TestError, UserTestPlan, run_randomized_test, save_randomized_test_plan,
|
||||||
};
|
};
|
||||||
@@ -51,17 +48,3 @@ fn room_participants(room: &Entity<Room>, cx: &mut TestAppContext) -> RoomPartic
|
|||||||
fn channel_id(room: &Entity<Room>, cx: &mut TestAppContext) -> Option<ChannelId> {
|
fn channel_id(room: &Entity<Room>, cx: &mut TestAppContext) -> Option<ChannelId> {
|
||||||
cx.read(|cx| room.read(cx).channel_id())
|
cx.read(|cx| room.read(cx).channel_id())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rust_lang() -> Arc<Language> {
|
|
||||||
Arc::new(Language::new(
|
|
||||||
LanguageConfig {
|
|
||||||
name: "Rust".into(),
|
|
||||||
matcher: LanguageMatcher {
|
|
||||||
path_suffixes: vec!["rs".to_string()],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
use crate::{
|
use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
|
||||||
rpc::RECONNECT_TIMEOUT,
|
|
||||||
tests::{TestServer, rust_lang},
|
|
||||||
};
|
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use editor::{
|
use editor::{
|
||||||
DocumentColorsRenderMode, Editor, FETCH_COLORS_DEBOUNCE_TIMEOUT, MultiBufferOffset, RowInfo,
|
DocumentColorsRenderMode, Editor, FETCH_COLORS_DEBOUNCE_TIMEOUT, MultiBufferOffset, RowInfo,
|
||||||
@@ -23,7 +20,7 @@ use gpui::{
|
|||||||
App, Rgba, SharedString, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext,
|
App, Rgba, SharedString, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext,
|
||||||
};
|
};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use language::FakeLspAdapter;
|
use language::{FakeLspAdapter, rust_lang};
|
||||||
use lsp::LSP_REQUEST_TIMEOUT;
|
use lsp::LSP_REQUEST_TIMEOUT;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use project::{
|
use project::{
|
||||||
@@ -3518,7 +3515,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(sha, message)| (sha.parse().unwrap(), message.into()))
|
.map(|(sha, message)| (sha.parse().unwrap(), message.into()))
|
||||||
.collect(),
|
.collect(),
|
||||||
remote_url: Some("git@github.com:zed-industries/zed.git".to_string()),
|
|
||||||
};
|
};
|
||||||
client_a.fs().set_blame_for_repo(
|
client_a.fs().set_blame_for_repo(
|
||||||
Path::new(path!("/my-repo/.git")),
|
Path::new(path!("/my-repo/.git")),
|
||||||
@@ -3603,10 +3599,6 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
|
|||||||
for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
|
for (idx, (buffer, entry)) in entries.iter().flatten().enumerate() {
|
||||||
let details = blame.details_for_entry(*buffer, entry).unwrap();
|
let details = blame.details_for_entry(*buffer, entry).unwrap();
|
||||||
assert_eq!(details.message, format!("message for idx-{}", idx));
|
assert_eq!(details.message, format!("message for idx-{}", idx));
|
||||||
assert_eq!(
|
|
||||||
details.permalink.unwrap().to_string(),
|
|
||||||
format!("https://github.com/zed-industries/zed/commit/{}", entry.sha)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::{
|
|||||||
rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
||||||
tests::{
|
tests::{
|
||||||
RoomParticipants, TestClient, TestServer, channel_id, following_tests::join_channel,
|
RoomParticipants, TestClient, TestServer, channel_id, following_tests::join_channel,
|
||||||
room_participants, rust_lang,
|
room_participants,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
@@ -26,7 +26,7 @@ use language::{
|
|||||||
Diagnostic, DiagnosticEntry, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig,
|
Diagnostic, DiagnosticEntry, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig,
|
||||||
LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
|
LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
|
||||||
language_settings::{Formatter, FormatterList},
|
language_settings::{Formatter, FormatterList},
|
||||||
tree_sitter_rust, tree_sitter_typescript,
|
rust_lang, tree_sitter_rust, tree_sitter_typescript,
|
||||||
};
|
};
|
||||||
use lsp::{LanguageServerId, OneOf};
|
use lsp::{LanguageServerId, OneOf};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ fs.workspace = true
|
|||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
http_client.workspace = true
|
http_client.workspace = true
|
||||||
edit_prediction.workspace = true
|
edit_prediction_types.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
lsp.workspace = true
|
lsp.workspace = true
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
pub mod copilot_chat;
|
pub mod copilot_chat;
|
||||||
mod copilot_completion_provider;
|
mod copilot_edit_prediction_delegate;
|
||||||
pub mod copilot_responses;
|
pub mod copilot_responses;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
mod sign_in;
|
mod sign_in;
|
||||||
@@ -46,7 +46,7 @@ use util::rel_path::RelPath;
|
|||||||
use util::{ResultExt, fs::remove_matching};
|
use util::{ResultExt, fs::remove_matching};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub use crate::copilot_completion_provider::CopilotCompletionProvider;
|
pub use crate::copilot_edit_prediction_delegate::CopilotEditPredictionDelegate;
|
||||||
pub use crate::sign_in::{CopilotCodeVerification, initiate_sign_in, reinstall_and_sign_in};
|
pub use crate::sign_in::{CopilotCodeVerification, initiate_sign_in, reinstall_and_sign_in};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{Completion, Copilot};
|
use crate::{Completion, Copilot};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use edit_prediction::{Direction, EditPrediction, EditPredictionProvider};
|
use edit_prediction_types::{Direction, EditPrediction, EditPredictionDelegate};
|
||||||
use gpui::{App, Context, Entity, EntityId, Task};
|
use gpui::{App, Context, Entity, EntityId, Task};
|
||||||
use language::{Buffer, OffsetRangeExt, ToOffset, language_settings::AllLanguageSettings};
|
use language::{Buffer, OffsetRangeExt, ToOffset, language_settings::AllLanguageSettings};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
@@ -8,7 +8,7 @@ use std::{path::Path, time::Duration};
|
|||||||
|
|
||||||
pub const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
|
pub const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
|
||||||
|
|
||||||
pub struct CopilotCompletionProvider {
|
pub struct CopilotEditPredictionDelegate {
|
||||||
cycled: bool,
|
cycled: bool,
|
||||||
buffer_id: Option<EntityId>,
|
buffer_id: Option<EntityId>,
|
||||||
completions: Vec<Completion>,
|
completions: Vec<Completion>,
|
||||||
@@ -19,7 +19,7 @@ pub struct CopilotCompletionProvider {
|
|||||||
copilot: Entity<Copilot>,
|
copilot: Entity<Copilot>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CopilotCompletionProvider {
|
impl CopilotEditPredictionDelegate {
|
||||||
pub fn new(copilot: Entity<Copilot>) -> Self {
|
pub fn new(copilot: Entity<Copilot>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
cycled: false,
|
cycled: false,
|
||||||
@@ -47,7 +47,7 @@ impl CopilotCompletionProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditPredictionProvider for CopilotCompletionProvider {
|
impl EditPredictionDelegate for CopilotEditPredictionDelegate {
|
||||||
fn name() -> &'static str {
|
fn name() -> &'static str {
|
||||||
"copilot"
|
"copilot"
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ impl EditPredictionProvider for CopilotCompletionProvider {
|
|||||||
"Copilot"
|
"Copilot"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_completions_in_menu() -> bool {
|
fn show_predictions_in_menu() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@ mod tests {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
let copilot_provider = cx.new(|_| CopilotEditPredictionDelegate::new(copilot));
|
||||||
cx.update_editor(|editor, window, cx| {
|
cx.update_editor(|editor, window, cx| {
|
||||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||||
});
|
});
|
||||||
@@ -546,7 +546,7 @@ mod tests {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
let copilot_provider = cx.new(|_| CopilotEditPredictionDelegate::new(copilot));
|
||||||
cx.update_editor(|editor, window, cx| {
|
cx.update_editor(|editor, window, cx| {
|
||||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||||
});
|
});
|
||||||
@@ -670,7 +670,7 @@ mod tests {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
let copilot_provider = cx.new(|_| CopilotEditPredictionDelegate::new(copilot));
|
||||||
cx.update_editor(|editor, window, cx| {
|
cx.update_editor(|editor, window, cx| {
|
||||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||||
});
|
});
|
||||||
@@ -753,7 +753,7 @@ mod tests {
|
|||||||
window.focus(&editor.focus_handle(cx));
|
window.focus(&editor.focus_handle(cx));
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
let copilot_provider = cx.new(|_| CopilotEditPredictionDelegate::new(copilot));
|
||||||
editor
|
editor
|
||||||
.update(cx, |editor, window, cx| {
|
.update(cx, |editor, window, cx| {
|
||||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||||
@@ -848,7 +848,7 @@ mod tests {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
let copilot_provider = cx.new(|_| CopilotEditPredictionDelegate::new(copilot));
|
||||||
cx.update_editor(|editor, window, cx| {
|
cx.update_editor(|editor, window, cx| {
|
||||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||||
});
|
});
|
||||||
@@ -1000,7 +1000,7 @@ mod tests {
|
|||||||
window.focus(&editor.focus_handle(cx))
|
window.focus(&editor.focus_handle(cx))
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let copilot_provider = cx.new(|_| CopilotCompletionProvider::new(copilot));
|
let copilot_provider = cx.new(|_| CopilotEditPredictionDelegate::new(copilot));
|
||||||
editor
|
editor
|
||||||
.update(cx, |editor, window, cx| {
|
.update(cx, |editor, window, cx| {
|
||||||
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
editor.set_edit_prediction_provider(Some(copilot_provider), window, cx)
|
||||||
@@ -1017,11 +1017,13 @@ impl SearchableItem for DapLogView {
|
|||||||
fn update_matches(
|
fn update_matches(
|
||||||
&mut self,
|
&mut self,
|
||||||
matches: &[Self::Match],
|
matches: &[Self::Match],
|
||||||
|
active_match_index: Option<usize>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.editor
|
self.editor.update(cx, |e, cx| {
|
||||||
.update(cx, |e, cx| e.update_matches(matches, window, cx))
|
e.update_matches(matches, active_match_index, window, cx)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
|
fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ dap_adapters = { workspace = true, optional = true }
|
|||||||
db.workspace = true
|
db.workspace = true
|
||||||
debugger_tools.workspace = true
|
debugger_tools.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
|
feature_flags.workspace = true
|
||||||
file_icons.workspace = true
|
file_icons.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
@@ -82,6 +83,7 @@ dap_adapters = { workspace = true, features = ["test-support"] }
|
|||||||
debugger_tools = { workspace = true, features = ["test-support"] }
|
debugger_tools = { workspace = true, features = ["test-support"] }
|
||||||
editor = { workspace = true, features = ["test-support"] }
|
editor = { workspace = true, features = ["test-support"] }
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
|
language = { workspace = true, features = ["test-support"] }
|
||||||
project = { workspace = true, features = ["test-support"] }
|
project = { workspace = true, features = ["test-support"] }
|
||||||
tree-sitter-go.workspace = true
|
tree-sitter-go.workspace = true
|
||||||
unindent.workspace = true
|
unindent.workspace = true
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ use dap::adapters::DebugAdapterName;
|
|||||||
use dap::{DapRegistry, StartDebuggingRequestArguments};
|
use dap::{DapRegistry, StartDebuggingRequestArguments};
|
||||||
use dap::{client::SessionId, debugger_settings::DebuggerSettings};
|
use dap::{client::SessionId, debugger_settings::DebuggerSettings};
|
||||||
use editor::{Editor, MultiBufferOffset, ToPoint};
|
use editor::{Editor, MultiBufferOffset, ToPoint};
|
||||||
|
use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, App, AsyncWindowContext, ClipboardItem, Context, DismissEvent, Entity, EntityId,
|
Action, App, AsyncWindowContext, ClipboardItem, Context, Corner, DismissEvent, Entity,
|
||||||
EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task,
|
EntityId, EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point,
|
||||||
WeakEntity, anchored, deferred,
|
Subscription, Task, WeakEntity, anchored, deferred,
|
||||||
};
|
};
|
||||||
|
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
@@ -31,7 +32,9 @@ use settings::Settings;
|
|||||||
use std::sync::{Arc, LazyLock};
|
use std::sync::{Arc, LazyLock};
|
||||||
use task::{DebugScenario, TaskContext};
|
use task::{DebugScenario, TaskContext};
|
||||||
use tree_sitter::{Query, StreamingIterator as _};
|
use tree_sitter::{Query, StreamingIterator as _};
|
||||||
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tab, Tooltip, prelude::*};
|
use ui::{
|
||||||
|
ContextMenu, Divider, PopoverMenu, PopoverMenuHandle, SplitButton, Tab, Tooltip, prelude::*,
|
||||||
|
};
|
||||||
use util::rel_path::RelPath;
|
use util::rel_path::RelPath;
|
||||||
use util::{ResultExt, debug_panic, maybe};
|
use util::{ResultExt, debug_panic, maybe};
|
||||||
use workspace::SplitDirection;
|
use workspace::SplitDirection;
|
||||||
@@ -42,6 +45,12 @@ use workspace::{
|
|||||||
};
|
};
|
||||||
use zed_actions::ToggleFocus;
|
use zed_actions::ToggleFocus;
|
||||||
|
|
||||||
|
pub struct DebuggerHistoryFeatureFlag;
|
||||||
|
|
||||||
|
impl FeatureFlag for DebuggerHistoryFeatureFlag {
|
||||||
|
const NAME: &'static str = "debugger-history";
|
||||||
|
}
|
||||||
|
|
||||||
const DEBUG_PANEL_KEY: &str = "DebugPanel";
|
const DEBUG_PANEL_KEY: &str = "DebugPanel";
|
||||||
|
|
||||||
pub struct DebugPanel {
|
pub struct DebugPanel {
|
||||||
@@ -284,7 +293,7 @@ impl DebugPanel {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
session.update(cx, |session, _| match &mut session.mode {
|
session.update(cx, |session, _| match &mut session.state {
|
||||||
SessionState::Booting(state_task) => {
|
SessionState::Booting(state_task) => {
|
||||||
*state_task = Some(boot_task);
|
*state_task = Some(boot_task);
|
||||||
}
|
}
|
||||||
@@ -662,6 +671,12 @@ impl DebugPanel {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let thread_status = active_session
|
||||||
|
.as_ref()
|
||||||
|
.map(|session| session.read(cx).running_state())
|
||||||
|
.and_then(|state| state.read(cx).thread_status(cx))
|
||||||
|
.unwrap_or(project::debugger::session::ThreadStatus::Exited);
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
div.w_full()
|
div.w_full()
|
||||||
.py_1()
|
.py_1()
|
||||||
@@ -679,10 +694,6 @@ impl DebugPanel {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|session| session.read(cx).running_state()),
|
.map(|session| session.read(cx).running_state()),
|
||||||
|this, running_state| {
|
|this, running_state| {
|
||||||
let thread_status =
|
|
||||||
running_state.read(cx).thread_status(cx).unwrap_or(
|
|
||||||
project::debugger::session::ThreadStatus::Exited,
|
|
||||||
);
|
|
||||||
let capabilities = running_state.read(cx).capabilities(cx);
|
let capabilities = running_state.read(cx).capabilities(cx);
|
||||||
let supports_detach =
|
let supports_detach =
|
||||||
running_state.read(cx).session().read(cx).is_attached();
|
running_state.read(cx).session().read(cx).is_attached();
|
||||||
@@ -871,36 +882,53 @@ impl DebugPanel {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.when(supports_detach, |div| {
|
||||||
|
div.child(
|
||||||
|
IconButton::new(
|
||||||
|
"debug-disconnect",
|
||||||
|
IconName::DebugDetach,
|
||||||
|
)
|
||||||
|
.disabled(
|
||||||
|
thread_status != ThreadStatus::Stopped
|
||||||
|
&& thread_status != ThreadStatus::Running,
|
||||||
|
)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.on_click(window.listener_for(
|
||||||
|
running_state,
|
||||||
|
|this, _, _, cx| {
|
||||||
|
this.detach_client(cx);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.tooltip({
|
||||||
|
let focus_handle = focus_handle.clone();
|
||||||
|
move |_window, cx| {
|
||||||
|
Tooltip::for_action_in(
|
||||||
|
"Detach",
|
||||||
|
&Detach,
|
||||||
|
&focus_handle,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
.when(
|
.when(
|
||||||
supports_detach,
|
cx.has_flag::<DebuggerHistoryFeatureFlag>(),
|
||||||
|div| {
|
|this| {
|
||||||
div.child(
|
this.child(Divider::vertical()).child(
|
||||||
IconButton::new(
|
SplitButton::new(
|
||||||
"debug-disconnect",
|
self.render_history_button(
|
||||||
IconName::DebugDetach,
|
&running_state,
|
||||||
|
thread_status,
|
||||||
|
window,
|
||||||
|
),
|
||||||
|
self.render_history_toggle_button(
|
||||||
|
thread_status,
|
||||||
|
&running_state,
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
.disabled(
|
.style(ui::SplitButtonStyle::Outlined),
|
||||||
thread_status != ThreadStatus::Stopped
|
|
||||||
&& thread_status != ThreadStatus::Running,
|
|
||||||
)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.on_click(window.listener_for(
|
|
||||||
running_state,
|
|
||||||
|this, _, _, cx| {
|
|
||||||
this.detach_client(cx);
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.tooltip({
|
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
move |_window, cx| {
|
|
||||||
Tooltip::for_action_in(
|
|
||||||
"Detach",
|
|
||||||
&Detach,
|
|
||||||
&focus_handle,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -1317,6 +1345,97 @@ impl DebugPanel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_history_button(
|
||||||
|
&self,
|
||||||
|
running_state: &Entity<RunningState>,
|
||||||
|
thread_status: ThreadStatus,
|
||||||
|
window: &mut Window,
|
||||||
|
) -> IconButton {
|
||||||
|
IconButton::new("debug-back-in-history", IconName::HistoryRerun)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.on_click(window.listener_for(running_state, |this, _, _window, cx| {
|
||||||
|
this.session().update(cx, |session, cx| {
|
||||||
|
let ix = session
|
||||||
|
.active_snapshot_index()
|
||||||
|
.unwrap_or_else(|| session.historic_snapshots().len());
|
||||||
|
|
||||||
|
session.select_historic_snapshot(Some(ix.saturating_sub(1)), cx);
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.disabled(
|
||||||
|
thread_status == ThreadStatus::Running || thread_status == ThreadStatus::Stepping,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_history_toggle_button(
|
||||||
|
&self,
|
||||||
|
thread_status: ThreadStatus,
|
||||||
|
running_state: &Entity<RunningState>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
PopoverMenu::new("debug-back-in-history-menu")
|
||||||
|
.trigger(
|
||||||
|
ui::ButtonLike::new_rounded_right("debug-back-in-history-menu-trigger")
|
||||||
|
.layer(ui::ElevationIndex::ModalSurface)
|
||||||
|
.size(ui::ButtonSize::None)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.px_1()
|
||||||
|
.child(Icon::new(IconName::ChevronDown).size(IconSize::XSmall)),
|
||||||
|
)
|
||||||
|
.disabled(
|
||||||
|
thread_status == ThreadStatus::Running
|
||||||
|
|| thread_status == ThreadStatus::Stepping,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.menu({
|
||||||
|
let running_state = running_state.clone();
|
||||||
|
move |window, cx| {
|
||||||
|
let handler =
|
||||||
|
|ix: Option<usize>, running_state: Entity<RunningState>, cx: &mut App| {
|
||||||
|
running_state.update(cx, |state, cx| {
|
||||||
|
state.session().update(cx, |session, cx| {
|
||||||
|
session.select_historic_snapshot(ix, cx);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let running_state = running_state.clone();
|
||||||
|
Some(ContextMenu::build(
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
move |mut context_menu, _window, cx| {
|
||||||
|
let history = running_state
|
||||||
|
.read(cx)
|
||||||
|
.session()
|
||||||
|
.read(cx)
|
||||||
|
.historic_snapshots();
|
||||||
|
|
||||||
|
context_menu = context_menu.entry("Current State", None, {
|
||||||
|
let running_state = running_state.clone();
|
||||||
|
move |_window, cx| {
|
||||||
|
handler(None, running_state.clone(), cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
context_menu = context_menu.separator();
|
||||||
|
|
||||||
|
for (ix, _) in history.iter().enumerate().rev() {
|
||||||
|
context_menu =
|
||||||
|
context_menu.entry(format!("history-{}", ix + 1), None, {
|
||||||
|
let running_state = running_state.clone();
|
||||||
|
move |_window, cx| {
|
||||||
|
handler(Some(ix), running_state.clone(), cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
context_menu
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.anchor(Corner::TopRight)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn register_session_inner(
|
async fn register_session_inner(
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ pub fn init(cx: &mut App) {
|
|||||||
window.on_action(
|
window.on_action(
|
||||||
TypeId::of::<editor::actions::EvaluateSelectedText>(),
|
TypeId::of::<editor::actions::EvaluateSelectedText>(),
|
||||||
move |_, _, window, cx| {
|
move |_, _, window, cx| {
|
||||||
maybe!({
|
let status = maybe!({
|
||||||
let text = editor
|
let text = editor
|
||||||
.update(cx, |editor, cx| {
|
.update(cx, |editor, cx| {
|
||||||
let range = editor
|
let range = editor
|
||||||
@@ -411,7 +411,13 @@ pub fn init(cx: &mut App) {
|
|||||||
|
|
||||||
state.session().update(cx, |session, cx| {
|
state.session().update(cx, |session, cx| {
|
||||||
session
|
session
|
||||||
.evaluate(text, None, stack_id, None, cx)
|
.evaluate(
|
||||||
|
text,
|
||||||
|
Some(dap::EvaluateArgumentsContext::Repl),
|
||||||
|
stack_id,
|
||||||
|
None,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
.detach();
|
.detach();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -419,6 +425,9 @@ pub fn init(cx: &mut App) {
|
|||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
});
|
});
|
||||||
|
if status.is_some() {
|
||||||
|
cx.stop_propagation();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -881,7 +881,6 @@ impl ConfigureMode {
|
|||||||
.label("Stop on Entry")
|
.label("Stop on Entry")
|
||||||
.label_position(SwitchLabelPosition::Start)
|
.label_position(SwitchLabelPosition::Start)
|
||||||
.label_size(LabelSize::Default)
|
.label_size(LabelSize::Default)
|
||||||
.color(ui::SwitchColor::Accent)
|
|
||||||
.on_click({
|
.on_click({
|
||||||
let this = cx.weak_entity();
|
let this = cx.weak_entity();
|
||||||
move |state, _, cx| {
|
move |state, _, cx| {
|
||||||
@@ -1023,7 +1022,7 @@ impl DebugDelegate {
|
|||||||
Some(TaskSourceKind::Lsp { language_name, .. }) => {
|
Some(TaskSourceKind::Lsp { language_name, .. }) => {
|
||||||
Some(format!("LSP: {language_name}"))
|
Some(format!("LSP: {language_name}"))
|
||||||
}
|
}
|
||||||
Some(TaskSourceKind::Language { name }) => Some(format!("Lang: {name}")),
|
Some(TaskSourceKind::Language { name }) => Some(format!("Language: {name}")),
|
||||||
_ => context.clone().and_then(|ctx| {
|
_ => context.clone().and_then(|ctx| {
|
||||||
ctx.task_context
|
ctx.task_context
|
||||||
.task_variables
|
.task_variables
|
||||||
|
|||||||
@@ -1743,7 +1743,7 @@ impl RunningState {
|
|||||||
|
|
||||||
let is_building = self.session.update(cx, |session, cx| {
|
let is_building = self.session.update(cx, |session, cx| {
|
||||||
session.shutdown(cx).detach();
|
session.shutdown(cx).detach();
|
||||||
matches!(session.mode, session::SessionState::Booting(_))
|
matches!(session.state, session::SessionState::Booting(_))
|
||||||
});
|
});
|
||||||
|
|
||||||
if is_building {
|
if is_building {
|
||||||
|
|||||||
@@ -252,10 +252,11 @@ impl Console {
|
|||||||
let start_offset = range.start;
|
let start_offset = range.start;
|
||||||
let range = buffer.anchor_after(MultiBufferOffset(range.start))
|
let range = buffer.anchor_after(MultiBufferOffset(range.start))
|
||||||
..buffer.anchor_before(MultiBufferOffset(range.end));
|
..buffer.anchor_before(MultiBufferOffset(range.end));
|
||||||
|
let color_fn = color_fetcher(color);
|
||||||
console.highlight_background_key::<ConsoleAnsiHighlight>(
|
console.highlight_background_key::<ConsoleAnsiHighlight>(
|
||||||
start_offset,
|
start_offset,
|
||||||
&[range],
|
&[range],
|
||||||
color_fetcher(color),
|
move |_, theme| color_fn(theme),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ impl LoadedSourceList {
|
|||||||
let list = ListState::new(0, gpui::ListAlignment::Top, px(1000.));
|
let list = ListState::new(0, gpui::ListAlignment::Top, px(1000.));
|
||||||
|
|
||||||
let _subscription = cx.subscribe(&session, |this, _, event, cx| match event {
|
let _subscription = cx.subscribe(&session, |this, _, event, cx| match event {
|
||||||
SessionEvent::Stopped(_) | SessionEvent::LoadedSources => {
|
SessionEvent::Stopped(_)
|
||||||
|
| SessionEvent::HistoricSnapshotSelected
|
||||||
|
| SessionEvent::LoadedSources => {
|
||||||
this.invalidate = true;
|
this.invalidate = true;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ impl ModuleList {
|
|||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
let _subscription = cx.subscribe(&session, |this, _, event, cx| match event {
|
let _subscription = cx.subscribe(&session, |this, _, event, cx| match event {
|
||||||
SessionEvent::Stopped(_) | SessionEvent::Modules => {
|
SessionEvent::Stopped(_)
|
||||||
|
| SessionEvent::HistoricSnapshotSelected
|
||||||
|
| SessionEvent::Modules => {
|
||||||
if this._rebuild_task.is_some() {
|
if this._rebuild_task.is_some() {
|
||||||
this.schedule_rebuild(cx);
|
this.schedule_rebuild(cx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use dap::StackFrameId;
|
use dap::StackFrameId;
|
||||||
|
use dap::adapters::DebugAdapterName;
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState,
|
Action, AnyElement, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, ListState,
|
||||||
@@ -20,7 +21,7 @@ use project::debugger::breakpoint_store::ActiveStackFrame;
|
|||||||
use project::debugger::session::{Session, SessionEvent, StackFrame, ThreadStatus};
|
use project::debugger::session::{Session, SessionEvent, StackFrame, ThreadStatus};
|
||||||
use project::{ProjectItem, ProjectPath};
|
use project::{ProjectItem, ProjectPath};
|
||||||
use ui::{Tooltip, WithScrollbar, prelude::*};
|
use ui::{Tooltip, WithScrollbar, prelude::*};
|
||||||
use workspace::{ItemHandle, Workspace};
|
use workspace::{ItemHandle, Workspace, WorkspaceId};
|
||||||
|
|
||||||
use super::RunningState;
|
use super::RunningState;
|
||||||
|
|
||||||
@@ -58,6 +59,14 @@ impl From<StackFrameFilter> for String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn stack_frame_filter_key(
|
||||||
|
adapter_name: &DebugAdapterName,
|
||||||
|
workspace_id: WorkspaceId,
|
||||||
|
) -> String {
|
||||||
|
let database_id: i64 = workspace_id.into();
|
||||||
|
format!("stack-frame-list-filter-{}-{}", adapter_name.0, database_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct StackFrameList {
|
pub struct StackFrameList {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
_subscription: Subscription,
|
_subscription: Subscription,
|
||||||
@@ -97,7 +106,9 @@ impl StackFrameList {
|
|||||||
SessionEvent::Threads => {
|
SessionEvent::Threads => {
|
||||||
this.schedule_refresh(false, window, cx);
|
this.schedule_refresh(false, window, cx);
|
||||||
}
|
}
|
||||||
SessionEvent::Stopped(..) | SessionEvent::StackTrace => {
|
SessionEvent::Stopped(..)
|
||||||
|
| SessionEvent::StackTrace
|
||||||
|
| SessionEvent::HistoricSnapshotSelected => {
|
||||||
this.schedule_refresh(true, window, cx);
|
this.schedule_refresh(true, window, cx);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -105,14 +116,18 @@ impl StackFrameList {
|
|||||||
|
|
||||||
let list_state = ListState::new(0, gpui::ListAlignment::Top, px(1000.));
|
let list_state = ListState::new(0, gpui::ListAlignment::Top, px(1000.));
|
||||||
|
|
||||||
let list_filter = KEY_VALUE_STORE
|
let list_filter = workspace
|
||||||
.read_kvp(&format!(
|
.read_with(cx, |workspace, _| workspace.database_id())
|
||||||
"stack-frame-list-filter-{}",
|
|
||||||
session.read(cx).adapter().0
|
|
||||||
))
|
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(StackFrameFilter::from_str_or_default)
|
.and_then(|database_id| {
|
||||||
|
let key = stack_frame_filter_key(&session.read(cx).adapter(), database_id);
|
||||||
|
KEY_VALUE_STORE
|
||||||
|
.read_kvp(&key)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.map(StackFrameFilter::from_str_or_default)
|
||||||
|
})
|
||||||
.unwrap_or(StackFrameFilter::All);
|
.unwrap_or(StackFrameFilter::All);
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
@@ -225,7 +240,6 @@ impl StackFrameList {
|
|||||||
}
|
}
|
||||||
this.update_in(cx, |this, window, cx| {
|
this.update_in(cx, |this, window, cx| {
|
||||||
this.build_entries(select_first, window, cx);
|
this.build_entries(select_first, window, cx);
|
||||||
cx.notify();
|
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
})
|
})
|
||||||
@@ -806,15 +820,8 @@ impl StackFrameList {
|
|||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
let database_id: i64 = database_id.into();
|
let key = stack_frame_filter_key(&self.session.read(cx).adapter(), database_id);
|
||||||
let save_task = KEY_VALUE_STORE.write_kvp(
|
let save_task = KEY_VALUE_STORE.write_kvp(key, self.list_filter.into());
|
||||||
format!(
|
|
||||||
"stack-frame-list-filter-{}-{}",
|
|
||||||
self.session.read(cx).adapter().0,
|
|
||||||
database_id,
|
|
||||||
),
|
|
||||||
self.list_filter.into(),
|
|
||||||
);
|
|
||||||
cx.background_spawn(save_task).detach();
|
cx.background_spawn(save_task).detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,6 +217,12 @@ impl VariableList {
|
|||||||
let _subscriptions = vec![
|
let _subscriptions = vec![
|
||||||
cx.subscribe(&stack_frame_list, Self::handle_stack_frame_list_events),
|
cx.subscribe(&stack_frame_list, Self::handle_stack_frame_list_events),
|
||||||
cx.subscribe(&session, |this, _, event, cx| match event {
|
cx.subscribe(&session, |this, _, event, cx| match event {
|
||||||
|
SessionEvent::HistoricSnapshotSelected => {
|
||||||
|
this.selection.take();
|
||||||
|
this.edited_path.take();
|
||||||
|
this.selected_stack_frame_id.take();
|
||||||
|
this.build_entries(cx);
|
||||||
|
}
|
||||||
SessionEvent::Stopped(_) => {
|
SessionEvent::Stopped(_) => {
|
||||||
this.selection.take();
|
this.selection.take();
|
||||||
this.edited_path.take();
|
this.edited_path.take();
|
||||||
@@ -225,7 +231,6 @@ impl VariableList {
|
|||||||
SessionEvent::Variables | SessionEvent::Watchers => {
|
SessionEvent::Variables | SessionEvent::Watchers => {
|
||||||
this.build_entries(cx);
|
this.build_entries(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}),
|
}),
|
||||||
cx.on_focus_out(&focus_handle, window, |this, _, _, cx| {
|
cx.on_focus_out(&focus_handle, window, |this, _, _, cx| {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use dap::{Scope, StackFrame, Variable, requests::Variables};
|
|||||||
use editor::{Editor, EditorMode, MultiBuffer};
|
use editor::{Editor, EditorMode, MultiBuffer};
|
||||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||||
use language::{
|
use language::{
|
||||||
Language, LanguageConfig, LanguageMatcher, tree_sitter_python, tree_sitter_rust,
|
Language, LanguageConfig, LanguageMatcher, rust_lang, tree_sitter_python,
|
||||||
tree_sitter_typescript,
|
tree_sitter_typescript,
|
||||||
};
|
};
|
||||||
use project::{FakeFs, Project};
|
use project::{FakeFs, Project};
|
||||||
@@ -224,7 +224,7 @@ fn main() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
buffer.update(cx, |buffer, cx| {
|
buffer.update(cx, |buffer, cx| {
|
||||||
buffer.set_language(Some(Arc::new(rust_lang())), cx);
|
buffer.set_language(Some(rust_lang()), cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let (editor, cx) = cx.add_window_view(|window, cx| {
|
let (editor, cx) = cx.add_window_view(|window, cx| {
|
||||||
@@ -1521,23 +1521,6 @@ fn main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rust_lang() -> Language {
|
|
||||||
let debug_variables_query = include_str!("../../../languages/src/rust/debugger.scm");
|
|
||||||
Language::new(
|
|
||||||
LanguageConfig {
|
|
||||||
name: "Rust".into(),
|
|
||||||
matcher: LanguageMatcher {
|
|
||||||
path_suffixes: vec!["rs".to_string()],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Some(tree_sitter_rust::LANGUAGE.into()),
|
|
||||||
)
|
|
||||||
.with_debug_variables_query(debug_variables_query)
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_python_inline_values(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
async fn test_python_inline_values(executor: BackgroundExecutor, cx: &mut TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
@@ -1859,21 +1842,23 @@ fn python_lang() -> Language {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn go_lang() -> Language {
|
fn go_lang() -> Arc<Language> {
|
||||||
let debug_variables_query = include_str!("../../../languages/src/go/debugger.scm");
|
let debug_variables_query = include_str!("../../../languages/src/go/debugger.scm");
|
||||||
Language::new(
|
Arc::new(
|
||||||
LanguageConfig {
|
Language::new(
|
||||||
name: "Go".into(),
|
LanguageConfig {
|
||||||
matcher: LanguageMatcher {
|
name: "Go".into(),
|
||||||
path_suffixes: vec!["go".to_string()],
|
matcher: LanguageMatcher {
|
||||||
|
path_suffixes: vec!["go".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
Some(tree_sitter_go::LANGUAGE.into()),
|
||||||
},
|
)
|
||||||
Some(tree_sitter_go::LANGUAGE.into()),
|
.with_debug_variables_query(debug_variables_query)
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.with_debug_variables_query(debug_variables_query)
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test utility function for inline values testing
|
/// Test utility function for inline values testing
|
||||||
@@ -1891,7 +1876,7 @@ async fn test_inline_values_util(
|
|||||||
before: &str,
|
before: &str,
|
||||||
after: &str,
|
after: &str,
|
||||||
active_debug_line: Option<usize>,
|
active_debug_line: Option<usize>,
|
||||||
language: Language,
|
language: Arc<Language>,
|
||||||
executor: BackgroundExecutor,
|
executor: BackgroundExecutor,
|
||||||
cx: &mut TestAppContext,
|
cx: &mut TestAppContext,
|
||||||
) {
|
) {
|
||||||
@@ -2091,7 +2076,7 @@ async fn test_inline_values_util(
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
buffer.update(cx, |buffer, cx| {
|
buffer.update(cx, |buffer, cx| {
|
||||||
buffer.set_language(Some(Arc::new(language)), cx);
|
buffer.set_language(Some(language), cx);
|
||||||
});
|
});
|
||||||
|
|
||||||
let (editor, cx) = cx.add_window_view(|window, cx| {
|
let (editor, cx) = cx.add_window_view(|window, cx| {
|
||||||
@@ -2276,55 +2261,61 @@ fn main() {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn javascript_lang() -> Language {
|
fn javascript_lang() -> Arc<Language> {
|
||||||
let debug_variables_query = include_str!("../../../languages/src/javascript/debugger.scm");
|
let debug_variables_query = include_str!("../../../languages/src/javascript/debugger.scm");
|
||||||
Language::new(
|
Arc::new(
|
||||||
LanguageConfig {
|
Language::new(
|
||||||
name: "JavaScript".into(),
|
LanguageConfig {
|
||||||
matcher: LanguageMatcher {
|
name: "JavaScript".into(),
|
||||||
path_suffixes: vec!["js".to_string()],
|
matcher: LanguageMatcher {
|
||||||
|
path_suffixes: vec!["js".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
|
||||||
},
|
)
|
||||||
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
|
.with_debug_variables_query(debug_variables_query)
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.with_debug_variables_query(debug_variables_query)
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn typescript_lang() -> Language {
|
fn typescript_lang() -> Arc<Language> {
|
||||||
let debug_variables_query = include_str!("../../../languages/src/typescript/debugger.scm");
|
let debug_variables_query = include_str!("../../../languages/src/typescript/debugger.scm");
|
||||||
Language::new(
|
Arc::new(
|
||||||
LanguageConfig {
|
Language::new(
|
||||||
name: "TypeScript".into(),
|
LanguageConfig {
|
||||||
matcher: LanguageMatcher {
|
name: "TypeScript".into(),
|
||||||
path_suffixes: vec!["ts".to_string()],
|
matcher: LanguageMatcher {
|
||||||
|
path_suffixes: vec!["ts".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
|
||||||
},
|
)
|
||||||
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
|
.with_debug_variables_query(debug_variables_query)
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.with_debug_variables_query(debug_variables_query)
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tsx_lang() -> Language {
|
fn tsx_lang() -> Arc<Language> {
|
||||||
let debug_variables_query = include_str!("../../../languages/src/tsx/debugger.scm");
|
let debug_variables_query = include_str!("../../../languages/src/tsx/debugger.scm");
|
||||||
Language::new(
|
Arc::new(
|
||||||
LanguageConfig {
|
Language::new(
|
||||||
name: "TSX".into(),
|
LanguageConfig {
|
||||||
matcher: LanguageMatcher {
|
name: "TSX".into(),
|
||||||
path_suffixes: vec!["tsx".to_string()],
|
matcher: LanguageMatcher {
|
||||||
|
path_suffixes: vec!["tsx".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
|
||||||
},
|
)
|
||||||
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
|
.with_debug_variables_query(debug_variables_query)
|
||||||
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.with_debug_variables_query(debug_variables_query)
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
debugger_panel::DebugPanel,
|
debugger_panel::DebugPanel,
|
||||||
session::running::stack_frame_list::{StackFrameEntry, StackFrameFilter},
|
session::running::stack_frame_list::{
|
||||||
|
StackFrameEntry, StackFrameFilter, stack_frame_filter_key,
|
||||||
|
},
|
||||||
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
|
tests::{active_debug_session_panel, init_test, init_test_workspace, start_debug_session},
|
||||||
};
|
};
|
||||||
use dap::{
|
use dap::{
|
||||||
StackFrame,
|
StackFrame,
|
||||||
requests::{Scopes, StackTrace, Threads},
|
requests::{Scopes, StackTrace, Threads},
|
||||||
};
|
};
|
||||||
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use editor::{Editor, ToPoint as _};
|
use editor::{Editor, ToPoint as _};
|
||||||
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
|
||||||
use project::{FakeFs, Project};
|
use project::{FakeFs, Project};
|
||||||
@@ -1085,3 +1088,180 @@ async fn test_stack_frame_filter(executor: BackgroundExecutor, cx: &mut TestAppC
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_stack_frame_filter_persistence(
|
||||||
|
executor: BackgroundExecutor,
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let fs = FakeFs::new(executor.clone());
|
||||||
|
|
||||||
|
fs.insert_tree(
|
||||||
|
path!("/project"),
|
||||||
|
json!({
|
||||||
|
"src": {
|
||||||
|
"test.js": "function main() { console.log('hello'); }",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||||
|
let workspace = init_test_workspace(&project, cx).await;
|
||||||
|
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||||
|
workspace
|
||||||
|
.update(cx, |workspace, _, _| {
|
||||||
|
workspace.set_random_database_id();
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let threads_response = dap::ThreadsResponse {
|
||||||
|
threads: vec![dap::Thread {
|
||||||
|
id: 1,
|
||||||
|
name: "Thread 1".into(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let stack_trace_response = dap::StackTraceResponse {
|
||||||
|
stack_frames: vec![StackFrame {
|
||||||
|
id: 1,
|
||||||
|
name: "main".into(),
|
||||||
|
source: Some(dap::Source {
|
||||||
|
name: Some("test.js".into()),
|
||||||
|
path: Some(path!("/project/src/test.js").into()),
|
||||||
|
source_reference: None,
|
||||||
|
presentation_hint: None,
|
||||||
|
origin: None,
|
||||||
|
sources: None,
|
||||||
|
adapter_data: None,
|
||||||
|
checksums: None,
|
||||||
|
}),
|
||||||
|
line: 1,
|
||||||
|
column: 1,
|
||||||
|
end_line: None,
|
||||||
|
end_column: None,
|
||||||
|
can_restart: None,
|
||||||
|
instruction_pointer_reference: None,
|
||||||
|
module_id: None,
|
||||||
|
presentation_hint: None,
|
||||||
|
}],
|
||||||
|
total_frames: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let stopped_event = dap::StoppedEvent {
|
||||||
|
reason: dap::StoppedEventReason::Pause,
|
||||||
|
description: None,
|
||||||
|
thread_id: Some(1),
|
||||||
|
preserve_focus_hint: None,
|
||||||
|
text: None,
|
||||||
|
all_threads_stopped: None,
|
||||||
|
hit_breakpoint_ids: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let session = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
|
let client = session.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
let adapter_name = session.update(cx, |session, _| session.adapter());
|
||||||
|
|
||||||
|
client.on_request::<Threads, _>({
|
||||||
|
let threads_response = threads_response.clone();
|
||||||
|
move |_, _| Ok(threads_response.clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on_request::<Scopes, _>(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] }));
|
||||||
|
|
||||||
|
client.on_request::<StackTrace, _>({
|
||||||
|
let stack_trace_response = stack_trace_response.clone();
|
||||||
|
move |_, _| Ok(stack_trace_response.clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
client
|
||||||
|
.fake_event(dap::messages::Events::Stopped(stopped_event.clone()))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let stack_frame_list =
|
||||||
|
active_debug_session_panel(workspace, cx).update(cx, |debug_panel_item, cx| {
|
||||||
|
debug_panel_item
|
||||||
|
.running_state()
|
||||||
|
.update(cx, |state, _| state.stack_frame_list().clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
stack_frame_list.update(cx, |stack_frame_list, _cx| {
|
||||||
|
assert_eq!(
|
||||||
|
stack_frame_list.list_filter(),
|
||||||
|
StackFrameFilter::All,
|
||||||
|
"Initial filter should be All"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
stack_frame_list.update(cx, |stack_frame_list, cx| {
|
||||||
|
stack_frame_list
|
||||||
|
.toggle_frame_filter(Some(project::debugger::session::ThreadStatus::Stopped), cx);
|
||||||
|
assert_eq!(
|
||||||
|
stack_frame_list.list_filter(),
|
||||||
|
StackFrameFilter::OnlyUserFrames,
|
||||||
|
"Filter should be OnlyUserFrames after toggle"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let workspace_id = workspace
|
||||||
|
.update(cx, |workspace, _window, _cx| workspace.database_id())
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.expect("workspace id has to be some for this test to work properly");
|
||||||
|
|
||||||
|
let key = stack_frame_filter_key(&adapter_name, workspace_id);
|
||||||
|
let stored_value = KEY_VALUE_STORE.read_kvp(&key).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
stored_value,
|
||||||
|
Some(StackFrameFilter::OnlyUserFrames.into()),
|
||||||
|
"Filter should be persisted in KVP store with key: {}",
|
||||||
|
key
|
||||||
|
);
|
||||||
|
|
||||||
|
client
|
||||||
|
.fake_event(dap::messages::Events::Terminated(None))
|
||||||
|
.await;
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let session2 = start_debug_session(&workspace, cx, |_| {}).unwrap();
|
||||||
|
let client2 = session2.update(cx, |session, _| session.adapter_client().unwrap());
|
||||||
|
|
||||||
|
client2.on_request::<Threads, _>({
|
||||||
|
let threads_response = threads_response.clone();
|
||||||
|
move |_, _| Ok(threads_response.clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
client2.on_request::<Scopes, _>(move |_, _| Ok(dap::ScopesResponse { scopes: vec![] }));
|
||||||
|
|
||||||
|
client2.on_request::<StackTrace, _>({
|
||||||
|
let stack_trace_response = stack_trace_response.clone();
|
||||||
|
move |_, _| Ok(stack_trace_response.clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
client2
|
||||||
|
.fake_event(dap::messages::Events::Stopped(stopped_event.clone()))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
|
let stack_frame_list2 =
|
||||||
|
active_debug_session_panel(workspace, cx).update(cx, |debug_panel_item, cx| {
|
||||||
|
debug_panel_item
|
||||||
|
.running_state()
|
||||||
|
.update(cx, |state, _| state.stack_frame_list().clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
stack_frame_list2.update(cx, |stack_frame_list, _cx| {
|
||||||
|
assert_eq!(
|
||||||
|
stack_frame_list.list_filter(),
|
||||||
|
StackFrameFilter::OnlyUserFrames,
|
||||||
|
"Filter should be restored from KVP store in new session"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,69 @@ workspace = true
|
|||||||
[lib]
|
[lib]
|
||||||
path = "src/edit_prediction.rs"
|
path = "src/edit_prediction.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
eval-support = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ai_onboarding.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
arrayvec.workspace = true
|
||||||
|
brotli.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
|
cloud_llm_client.workspace = true
|
||||||
|
cloud_zeta2_prompt.workspace = true
|
||||||
|
collections.workspace = true
|
||||||
|
copilot.workspace = true
|
||||||
|
credentials_provider.workspace = true
|
||||||
|
db.workspace = true
|
||||||
|
edit_prediction_types.workspace = true
|
||||||
|
edit_prediction_context.workspace = true
|
||||||
|
feature_flags.workspace = true
|
||||||
|
fs.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
|
indoc.workspace = true
|
||||||
|
itertools.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
|
language_model.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
lsp.workspace = true
|
||||||
|
menu.workspace = true
|
||||||
|
open_ai.workspace = true
|
||||||
|
postage.workspace = true
|
||||||
|
pretty_assertions.workspace = true
|
||||||
|
project.workspace = true
|
||||||
|
rand.workspace = true
|
||||||
|
regex.workspace = true
|
||||||
|
release_channel.workspace = true
|
||||||
|
semver.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
settings.workspace = true
|
||||||
|
smol.workspace = true
|
||||||
|
strsim.workspace = true
|
||||||
|
strum.workspace = true
|
||||||
|
telemetry.workspace = true
|
||||||
|
telemetry_events.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
ui.workspace = true
|
||||||
|
util.workspace = true
|
||||||
|
uuid.workspace = true
|
||||||
|
workspace.workspace = true
|
||||||
|
worktree.workspace = true
|
||||||
|
zed_actions.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
clock = { workspace = true, features = ["test-support"] }
|
||||||
|
cloud_api_types.workspace = true
|
||||||
|
cloud_llm_client = { workspace = true, features = ["test-support"] }
|
||||||
|
ctor.workspace = true
|
||||||
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
|
indoc.workspace = true
|
||||||
|
language = { workspace = true, features = ["test-support"] }
|
||||||
|
language_model = { workspace = true, features = ["test-support"] }
|
||||||
|
lsp.workspace = true
|
||||||
|
parking_lot.workspace = true
|
||||||
|
project = { workspace = true, features = ["test-support"] }
|
||||||
|
settings = { workspace = true, features = ["test-support"] }
|
||||||
|
zlog.workspace = true
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user