diff --git a/.github/workflows/release_actions.yml b/.github/workflows/release_actions.yml
index a99a7a0c85d9cc5ac3a59cb1fa67e5c3cc24d023..fed1d728721e893c7f02dfac05eebb9185e6a6c1 100644
--- a/.github/workflows/release_actions.yml
+++ b/.github/workflows/release_actions.yml
@@ -16,7 +16,7 @@ jobs:
fi
echo "::set-output name=URL::$URL"
- name: Get content
- uses: 2428392/gh-truncate-string-action@67b1b814955634208b103cff064be3cb1c7a19be # v1.3.0
+ uses: 2428392/gh-truncate-string-action@e6b5885fb83c81ca9a700a91b079baec2133be3e # v1.4.0
id: get-content
with:
stringToTruncate: |
diff --git a/.zed/tasks.json b/.zed/tasks.json
index 259ab07f3e0656ec77b53af0cff6300640c2d308..800588a96baf1568806ef0645fd790a8df9fc2d5 100644
--- a/.zed/tasks.json
+++ b/.zed/tasks.json
@@ -3,5 +3,10 @@
"label": "clippy",
"command": "./script/clippy",
"args": []
+ },
+ {
+ "label": "cargo run --profile release-fast",
+ "command": "cargo",
+ "args": ["run", "--profile", "release-fast"]
}
]
diff --git a/Cargo.lock b/Cargo.lock
index e03250a23bf94da908a59343e42267092a475e94..402953d95fb1bf672992c9d3da9a631c53f02e38 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -358,6 +358,7 @@ dependencies = [
"clock",
"collections",
"command_palette_hooks",
+ "context_servers",
"ctor",
"db",
"editor",
@@ -366,6 +367,7 @@ dependencies = [
"fs",
"futures 0.3.30",
"fuzzy",
+ "globset",
"gpui",
"handlebars",
"heed",
@@ -397,6 +399,7 @@ dependencies = [
"serde_json",
"serde_json_lenient",
"settings",
+ "similar",
"smallvec",
"smol",
"telemetry_events",
@@ -791,9 +794,9 @@ dependencies = [
[[package]]
name = "async-stripe"
-version = "0.37.3"
+version = "0.38.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2f14b5943a52cf051bbbbb68538e93a69d1e291934174121e769f4b181113f5"
+checksum = "97ddaa6999d246ba2c6c84d830a1ba0cd16c9234d58701988b3869f0e5bd732d"
dependencies = [
"chrono",
"futures-util",
@@ -983,9 +986,9 @@ dependencies = [
[[package]]
name = "aws-config"
-version = "1.5.4"
+version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "caf6cfe2881cb1fcbba9ae946fb9a6480d3b7a714ca84c74925014a89ef3387a"
+checksum = "4e95816a168520d72c0e7680c405a5a8c1fb6a035b4bc4b9d7b0de8e1a941697"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1003,7 +1006,6 @@ dependencies = [
"fastrand 2.1.0",
"hex",
"http 0.2.12",
- "hyper",
"ring",
"time",
"tokio",
@@ -1026,9 +1028,9 @@ dependencies = [
[[package]]
name = "aws-runtime"
-version = "1.3.1"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87c5f920ffd1e0526ec9e70e50bf444db50b204395a0fa7016bbf9e31ea1698f"
+checksum = "f42c2d4218de4dcd890a109461e2f799a1a2ba3bcd2cde9af88360f5df9266c6"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -1042,6 +1044,7 @@ dependencies = [
"fastrand 2.1.0",
"http 0.2.12",
"http-body 0.4.6",
+ "once_cell",
"percent-encoding",
"pin-project-lite",
"tracing",
@@ -1050,9 +1053,9 @@ dependencies = [
[[package]]
name = "aws-sdk-s3"
-version = "1.42.0"
+version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "558bbcec8db82a1a8af1610afcb3b10d00652d25ad366a0558eecdff2400a1d1"
+checksum = "9ccda7e730ace3cb8bbd4071bc650c6d294364891f9564bd4e43adfc8dea3177"
dependencies = [
"ahash 0.8.11",
"aws-credential-types",
@@ -1085,9 +1088,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
-version = "1.36.0"
+version = "1.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6acca681c53374bf1d9af0e317a41d12a44902ca0f2d1e10e5cb5bb98ed74f35"
+checksum = "1074e818fbe4f9169242d78448b15be8916a79daa38ea1231f2e2e10d993fcd2"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1107,9 +1110,9 @@ dependencies = [
[[package]]
name = "aws-sdk-ssooidc"
-version = "1.37.0"
+version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b79c6bdfe612503a526059c05c9ccccbf6bd9530b003673cb863e547fd7c0c9a"
+checksum = "29755c51e33fa3f678598f64324a169cf4b7d3c4865d2709d4308f53366a92a4"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1129,9 +1132,9 @@ dependencies = [
[[package]]
name = "aws-sdk-sts"
-version = "1.36.0"
+version = "1.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32e6ecdb2bd756f3b2383e6f0588dc10a4e65f5d551e70a56e0bfe0c884673ce"
+checksum = "6e52dc3fd7dfa6c01a69cf3903e00aa467261639138a05b06cd92314d2c8fb07"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -1192,9 +1195,9 @@ dependencies = [
[[package]]
name = "aws-smithy-checksums"
-version = "0.60.11"
+version = "0.60.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48c4134cf3adaeacff34d588dbe814200357b0c466d730cf1c0d8054384a2de4"
+checksum = "598b1689d001c4d4dc3cb386adb07d37786783aee3ac4b324bcadac116bf3d23"
dependencies = [
"aws-smithy-http",
"aws-smithy-types",
@@ -1291,9 +1294,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime-api"
-version = "1.7.1"
+version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30819352ed0a04ecf6a2f3477e344d2d1ba33d43e0f09ad9047c12e0d923616f"
+checksum = "e086682a53d3aa241192aa110fa8dfce98f2f5ac2ead0de84d41582c7e8fdb96"
dependencies = [
"aws-smithy-async",
"aws-smithy-types",
@@ -2206,9 +2209,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.13"
+version = "4.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
+checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc"
dependencies = [
"clap_builder",
"clap_derive",
@@ -2216,9 +2219,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.13"
+version = "4.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
+checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
dependencies = [
"anstream",
"anstyle",
@@ -2668,6 +2671,27 @@ dependencies = [
"tiny-keccak",
]
+[[package]]
+name = "context_servers"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "collections",
+ "futures 0.3.30",
+ "gpui",
+ "log",
+ "parking_lot",
+ "postage",
+ "schemars",
+ "serde",
+ "serde_json",
+ "settings",
+ "smol",
+ "url",
+ "util",
+ "workspace",
+]
+
[[package]]
name = "convert_case"
version = "0.4.0"
@@ -2736,9 +2760,9 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
-version = "0.8.6"
+version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "core-graphics"
@@ -3141,12 +3165,12 @@ dependencies = [
[[package]]
name = "ctrlc"
-version = "3.4.4"
+version = "3.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345"
+checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
dependencies = [
- "nix 0.28.0",
- "windows-sys 0.52.0",
+ "nix 0.29.0",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -5033,9 +5057,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "heed"
-version = "0.20.3"
+version = "0.20.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bc30da4a93ff8cb98e535d595d6de42731d4719d707bc1c86f579158751a24e"
+checksum = "620033c8c8edfd2f53e6f99a30565eb56a33b42c468e3ad80e21d85fb93bafb0"
dependencies = [
"bitflags 2.6.0",
"byteorder",
@@ -6220,18 +6244,18 @@ dependencies = [
[[package]]
name = "linkme"
-version = "0.3.27"
+version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccb76662d78edc9f9bf56360d6919bdacc8b7761227727e5082f128eeb90bbf5"
+checksum = "3c943daedff228392b791b33bba32e75737756e80a613e32e246c6ce9cbab20a"
dependencies = [
"linkme-impl",
]
[[package]]
name = "linkme-impl"
-version = "0.3.27"
+version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8dccda732e04fa3baf2e17cf835bfe2601c7c2edafd64417c627dabae3a8cda"
+checksum = "cb26336e6dc7cc76e7927d2c9e7e3bb376d7af65a6f56a0b16c47d18a9b1abc5"
dependencies = [
"proc-macro2",
"quote",
@@ -6290,9 +6314,9 @@ dependencies = [
[[package]]
name = "lmdb-master-sys"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57640c190703d5ccf4a86aff4aeb749b2d287a8cb1723c76b51f39d77ab53b24"
+checksum = "1de7e761853c15ca72821d9f928d7bb123ef4c05377c4e7ab69fa1c742f91d24"
dependencies = [
"cc",
"doxygen-rs",
@@ -9533,18 +9557,18 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.204"
+version = "1.0.207"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
+checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.204"
+version = "1.0.207"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
+checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e"
dependencies = [
"proc-macro2",
"quote",
@@ -9584,9 +9608,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.122"
+version = "1.0.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da"
+checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed"
dependencies = [
"indexmap 2.3.0",
"itoa",
@@ -9920,6 +9944,13 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "slash_commands_example"
+version = "0.1.0"
+dependencies = [
+ "zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
[[package]]
name = "slice-group-by"
version = "0.3.1"
@@ -13783,7 +13814,7 @@ dependencies = [
[[package]]
name = "zed"
-version = "0.149.0"
+version = "0.150.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -13977,12 +14008,23 @@ dependencies = [
"wit-bindgen",
]
+[[package]]
+name = "zed_extension_api"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "594fd10dd0f2f853eb243e2425e7c95938cef49adb81d9602921d002c5e6d9d9"
+dependencies = [
+ "serde",
+ "serde_json",
+ "wit-bindgen",
+]
+
[[package]]
name = "zed_gleam"
-version = "0.1.3"
+version = "0.2.0"
dependencies = [
"html_to_markdown 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "zed_extension_api 0.1.0",
+ "zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -14108,9 +14150,9 @@ dependencies = [
[[package]]
name = "zed_zig"
-version = "0.1.5"
+version = "0.2.0"
dependencies = [
- "zed_extension_api 0.0.6",
+ "zed_extension_api 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 740aaa9c1ab255341b536123b93e9d65b5f15f66..329688b34b946f793e81f26d20b0ccf5e2ff3fdf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,6 +19,7 @@ members = [
"crates/collections",
"crates/command_palette",
"crates/command_palette_hooks",
+ "crates/context_servers",
"crates/copilot",
"crates/db",
"crates/dev_server_projects",
@@ -148,6 +149,7 @@ members = [
"extensions/purescript",
"extensions/ruff",
"extensions/ruby",
+ "extensions/slash-commands-example",
"extensions/snippets",
"extensions/svelte",
"extensions/terraform",
@@ -189,6 +191,7 @@ collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections" }
command_palette = { path = "crates/command_palette" }
command_palette_hooks = { path = "crates/command_palette_hooks" }
+context_servers = { path = "crates/context_servers" }
copilot = { path = "crates/copilot" }
db = { path = "crates/db" }
dev_server_projects = { path = "crates/dev_server_projects" }
@@ -461,7 +464,7 @@ which = "6.0.0"
wit-component = "0.201"
[workspace.dependencies.async-stripe]
-version = "0.37"
+version = "0.38"
default-features = false
features = [
"runtime-tokio-hyper-rustls",
diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json
index e0236c856340595756745afbadaf9f300769cae6..4d588a4c8f332c7f6a843bc797ed7ba1cc673049 100644
--- a/assets/keymaps/default-macos.json
+++ b/assets/keymaps/default-macos.json
@@ -89,9 +89,9 @@
"alt-right": "editor::MoveToNextWordEnd",
"alt-f": "editor::MoveToNextWordEnd",
"cmd-left": "editor::MoveToBeginningOfLine",
- "ctrl-a": ["editor::MoveToBeginningOfLine", { "stop_at_soft_wraps": false }],
+ "ctrl-a": "editor::MoveToBeginningOfLine",
"cmd-right": "editor::MoveToEndOfLine",
- "ctrl-e": ["editor::MoveToEndOfLine", { "stop_at_soft_wraps": false }],
+ "ctrl-e": "editor::MoveToEndOfLine",
"cmd-up": "editor::MoveToBeginning",
"cmd-down": "editor::MoveToEnd",
"shift-up": "editor::SelectUp",
diff --git a/assets/prompts/content_prompt.hbs b/assets/prompts/content_prompt.hbs
index 107b6be0425d549a53279f1821926d37d526802b..cd618a67613e54f63796b62f0b3e3386d258af19 100644
--- a/assets/prompts/content_prompt.hbs
+++ b/assets/prompts/content_prompt.hbs
@@ -1,49 +1,423 @@
-Here's a text file that I'm going to ask you to make an edit to.
+You are an expert developer assistant working in an AI-enabled text editor.
+Your task is to rewrite a specific section of the provided document based on a user-provided prompt.
-{{#if language_name}}
-The file is in {{language_name}}.
-{{/if}}
+
+1. Scope: Modify only content within tags. Do not alter anything outside these boundaries.
+2. Precision: Make changes strictly necessary to fulfill the given prompt. Preserve all other content as-is.
+3. Seamless integration: Ensure rewritten sections flow naturally with surrounding text and maintain document structure.
+4. Tag exclusion: Never include , , , or tags in the output.
+5. Indentation: Maintain the original indentation level of the file in rewritten sections.
+6. Completeness: Rewrite the entire tagged section, even if only partial changes are needed. Avoid omissions or elisions.
+7. Insertions: Replace tags with appropriate content as specified by the prompt.
+8. Code integrity: Respect existing code structure and functionality when making changes.
+9. Consistency: Maintain a uniform style and tone throughout the rewritten text.
+
-You need to rewrite a portion of it.
+
+
+
+
+use std::cell::Cell;
+use std::collections::HashMap;
+use std::cmp;
-The section you'll need to edit is marked with tags.
+
+
+
+pub struct LruCache {
+ /// The maximum number of items the cache can hold.
+ capacity: usize,
+ /// The map storing the cached items.
+ items: HashMap,
+}
+// The rest of the implementation...
+
+
+doc this
+
+
+
+
+/// Represents an Axis-Aligned Bounding Box (AABB) tree data structure.
+///
+/// This structure is used for efficient spatial queries and collision detection.
+/// It organizes objects in a hierarchical tree structure based on their bounding boxes.
+///
+/// # Type Parameters
+///
+/// * `T`: The type of data associated with each node in the tree.
+pub struct AabbTree {
+ root: Option,
+
+
+/// Represents an Axis-Aligned Bounding Box (AABB) tree data structure.
+///
+/// This structure is used for efficient spatial queries and collision detection.
+/// It organizes objects in a hierarchical tree structure based on their bounding boxes.
+///
+/// # Type Parameters
+///
+/// * `T`: The type of data associated with each node in the tree.
+
+
+
+
+
-{{{document_content}}}
+import math
+
+def calculate_circle_area(radius):
+ """Calculate the area of a circle given its radius."""
+ return math.pi * radius ** 2
+
+
+
+
+class Circle:
+ def __init__(self, radius):
+ self.radius = radius
+
+ def area(self):
+ return math.pi * self.radius ** 2
+
+ def circumference(self):
+ return 2 * math.pi * self.radius
+
+# Usage example
+circle = Circle(5)
+print(f"Area: {circle.area():.2f}")
+print(f"Circumference: {circle.circumference():.2f}")
+
+write docs
+
+
-{{#if is_truncated}}
-The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.
-{{/if}}
+
+"""
+Represents a circle with methods to calculate its area and circumference.
+
+This class provides a simple way to work with circles in a geometric context.
+It allows for the creation of Circle objects with a specified radius and
+offers methods to compute the circle's area and circumference.
+
+Attributes:
+ radius (float): The radius of the circle.
-Rewrite the section of {{content_type}} in tags based on the following prompt:
+Methods:
+ area(): Calculates and returns the area of the circle.
+ circumference(): Calculates and returns the circumference of the circle.
+"""
+class Circle:
+
+
+"""
+Represents a circle with methods to calculate its area and circumference.
+This class provides a simple way to work with circles in a geometric context.
+It allows for the creation of Circle objects with a specified radius and
+offers methods to compute the circle's area and circumference.
+
+Attributes:
+ radius (float): The radius of the circle.
+
+Methods:
+ area(): Calculates and returns the area of the circle.
+ circumference(): Calculates and returns the circumference of the circle.
+"""
+
+
+
+
+
+
+class BankAccount {
+ private balance: number;
+
+ constructor(initialBalance: number) {
+ this.balance = initialBalance;
+ }
+
+
+
+
+ deposit(amount: number): void {
+ if (amount > 0) {
+ this.balance += amount;
+ }
+ }
+
+ withdraw(amount: number): boolean {
+ if (amount > 0 && this.balance >= amount) {
+ this.balance -= amount;
+ return true;
+ }
+ return false;
+ }
+
+ getBalance(): number {
+ return this.balance;
+ }
+}
+
+// Usage
+const account = new BankAccount(1000);
+account.deposit(500);
+console.log(account.getBalance()); // 1500
+account.withdraw(200);
+console.log(account.getBalance()); // 1300
+
-{{{user_prompt}}}
+//
+
-Here's the section to edit based on that prompt again for reference:
+
+ /**
+ * Deposits the specified amount into the bank account.
+ *
+ * @param amount The amount to deposit. Must be a positive number.
+ * @throws Error if the amount is not positive.
+ */
+ deposit(amount: number): void {
+ if (amount > 0) {
+ this.balance += amount;
+ } else {
+ throw new Error("Deposit amount must be positive");
+ }
+ }
+
+
+ /**
+ * Deposits the specified amount into the bank account.
+ *
+ * @param amount The amount to deposit. Must be a positive number.
+ * @throws Error if the amount is not positive.
+ */
+
+
+
+
+
+
+use std::collections::VecDeque;
+
+pub struct BinaryTree {
+ root: Option>,
+}
-{{{rewrite_section}}}
+
+struct Node {
+ value: T,
+ left: Option>>,
+ right: Option>>,
+}
+
+
+derive clone
+
+
+
+
+#[derive(Clone)]
+
+struct Node {
+ value: T,
+ left: Option>>,
+ right: Option>>,
+}
+
+
+
+pub struct BinaryTree {
+ root: Option>,
+}
+
+#[derive(Clone)]
+
+
+
+#[derive(Clone)]
+struct Node {
+ value: T,
+ left: Option>>,
+ right: Option>>,
+}
-You'll rewrite this entire section, but you will only make changes within certain subsections.
+impl Node {
+ fn new(value: T) -> Self {
+ Node {
+ value,
+ left: None,
+ right: None,
+ }
+ }
+}
+
+
+#[derive(Clone)]
+
+
+
+
+
+import math
+
+def calculate_circle_area(radius):
+ """Calculate the area of a circle given its radius."""
+ return math.pi * radius ** 2
+
+
+
+
+class Circle:
+ def __init__(self, radius):
+ self.radius = radius
+
+ def area(self):
+ return math.pi * self.radius ** 2
+
+ def circumference(self):
+ return 2 * math.pi * self.radius
+
+# Usage example
+circle = Circle(5)
+print(f"Area: {circle.area():.2f}")
+print(f"Circumference: {circle.circumference():.2f}")
+
+
+add dataclass decorator
+
+
+
+
+@dataclass
+class Circle:
+ radius: float
+
+ def __init__(self, radius):
+ self.radius = radius
+
+ def area(self):
+ return math.pi * self.radius ** 2
+
+
+@dataclass
+
+
+
+
+
+
+interface ShoppingCart {
+ items: string[];
+ total: number;
+}
+
+
+class ShoppingCartManager {
+
+ private cart: ShoppingCart;
+
+ constructor() {
+ this.cart = { items: [], total: 0 };
+ }
+
+ addItem(item: string, price: number): void {
+ this.cart.items.push(item);
+ this.cart.total += price;
+ }
+
+ getTotal(): number {
+ return this.cart.total;
+ }
+}
+
+// Usage
+const manager = new ShoppingCartManager();
+manager.addItem("Book", 15.99);
+console.log(manager.getTotal()); // 15.99
+
+
+add readonly modifier
+
+
+
+
+readonly interface ShoppingCart {
+ items: string[];
+ total: number;
+}
+
+class ShoppingCartManager {
+ private readonly cart: ShoppingCart;
+
+ constructor() {
+ this.cart = { items: [], total: 0 };
+ }
+
+
+readonly interface ShoppingCart {
+
+
+
+
+
+With these examples in mind, edit the following file:
+
+
+{{{ document_content }}}
+
+
+{{#if is_truncated}}
+The provided document has been truncated (potentially mid-line) for brevity.
+{{/if}}
+
+
{{#if has_insertion}}
-Insert text anywhere you see it marked with with tags. Do not include tags in your output.
+Insert text anywhere you see marked with tags. It's CRITICAL that you DO NOT include tags in your output.
{{/if}}
{{#if has_replacement}}
-Edit edit text that you see surrounded with tags. Do not include tags in your output.
+Edit text that you see surrounded with ... tags. It's CRITICAL that you DO NOT include tags in your output.
{{/if}}
+Make no changes to the rewritten content outside these tags.
+
+{{{ rewrite_section_prefix }}}
-{{{rewrite_section_with_selections}}}
+{{{ rewrite_section_with_edits }}}
+{{{ rewrite_section_suffix }}}
+
+
+Rewrite the lines enclosed within the tags in accordance with the provided instructions and the prompt below.
+
+
+{{{ user_prompt }}}
+
+
+Do not include or annotations in your output. Here is a clean copy of the snippet without annotations for your reference.
-Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding {{content_type}} will be preserved. Do not output the tags or anything outside of them.
+
+{{{ rewrite_section_prefix }}}
+{{{ rewrite_section }}}
+{{{ rewrite_section_suffix }}}
+
+
-Start at the indentation level in the original file in the rewritten {{content_type}}. Don't stop until you've rewritten the entire section, even if you have no more changes to make. Always write out the whole section with no unnecessary elisions.
+
+1. Focus on necessary changes: Modify only what's required to fulfill the prompt.
+2. Preserve context: Maintain all surrounding content as-is, ensuring the rewritten section seamlessly integrates with the existing document structure and flow.
+3. Exclude annotation tags: Do not output , , , or tags.
+4. Maintain indentation: Begin at the original file's indentation level.
+5. Complete rewrite: Continue until the entire section is rewritten, even if no further changes are needed.
+6. Avoid elisions: Always write out the full section without unnecessary omissions. NEVER say `// ...` or `// ...existing code` in your output.
+7. Respect content boundaries: Preserve code integrity.
+
Immediately start with the following format with no remarks:
diff --git a/assets/prompts/edit_workflow.hbs b/assets/prompts/edit_workflow.hbs
index 4675aa921bcfc32f23e88c146a4d5ffbbd3310c6..7abf79d20d6d6ed69da80b4010b408c32006f034 100644
--- a/assets/prompts/edit_workflow.hbs
+++ b/assets/prompts/edit_workflow.hbs
@@ -1,87 +1,302 @@
+
Guide the user through code changes in numbered steps that focus on individual functions, type definitions, etc.
-Surround each distinct step in a XML tag.
+Surround each distinct step in a XML tag. The user will be performing these steps in a code editor
+named Zed, which is where they will have entered this prompt and will be seeing the response.
-Here's an example of a user-assistant dialog with step numbers and a follow-up question in Rust:
+
+- Use the language of the file for code fence blocks unless otherwise specified.
+- Include a code or file action in each step.
+- Only put code in separate steps if it should either go in separate files, or in different (non-contiguous) places in the same file.
+- Provide error handling and input validation where appropriate.
+- Adapt explanations based on the user's perceived level of expertise.
+- Include comments in code examples to enhance understanding.
+- Consider more complex programming scenarios when relevant.
+- Avoid using the terminal to perform filesystem operations such as creating, deleting, renaming, and editing files. Assume the user prefers to use Zed for these operations, unless the prompt suggests that the user is expecting terminal commands.
+- When creating a new file, Zed will automatically create any necessary directories in that file's path. So when a step will create a new file in Zed, don't mention needing to create the directory that it will go in.
+- Assume the user is performing these actions in the Zed code editor, so avoid redundancies like "In your code editor, ..." or "In Zed, ..."
+- Be concise without leaving out important information.
+
-1. User: I want to create a User struct with name and age fields, and a method to print the user's information. Can you help me with that?
-A: Certainly! Let's create a User struct with the specified fields and a method to print the information:
-Define the User struct
+Examples of user-assistant dialogs with step numbers and user follow-up requests:
+
+
+
+Create a Library struct with a vector of Book structs. Include methods to add books and find books by author. Use generics for the book ID.
+Certainly! Let's create a Library struct with a vector of Book structs, including methods to add books and find books by author, using generics for the book ID:
+
+Define the Book and Library structs
```rust
-struct User {
- name: String,
- age: u32,
+use std::fmt::Display;
+
+struct Book
+where
+ T: Display + Clone
+{
+ id: T,
+ title: String,
+ author: String,
+}
+
+struct Library
+where
+ T: Display + Clone
+{
+ books: Vec>,
}
```
-Implement methods for the User struct
+Implement methods for the Library struct
```rust
-impl User {
- fn new(name: String, age: u32) -> Self {
- User { name, age }
+impl Library
+where
+ T: Display + Clone
+{
+ fn new() -> Self {
+ Library { books: Vec::new() }
+ }
+
+ fn add_book(&mut self, id: T, title: String, author: String) {
+ let book = Book { id, title, author };
+ self.books.push(book);
}
- fn print_info(&self) {
- println!("Name: {}, Age: {}", self.name, self.age);
+
+ fn find_books_by_author(&self, author: &str) -> Vec<&Book> {
+ self.books
+ .iter()
+ .filter(|book| book.author.to_lowercase() == author.to_lowercase())
+ .collect()
}
}
```
-This code defines a User struct and implements two methods:
-- `new`: a constructor to create a new User
-- `print_info`: a method to print the user's information
+This code defines:
+1. A generic Book struct with an ID of type T, title, and author
+2. A Library struct containing a vector of Books
+3. Methods to create a new Library, add books, and find books by author
+The use of generics allows flexibility in the type of ID used for books.
+
-2. User: Great! Now, how can I create a User instance and call the print_info method?
-A: You can create a User instance and call the print_info method like this:
+
+How can I use this Library struct in a main function?
+Here's how you can use the Library struct in a main function:
-Create a User instance and call print_info
+Create a main function to demonstrate Library usage
```rust
fn main() {
- let user = User::new(String::from("Alice"), 30);
- user.print_info();
+ // Create a new Library with string IDs
+ let mut library = Library::new();
+
+ // Add some books
+ library.add_book(String::from("001"), String::from("The Rust Programming Language"), String::from("Steve Klabnik"));
+ library.add_book(String::from("002"), String::from("Programming Rust"), String::from("Jim Blandy"));
+ library.add_book(String::from("003"), String::from("Rust in Action"), String::from("Tim McNamara"));
+
+ // Find books by author
+ let author = "Steve Klabnik";
+ let books = library.find_books_by_author(author);
+
+ println!("Books by {}:", author);
+ for book in books {
+ println!("ID: {}, Title: {}", book.id, book.title);
+ }
}
```
-This will output: `Name: Alice, Age: 30`
+This main function demonstrates:
+1. Creating a new Library instance
+2. Adding books with string IDs
+3. Finding and displaying books by a specific author
+
-3. User: Can you add an email field to the User struct and update the print_info method to include it?
-A: Certainly! Let's modify the User struct and update the print_info method:
+
+Can you add a method to remove a book by ID and handle potential errors?
+Certainly! Let's add a method to remove a book by ID and handle potential errors:
-Add email field to User struct
+Add a remove_book method to the Library struct
```rust
-struct User {
- name: String,
- age: u32,
- email: String,
+use std::fmt::Display;
+use std::error::Error;
+use std::fmt;
+
+#[derive(Debug)]
+struct BookNotFoundError;
+
+impl Error for BookNotFoundError {}
+
+impl fmt::Display for BookNotFoundError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Book not found")
+ }
+}
+
+impl Library
+where
+ T: Display + Clone + PartialEq
+{
+ // ... (previous methods remain the same)
+
+ fn remove_book(&mut self, id: &T) -> Result, BookNotFoundError> {
+ if let Some(index) = self.books.iter().position(|book| &book.id == id) {
+ Ok(self.books.remove(index))
+ } else {
+ Err(BookNotFoundError)
+ }
+ }
}
```
-
-Update the new method and print_info method
+Update the main function to demonstrate book removal
```rust
-impl User {
- fn new(name: String, age: u32, email: String) -> Self {
- User { name, age, email }
+fn main() {
+ // ... (previous code remains the same)
+
+ // Attempt to remove a book
+ let id_to_remove = String::from("002");
+ match library.remove_book(&id_to_remove) {
+ Ok(removed_book) => println!("Removed book: {} by {}", removed_book.title, removed_book.author),
+ Err(e) => println!("Error removing book: {}", e),
}
- fn print_info(&self) {
- println!("Name: {}, Age: {}, Email: {}", self.name, self.age, self.email);
+
+ // Attempt to remove a non-existent book
+ let non_existent_id = String::from("999");
+ match library.remove_book(&non_existent_id) {
+ Ok(_) => println!("Book removed successfully"),
+ Err(e) => println!("Error removing book: {}", e),
+ }
+
+ // Display remaining books
+ println!("\nRemaining books:");
+ for book in &library.books {
+ println!("ID: {}, Title: {} by {}", book.id, book.title, book.author);
}
}
```
-Update the main function
-```rust
-fn main() {
- let user = User::new(String::from("Alice"), 30, String::from("alice@example.com"));
- user.print_info();
+This update includes:
+1. A new remove_book method that returns a Result type
+2. A custom BookNotFoundError for error handling
+3. Demonstration of successful book removal and error handling for non-existent books
+4. Display of remaining books after removal attempts
+
+The remove_book method uses the PartialEq trait to compare book IDs, allowing for flexible ID types while maintaining type safety.
+
+
+
+
+
+Create a Person struct with name, age, and email fields, and a method to greet. Include error handling for age.
+Certainly! Let's create a Person struct with the specified fields and a method to greet in Go:
+
+Define the Person struct and NewPerson function with error handling
+```go
+type Person struct {
+ Name string
+ Age int
+ Email string
+}
+
+func NewPerson(name string, age int, email string) (*Person, error) {
+ if age < 0 || age > 150 {
+ return nil, fmt.Errorf("invalid age: %d", age)
+ }
+ return &Person{Name: name, Age: age, Email: email}, nil
+}
+```
+
+Implement the Greet method for the Person struct
+```go
+func (p *Person) Greet() string {
+ return fmt.Sprintf("Hello, I'm %s, %d years old. You can reach me at %s.", p.Name, p.Age, p.Email)
+}
+```
+
+
+This code defines a Person struct, a NewPerson function with age validation, and a Greet method.
+
+
+
+How can I use these in a main function?
+Here's how to use the Person struct and its methods in a main function:
+
+Create a main function with error handling
+```go
+func main() {
+ // Create a new Person instance
+ person, err := NewPerson("Alice", 30, "alice@example.com")
+ if err != nil {
+ fmt.Println("Error creating person:", err)
+ return
+ }
+
+ // Use the Greet method
+ fmt.Println(person.Greet())
+
+ // Demonstrate error handling with invalid age
+ invalidPerson, err := NewPerson("Bob", 200, "bob@example.com")
+ if err != nil {
+ fmt.Println("Error creating person with invalid age:", err)
+ }
+}
+```
+
+
+This main function demonstrates:
+1. Creating a valid Person instance
+2. Using the Greet method
+3. Handling errors when creating a Person with an invalid age
+
+
+
+Can you add a method to update the email with validation?
+Certainly! Let's add a method to update the email with validation:
+
+Add UpdateEmail method with validation
+```go
+import (
+ "fmt"
+ "regexp"
+)
+
+func (p *Person) UpdateEmail(newEmail string) error {
+ // Simple email validation using regex
+ emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
+ if !emailRegex.MatchString(newEmail) {
+ return fmt.Errorf("invalid email format: %s", newEmail)
+ }
+ p.Email = newEmail
+ return nil
+}
+```
+
+Update the main function to demonstrate email update
+```go
+func main() {
+ // ... (previous code remains the same)
+
+ // Demonstrate email update
+ err = person.UpdateEmail("alice.new@example.com")
+ if err != nil {
+ fmt.Println("Error updating email:", err)
+ } else {
+ fmt.Println("Updated greeting:", person.Greet())
+ }
+
+ // Demonstrate email update with invalid email
+ err = person.UpdateEmail("invalid-email")
+ if err != nil {
+ fmt.Println("Error updating email:", err)
+ }
}
```
-This will now output: `Name: Alice, Age: 30, Email: alice@example.com`
-The changes include:
-1. Adding the `email` field to the User struct
-2. Updating the `new` method to accept an email parameter
-3. Modifying the `print_info` method to include the email
-4. Updating the main function to provide an email when creating a User instance
+This update includes:
+1. An UpdateEmail method with email format validation
+2. Demonstration of successful email update in the main function
+3. Handling of invalid email update attempt
+
+
+
diff --git a/assets/prompts/step_resolution.hbs b/assets/prompts/step_resolution.hbs
index 74bc43e078129487db042a0df4ba5ccf85ed30b2..523584dafc4b1ef1bf189cc800ec2493c4420b99 100644
--- a/assets/prompts/step_resolution.hbs
+++ b/assets/prompts/step_resolution.hbs
@@ -1,22 +1,27 @@
-Your task is to map a step from the conversation above to suggestions on symbols inside the provided source files.
+
+Your task is to map a step from a workflow to locations in source code where code needs to be changed to fulfill that step.
+Given a workflow containing background context plus a series of tags, you will resolve *one* of these step tags to resolve to one or more locations in the code.
+With each location, you will produce a brief, one-line description of the changes to be made.
-Guidelines:
+
- There's no need to describe *what* to do, just *where* to do it.
+- Only reference locations that actually exist (unless you're creating a file).
- If creating a file, assume any subsequent updates are included at the time of creation.
-- Don't create and then update a file.
-- We'll create it in one shot.
+- Don't create and then update a file. Always create new files in shot.
- Prefer updating symbols lower in the syntax tree if possible.
- Never include suggestions on a parent symbol and one of its children in the same suggestions block.
- Never nest an operation with another operation or include CDATA or other content. All suggestions are leaf nodes.
-- Include a description attribute for each operation with a brief, one-line description of the change to perform.
- Descriptions are required for all suggestions except delete.
- When generating multiple suggestions, ensure the descriptions are specific to each individual operation.
- Avoid referring to the location in the description. Focus on the change to be made, not the location where it's made. That's implicit with the symbol you provide.
- Don't generate multiple suggestions at the same location. Instead, combine them together in a single operation with a succinct combined description.
+
+
-Example 1:
-
-User:
+
+
+
+
```rs src/rectangle.rs
struct Rectangle {
width: f64,
@@ -30,12 +35,21 @@ impl Rectangle {
}
```
+We need to add methods to calculate the area and perimeter of the rectangle. Can you help with that?
+
+
+Sure, I can help with that!
+
Add new methods 'calculate_area' and 'calculate_perimeter' to the Rectangle struct
Implement the 'Display' trait for the Rectangle struct
+
+
-What are the suggestions for the step: Add a new method 'calculate_area' to the Rectangle struct
+
+Add new methods 'calculate_area' and 'calculate_perimeter' to the Rectangle struct
+
-A (wrong):
+
{
"title": "Add Rectangle methods",
"suggestions": [
@@ -53,10 +67,9 @@ A (wrong):
}
]
}
+
-This demonstrates what NOT to do. NEVER append multiple children at the same location.
-
-A (corrected):
+
{
"title": "Add Rectangle methods",
"suggestions": [
@@ -68,11 +81,13 @@ A (corrected):
}
]
}
+
-User:
-What are the suggestions for the step: Implement the 'Display' trait for the Rectangle struct
+
+Implement the 'Display' trait for the Rectangle struct
+
-A:
+
-Example 2:
-
-User:
+
+
+
```rs src/user.rs
struct User {
pub name: String,
@@ -105,13 +121,19 @@ impl User {
}
}
```
-
+
+
+Certainly!
Update the 'print_info' method to use formatted output
Remove the 'email' field from the User struct
+
+
-What are the suggestions for the step: Update the 'print_info' method to use formatted output
+
+Update the 'print_info' method to use formatted output
+
-A:
+
-User:
-What are the suggestions for the step: Remove the 'email' field from the User struct
+
+Remove the 'email' field from the User struct
+
-A:
+
+
-Example 3:
-
-User:
+
+
+
```rs src/vehicle.rs
struct Vehicle {
make: String,
@@ -159,13 +185,18 @@ impl Vehicle {
}
}
```
-
+
+
Add a 'use std::fmt;' statement at the beginning of the file
Add a new method 'start_engine' in the Vehicle impl block
+
+
-What are the suggestions for the step: Add a 'use std::fmt;' statement at the beginning of the file
+
+Add a 'use std::fmt;' statement at the beginning of the file
+
-A:
+
-User:
-What are the suggestions for the step: Add a new method 'start_engine' in the Vehicle impl block
+
+Add a new method 'start_engine' in the Vehicle impl block
+
-A:
+
+
-Example 4:
-
-User:
+
+
+
```rs src/employee.rs
struct Employee {
name: String,
@@ -219,12 +254,18 @@ impl Employee {
}
}
```
-
+
+
Make salary an f32
+Remove the 'department' field and update the 'print_details' method
+
+
-What are the suggestions for the step: Make salary an f32
+
+Make salary an f32
+
-A (wrong):
+
{
"title": "Change salary to f32",
"suggestions": [
@@ -242,10 +283,9 @@ A (wrong):
}
]
}
+
-This example demonstrates what not to do. `struct Employee salary` is a child of `struct Employee`.
-
-A (corrected):
+
{
"title": "Change salary to f32",
"suggestions": [
@@ -257,11 +297,13 @@ A (corrected):
}
]
}
+
-User:
-What are the correct suggestions for the step: Remove the 'department' field and update the 'print_details' method
+
+Remove the 'department' field and update the 'print_details' method
+
-A:
+
+
-Example 5:
-
-User:
+
+
+
```rs src/game.rs
struct Player {
name: String,
@@ -305,10 +349,17 @@ impl Game {
}
}
```
-
+
+
Add a 'level' field to Player and update the 'new' method
+
+
+
+
+Add a 'level' field to Player and update the 'new' method
+
-A:
+
+
-Example 6:
-
-User:
+
+
+
```rs src/config.rs
use std::collections::HashMap;
@@ -343,10 +396,17 @@ impl Config {
}
}
```
-
+
+
Add a 'load_from_file' method to Config and import necessary modules
+
+
+
+
+Add a 'load_from_file' method to Config and import necessary modules
+
-A:
+
+
-Example 7:
-
-User:
+
+
+
```rs src/database.rs
pub(crate) struct Database {
connection: Connection,
@@ -383,10 +445,17 @@ impl Database {
}
}
```
-
+
+
Add error handling to the 'query' method and create a custom error type
+
+
+
+
+Add error handling to the 'query' method and create a custom error type
+
-A:
+
+
+
Now generate the suggestions for the following step:
+
+
+{{{workflow_context}}}
+
+
+
+{{{step_to_resolve}}}
+
diff --git a/assets/settings/default.json b/assets/settings/default.json
index 1648237d2d20c82cbf9689eb37093515cc641007..86c73dfefb152a4ccb09f04b82b0983485b4ba4e 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -1010,5 +1010,16 @@
// ]
// }
// ]
- "ssh_connections": null
+ "ssh_connections": null,
+ // Configures the Context Server Protocol binaries
+ //
+ // Examples:
+ // {
+ // "id": "server-1",
+ // "executable": "/path",
+ // "args": ['arg1", "args2"]
+ // }
+ "experimental.context_servers": {
+ "servers": []
+ }
}
diff --git a/crates/anthropic/src/anthropic.rs b/crates/anthropic/src/anthropic.rs
index 0ceee553d21eeb5ac33e5a716ca7d549baf0ae46..e9f0ea51a99562a149960806573cbbd17678e827 100644
--- a/crates/anthropic/src/anthropic.rs
+++ b/crates/anthropic/src/anthropic.rs
@@ -5,8 +5,8 @@ use futures::{io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, S
use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
use isahc::config::Configurable;
use serde::{Deserialize, Serialize};
-use std::str::FromStr;
use std::time::Duration;
+use std::{pin::Pin, str::FromStr};
use strum::{EnumIter, EnumString};
use thiserror::Error;
@@ -14,6 +14,14 @@ pub use supported_countries::*;
pub const ANTHROPIC_API_URL: &'static str = "https://api.anthropic.com";
+#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
+pub struct AnthropicModelCacheConfiguration {
+ pub min_total_token: usize,
+ pub should_speculate: bool,
+ pub max_cache_anchors: usize,
+}
+
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
pub enum Model {
@@ -32,6 +40,9 @@ pub enum Model {
max_tokens: usize,
/// Override this model with a different Anthropic model for tool calls.
tool_override: Option,
+ /// Indicates whether this custom model supports caching.
+ cache_configuration: Option,
+ max_output_tokens: Option,
},
}
@@ -70,6 +81,21 @@ impl Model {
}
}
+ pub fn cache_configuration(&self) -> Option {
+ match self {
+ Self::Claude3_5Sonnet | Self::Claude3Haiku => Some(AnthropicModelCacheConfiguration {
+ min_total_token: 2_048,
+ should_speculate: true,
+ max_cache_anchors: 4,
+ }),
+ Self::Custom {
+ cache_configuration,
+ ..
+ } => cache_configuration.clone(),
+ _ => None,
+ }
+ }
+
pub fn max_token_count(&self) -> usize {
match self {
Self::Claude3_5Sonnet
@@ -80,6 +106,16 @@ impl Model {
}
}
+ pub fn max_output_tokens(&self) -> u32 {
+ match self {
+ Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3Haiku => 4_096,
+ Self::Claude3_5Sonnet => 8_192,
+ Self::Custom {
+ max_output_tokens, ..
+ } => max_output_tokens.unwrap_or(4_096),
+ }
+ }
+
pub fn tool_model_id(&self) -> &str {
if let Self::Custom {
tool_override: Some(tool_override),
@@ -104,7 +140,10 @@ pub async fn complete(
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
- .header("Anthropic-Beta", "tools-2024-04-04")
+ .header(
+ "Anthropic-Beta",
+ "tools-2024-04-04,prompt-caching-2024-07-31,max-tokens-3-5-sonnet-2024-07-15",
+ )
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
@@ -161,7 +200,10 @@ pub async fn stream_completion(
.method(Method::POST)
.uri(uri)
.header("Anthropic-Version", "2023-06-01")
- .header("Anthropic-Beta", "tools-2024-04-04")
+ .header(
+ "Anthropic-Beta",
+ "tools-2024-04-04,prompt-caching-2024-07-31,max-tokens-3-5-sonnet-2024-07-15",
+ )
.header("X-Api-Key", api_key)
.header("Content-Type", "application/json");
if let Some(low_speed_timeout) = low_speed_timeout {
@@ -226,7 +268,7 @@ pub fn extract_text_from_events(
match response {
Ok(response) => match response {
Event::ContentBlockStart { content_block, .. } => match content_block {
- Content::Text { text } => Some(Ok(text)),
+ Content::Text { text, .. } => Some(Ok(text)),
_ => None,
},
Event::ContentBlockDelta { delta, .. } => match delta {
@@ -241,13 +283,69 @@ pub fn extract_text_from_events(
})
}
+pub async fn extract_tool_args_from_events(
+ tool_name: String,
+ mut events: Pin>>>,
+) -> Result>> {
+ let mut tool_use_index = None;
+ while let Some(event) = events.next().await {
+ if let Event::ContentBlockStart {
+ index,
+ content_block,
+ } = event?
+ {
+ if let Content::ToolUse { name, .. } = content_block {
+ if name == tool_name {
+ tool_use_index = Some(index);
+ break;
+ }
+ }
+ }
+ }
+
+ let Some(tool_use_index) = tool_use_index else {
+ return Err(anyhow!("tool not used"));
+ };
+
+ Ok(events.filter_map(move |event| {
+ let result = match event {
+ Err(error) => Some(Err(error)),
+ Ok(Event::ContentBlockDelta { index, delta }) => match delta {
+ ContentDelta::TextDelta { .. } => None,
+ ContentDelta::InputJsonDelta { partial_json } => {
+ if index == tool_use_index {
+ Some(Ok(partial_json))
+ } else {
+ None
+ }
+ }
+ },
+ _ => None,
+ };
+
+ async move { result }
+ }))
+}
+
+#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
+#[serde(rename_all = "lowercase")]
+pub enum CacheControlType {
+ Ephemeral,
+}
+
+#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
+pub struct CacheControl {
+ #[serde(rename = "type")]
+ pub cache_type: CacheControlType,
+}
+
#[derive(Debug, Serialize, Deserialize)]
pub struct Message {
pub role: Role,
pub content: Vec,
}
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum Role {
User,
@@ -258,19 +356,31 @@ pub enum Role {
#[serde(tag = "type")]
pub enum Content {
#[serde(rename = "text")]
- Text { text: String },
+ Text {
+ text: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ cache_control: Option,
+ },
#[serde(rename = "image")]
- Image { source: ImageSource },
+ Image {
+ source: ImageSource,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ cache_control: Option,
+ },
#[serde(rename = "tool_use")]
ToolUse {
id: String,
name: String,
input: serde_json::Value,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ cache_control: Option,
},
#[serde(rename = "tool_result")]
ToolResult {
tool_use_id: String,
content: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ cache_control: Option,
},
}
diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml
index ba39e741e942e743f8b0593466f4ab03982bc0c4..98dfd40c74251352f3a84529afc9d42e586fdcdb 100644
--- a/crates/assistant/Cargo.toml
+++ b/crates/assistant/Cargo.toml
@@ -33,11 +33,13 @@ clock.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
db.workspace = true
+context_servers.workspace = true
editor.workspace = true
feature_flags.workspace = true
fs.workspace = true
futures.workspace = true
fuzzy.workspace = true
+globset.workspace = true
gpui.workspace = true
handlebars.workspace = true
heed.workspace = true
@@ -67,6 +69,7 @@ serde.workspace = true
serde_json.workspace = true
settings.workspace = true
smallvec.workspace = true
+similar.workspace = true
smol.workspace = true
telemetry_events.workspace = true
terminal.workspace = true
diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs
index 28be7e98b97dda4c53973d3f23ba340ef2367351..7b26113ea9538b206203de72f22dda8ee7ccfa97 100644
--- a/crates/assistant/src/assistant.rs
+++ b/crates/assistant/src/assistant.rs
@@ -3,7 +3,6 @@
pub mod assistant_panel;
pub mod assistant_settings;
mod context;
-pub(crate) mod context_inspector;
pub mod context_store;
mod inline_assistant;
mod model_selector;
@@ -13,6 +12,7 @@ mod slash_command;
pub mod slash_command_settings;
mod streaming_diff;
mod terminal_inline_assistant;
+mod workflow;
pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
use assistant_settings::AssistantSettings;
@@ -20,9 +20,11 @@ use assistant_slash_command::SlashCommandRegistry;
use client::{proto, Client};
use command_palette_hooks::CommandPaletteFilter;
pub use context::*;
+use context_servers::ContextServerRegistry;
pub use context_store::*;
use feature_flags::FeatureFlagAppExt;
use fs::Fs;
+use gpui::Context as _;
use gpui::{actions, impl_actions, AppContext, Global, SharedString, UpdateGlobal};
use indexed_docs::IndexedDocsRegistry;
pub(crate) use inline_assistant::*;
@@ -36,13 +38,14 @@ use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsStore};
use slash_command::{
- default_command, diagnostics_command, docs_command, fetch_command, file_command, now_command,
- project_command, prompt_command, search_command, symbols_command, tab_command,
- terminal_command, workflow_command,
+ context_server_command, default_command, diagnostics_command, docs_command, fetch_command,
+ file_command, now_command, project_command, prompt_command, search_command, symbols_command,
+ tab_command, terminal_command, workflow_command,
};
use std::sync::Arc;
pub(crate) use streaming_diff::*;
use util::ResultExt;
+pub use workflow::*;
use crate::slash_command_settings::SlashCommandSettings;
@@ -61,7 +64,6 @@ actions!(
DeployPromptLibrary,
ConfirmCommand,
ToggleModelSelector,
- DebugWorkflowSteps
]
);
@@ -219,6 +221,7 @@ pub fn init(
init_language_model_settings(cx);
assistant_slash_command::init(cx);
assistant_panel::init(cx);
+ context_servers::init(cx);
let prompt_builder = prompts::PromptBuilder::new(Some(PromptOverrideContext {
dev_mode,
@@ -259,9 +262,69 @@ pub fn init(
})
.detach();
+ register_context_server_handlers(cx);
+
prompt_builder
}
+fn register_context_server_handlers(cx: &mut AppContext) {
+ cx.subscribe(
+ &context_servers::manager::ContextServerManager::global(cx),
+ |manager, event, cx| match event {
+ context_servers::manager::Event::ServerStarted { server_id } => {
+ cx.update_model(
+ &manager,
+ |manager: &mut context_servers::manager::ContextServerManager, cx| {
+ let slash_command_registry = SlashCommandRegistry::global(cx);
+ let context_server_registry = ContextServerRegistry::global(cx);
+ if let Some(server) = manager.get_server(server_id) {
+ cx.spawn(|_, _| async move {
+ let Some(protocol) = server.client.read().clone() else {
+ return;
+ };
+
+ if let Some(prompts) = protocol.list_prompts().await.log_err() {
+ for prompt in prompts
+ .into_iter()
+ .filter(context_server_command::acceptable_prompt)
+ {
+ log::info!(
+ "registering context server command: {:?}",
+ prompt.name
+ );
+ context_server_registry.register_command(
+ server.id.clone(),
+ prompt.name.as_str(),
+ );
+ slash_command_registry.register_command(
+ context_server_command::ContextServerSlashCommand::new(
+ &server, prompt,
+ ),
+ true,
+ );
+ }
+ }
+ })
+ .detach();
+ }
+ },
+ );
+ }
+ context_servers::manager::Event::ServerStopped { server_id } => {
+ let slash_command_registry = SlashCommandRegistry::global(cx);
+ let context_server_registry = ContextServerRegistry::global(cx);
+ if let Some(commands) = context_server_registry.get_commands(server_id) {
+ for command_name in commands {
+ slash_command_registry.unregister_command_by_name(&command_name);
+ context_server_registry.unregister_command(&server_id, &command_name);
+ }
+ }
+ }
+ },
+ )
+ .detach();
+}
+
fn init_language_model_settings(cx: &mut AppContext) {
update_active_language_model_from_settings(cx);
diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs
index bfb95b9e5a2541ca47725f5f1f8496a6762b7734..ac2e54ab12103f27a0aa0c53e89a043017e44868 100644
--- a/crates/assistant/src/assistant_panel.rs
+++ b/crates/assistant/src/assistant_panel.rs
@@ -1,6 +1,5 @@
use crate::{
assistant_settings::{AssistantDockPosition, AssistantSettings},
- context_inspector::ContextInspector,
humanize_token_count,
prompt_library::open_prompt_library,
prompts::PromptBuilder,
@@ -12,12 +11,12 @@ use crate::{
},
terminal_inline_assistant::TerminalInlineAssistant,
Assist, ConfirmCommand, Context, ContextEvent, ContextId, ContextStore, CycleMessageRole,
- DebugWorkflowSteps, DeployHistory, DeployPromptLibrary, InlineAssist, InlineAssistId,
- InlineAssistant, InsertIntoEditor, MessageStatus, ModelSelector, PendingSlashCommand,
- PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, ResolvedWorkflowStep,
- SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector,
+ DeployHistory, DeployPromptLibrary, InlineAssist, InlineAssistId, InlineAssistant,
+ InsertIntoEditor, MessageStatus, ModelSelector, PendingSlashCommand, PendingSlashCommandStatus,
+ QuoteSelection, RemoteContextMetadata, SavedContextMetadata, Split, ToggleFocus,
+ ToggleModelSelector, WorkflowStepResolution, WorkflowStepView,
};
-use crate::{ContextStoreEvent, ShowConfiguration};
+use crate::{ContextStoreEvent, ModelPickerDelegate, ShowConfiguration};
use anyhow::{anyhow, Result};
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
use client::{proto, Client, Status};
@@ -36,10 +35,10 @@ use fs::Fs;
use gpui::{
canvas, div, img, percentage, point, pulsating_between, size, Action, Animation, AnimationExt,
AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem,
- Context as _, DismissEvent, Empty, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
- FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels, ReadGlobal, Render,
- RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task,
- Transformation, UpdateGlobal, View, VisualContext, WeakView, WindowContext,
+ Context as _, CursorStyle, DismissEvent, Empty, Entity, EntityId, EventEmitter, FocusHandle,
+ FocusableView, FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels,
+ ReadGlobal, Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled,
+ Subscription, Task, Transformation, UpdateGlobal, View, VisualContext, WeakView, WindowContext,
};
use indexed_docs::IndexedDocsStore;
use language::{
@@ -57,15 +56,14 @@ use settings::{update_settings_file, Settings};
use smol::stream::StreamExt;
use std::{
borrow::Cow,
- cmp::{self, Ordering},
+ cmp,
fmt::Write,
- ops::Range,
+ ops::{DerefMut, Range},
path::PathBuf,
sync::Arc,
time::Duration,
};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
-use text::OffsetRangeExt;
use ui::TintColor;
use ui::{
prelude::*,
@@ -77,7 +75,6 @@ use util::ResultExt;
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
item::{self, FollowableItem, Item, ItemHandle},
- notifications::NotifyTaskExt,
pane::{self, SaveIntent},
searchable::{SearchEvent, SearchableItem},
Pane, Save, ToggleZoom, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
@@ -145,7 +142,7 @@ pub struct AssistantPanel {
languages: Arc,
fs: Arc,
subscriptions: Vec,
- model_selector_menu_handle: PopoverMenuHandle,
+ model_selector_menu_handle: PopoverMenuHandle>,
model_summary_editor: View,
authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
configuration_subscription: Option,
@@ -404,56 +401,13 @@ impl AssistantPanel {
} else {
"Zoom In"
};
- let weak_pane = cx.view().downgrade();
let menu = ContextMenu::build(cx, |menu, cx| {
- let menu = menu
- .context(pane.focus_handle(cx))
+ menu.context(pane.focus_handle(cx))
.action("New Context", Box::new(NewFile))
.action("History", Box::new(DeployHistory))
.action("Prompt Library", Box::new(DeployPromptLibrary))
.action("Configure", Box::new(ShowConfiguration))
- .action(zoom_label, Box::new(ToggleZoom));
-
- if let Some(editor) = pane
- .active_item()
- .and_then(|e| e.downcast::())
- {
- let is_enabled = editor.read(cx).debug_inspector.is_some();
- menu.separator().toggleable_entry(
- "Debug Workflows",
- is_enabled,
- IconPosition::End,
- None,
- move |cx| {
- weak_pane
- .update(cx, |this, cx| {
- if let Some(context_editor) =
- this.active_item().and_then(|item| {
- item.downcast::()
- })
- {
- context_editor.update(cx, |this, cx| {
- if let Some(mut state) =
- this.debug_inspector.take()
- {
- state.deactivate(cx);
- } else {
- this.debug_inspector = Some(
- ContextInspector::new(
- this.editor.clone(),
- this.context.clone(),
- ),
- );
- }
- })
- }
- })
- .ok();
- },
- )
- } else {
- menu
- }
+ .action(zoom_label, Box::new(ToggleZoom))
});
cx.subscribe(&menu, |pane, _, _: &DismissEvent, _| {
pane.new_item_menu = None;
@@ -1380,15 +1334,17 @@ struct WorkflowStep {
range: Range,
header_block_id: CustomBlockId,
footer_block_id: CustomBlockId,
- resolved_step: Option>>,
+ resolved_step: Option>>,
assist: Option,
}
impl WorkflowStep {
fn status(&self, cx: &AppContext) -> WorkflowStepStatus {
match self.resolved_step.as_ref() {
- Some(Ok(_)) => {
- if let Some(assist) = self.assist.as_ref() {
+ Some(Ok(step)) => {
+ if step.suggestion_groups.is_empty() {
+ WorkflowStepStatus::Empty
+ } else if let Some(assist) = self.assist.as_ref() {
let assistant = InlineAssistant::global(cx);
if assist
.assist_ids
@@ -1424,6 +1380,7 @@ impl WorkflowStep {
enum WorkflowStepStatus {
Resolving,
Error(Arc),
+ Empty,
Idle,
Pending,
Done,
@@ -1435,6 +1392,45 @@ impl WorkflowStepStatus {
matches!(self, Self::Confirmed)
}
+ fn render_workflow_step_error(
+ id: EntityId,
+ editor: WeakView,
+ step_range: Range,
+ error: String,
+ ) -> AnyElement {
+ h_flex()
+ .gap_2()
+ .child(
+ div()
+ .id("step-resolution-failure")
+ .child(
+ Label::new("Step Resolution Failed")
+ .size(LabelSize::Small)
+ .color(Color::Error),
+ )
+ .tooltip(move |cx| Tooltip::text(error.clone(), cx)),
+ )
+ .child(
+ Button::new(("transform", id), "Retry")
+ .icon(IconName::Update)
+ .icon_position(IconPosition::Start)
+ .icon_size(IconSize::Small)
+ .label_size(LabelSize::Small)
+ .on_click({
+ let editor = editor.clone();
+ let step_range = step_range.clone();
+ move |_, cx| {
+ editor
+ .update(cx, |this, cx| {
+ this.resolve_workflow_step(step_range.clone(), cx)
+ })
+ .ok();
+ }
+ }),
+ )
+ .into_any()
+ }
+
pub(crate) fn into_element(
&self,
step_range: Range,
@@ -1469,44 +1465,20 @@ impl WorkflowStepStatus {
|label, delta| label.alpha(delta),
)
.into_any_element(),
-
- WorkflowStepStatus::Error(error) => {
- let error = error.clone();
- h_flex()
- .gap_2()
- .child(
- div()
- .id("step-resolution-failure")
- .child(
- Label::new("Step Resolution Failed")
- .size(LabelSize::Small)
- .color(Color::Error),
- )
- .tooltip(move |cx| Tooltip::text(error.to_string(), cx)),
- )
- .child(
- Button::new(("transform", id), "Retry")
- .icon(IconName::Update)
- .icon_position(IconPosition::Start)
- .icon_size(IconSize::Small)
- .label_size(LabelSize::Small)
- .on_click({
- let editor = editor.clone();
- let step_range = step_range.clone();
- move |_, cx| {
- editor
- .update(cx, |this, cx| {
- this.resolve_workflow_step(step_range.clone(), cx)
- })
- .ok();
- }
- }),
- )
- .into_any()
- }
-
+ WorkflowStepStatus::Error(error) => Self::render_workflow_step_error(
+ id,
+ editor.clone(),
+ step_range.clone(),
+ error.to_string(),
+ ),
+ WorkflowStepStatus::Empty => Self::render_workflow_step_error(
+ id,
+ editor.clone(),
+ step_range.clone(),
+ "Model was unable to locate the code to edit".to_string(),
+ ),
WorkflowStepStatus::Idle => Button::new(("transform", id), "Transform")
- .icon(IconName::Sparkle)
+ .icon(IconName::SparkleAlt)
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
.label_size(LabelSize::Small)
@@ -1559,7 +1531,8 @@ impl WorkflowStepStatus {
.child(
IconButton::new(("stop-transformation", id), IconName::Stop)
.icon_size(IconSize::Small)
- .style(ButtonStyle::Tinted(TintColor::Negative))
+ .icon_color(Color::Error)
+ .style(ButtonStyle::Subtle)
.tooltip({
let step_range = step_range.clone();
let editor = editor.clone();
@@ -1725,7 +1698,6 @@ pub struct ContextEditor {
active_workflow_step: Option,
assistant_panel: WeakView,
error_message: Option,
- debug_inspector: Option,
show_accept_terms: bool,
}
@@ -1787,7 +1759,6 @@ impl ContextEditor {
active_workflow_step: None,
assistant_panel,
error_message: None,
- debug_inspector: None,
show_accept_terms: false,
};
this.update_message_headers(cx);
@@ -1872,7 +1843,7 @@ impl ContextEditor {
self.confirm_workflow_step(range, cx);
true
}
- WorkflowStepStatus::Error(_) => {
+ WorkflowStepStatus::Error(_) | WorkflowStepStatus::Empty => {
self.resolve_workflow_step(range, cx);
true
}
@@ -1885,9 +1856,8 @@ impl ContextEditor {
range: Range,
cx: &mut ViewContext,
) {
- self.context.update(cx, |context, cx| {
- context.resolve_workflow_step(range, self.project.clone(), cx)
- });
+ self.context
+ .update(cx, |context, cx| context.resolve_workflow_step(range, cx));
}
fn stop_workflow_step(&mut self, range: Range, cx: &mut ViewContext) {
@@ -1998,51 +1968,6 @@ impl ContextEditor {
cx.propagate();
}
- fn debug_workflow_steps(&mut self, _: &DebugWorkflowSteps, cx: &mut ViewContext) {
- let mut output = String::new();
- for (i, step) in self.context.read(cx).workflow_steps().iter().enumerate() {
- output.push_str(&format!("Step {}:\n", i + 1));
- output.push_str(&format!(
- "Content: {}\n",
- self.context
- .read(cx)
- .buffer()
- .read(cx)
- .text_for_range(step.tagged_range.clone())
- .collect::()
- ));
- match &step.status {
- crate::WorkflowStepStatus::Resolved(ResolvedWorkflowStep {
- title,
- suggestions,
- }) => {
- output.push_str("Resolution:\n");
- output.push_str(&format!(" {:?}\n", title));
- output.push_str(&format!(" {:?}\n", suggestions));
- }
- crate::WorkflowStepStatus::Pending(_) => {
- output.push_str("Resolution: Pending\n");
- }
- crate::WorkflowStepStatus::Error(error) => {
- writeln!(output, "Resolution: Error\n{:?}", error).unwrap();
- }
- }
- output.push('\n');
- }
-
- let editor = self
- .workspace
- .update(cx, |workspace, cx| Editor::new_in_workspace(workspace, cx));
-
- if let Ok(editor) = editor {
- cx.spawn(|_, mut cx| async move {
- let editor = editor.await?;
- editor.update(&mut cx, |editor, cx| editor.set_text(output, cx))
- })
- .detach_and_notify_err(cx);
- }
- }
-
fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext) {
let cursors = self.cursors(cx);
self.context.update(cx, |context, cx| {
@@ -2086,7 +2011,7 @@ impl ContextEditor {
}
editor.insert(&format!("/{name}"), cx);
- if command.requires_argument() {
+ if command.accepts_arguments() {
editor.insert(" ", cx);
editor.show_completions(&ShowCompletions::default(), cx);
}
@@ -2099,6 +2024,10 @@ impl ContextEditor {
}
pub fn confirm_command(&mut self, _: &ConfirmCommand, cx: &mut ViewContext) {
+ if self.editor.read(cx).has_active_completions_menu() {
+ return;
+ }
+
let selections = self.editor.read(cx).selections.disjoint_anchors();
let mut commands_by_range = HashMap::default();
let workspace = self.workspace.clone();
@@ -2460,9 +2389,6 @@ impl ContextEditor {
blocks_to_remove.insert(step.header_block_id);
blocks_to_remove.insert(step.footer_block_id);
}
- if let Some(debug) = self.debug_inspector.as_mut() {
- debug.deactivate_for(step_range, cx);
- }
}
self.editor.update(cx, |editor, cx| {
editor.remove_blocks(blocks_to_remove, None, cx)
@@ -2481,17 +2407,14 @@ impl ContextEditor {
let Some(step) = self
.context
.read(cx)
- .workflow_step_for_range(step_range.clone())
+ .workflow_step_for_range(step_range.clone(), cx)
else {
return;
};
- let resolved_step = step.status.into_resolved();
+ let resolved_step = step.read(cx).resolution.clone();
if let Some(existing_step) = self.workflow_steps.get_mut(&step_range) {
existing_step.resolved_step = resolved_step;
- if let Some(debug) = self.debug_inspector.as_mut() {
- debug.refresh(&step_range, cx);
- }
} else {
let start = buffer_snapshot
.anchor_in_excerpt(excerpt_id, step_range.start)
@@ -2531,85 +2454,102 @@ impl ContextEditor {
} else {
theme.info_border
};
- let step_index = weak_self.update(&mut **cx, |this, cx| {
- let snapshot = this.editor.read(cx).buffer().read(cx).as_singleton()?.read(cx).text_snapshot();
- let start_offset = step_range.start.to_offset(&snapshot);
- let parent_message = this.context.read(cx).messages_for_offsets([start_offset], cx);
- debug_assert_eq!(parent_message.len(), 1);
- let parent_message = parent_message.first()?;
-
- let index_of_current_step = this.workflow_steps.keys().filter(|workflow_step_range| workflow_step_range.start.cmp(&parent_message.anchor, &snapshot).is_ge() && workflow_step_range.end.cmp(&step_range.end, &snapshot).is_le()).count();
- Some(index_of_current_step)
- }).ok().flatten();
-
- let debug_header = weak_self
- .update(&mut **cx, |this, _| {
- if let Some(inspector) = this.debug_inspector.as_mut() {
- Some(inspector.is_active(&step_range))
- } else {
- None
- }
+ let step_index = weak_self
+ .update(&mut **cx, |this, cx| {
+ let snapshot = this
+ .editor
+ .read(cx)
+ .buffer()
+ .read(cx)
+ .as_singleton()?
+ .read(cx)
+ .text_snapshot();
+ let start_offset =
+ step_range.start.to_offset(&snapshot);
+ let parent_message = this
+ .context
+ .read(cx)
+ .messages_for_offsets([start_offset], cx);
+ debug_assert_eq!(parent_message.len(), 1);
+ let parent_message = parent_message.first()?;
+
+ let index_of_current_step = this
+ .workflow_steps
+ .keys()
+ .filter(|workflow_step_range| {
+ workflow_step_range
+ .start
+ .cmp(&parent_message.anchor, &snapshot)
+ .is_ge()
+ && workflow_step_range
+ .end
+ .cmp(&step_range.end, &snapshot)
+ .is_le()
+ })
+ .count();
+ Some(index_of_current_step)
})
- .unwrap_or_default();
- let step_label = if let Some(index) = step_index {
+ .ok()
+ .flatten();
+ let step_label = if let Some(index) = step_index {
Label::new(format!("Step {index}")).size(LabelSize::Small)
- } else {
- Label::new("Step").size(LabelSize::Small)
- };
- let step_label = if current_status.as_ref().is_some_and(|status| status.is_confirmed()) {
- h_flex().items_center().gap_2().child(step_label.strikethrough(true).color(Color::Muted)).child(Icon::new(IconName::Check).size(IconSize::Small).color(Color::Created))
+ } else {
+ Label::new("Step").size(LabelSize::Small)
+ };
+
+ let step_label = if current_status
+ .as_ref()
+ .is_some_and(|status| status.is_confirmed())
+ {
+ h_flex()
+ .items_center()
+ .gap_2()
+ .child(
+ step_label.strikethrough(true).color(Color::Muted),
+ )
+ .child(
+ Icon::new(IconName::Check)
+ .size(IconSize::Small)
+ .color(Color::Created),
+ )
} else {
div().child(step_label)
};
+
+ let step_label = step_label
+ .id("step")
+ .cursor(CursorStyle::PointingHand)
+ .on_click({
+ let this = weak_self.clone();
+ let step_range = step_range.clone();
+ move |_, cx| {
+ this.update(cx, |this, cx| {
+ this.open_workflow_step(step_range.clone(), cx);
+ })
+ .ok();
+ }
+ });
+
div()
.w_full()
.px(cx.gutter_dimensions.full_width())
.child(
h_flex()
.w_full()
+ .h_8()
.border_b_1()
.border_color(border_color)
- .pb_1p5()
+ .pb_2()
+ .items_center()
.justify_between()
.gap_2()
- .child(h_flex().justify_start().gap_2().child(step_label).children(
- debug_header.map(|is_active| {
-
- Button::new("debug-workflows-toggle", "Debug")
- .icon_color(Color::Hidden)
- .color(Color::Hidden)
- .selected_icon_color(Color::Default)
- .selected_label_color(Color::Default)
- .icon(IconName::Microscope)
- .icon_position(IconPosition::Start)
- .icon_size(IconSize::Small)
- .label_size(LabelSize::Small)
- .selected(is_active)
- .on_click({
- let weak_self = weak_self.clone();
- let step_range = step_range.clone();
- move |_, cx| {
- weak_self
- .update(cx, |this, cx| {
- if let Some(inspector) =
- this.debug_inspector
- .as_mut()
- {
- if is_active {
-
- inspector.deactivate_for(&step_range, cx);
- } else {
- inspector.activate_for_step(step_range.clone(), cx);
- }
- }
- })
- .ok();
- }
- })
- })
-
- ))
+ .child(
+ h_flex()
+ .justify_start()
+ .gap_2()
+ .child(step_label),
+ )
.children(current_status.as_ref().map(|status| {
h_flex().w_full().justify_end().child(
status.into_element(
@@ -2679,6 +2619,38 @@ impl ContextEditor {
self.update_active_workflow_step(cx);
}
+ fn open_workflow_step(
+ &mut self,
+ step_range: Range,
+ cx: &mut ViewContext,
+ ) -> Option<()> {
+ let pane = self
+ .assistant_panel
+ .update(cx, |panel, _| panel.pane())
+ .ok()??;
+ let context = self.context.read(cx);
+ let language_registry = context.language_registry();
+ let step = context.workflow_step_for_range(step_range, cx)?;
+ let context = self.context.clone();
+ cx.deref_mut().defer(move |cx| {
+ pane.update(cx, |pane, cx| {
+ let existing_item = pane
+ .items_of_type::()
+ .find(|item| *item.read(cx).step() == step.downgrade());
+ if let Some(item) = existing_item {
+ if let Some(index) = pane.index_for_item(&item) {
+ pane.activate_item(index, true, true, cx);
+ }
+ } else {
+ let view = cx
+ .new_view(|cx| WorkflowStepView::new(context, step, language_registry, cx));
+ pane.add_item(Box::new(view), true, true, None, cx);
+ }
+ });
+ });
+ None
+ }
+
fn update_active_workflow_step(&mut self, cx: &mut ViewContext) {
let new_step = self.active_workflow_step_for_cursor(cx);
if new_step.as_ref() != self.active_workflow_step.as_ref() {
@@ -2793,25 +2765,31 @@ impl ContextEditor {
}
fn open_assists_for_step(
- resolved_step: &ResolvedWorkflowStep,
+ resolved_step: &WorkflowStepResolution,
project: &Model,
assistant_panel: &WeakView,
workspace: &WeakView,
cx: &mut ViewContext,
) -> Option {
let assistant_panel = assistant_panel.upgrade()?;
- if resolved_step.suggestions.is_empty() {
+ if resolved_step.suggestion_groups.is_empty() {
return None;
}
let editor;
let mut editor_was_open = false;
let mut suggestion_groups = Vec::new();
- if resolved_step.suggestions.len() == 1
- && resolved_step.suggestions.values().next().unwrap().len() == 1
+ if resolved_step.suggestion_groups.len() == 1
+ && resolved_step
+ .suggestion_groups
+ .values()
+ .next()
+ .unwrap()
+ .len()
+ == 1
{
// If there's only one buffer and one suggestion group, open it directly
- let (buffer, groups) = resolved_step.suggestions.iter().next().unwrap();
+ let (buffer, groups) = resolved_step.suggestion_groups.iter().next().unwrap();
let group = groups.into_iter().next().unwrap();
editor = workspace
.update(cx, |workspace, cx| {
@@ -2864,7 +2842,7 @@ impl ContextEditor {
let replica_id = project.read(cx).replica_id();
let mut multibuffer = MultiBuffer::new(replica_id, Capability::ReadWrite)
.with_title(resolved_step.title.clone());
- for (buffer, groups) in &resolved_step.suggestions {
+ for (buffer, groups) in &resolved_step.suggestion_groups {
let excerpt_ids = multibuffer.push_excerpts(
buffer.clone(),
groups.iter().map(|suggestion_group| ExcerptRange {
@@ -3545,7 +3523,7 @@ impl ContextEditor {
let button_text = match self.active_workflow_step() {
Some(step) => match step.status(cx) {
WorkflowStepStatus::Resolving => "Resolving Step...",
- WorkflowStepStatus::Error(_) => "Retry Step Resolution",
+ WorkflowStepStatus::Empty | WorkflowStepStatus::Error(_) => "Retry Step Resolution",
WorkflowStepStatus::Idle => "Transform",
WorkflowStepStatus::Pending => "Transforming...",
WorkflowStepStatus::Done => "Accept Transformation",
@@ -3605,28 +3583,11 @@ impl ContextEditor {
fn active_workflow_step_for_cursor(&self, cx: &AppContext) -> Option {
let newest_cursor = self.editor.read(cx).selections.newest::(cx).head();
let context = self.context.read(cx);
- let buffer = context.buffer().read(cx);
-
- let workflow_steps = context.workflow_steps();
- workflow_steps
- .binary_search_by(|step| {
- let step_range = step.tagged_range.to_offset(&buffer);
- if newest_cursor < step_range.start {
- Ordering::Greater
- } else if newest_cursor > step_range.end {
- Ordering::Less
- } else {
- Ordering::Equal
- }
- })
- .ok()
- .and_then(|index| {
- let range = workflow_steps[index].tagged_range.clone();
- Some(ActiveWorkflowStep {
- resolved: self.workflow_steps.get(&range)?.resolved_step.is_some(),
- range,
- })
- })
+ let (range, step) = context.workflow_step_containing(newest_cursor, cx)?;
+ Some(ActiveWorkflowStep {
+ resolved: step.read(cx).resolution.is_some(),
+ range,
+ })
}
}
@@ -3654,7 +3615,6 @@ impl Render for ContextEditor {
.capture_action(cx.listener(ContextEditor::confirm_command))
.on_action(cx.listener(ContextEditor::assist))
.on_action(cx.listener(ContextEditor::split))
- .on_action(cx.listener(ContextEditor::debug_workflow_steps))
.size_full()
.children(self.render_notice(cx))
.child(
@@ -3974,12 +3934,13 @@ pub struct ContextEditorToolbarItem {
workspace: WeakView,
active_context_editor: Option>,
model_summary_editor: View,
+ model_selector_menu_handle: PopoverMenuHandle>,
}
impl ContextEditorToolbarItem {
pub fn new(
workspace: &Workspace,
- _model_selector_menu_handle: PopoverMenuHandle,
+ model_selector_menu_handle: PopoverMenuHandle>,
model_summary_editor: View,
) -> Self {
Self {
@@ -3987,6 +3948,7 @@ impl ContextEditorToolbarItem {
workspace: workspace.weak_handle(),
active_context_editor: None,
model_summary_editor,
+ model_selector_menu_handle,
}
}
@@ -4120,49 +4082,52 @@ impl Render for ContextEditorToolbarItem {
let right_side = h_flex()
.gap_2()
- .child(ModelSelector::new(
- self.fs.clone(),
- ButtonLike::new("active-model")
- .style(ButtonStyle::Subtle)
- .child(
- h_flex()
- .w_full()
- .gap_0p5()
- .child(
- div()
- .overflow_x_hidden()
- .flex_grow()
- .whitespace_nowrap()
- .child(match (active_provider, active_model) {
- (Some(provider), Some(model)) => h_flex()
- .gap_1()
- .child(
- Icon::new(provider.icon())
- .color(Color::Muted)
- .size(IconSize::XSmall),
- )
- .child(
- Label::new(model.name().0)
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- .into_any_element(),
- _ => Label::new("No model selected")
- .size(LabelSize::Small)
- .color(Color::Muted)
- .into_any_element(),
- }),
- )
- .child(
- Icon::new(IconName::ChevronDown)
- .color(Color::Muted)
- .size(IconSize::XSmall),
- ),
- )
- .tooltip(move |cx| {
- Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
- }),
- ))
+ .child(
+ ModelSelector::new(
+ self.fs.clone(),
+ ButtonLike::new("active-model")
+ .style(ButtonStyle::Subtle)
+ .child(
+ h_flex()
+ .w_full()
+ .gap_0p5()
+ .child(
+ div()
+ .overflow_x_hidden()
+ .flex_grow()
+ .whitespace_nowrap()
+ .child(match (active_provider, active_model) {
+ (Some(provider), Some(model)) => h_flex()
+ .gap_1()
+ .child(
+ Icon::new(provider.icon())
+ .color(Color::Muted)
+ .size(IconSize::XSmall),
+ )
+ .child(
+ Label::new(model.name().0)
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ .into_any_element(),
+ _ => Label::new("No model selected")
+ .size(LabelSize::Small)
+ .color(Color::Muted)
+ .into_any_element(),
+ }),
+ )
+ .child(
+ Icon::new(IconName::ChevronDown)
+ .color(Color::Muted)
+ .size(IconSize::XSmall),
+ ),
+ )
+ .tooltip(move |cx| {
+ Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
+ }),
+ )
+ .with_handle(self.model_selector_menu_handle.clone()),
+ )
.children(self.render_remaining_tokens(cx))
.child(self.render_inject_context_menu(cx));
diff --git a/crates/assistant/src/context.rs b/crates/assistant/src/context.rs
index b98dc64d96ea338072725b4ac8123881e38722ae..5b0209b433671e590cc9d2a9c02d8bf01c414207 100644
--- a/crates/assistant/src/context.rs
+++ b/crates/assistant/src/context.rs
@@ -1,6 +1,9 @@
+#[cfg(test)]
+mod context_tests;
+
use crate::{
- prompts::PromptBuilder, slash_command::SlashCommandLine, AssistantPanel, InitialInsertion,
- InlineAssistId, InlineAssistant, MessageId, MessageStatus,
+ prompts::PromptBuilder, slash_command::SlashCommandLine, workflow::WorkflowStep, MessageId,
+ MessageStatus,
};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{
@@ -9,34 +12,25 @@ use assistant_slash_command::{
use client::{self, proto, telemetry::Telemetry};
use clock::ReplicaId;
use collections::{HashMap, HashSet};
-use editor::Editor;
use fs::{Fs, RemoveOptions};
-use futures::{
- future::{self, Shared},
- stream::FuturesUnordered,
- FutureExt, StreamExt,
-};
+use futures::{future::Shared, stream::FuturesUnordered, FutureExt, StreamExt};
use gpui::{
- AppContext, Context as _, EventEmitter, Image, Model, ModelContext, RenderImage, Subscription,
- Task, UpdateGlobal, View, WeakView,
+ AppContext, Context as _, EventEmitter, Image, Model, ModelContext, RenderImage, SharedString,
+ Subscription, Task,
};
-use language::{
- AnchorRangeExt, Bias, Buffer, BufferSnapshot, LanguageRegistry, OffsetRangeExt, ParseStatus,
- Point, ToOffset,
-};
+use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
use language_model::{
- LanguageModelImage, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage,
- LanguageModelTool, Role,
+ LanguageModel, LanguageModelCacheConfiguration, LanguageModelImage, LanguageModelRegistry,
+ LanguageModelRequest, LanguageModelRequestMessage, MessageContent, Role,
};
use open_ai::Model as OpenAiModel;
use paths::{context_images_dir, contexts_dir};
use project::Project;
-use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::{
- cmp::{self, Ordering},
+ cmp::{max, Ordering},
collections::hash_map,
fmt::Debug,
iter, mem,
@@ -46,10 +40,8 @@ use std::{
time::{Duration, Instant},
};
use telemetry_events::AssistantKind;
-use ui::{SharedString, WindowContext};
use util::{post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
-use workspace::Workspace;
#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ContextId(String);
@@ -115,6 +107,8 @@ impl ContextOperation {
message.status.context("invalid status")?,
),
timestamp: id.0,
+ should_cache: false,
+ is_cache_anchor: false,
},
version: language::proto::deserialize_version(&insert.version),
})
@@ -129,6 +123,8 @@ impl ContextOperation {
timestamp: language::proto::deserialize_timestamp(
update.timestamp.context("invalid timestamp")?,
),
+ should_cache: false,
+ is_cache_anchor: false,
},
version: language::proto::deserialize_version(&update.version),
}),
@@ -319,8 +315,10 @@ pub struct MessageAnchor {
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct MessageMetadata {
pub role: Role,
- status: MessageStatus,
+ pub status: MessageStatus,
timestamp: clock::Lamport,
+ should_cache: bool,
+ is_cache_anchor: bool,
}
#[derive(Clone, Debug)]
@@ -346,21 +344,19 @@ pub struct Message {
pub anchor: language::Anchor,
pub role: Role,
pub status: MessageStatus,
+ pub cache: bool,
}
impl Message {
- fn to_request_message(&self, buffer: &Buffer) -> LanguageModelRequestMessage {
+ fn to_request_message(&self, buffer: &Buffer) -> Option {
let mut content = Vec::new();
let mut range_start = self.offset_range.start;
for (image_offset, message_image) in self.image_offsets.iter() {
if *image_offset != range_start {
- content.push(
- buffer
- .text_for_range(range_start..*image_offset)
- .collect::()
- .into(),
- )
+ if let Some(text) = Self::collect_text_content(buffer, range_start..*image_offset) {
+ content.push(text);
+ }
}
if let Some(image) = message_image.image.clone().now_or_never().flatten() {
@@ -370,17 +366,30 @@ impl Message {
range_start = *image_offset;
}
if range_start != self.offset_range.end {
- content.push(
- buffer
- .text_for_range(range_start..self.offset_range.end)
- .collect::()
- .into(),
- )
+ if let Some(text) =
+ Self::collect_text_content(buffer, range_start..self.offset_range.end)
+ {
+ content.push(text);
+ }
+ }
+
+ if content.is_empty() {
+ return None;
}
- LanguageModelRequestMessage {
+ Some(LanguageModelRequestMessage {
role: self.role,
content,
+ cache: self.cache,
+ })
+ }
+
+ fn collect_text_content(buffer: &Buffer, range: Range) -> Option {
+ let text: String = buffer.text_for_range(range.clone()).collect();
+ if text.trim().is_empty() {
+ None
+ } else {
+ Some(MessageContent::Text(text))
}
}
}
@@ -393,12 +402,6 @@ pub struct ImageAnchor {
pub image: Shared>>,
}
-impl PartialEq for ImageAnchor {
- fn eq(&self, other: &Self) -> bool {
- self.image_id == other.image_id
- }
-}
-
struct PendingCompletion {
id: usize,
assistant_message_id: MessageId,
@@ -408,251 +411,9 @@ struct PendingCompletion {
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct SlashCommandId(clock::Lamport);
-#[derive(Debug)]
-pub struct WorkflowStep {
- pub tagged_range: Range,
- pub status: WorkflowStepStatus,
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct ResolvedWorkflowStep {
- pub title: String,
- pub suggestions: HashMap, Vec>,
-}
-
-pub enum WorkflowStepStatus {
- Pending(Task