Compare commits
21 Commits
acp-rewind
...
v0.184.2-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43a04dc46f | ||
|
|
00e92e37fd | ||
|
|
9e703accdb | ||
|
|
44d5976849 | ||
|
|
44fc0045dd | ||
|
|
c60b731f2f | ||
|
|
b85bfbad67 | ||
|
|
f743cdc41a | ||
|
|
808d9c9361 | ||
|
|
a53e561d5e | ||
|
|
80bac1ca92 | ||
|
|
4171bbbe9e | ||
|
|
2a88867aa0 | ||
|
|
9d420ae8f1 | ||
|
|
b431886188 | ||
|
|
43c99ad066 | ||
|
|
f802b56299 | ||
|
|
0c292b6cc6 | ||
|
|
8446005112 | ||
|
|
5b028ac2d0 | ||
|
|
57d0276180 |
461
Cargo.lock
generated
461
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -109,7 +109,7 @@ members = [
|
||||
"crates/project",
|
||||
"crates/project_panel",
|
||||
"crates/project_symbols",
|
||||
"crates/prompt_library",
|
||||
"crates/rules_library",
|
||||
"crates/prompt_store",
|
||||
"crates/proto",
|
||||
"crates/recent_projects",
|
||||
@@ -318,7 +318,7 @@ prettier = { path = "crates/prettier" }
|
||||
project = { path = "crates/project" }
|
||||
project_panel = { path = "crates/project_panel" }
|
||||
project_symbols = { path = "crates/project_symbols" }
|
||||
prompt_library = { path = "crates/prompt_library" }
|
||||
rules_library = { path = "crates/rules_library" }
|
||||
prompt_store = { path = "crates/prompt_store" }
|
||||
proto = { path = "crates/proto" }
|
||||
recent_projects = { path = "crates/recent_projects" }
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
"ctrl-shift-g": "search::SelectPreviousMatch",
|
||||
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
||||
"ctrl-k h": "assistant::DeployHistory",
|
||||
"ctrl-k l": "assistant::OpenPromptLibrary",
|
||||
"ctrl-k l": "assistant::OpenRulesLibrary",
|
||||
"new": "assistant::NewChat",
|
||||
"ctrl-t": "assistant::NewChat",
|
||||
"ctrl-n": "assistant::NewChat"
|
||||
@@ -241,7 +241,7 @@
|
||||
"ctrl-alt-n": "agent::NewTextThread",
|
||||
"ctrl-shift-h": "agent::OpenHistory",
|
||||
"ctrl-alt-c": "agent::OpenConfiguration",
|
||||
"ctrl-alt-p": "assistant::OpenPromptLibrary",
|
||||
"ctrl-alt-p": "assistant::OpenRulesLibrary",
|
||||
"ctrl-i": "agent::ToggleProfileSelector",
|
||||
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
||||
"ctrl-shift-a": "agent::ToggleContextPicker",
|
||||
@@ -308,9 +308,9 @@
|
||||
{
|
||||
"context": "PromptLibrary",
|
||||
"bindings": {
|
||||
"new": "prompt_library::NewPrompt",
|
||||
"ctrl-n": "prompt_library::NewPrompt",
|
||||
"ctrl-shift-s": "prompt_library::ToggleDefaultPrompt"
|
||||
"new": "rules_library::NewRule",
|
||||
"ctrl-n": "rules_library::NewRule",
|
||||
"ctrl-shift-s": "rules_library::ToggleDefaultRule"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -675,7 +675,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"context": "!ContextEditor > Editor && mode == full",
|
||||
"bindings": {
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
"shift-enter": "editor::ExpandExcerpts",
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
"context": "PromptLibrary",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-n": "prompt_library::NewPrompt",
|
||||
"cmd-shift-s": "prompt_library::ToggleDefaultPrompt",
|
||||
"cmd-n": "rules_library::NewRule",
|
||||
"cmd-shift-s": "rules_library::ToggleDefaultRule",
|
||||
"cmd-w": "workspace::CloseWindow"
|
||||
}
|
||||
},
|
||||
@@ -257,7 +257,7 @@
|
||||
"cmd-shift-g": "search::SelectPreviousMatch",
|
||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||
"cmd-k h": "assistant::DeployHistory",
|
||||
"cmd-k l": "assistant::OpenPromptLibrary",
|
||||
"cmd-k l": "assistant::OpenRulesLibrary",
|
||||
"cmd-t": "assistant::NewChat",
|
||||
"cmd-n": "assistant::NewChat"
|
||||
}
|
||||
@@ -286,7 +286,7 @@
|
||||
"cmd-alt-n": "agent::NewTextThread",
|
||||
"cmd-shift-h": "agent::OpenHistory",
|
||||
"cmd-alt-c": "agent::OpenConfiguration",
|
||||
"cmd-alt-p": "assistant::OpenPromptLibrary",
|
||||
"cmd-alt-p": "assistant::OpenRulesLibrary",
|
||||
"cmd-i": "agent::ToggleProfileSelector",
|
||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||
"cmd-shift-a": "agent::ToggleContextPicker",
|
||||
@@ -738,7 +738,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"context": "Editor && mode == full",
|
||||
"context": "!ContextEditor > Editor && mode == full",
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"alt-enter": "editor::OpenExcerpts",
|
||||
|
||||
@@ -31,6 +31,9 @@ If appropriate, use tool calls to explore the current project, which contains th
|
||||
- When looking for symbols in the project, prefer the `grep` tool.
|
||||
- As you learn about the structure of the project, use that information to scope `grep` searches to targeted subtrees of the project.
|
||||
- Bias towards not asking the user for help if you can find the answer yourself.
|
||||
{{! TODO: Only mention tools if they are enabled }}
|
||||
- The user might specify a partial file path. If you don't know the full path, use `find_path` (not `grep`) before you read the file.
|
||||
- Before you read or edit a file, you must first find the full path. DO NOT ever guess a file path!
|
||||
|
||||
## Fixing Diagnostics
|
||||
|
||||
|
||||
@@ -646,7 +646,7 @@
|
||||
"fetch": true,
|
||||
"list_directory": false,
|
||||
"now": true,
|
||||
"path_search": true,
|
||||
"find_path": true,
|
||||
"read_file": true,
|
||||
"grep": true,
|
||||
"thinking": true,
|
||||
@@ -670,7 +670,7 @@
|
||||
"list_directory": true,
|
||||
"move_path": false,
|
||||
"now": false,
|
||||
"path_search": true,
|
||||
"find_path": true,
|
||||
"read_file": true,
|
||||
"grep": true,
|
||||
"rename": false,
|
||||
|
||||
@@ -62,7 +62,7 @@ parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
prompt_library.workspace = true
|
||||
rules_library.workspace = true
|
||||
prompt_store.workspace = true
|
||||
proto.workspace = true
|
||||
release_channel.workspace = true
|
||||
|
||||
@@ -43,8 +43,9 @@ use ui::{
|
||||
Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, TextSize, Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use util::markdown::MarkdownString;
|
||||
use workspace::{OpenOptions, Workspace};
|
||||
use zed_actions::assistant::OpenPromptLibrary;
|
||||
use zed_actions::assistant::OpenRulesLibrary;
|
||||
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
@@ -433,47 +434,39 @@ fn render_markdown_code_block(
|
||||
workspace
|
||||
.update(cx, {
|
||||
|workspace, cx| {
|
||||
if let Some(project_path) = workspace
|
||||
let Some(project_path) = workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.find_project_path(&path_range.path, cx)
|
||||
{
|
||||
let target = path_range.range.as_ref().map(|range| {
|
||||
Point::new(
|
||||
// Line number is 1-based
|
||||
range.start.line.saturating_sub(1),
|
||||
range.start.col.unwrap_or(0),
|
||||
)
|
||||
});
|
||||
let open_task = workspace.open_path(
|
||||
project_path,
|
||||
None,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let item = open_task.await?;
|
||||
if let Some(target) = target {
|
||||
if let Some(active_editor) =
|
||||
item.downcast::<Editor>()
|
||||
{
|
||||
active_editor
|
||||
.downgrade()
|
||||
.update_in(cx, |editor, window, cx| {
|
||||
editor
|
||||
.go_to_singleton_buffer_point(
|
||||
target, window, cx,
|
||||
);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(target) = path_range.range.as_ref().map(|range| {
|
||||
Point::new(
|
||||
// Line number is 1-based
|
||||
range.start.line.saturating_sub(1),
|
||||
range.start.col.unwrap_or(0),
|
||||
)
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
let open_task =
|
||||
workspace.open_path(project_path, None, true, window, cx);
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let item = open_task.await?;
|
||||
if let Some(active_editor) = item.downcast::<Editor>() {
|
||||
active_editor
|
||||
.update_in(cx, |editor, window, cx| {
|
||||
editor.go_to_singleton_buffer_point(
|
||||
target, window, cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
@@ -703,7 +696,7 @@ fn open_markdown_link(
|
||||
}),
|
||||
Some(MentionLink::Fetch(url)) => cx.open_url(&url),
|
||||
Some(MentionLink::Rules(prompt_id)) => window.dispatch_action(
|
||||
Box::new(OpenPromptLibrary {
|
||||
Box::new(OpenRulesLibrary {
|
||||
prompt_to_select: Some(prompt_id.0),
|
||||
}),
|
||||
cx,
|
||||
@@ -777,7 +770,7 @@ impl ActiveThread {
|
||||
this.render_tool_use_markdown(
|
||||
tool_use.id.clone(),
|
||||
tool_use.ui_text.clone(),
|
||||
&tool_use.input,
|
||||
&serde_json::to_string_pretty(&tool_use.input).unwrap_or_default(),
|
||||
tool_use.status.text(),
|
||||
cx,
|
||||
);
|
||||
@@ -807,10 +800,11 @@ impl ActiveThread {
|
||||
self.thread.read(cx).summary_or_default()
|
||||
}
|
||||
|
||||
pub fn cancel_last_completion(&mut self, cx: &mut App) -> bool {
|
||||
pub fn cancel_last_completion(&mut self, window: &mut Window, cx: &mut App) -> bool {
|
||||
self.last_error.take();
|
||||
self.thread
|
||||
.update(cx, |thread, cx| thread.cancel_last_completion(cx))
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.cancel_last_completion(Some(window.window_handle()), cx)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn last_error(&self) -> Option<ThreadError> {
|
||||
@@ -877,7 +871,7 @@ impl ActiveThread {
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_label: impl Into<SharedString>,
|
||||
tool_input: &serde_json::Value,
|
||||
tool_input: &str,
|
||||
tool_output: SharedString,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
@@ -900,11 +894,10 @@ impl ActiveThread {
|
||||
this.replace(tool_label, cx);
|
||||
});
|
||||
rendered.input.update(cx, |this, cx| {
|
||||
let input = format!(
|
||||
"```json\n{}\n```",
|
||||
serde_json::to_string_pretty(tool_input).unwrap_or_default()
|
||||
this.replace(
|
||||
MarkdownString::code_block("json", tool_input).to_string(),
|
||||
cx,
|
||||
);
|
||||
this.replace(input, cx);
|
||||
});
|
||||
rendered.output.update(cx, |this, cx| {
|
||||
this.replace(tool_output, cx);
|
||||
@@ -995,7 +988,7 @@ impl ActiveThread {
|
||||
self.render_tool_use_markdown(
|
||||
tool_use.id.clone(),
|
||||
tool_use.ui_text.clone(),
|
||||
&tool_use.input,
|
||||
&serde_json::to_string_pretty(&tool_use.input).unwrap_or_default(),
|
||||
"".into(),
|
||||
cx,
|
||||
);
|
||||
@@ -1009,7 +1002,7 @@ impl ActiveThread {
|
||||
self.render_tool_use_markdown(
|
||||
tool_use_id.clone(),
|
||||
ui_text.clone(),
|
||||
input,
|
||||
&serde_json::to_string_pretty(&input).unwrap_or_default(),
|
||||
"".into(),
|
||||
cx,
|
||||
);
|
||||
@@ -1021,7 +1014,7 @@ impl ActiveThread {
|
||||
self.render_tool_use_markdown(
|
||||
tool_use.id.clone(),
|
||||
tool_use.ui_text.clone(),
|
||||
&tool_use.input,
|
||||
&serde_json::to_string_pretty(&tool_use.input).unwrap_or_default(),
|
||||
self.thread
|
||||
.read(cx)
|
||||
.output_for_tool(&tool_use.id)
|
||||
@@ -1033,6 +1026,23 @@ impl ActiveThread {
|
||||
}
|
||||
ThreadEvent::CheckpointChanged => cx.notify(),
|
||||
ThreadEvent::ReceivedTextChunk => {}
|
||||
ThreadEvent::InvalidToolInput {
|
||||
tool_use_id,
|
||||
ui_text,
|
||||
invalid_input_json,
|
||||
} => {
|
||||
self.render_tool_use_markdown(
|
||||
tool_use_id.clone(),
|
||||
ui_text,
|
||||
invalid_input_json,
|
||||
self.thread
|
||||
.read(cx)
|
||||
.output_for_tool(tool_use_id)
|
||||
.map(|output| output.clone().into())
|
||||
.unwrap_or("".into()),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1314,7 +1324,7 @@ impl ActiveThread {
|
||||
fn confirm_editing_message(
|
||||
&mut self,
|
||||
_: &menu::Confirm,
|
||||
_: &mut Window,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some((message_id, state)) = self.editing_message.take() else {
|
||||
@@ -1344,7 +1354,7 @@ impl ActiveThread {
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.advance_prompt_id();
|
||||
thread.send_to_model(model.model, cx)
|
||||
thread.send_to_model(model.model, Some(window.window_handle()), cx);
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
@@ -1492,41 +1502,13 @@ impl ActiveThread {
|
||||
let is_first_message = ix == 0;
|
||||
let is_last_message = ix == self.messages.len() - 1;
|
||||
|
||||
let show_feedback = (!is_generating && is_last_message && message.role != Role::User)
|
||||
|| self.messages.get(ix + 1).map_or(false, |next_id| {
|
||||
self.thread
|
||||
.read(cx)
|
||||
.message(*next_id)
|
||||
.map_or(false, |next_message| {
|
||||
next_message.role == Role::User
|
||||
&& thread.tool_uses_for_message(*next_id, cx).is_empty()
|
||||
&& thread.tool_results_for_message(*next_id).is_empty()
|
||||
})
|
||||
});
|
||||
let show_feedback = thread.is_turn_end(ix);
|
||||
|
||||
let needs_confirmation = tool_uses.iter().any(|tool_use| tool_use.needs_confirmation);
|
||||
|
||||
let generating_label = (is_generating && is_last_message)
|
||||
.then(|| AnimatedLabel::new("Generating").size(LabelSize::Small));
|
||||
|
||||
// Don't render user messages that are just there for returning tool results.
|
||||
if message.role == Role::User && thread.message_has_tool_results(message_id) {
|
||||
if let Some(generating_label) = generating_label {
|
||||
return h_flex()
|
||||
.w_full()
|
||||
.h_10()
|
||||
.py_1p5()
|
||||
.pl_4()
|
||||
.pb_3()
|
||||
.child(generating_label)
|
||||
.into_any_element();
|
||||
}
|
||||
|
||||
return Empty.into_any();
|
||||
}
|
||||
|
||||
let allow_editing_message = message.role == Role::User;
|
||||
|
||||
let edit_message_editor = self
|
||||
.editing_message
|
||||
.as_ref()
|
||||
@@ -1545,13 +1527,14 @@ impl ActiveThread {
|
||||
window.dispatch_action(Box::new(OpenActiveThreadAsMarkdown), cx)
|
||||
});
|
||||
|
||||
// For all items that should be aligned with the Assistant's response.
|
||||
// For all items that should be aligned with the LLM's response.
|
||||
const RESPONSE_PADDING_X: Pixels = px(18.);
|
||||
|
||||
let feedback_container = h_flex()
|
||||
.py_2()
|
||||
.px(RESPONSE_PADDING_X)
|
||||
.gap_1()
|
||||
.flex_wrap()
|
||||
.justify_between();
|
||||
let feedback_items = match self.thread.read(cx).message_feedback(message_id) {
|
||||
Some(feedback) => feedback_container
|
||||
@@ -1563,7 +1546,8 @@ impl ActiveThread {
|
||||
}
|
||||
})
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
.size(LabelSize::XSmall)
|
||||
.truncate(),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -1614,7 +1598,8 @@ impl ActiveThread {
|
||||
"Rating the thread sends all of your current conversation to the Zed team.",
|
||||
)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
.size(LabelSize::XSmall)
|
||||
.truncate(),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -1756,93 +1741,70 @@ impl ActiveThread {
|
||||
.pb_4()
|
||||
.child(
|
||||
v_flex()
|
||||
.id(("user-message", ix))
|
||||
.bg(editor_bg_color)
|
||||
.rounded_lg()
|
||||
.shadow_md()
|
||||
.border_1()
|
||||
.border_color(colors.border)
|
||||
.shadow_md()
|
||||
.child(div().p_2().children(message_content))
|
||||
.child(
|
||||
h_flex()
|
||||
.p_1()
|
||||
.border_t_1()
|
||||
.border_color(colors.border_variant)
|
||||
.justify_end()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.when_some(
|
||||
edit_message_editor.clone(),
|
||||
|this, edit_message_editor| {
|
||||
let focus_handle =
|
||||
edit_message_editor.focus_handle(cx);
|
||||
this.child(
|
||||
Button::new("cancel-edit-message", "Cancel")
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
cx.listener(Self::handle_cancel_click),
|
||||
),
|
||||
.hover(|hover| hover.border_color(colors.text_accent.opacity(0.5)))
|
||||
.cursor_pointer()
|
||||
.child(div().p_2().pt_2p5().children(message_content))
|
||||
.when_some(edit_message_editor.clone(), |this, edit_editor| {
|
||||
let focus_handle = edit_editor.focus_handle(cx);
|
||||
|
||||
this.child(
|
||||
h_flex()
|
||||
.p_1()
|
||||
.border_t_1()
|
||||
.border_color(colors.border_variant)
|
||||
.gap_1()
|
||||
.justify_end()
|
||||
.child(
|
||||
Button::new("cancel-edit-message", "Cancel")
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Cancel,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
Button::new(
|
||||
"confirm-edit-message",
|
||||
"Regenerate",
|
||||
)
|
||||
.disabled(
|
||||
edit_message_editor.read(cx).is_empty(cx),
|
||||
)
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(
|
||||
cx.listener(Self::handle_regenerate_click),
|
||||
),
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(cx.listener(Self::handle_cancel_click)),
|
||||
)
|
||||
.child(
|
||||
Button::new("confirm-edit-message", "Regenerate")
|
||||
.disabled(edit_editor.read(cx).is_empty(cx))
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(
|
||||
KeyBinding::for_action_in(
|
||||
&menu::Confirm,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
)
|
||||
.when(
|
||||
edit_message_editor.is_none() && allow_editing_message,
|
||||
|this| {
|
||||
this.child(
|
||||
Button::new("edit-message", "Edit Message")
|
||||
.label_size(LabelSize::Small)
|
||||
.icon(IconName::Pencil)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.icon_position(IconPosition::Start)
|
||||
.on_click(cx.listener({
|
||||
let message_segments =
|
||||
message.segments.clone();
|
||||
move |this, _, window, cx| {
|
||||
this.start_editing_message(
|
||||
message_id,
|
||||
&message_segments,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})),
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
.map(|kb| kb.size(rems_from_px(12.))),
|
||||
)
|
||||
.on_click(cx.listener(Self::handle_regenerate_click)),
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(edit_message_editor.is_none(), |this| {
|
||||
this.tooltip(Tooltip::text("Click To Edit"))
|
||||
})
|
||||
.on_click(cx.listener({
|
||||
let message_segments = message.segments.clone();
|
||||
move |this, _, window, cx| {
|
||||
this.start_editing_message(
|
||||
message_id,
|
||||
&message_segments,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})),
|
||||
),
|
||||
Role::Assistant => v_flex()
|
||||
.id(("message-container", ix))
|
||||
@@ -1850,11 +1812,9 @@ impl ActiveThread {
|
||||
.gap_2()
|
||||
.children(message_content)
|
||||
.when(has_tool_uses, |parent| {
|
||||
parent.children(
|
||||
tool_uses
|
||||
.into_iter()
|
||||
.map(|tool_use| self.render_tool_use(tool_use, window, cx)),
|
||||
)
|
||||
parent.children(tool_uses.into_iter().map(|tool_use| {
|
||||
self.render_tool_use(tool_use, window, workspace.clone(), cx)
|
||||
}))
|
||||
}),
|
||||
Role::System => div().id(("message-container", ix)).py_1().px_2().child(
|
||||
v_flex()
|
||||
@@ -2447,10 +2407,11 @@ impl ActiveThread {
|
||||
&self,
|
||||
tool_use: ToolUse,
|
||||
window: &mut Window,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement + use<> {
|
||||
if let Some(card) = self.thread.read(cx).card_for_tool(&tool_use.id) {
|
||||
return card.render(&tool_use.status, window, cx);
|
||||
return card.render(&tool_use.status, window, workspace, cx);
|
||||
}
|
||||
|
||||
let is_open = self
|
||||
@@ -3000,11 +2961,11 @@ impl ActiveThread {
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Ignored)
|
||||
// TODO: Figure out a way to pass focus handle here so we can display the `OpenPromptLibrary` keybinding
|
||||
// TODO: Figure out a way to pass focus handle here so we can display the `OpenRulesLibrary` keybinding
|
||||
.tooltip(Tooltip::text("View User Rules"))
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(
|
||||
Box::new(OpenPromptLibrary {
|
||||
Box::new(OpenRulesLibrary {
|
||||
prompt_to_select: first_user_rules_id,
|
||||
}),
|
||||
cx,
|
||||
@@ -3047,7 +3008,7 @@ impl ActiveThread {
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
_: &ClickEvent,
|
||||
_window: &mut Window,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(PendingToolUseStatus::NeedsConfirmation(c)) = self
|
||||
@@ -3063,6 +3024,7 @@ impl ActiveThread {
|
||||
c.input.clone(),
|
||||
&c.messages,
|
||||
c.tool.clone(),
|
||||
Some(window.window_handle()),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -3074,11 +3036,12 @@ impl ActiveThread {
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_name: Arc<str>,
|
||||
_: &ClickEvent,
|
||||
_window: &mut Window,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let window_handle = window.window_handle();
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.deny_tool_use(tool_use_id, tool_name, cx);
|
||||
thread.deny_tool_use(tool_use_id, tool_name, Some(window_handle), cx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3288,7 +3251,7 @@ pub(crate) fn open_context(
|
||||
})
|
||||
}
|
||||
AssistantContext::Rules(rules_context) => window.dispatch_action(
|
||||
Box::new(OpenPromptLibrary {
|
||||
Box::new(OpenRulesLibrary {
|
||||
prompt_to_select: Some(rules_context.prompt_id.0),
|
||||
}),
|
||||
cx,
|
||||
|
||||
@@ -12,21 +12,21 @@ use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
|
||||
use client::zed_urls;
|
||||
use client::{UserStore, zed_urls};
|
||||
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, Corner, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext, Pixels, Subscription, Task,
|
||||
UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
|
||||
Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
|
||||
Corner, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext, Pixels,
|
||||
Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
|
||||
use language_model_selector::ToggleModelSelector;
|
||||
use project::Project;
|
||||
use prompt_library::{PromptLibrary, open_prompt_library};
|
||||
use prompt_store::{PromptBuilder, PromptId, UserPromptId};
|
||||
use proto::Plan;
|
||||
use rules_library::{RulesLibrary, open_rules_library};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use time::UtcOffset;
|
||||
use ui::{
|
||||
@@ -36,7 +36,7 @@ use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||
use zed_actions::agent::OpenConfiguration;
|
||||
use zed_actions::assistant::{OpenPromptLibrary, ToggleFocus};
|
||||
use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};
|
||||
|
||||
use crate::active_thread::{ActiveThread, ActiveThreadEvent};
|
||||
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
|
||||
@@ -79,11 +79,11 @@ pub fn init(cx: &mut App) {
|
||||
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
|
||||
}
|
||||
})
|
||||
.register_action(|workspace, action: &OpenPromptLibrary, window, cx| {
|
||||
.register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.deploy_prompt_library(action, window, cx)
|
||||
panel.deploy_rules_library(action, window, cx)
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -180,6 +180,7 @@ impl ActiveView {
|
||||
|
||||
pub struct AssistantPanel {
|
||||
workspace: WeakEntity<Workspace>,
|
||||
user_store: Entity<UserStore>,
|
||||
project: Entity<Project>,
|
||||
fs: Arc<dyn Fs>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
@@ -243,6 +244,7 @@ impl AssistantPanel {
|
||||
) -> Self {
|
||||
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let user_store = workspace.app_state().user_store.clone();
|
||||
let project = workspace.project();
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
let workspace = workspace.weak_handle();
|
||||
@@ -307,6 +309,7 @@ impl AssistantPanel {
|
||||
Self {
|
||||
active_view,
|
||||
workspace,
|
||||
user_store,
|
||||
project: project.clone(),
|
||||
fs: fs.clone(),
|
||||
language_registry,
|
||||
@@ -356,14 +359,9 @@ impl AssistantPanel {
|
||||
&self.thread_store
|
||||
}
|
||||
|
||||
fn cancel(
|
||||
&mut self,
|
||||
_: &editor::actions::Cancel,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.thread
|
||||
.update(cx, |thread, cx| thread.cancel_last_completion(cx));
|
||||
.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
|
||||
}
|
||||
|
||||
fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -486,13 +484,13 @@ impl AssistantPanel {
|
||||
context_editor.focus_handle(cx).focus(window);
|
||||
}
|
||||
|
||||
fn deploy_prompt_library(
|
||||
fn deploy_rules_library(
|
||||
&mut self,
|
||||
action: &OpenPromptLibrary,
|
||||
action: &OpenRulesLibrary,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
open_prompt_library(
|
||||
open_rules_library(
|
||||
self.language_registry.clone(),
|
||||
Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
|
||||
Arc::new(|| {
|
||||
@@ -1122,7 +1120,7 @@ impl AssistantPanel {
|
||||
"New Text Thread",
|
||||
NewTextThread.boxed_clone(),
|
||||
)
|
||||
.action("Prompt Library", Box::new(OpenPromptLibrary::default()))
|
||||
.action("Rules Library", Box::new(OpenRulesLibrary::default()))
|
||||
.action("Settings", Box::new(OpenConfiguration))
|
||||
.separator()
|
||||
.header("MCPs")
|
||||
@@ -1548,9 +1546,19 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn render_usage_banner(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let plan = self
|
||||
.user_store
|
||||
.read(cx)
|
||||
.current_plan()
|
||||
.map(|plan| match plan {
|
||||
Plan::Free => zed_llm_client::Plan::Free,
|
||||
Plan::ZedPro => zed_llm_client::Plan::ZedPro,
|
||||
Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
|
||||
})
|
||||
.unwrap_or(zed_llm_client::Plan::Free);
|
||||
let usage = self.thread.read(cx).last_usage()?;
|
||||
|
||||
Some(UsageBanner::new(zed_llm_client::Plan::ZedProTrial, usage).into_any_element())
|
||||
Some(UsageBanner::new(plan, usage).into_any_element())
|
||||
}
|
||||
|
||||
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
@@ -1605,6 +1613,8 @@ impl AssistantPanel {
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.mt_1()
|
||||
.gap_1()
|
||||
.child(self.create_copy_button(ERROR_MESSAGE))
|
||||
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|
||||
|this, _, _, cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
@@ -1651,6 +1661,8 @@ impl AssistantPanel {
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.mt_1()
|
||||
.gap_1()
|
||||
.child(self.create_copy_button(ERROR_MESSAGE))
|
||||
.child(
|
||||
Button::new("subscribe", "Update Monthly Spend Limit").on_click(
|
||||
cx.listener(|this, _, _, cx| {
|
||||
@@ -1716,6 +1728,8 @@ impl AssistantPanel {
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.mt_1()
|
||||
.gap_1()
|
||||
.child(self.create_copy_button(error_message))
|
||||
.child(
|
||||
Button::new("subscribe", call_to_action).on_click(cx.listener(
|
||||
|this, _, _, cx| {
|
||||
@@ -1747,6 +1761,7 @@ impl AssistantPanel {
|
||||
message: SharedString,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
let message_with_header = format!("{}\n{}", header, message);
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
@@ -1761,12 +1776,14 @@ impl AssistantPanel {
|
||||
.id("error-message")
|
||||
.max_h_32()
|
||||
.overflow_y_scroll()
|
||||
.child(Label::new(message)),
|
||||
.child(Label::new(message.clone())),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_end()
|
||||
.mt_1()
|
||||
.gap_1()
|
||||
.child(self.create_copy_button(message_with_header))
|
||||
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
||||
|this, _, _, cx| {
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
@@ -1780,6 +1797,15 @@ impl AssistantPanel {
|
||||
.into_any()
|
||||
}
|
||||
|
||||
fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
|
||||
let message = message.into();
|
||||
IconButton::new("copy", IconName::Copy)
|
||||
.on_click(move |_, _, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
|
||||
})
|
||||
.tooltip(Tooltip::text("Copy Error Message"))
|
||||
}
|
||||
|
||||
fn key_context(&self) -> KeyContext {
|
||||
let mut key_context = KeyContext::new_with_defaults();
|
||||
key_context.add("AgentPanel");
|
||||
@@ -1807,7 +1833,7 @@ impl Render for AssistantPanel {
|
||||
this.open_configuration(window, cx);
|
||||
}))
|
||||
.on_action(cx.listener(Self::open_active_thread_as_markdown))
|
||||
.on_action(cx.listener(Self::deploy_prompt_library))
|
||||
.on_action(cx.listener(Self::deploy_rules_library))
|
||||
.on_action(cx.listener(Self::open_agent_diff))
|
||||
.on_action(cx.listener(Self::go_back))
|
||||
.child(self.render_toolbar(window, cx))
|
||||
@@ -1834,13 +1860,13 @@ impl PromptLibraryInlineAssist {
|
||||
}
|
||||
}
|
||||
|
||||
impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
|
||||
impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
|
||||
fn assist(
|
||||
&self,
|
||||
prompt_editor: &Entity<Editor>,
|
||||
_initial_prompt: Option<String>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<PromptLibrary>,
|
||||
cx: &mut Context<RulesLibrary>,
|
||||
) {
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
let Some(project) = self
|
||||
|
||||
@@ -5,7 +5,9 @@ use anyhow::Result;
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::HashSet;
|
||||
use editor::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint};
|
||||
use futures::{SinkExt, Stream, StreamExt, channel::mpsc, future::LocalBoxFuture, join};
|
||||
use futures::{
|
||||
SinkExt, Stream, StreamExt, TryStreamExt as _, channel::mpsc, future::LocalBoxFuture, join,
|
||||
};
|
||||
use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Subscription, Task};
|
||||
use language::{Buffer, IndentKind, Point, TransactionId, line_diff};
|
||||
use language_model::{
|
||||
@@ -508,7 +510,9 @@ impl CodegenAlternative {
|
||||
let mut response_latency = None;
|
||||
let request_start = Instant::now();
|
||||
let diff = async {
|
||||
let chunks = StripInvalidSpans::new(stream?.stream);
|
||||
let chunks = StripInvalidSpans::new(
|
||||
stream?.stream.map_err(|error| error.into()),
|
||||
);
|
||||
futures::pin_mut!(chunks);
|
||||
let mut diff = StreamingDiff::new(selected_text.to_string());
|
||||
let mut line_diff = LineDiff::default();
|
||||
|
||||
@@ -195,6 +195,7 @@ impl MessageEditor {
|
||||
editor.set_mode(EditorMode::Full {
|
||||
scale_ui_elements_with_buffer_font_size: false,
|
||||
show_active_line_background: false,
|
||||
sized_by_content: false,
|
||||
})
|
||||
} else {
|
||||
editor.set_mode(EditorMode::AutoHeight {
|
||||
@@ -277,6 +278,7 @@ impl MessageEditor {
|
||||
let context_store = self.context_store.clone();
|
||||
let git_store = self.project.read(cx).git_store().clone();
|
||||
let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
|
||||
let window_handle = window.window_handle();
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
let checkpoint = checkpoint.await.ok();
|
||||
@@ -333,7 +335,7 @@ impl MessageEditor {
|
||||
thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.advance_prompt_id();
|
||||
thread.send_to_model(model, cx);
|
||||
thread.send_to_model(model, Some(window_handle), cx);
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
@@ -341,9 +343,9 @@ impl MessageEditor {
|
||||
}
|
||||
|
||||
fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let cancelled = self
|
||||
.thread
|
||||
.update(cx, |thread, cx| thread.cancel_last_completion(cx));
|
||||
let cancelled = self.thread.update(cx, |thread, cx| {
|
||||
thread.cancel_last_completion(Some(window.window_handle()), cx)
|
||||
});
|
||||
|
||||
if cancelled {
|
||||
self.set_editor_is_expanded(false, cx);
|
||||
|
||||
@@ -13,12 +13,14 @@ use feature_flags::{self, FeatureFlagAppExt};
|
||||
use futures::future::Shared;
|
||||
use futures::{FutureExt, StreamExt as _};
|
||||
use git::repository::DiffType;
|
||||
use gpui::{App, AppContext, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};
|
||||
use gpui::{
|
||||
AnyWindowHandle, App, AppContext, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
|
||||
};
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModel, LanguageModelCompletionEvent, LanguageModelId,
|
||||
LanguageModelImage, LanguageModelKnownError, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,
|
||||
LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent,
|
||||
ConfiguredModel, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelId, LanguageModelImage, LanguageModelKnownError, LanguageModelRegistry,
|
||||
LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool,
|
||||
LanguageModelToolResult, LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent,
|
||||
ModelRequestLimitReachedError, PaymentRequiredError, RequestUsage, Role, StopReason,
|
||||
TokenUsage,
|
||||
};
|
||||
@@ -389,8 +391,7 @@ impl Thread {
|
||||
.map(|message| message.id.0 + 1)
|
||||
.unwrap_or(0),
|
||||
);
|
||||
let tool_use =
|
||||
ToolUseState::from_serialized_messages(tools.clone(), &serialized.messages, |_| true);
|
||||
let tool_use = ToolUseState::from_serialized_messages(tools.clone(), &serialized.messages);
|
||||
|
||||
Self {
|
||||
id,
|
||||
@@ -522,7 +523,12 @@ impl Thread {
|
||||
}
|
||||
|
||||
pub fn message(&self, id: MessageId) -> Option<&Message> {
|
||||
self.messages.iter().find(|message| message.id == id)
|
||||
let index = self
|
||||
.messages
|
||||
.binary_search_by(|message| message.id.cmp(&id))
|
||||
.ok()?;
|
||||
|
||||
self.messages.get(index)
|
||||
}
|
||||
|
||||
pub fn messages(&self) -> impl ExactSizeIterator<Item = &Message> {
|
||||
@@ -671,6 +677,32 @@ impl Thread {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_turn_end(&self, ix: usize) -> bool {
|
||||
if self.messages.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !self.is_generating() && ix == self.messages.len() - 1 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some(message) = self.messages.get(ix) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if message.role != Role::Assistant {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.messages
|
||||
.get(ix + 1)
|
||||
.and_then(|message| {
|
||||
self.message(message.id)
|
||||
.map(|next_message| next_message.role == Role::User)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns whether all of the tool uses have finished running.
|
||||
pub fn all_tools_finished(&self) -> bool {
|
||||
// If the only pending tool uses left are the ones with errors, then
|
||||
@@ -685,8 +717,11 @@ impl Thread {
|
||||
self.tool_use.tool_uses_for_message(id, cx)
|
||||
}
|
||||
|
||||
pub fn tool_results_for_message(&self, id: MessageId) -> Vec<&LanguageModelToolResult> {
|
||||
self.tool_use.tool_results_for_message(id)
|
||||
pub fn tool_results_for_message(
|
||||
&self,
|
||||
assistant_message_id: MessageId,
|
||||
) -> Vec<&LanguageModelToolResult> {
|
||||
self.tool_use.tool_results_for_message(assistant_message_id)
|
||||
}
|
||||
|
||||
pub fn tool_result(&self, id: &LanguageModelToolUseId) -> Option<&LanguageModelToolResult> {
|
||||
@@ -701,10 +736,6 @@ impl Thread {
|
||||
self.tool_use.tool_result_card(id).cloned()
|
||||
}
|
||||
|
||||
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
|
||||
self.tool_use.message_has_tool_results(message_id)
|
||||
}
|
||||
|
||||
/// Filter out contexts that have already been included in previous messages
|
||||
pub fn filter_new_context<'a>(
|
||||
&self,
|
||||
@@ -951,7 +982,12 @@ impl Thread {
|
||||
self.remaining_turns = remaining_turns;
|
||||
}
|
||||
|
||||
pub fn send_to_model(&mut self, model: Arc<dyn LanguageModel>, cx: &mut Context<Self>) {
|
||||
pub fn send_to_model(
|
||||
&mut self,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.remaining_turns == 0 {
|
||||
return;
|
||||
}
|
||||
@@ -982,7 +1018,7 @@ impl Thread {
|
||||
};
|
||||
}
|
||||
|
||||
self.stream_completion(request, model, cx);
|
||||
self.stream_completion(request, model, window, cx);
|
||||
}
|
||||
|
||||
pub fn used_tools_since_last_user_message(&self) -> bool {
|
||||
@@ -1044,9 +1080,6 @@ impl Thread {
|
||||
cache: false,
|
||||
};
|
||||
|
||||
self.tool_use
|
||||
.attach_tool_results(message.id, &mut request_message);
|
||||
|
||||
if !message.context.is_empty() {
|
||||
request_message
|
||||
.content
|
||||
@@ -1097,6 +1130,10 @@ impl Thread {
|
||||
.attach_tool_uses(message.id, &mut request_message);
|
||||
|
||||
request.messages.push(request_message);
|
||||
|
||||
if let Some(tool_results_message) = self.tool_use.tool_results_message(message.id) {
|
||||
request.messages.push(tool_results_message);
|
||||
}
|
||||
}
|
||||
|
||||
// https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
|
||||
@@ -1126,11 +1163,6 @@ impl Thread {
|
||||
cache: false,
|
||||
};
|
||||
|
||||
// Skip tool results during summarization.
|
||||
if self.tool_use.message_has_tool_results(message.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for segment in &message.segments {
|
||||
match segment {
|
||||
MessageSegment::Text(text) => request_message
|
||||
@@ -1201,6 +1233,7 @@ impl Thread {
|
||||
&mut self,
|
||||
request: LanguageModelRequest,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let pending_completion_id = post_inc(&mut self.completion_count);
|
||||
@@ -1234,22 +1267,45 @@ impl Thread {
|
||||
.ok();
|
||||
}
|
||||
|
||||
let mut request_assistant_message_id = None;
|
||||
|
||||
while let Some(event) = events.next().await {
|
||||
if let Some((_, response_events)) = request_callback_parameters.as_mut() {
|
||||
response_events
|
||||
.push(event.as_ref().map_err(|error| error.to_string()).cloned());
|
||||
}
|
||||
|
||||
let event = event?;
|
||||
|
||||
thread.update(cx, |thread, cx| {
|
||||
let event = match event {
|
||||
Ok(event) => event,
|
||||
Err(LanguageModelCompletionError::BadInputJson {
|
||||
id,
|
||||
tool_name,
|
||||
raw_input: invalid_input_json,
|
||||
json_parse_error,
|
||||
}) => {
|
||||
thread.receive_invalid_tool_json(
|
||||
id,
|
||||
tool_name,
|
||||
invalid_input_json,
|
||||
json_parse_error,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
Err(LanguageModelCompletionError::Other(error)) => {
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
match event {
|
||||
LanguageModelCompletionEvent::StartMessage { .. } => {
|
||||
thread.insert_message(
|
||||
request_assistant_message_id = Some(thread.insert_message(
|
||||
Role::Assistant,
|
||||
vec![MessageSegment::Text(String::new())],
|
||||
cx,
|
||||
);
|
||||
));
|
||||
}
|
||||
LanguageModelCompletionEvent::Stop(reason) => {
|
||||
stop_reason = reason;
|
||||
@@ -1264,7 +1320,9 @@ impl Thread {
|
||||
LanguageModelCompletionEvent::Text(chunk) => {
|
||||
cx.emit(ThreadEvent::ReceivedTextChunk);
|
||||
if let Some(last_message) = thread.messages.last_mut() {
|
||||
if last_message.role == Role::Assistant {
|
||||
if last_message.role == Role::Assistant
|
||||
&& !thread.tool_use.has_tool_results(last_message.id)
|
||||
{
|
||||
last_message.push_text(&chunk);
|
||||
cx.emit(ThreadEvent::StreamedAssistantText(
|
||||
last_message.id,
|
||||
@@ -1276,11 +1334,11 @@ impl Thread {
|
||||
//
|
||||
// Importantly: We do *not* want to emit a `StreamedAssistantText` event here, as it
|
||||
// will result in duplicating the text of the chunk in the rendered Markdown.
|
||||
thread.insert_message(
|
||||
request_assistant_message_id = Some(thread.insert_message(
|
||||
Role::Assistant,
|
||||
vec![MessageSegment::Text(chunk.to_string())],
|
||||
cx,
|
||||
);
|
||||
));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1289,7 +1347,9 @@ impl Thread {
|
||||
signature,
|
||||
} => {
|
||||
if let Some(last_message) = thread.messages.last_mut() {
|
||||
if last_message.role == Role::Assistant {
|
||||
if last_message.role == Role::Assistant
|
||||
&& !thread.tool_use.has_tool_results(last_message.id)
|
||||
{
|
||||
last_message.push_thinking(&chunk, signature);
|
||||
cx.emit(ThreadEvent::StreamedAssistantThinking(
|
||||
last_message.id,
|
||||
@@ -1301,25 +1361,25 @@ impl Thread {
|
||||
//
|
||||
// Importantly: We do *not* want to emit a `StreamedAssistantText` event here, as it
|
||||
// will result in duplicating the text of the chunk in the rendered Markdown.
|
||||
thread.insert_message(
|
||||
request_assistant_message_id = Some(thread.insert_message(
|
||||
Role::Assistant,
|
||||
vec![MessageSegment::Thinking {
|
||||
text: chunk.to_string(),
|
||||
signature,
|
||||
}],
|
||||
cx,
|
||||
);
|
||||
));
|
||||
};
|
||||
}
|
||||
}
|
||||
LanguageModelCompletionEvent::ToolUse(tool_use) => {
|
||||
let last_assistant_message_id = thread
|
||||
.messages
|
||||
.iter_mut()
|
||||
.rfind(|message| message.role == Role::Assistant)
|
||||
.map(|message| message.id)
|
||||
let last_assistant_message_id = request_assistant_message_id
|
||||
.unwrap_or_else(|| {
|
||||
thread.insert_message(Role::Assistant, vec![], cx)
|
||||
let new_assistant_message_id =
|
||||
thread.insert_message(Role::Assistant, vec![], cx);
|
||||
request_assistant_message_id =
|
||||
Some(new_assistant_message_id);
|
||||
new_assistant_message_id
|
||||
});
|
||||
|
||||
let tool_use_id = tool_use.id.clone();
|
||||
@@ -1351,7 +1411,8 @@ impl Thread {
|
||||
cx.notify();
|
||||
|
||||
thread.auto_capture_telemetry(cx);
|
||||
})?;
|
||||
Ok(())
|
||||
})??;
|
||||
|
||||
smol::future::yield_now().await;
|
||||
}
|
||||
@@ -1382,7 +1443,7 @@ impl Thread {
|
||||
match result.as_ref() {
|
||||
Ok(stop_reason) => match stop_reason {
|
||||
StopReason::ToolUse => {
|
||||
let tool_uses = thread.use_pending_tools(cx);
|
||||
let tool_uses = thread.use_pending_tools(window, cx);
|
||||
cx.emit(ThreadEvent::UsePendingTools { tool_uses });
|
||||
}
|
||||
StopReason::EndTurn => {}
|
||||
@@ -1427,7 +1488,7 @@ impl Thread {
|
||||
}));
|
||||
}
|
||||
|
||||
thread.cancel_last_completion(cx);
|
||||
thread.cancel_last_completion(window, cx);
|
||||
}
|
||||
}
|
||||
cx.emit(ThreadEvent::Stopped(result.map_err(Arc::new)));
|
||||
@@ -1596,7 +1657,11 @@ impl Thread {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn use_pending_tools(&mut self, cx: &mut Context<Self>) -> Vec<PendingToolUse> {
|
||||
pub fn use_pending_tools(
|
||||
&mut self,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Vec<PendingToolUse> {
|
||||
self.auto_capture_telemetry(cx);
|
||||
let request = self.to_completion_request(cx);
|
||||
let messages = Arc::new(request.messages);
|
||||
@@ -1628,6 +1693,7 @@ impl Thread {
|
||||
tool_use.input.clone(),
|
||||
&messages,
|
||||
tool,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@@ -1637,6 +1703,41 @@ impl Thread {
|
||||
pending_tool_uses
|
||||
}
|
||||
|
||||
pub fn receive_invalid_tool_json(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_name: Arc<str>,
|
||||
invalid_json: Arc<str>,
|
||||
error: String,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut Context<Thread>,
|
||||
) {
|
||||
log::error!("The model returned invalid input JSON: {invalid_json}");
|
||||
|
||||
let pending_tool_use = self.tool_use.insert_tool_output(
|
||||
tool_use_id.clone(),
|
||||
tool_name,
|
||||
Err(anyhow!("Error parsing input JSON: {error}")),
|
||||
cx,
|
||||
);
|
||||
let ui_text = if let Some(pending_tool_use) = &pending_tool_use {
|
||||
pending_tool_use.ui_text.clone()
|
||||
} else {
|
||||
log::error!(
|
||||
"There was no pending tool use for tool use {tool_use_id}, even though it finished (with invalid input JSON)."
|
||||
);
|
||||
format!("Unknown tool {}", tool_use_id).into()
|
||||
};
|
||||
|
||||
cx.emit(ThreadEvent::InvalidToolInput {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
ui_text,
|
||||
invalid_input_json: invalid_json,
|
||||
});
|
||||
|
||||
self.tool_finished(tool_use_id, pending_tool_use, false, window, cx);
|
||||
}
|
||||
|
||||
pub fn run_tool(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
@@ -1644,9 +1745,10 @@ impl Thread {
|
||||
input: serde_json::Value,
|
||||
messages: &[LanguageModelRequestMessage],
|
||||
tool: Arc<dyn Tool>,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut Context<Thread>,
|
||||
) {
|
||||
let task = self.spawn_tool_use(tool_use_id.clone(), messages, input, tool, cx);
|
||||
let task = self.spawn_tool_use(tool_use_id.clone(), messages, input, tool, window, cx);
|
||||
self.tool_use
|
||||
.run_pending_tool(tool_use_id, ui_text.into(), task);
|
||||
}
|
||||
@@ -1657,6 +1759,7 @@ impl Thread {
|
||||
messages: &[LanguageModelRequestMessage],
|
||||
input: serde_json::Value,
|
||||
tool: Arc<dyn Tool>,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut Context<Thread>,
|
||||
) -> Task<()> {
|
||||
let tool_name: Arc<str> = tool.name().into();
|
||||
@@ -1669,6 +1772,7 @@ impl Thread {
|
||||
messages,
|
||||
self.project.clone(),
|
||||
self.action_log.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
};
|
||||
@@ -1691,7 +1795,7 @@ impl Thread {
|
||||
output,
|
||||
cx,
|
||||
);
|
||||
thread.tool_finished(tool_use_id, pending_tool_use, false, cx);
|
||||
thread.tool_finished(tool_use_id, pending_tool_use, false, window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -1703,15 +1807,16 @@ impl Thread {
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
pending_tool_use: Option<PendingToolUse>,
|
||||
canceled: bool,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.all_tools_finished() {
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
if let Some(ConfiguredModel { model, .. }) = model_registry.default_model() {
|
||||
self.attach_tool_results(cx);
|
||||
if !canceled {
|
||||
self.send_to_model(model, cx);
|
||||
self.send_to_model(model, window, cx);
|
||||
}
|
||||
self.auto_capture_telemetry(cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1721,33 +1826,27 @@ impl Thread {
|
||||
});
|
||||
}
|
||||
|
||||
/// Insert an empty message to be populated with tool results upon send.
|
||||
pub fn attach_tool_results(&mut self, cx: &mut Context<Self>) {
|
||||
// Tool results are assumed to be waiting on the next message id, so they will populate
|
||||
// this empty message before sending to model. Would prefer this to be more straightforward.
|
||||
self.insert_message(Role::User, vec![], cx);
|
||||
self.auto_capture_telemetry(cx);
|
||||
}
|
||||
|
||||
/// Cancels the last pending completion, if there are any pending.
|
||||
///
|
||||
/// Returns whether a completion was canceled.
|
||||
pub fn cancel_last_completion(&mut self, cx: &mut Context<Self>) -> bool {
|
||||
let canceled = if self.pending_completions.pop().is_some() {
|
||||
true
|
||||
} else {
|
||||
let mut canceled = false;
|
||||
for pending_tool_use in self.tool_use.cancel_pending() {
|
||||
canceled = true;
|
||||
self.tool_finished(
|
||||
pending_tool_use.id.clone(),
|
||||
Some(pending_tool_use),
|
||||
true,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
canceled
|
||||
};
|
||||
pub fn cancel_last_completion(
|
||||
&mut self,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
let mut canceled = self.pending_completions.pop().is_some();
|
||||
|
||||
for pending_tool_use in self.tool_use.cancel_pending() {
|
||||
canceled = true;
|
||||
self.tool_finished(
|
||||
pending_tool_use.id.clone(),
|
||||
Some(pending_tool_use),
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
self.finalize_pending_checkpoint(cx);
|
||||
canceled
|
||||
}
|
||||
@@ -2028,7 +2127,7 @@ impl Thread {
|
||||
}
|
||||
|
||||
for tool_result in self.tool_results_for_message(message.id) {
|
||||
write!(markdown, "**Tool Results: {}", tool_result.tool_use_id)?;
|
||||
write!(markdown, "\n**Tool Results: {}", tool_result.tool_use_id)?;
|
||||
if tool_result.is_error {
|
||||
write!(markdown, " (Error)")?;
|
||||
}
|
||||
@@ -2199,6 +2298,7 @@ impl Thread {
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_name: Arc<str>,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let err = Err(anyhow::anyhow!(
|
||||
@@ -2207,7 +2307,7 @@ impl Thread {
|
||||
|
||||
self.tool_use
|
||||
.insert_tool_output(tool_use_id.clone(), tool_name, err, cx);
|
||||
self.tool_finished(tool_use_id.clone(), None, true, cx);
|
||||
self.tool_finished(tool_use_id.clone(), None, true, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2239,6 +2339,11 @@ pub enum ThreadEvent {
|
||||
ui_text: Arc<str>,
|
||||
input: serde_json::Value,
|
||||
},
|
||||
InvalidToolInput {
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
ui_text: Arc<str>,
|
||||
invalid_input_json: Arc<str>,
|
||||
},
|
||||
Stopped(Result<StopReason, Arc<anyhow::Error>>),
|
||||
MessageAdded(MessageId),
|
||||
MessageEdited(MessageId),
|
||||
|
||||
@@ -639,12 +639,17 @@ pub struct SerializedThread {
|
||||
}
|
||||
|
||||
impl SerializedThread {
|
||||
pub const VERSION: &'static str = "0.1.0";
|
||||
pub const VERSION: &'static str = "0.2.0";
|
||||
|
||||
pub fn from_json(json: &[u8]) -> Result<Self> {
|
||||
let saved_thread_json = serde_json::from_slice::<serde_json::Value>(json)?;
|
||||
match saved_thread_json.get("version") {
|
||||
Some(serde_json::Value::String(version)) => match version.as_str() {
|
||||
SerializedThreadV0_1_0::VERSION => {
|
||||
let saved_thread =
|
||||
serde_json::from_value::<SerializedThreadV0_1_0>(saved_thread_json)?;
|
||||
Ok(saved_thread.upgrade())
|
||||
}
|
||||
SerializedThread::VERSION => Ok(serde_json::from_value::<SerializedThread>(
|
||||
saved_thread_json,
|
||||
)?),
|
||||
@@ -666,6 +671,38 @@ impl SerializedThread {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SerializedThreadV0_1_0(
|
||||
// The structure did not change, so we are reusing the latest SerializedThread.
|
||||
// When making the next version, make sure this points to SerializedThreadV0_2_0
|
||||
SerializedThread,
|
||||
);
|
||||
|
||||
impl SerializedThreadV0_1_0 {
|
||||
pub const VERSION: &'static str = "0.1.0";
|
||||
|
||||
pub fn upgrade(self) -> SerializedThread {
|
||||
debug_assert_eq!(SerializedThread::VERSION, "0.2.0");
|
||||
|
||||
let mut messages: Vec<SerializedMessage> = Vec::with_capacity(self.0.messages.len());
|
||||
|
||||
for message in self.0.messages {
|
||||
if message.role == Role::User && !message.tool_results.is_empty() {
|
||||
if let Some(last_message) = messages.last_mut() {
|
||||
debug_assert!(last_message.role == Role::Assistant);
|
||||
|
||||
last_message.tool_results = message.tool_results;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
messages.push(message);
|
||||
}
|
||||
|
||||
SerializedThread { messages, ..self.0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SerializedMessage {
|
||||
pub id: MessageId,
|
||||
|
||||
@@ -30,7 +30,6 @@ pub struct ToolUse {
|
||||
pub struct ToolUseState {
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
tool_uses_by_assistant_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
|
||||
tool_uses_by_user_message: HashMap<MessageId, Vec<LanguageModelToolUseId>>,
|
||||
tool_results: HashMap<LanguageModelToolUseId, LanguageModelToolResult>,
|
||||
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
||||
tool_result_cards: HashMap<LanguageModelToolUseId, AnyToolCard>,
|
||||
@@ -42,7 +41,6 @@ impl ToolUseState {
|
||||
Self {
|
||||
tools,
|
||||
tool_uses_by_assistant_message: HashMap::default(),
|
||||
tool_uses_by_user_message: HashMap::default(),
|
||||
tool_results: HashMap::default(),
|
||||
pending_tool_uses_by_id: HashMap::default(),
|
||||
tool_result_cards: HashMap::default(),
|
||||
@@ -56,7 +54,6 @@ impl ToolUseState {
|
||||
pub fn from_serialized_messages(
|
||||
tools: Entity<ToolWorkingSet>,
|
||||
messages: &[SerializedMessage],
|
||||
mut filter_by_tool_name: impl FnMut(&str) -> bool,
|
||||
) -> Self {
|
||||
let mut this = Self::new(tools);
|
||||
let mut tool_names_by_id = HashMap::default();
|
||||
@@ -68,10 +65,10 @@ impl ToolUseState {
|
||||
let tool_uses = message
|
||||
.tool_uses
|
||||
.iter()
|
||||
.filter(|tool_use| (filter_by_tool_name)(tool_use.name.as_ref()))
|
||||
.map(|tool_use| LanguageModelToolUse {
|
||||
id: tool_use.id.clone(),
|
||||
name: tool_use.name.clone().into(),
|
||||
raw_input: tool_use.input.to_string(),
|
||||
input: tool_use.input.clone(),
|
||||
is_input_complete: true,
|
||||
})
|
||||
@@ -85,14 +82,6 @@ impl ToolUseState {
|
||||
|
||||
this.tool_uses_by_assistant_message
|
||||
.insert(message.id, tool_uses);
|
||||
}
|
||||
}
|
||||
Role::User => {
|
||||
if !message.tool_results.is_empty() {
|
||||
let tool_uses_by_user_message = this
|
||||
.tool_uses_by_user_message
|
||||
.entry(message.id)
|
||||
.or_default();
|
||||
|
||||
for tool_result in &message.tool_results {
|
||||
let tool_use_id = tool_result.tool_use_id.clone();
|
||||
@@ -101,11 +90,6 @@ impl ToolUseState {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !(filter_by_tool_name)(tool_use.as_ref()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tool_uses_by_user_message.push(tool_use_id.clone());
|
||||
this.tool_results.insert(
|
||||
tool_use_id.clone(),
|
||||
LanguageModelToolResult {
|
||||
@@ -118,7 +102,7 @@ impl ToolUseState {
|
||||
}
|
||||
}
|
||||
}
|
||||
Role::System => {}
|
||||
Role::System | Role::User => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,20 +212,26 @@ impl ToolUseState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tool_results_for_message(&self, message_id: MessageId) -> Vec<&LanguageModelToolResult> {
|
||||
let empty = Vec::new();
|
||||
pub fn tool_results_for_message(
|
||||
&self,
|
||||
assistant_message_id: MessageId,
|
||||
) -> Vec<&LanguageModelToolResult> {
|
||||
let Some(tool_uses) = self
|
||||
.tool_uses_by_assistant_message
|
||||
.get(&assistant_message_id)
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
self.tool_uses_by_user_message
|
||||
.get(&message_id)
|
||||
.unwrap_or(&empty)
|
||||
tool_uses
|
||||
.iter()
|
||||
.filter_map(|tool_use_id| self.tool_results.get(&tool_use_id))
|
||||
.filter_map(|tool_use| self.tool_results.get(&tool_use.id))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
|
||||
self.tool_uses_by_user_message
|
||||
.get(&message_id)
|
||||
pub fn message_has_tool_results(&self, assistant_message_id: MessageId) -> bool {
|
||||
self.tool_uses_by_assistant_message
|
||||
.get(&assistant_message_id)
|
||||
.map_or(false, |results| !results.is_empty())
|
||||
}
|
||||
|
||||
@@ -293,14 +283,6 @@ impl ToolUseState {
|
||||
self.tool_use_metadata_by_id
|
||||
.insert(tool_use.id.clone(), metadata);
|
||||
|
||||
// The tool use is being requested by the Assistant, so we want to
|
||||
// attach the tool results to the next user message.
|
||||
let next_user_message_id = MessageId(assistant_message_id.0 + 1);
|
||||
self.tool_uses_by_user_message
|
||||
.entry(next_user_message_id)
|
||||
.or_default()
|
||||
.push(tool_use.id.clone());
|
||||
|
||||
PendingToolUseStatus::Idle
|
||||
} else {
|
||||
PendingToolUseStatus::InputStillStreaming
|
||||
@@ -466,31 +448,49 @@ impl ToolUseState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attach_tool_results(
|
||||
pub fn has_tool_results(&self, assistant_message_id: MessageId) -> bool {
|
||||
self.tool_uses_by_assistant_message
|
||||
.contains_key(&assistant_message_id)
|
||||
}
|
||||
|
||||
pub fn tool_results_message(
|
||||
&self,
|
||||
message_id: MessageId,
|
||||
request_message: &mut LanguageModelRequestMessage,
|
||||
) {
|
||||
if let Some(tool_uses) = self.tool_uses_by_user_message.get(&message_id) {
|
||||
for tool_use_id in tool_uses {
|
||||
if let Some(tool_result) = self.tool_results.get(tool_use_id) {
|
||||
request_message.content.push(MessageContent::ToolResult(
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
tool_name: tool_result.tool_name.clone(),
|
||||
is_error: tool_result.is_error,
|
||||
content: if tool_result.content.is_empty() {
|
||||
// Surprisingly, the API fails if we return an empty string here.
|
||||
// It thinks we are sending a tool use without a tool result.
|
||||
"<Tool returned an empty string>".into()
|
||||
} else {
|
||||
tool_result.content.clone()
|
||||
},
|
||||
assistant_message_id: MessageId,
|
||||
) -> Option<LanguageModelRequestMessage> {
|
||||
let tool_uses = self
|
||||
.tool_uses_by_assistant_message
|
||||
.get(&assistant_message_id)?;
|
||||
|
||||
if tool_uses.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut request_message = LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![],
|
||||
cache: false,
|
||||
};
|
||||
|
||||
for tool_use in tool_uses {
|
||||
if let Some(tool_result) = self.tool_results.get(&tool_use.id) {
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::ToolResult(LanguageModelToolResult {
|
||||
tool_use_id: tool_use.id.clone(),
|
||||
tool_name: tool_result.tool_name.clone(),
|
||||
is_error: tool_result.is_error,
|
||||
content: if tool_result.content.is_empty() {
|
||||
// Surprisingly, the API fails if we return an empty string here.
|
||||
// It thinks we are sending a tool use without a tool result.
|
||||
"<Tool returned an empty string>".into()
|
||||
} else {
|
||||
tool_result.content.clone()
|
||||
},
|
||||
));
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Some(request_message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
parking_lot.workspace = true
|
||||
project.workspace = true
|
||||
prompt_library.workspace = true
|
||||
rules_library.workspace = true
|
||||
prompt_store.workspace = true
|
||||
proto.workspace = true
|
||||
rope.workspace = true
|
||||
|
||||
@@ -101,7 +101,7 @@ pub fn init(
|
||||
SlashCommandSettings::register(cx);
|
||||
|
||||
assistant_context_editor::init(client.clone(), cx);
|
||||
prompt_library::init(cx);
|
||||
rules_library::init(cx);
|
||||
init_language_model_settings(cx);
|
||||
assistant_slash_command::init(cx);
|
||||
assistant_tool::init(cx);
|
||||
|
||||
@@ -25,8 +25,8 @@ use language_model::{
|
||||
AuthenticateError, ConfiguredModel, LanguageModelProviderId, LanguageModelRegistry,
|
||||
};
|
||||
use project::Project;
|
||||
use prompt_library::{PromptLibrary, open_prompt_library};
|
||||
use prompt_store::{PromptBuilder, PromptId, UserPromptId};
|
||||
use rules_library::{RulesLibrary, open_rules_library};
|
||||
|
||||
use search::{BufferSearchBar, buffer_search::DivRegistrar};
|
||||
use settings::{Settings, update_settings_file};
|
||||
@@ -43,7 +43,7 @@ use workspace::{
|
||||
dock::{DockPosition, Panel, PanelEvent},
|
||||
pane,
|
||||
};
|
||||
use zed_actions::assistant::{InlineAssist, OpenPromptLibrary, ShowConfiguration, ToggleFocus};
|
||||
use zed_actions::assistant::{InlineAssist, OpenRulesLibrary, ShowConfiguration, ToggleFocus};
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
|
||||
@@ -57,11 +57,11 @@ pub fn init(cx: &mut App) {
|
||||
.register_action(AssistantPanel::show_configuration)
|
||||
.register_action(AssistantPanel::create_new_context)
|
||||
.register_action(AssistantPanel::restart_context_servers)
|
||||
.register_action(|workspace, action: &OpenPromptLibrary, window, cx| {
|
||||
.register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
|
||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.deploy_prompt_library(action, window, cx)
|
||||
panel.deploy_rules_library(action, window, cx)
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -272,8 +272,8 @@ impl AssistantPanel {
|
||||
.action("New Chat", Box::new(NewChat))
|
||||
.action("History", Box::new(DeployHistory))
|
||||
.action(
|
||||
"Prompt Library",
|
||||
Box::new(OpenPromptLibrary::default()),
|
||||
"Rules Library",
|
||||
Box::new(OpenRulesLibrary::default()),
|
||||
)
|
||||
.action("Configure", Box::new(ShowConfiguration))
|
||||
.action(zoom_label, Box::new(ToggleZoom))
|
||||
@@ -1043,13 +1043,13 @@ impl AssistantPanel {
|
||||
}
|
||||
}
|
||||
|
||||
fn deploy_prompt_library(
|
||||
fn deploy_rules_library(
|
||||
&mut self,
|
||||
action: &OpenPromptLibrary,
|
||||
action: &OpenRulesLibrary,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
open_prompt_library(
|
||||
open_rules_library(
|
||||
self.languages.clone(),
|
||||
Box::new(PromptLibraryInlineAssist),
|
||||
Arc::new(|| {
|
||||
@@ -1235,7 +1235,7 @@ impl Render for AssistantPanel {
|
||||
this.show_configuration_tab(window, cx)
|
||||
}))
|
||||
.on_action(cx.listener(AssistantPanel::deploy_history))
|
||||
.on_action(cx.listener(AssistantPanel::deploy_prompt_library))
|
||||
.on_action(cx.listener(AssistantPanel::deploy_rules_library))
|
||||
.child(registrar.size_full().child(self.pane.clone()))
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -1350,13 +1350,13 @@ impl Focusable for AssistantPanel {
|
||||
|
||||
struct PromptLibraryInlineAssist;
|
||||
|
||||
impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
|
||||
impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
|
||||
fn assist(
|
||||
&self,
|
||||
prompt_editor: &Entity<Editor>,
|
||||
initial_prompt: Option<String>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<PromptLibrary>,
|
||||
cx: &mut Context<RulesLibrary>,
|
||||
) {
|
||||
InlineAssistant::update_global(cx, |assistant, cx| {
|
||||
assistant.assist(&prompt_editor, None, None, initial_prompt, window, cx)
|
||||
|
||||
@@ -22,7 +22,7 @@ use feature_flags::{
|
||||
};
|
||||
use fs::Fs;
|
||||
use futures::{
|
||||
SinkExt, Stream, StreamExt,
|
||||
SinkExt, Stream, StreamExt, TryStreamExt as _,
|
||||
channel::mpsc,
|
||||
future::{BoxFuture, LocalBoxFuture},
|
||||
join,
|
||||
@@ -3056,7 +3056,8 @@ impl CodegenAlternative {
|
||||
let mut response_latency = None;
|
||||
let request_start = Instant::now();
|
||||
let diff = async {
|
||||
let chunks = StripInvalidSpans::new(stream?.stream);
|
||||
let chunks =
|
||||
StripInvalidSpans::new(stream?.stream.map_err(|e| e.into()));
|
||||
futures::pin_mut!(chunks);
|
||||
let mut diff = StreamingDiff::new(selected_text.to_string());
|
||||
let mut line_diff = LineDiff::default();
|
||||
|
||||
@@ -28,6 +28,7 @@ serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
text.workspace = true
|
||||
util.workspace = true
|
||||
workspace.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -10,14 +10,16 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::AnyElement;
|
||||
use gpui::AnyWindowHandle;
|
||||
use gpui::Context;
|
||||
use gpui::IntoElement;
|
||||
use gpui::Window;
|
||||
use gpui::{App, Entity, SharedString, Task};
|
||||
use gpui::{App, Entity, SharedString, Task, WeakEntity};
|
||||
use icons::IconName;
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
use project::Project;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub use crate::action_log::*;
|
||||
pub use crate::tool_registry::*;
|
||||
@@ -65,6 +67,7 @@ pub trait ToolCard: 'static + Sized {
|
||||
&mut self,
|
||||
status: &ToolUseStatus,
|
||||
window: &mut Window,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement;
|
||||
}
|
||||
@@ -76,6 +79,7 @@ pub struct AnyToolCard {
|
||||
entity: gpui::AnyEntity,
|
||||
status: &ToolUseStatus,
|
||||
window: &mut Window,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut App,
|
||||
) -> AnyElement,
|
||||
}
|
||||
@@ -86,11 +90,14 @@ impl<T: ToolCard> From<Entity<T>> for AnyToolCard {
|
||||
entity: gpui::AnyEntity,
|
||||
status: &ToolUseStatus,
|
||||
window: &mut Window,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut App,
|
||||
) -> AnyElement {
|
||||
let entity = entity.downcast::<T>().unwrap();
|
||||
entity.update(cx, |entity, cx| {
|
||||
entity.render(status, window, cx).into_any_element()
|
||||
entity
|
||||
.render(status, window, workspace, cx)
|
||||
.into_any_element()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -102,8 +109,14 @@ impl<T: ToolCard> From<Entity<T>> for AnyToolCard {
|
||||
}
|
||||
|
||||
impl AnyToolCard {
|
||||
pub fn render(&self, status: &ToolUseStatus, window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
(self.render)(self.entity.clone(), status, window, cx)
|
||||
pub fn render(
|
||||
&self,
|
||||
status: &ToolUseStatus,
|
||||
window: &mut Window,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut App,
|
||||
) -> AnyElement {
|
||||
(self.render)(self.entity.clone(), status, window, workspace, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +176,7 @@ pub trait Tool: 'static + Send + Sync {
|
||||
messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult;
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ path = "src/assistant_tools.rs"
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
collections.workspace = true
|
||||
component.workspace = true
|
||||
editor.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
html_to_markdown.workspace = true
|
||||
@@ -35,6 +37,7 @@ serde_json.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
web_search.workspace = true
|
||||
workspace.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
worktree.workspace = true
|
||||
zed_llm_client.workspace = true
|
||||
|
||||
@@ -9,12 +9,12 @@ mod delete_path_tool;
|
||||
mod diagnostics_tool;
|
||||
mod edit_file_tool;
|
||||
mod fetch_tool;
|
||||
mod find_path_tool;
|
||||
mod grep_tool;
|
||||
mod list_directory_tool;
|
||||
mod move_path_tool;
|
||||
mod now_tool;
|
||||
mod open_tool;
|
||||
mod path_search_tool;
|
||||
mod read_file_tool;
|
||||
mod rename_tool;
|
||||
mod replace;
|
||||
@@ -45,18 +45,21 @@ use crate::delete_path_tool::DeletePathTool;
|
||||
use crate::diagnostics_tool::DiagnosticsTool;
|
||||
use crate::edit_file_tool::EditFileTool;
|
||||
use crate::fetch_tool::FetchTool;
|
||||
use crate::find_path_tool::FindPathTool;
|
||||
use crate::grep_tool::GrepTool;
|
||||
use crate::list_directory_tool::ListDirectoryTool;
|
||||
use crate::now_tool::NowTool;
|
||||
use crate::open_tool::OpenTool;
|
||||
use crate::path_search_tool::PathSearchTool;
|
||||
use crate::read_file_tool::ReadFileTool;
|
||||
use crate::rename_tool::RenameTool;
|
||||
use crate::symbol_info_tool::SymbolInfoTool;
|
||||
use crate::terminal_tool::TerminalTool;
|
||||
use crate::thinking_tool::ThinkingTool;
|
||||
|
||||
pub use path_search_tool::PathSearchToolInput;
|
||||
pub use create_file_tool::CreateFileToolInput;
|
||||
pub use edit_file_tool::EditFileToolInput;
|
||||
pub use find_path_tool::FindPathToolInput;
|
||||
pub use read_file_tool::ReadFileToolInput;
|
||||
|
||||
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
|
||||
assistant_tool::init(cx);
|
||||
@@ -78,7 +81,7 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
|
||||
registry.register_tool(OpenTool);
|
||||
registry.register_tool(CodeSymbolsTool);
|
||||
registry.register_tool(ContentsTool);
|
||||
registry.register_tool(PathSearchTool);
|
||||
registry.register_tool(FindPathTool);
|
||||
registry.register_tool(ReadFileTool);
|
||||
registry.register_tool(GrepTool);
|
||||
registry.register_tool(RenameTool);
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult, ToolWorkingSet};
|
||||
use futures::future::join_all;
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
@@ -97,7 +97,7 @@ pub struct BatchToolInput {
|
||||
/// }
|
||||
/// },
|
||||
/// {
|
||||
/// "name": "path_search",
|
||||
/// "name": "find_path",
|
||||
/// "input": {
|
||||
/// "glob": "**/*test*.rs"
|
||||
/// }
|
||||
@@ -218,6 +218,7 @@ impl Tool for BatchTool {
|
||||
messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<BatchToolInput>(input) {
|
||||
@@ -258,7 +259,9 @@ impl Tool for BatchTool {
|
||||
let action_log = action_log.clone();
|
||||
let messages = messages.clone();
|
||||
let tool_result = cx
|
||||
.update(|cx| tool.run(invocation.input, &messages, project, action_log, cx))
|
||||
.update(|cx| {
|
||||
tool.run(invocation.input, &messages, project, action_log, window, cx)
|
||||
})
|
||||
.map_err(|err| anyhow!("Failed to start tool '{}': {}", tool_name, err))?;
|
||||
|
||||
tasks.push(tool_result.output);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use language::{self, Anchor, Buffer, ToPointUtf16};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::{self, LspAction, Project};
|
||||
@@ -140,6 +140,7 @@ impl Tool for CodeActionTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<CodeActionToolInput>(input) {
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use collections::IndexMap;
|
||||
use gpui::{App, AsyncApp, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, AsyncApp, Entity, Task};
|
||||
use language::{OutlineItem, ParseStatus, Point};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::{Project, Symbol};
|
||||
@@ -128,6 +128,7 @@ impl Tool for CodeSymbolsTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<CodeSymbolsInput>(input) {
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use itertools::Itertools;
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -102,6 +102,7 @@ impl Tool for ContentsTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<ContentsToolInput>(input) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::AnyWindowHandle;
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
@@ -76,6 +77,7 @@ impl Tool for CopyPathTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<CopyPathToolInput>(input) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::AnyWindowHandle;
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
@@ -67,6 +68,7 @@ impl Tool for CreateDirectoryTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<CreateDirectoryToolInput>(input) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::AnyWindowHandle;
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
@@ -23,6 +24,9 @@ pub struct CreateFileToolInput {
|
||||
///
|
||||
/// You can create a new file by providing a path of "directory1/new_file.txt"
|
||||
/// </example>
|
||||
///
|
||||
/// Make sure to include this field before the `contents` field in the input object
|
||||
/// so that we can display it immediately.
|
||||
pub path: String,
|
||||
|
||||
/// The text contents of the file to create.
|
||||
@@ -89,6 +93,7 @@ impl Tool for CreateFileTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<CreateFileToolInput>(input) {
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use futures::{SinkExt, StreamExt, channel::mpsc};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::{Project, ProjectPath};
|
||||
use schemars::JsonSchema;
|
||||
@@ -62,6 +62,7 @@ impl Tool for DeletePathTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let path_str = match serde_json::from_value::<DeletePathToolInput>(input) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use language::{DiagnosticSeverity, OffsetRangeExt};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -82,6 +82,7 @@ impl Tool for DiagnosticsTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
match serde_json::from_value::<DiagnosticsToolInput>(input)
|
||||
|
||||
@@ -1,18 +1,40 @@
|
||||
use crate::{replace::replace_with_flexible_indent, schema::json_schema_for};
|
||||
use crate::{
|
||||
replace::{replace_exact, replace_with_flexible_indent},
|
||||
schema::json_schema_for,
|
||||
};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, AppContext, AsyncApp, Entity, Task};
|
||||
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolCard, ToolResult, ToolUseStatus};
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use editor::{Editor, EditorMode, MultiBuffer, PathKey};
|
||||
use gpui::{
|
||||
AnyWindowHandle, App, AppContext, AsyncApp, Context, Entity, EntityId, Task, WeakEntity,
|
||||
};
|
||||
use language::{
|
||||
Anchor, Buffer, Capability, LanguageRegistry, LineEnding, OffsetRangeExt, Rope, TextBuffer,
|
||||
};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use ui::IconName;
|
||||
|
||||
use crate::replace::replace_exact;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use ui::{Disclosure, Tooltip, Window, prelude::*};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct EditFileToolInput {
|
||||
/// A user-friendly markdown description of what's being replaced. This will be shown in the UI.
|
||||
///
|
||||
/// <example>Fix API endpoint URLs</example>
|
||||
/// <example>Update copyright year in `page_footer`</example>
|
||||
///
|
||||
/// Make sure to include this field before all the others in the input object
|
||||
/// so that we can display it immediately.
|
||||
pub display_description: String,
|
||||
|
||||
/// The full path of the file to modify in the project.
|
||||
///
|
||||
/// WARNING: When specifying which file path need changing, you MUST
|
||||
@@ -34,12 +56,6 @@ pub struct EditFileToolInput {
|
||||
/// </example>
|
||||
pub path: PathBuf,
|
||||
|
||||
/// A user-friendly markdown description of what's being replaced. This will be shown in the UI.
|
||||
///
|
||||
/// <example>Fix API endpoint URLs</example>
|
||||
/// <example>Update copyright year in `page_footer`</example>
|
||||
pub display_description: String,
|
||||
|
||||
/// The text to replace.
|
||||
pub old_string: String,
|
||||
|
||||
@@ -113,6 +129,7 @@ impl Tool for EditFileTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<EditFileToolInput>(input) {
|
||||
@@ -120,7 +137,18 @@ impl Tool for EditFileTool {
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))).into(),
|
||||
};
|
||||
|
||||
cx.spawn(async move |cx: &mut AsyncApp| {
|
||||
let card = window.and_then(|window| {
|
||||
window
|
||||
.update(cx, |_, window, cx| {
|
||||
cx.new(|cx| {
|
||||
EditFileToolCard::new(input.path.clone(), project.clone(), window, cx)
|
||||
})
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
let card_clone = card.clone();
|
||||
let task = cx.spawn(async move |cx: &mut AsyncApp| {
|
||||
let project_path = project.read_with(cx, |project, cx| {
|
||||
project
|
||||
.find_project_path(&input.path, cx)
|
||||
@@ -128,26 +156,38 @@ impl Tool for EditFileTool {
|
||||
})??;
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_buffer(project_path, cx))?
|
||||
.update(cx, |project, cx| {
|
||||
project.open_buffer(project_path.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
|
||||
|
||||
if input.old_string.is_empty() {
|
||||
return Err(anyhow!("`old_string` cannot be empty. Use a different tool if you want to create a file."));
|
||||
return Err(anyhow!(
|
||||
"`old_string` can't be empty, use another tool if you want to create a file."
|
||||
));
|
||||
}
|
||||
|
||||
if input.old_string == input.new_string {
|
||||
return Err(anyhow!("The `old_string` and `new_string` are identical, so no changes would be made."));
|
||||
return Err(anyhow!(
|
||||
"The `old_string` and `new_string` are identical, so no changes would be made."
|
||||
));
|
||||
}
|
||||
|
||||
let result = cx
|
||||
.background_spawn(async move {
|
||||
// Try to match exactly
|
||||
let diff = replace_exact(&input.old_string, &input.new_string, &snapshot)
|
||||
.await
|
||||
// If that fails, try being flexible about indentation
|
||||
.or_else(|| replace_with_flexible_indent(&input.old_string, &input.new_string, &snapshot))?;
|
||||
.await
|
||||
// If that fails, try being flexible about indentation
|
||||
.or_else(|| {
|
||||
replace_with_flexible_indent(
|
||||
&input.old_string,
|
||||
&input.new_string,
|
||||
&snapshot,
|
||||
)
|
||||
})?;
|
||||
|
||||
if diff.edits.is_empty() {
|
||||
return None;
|
||||
@@ -177,41 +217,409 @@ impl Tool for EditFileTool {
|
||||
}
|
||||
})?;
|
||||
|
||||
return Err(err)
|
||||
return Err(err);
|
||||
};
|
||||
|
||||
let snapshot = cx.update(|cx| {
|
||||
action_log.update(cx, |log, cx| {
|
||||
log.track_buffer(buffer.clone(), cx)
|
||||
});
|
||||
action_log.update(cx, |log, cx| log.track_buffer(buffer.clone(), cx));
|
||||
|
||||
let snapshot = buffer.update(cx, |buffer, cx| {
|
||||
buffer.finalize_last_transaction();
|
||||
buffer.apply_diff(diff, cx);
|
||||
buffer.finalize_last_transaction();
|
||||
buffer.snapshot()
|
||||
});
|
||||
action_log.update(cx, |log, cx| {
|
||||
log.buffer_edited(buffer.clone(), cx)
|
||||
});
|
||||
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
|
||||
snapshot
|
||||
})?;
|
||||
|
||||
project.update( cx, |project, cx| {
|
||||
project.save_buffer(buffer, cx)
|
||||
})?.await?;
|
||||
project
|
||||
.update(cx, |project, cx| project.save_buffer(buffer, cx))?
|
||||
.await?;
|
||||
|
||||
let diff_str = cx.background_spawn(async move {
|
||||
let new_text = snapshot.text();
|
||||
language::unified_diff(&old_text, &new_text)
|
||||
}).await;
|
||||
let new_text = snapshot.text();
|
||||
let diff_str = cx
|
||||
.background_spawn({
|
||||
let old_text = old_text.clone();
|
||||
let new_text = new_text.clone();
|
||||
async move { language::unified_diff(&old_text, &new_text) }
|
||||
})
|
||||
.await;
|
||||
|
||||
if let Some(card) = card_clone {
|
||||
card.update(cx, |card, cx| {
|
||||
card.set_diff(project_path.path.clone(), old_text, new_text, cx);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
Ok(format!("Edited {}:\n\n```diff\n{}\n```", input.path.display(), diff_str))
|
||||
Ok(format!(
|
||||
"Edited {}:\n\n```diff\n{}\n```",
|
||||
input.path.display(),
|
||||
diff_str
|
||||
))
|
||||
});
|
||||
|
||||
}).into()
|
||||
ToolResult {
|
||||
output: task,
|
||||
card: card.map(AnyToolCard::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditFileToolCard {
|
||||
path: PathBuf,
|
||||
editor: Entity<Editor>,
|
||||
multibuffer: Entity<MultiBuffer>,
|
||||
project: Entity<Project>,
|
||||
diff_task: Option<Task<Result<()>>>,
|
||||
preview_expanded: bool,
|
||||
full_height_expanded: bool,
|
||||
editor_unique_id: EntityId,
|
||||
}
|
||||
|
||||
impl EditFileToolCard {
|
||||
fn new(path: PathBuf, project: Entity<Project>, window: &mut Window, cx: &mut App) -> Self {
|
||||
let multibuffer = cx.new(|_| MultiBuffer::without_headers(Capability::ReadOnly));
|
||||
let editor = cx.new(|cx| {
|
||||
let mut editor = Editor::new(
|
||||
EditorMode::Full {
|
||||
scale_ui_elements_with_buffer_font_size: false,
|
||||
show_active_line_background: false,
|
||||
sized_by_content: true,
|
||||
},
|
||||
multibuffer.clone(),
|
||||
Some(project.clone()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.set_show_scrollbars(false, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
editor.disable_inline_diagnostics();
|
||||
editor.disable_scrolling(cx);
|
||||
editor.disable_expand_excerpt_buttons(cx);
|
||||
editor.set_show_breakpoints(false, cx);
|
||||
editor.set_show_code_actions(false, cx);
|
||||
editor.set_show_git_diff_gutter(false, cx);
|
||||
editor.set_expand_all_diff_hunks(cx);
|
||||
editor
|
||||
});
|
||||
Self {
|
||||
editor_unique_id: editor.entity_id(),
|
||||
path,
|
||||
project,
|
||||
editor,
|
||||
multibuffer,
|
||||
diff_task: None,
|
||||
preview_expanded: true,
|
||||
full_height_expanded: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_diff(
|
||||
&mut self,
|
||||
path: Arc<Path>,
|
||||
old_text: String,
|
||||
new_text: String,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let language_registry = self.project.read(cx).languages().clone();
|
||||
self.diff_task = Some(cx.spawn(async move |this, cx| {
|
||||
let buffer = build_buffer(new_text, path.clone(), &language_registry, cx).await?;
|
||||
let buffer_diff = build_buffer_diff(old_text, &buffer, &language_registry, cx).await?;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.multibuffer.update(cx, |multibuffer, cx| {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let diff = buffer_diff.read(cx);
|
||||
let diff_hunk_ranges = diff
|
||||
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx)
|
||||
.map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot))
|
||||
.collect::<Vec<_>>();
|
||||
let (_, is_newly_added) = multibuffer.set_excerpts_for_path(
|
||||
PathKey::for_buffer(&buffer, cx),
|
||||
buffer,
|
||||
diff_hunk_ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
cx,
|
||||
);
|
||||
debug_assert!(is_newly_added);
|
||||
multibuffer.add_diff(buffer_diff, cx);
|
||||
});
|
||||
cx.notify();
|
||||
})
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolCard for EditFileToolCard {
|
||||
fn render(
|
||||
&mut self,
|
||||
status: &ToolUseStatus,
|
||||
window: &mut Window,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let failed = matches!(status, ToolUseStatus::Error(_));
|
||||
|
||||
let path_label_button = h_flex()
|
||||
.id(("edit-tool-path-label-button", self.editor_unique_id))
|
||||
.w_full()
|
||||
.max_w_full()
|
||||
.px_1()
|
||||
.gap_0p5()
|
||||
.cursor_pointer()
|
||||
.rounded_sm()
|
||||
.opacity(0.8)
|
||||
.hover(|label| {
|
||||
label
|
||||
.opacity(1.)
|
||||
.bg(cx.theme().colors().element_hover.opacity(0.5))
|
||||
})
|
||||
.tooltip(Tooltip::text("Jump to File"))
|
||||
.child(
|
||||
h_flex()
|
||||
.child(
|
||||
Icon::new(IconName::Pencil)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_size(rems(0.8125))
|
||||
.child(self.path.display().to_string())
|
||||
.ml_1p5()
|
||||
.mr_0p5(),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ArrowUpRight)
|
||||
.size(IconSize::XSmall)
|
||||
.color(Color::Ignored),
|
||||
),
|
||||
)
|
||||
.on_click({
|
||||
let path = self.path.clone();
|
||||
let workspace = workspace.clone();
|
||||
move |_, window, cx| {
|
||||
workspace
|
||||
.update(cx, {
|
||||
|workspace, cx| {
|
||||
let Some(project_path) =
|
||||
workspace.project().read(cx).find_project_path(&path, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let open_task =
|
||||
workspace.open_path(project_path, None, true, window, cx);
|
||||
window
|
||||
.spawn(cx, async move |cx| {
|
||||
let item = open_task.await?;
|
||||
if let Some(active_editor) = item.downcast::<Editor>() {
|
||||
active_editor
|
||||
.update_in(cx, |editor, window, cx| {
|
||||
editor.go_to_singleton_buffer_point(
|
||||
language::Point::new(0, 0),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.into_any_element();
|
||||
|
||||
let codeblock_header_bg = cx
|
||||
.theme()
|
||||
.colors()
|
||||
.element_background
|
||||
.blend(cx.theme().colors().editor_foreground.opacity(0.025));
|
||||
|
||||
let codeblock_header = h_flex()
|
||||
.flex_none()
|
||||
.p_1()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.rounded_t_md()
|
||||
.when(!failed, |header| header.bg(codeblock_header_bg))
|
||||
.child(path_label_button)
|
||||
.map(|container| {
|
||||
if failed {
|
||||
container.child(
|
||||
Icon::new(IconName::Close)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
)
|
||||
} else {
|
||||
container.child(
|
||||
Disclosure::new(
|
||||
("edit-file-disclosure", self.editor_unique_id),
|
||||
self.preview_expanded,
|
||||
)
|
||||
.opened_icon(IconName::ChevronUp)
|
||||
.closed_icon(IconName::ChevronDown)
|
||||
.on_click(cx.listener(
|
||||
move |this, _event, _window, _cx| {
|
||||
this.preview_expanded = !this.preview_expanded;
|
||||
},
|
||||
)),
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let editor = self.editor.update(cx, |editor, cx| {
|
||||
editor.render(window, cx).into_any_element()
|
||||
});
|
||||
|
||||
let (full_height_icon, full_height_tooltip_label) = if self.full_height_expanded {
|
||||
(IconName::ChevronUp, "Collapse Code Block")
|
||||
} else {
|
||||
(IconName::ChevronDown, "Expand Code Block")
|
||||
};
|
||||
|
||||
let gradient_overlay = div()
|
||||
.absolute()
|
||||
.bottom_0()
|
||||
.left_0()
|
||||
.w_full()
|
||||
.h_2_5()
|
||||
.rounded_b_lg()
|
||||
.bg(gpui::linear_gradient(
|
||||
0.,
|
||||
gpui::linear_color_stop(cx.theme().colors().editor_background, 0.),
|
||||
gpui::linear_color_stop(cx.theme().colors().editor_background.opacity(0.), 1.),
|
||||
));
|
||||
|
||||
let border_color = cx.theme().colors().border.opacity(0.6);
|
||||
|
||||
v_flex()
|
||||
.mb_2()
|
||||
.border_1()
|
||||
.when(failed, |card| card.border_dashed())
|
||||
.border_color(border_color)
|
||||
.rounded_lg()
|
||||
.overflow_hidden()
|
||||
.child(codeblock_header)
|
||||
.when(!failed && self.preview_expanded, |card| {
|
||||
card.child(
|
||||
v_flex()
|
||||
.relative()
|
||||
.overflow_hidden()
|
||||
.border_t_1()
|
||||
.border_color(border_color)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.map(|editor_container| {
|
||||
if self.full_height_expanded {
|
||||
editor_container.h_full()
|
||||
} else {
|
||||
editor_container.max_h_64()
|
||||
}
|
||||
})
|
||||
.child(div().pl_1().child(editor))
|
||||
.when(!self.full_height_expanded, |editor_container| {
|
||||
editor_container.child(gradient_overlay)
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(!failed && self.preview_expanded, |card| {
|
||||
card.child(
|
||||
h_flex()
|
||||
.id(("edit-tool-card-inner-hflex", self.editor_unique_id))
|
||||
.flex_none()
|
||||
.cursor_pointer()
|
||||
.h_5()
|
||||
.justify_center()
|
||||
.rounded_b_md()
|
||||
.border_t_1()
|
||||
.border_color(border_color)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.hover(|style| style.bg(cx.theme().colors().element_hover.opacity(0.1)))
|
||||
.child(
|
||||
Icon::new(full_height_icon)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.tooltip(Tooltip::text(full_height_tooltip_label))
|
||||
.on_click(cx.listener(move |this, _event, _window, _cx| {
|
||||
this.full_height_expanded = !this.full_height_expanded;
|
||||
})),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn build_buffer(
|
||||
mut text: String,
|
||||
path: Arc<Path>,
|
||||
language_registry: &Arc<language::LanguageRegistry>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Entity<Buffer>> {
|
||||
let line_ending = LineEnding::detect(&text);
|
||||
LineEnding::normalize(&mut text);
|
||||
let text = Rope::from(text);
|
||||
let language = cx
|
||||
.update(|_cx| language_registry.language_for_file_path(&path))?
|
||||
.await
|
||||
.ok();
|
||||
let buffer = cx.new(|cx| {
|
||||
let buffer = TextBuffer::new_normalized(
|
||||
0,
|
||||
cx.entity_id().as_non_zero_u64().into(),
|
||||
line_ending,
|
||||
text,
|
||||
);
|
||||
let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
|
||||
buffer.set_language(language, cx);
|
||||
buffer
|
||||
})?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
async fn build_buffer_diff(
|
||||
mut old_text: String,
|
||||
buffer: &Entity<Buffer>,
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Entity<BufferDiff>> {
|
||||
LineEnding::normalize(&mut old_text);
|
||||
|
||||
let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
|
||||
|
||||
let base_buffer = cx
|
||||
.update(|cx| {
|
||||
Buffer::build_snapshot(
|
||||
old_text.clone().into(),
|
||||
buffer.language().cloned(),
|
||||
Some(language_registry.clone()),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
|
||||
let diff_snapshot = cx
|
||||
.update(|cx| {
|
||||
BufferDiffSnapshot::new_with_base_buffer(
|
||||
buffer.text.clone(),
|
||||
Some(old_text.into()),
|
||||
base_buffer,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
|
||||
cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new(&buffer.text, cx);
|
||||
diff.set_snapshot(diff_snapshot, &buffer.text, cx);
|
||||
diff
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::schema::json_schema_for;
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use futures::AsyncReadExt as _;
|
||||
use gpui::{App, AppContext as _, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, AppContext as _, Entity, Task};
|
||||
use html_to_markdown::{TagHandler, convert_html_to_markdown, markdown};
|
||||
use http_client::{AsyncBody, HttpClientWithUrl};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -145,6 +145,7 @@ impl Tool for FetchTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<FetchToolInput>(input) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
@@ -12,7 +12,7 @@ use util::paths::PathMatcher;
|
||||
use worktree::Snapshot;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct PathSearchToolInput {
|
||||
pub struct FindPathToolInput {
|
||||
/// The glob to match against every path in the project.
|
||||
///
|
||||
/// <example>
|
||||
@@ -34,11 +34,11 @@ pub struct PathSearchToolInput {
|
||||
|
||||
const RESULTS_PER_PAGE: usize = 50;
|
||||
|
||||
pub struct PathSearchTool;
|
||||
pub struct FindPathTool;
|
||||
|
||||
impl Tool for PathSearchTool {
|
||||
impl Tool for FindPathTool {
|
||||
fn name(&self) -> String {
|
||||
"path_search".into()
|
||||
"find_path".into()
|
||||
}
|
||||
|
||||
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
||||
@@ -46,7 +46,7 @@ impl Tool for PathSearchTool {
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
include_str!("./path_search_tool/description.md").into()
|
||||
include_str!("./find_path_tool/description.md").into()
|
||||
}
|
||||
|
||||
fn icon(&self) -> IconName {
|
||||
@@ -54,11 +54,11 @@ impl Tool for PathSearchTool {
|
||||
}
|
||||
|
||||
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
|
||||
json_schema_for::<PathSearchToolInput>(format)
|
||||
json_schema_for::<FindPathToolInput>(format)
|
||||
}
|
||||
|
||||
fn ui_text(&self, input: &serde_json::Value) -> String {
|
||||
match serde_json::from_value::<PathSearchToolInput>(input.clone()) {
|
||||
match serde_json::from_value::<FindPathToolInput>(input.clone()) {
|
||||
Ok(input) => format!("Find paths matching “`{}`”", input.glob),
|
||||
Err(_) => "Search paths".to_string(),
|
||||
}
|
||||
@@ -70,9 +70,10 @@ impl Tool for PathSearchTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let (offset, glob) = match serde_json::from_value::<PathSearchToolInput>(input) {
|
||||
let (offset, glob) = match serde_json::from_value::<FindPathToolInput>(input) {
|
||||
Ok(input) => (input.offset, input.glob),
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))).into(),
|
||||
};
|
||||
@@ -143,7 +144,7 @@ mod test {
|
||||
use util::path;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_path_search_tool(cx: &mut TestAppContext) {
|
||||
async fn test_find_path_tool(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
@@ -1,4 +1,4 @@
|
||||
Fast file pattern matching tool that works with any codebase size
|
||||
Fast file path pattern matching tool that works with any codebase size
|
||||
|
||||
- Supports glob patterns like "**/*.js" or "src/**/*.ts"
|
||||
- Returns matching file paths sorted alphabetically
|
||||
@@ -2,7 +2,7 @@ use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use futures::StreamExt;
|
||||
use gpui::{App, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use language::OffsetRangeExt;
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::{
|
||||
@@ -20,6 +20,8 @@ use util::paths::PathMatcher;
|
||||
pub struct GrepToolInput {
|
||||
/// A regex pattern to search for in the entire project. Note that the regex
|
||||
/// will be parsed by the Rust `regex` crate.
|
||||
///
|
||||
/// Do NOT specify a path here! This will only be matched against the code **content**.
|
||||
pub regex: String,
|
||||
|
||||
/// A glob pattern for the paths of files to include in the search.
|
||||
@@ -96,6 +98,7 @@ impl Tool for GrepTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
const CONTEXT_LINES: u32 = 2;
|
||||
@@ -405,7 +408,7 @@ mod tests {
|
||||
) -> String {
|
||||
let tool = Arc::new(GrepTool);
|
||||
let action_log = cx.new(|_cx| ActionLog::new(project.clone()));
|
||||
let task = cx.update(|cx| tool.run(input, &[], project, action_log, cx));
|
||||
let task = cx.update(|cx| tool.run(input, &[], project, action_log, None, cx));
|
||||
|
||||
match task.output.await {
|
||||
Ok(result) => result,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
@@ -76,6 +76,7 @@ impl Tool for ListDirectoryTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<ListDirectoryToolInput>(input) {
|
||||
|
||||
@@ -1 +1 @@
|
||||
Lists files and directories in a given path. Prefer the `grep` or `path_search` tools when searching the codebase.
|
||||
Lists files and directories in a given path. Prefer the `grep` or `find_path` tools when searching the codebase.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
@@ -89,6 +89,7 @@ impl Tool for MovePathTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<MovePathToolInput>(input) {
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use chrono::{Local, Utc};
|
||||
use gpui::{App, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
@@ -59,6 +59,7 @@ impl Tool for NowTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
_cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input: NowToolInput = match serde_json::from_value(input) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
@@ -52,6 +52,7 @@ impl Tool for OpenTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input: OpenToolInput = match serde_json::from_value(input) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
|
||||
use indoc::formatdoc;
|
||||
use itertools::Itertools;
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -87,6 +88,7 @@ impl Tool for ReadFileTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<ReadFileToolInput>(input) {
|
||||
@@ -193,7 +195,7 @@ mod test {
|
||||
"path": "root/nonexistent_file.txt"
|
||||
});
|
||||
Arc::new(ReadFileTool)
|
||||
.run(input, &[], project.clone(), action_log, cx)
|
||||
.run(input, &[], project.clone(), action_log, None, cx)
|
||||
.output
|
||||
})
|
||||
.await;
|
||||
@@ -223,7 +225,7 @@ mod test {
|
||||
"path": "root/small_file.txt"
|
||||
});
|
||||
Arc::new(ReadFileTool)
|
||||
.run(input, &[], project.clone(), action_log, cx)
|
||||
.run(input, &[], project.clone(), action_log, None, cx)
|
||||
.output
|
||||
})
|
||||
.await;
|
||||
@@ -253,7 +255,7 @@ mod test {
|
||||
"path": "root/large_file.rs"
|
||||
});
|
||||
Arc::new(ReadFileTool)
|
||||
.run(input, &[], project.clone(), action_log.clone(), cx)
|
||||
.run(input, &[], project.clone(), action_log.clone(), None, cx)
|
||||
.output
|
||||
})
|
||||
.await;
|
||||
@@ -277,7 +279,7 @@ mod test {
|
||||
"offset": 1
|
||||
});
|
||||
Arc::new(ReadFileTool)
|
||||
.run(input, &[], project.clone(), action_log, cx)
|
||||
.run(input, &[], project.clone(), action_log, None, cx)
|
||||
.output
|
||||
})
|
||||
.await;
|
||||
@@ -323,7 +325,7 @@ mod test {
|
||||
"end_line": 4
|
||||
});
|
||||
Arc::new(ReadFileTool)
|
||||
.run(input, &[], project.clone(), action_log, cx)
|
||||
.run(input, &[], project.clone(), action_log, None, cx)
|
||||
.output
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use language::{self, Buffer, ToPointUtf16};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -87,6 +87,7 @@ impl Tool for RenameTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<RenameToolInput>(input) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, AsyncApp, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, AsyncApp, Entity, Task};
|
||||
use language::{self, Anchor, Buffer, BufferSnapshot, Location, Point, ToPoint, ToPointUtf16};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -121,6 +121,7 @@ impl Tool for SymbolInfoTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<SymbolInfoToolInput>(input) {
|
||||
|
||||
@@ -3,7 +3,7 @@ use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use futures::io::BufReader;
|
||||
use futures::{AsyncBufReadExt, AsyncReadExt, FutureExt};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
@@ -78,6 +78,7 @@ impl Tool for TerminalTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input: TerminalToolInput = match serde_json::from_value(input) {
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult};
|
||||
use gpui::{App, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
@@ -50,6 +50,7 @@ impl Tool for ThinkingTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
_cx: &mut App,
|
||||
) -> ToolResult {
|
||||
// This tool just "thinks out loud" and doesn't perform any actions.
|
||||
|
||||
@@ -5,13 +5,16 @@ use crate::ui::ToolCallCardHeader;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolCard, ToolResult, ToolUseStatus};
|
||||
use futures::{Future, FutureExt, TryFutureExt};
|
||||
use gpui::{App, AppContext, Context, Entity, IntoElement, Task, Window};
|
||||
use gpui::{
|
||||
AnyWindowHandle, App, AppContext, Context, Entity, IntoElement, Task, WeakEntity, Window,
|
||||
};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ui::{IconName, Tooltip, prelude::*};
|
||||
use web_search::WebSearchRegistry;
|
||||
use workspace::Workspace;
|
||||
use zed_llm_client::{WebSearchCitation, WebSearchResponse};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -54,6 +57,7 @@ impl Tool for WebSearchTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
let input = match serde_json::from_value::<WebSearchToolInput>(input) {
|
||||
@@ -111,6 +115,7 @@ impl ToolCard for WebSearchToolCard {
|
||||
&mut self,
|
||||
_status: &ToolUseStatus,
|
||||
_window: &mut Window,
|
||||
_workspace: WeakEntity<Workspace>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let header = match self.response.as_ref() {
|
||||
@@ -220,8 +225,13 @@ impl Component for WebSearchTool {
|
||||
div()
|
||||
.size_full()
|
||||
.child(in_progress_search.update(cx, |tool, cx| {
|
||||
tool.render(&ToolUseStatus::Pending, window, cx)
|
||||
.into_any_element()
|
||||
tool.render(
|
||||
&ToolUseStatus::Pending,
|
||||
window,
|
||||
WeakEntity::new_invalid(),
|
||||
cx,
|
||||
)
|
||||
.into_any_element()
|
||||
}))
|
||||
.into_any_element(),
|
||||
),
|
||||
@@ -230,8 +240,13 @@ impl Component for WebSearchTool {
|
||||
div()
|
||||
.size_full()
|
||||
.child(successful_search.update(cx, |tool, cx| {
|
||||
tool.render(&ToolUseStatus::Finished("".into()), window, cx)
|
||||
.into_any_element()
|
||||
tool.render(
|
||||
&ToolUseStatus::Finished("".into()),
|
||||
window,
|
||||
WeakEntity::new_invalid(),
|
||||
cx,
|
||||
)
|
||||
.into_any_element()
|
||||
}))
|
||||
.into_any_element(),
|
||||
),
|
||||
@@ -240,8 +255,13 @@ impl Component for WebSearchTool {
|
||||
div()
|
||||
.size_full()
|
||||
.child(error_search.update(cx, |tool, cx| {
|
||||
tool.render(&ToolUseStatus::Error("".into()), window, cx)
|
||||
.into_any_element()
|
||||
tool.render(
|
||||
&ToolUseStatus::Error("".into()),
|
||||
window,
|
||||
WeakEntity::new_invalid(),
|
||||
cx,
|
||||
)
|
||||
.into_any_element()
|
||||
}))
|
||||
.into_any_element(),
|
||||
),
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use assistant_tool::{ActionLog, Tool, ToolResult, ToolSource};
|
||||
use gpui::{App, Entity, Task};
|
||||
use gpui::{AnyWindowHandle, App, Entity, Task};
|
||||
use icons::IconName;
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -77,6 +77,7 @@ impl Tool for ContextServerTool {
|
||||
_messages: &[LanguageModelRequestMessage],
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_window: Option<AnyWindowHandle>,
|
||||
cx: &mut App,
|
||||
) -> ToolResult {
|
||||
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
|
||||
|
||||
@@ -427,6 +427,8 @@ pub enum EditorMode {
|
||||
scale_ui_elements_with_buffer_font_size: bool,
|
||||
/// When set to `true`, the editor will render a background for the active line.
|
||||
show_active_line_background: bool,
|
||||
/// When set to `true`, the editor's height will be determined by its content.
|
||||
sized_by_content: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -435,6 +437,7 @@ impl EditorMode {
|
||||
Self::Full {
|
||||
scale_ui_elements_with_buffer_font_size: true,
|
||||
show_active_line_background: true,
|
||||
sized_by_content: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,6 +791,8 @@ pub struct Editor {
|
||||
show_breadcrumbs: bool,
|
||||
show_gutter: bool,
|
||||
show_scrollbars: bool,
|
||||
disable_scrolling: bool,
|
||||
disable_expand_excerpt_buttons: bool,
|
||||
show_line_numbers: Option<bool>,
|
||||
use_relative_line_numbers: Option<bool>,
|
||||
show_git_diff_gutter: Option<bool>,
|
||||
@@ -1563,11 +1568,13 @@ impl Editor {
|
||||
blink_manager: blink_manager.clone(),
|
||||
show_local_selections: true,
|
||||
show_scrollbars: true,
|
||||
disable_scrolling: false,
|
||||
mode,
|
||||
show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
|
||||
show_gutter: mode.is_full(),
|
||||
show_line_numbers: None,
|
||||
use_relative_line_numbers: None,
|
||||
disable_expand_excerpt_buttons: false,
|
||||
show_git_diff_gutter: None,
|
||||
show_code_actions: None,
|
||||
show_runnables: None,
|
||||
@@ -16146,11 +16153,21 @@ impl Editor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn disable_scrolling(&mut self, cx: &mut Context<Self>) {
|
||||
self.disable_scrolling = true;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
|
||||
self.show_line_numbers = Some(show_line_numbers);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
|
||||
self.disable_expand_excerpt_buttons = true;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
|
||||
self.show_git_diff_gutter = Some(show_git_diff_gutter);
|
||||
cx.notify();
|
||||
|
||||
@@ -10419,6 +10419,7 @@ async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppConte
|
||||
EditorMode::Full {
|
||||
scale_ui_elements_with_buffer_font_size: false,
|
||||
show_active_line_background: false,
|
||||
sized_by_content: false,
|
||||
},
|
||||
multi_buffer.clone(),
|
||||
Some(project.clone()),
|
||||
|
||||
@@ -2183,6 +2183,10 @@ impl EditorElement {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Vec<Option<(AnyElement, gpui::Point<Pixels>)>> {
|
||||
if self.editor.read(cx).disable_expand_excerpt_buttons {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let editor_font_size = self.style.text.font_size.to_pixels(window.rem_size()) * 1.2;
|
||||
|
||||
let scroll_top = scroll_position.y * line_height;
|
||||
@@ -5512,7 +5516,9 @@ impl EditorElement {
|
||||
}
|
||||
|
||||
fn paint_mouse_listeners(&mut self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
|
||||
self.paint_scroll_wheel_listener(layout, window, cx);
|
||||
if !self.editor.read(cx).disable_scrolling {
|
||||
self.paint_scroll_wheel_listener(layout, window, cx);
|
||||
}
|
||||
|
||||
window.on_mouse_event({
|
||||
let position_map = layout.position_map.clone();
|
||||
@@ -6563,10 +6569,21 @@ impl Element for EditorElement {
|
||||
},
|
||||
)
|
||||
}
|
||||
EditorMode::Full { .. } => {
|
||||
EditorMode::Full {
|
||||
sized_by_content, ..
|
||||
} => {
|
||||
let mut style = Style::default();
|
||||
style.size.width = relative(1.).into();
|
||||
style.size.height = relative(1.).into();
|
||||
if sized_by_content {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let line_height =
|
||||
self.style.text.line_height_in_pixels(window.rem_size());
|
||||
let scroll_height =
|
||||
(snapshot.max_point().row().next_row().0 as f32) * line_height;
|
||||
style.size.height = scroll_height.into();
|
||||
} else {
|
||||
style.size.height = relative(1.).into();
|
||||
}
|
||||
window.request_layout(style, None, cx)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ assistant_tool.workspace = true
|
||||
assistant_tools.workspace = true
|
||||
async-trait.workspace = true
|
||||
async-watch.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
clap.workspace = true
|
||||
client.workspace = true
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{self, Debug},
|
||||
path::Path,
|
||||
sync::{Arc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
@@ -12,6 +13,8 @@ use crate::{
|
||||
use agent::ThreadEvent;
|
||||
use anyhow::{Result, anyhow};
|
||||
use async_trait::async_trait;
|
||||
use buffer_diff::DiffHunkStatus;
|
||||
use collections::HashMap;
|
||||
use futures::{FutureExt as _, StreamExt, channel::mpsc, select_biased};
|
||||
use gpui::{AppContext, AsyncApp, Entity};
|
||||
use language_model::{LanguageModel, Role, StopReason};
|
||||
@@ -234,9 +237,9 @@ impl ExampleContext {
|
||||
let mut tool_metrics = tool_metrics.lock().unwrap();
|
||||
if let Some(tool_result) = thread.tool_result(&tool_use_id) {
|
||||
let message = if tool_result.is_error {
|
||||
format!("TOOL FAILED: {}", tool_use.name)
|
||||
format!("✖︎ {}", tool_use.name)
|
||||
} else {
|
||||
format!("TOOL FINISHED: {}", tool_use.name)
|
||||
format!("✔︎ {}", tool_use.name)
|
||||
};
|
||||
println!("{log_prefix}{message}");
|
||||
tool_metrics
|
||||
@@ -250,6 +253,9 @@ impl ExampleContext {
|
||||
}
|
||||
});
|
||||
}
|
||||
ThreadEvent::InvalidToolInput { .. } => {
|
||||
println!("{log_prefix} invalid tool input");
|
||||
}
|
||||
ThreadEvent::ToolConfirmationNeeded => {
|
||||
panic!(
|
||||
"{}Bug: Tool confirmation should not be required in eval",
|
||||
@@ -278,7 +284,7 @@ impl ExampleContext {
|
||||
|
||||
let message_count_before = self.app.update_entity(&self.agent_thread, |thread, cx| {
|
||||
thread.set_remaining_turns(iterations);
|
||||
thread.send_to_model(model, cx);
|
||||
thread.send_to_model(model, None, cx);
|
||||
thread.messages().len()
|
||||
})?;
|
||||
|
||||
@@ -320,6 +326,36 @@ impl ExampleContext {
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn edits(&self) -> HashMap<Arc<Path>, FileEdits> {
|
||||
self.app
|
||||
.read_entity(&self.agent_thread, |thread, cx| {
|
||||
let action_log = thread.action_log().read(cx);
|
||||
HashMap::from_iter(action_log.changed_buffers(cx).into_iter().map(
|
||||
|(buffer, diff)| {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
|
||||
let file = snapshot.file().unwrap();
|
||||
let diff = diff.read(cx);
|
||||
let base_text = diff.base_text().text();
|
||||
|
||||
let hunks = diff
|
||||
.hunks(&snapshot, cx)
|
||||
.map(|hunk| FileEditHunk {
|
||||
base_text: base_text[hunk.diff_base_byte_range.clone()].to_string(),
|
||||
text: snapshot
|
||||
.text_for_range(hunk.range.clone())
|
||||
.collect::<String>(),
|
||||
status: hunk.status(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
(file.path().clone(), FileEdits { hunks })
|
||||
},
|
||||
))
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -344,6 +380,10 @@ impl Response {
|
||||
});
|
||||
cx.assert_some(result, format!("called `{}`", tool_name))
|
||||
}
|
||||
|
||||
pub fn tool_uses(&self) -> impl Iterator<Item = &ToolUse> {
|
||||
self.messages.iter().flat_map(|msg| &msg.tool_use)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -355,17 +395,37 @@ pub struct Message {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToolUse {
|
||||
name: String,
|
||||
pub name: String,
|
||||
value: serde_json::Value,
|
||||
}
|
||||
|
||||
impl ToolUse {
|
||||
pub fn expect_input<Input>(&self, cx: &mut ExampleContext) -> Result<Input>
|
||||
pub fn parse_input<Input>(&self) -> Result<Input>
|
||||
where
|
||||
Input: for<'de> serde::Deserialize<'de>,
|
||||
{
|
||||
let result =
|
||||
serde_json::from_value::<Input>(self.value.clone()).map_err(|err| anyhow!(err));
|
||||
cx.log_assertion(result, format!("valid `{}` input", &self.name))
|
||||
serde_json::from_value::<Input>(self.value.clone()).map_err(|err| anyhow!(err))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileEdits {
|
||||
hunks: Vec<FileEditHunk>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FileEditHunk {
|
||||
base_text: String,
|
||||
text: String,
|
||||
status: DiffHunkStatus,
|
||||
}
|
||||
|
||||
impl FileEdits {
|
||||
pub fn has_added_line(&self, line: &str) -> bool {
|
||||
self.hunks.iter().any(|hunk| {
|
||||
hunk.status == DiffHunkStatus::added_none()
|
||||
&& hunk.base_text.is_empty()
|
||||
&& hunk.text.contains(line)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
147
crates/eval/src/examples/add_arg_to_trait_method.rs
Normal file
147
crates/eval/src/examples/add_arg_to_trait_method.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use std::{collections::HashSet, path::Path};
|
||||
|
||||
use anyhow::Result;
|
||||
use assistant_tools::{CreateFileToolInput, EditFileToolInput, ReadFileToolInput};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion, LanguageServer};
|
||||
|
||||
pub struct AddArgToTraitMethod;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Example for AddArgToTraitMethod {
|
||||
fn meta(&self) -> ExampleMetadata {
|
||||
ExampleMetadata {
|
||||
name: "add_arg_to_trait_method".to_string(),
|
||||
url: "https://github.com/zed-industries/zed.git".to_string(),
|
||||
revision: "f69aeb6311dde3c0b8979c293d019d66498d54f2".to_string(),
|
||||
language_server: Some(LanguageServer {
|
||||
file_extension: "rs".to_string(),
|
||||
allow_preexisting_diagnostics: false,
|
||||
}),
|
||||
max_assertions: None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
|
||||
const FILENAME: &str = "assistant_tool.rs";
|
||||
cx.push_user_message(format!(
|
||||
r#"
|
||||
Add a `window: Option<gpui::AnyWindowHandle>` argument to the `Tool::run` trait method in {FILENAME},
|
||||
and update all the implementations of the trait and call sites accordingly.
|
||||
"#
|
||||
));
|
||||
|
||||
let response = cx.run_to_end().await?;
|
||||
|
||||
// Reads files before it edits them
|
||||
|
||||
let mut read_files = HashSet::new();
|
||||
|
||||
for tool_use in response.tool_uses() {
|
||||
match tool_use.name.as_str() {
|
||||
"read_file" => {
|
||||
if let Ok(input) = tool_use.parse_input::<ReadFileToolInput>() {
|
||||
read_files.insert(input.path);
|
||||
}
|
||||
}
|
||||
"create_file" => {
|
||||
if let Ok(input) = tool_use.parse_input::<CreateFileToolInput>() {
|
||||
read_files.insert(input.path);
|
||||
}
|
||||
}
|
||||
"edit_file" => {
|
||||
if let Ok(input) = tool_use.parse_input::<EditFileToolInput>() {
|
||||
cx.assert(
|
||||
read_files.contains(input.path.to_str().unwrap()),
|
||||
format!(
|
||||
"Read before edit: {}",
|
||||
&input.path.file_stem().unwrap().to_str().unwrap()
|
||||
),
|
||||
)
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Adds ignored argument to all but `batch_tool`
|
||||
|
||||
let add_ignored_window_paths = &[
|
||||
"code_action_tool",
|
||||
"code_symbols_tool",
|
||||
"contents_tool",
|
||||
"copy_path_tool",
|
||||
"create_directory_tool",
|
||||
"create_file_tool",
|
||||
"delete_path_tool",
|
||||
"diagnostics_tool",
|
||||
"edit_file_tool",
|
||||
"fetch_tool",
|
||||
"grep_tool",
|
||||
"list_directory_tool",
|
||||
"move_path_tool",
|
||||
"now_tool",
|
||||
"open_tool",
|
||||
"path_search_tool",
|
||||
"read_file_tool",
|
||||
"rename_tool",
|
||||
"symbol_info_tool",
|
||||
"terminal_tool",
|
||||
"thinking_tool",
|
||||
"web_search_tool",
|
||||
];
|
||||
|
||||
let edits = cx.edits();
|
||||
|
||||
for tool_name in add_ignored_window_paths {
|
||||
let path_str = format!("crates/assistant_tools/src/{}.rs", tool_name);
|
||||
let edits = edits.get(Path::new(&path_str));
|
||||
|
||||
let ignored = edits.map_or(false, |edits| {
|
||||
edits.has_added_line(" _window: Option<gpui::AnyWindowHandle>,\n")
|
||||
});
|
||||
let uningored = edits.map_or(false, |edits| {
|
||||
edits.has_added_line(" window: Option<gpui::AnyWindowHandle>,\n")
|
||||
});
|
||||
|
||||
cx.assert(ignored || uningored, format!("Argument: {}", tool_name))
|
||||
.ok();
|
||||
|
||||
cx.assert(ignored, format!("`_` prefix: {}", tool_name))
|
||||
.ok();
|
||||
}
|
||||
|
||||
// Adds unignored argument to `batch_tool`
|
||||
|
||||
let batch_tool_edits = edits.get(Path::new("crates/assistant_tools/src/batch_tool.rs"));
|
||||
|
||||
cx.assert(
|
||||
batch_tool_edits.map_or(false, |edits| {
|
||||
edits.has_added_line(" window: Option<gpui::AnyWindowHandle>,\n")
|
||||
}),
|
||||
"Argument: batch_tool",
|
||||
)
|
||||
.ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn diff_assertions(&self) -> Vec<JudgeAssertion> {
|
||||
vec![
|
||||
JudgeAssertion {
|
||||
id: "batch tool passes window to each".to_string(),
|
||||
description:
|
||||
"batch_tool is modified to pass a clone of the window to each tool it calls."
|
||||
.to_string(),
|
||||
},
|
||||
JudgeAssertion {
|
||||
id: "tool tests updated".to_string(),
|
||||
description:
|
||||
"tool tests are updated to pass the new `window` argument (`None` is ok)."
|
||||
.to_string(),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use assistant_tools::PathSearchToolInput;
|
||||
use assistant_tools::FindPathToolInput;
|
||||
use async_trait::async_trait;
|
||||
use regex::Regex;
|
||||
|
||||
@@ -15,7 +15,7 @@ impl Example for FileSearchExample {
|
||||
url: "https://github.com/zed-industries/zed.git".to_string(),
|
||||
revision: "03ecb88fe30794873f191ddb728f597935b3101c".to_string(),
|
||||
language_server: None,
|
||||
max_assertions: Some(4),
|
||||
max_assertions: Some(3),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,21 +32,18 @@ impl Example for FileSearchExample {
|
||||
));
|
||||
|
||||
let response = cx.run_turn().await?;
|
||||
let tool_use = response.expect_tool("path_search", cx)?;
|
||||
let input = tool_use.expect_input::<PathSearchToolInput>(cx)?;
|
||||
let tool_use = response.expect_tool("find_path", cx)?;
|
||||
let input = tool_use.parse_input::<FindPathToolInput>()?;
|
||||
|
||||
let glob = input.glob;
|
||||
cx.assert(
|
||||
glob.ends_with(FILENAME),
|
||||
format!("glob ends with `{FILENAME}`"),
|
||||
)?;
|
||||
cx.assert(glob.ends_with(FILENAME), "glob ends with file name")?;
|
||||
|
||||
let without_filename = glob.replace(FILENAME, "");
|
||||
let matches = Regex::new("(\\*\\*|zed)/(\\*\\*?/)?")
|
||||
.unwrap()
|
||||
.is_match(&without_filename);
|
||||
|
||||
cx.assert(matches, "glob starts with either `**` or `zed`")?;
|
||||
cx.assert(matches, "glob starts with `**` or project")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11,10 +11,14 @@ use util::serde::default_true;
|
||||
|
||||
use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion};
|
||||
|
||||
mod add_arg_to_trait_method;
|
||||
mod file_search;
|
||||
|
||||
pub fn all(examples_dir: &Path) -> Vec<Rc<dyn Example>> {
|
||||
let mut threads: Vec<Rc<dyn Example>> = vec![Rc::new(file_search::FileSearchExample)];
|
||||
let mut threads: Vec<Rc<dyn Example>> = vec![
|
||||
Rc::new(file_search::FileSearchExample),
|
||||
Rc::new(add_arg_to_trait_method::AddArgToTraitMethod),
|
||||
];
|
||||
|
||||
for example_path in list_declarative_examples(examples_dir).unwrap() {
|
||||
threads.push(Rc::new(DeclarativeExample::load(&example_path).unwrap()));
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::{
|
||||
num::NonZeroU64,
|
||||
sync::{
|
||||
Arc, Weak,
|
||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
|
||||
},
|
||||
thread::panicking,
|
||||
};
|
||||
@@ -572,6 +572,30 @@ impl AnyWeakEntity {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a weak entity that can never be upgraded.
|
||||
pub fn new_invalid() -> Self {
|
||||
/// To hold the invariant that all ids are unique, and considering that slotmap
|
||||
/// increases their IDs from `0`, we can decrease ours from `u64::MAX` so these
|
||||
/// two will never conflict (u64 is way too large).
|
||||
static UNIQUE_NON_CONFLICTING_ID_GENERATOR: AtomicU64 = AtomicU64::new(u64::MAX);
|
||||
let entity_id = UNIQUE_NON_CONFLICTING_ID_GENERATOR.fetch_sub(1, SeqCst);
|
||||
|
||||
Self {
|
||||
// Safety:
|
||||
// Docs say this is safe but can be unspecified if slotmap changes the representation
|
||||
// after `1.0.7`, that said, providing a valid entity_id here is not necessary as long
|
||||
// as we guarantee that that `entity_id` is never used if `entity_ref_counts` equals
|
||||
// to `Weak::new()` (that is, it's unable to upgrade), that is the invariant that
|
||||
// actually needs to be hold true.
|
||||
//
|
||||
// And there is no sane reason to read an entity slot if `entity_ref_counts` can't be
|
||||
// read in the first place, so we're good!
|
||||
entity_id: entity_id.into(),
|
||||
entity_type: TypeId::of::<()>(),
|
||||
entity_ref_counts: Weak::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AnyWeakEntity {
|
||||
@@ -707,6 +731,14 @@ impl<T: 'static> WeakEntity<T> {
|
||||
.map(|this| cx.read_entity(&this, read)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new weak entity that can never be upgraded.
|
||||
pub fn new_invalid() -> Self {
|
||||
Self {
|
||||
any_entity: AnyWeakEntity::new_invalid(),
|
||||
entity_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Hash for WeakEntity<T> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId,
|
||||
LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
|
||||
LanguageModelProviderState, LanguageModelRequest,
|
||||
AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,
|
||||
LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,
|
||||
};
|
||||
use futures::{FutureExt, StreamExt, channel::mpsc, future::BoxFuture, stream::BoxStream};
|
||||
use gpui::{AnyView, App, AsyncApp, Entity, Task, Window};
|
||||
@@ -168,7 +168,12 @@ impl LanguageModel for FakeLanguageModel {
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
_: &AsyncApp,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>> {
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
>,
|
||||
> {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
self.current_completion_txs.lock().push((request, tx));
|
||||
async move {
|
||||
|
||||
@@ -76,6 +76,19 @@ pub enum LanguageModelCompletionEvent {
|
||||
UsageUpdate(TokenUsage),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LanguageModelCompletionError {
|
||||
#[error("received bad input JSON")]
|
||||
BadInputJson {
|
||||
id: LanguageModelToolUseId,
|
||||
tool_name: Arc<str>,
|
||||
raw_input: Arc<str>,
|
||||
json_parse_error: String,
|
||||
},
|
||||
#[error(transparent)]
|
||||
Other(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
/// Indicates the format used to define the input schema for a language model tool.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||
pub enum LanguageModelToolSchemaFormat {
|
||||
@@ -186,13 +199,14 @@ where
|
||||
pub struct LanguageModelToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
pub name: Arc<str>,
|
||||
pub raw_input: String,
|
||||
pub input: serde_json::Value,
|
||||
pub is_input_complete: bool,
|
||||
}
|
||||
|
||||
pub struct LanguageModelTextStream {
|
||||
pub message_id: Option<String>,
|
||||
pub stream: BoxStream<'static, Result<String>>,
|
||||
pub stream: BoxStream<'static, Result<String, LanguageModelCompletionError>>,
|
||||
// Has complete token usage after the stream has finished
|
||||
pub last_token_usage: Arc<Mutex<TokenUsage>>,
|
||||
}
|
||||
@@ -245,7 +259,12 @@ pub trait LanguageModel: Send + Sync {
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>>;
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
>,
|
||||
>;
|
||||
|
||||
fn stream_completion_with_usage(
|
||||
&self,
|
||||
@@ -254,7 +273,7 @@ pub trait LanguageModel: Send + Sync {
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<(
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent>>,
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
Option<RequestUsage>,
|
||||
)>,
|
||||
> {
|
||||
|
||||
@@ -12,10 +12,10 @@ use gpui::{
|
||||
};
|
||||
use http_client::HttpClient;
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModel, LanguageModelCacheConfiguration, LanguageModelId,
|
||||
LanguageModelKnownError, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,
|
||||
LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, MessageContent,
|
||||
RateLimiter, Role,
|
||||
AuthenticateError, LanguageModel, LanguageModelCacheConfiguration,
|
||||
LanguageModelCompletionError, LanguageModelId, LanguageModelKnownError, LanguageModelName,
|
||||
LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
|
||||
LanguageModelProviderState, LanguageModelRequest, MessageContent, RateLimiter, Role,
|
||||
};
|
||||
use language_model::{LanguageModelCompletionEvent, LanguageModelToolUse, StopReason};
|
||||
use schemars::JsonSchema;
|
||||
@@ -27,7 +27,7 @@ use std::sync::Arc;
|
||||
use strum::IntoEnumIterator;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{Icon, IconName, List, Tooltip, prelude::*};
|
||||
use util::{ResultExt, maybe};
|
||||
use util::ResultExt;
|
||||
|
||||
const PROVIDER_ID: &str = language_model::ANTHROPIC_PROVIDER_ID;
|
||||
const PROVIDER_NAME: &str = "Anthropic";
|
||||
@@ -448,7 +448,12 @@ impl LanguageModel for AnthropicModel {
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>> {
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
>,
|
||||
> {
|
||||
let request = into_anthropic(
|
||||
request,
|
||||
self.model.request_id().into(),
|
||||
@@ -626,7 +631,7 @@ pub fn into_anthropic(
|
||||
|
||||
pub fn map_to_language_model_completion_events(
|
||||
events: Pin<Box<dyn Send + Stream<Item = Result<Event, AnthropicError>>>>,
|
||||
) -> impl Stream<Item = Result<LanguageModelCompletionEvent>> {
|
||||
) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {
|
||||
struct RawToolUse {
|
||||
id: String,
|
||||
name: String,
|
||||
@@ -714,61 +719,58 @@ pub fn map_to_language_model_completion_events(
|
||||
if let Some(tool_use) = state.tool_uses_by_index.get_mut(&index) {
|
||||
tool_use.input_json.push_str(&partial_json);
|
||||
|
||||
return Some((
|
||||
vec![maybe!({
|
||||
Ok(LanguageModelCompletionEvent::ToolUse(
|
||||
// Try to convert invalid (incomplete) JSON into
|
||||
// valid JSON that serde can accept, e.g. by closing
|
||||
// unclosed delimiters. This way, we can update the
|
||||
// UI with whatever has been streamed back so far.
|
||||
if let Ok(input) = serde_json::Value::from_str(
|
||||
&partial_json_fixer::fix_json(&tool_use.input_json),
|
||||
) {
|
||||
return Some((
|
||||
vec![Ok(LanguageModelCompletionEvent::ToolUse(
|
||||
LanguageModelToolUse {
|
||||
id: tool_use.id.clone().into(),
|
||||
name: tool_use.name.clone().into(),
|
||||
is_input_complete: false,
|
||||
input: if tool_use.input_json.is_empty() {
|
||||
serde_json::Value::Object(
|
||||
serde_json::Map::default(),
|
||||
)
|
||||
} else {
|
||||
serde_json::Value::from_str(
|
||||
// Convert invalid (incomplete) JSON into
|
||||
// JSON that serde will accept, e.g. by closing
|
||||
// unclosed delimiters. This way, we can update
|
||||
// the UI with whatever has been streamed back so far.
|
||||
&partial_json_fixer::fix_json(
|
||||
&tool_use.input_json,
|
||||
),
|
||||
)
|
||||
.map_err(|err| anyhow!(err))?
|
||||
},
|
||||
raw_input: tool_use.input_json.clone(),
|
||||
input,
|
||||
},
|
||||
))
|
||||
})],
|
||||
state,
|
||||
));
|
||||
))],
|
||||
state,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Event::ContentBlockStop { index } => {
|
||||
if let Some(tool_use) = state.tool_uses_by_index.remove(&index) {
|
||||
return Some((
|
||||
vec![maybe!({
|
||||
Ok(LanguageModelCompletionEvent::ToolUse(
|
||||
LanguageModelToolUse {
|
||||
id: tool_use.id.into(),
|
||||
name: tool_use.name.into(),
|
||||
is_input_complete: true,
|
||||
input: if tool_use.input_json.is_empty() {
|
||||
serde_json::Value::Object(
|
||||
serde_json::Map::default(),
|
||||
)
|
||||
} else {
|
||||
serde_json::Value::from_str(
|
||||
&tool_use.input_json,
|
||||
)
|
||||
.map_err(|err| anyhow!(err))?
|
||||
},
|
||||
},
|
||||
))
|
||||
})],
|
||||
state,
|
||||
));
|
||||
let input_json = tool_use.input_json.trim();
|
||||
let input_value = if input_json.is_empty() {
|
||||
Ok(serde_json::Value::Object(serde_json::Map::default()))
|
||||
} else {
|
||||
serde_json::Value::from_str(input_json)
|
||||
};
|
||||
let event_result = match input_value {
|
||||
Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(
|
||||
LanguageModelToolUse {
|
||||
id: tool_use.id.into(),
|
||||
name: tool_use.name.into(),
|
||||
is_input_complete: true,
|
||||
input,
|
||||
raw_input: tool_use.input_json.clone(),
|
||||
},
|
||||
)),
|
||||
Err(json_parse_err) => {
|
||||
Err(LanguageModelCompletionError::BadInputJson {
|
||||
id: tool_use.id.into(),
|
||||
tool_name: tool_use.name.into(),
|
||||
raw_input: input_json.into(),
|
||||
json_parse_error: json_parse_err.to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
return Some((vec![event_result], state));
|
||||
}
|
||||
}
|
||||
Event::MessageStart { message } => {
|
||||
@@ -815,14 +817,19 @@ pub fn map_to_language_model_completion_events(
|
||||
}
|
||||
Event::Error { error } => {
|
||||
return Some((
|
||||
vec![Err(anyhow!(AnthropicError::ApiError(error)))],
|
||||
vec![Err(LanguageModelCompletionError::Other(anyhow!(
|
||||
AnthropicError::ApiError(error)
|
||||
)))],
|
||||
state,
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(err) => {
|
||||
return Some((vec![Err(anthropic_err_to_anyhow(err))], state));
|
||||
return Some((
|
||||
vec![Err(LanguageModelCompletionError::Other(anyhow!(err)))],
|
||||
state,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,10 @@ use gpui_tokio::Tokio;
|
||||
use http_client::HttpClient;
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModel, LanguageModelCacheConfiguration,
|
||||
LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider,
|
||||
LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,
|
||||
LanguageModelRequest, LanguageModelToolUse, MessageContent, RateLimiter, Role, TokenUsage,
|
||||
LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName,
|
||||
LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
|
||||
LanguageModelProviderState, LanguageModelRequest, LanguageModelToolUse, MessageContent,
|
||||
RateLimiter, Role, TokenUsage,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -542,7 +543,12 @@ impl LanguageModel for BedrockModel {
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>> {
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
>,
|
||||
> {
|
||||
let Ok(region) = cx.read_entity(&self.state, |state, _cx| {
|
||||
// Get region - from credentials or directly from settings
|
||||
let region = state
|
||||
@@ -780,7 +786,7 @@ pub fn get_bedrock_tokens(
|
||||
pub fn map_to_language_model_completion_events(
|
||||
events: Pin<Box<dyn Send + Stream<Item = Result<BedrockStreamingResponse, BedrockError>>>>,
|
||||
handle: Handle,
|
||||
) -> impl Stream<Item = Result<LanguageModelCompletionEvent>> {
|
||||
) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {
|
||||
struct RawToolUse {
|
||||
id: String,
|
||||
name: String,
|
||||
@@ -894,6 +900,7 @@ pub fn map_to_language_model_completion_events(
|
||||
id: tool_use.id.into(),
|
||||
name: tool_use.name.into(),
|
||||
is_input_complete: true,
|
||||
raw_input: tool_use.input_json.clone(),
|
||||
input: if tool_use.input_json.is_empty() {
|
||||
Value::Null
|
||||
} else {
|
||||
@@ -970,7 +977,7 @@ pub fn map_to_language_model_completion_events(
|
||||
_ => {}
|
||||
},
|
||||
|
||||
Err(err) => return Some((Some(Err(anyhow!(err))), state)),
|
||||
Err(err) => return Some((Some(Err(anyhow!(err).into())), state)),
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
@@ -10,11 +10,11 @@ use futures::{
|
||||
use gpui::{AnyElement, AnyView, App, AsyncApp, Context, Entity, Subscription, Task};
|
||||
use http_client::{AsyncBody, HttpClient, Method, Response, StatusCode};
|
||||
use language_model::{
|
||||
AuthenticateError, CloudModel, LanguageModel, LanguageModelCacheConfiguration, LanguageModelId,
|
||||
LanguageModelKnownError, LanguageModelName, LanguageModelProviderId, LanguageModelProviderName,
|
||||
LanguageModelProviderState, LanguageModelProviderTosView, LanguageModelRequest,
|
||||
LanguageModelToolSchemaFormat, ModelRequestLimitReachedError, RateLimiter, RequestUsage,
|
||||
ZED_CLOUD_PROVIDER_ID,
|
||||
AuthenticateError, CloudModel, LanguageModel, LanguageModelCacheConfiguration,
|
||||
LanguageModelCompletionError, LanguageModelId, LanguageModelKnownError, LanguageModelName,
|
||||
LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,
|
||||
LanguageModelProviderTosView, LanguageModelRequest, LanguageModelToolSchemaFormat,
|
||||
ModelRequestLimitReachedError, RateLimiter, RequestUsage, ZED_CLOUD_PROVIDER_ID,
|
||||
};
|
||||
use language_model::{
|
||||
LanguageModelAvailability, LanguageModelCompletionEvent, LanguageModelProvider, LlmApiToken,
|
||||
@@ -711,7 +711,12 @@ impl LanguageModel for CloudLanguageModel {
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>> {
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
>,
|
||||
> {
|
||||
self.stream_completion_with_usage(request, cx)
|
||||
.map(|result| result.map(|(stream, _)| stream))
|
||||
.boxed()
|
||||
@@ -724,7 +729,7 @@ impl LanguageModel for CloudLanguageModel {
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<(
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent>>,
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
Option<RequestUsage>,
|
||||
)>,
|
||||
> {
|
||||
|
||||
@@ -17,16 +17,16 @@ use gpui::{
|
||||
Transformation, percentage, svg,
|
||||
};
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId,
|
||||
LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
|
||||
LanguageModelProviderState, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason,
|
||||
AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,
|
||||
LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelToolUse, MessageContent, RateLimiter, Role,
|
||||
StopReason,
|
||||
};
|
||||
use settings::SettingsStore;
|
||||
use std::time::Duration;
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::prelude::*;
|
||||
use util::maybe;
|
||||
|
||||
use super::anthropic::count_anthropic_tokens;
|
||||
use super::google::count_google_tokens;
|
||||
@@ -242,7 +242,12 @@ impl LanguageModel for CopilotChatLanguageModel {
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>> {
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
>,
|
||||
> {
|
||||
if let Some(message) = request.messages.last() {
|
||||
if message.contents_empty() {
|
||||
const EMPTY_PROMPT_MSG: &str =
|
||||
@@ -285,7 +290,7 @@ impl LanguageModel for CopilotChatLanguageModel {
|
||||
pub fn map_to_language_model_completion_events(
|
||||
events: Pin<Box<dyn Send + Stream<Item = Result<ResponseEvent>>>>,
|
||||
is_streaming: bool,
|
||||
) -> impl Stream<Item = Result<LanguageModelCompletionEvent>> {
|
||||
) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {
|
||||
#[derive(Default)]
|
||||
struct RawToolCall {
|
||||
id: String,
|
||||
@@ -309,7 +314,7 @@ pub fn map_to_language_model_completion_events(
|
||||
Ok(event) => {
|
||||
let Some(choice) = event.choices.first() else {
|
||||
return Some((
|
||||
vec![Err(anyhow!("Response contained no choices"))],
|
||||
vec![Err(anyhow!("Response contained no choices").into())],
|
||||
state,
|
||||
));
|
||||
};
|
||||
@@ -322,7 +327,7 @@ pub fn map_to_language_model_completion_events(
|
||||
|
||||
let Some(delta) = delta else {
|
||||
return Some((
|
||||
vec![Err(anyhow!("Response contained no delta"))],
|
||||
vec![Err(anyhow!("Response contained no delta").into())],
|
||||
state,
|
||||
));
|
||||
};
|
||||
@@ -361,19 +366,26 @@ pub fn map_to_language_model_completion_events(
|
||||
}
|
||||
Some("tool_calls") => {
|
||||
events.extend(state.tool_calls_by_index.drain().map(
|
||||
|(_, tool_call)| {
|
||||
maybe!({
|
||||
Ok(LanguageModelCompletionEvent::ToolUse(
|
||||
LanguageModelToolUse {
|
||||
id: tool_call.id.into(),
|
||||
name: tool_call.name.as_str().into(),
|
||||
is_input_complete: true,
|
||||
input: serde_json::Value::from_str(
|
||||
&tool_call.arguments,
|
||||
)?,
|
||||
},
|
||||
))
|
||||
})
|
||||
|(_, tool_call)| match serde_json::Value::from_str(
|
||||
&tool_call.arguments,
|
||||
) {
|
||||
Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(
|
||||
LanguageModelToolUse {
|
||||
id: tool_call.id.clone().into(),
|
||||
name: tool_call.name.as_str().into(),
|
||||
is_input_complete: true,
|
||||
input,
|
||||
raw_input: tool_call.arguments.clone(),
|
||||
},
|
||||
)),
|
||||
Err(error) => {
|
||||
Err(LanguageModelCompletionError::BadInputJson {
|
||||
id: tool_call.id.into(),
|
||||
tool_name: tool_call.name.as_str().into(),
|
||||
raw_input: tool_call.arguments.into(),
|
||||
json_parse_error: error.to_string(),
|
||||
})
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
@@ -392,7 +404,7 @@ pub fn map_to_language_model_completion_events(
|
||||
|
||||
return Some((events, state));
|
||||
}
|
||||
Err(err) => return Some((vec![Err(err)], state)),
|
||||
Err(err) => return Some((vec![Err(anyhow!(err).into())], state)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,9 +464,11 @@ impl CopilotChatLanguageModel {
|
||||
}
|
||||
}
|
||||
|
||||
messages.push(ChatMessage::User {
|
||||
content: text_content,
|
||||
});
|
||||
if !text_content.is_empty() {
|
||||
messages.push(ChatMessage::User {
|
||||
content: text_content,
|
||||
});
|
||||
}
|
||||
}
|
||||
Role::Assistant => {
|
||||
let mut tool_calls = Vec::new();
|
||||
|
||||
@@ -9,9 +9,9 @@ use gpui::{
|
||||
};
|
||||
use http_client::HttpClient;
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId,
|
||||
LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
|
||||
LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role,
|
||||
AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,
|
||||
LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -324,7 +324,12 @@ impl LanguageModel for DeepSeekLanguageModel {
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>> {
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
>,
|
||||
> {
|
||||
let request = into_deepseek(
|
||||
request,
|
||||
self.model.id().to_string(),
|
||||
@@ -336,20 +341,22 @@ impl LanguageModel for DeepSeekLanguageModel {
|
||||
let stream = stream.await?;
|
||||
Ok(stream
|
||||
.map(|result| {
|
||||
result.and_then(|response| {
|
||||
response
|
||||
.choices
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("Empty response"))
|
||||
.map(|choice| {
|
||||
choice
|
||||
.delta
|
||||
.content
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.map(LanguageModelCompletionEvent::Text)
|
||||
})
|
||||
})
|
||||
result
|
||||
.and_then(|response| {
|
||||
response
|
||||
.choices
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("Empty response"))
|
||||
.map(|choice| {
|
||||
choice
|
||||
.delta
|
||||
.content
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.map(LanguageModelCompletionEvent::Text)
|
||||
})
|
||||
})
|
||||
.map_err(LanguageModelCompletionError::Other)
|
||||
})
|
||||
.boxed())
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ use gpui::{
|
||||
};
|
||||
use http_client::HttpClient;
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModelCompletionEvent, LanguageModelToolSchemaFormat,
|
||||
LanguageModelToolUse, LanguageModelToolUseId, MessageContent, StopReason,
|
||||
AuthenticateError, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelToolSchemaFormat, LanguageModelToolUse, LanguageModelToolUseId, MessageContent,
|
||||
StopReason,
|
||||
};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider,
|
||||
@@ -355,12 +356,19 @@ impl LanguageModel for GoogleLanguageModel {
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<futures::stream::BoxStream<'static, Result<LanguageModelCompletionEvent>>>,
|
||||
Result<
|
||||
futures::stream::BoxStream<
|
||||
'static,
|
||||
Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
|
||||
>,
|
||||
>,
|
||||
> {
|
||||
let request = into_google(request, self.model.id().to_string());
|
||||
let request = self.stream_completion(request, cx);
|
||||
let future = self.request_limiter.stream(async move {
|
||||
let response = request.await.map_err(|err| anyhow!(err))?;
|
||||
let response = request
|
||||
.await
|
||||
.map_err(|err| LanguageModelCompletionError::Other(anyhow!(err)))?;
|
||||
Ok(map_to_language_model_completion_events(response))
|
||||
});
|
||||
async move { Ok(future.await?.boxed()) }.boxed()
|
||||
@@ -471,7 +479,7 @@ pub fn into_google(
|
||||
|
||||
pub fn map_to_language_model_completion_events(
|
||||
events: Pin<Box<dyn Send + Stream<Item = Result<GenerateContentResponse>>>>,
|
||||
) -> impl Stream<Item = Result<LanguageModelCompletionEvent>> {
|
||||
) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
static TOOL_CALL_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
@@ -492,7 +500,7 @@ pub fn map_to_language_model_completion_events(
|
||||
if let Some(event) = state.events.next().await {
|
||||
match event {
|
||||
Ok(event) => {
|
||||
let mut events: Vec<Result<LanguageModelCompletionEvent>> = Vec::new();
|
||||
let mut events: Vec<_> = Vec::new();
|
||||
let mut wants_to_use_tool = false;
|
||||
if let Some(usage_metadata) = event.usage_metadata {
|
||||
update_usage(&mut state.usage, &usage_metadata);
|
||||
@@ -537,6 +545,10 @@ pub fn map_to_language_model_completion_events(
|
||||
id,
|
||||
name,
|
||||
is_input_complete: true,
|
||||
raw_input: function_call_part
|
||||
.function_call
|
||||
.args
|
||||
.to_string(),
|
||||
input: function_call_part.function_call.args,
|
||||
},
|
||||
)));
|
||||
@@ -555,7 +567,10 @@ pub fn map_to_language_model_completion_events(
|
||||
return Some((events, state));
|
||||
}
|
||||
Err(err) => {
|
||||
return Some((vec![Err(anyhow!(err))], state));
|
||||
return Some((
|
||||
vec![Err(LanguageModelCompletionError::Other(anyhow!(err)))],
|
||||
state,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ use anyhow::{Result, anyhow};
|
||||
use futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};
|
||||
use gpui::{AnyView, App, AsyncApp, Context, Subscription, Task};
|
||||
use http_client::HttpClient;
|
||||
use language_model::{AuthenticateError, LanguageModelCompletionEvent};
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider,
|
||||
LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,
|
||||
@@ -310,7 +312,12 @@ impl LanguageModel for LmStudioLanguageModel {
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>> {
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
>,
|
||||
> {
|
||||
let request = self.to_lmstudio_request(request);
|
||||
|
||||
let http_client = self.http_client.clone();
|
||||
@@ -364,7 +371,11 @@ impl LanguageModel for LmStudioLanguageModel {
|
||||
async move {
|
||||
Ok(future
|
||||
.await?
|
||||
.map(|result| result.map(LanguageModelCompletionEvent::Text))
|
||||
.map(|result| {
|
||||
result
|
||||
.map(LanguageModelCompletionEvent::Text)
|
||||
.map_err(LanguageModelCompletionError::Other)
|
||||
})
|
||||
.boxed())
|
||||
}
|
||||
.boxed()
|
||||
|
||||
@@ -8,9 +8,9 @@ use gpui::{
|
||||
};
|
||||
use http_client::HttpClient;
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId,
|
||||
LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
|
||||
LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role,
|
||||
AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,
|
||||
LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, RateLimiter, Role,
|
||||
};
|
||||
|
||||
use futures::stream::BoxStream;
|
||||
@@ -344,7 +344,12 @@ impl LanguageModel for MistralLanguageModel {
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>> {
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
>,
|
||||
> {
|
||||
let request = into_mistral(
|
||||
request,
|
||||
self.model.id().to_string(),
|
||||
@@ -356,20 +361,22 @@ impl LanguageModel for MistralLanguageModel {
|
||||
let stream = stream.await?;
|
||||
Ok(stream
|
||||
.map(|result| {
|
||||
result.and_then(|response| {
|
||||
response
|
||||
.choices
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("Empty response"))
|
||||
.map(|choice| {
|
||||
choice
|
||||
.delta
|
||||
.content
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.map(LanguageModelCompletionEvent::Text)
|
||||
})
|
||||
})
|
||||
result
|
||||
.and_then(|response| {
|
||||
response
|
||||
.choices
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("Empty response"))
|
||||
.map(|choice| {
|
||||
choice
|
||||
.delta
|
||||
.content
|
||||
.clone()
|
||||
.unwrap_or_default()
|
||||
.map(LanguageModelCompletionEvent::Text)
|
||||
})
|
||||
})
|
||||
.map_err(LanguageModelCompletionError::Other)
|
||||
})
|
||||
.boxed())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ use anyhow::{Result, anyhow};
|
||||
use futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};
|
||||
use gpui::{AnyView, App, AsyncApp, Context, Subscription, Task};
|
||||
use http_client::HttpClient;
|
||||
use language_model::{AuthenticateError, LanguageModelCompletionEvent};
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelId, LanguageModelName, LanguageModelProvider,
|
||||
LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState,
|
||||
@@ -322,7 +324,12 @@ impl LanguageModel for OllamaLanguageModel {
|
||||
&self,
|
||||
request: LanguageModelRequest,
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>> {
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<
|
||||
BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
|
||||
>,
|
||||
> {
|
||||
let request = self.to_ollama_request(request);
|
||||
|
||||
let http_client = self.http_client.clone();
|
||||
@@ -356,7 +363,11 @@ impl LanguageModel for OllamaLanguageModel {
|
||||
async move {
|
||||
Ok(future
|
||||
.await?
|
||||
.map(|result| result.map(LanguageModelCompletionEvent::Text))
|
||||
.map(|result| {
|
||||
result
|
||||
.map(LanguageModelCompletionEvent::Text)
|
||||
.map_err(LanguageModelCompletionError::Other)
|
||||
})
|
||||
.boxed())
|
||||
}
|
||||
.boxed()
|
||||
|
||||
@@ -9,10 +9,10 @@ use gpui::{
|
||||
};
|
||||
use http_client::HttpClient;
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModel, LanguageModelCompletionEvent, LanguageModelId,
|
||||
LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
|
||||
LanguageModelProviderState, LanguageModelRequest, LanguageModelToolUse, MessageContent,
|
||||
RateLimiter, Role, StopReason,
|
||||
AuthenticateError, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
|
||||
LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,
|
||||
LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,
|
||||
LanguageModelToolUse, MessageContent, RateLimiter, Role, StopReason,
|
||||
};
|
||||
use open_ai::{Model, ResponseStreamEvent, stream_completion};
|
||||
use schemars::JsonSchema;
|
||||
@@ -24,7 +24,7 @@ use std::sync::Arc;
|
||||
use strum::IntoEnumIterator;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{Icon, IconName, List, Tooltip, prelude::*};
|
||||
use util::{ResultExt, maybe};
|
||||
use util::ResultExt;
|
||||
|
||||
use crate::{AllLanguageModelSettings, ui::InstructionListItem};
|
||||
|
||||
@@ -321,7 +321,12 @@ impl LanguageModel for OpenAiLanguageModel {
|
||||
cx: &AsyncApp,
|
||||
) -> BoxFuture<
|
||||
'static,
|
||||
Result<futures::stream::BoxStream<'static, Result<LanguageModelCompletionEvent>>>,
|
||||
Result<
|
||||
futures::stream::BoxStream<
|
||||
'static,
|
||||
Result<LanguageModelCompletionEvent, LanguageModelCompletionError>,
|
||||
>,
|
||||
>,
|
||||
> {
|
||||
let request = into_open_ai(request, &self.model, self.max_output_tokens());
|
||||
let completions = self.stream_completion(request, cx);
|
||||
@@ -419,7 +424,7 @@ pub fn into_open_ai(
|
||||
|
||||
pub fn map_to_language_model_completion_events(
|
||||
events: Pin<Box<dyn Send + Stream<Item = Result<ResponseStreamEvent>>>>,
|
||||
) -> impl Stream<Item = Result<LanguageModelCompletionEvent>> {
|
||||
) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {
|
||||
#[derive(Default)]
|
||||
struct RawToolCall {
|
||||
id: String,
|
||||
@@ -443,7 +448,9 @@ pub fn map_to_language_model_completion_events(
|
||||
Ok(event) => {
|
||||
let Some(choice) = event.choices.first() else {
|
||||
return Some((
|
||||
vec![Err(anyhow!("Response contained no choices"))],
|
||||
vec![Err(LanguageModelCompletionError::Other(anyhow!(
|
||||
"Response contained no choices"
|
||||
)))],
|
||||
state,
|
||||
));
|
||||
};
|
||||
@@ -484,19 +491,26 @@ pub fn map_to_language_model_completion_events(
|
||||
}
|
||||
Some("tool_calls") => {
|
||||
events.extend(state.tool_calls_by_index.drain().map(
|
||||
|(_, tool_call)| {
|
||||
maybe!({
|
||||
Ok(LanguageModelCompletionEvent::ToolUse(
|
||||
LanguageModelToolUse {
|
||||
id: tool_call.id.into(),
|
||||
name: tool_call.name.as_str().into(),
|
||||
is_input_complete: true,
|
||||
input: serde_json::Value::from_str(
|
||||
&tool_call.arguments,
|
||||
)?,
|
||||
},
|
||||
))
|
||||
})
|
||||
|(_, tool_call)| match serde_json::Value::from_str(
|
||||
&tool_call.arguments,
|
||||
) {
|
||||
Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(
|
||||
LanguageModelToolUse {
|
||||
id: tool_call.id.clone().into(),
|
||||
name: tool_call.name.as_str().into(),
|
||||
is_input_complete: true,
|
||||
input,
|
||||
raw_input: tool_call.arguments.clone(),
|
||||
},
|
||||
)),
|
||||
Err(error) => {
|
||||
Err(LanguageModelCompletionError::BadInputJson {
|
||||
id: tool_call.id.into(),
|
||||
tool_name: tool_call.name.as_str().into(),
|
||||
raw_input: tool_call.arguments.into(),
|
||||
json_parse_error: error.to_string(),
|
||||
})
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
@@ -515,7 +529,9 @@ pub fn map_to_language_model_completion_events(
|
||||
|
||||
return Some((events, state));
|
||||
}
|
||||
Err(err) => return Some((vec![Err(err)], state)),
|
||||
Err(err) => {
|
||||
return Some((vec![Err(LanguageModelCompletionError::Other(err))], state));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,3 +51,9 @@ pub(crate) mod m_2025_04_21 {
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
pub(crate) mod m_2025_04_23 {
|
||||
mod settings;
|
||||
|
||||
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||
}
|
||||
|
||||
27
crates/migrator/src/migrations/m_2025_04_23/settings.rs
Normal file
27
crates/migrator/src/migrations/m_2025_04_23/settings.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use std::ops::Range;
|
||||
use tree_sitter::{Query, QueryMatch};
|
||||
|
||||
use crate::MigrationPatterns;
|
||||
use crate::patterns::SETTINGS_ASSISTANT_TOOLS_PATTERN;
|
||||
|
||||
pub const SETTINGS_PATTERNS: MigrationPatterns =
|
||||
&[(SETTINGS_ASSISTANT_TOOLS_PATTERN, rename_path_search_tool)];
|
||||
|
||||
fn rename_path_search_tool(
|
||||
contents: &str,
|
||||
mat: &QueryMatch,
|
||||
query: &Query,
|
||||
) -> Option<(Range<usize>, String)> {
|
||||
let tool_name_capture_ix = query.capture_index_for_name("tool_name")?;
|
||||
let tool_name_range = mat
|
||||
.nodes_for_capture_index(tool_name_capture_ix)
|
||||
.next()?
|
||||
.byte_range();
|
||||
let tool_name = contents.get(tool_name_range.clone())?;
|
||||
|
||||
if tool_name == "path_search" {
|
||||
return Some((tool_name_range, "find_path".to_string()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
@@ -132,6 +132,10 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
|
||||
migrations::m_2025_04_21::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_04_21,
|
||||
),
|
||||
(
|
||||
migrations::m_2025_04_23::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_04_23,
|
||||
),
|
||||
];
|
||||
run_migrations(text, migrations)
|
||||
}
|
||||
@@ -214,6 +218,10 @@ define_query!(
|
||||
SETTINGS_QUERY_2025_04_21,
|
||||
migrations::m_2025_04_21::SETTINGS_PATTERNS
|
||||
);
|
||||
define_query!(
|
||||
SETTINGS_QUERY_2025_04_23,
|
||||
migrations::m_2025_04_23::SETTINGS_PATTERNS
|
||||
);
|
||||
|
||||
// custom query
|
||||
static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
||||
@@ -639,7 +647,7 @@ mod tests {
|
||||
"name": "Custom",
|
||||
"tools": {
|
||||
"diagnostics": true,
|
||||
"path_search": true,
|
||||
"find_path": true,
|
||||
"read_file": true
|
||||
}
|
||||
}
|
||||
@@ -650,4 +658,40 @@ mod tests {
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rename_path_search_to_find_path() {
|
||||
assert_migrate_settings(
|
||||
r#"
|
||||
{
|
||||
"assistant": {
|
||||
"profiles": {
|
||||
"default": {
|
||||
"tools": {
|
||||
"path_search": true,
|
||||
"read_file": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
Some(
|
||||
r#"
|
||||
{
|
||||
"assistant": {
|
||||
"profiles": {
|
||||
"default": {
|
||||
"tools": {
|
||||
"find_path": true,
|
||||
"read_file": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "prompt_library"
|
||||
name = "rules_library"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
@@ -9,7 +9,7 @@ license = "GPL-3.0-or-later"
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/prompt_library.rs"
|
||||
path = "src/rules_library.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
File diff suppressed because it is too large
Load Diff
@@ -83,7 +83,6 @@ impl VsCodeDebugTaskDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
/// blah
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VsCodeDebugTaskFile {
|
||||
|
||||
@@ -115,6 +115,7 @@ pub struct TerminalView {
|
||||
blinking_paused: bool,
|
||||
blink_epoch: usize,
|
||||
hover_target_tooltip: Option<String>,
|
||||
hover_tooltip_update: Task<()>,
|
||||
workspace_id: Option<WorkspaceId>,
|
||||
show_breadcrumbs: bool,
|
||||
block_below_cursor: Option<Rc<BlockProperties>>,
|
||||
@@ -197,6 +198,7 @@ impl TerminalView {
|
||||
blinking_paused: false,
|
||||
blink_epoch: 0,
|
||||
hover_target_tooltip: None,
|
||||
hover_tooltip_update: Task::ready(()),
|
||||
workspace_id,
|
||||
show_breadcrumbs: TerminalSettings::get_global(cx).toolbar.breadcrumbs,
|
||||
block_below_cursor: None,
|
||||
@@ -844,7 +846,7 @@ fn subscribe_for_terminal_events(
|
||||
let terminal_events_subscription = cx.subscribe_in(
|
||||
terminal,
|
||||
window,
|
||||
move |this, _, event, window, cx| match event {
|
||||
move |terminal_view, _, event, window, cx| match event {
|
||||
Event::Wakeup => {
|
||||
cx.notify();
|
||||
cx.emit(Event::Wakeup);
|
||||
@@ -853,7 +855,7 @@ fn subscribe_for_terminal_events(
|
||||
}
|
||||
|
||||
Event::Bell => {
|
||||
this.has_bell = true;
|
||||
terminal_view.has_bell = true;
|
||||
cx.emit(Event::Wakeup);
|
||||
}
|
||||
|
||||
@@ -862,7 +864,7 @@ fn subscribe_for_terminal_events(
|
||||
TerminalSettings::get_global(cx).blinking,
|
||||
TerminalBlink::TerminalControlled
|
||||
) {
|
||||
this.blinking_terminal_enabled = *blinking;
|
||||
terminal_view.blinking_terminal_enabled = *blinking;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -871,25 +873,46 @@ fn subscribe_for_terminal_events(
|
||||
}
|
||||
|
||||
Event::NewNavigationTarget(maybe_navigation_target) => {
|
||||
this.hover_target_tooltip =
|
||||
maybe_navigation_target
|
||||
.as_ref()
|
||||
.and_then(|navigation_target| match navigation_target {
|
||||
MaybeNavigationTarget::Url(url) => Some(url.clone()),
|
||||
MaybeNavigationTarget::PathLike(path_like_target) => {
|
||||
let valid_files_to_open_task = possible_open_target(
|
||||
&workspace,
|
||||
&path_like_target.terminal_dir,
|
||||
&path_like_target.maybe_path,
|
||||
cx,
|
||||
);
|
||||
Some(match smol::block_on(valid_files_to_open_task)? {
|
||||
OpenTarget::File(path, _) | OpenTarget::Worktree(path, _) => {
|
||||
path.to_string(|path| path.to_string_lossy().to_string())
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
match maybe_navigation_target.as_ref() {
|
||||
None => {
|
||||
terminal_view.hover_target_tooltip = None;
|
||||
terminal_view.hover_tooltip_update = Task::ready(());
|
||||
}
|
||||
Some(MaybeNavigationTarget::Url(url)) => {
|
||||
terminal_view.hover_target_tooltip = Some(url.clone());
|
||||
terminal_view.hover_tooltip_update = Task::ready(());
|
||||
}
|
||||
Some(MaybeNavigationTarget::PathLike(path_like_target)) => {
|
||||
let valid_files_to_open_task = possible_open_target(
|
||||
&workspace,
|
||||
&path_like_target.terminal_dir,
|
||||
&path_like_target.maybe_path,
|
||||
cx,
|
||||
);
|
||||
|
||||
terminal_view.hover_tooltip_update =
|
||||
cx.spawn(async move |terminal_view, cx| {
|
||||
let file_to_open = valid_files_to_open_task.await;
|
||||
terminal_view
|
||||
.update(cx, |terminal_view, _| match file_to_open {
|
||||
Some(
|
||||
OpenTarget::File(path, _)
|
||||
| OpenTarget::Worktree(path, _),
|
||||
) => {
|
||||
terminal_view.hover_target_tooltip =
|
||||
Some(path.to_string(|path| {
|
||||
path.to_string_lossy().to_string()
|
||||
}));
|
||||
}
|
||||
None => {
|
||||
terminal_view.hover_target_tooltip = None;
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
@@ -897,7 +920,7 @@ fn subscribe_for_terminal_events(
|
||||
MaybeNavigationTarget::Url(url) => cx.open_url(url),
|
||||
|
||||
MaybeNavigationTarget::PathLike(path_like_target) => {
|
||||
if this.hover_target_tooltip.is_none() {
|
||||
if terminal_view.hover_target_tooltip.is_none() {
|
||||
return;
|
||||
}
|
||||
let task_workspace = workspace.clone();
|
||||
@@ -1207,9 +1230,12 @@ fn possible_open_target(
|
||||
|
||||
let fs = workspace.read(cx).project().read(cx).fs().clone();
|
||||
cx.background_spawn(async move {
|
||||
for path_to_check in fs_paths_to_check {
|
||||
if let Some(metadata) = fs.metadata(&path_to_check.path).await.ok().flatten() {
|
||||
return Some(OpenTarget::File(path_to_check, metadata));
|
||||
for mut path_to_check in fs_paths_to_check {
|
||||
if let Some(fs_path_to_check) = fs.canonicalize(&path_to_check.path).await.ok() {
|
||||
if let Some(metadata) = fs.metadata(&fs_path_to_check).await.ok().flatten() {
|
||||
path_to_check.path = fs_path_to_check;
|
||||
return Some(OpenTarget::File(path_to_check, metadata));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ itertools.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = ["use_tokio"], optional = true }
|
||||
nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", rev = "764dd270c642f77f10f3e19d05cc178a6cbe69f3", features = ["use_tokio"], optional = true }
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition.workspace = true
|
||||
name = "zed"
|
||||
version = "0.184.0"
|
||||
version = "0.184.2"
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
preview
|
||||
@@ -199,14 +199,14 @@ pub mod assistant {
|
||||
|
||||
#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OpenPromptLibrary {
|
||||
pub struct OpenRulesLibrary {
|
||||
#[serde(skip)]
|
||||
pub prompt_to_select: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl_action_with_deprecated_aliases!(
|
||||
assistant,
|
||||
OpenPromptLibrary,
|
||||
OpenRulesLibrary,
|
||||
["assistant::DeployPromptLibrary"]
|
||||
);
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ regex = { version = "1" }
|
||||
regex-automata = { version = "0.4" }
|
||||
regex-syntax = { version = "0.8" }
|
||||
rust_decimal = { version = "1", default-features = false, features = ["maths", "serde", "std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["fs", "net"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["fs", "net", "std"] }
|
||||
rustls = { version = "0.23", features = ["ring"] }
|
||||
rustls-webpki = { version = "0.103", default-features = false, features = ["aws-lc-rs", "ring", "std"] }
|
||||
sea-orm = { version = "1", features = ["runtime-tokio-rustls", "sqlx-postgres", "sqlx-sqlite"] }
|
||||
@@ -205,7 +205,7 @@ regex = { version = "1" }
|
||||
regex-automata = { version = "0.4" }
|
||||
regex-syntax = { version = "0.8" }
|
||||
rust_decimal = { version = "1", default-features = false, features = ["maths", "serde", "std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["fs", "net"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["fs", "net", "std"] }
|
||||
rustls = { version = "0.23", features = ["ring"] }
|
||||
rustls-webpki = { version = "0.103", default-features = false, features = ["aws-lc-rs", "ring", "std"] }
|
||||
sea-orm = { version = "1", features = ["runtime-tokio-rustls", "sqlx-postgres", "sqlx-sqlite"] }
|
||||
@@ -258,8 +258,8 @@ naga = { version = "23", features = ["msl-out", "wgsl-in"] }
|
||||
nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
|
||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||
scopeguard = { version = "1" }
|
||||
security-framework = { version = "3", features = ["OSX_10_14"] }
|
||||
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
||||
@@ -283,8 +283,8 @@ nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
|
||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||
scopeguard = { version = "1" }
|
||||
security-framework = { version = "3", features = ["OSX_10_14"] }
|
||||
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
||||
@@ -306,8 +306,8 @@ naga = { version = "23", features = ["msl-out", "wgsl-in"] }
|
||||
nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
|
||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||
scopeguard = { version = "1" }
|
||||
security-framework = { version = "3", features = ["OSX_10_14"] }
|
||||
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
||||
@@ -331,8 +331,8 @@ nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
|
||||
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
|
||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||
scopeguard = { version = "1" }
|
||||
security-framework = { version = "3", features = ["OSX_10_14"] }
|
||||
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
|
||||
@@ -356,7 +356,8 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
|
||||
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
|
||||
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
|
||||
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
|
||||
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
|
||||
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
mio = { version = "1", features = ["net", "os-ext"] }
|
||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||
@@ -367,8 +368,8 @@ proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||
quote = { version = "1" }
|
||||
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||
scopeguard = { version = "1" }
|
||||
smallvec = { version = "1", default-features = false, features = ["write"] }
|
||||
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
|
||||
@@ -396,7 +397,8 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
|
||||
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
|
||||
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
|
||||
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
|
||||
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
|
||||
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
mio = { version = "1", features = ["net", "os-ext"] }
|
||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||
@@ -406,8 +408,8 @@ object = { version = "0.36", default-features = false, features = ["archive", "r
|
||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||
scopeguard = { version = "1" }
|
||||
smallvec = { version = "1", default-features = false, features = ["write"] }
|
||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
||||
@@ -434,7 +436,8 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
|
||||
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
|
||||
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
|
||||
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
|
||||
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
|
||||
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
mio = { version = "1", features = ["net", "os-ext"] }
|
||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||
@@ -445,8 +448,8 @@ proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||
quote = { version = "1" }
|
||||
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||
scopeguard = { version = "1" }
|
||||
smallvec = { version = "1", default-features = false, features = ["write"] }
|
||||
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
|
||||
@@ -474,7 +477,8 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
|
||||
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
|
||||
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
|
||||
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
|
||||
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
|
||||
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
mio = { version = "1", features = ["net", "os-ext"] }
|
||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||
@@ -484,8 +488,8 @@ object = { version = "0.36", default-features = false, features = ["archive", "r
|
||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||
scopeguard = { version = "1" }
|
||||
smallvec = { version = "1", default-features = false, features = ["write"] }
|
||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
||||
@@ -506,7 +510,7 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1"
|
||||
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
|
||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event"] }
|
||||
scopeguard = { version = "1" }
|
||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||
@@ -514,7 +518,7 @@ tokio-stream = { version = "0.1", features = ["fs"] }
|
||||
winapi = { version = "0.3", default-features = false, features = ["cfg", "consoleapi", "errhandlingapi", "evntrace", "fileapi", "handleapi", "in6addr", "inaddr", "knownfolders", "minwinbase", "ntsecapi", "objbase", "processenv", "processthreadsapi", "shlobj", "std", "sysinfoapi", "winbase", "windef", "winerror", "winioctl"] }
|
||||
windows-core = { version = "0.61" }
|
||||
windows-numerics = { version = "0.2" }
|
||||
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
||||
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
||||
windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] }
|
||||
windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_UI_Shell"] }
|
||||
|
||||
@@ -529,7 +533,7 @@ itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
|
||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event"] }
|
||||
scopeguard = { version = "1" }
|
||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
||||
tokio-socks = { version = "0.5", features = ["futures-io"] }
|
||||
@@ -537,7 +541,7 @@ tokio-stream = { version = "0.1", features = ["fs"] }
|
||||
winapi = { version = "0.3", default-features = false, features = ["cfg", "consoleapi", "errhandlingapi", "evntrace", "fileapi", "handleapi", "in6addr", "inaddr", "knownfolders", "minwinbase", "ntsecapi", "objbase", "processenv", "processthreadsapi", "shlobj", "std", "sysinfoapi", "winbase", "windef", "winerror", "winioctl"] }
|
||||
windows-core = { version = "0.61" }
|
||||
windows-numerics = { version = "0.2" }
|
||||
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
||||
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
||||
windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] }
|
||||
windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_UI_Shell"] }
|
||||
|
||||
@@ -557,7 +561,8 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
|
||||
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
|
||||
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
|
||||
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
|
||||
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
|
||||
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
mio = { version = "1", features = ["net", "os-ext"] }
|
||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||
@@ -568,8 +573,8 @@ proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||
quote = { version = "1" }
|
||||
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||
scopeguard = { version = "1" }
|
||||
smallvec = { version = "1", default-features = false, features = ["write"] }
|
||||
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
|
||||
@@ -597,7 +602,8 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
|
||||
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
|
||||
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
|
||||
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
|
||||
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
|
||||
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
|
||||
mio = { version = "1", features = ["net", "os-ext"] }
|
||||
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
|
||||
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
|
||||
@@ -607,8 +613,8 @@ object = { version = "0.36", default-features = false, features = ["archive", "r
|
||||
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
|
||||
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
|
||||
ring = { version = "0.17", features = ["std"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
|
||||
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
|
||||
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
|
||||
scopeguard = { version = "1" }
|
||||
smallvec = { version = "1", default-features = false, features = ["write"] }
|
||||
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
|
||||
|
||||
Reference in New Issue
Block a user