Compare commits
24 Commits
rustls-pan
...
tool-rende
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac63ea504a | ||
|
|
11aaea964f | ||
|
|
cd9a45a262 | ||
|
|
1965029528 | ||
|
|
46c738d724 | ||
|
|
b18bd2d67b | ||
|
|
7e0a97fabe | ||
|
|
8ba9429e60 | ||
|
|
0b75c13034 | ||
|
|
38ec45008c | ||
|
|
97641c3298 | ||
|
|
ca8f6e8a3f | ||
|
|
db53da49e1 | ||
|
|
df94dcdea6 | ||
|
|
1c85901440 | ||
|
|
9fb77ad176 | ||
|
|
cad3ee8def | ||
|
|
feafad2f9d | ||
|
|
42cb40170e | ||
|
|
7dc3a39edf | ||
|
|
86ef00054b | ||
|
|
fc1b3889c2 | ||
|
|
34c6fee959 | ||
|
|
b25ec65b48 |
36
.github/ISSUE_TEMPLATE/01_bug_agent.yml
vendored
Normal file
36
.github/ISSUE_TEMPLATE/01_bug_agent.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Bug Report (Agent Panel)
|
||||
description: Zed Agent Panel Bugs
|
||||
type: "Bug"
|
||||
labels: ["agent", "ai"]
|
||||
title: "Agent Panel: <a short description of the Agent Panel bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
### Description
|
||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
||||
<!-- Please include the LLM provider and model name you are using -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
Expected Behavior:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
||||
placeholder: |
|
||||
Output of "zed: Copy System Specs Into Clipboard"
|
||||
validations:
|
||||
required: true
|
||||
51
.github/ISSUE_TEMPLATE/01_git_bug_report.yml
vendored
51
.github/ISSUE_TEMPLATE/01_git_bug_report.yml
vendored
@@ -1,51 +0,0 @@
|
||||
name: Git Bug Report
|
||||
description: There is a bug related to Git features in Zed
|
||||
type: "Bug"
|
||||
labels: ["git"]
|
||||
title: "Git: <a short description of the Git bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
|
||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
|
||||
Expected Behavior:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
||||
placeholder: |
|
||||
Output of "zed: Copy System Specs Into Clipboard"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
description: |
|
||||
macOS: `~/Library/Logs/Zed/Zed.log`
|
||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
```
|
||||
|
||||
```
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
||||
validations:
|
||||
required: false
|
||||
51
.github/ISSUE_TEMPLATE/02_agent_bug_report.yml
vendored
51
.github/ISSUE_TEMPLATE/02_agent_bug_report.yml
vendored
@@ -1,51 +0,0 @@
|
||||
name: Agent Panel Bug Report
|
||||
description: There is a bug related to the Agent Panel in Zed
|
||||
type: "Bug"
|
||||
labels: ["agent", "ai"]
|
||||
title: "Agent Panel: <a short description of the Agent Panel bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
|
||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
|
||||
Expected Behavior:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
||||
placeholder: |
|
||||
Output of "zed: Copy System Specs Into Clipboard"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
description: |
|
||||
macOS: `~/Library/Logs/Zed/Zed.log`
|
||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
```
|
||||
|
||||
```
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Edit Predictions Bug Report
|
||||
description: There is a bug related to Edit Predictions in Zed
|
||||
name: Bug Report (Edit Predictions)
|
||||
description: Zed Edit Predictions bugs
|
||||
type: "Bug"
|
||||
labels: ["ai", "inline completion", "zeta"]
|
||||
title: "Edit Predictions: <a short description of the Edit Prediction bug>"
|
||||
@@ -10,19 +10,21 @@ body:
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
||||
### Description
|
||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
||||
<!-- Please include the LLM provider and model name you are using -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
|
||||
Expected Behavior:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
@@ -32,20 +34,3 @@ body:
|
||||
Output of "zed: Copy System Specs Into Clipboard"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: If applicable, attach your `~/Library/Logs/Zed/Zed.log` file to this issue.
|
||||
description: |
|
||||
macOS: `~/Library/Logs/Zed/Zed.log`
|
||||
Linux: `~/.local/share/zed/logs/Zed.log` or $XDG_DATA_HOME
|
||||
If you only need the most recent lines, you can run the `zed: open log` command palette action to see the last 1000.
|
||||
value: |
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
```
|
||||
|
||||
```
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
||||
validations:
|
||||
required: false
|
||||
35
.github/ISSUE_TEMPLATE/03_bug_git.yml
vendored
Normal file
35
.github/ISSUE_TEMPLATE/03_bug_git.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Bug Report (Git)
|
||||
description: Zed Git-Related Bugs
|
||||
type: "Bug"
|
||||
labels: ["git"]
|
||||
title: "Git: <a short description of the Git bug>"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
### Description
|
||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install. -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
Expected Behavior:
|
||||
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
||||
placeholder: |
|
||||
Output of "zed: Copy System Specs Into Clipboard"
|
||||
validations:
|
||||
required: true
|
||||
47
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
47
.github/ISSUE_TEMPLATE/10_bug_report.yml
vendored
@@ -1,46 +1,44 @@
|
||||
name: Bug Report
|
||||
name: Bug Report (Other)
|
||||
description: |
|
||||
Something is broken in Zed (exclude crashing).
|
||||
Something else is broken in Zed (exclude crashing).
|
||||
type: "Bug"
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
description: Provide a one sentence summary and detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
|
||||
<!-- Begin your issue with a one sentence summary -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
<!-- Be verbose: Include all steps necessary to reproduce from a clean Zed installation. -->
|
||||
<!-- Code snippets are better than images, a repository link that reproduces the issue is ideal. -->
|
||||
### Description
|
||||
<!-- Describe with sufficient detail to reproduce from a clean Zed install.
|
||||
- Any code must be sufficient to reproduce (include context!)
|
||||
- Code must as text, not just as a screenshot.
|
||||
- Issues with insufficient detail may be summarily closed.
|
||||
-->
|
||||
|
||||
Steps to trigger the problem:
|
||||
Steps to reproduce:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
Expected Behavior:
|
||||
Actual Behavior:
|
||||
|
||||
Expected Behavior:
|
||||
<!-- Before Submitting, did you:
|
||||
1. Include settings.json, keymap.json, .editorconfig if relevant?
|
||||
2. Check your Zed.log for relevant errors? (please include!)
|
||||
3. Click Preview to ensure everything looks right?
|
||||
4. Hide videos, large images and logs in ``` inside collapsible blocks:
|
||||
|
||||
<!--
|
||||
Is there anything additional necessary to reproduce this issue?
|
||||
- settings.json, keymap.json, .editorconfig etc?
|
||||
- Does it happen intermittently or only with specific projects / file types?
|
||||
- Have you found a workaround?
|
||||
<details><summary>click to expand</summary>
|
||||
|
||||
Did you check your Zed.log to see if there is any relevant details there?
|
||||
- When including large items (videos, screenshots, logs, configs) please wrap with:
|
||||
```json
|
||||
|
||||
<details><summary>See inside for XXXXYYY</summary>
|
||||
|
||||
```shell
|
||||
code
|
||||
```
|
||||
|
||||
</details>
|
||||
```
|
||||
</details>
|
||||
-->
|
||||
|
||||
validations:
|
||||
@@ -50,7 +48,8 @@ body:
|
||||
id: environment
|
||||
attributes:
|
||||
label: Zed Version and System Specs
|
||||
description: 'Open Zed, and in the command palette select "zed: Copy System Specs Into Clipboard"'
|
||||
description: |
|
||||
Open Zed, from the command palette select "zed: Copy System Specs Into Clipboard"
|
||||
placeholder: |
|
||||
Output of "zed: Copy System Specs Into Clipboard"
|
||||
validations:
|
||||
|
||||
14
.github/ISSUE_TEMPLATE/11_crash_report.yml
vendored
14
.github/ISSUE_TEMPLATE/11_crash_report.yml
vendored
@@ -5,10 +5,12 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Describe the bug with a one line summary, and provide detailed reproduction steps
|
||||
description: Summarize the issue with detailed reproduction steps
|
||||
value: |
|
||||
<!-- Please insert a one line summary of the issue below -->
|
||||
<!-- Begin your issue with a one sentence summary -->
|
||||
SUMMARY_SENTENCE_HERE
|
||||
|
||||
### Description
|
||||
<!-- Include all steps necessary to reproduce from a clean Zed installation. Be verbose -->
|
||||
Steps to trigger the problem:
|
||||
1.
|
||||
@@ -16,7 +18,6 @@ body:
|
||||
3.
|
||||
|
||||
Actual Behavior:
|
||||
|
||||
Expected Behavior:
|
||||
|
||||
validations:
|
||||
@@ -40,10 +41,11 @@ body:
|
||||
value: |
|
||||
<details><summary>Zed.log</summary>
|
||||
|
||||
<!-- Click below this line and paste or drag-and-drop your log-->
|
||||
```
|
||||
<!-- Paste your log inside the code block. -->
|
||||
```log
|
||||
|
||||
```
|
||||
<!-- Click above this line and paste or drag-and-drop your log--></details>
|
||||
|
||||
</details>
|
||||
validations:
|
||||
required: false
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -4,9 +4,6 @@ contact_links:
|
||||
- name: Feature Request
|
||||
url: https://github.com/zed-industries/zed/discussions/new/choose
|
||||
about: To request a feature, open a new Discussion in one of the appropriate Discussion categories
|
||||
- name: Zed Discussion Forum
|
||||
url: https://github.com/zed-industries/zed/discussions
|
||||
about: A community discussion forum
|
||||
- name: "Zed Discord: #Support Channel"
|
||||
- name: "Zed Discord"
|
||||
url: https://zed.dev/community-links
|
||||
about: Real-time discussion and user support
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -114,7 +114,9 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
name: Check workspace-hack crate
|
||||
needs: [job_spec]
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
if: |
|
||||
github.repository_owner == 'zed-industries' &&
|
||||
needs.job_spec.outputs.run_tests == 'true'
|
||||
runs-on:
|
||||
- buildjet-8vcpu-ubuntu-2204
|
||||
steps:
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -746,7 +746,6 @@ dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"language",
|
||||
"language_model",
|
||||
"lsp",
|
||||
"open",
|
||||
"project",
|
||||
"rand 0.8.5",
|
||||
|
||||
@@ -6,11 +6,16 @@ You are an AI assistant integrated into a code editor. You have the programming
|
||||
It will be up to you to decide which of these you are doing based on what the user has told you. When unclear, ask clarifying questions to understand the user's intent before proceeding.
|
||||
|
||||
You should only perform actions that modify the user's system if explicitly requested by the user:
|
||||
- If the user asks a question about how to accomplish a task, provide guidance or information, and use read-only tools (e.g., search) to assist. You may suggest potential actions, but do not directly modify the user’s system without explicit instruction.
|
||||
- If the user asks a question about how to accomplish a task, provide guidance or information, and use read-only tools (e.g., search) to assist. You may suggest potential actions, but do not directly modify the user's system without explicit instruction.
|
||||
- If the user clearly requests that you perform an action, carry out the action directly without explaining why you are doing so.
|
||||
|
||||
When answering questions, it's okay to give incomplete examples containing comments about what would go there in a real version. When being asked to directly perform tasks on the code base, you must ALWAYS make fully working code. You may never "simplify" the code by omitting or deleting functionality you know the user has requested, and you must NEVER write comments like "in a full version, this would..." - instead, you must actually implement the real version. Don't be lazy!
|
||||
|
||||
Note that project files are automatically backed up. The user can always get them back later if anything goes wrong, so there's
|
||||
no need to create backup files (e.g. `.bak` files) because these files will just take up unnecessary space on the user's disk.
|
||||
|
||||
When attempting to resolve issues around failing tests, never simply remove the failing tests. Unless the user explicitly asks you to remove tests, ALWAYS attempt to fix the code causing the tests to fail.
|
||||
|
||||
<style>
|
||||
Editing code:
|
||||
- Make sure to take previous edits into account.
|
||||
|
||||
@@ -21,7 +21,10 @@ use gpui::{
|
||||
linear_color_stop, linear_gradient, list, percentage, pulsating_between,
|
||||
};
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use language_model::{ConfiguredModel, LanguageModelRegistry, LanguageModelToolUseId, Role};
|
||||
use language_model::{
|
||||
ConfiguredModel, LanguageModelRegistry, LanguageModelToolUseId, Role, StringToolOutput,
|
||||
ToolOutput,
|
||||
};
|
||||
use markdown::parser::CodeBlockKind;
|
||||
use markdown::{Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown, without_fences};
|
||||
use project::ProjectItem as _;
|
||||
@@ -36,6 +39,7 @@ use text::ToPoint;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, Tooltip, prelude::*};
|
||||
use util::ResultExt as _;
|
||||
use util::markdown::MarkdownString;
|
||||
use workspace::{OpenOptions, Workspace};
|
||||
|
||||
use crate::context_store::ContextStore;
|
||||
@@ -74,7 +78,7 @@ struct RenderedMessage {
|
||||
struct RenderedToolUse {
|
||||
label: Entity<Markdown>,
|
||||
input: Entity<Markdown>,
|
||||
output: Entity<Markdown>,
|
||||
output: ToolOutput,
|
||||
}
|
||||
|
||||
impl RenderedMessage {
|
||||
@@ -729,21 +733,28 @@ impl ActiveThread {
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_label: impl Into<SharedString>,
|
||||
tool_input: &serde_json::Value,
|
||||
tool_output: SharedString,
|
||||
tool_output: ToolOutput,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let rendered = RenderedToolUse {
|
||||
label: render_tool_use_markdown(tool_label.into(), self.language_registry.clone(), cx),
|
||||
input: render_tool_use_markdown(
|
||||
format!(
|
||||
"```json\n{}\n```",
|
||||
serde_json::to_string_pretty(tool_input).unwrap_or_default()
|
||||
MarkdownString::code_block(
|
||||
"json",
|
||||
&serde_json::to_string_pretty(tool_input).unwrap_or_default(),
|
||||
)
|
||||
.to_string()
|
||||
.into(),
|
||||
self.language_registry.clone(),
|
||||
cx,
|
||||
),
|
||||
output: render_tool_use_markdown(tool_output, self.language_registry.clone(), cx),
|
||||
output: render_tool_use_markdown(
|
||||
tool_output.clone(),
|
||||
self.language_registry.clone(),
|
||||
cx,
|
||||
),
|
||||
output: StringToolOutput::new(tool_output, language_registry: Arc<LanguageRegistry>),
|
||||
|
||||
};
|
||||
self.rendered_tool_uses
|
||||
.insert(tool_use_id.clone(), rendered);
|
||||
@@ -2094,24 +2105,50 @@ impl ActiveThread {
|
||||
results_content_container()
|
||||
.border_t_1()
|
||||
.border_color(self.tool_card_border_color(cx))
|
||||
.child(
|
||||
Label::new("Result")
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted)
|
||||
.buffer_font(cx),
|
||||
)
|
||||
.child(div().w_full().text_ui_sm(cx).children(
|
||||
rendered_tool_use.as_ref().map(|rendered| {
|
||||
MarkdownElement::new(
|
||||
rendered.output.clone(),
|
||||
tool_use_markdown_style(window, cx),
|
||||
)
|
||||
.on_url_click({
|
||||
let workspace = self.workspace.clone();
|
||||
move |text, window, cx| {
|
||||
open_markdown_link(text, workspace.clone(), window, cx);
|
||||
let tool_name = tool_use.name.to_string();
|
||||
let tool_registry = assistant_tool::ToolRegistry::global(cx);
|
||||
|
||||
if let Some(_tool) = tool_registry.tool(&tool_name) {
|
||||
// Tool doesn't have a render method, but ToolOutput does
|
||||
match rendered.output.render(window, cx) {
|
||||
Some(rendered) => rendered,
|
||||
None => {
|
||||
// Default to rendering the output as markdown
|
||||
div()
|
||||
.child(
|
||||
Label::new("Result")
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted)
|
||||
.buffer_font(cx),
|
||||
)
|
||||
.child(
|
||||
div().w_full().text_ui_sm(cx).child(
|
||||
MarkdownElement::new(
|
||||
rendered.output.clone(),
|
||||
tool_use_markdown_style(window, cx),
|
||||
)
|
||||
.on_url_click({
|
||||
let workspace = self.workspace.clone();
|
||||
move |text, window, cx| {
|
||||
open_markdown_link(
|
||||
text,
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
log::error!("Tool not found: {tool_name}");
|
||||
gpui::Empty.into_any_element()
|
||||
}
|
||||
}),
|
||||
)),
|
||||
),
|
||||
@@ -2158,16 +2195,30 @@ impl ActiveThread {
|
||||
div()
|
||||
.text_ui_sm(cx)
|
||||
.children(rendered_tool_use.as_ref().map(|rendered| {
|
||||
MarkdownElement::new(
|
||||
rendered.output.clone(),
|
||||
tool_use_markdown_style(window, cx),
|
||||
)
|
||||
.on_url_click({
|
||||
let workspace = self.workspace.clone();
|
||||
move |text, window, cx| {
|
||||
open_markdown_link(text, workspace.clone(), window, cx);
|
||||
}
|
||||
})
|
||||
let tool_name = tool_use.name.to_string();
|
||||
let tool_registry = assistant_tool::ToolRegistry::global(cx);
|
||||
|
||||
tool_registry
|
||||
.tool(&tool_name)
|
||||
.and_then(|_tool| None) // Tool doesn't have a render method, but ToolOutput does
|
||||
.unwrap_or_else(|| {
|
||||
MarkdownElement::new(
|
||||
rendered.output.clone(),
|
||||
tool_use_markdown_style(window, cx),
|
||||
)
|
||||
.on_url_click({
|
||||
let workspace = self.workspace.clone();
|
||||
move |text, window, cx| {
|
||||
open_markdown_link(
|
||||
text,
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
})
|
||||
})),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -6,6 +6,7 @@ use collections::HashMap;
|
||||
use futures::FutureExt as _;
|
||||
use futures::future::Shared;
|
||||
use gpui::{App, SharedString, Task};
|
||||
use language_model::ToolOutput;
|
||||
use language_model::{
|
||||
LanguageModelRegistry, LanguageModelRequestMessage, LanguageModelToolResult,
|
||||
LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role,
|
||||
@@ -32,7 +33,7 @@ pub enum ToolUseStatus {
|
||||
NeedsConfirmation,
|
||||
Pending,
|
||||
Running,
|
||||
Finished(SharedString),
|
||||
Finished(ToolOutput),
|
||||
Error(SharedString),
|
||||
}
|
||||
|
||||
@@ -131,6 +132,7 @@ impl ToolUseState {
|
||||
tool_name: tool_use.clone(),
|
||||
is_error: tool_result.is_error,
|
||||
content: tool_result.content.clone(),
|
||||
tool_output: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -153,6 +155,7 @@ impl ToolUseState {
|
||||
tool_name: tool_use.name.clone(),
|
||||
content: "Tool canceled by user".into(),
|
||||
is_error: true,
|
||||
tool_output: None,
|
||||
},
|
||||
);
|
||||
pending_tools.push(tool_use.clone());
|
||||
@@ -331,7 +334,7 @@ impl ToolUseState {
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
tool_name: Arc<str>,
|
||||
output: Result<String>,
|
||||
output: Result<ToolOutput>,
|
||||
cx: &App,
|
||||
) -> Option<PendingToolUse> {
|
||||
match output {
|
||||
@@ -346,16 +349,19 @@ impl ToolUseState {
|
||||
.map(|model| model.model.max_token_count() * BYTES_PER_TOKEN_ESTIMATE)
|
||||
.unwrap_or(usize::MAX);
|
||||
|
||||
let tool_result = if tool_result.len() <= tool_output_limit {
|
||||
tool_result
|
||||
} else {
|
||||
let truncated = truncate_lines_to_byte_limit(&tool_result, tool_output_limit);
|
||||
// Get string representation of the tool result
|
||||
let response_text = tool_result.response_for_model();
|
||||
|
||||
format!(
|
||||
"Tool result too long. The first {} bytes:\n\n{}",
|
||||
truncated.len(),
|
||||
truncated
|
||||
)
|
||||
// Check length and truncate if needed
|
||||
let final_tool_result = if response_text.len() <= tool_output_limit {
|
||||
response_text.to_string()
|
||||
} else {
|
||||
let response_string = response_text.to_string();
|
||||
let truncated =
|
||||
truncate_lines_to_byte_limit(&response_string, tool_output_limit);
|
||||
let truncated_len = truncated.len();
|
||||
|
||||
format!("Tool result too long. The first {truncated_len} bytes:\n\n{truncated}")
|
||||
};
|
||||
|
||||
self.tool_results.insert(
|
||||
@@ -363,8 +369,9 @@ impl ToolUseState {
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
tool_name,
|
||||
content: tool_result.into(),
|
||||
content: Arc::from(final_tool_result),
|
||||
is_error: false,
|
||||
tool_output: Some(Arc::new(tool_result)),
|
||||
},
|
||||
);
|
||||
self.pending_tool_uses_by_id.remove(&tool_use_id)
|
||||
@@ -377,6 +384,7 @@ impl ToolUseState {
|
||||
tool_name,
|
||||
content: err.to_string().into(),
|
||||
is_error: true,
|
||||
tool_output: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -451,6 +459,7 @@ impl ToolUseState {
|
||||
} else {
|
||||
tool_result.content.clone()
|
||||
},
|
||||
tool_output: tool_result.tool_output.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -8,13 +8,15 @@ use std::fmt::Formatter;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{App, Entity, SharedString, Task};
|
||||
use gpui::{self, App, Entity, SharedString, Task};
|
||||
use icons::IconName;
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
use language_model::ToolOutput;
|
||||
use project::Project;
|
||||
|
||||
pub use crate::action_log::*;
|
||||
// StringToolOutput is now directly imported from language_model
|
||||
pub use crate::tool_registry::*;
|
||||
pub use crate::tool_working_set::*;
|
||||
|
||||
@@ -66,7 +68,7 @@ pub trait Tool: 'static + Send + Sync {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>>;
|
||||
) -> Task<Result<ToolOutput>>;
|
||||
}
|
||||
|
||||
impl Debug for dyn Tool {
|
||||
|
||||
@@ -23,7 +23,6 @@ http_client.workspace = true
|
||||
itertools.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
lsp.workspace = true
|
||||
project.workspace = true
|
||||
regex.workspace = true
|
||||
schemars.workspace = true
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
mod bash_tool;
|
||||
mod batch_tool;
|
||||
mod code_symbol_iter;
|
||||
mod code_symbols_tool;
|
||||
mod copy_path_tool;
|
||||
mod create_directory_tool;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use futures::io::BufReader;
|
||||
use futures::{AsyncBufReadExt, AsyncReadExt};
|
||||
use gpui::{App, Entity, Task};
|
||||
@@ -16,7 +17,7 @@ use util::markdown::MarkdownString;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct BashToolInput {
|
||||
/// The bash command to execute as a one-liner.
|
||||
/// The bash one-liner command to execute.
|
||||
command: String,
|
||||
/// Working directory for the command. This must be one of the root directories of the project.
|
||||
cd: String,
|
||||
@@ -76,7 +77,7 @@ impl Tool for BashTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input: BashToolInput = match serde_json::from_value(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -123,7 +124,7 @@ impl Tool for BashTool {
|
||||
worktree.read(cx).abs_path()
|
||||
};
|
||||
|
||||
cx.spawn(async move |_| {
|
||||
cx.spawn(async move |_| -> Result<ToolOutput> {
|
||||
// Add 2>&1 to merge stderr into stdout for proper interleaving.
|
||||
let command = format!("({}) 2>&1", input.command);
|
||||
|
||||
@@ -192,25 +193,21 @@ impl Tool for BashTool {
|
||||
String::from_utf8_lossy(&output_bytes).into()
|
||||
};
|
||||
|
||||
let output_with_status = if status.success() {
|
||||
if status.success() {
|
||||
if output_string.is_empty() {
|
||||
"Command executed successfully.".to_string()
|
||||
Ok(StringToolOutput::new("Command executed successfully."))
|
||||
} else {
|
||||
output_string.to_string()
|
||||
Ok(StringToolOutput::new(output_string))
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
Ok(StringToolOutput::new(format!(
|
||||
"{}{}{}{}",
|
||||
ERR_MESSAGE_1,
|
||||
status.code().unwrap_or(-1),
|
||||
ERR_MESSAGE_2,
|
||||
output_string,
|
||||
)
|
||||
};
|
||||
|
||||
debug_assert!(output_with_status.len() <= STDOUT_LIMIT);
|
||||
|
||||
Ok(output_with_status)
|
||||
)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool, ToolWorkingSet};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use futures::future::join_all;
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -210,7 +211,7 @@ impl Tool for BatchTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<BatchToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -280,7 +281,7 @@ impl Tool for BatchTool {
|
||||
match result {
|
||||
Ok(output) => {
|
||||
formatted_results
|
||||
.push_str(&format!("Tool '{}' result:\n{}\n\n", tool_name, output));
|
||||
.push_str(&format!("Tool '{}' result:\n{}\n\n", tool_name, output.response_for_model()));
|
||||
}
|
||||
Err(err) => {
|
||||
error_occurred = true;
|
||||
@@ -295,7 +296,7 @@ impl Tool for BatchTool {
|
||||
.push_str("Note: Some tool invocations failed. See individual results above.");
|
||||
}
|
||||
|
||||
Ok(formatted_results.trim().to_string())
|
||||
Ok(StringToolOutput::new(formatted_results.trim().to_string()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
use project::DocumentSymbol;
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Entry {
|
||||
pub name: String,
|
||||
pub kind: lsp::SymbolKind,
|
||||
pub depth: u32,
|
||||
pub start_line: usize,
|
||||
pub end_line: usize,
|
||||
}
|
||||
|
||||
/// An iterator that filters document symbols based on a regex pattern.
|
||||
/// This iterator recursively traverses the document symbol tree, incrementing depth for child symbols.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CodeSymbolIterator<'a> {
|
||||
symbols: &'a [DocumentSymbol],
|
||||
regex: Option<Regex>,
|
||||
// Stack of (symbol, depth) pairs to process
|
||||
pending_symbols: Vec<(&'a DocumentSymbol, u32)>,
|
||||
current_index: usize,
|
||||
current_depth: u32,
|
||||
}
|
||||
|
||||
impl<'a> CodeSymbolIterator<'a> {
|
||||
pub fn new(symbols: &'a [DocumentSymbol], regex: Option<Regex>) -> Self {
|
||||
Self {
|
||||
symbols,
|
||||
regex,
|
||||
pending_symbols: Vec::new(),
|
||||
current_index: 0,
|
||||
current_depth: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for CodeSymbolIterator<'_> {
|
||||
type Item = Entry;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some((symbol, depth)) = self.pending_symbols.pop() {
|
||||
for child in symbol.children.iter().rev() {
|
||||
self.pending_symbols.push((child, depth + 1));
|
||||
}
|
||||
|
||||
return Some(Entry {
|
||||
name: symbol.name.clone(),
|
||||
kind: symbol.kind,
|
||||
depth,
|
||||
start_line: symbol.range.start.0.row as usize,
|
||||
end_line: symbol.range.end.0.row as usize,
|
||||
});
|
||||
}
|
||||
|
||||
while self.current_index < self.symbols.len() {
|
||||
let regex = self.regex.as_ref();
|
||||
let symbol = &self.symbols[self.current_index];
|
||||
self.current_index += 1;
|
||||
|
||||
if regex.is_none_or(|regex| regex.is_match(&symbol.name)) {
|
||||
// Push in reverse order to maintain traversal order
|
||||
for child in symbol.children.iter().rev() {
|
||||
self.pending_symbols.push((child, self.current_depth + 1));
|
||||
}
|
||||
|
||||
return Some(Entry {
|
||||
name: symbol.name.clone(),
|
||||
kind: symbol.kind,
|
||||
depth: self.current_depth,
|
||||
start_line: symbol.range.start.0.row as usize,
|
||||
end_line: symbol.range.end.0.row as usize,
|
||||
});
|
||||
} else {
|
||||
// Even if parent doesn't match, push children to check them later
|
||||
for child in symbol.children.iter().rev() {
|
||||
self.pending_symbols.push((child, self.current_depth + 1));
|
||||
}
|
||||
|
||||
// Check if any pending children match our criteria
|
||||
if let Some(result) = self.next() {
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,22 @@
|
||||
use std::fmt::{self, Write};
|
||||
use std::fmt::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use collections::IndexMap;
|
||||
use gpui::{App, AsyncApp, Entity, Task};
|
||||
use language::{CodeLabel, Language, LanguageRegistry};
|
||||
use language::{OutlineItem, ParseStatus, Point};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use lsp::SymbolKind;
|
||||
use project::{DocumentSymbol, Project, Symbol};
|
||||
use project::{Project, Symbol};
|
||||
use regex::{Regex, RegexBuilder};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
|
||||
use crate::code_symbol_iter::{CodeSymbolIterator, Entry};
|
||||
use crate::schema::json_schema_for;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CodeSymbolsInput {
|
||||
/// The relative path of the source code file to read and get the symbols for.
|
||||
@@ -132,7 +130,7 @@ impl Tool for CodeSymbolsTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<CodeSymbolsInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -150,8 +148,8 @@ impl Tool for CodeSymbolsTool {
|
||||
};
|
||||
|
||||
cx.spawn(async move |cx| match input.path {
|
||||
Some(path) => file_outline(project, path, action_log, regex, input.offset, cx).await,
|
||||
None => project_symbols(project, regex, input.offset, cx).await,
|
||||
Some(path) => file_outline(project, path, action_log, regex, input.offset, cx).await.map(StringToolOutput::new),
|
||||
None => project_symbols(project, regex, input.offset, cx).await.map(StringToolOutput::new),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -180,24 +178,28 @@ pub async fn file_outline(
|
||||
action_log.buffer_read(buffer.clone(), cx);
|
||||
})?;
|
||||
|
||||
let symbols = project
|
||||
.update(cx, |project, cx| project.document_symbols(&buffer, cx))?
|
||||
.await?;
|
||||
// Wait until the buffer has been fully parsed, so that we can read its outline.
|
||||
let mut parse_status = buffer.read_with(cx, |buffer, _| buffer.parse_status())?;
|
||||
while parse_status
|
||||
.recv()
|
||||
.await
|
||||
.map_or(false, |status| status != ParseStatus::Idle)
|
||||
{}
|
||||
|
||||
if symbols.is_empty() {
|
||||
return Err(
|
||||
if buffer.read_with(cx, |buffer, _| buffer.snapshot().is_empty())? {
|
||||
anyhow!("This file is empty.")
|
||||
} else {
|
||||
anyhow!("No outline information available for this file.")
|
||||
},
|
||||
);
|
||||
}
|
||||
let snapshot = buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
|
||||
let Some(outline) = snapshot.outline(None) else {
|
||||
return Err(anyhow!("No outline information available for this file."));
|
||||
};
|
||||
|
||||
let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned())?;
|
||||
let language_registry = project.read_with(cx, |project, _| project.languages().clone())?;
|
||||
|
||||
render_outline(&symbols, language, language_registry, regex, offset).await
|
||||
render_outline(
|
||||
outline
|
||||
.items
|
||||
.into_iter()
|
||||
.map(|item| item.to_point(&snapshot)),
|
||||
regex,
|
||||
offset,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn project_symbols(
|
||||
@@ -292,61 +294,27 @@ async fn project_symbols(
|
||||
}
|
||||
|
||||
async fn render_outline(
|
||||
symbols: &[DocumentSymbol],
|
||||
language: Option<Arc<Language>>,
|
||||
registry: Arc<LanguageRegistry>,
|
||||
items: impl IntoIterator<Item = OutlineItem<Point>>,
|
||||
regex: Option<Regex>,
|
||||
offset: u32,
|
||||
) -> Result<String> {
|
||||
const RESULTS_PER_PAGE_USIZE: usize = RESULTS_PER_PAGE as usize;
|
||||
let entries = CodeSymbolIterator::new(symbols, regex.clone())
|
||||
.skip(offset as usize)
|
||||
// Take 1 more than RESULTS_PER_PAGE so we can tell if there are more results.
|
||||
.take(RESULTS_PER_PAGE_USIZE.saturating_add(1))
|
||||
.collect::<Vec<Entry>>();
|
||||
let has_more = entries.len() > RESULTS_PER_PAGE_USIZE;
|
||||
|
||||
// Get language-specific labels, if available
|
||||
let labels = match &language {
|
||||
Some(lang) => {
|
||||
let entries_for_labels: Vec<(String, SymbolKind)> = entries
|
||||
.iter()
|
||||
.take(RESULTS_PER_PAGE_USIZE)
|
||||
.map(|entry| (entry.name.clone(), entry.kind))
|
||||
.collect();
|
||||
let mut items = items.into_iter().skip(offset as usize);
|
||||
|
||||
let lang_name = lang.name();
|
||||
if let Some(lsp_adapter) = registry.lsp_adapters(&lang_name).first().cloned() {
|
||||
lsp_adapter
|
||||
.labels_for_symbols(&entries_for_labels, lang)
|
||||
.await
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
let entries = items
|
||||
.by_ref()
|
||||
.filter(|item| {
|
||||
regex
|
||||
.as_ref()
|
||||
.is_none_or(|regex| regex.is_match(&item.text))
|
||||
})
|
||||
.take(RESULTS_PER_PAGE_USIZE)
|
||||
.collect::<Vec<_>>();
|
||||
let has_more = items.next().is_some();
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
let entries_rendered = match &labels {
|
||||
Some(label_list) => render_entries(
|
||||
&mut output,
|
||||
entries
|
||||
.into_iter()
|
||||
.take(RESULTS_PER_PAGE_USIZE)
|
||||
.zip(label_list.iter())
|
||||
.map(|(entry, label)| (entry, label.as_ref())),
|
||||
),
|
||||
None => render_entries(
|
||||
&mut output,
|
||||
entries
|
||||
.into_iter()
|
||||
.take(RESULTS_PER_PAGE_USIZE)
|
||||
.map(|entry| (entry, None)),
|
||||
),
|
||||
};
|
||||
let entries_rendered = render_entries(&mut output, entries);
|
||||
|
||||
// Calculate pagination information
|
||||
let page_start = offset + 1;
|
||||
@@ -372,31 +340,19 @@ async fn render_outline(
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn render_entries<'a>(
|
||||
output: &mut String,
|
||||
entries: impl IntoIterator<Item = (Entry, Option<&'a CodeLabel>)>,
|
||||
) -> u32 {
|
||||
fn render_entries(output: &mut String, items: impl IntoIterator<Item = OutlineItem<Point>>) -> u32 {
|
||||
let mut entries_rendered = 0;
|
||||
|
||||
for (entry, label) in entries {
|
||||
for item in items {
|
||||
// Indent based on depth ("" for level 0, " " for level 1, etc.)
|
||||
for _ in 0..entry.depth {
|
||||
output.push_str(" ");
|
||||
}
|
||||
|
||||
match label {
|
||||
Some(label) => {
|
||||
output.push_str(label.text());
|
||||
}
|
||||
None => {
|
||||
write_symbol_kind(output, entry.kind).ok();
|
||||
output.push_str(&entry.name);
|
||||
}
|
||||
for _ in 0..item.depth {
|
||||
output.push(' ');
|
||||
}
|
||||
output.push_str(&item.text);
|
||||
|
||||
// Add position information - convert to 1-based line numbers for display
|
||||
let start_line = entry.start_line + 1;
|
||||
let end_line = entry.end_line + 1;
|
||||
let start_line = item.range.start.row + 1;
|
||||
let end_line = item.range.end.row + 1;
|
||||
|
||||
if start_line == end_line {
|
||||
writeln!(output, " [L{}]", start_line).ok();
|
||||
@@ -408,38 +364,3 @@ fn render_entries<'a>(
|
||||
|
||||
entries_rendered
|
||||
}
|
||||
|
||||
// We may not have a language server adapter to have language-specific
|
||||
// ways to translate SymbolKnd into a string. In that situation,
|
||||
// fall back on some reasonable default strings to render.
|
||||
fn write_symbol_kind(buf: &mut String, kind: SymbolKind) -> Result<(), fmt::Error> {
|
||||
match kind {
|
||||
SymbolKind::FILE => write!(buf, "file "),
|
||||
SymbolKind::MODULE => write!(buf, "module "),
|
||||
SymbolKind::NAMESPACE => write!(buf, "namespace "),
|
||||
SymbolKind::PACKAGE => write!(buf, "package "),
|
||||
SymbolKind::CLASS => write!(buf, "class "),
|
||||
SymbolKind::METHOD => write!(buf, "method "),
|
||||
SymbolKind::PROPERTY => write!(buf, "property "),
|
||||
SymbolKind::FIELD => write!(buf, "field "),
|
||||
SymbolKind::CONSTRUCTOR => write!(buf, "constructor "),
|
||||
SymbolKind::ENUM => write!(buf, "enum "),
|
||||
SymbolKind::INTERFACE => write!(buf, "interface "),
|
||||
SymbolKind::FUNCTION => write!(buf, "function "),
|
||||
SymbolKind::VARIABLE => write!(buf, "variable "),
|
||||
SymbolKind::CONSTANT => write!(buf, "constant "),
|
||||
SymbolKind::STRING => write!(buf, "string "),
|
||||
SymbolKind::NUMBER => write!(buf, "number "),
|
||||
SymbolKind::BOOLEAN => write!(buf, "boolean "),
|
||||
SymbolKind::ARRAY => write!(buf, "array "),
|
||||
SymbolKind::OBJECT => write!(buf, "object "),
|
||||
SymbolKind::KEY => write!(buf, "key "),
|
||||
SymbolKind::NULL => write!(buf, "null "),
|
||||
SymbolKind::ENUM_MEMBER => write!(buf, "enum member "),
|
||||
SymbolKind::STRUCT => write!(buf, "struct "),
|
||||
SymbolKind::EVENT => write!(buf, "event "),
|
||||
SymbolKind::OPERATOR => write!(buf, "operator "),
|
||||
SymbolKind::TYPE_PARAMETER => write!(buf, "type parameter "),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
@@ -77,7 +78,7 @@ impl Tool for CopyPathTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<CopyPathToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -105,10 +106,10 @@ impl Tool for CopyPathTool {
|
||||
|
||||
cx.background_spawn(async move {
|
||||
match copy_task.await {
|
||||
Ok(_) => Ok(format!(
|
||||
Ok(_) => Ok(StringToolOutput::new(format!(
|
||||
"Copied {} to {}",
|
||||
input.source_path, input.destination_path
|
||||
)),
|
||||
))),
|
||||
Err(err) => Err(anyhow!(
|
||||
"Failed to copy {} to {}: {}",
|
||||
input.source_path,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
@@ -68,7 +69,7 @@ impl Tool for CreateDirectoryTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<CreateDirectoryToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -87,7 +88,7 @@ impl Tool for CreateDirectoryTool {
|
||||
.await
|
||||
.map_err(|err| anyhow!("Unable to create directory {destination_path}: {err}"))?;
|
||||
|
||||
Ok(format!("Created directory {destination_path}"))
|
||||
Ok(StringToolOutput::new(format!("Created directory {destination_path}")))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::LanguageModelRequestMessage;
|
||||
use language_model::LanguageModelToolSchemaFormat;
|
||||
@@ -73,7 +74,7 @@ impl Tool for CreateFileTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<CreateFileToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -104,7 +105,7 @@ impl Tool for CreateFileTool {
|
||||
.await
|
||||
.map_err(|err| anyhow!("Unable to save buffer for {destination_path}: {err}"))?;
|
||||
|
||||
Ok(format!("Created file {destination_path}"))
|
||||
Ok(StringToolOutput::new(format!("Created file {destination_path}")))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use futures::{SinkExt, StreamExt, channel::mpsc};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -63,7 +64,7 @@ impl Tool for DeletePathTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let path_str = match serde_json::from_value::<DeletePathToolInput>(input) {
|
||||
Ok(input) => input.path,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -124,7 +125,7 @@ impl Tool for DeletePathTool {
|
||||
|
||||
match delete {
|
||||
Some(deletion_task) => match deletion_task.await {
|
||||
Ok(()) => Ok(format!("Deleted {path_str}")),
|
||||
Ok(()) => Ok(StringToolOutput::new(format!("Deleted {path_str}"))),
|
||||
Err(err) => Err(anyhow!("Failed to delete {path_str}: {err}")),
|
||||
},
|
||||
None => Err(anyhow!(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language::{DiagnosticSeverity, OffsetRangeExt};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -83,7 +84,7 @@ impl Tool for DiagnosticsTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
match serde_json::from_value::<DiagnosticsToolInput>(input)
|
||||
.ok()
|
||||
.and_then(|input| input.path)
|
||||
@@ -120,9 +121,9 @@ impl Tool for DiagnosticsTool {
|
||||
}
|
||||
|
||||
if output.is_empty() {
|
||||
Ok("File doesn't have errors or warnings!".to_string())
|
||||
Ok(StringToolOutput::new("File doesn't have errors or warnings!"))
|
||||
} else {
|
||||
Ok(output)
|
||||
Ok(StringToolOutput::new(output))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -155,9 +156,9 @@ impl Tool for DiagnosticsTool {
|
||||
});
|
||||
|
||||
if has_diagnostics {
|
||||
Task::ready(Ok(output))
|
||||
Task::ready(Ok(StringToolOutput::new(output)))
|
||||
} else {
|
||||
Task::ready(Ok("No errors or warnings found in the project.".to_string()))
|
||||
Task::ready(Ok(StringToolOutput::new("No errors or warnings found in the project.")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::sync::Arc;
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Context as _, Result, anyhow, bail};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use futures::AsyncReadExt as _;
|
||||
use gpui::{App, AppContext as _, Entity, Task};
|
||||
use html_to_markdown::{TagHandler, convert_html_to_markdown, markdown};
|
||||
@@ -146,7 +147,7 @@ impl Tool for FetchTool {
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<FetchToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -164,7 +165,7 @@ impl Tool for FetchTool {
|
||||
bail!("no textual content found");
|
||||
}
|
||||
|
||||
Ok(text)
|
||||
Ok(StringToolOutput::new(text))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::{replace::replace_with_flexible_indent, schema::json_schema_for};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AppContext, AsyncApp, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -159,7 +160,7 @@ impl Tool for FindReplaceFileTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<FindReplaceFileToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -251,7 +252,7 @@ impl Tool for FindReplaceFileTool {
|
||||
}).await;
|
||||
|
||||
|
||||
Ok(format!("Edited {}:\n\n```diff\n{}\n```", input.path.display(), diff_str))
|
||||
Ok(StringToolOutput::new(format!("Edited {}:\n\n```diff\n{}\n```", input.path.display(), diff_str)))
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -77,7 +78,7 @@ impl Tool for ListDirectoryTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<ListDirectoryToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -101,7 +102,7 @@ impl Tool for ListDirectoryTool {
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
return Task::ready(Ok(output));
|
||||
return Task::ready(Ok(StringToolOutput::new(output)));
|
||||
}
|
||||
|
||||
let Some(project_path) = project.read(cx).find_project_path(&input.path, cx) else {
|
||||
@@ -133,8 +134,8 @@ impl Tool for ListDirectoryTool {
|
||||
.unwrap();
|
||||
}
|
||||
if output.is_empty() {
|
||||
return Task::ready(Ok(format!("{} is empty.", input.path)));
|
||||
return Task::ready(Ok(StringToolOutput::new(format!("{} is empty.", input.path))));
|
||||
}
|
||||
Task::ready(Ok(output))
|
||||
Task::ready(Ok(StringToolOutput::new(output)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -90,7 +91,7 @@ impl Tool for MovePathTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<MovePathToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -116,10 +117,10 @@ impl Tool for MovePathTool {
|
||||
|
||||
cx.background_spawn(async move {
|
||||
match rename_task.await {
|
||||
Ok(_) => Ok(format!(
|
||||
Ok(_) => Ok(StringToolOutput::new(format!(
|
||||
"Moved {} to {}",
|
||||
input.source_path, input.destination_path
|
||||
)),
|
||||
))),
|
||||
Err(err) => Err(anyhow!(
|
||||
"Failed to move {} to {}: {}",
|
||||
input.source_path,
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use chrono::{Local, Utc};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -60,7 +61,7 @@ impl Tool for NowTool {
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input: NowToolInput = match serde_json::from_value(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -72,6 +73,6 @@ impl Tool for NowTool {
|
||||
};
|
||||
let text = format!("The current datetime is {now}.");
|
||||
|
||||
Task::ready(Ok(text))
|
||||
Task::ready(Ok(StringToolOutput::new(text)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -53,7 +54,7 @@ impl Tool for OpenTool {
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input: OpenToolInput = match serde_json::from_value(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -62,7 +63,7 @@ impl Tool for OpenTool {
|
||||
cx.background_spawn(async move {
|
||||
open::that(&input.path_or_url).context("Failed to open URL or file path")?;
|
||||
|
||||
Ok(format!("Successfully opened {}", input.path_or_url))
|
||||
Ok(StringToolOutput::new(format!("Successfully opened {}", input.path_or_url)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AppContext, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -71,7 +72,7 @@ impl Tool for PathSearchTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let (offset, glob) = match serde_json::from_value::<PathSearchToolInput>(input) {
|
||||
Ok(input) => (input.offset, input.glob),
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -110,7 +111,7 @@ impl Tool for PathSearchTool {
|
||||
}
|
||||
|
||||
if matches.is_empty() {
|
||||
Ok(format!("No paths in the project matched the glob {glob:?}"))
|
||||
Ok(StringToolOutput::new(format!("No paths in the project matched the glob {glob:?}")))
|
||||
} else {
|
||||
// Sort to group entries in the same directory together.
|
||||
matches.sort();
|
||||
@@ -134,7 +135,7 @@ impl Tool for PathSearchTool {
|
||||
matches.join("\n")
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
Ok(StringToolOutput::new(response))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::code_symbols_tool::file_outline;
|
||||
use crate::schema::json_schema_for;
|
||||
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use itertools::Itertools;
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -16,7 +16,7 @@ use util::markdown::MarkdownString;
|
||||
/// If the model requests to read a file whose size exceeds this, then
|
||||
/// the tool will return an error along with the model's symbol outline,
|
||||
/// and suggest trying again using line ranges from the outline.
|
||||
const MAX_FILE_SIZE_TO_READ: usize = 4096;
|
||||
const MAX_FILE_SIZE_TO_READ: usize = 16384;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ReadFileToolInput {
|
||||
@@ -89,7 +89,7 @@ impl Tool for ReadFileTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<ReadFileToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -115,9 +115,9 @@ impl Tool for ReadFileTool {
|
||||
let lines = text.split('\n').skip(start - 1);
|
||||
if let Some(end) = input.end_line {
|
||||
let count = end.saturating_sub(start).max(1); // Ensure at least 1 line
|
||||
Itertools::intersperse(lines.take(count), "\n").collect()
|
||||
Itertools::intersperse(lines.take(count), "\n").collect::<String>()
|
||||
} else {
|
||||
Itertools::intersperse(lines, "\n").collect()
|
||||
Itertools::intersperse(lines, "\n").collect::<String>()
|
||||
}
|
||||
})?;
|
||||
|
||||
@@ -125,7 +125,7 @@ impl Tool for ReadFileTool {
|
||||
log.buffer_read(buffer, cx);
|
||||
})?;
|
||||
|
||||
Ok(result)
|
||||
Ok(StringToolOutput::new(result))
|
||||
} else {
|
||||
// No line ranges specified, so check file size to see if it's too big.
|
||||
let file_size = buffer.read_with(cx, |buffer, _cx| buffer.text().len())?;
|
||||
@@ -138,13 +138,13 @@ impl Tool for ReadFileTool {
|
||||
log.buffer_read(buffer, cx);
|
||||
})?;
|
||||
|
||||
Ok(result)
|
||||
Ok(StringToolOutput::new(result))
|
||||
} else {
|
||||
// File is too big, so return an error with the outline
|
||||
// and a suggestion to read again with line numbers.
|
||||
let outline = file_outline(project, file_path, action_log, None, 0, cx).await?;
|
||||
|
||||
Ok(format!("This file was too big to read all at once. Here is an outline of its symbols:\n\n{outline}\n\nUsing the line numbers in this outline, you can call this tool again while specifying the start_line and end_line fields to see the implementations of symbols in the outline."))
|
||||
Ok(StringToolOutput::new(format!("This file was too big to read all at once. Here is an outline of its symbols:\n\n{outline}\n\nUsing the line numbers in this outline, you can call this tool again while specifying the start_line and end_line fields to see the implementations of symbols in the outline.")))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use futures::StreamExt;
|
||||
use gpui::{App, Entity, Task};
|
||||
use language::OffsetRangeExt;
|
||||
@@ -83,7 +84,7 @@ impl Tool for RegexSearchTool {
|
||||
project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
const CONTEXT_LINES: u32 = 2;
|
||||
|
||||
let (offset, regex) = match serde_json::from_value::<RegexSearchToolInput>(input) {
|
||||
@@ -179,16 +180,16 @@ impl Tool for RegexSearchTool {
|
||||
}
|
||||
|
||||
if matches_found == 0 {
|
||||
Ok("No matches found".to_string())
|
||||
Ok(StringToolOutput::new("No matches found".to_string()))
|
||||
} else if has_more_matches {
|
||||
Ok(format!(
|
||||
Ok(StringToolOutput::new(format!(
|
||||
"Showing matches {}-{} (there were more matches found; use offset: {} to see next page):\n{output}",
|
||||
offset + 1,
|
||||
offset + matches_found,
|
||||
offset + RESULTS_PER_PAGE,
|
||||
))
|
||||
)))
|
||||
} else {
|
||||
Ok(format!("Found {matches_found} matches:\n{output}"))
|
||||
Ok(StringToolOutput::new(format!("Found {matches_found} matches:\n{output}")))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, AsyncApp, Entity, Task};
|
||||
use language::{self, Anchor, Buffer, BufferSnapshot, Location, Point, ToPoint, ToPointUtf16};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -122,7 +123,7 @@ impl Tool for SymbolInfoTool {
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
let input = match serde_json::from_value::<SymbolInfoToolInput>(input) {
|
||||
Ok(input) => input,
|
||||
Err(err) => return Task::ready(Err(anyhow!(err))),
|
||||
@@ -203,7 +204,7 @@ impl Tool for SymbolInfoTool {
|
||||
if output.is_empty() {
|
||||
Err(anyhow!("None found."))
|
||||
} else {
|
||||
Ok(output)
|
||||
Ok(StringToolOutput::new(output))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use crate::schema::json_schema_for;
|
||||
use anyhow::{Result, anyhow};
|
||||
use assistant_tool::{ActionLog, Tool};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
use project::Project;
|
||||
@@ -51,10 +52,10 @@ impl Tool for ThinkingTool {
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
_cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
// This tool just "thinks out loud" and doesn't perform any actions.
|
||||
Task::ready(match serde_json::from_value::<ThinkingToolInput>(input) {
|
||||
Ok(_input) => Ok("Finished thinking.".to_string()),
|
||||
Ok(_input) => Ok(StringToolOutput::new("Finished thinking.")),
|
||||
Err(err) => Err(anyhow!(err)),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use assistant_tool::{ActionLog, Tool, ToolSource};
|
||||
use language_model::{ToolOutput, StringToolOutput};
|
||||
use gpui::{App, Entity, Task};
|
||||
use icons::IconName;
|
||||
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
|
||||
@@ -76,7 +77,7 @@ impl Tool for ContextServerTool {
|
||||
_project: Entity<Project>,
|
||||
_action_log: Entity<ActionLog>,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<String>> {
|
||||
) -> Task<Result<ToolOutput>> {
|
||||
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {
|
||||
let tool_name = self.tool.name.clone();
|
||||
let server_clone = server.clone();
|
||||
@@ -114,7 +115,7 @@ impl Tool for ContextServerTool {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
Ok(StringToolOutput::new(result))
|
||||
})
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("Context server not found")))
|
||||
|
||||
@@ -1280,10 +1280,6 @@ mod tests {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn to_proto(&self, _: &App) -> rpc::proto::File {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ pub(super) struct NewSessionModal {
|
||||
debug_panel: WeakEntity<DebugPanel>,
|
||||
mode: NewSessionMode,
|
||||
stop_on_entry: ToggleState,
|
||||
initialize_args: Option<serde_json::Value>,
|
||||
debugger: Option<SharedString>,
|
||||
last_selected_profile_name: Option<SharedString>,
|
||||
}
|
||||
@@ -82,17 +83,17 @@ impl NewSessionModal {
|
||||
.map(Into::into)
|
||||
.unwrap_or(ToggleState::Unselected),
|
||||
last_selected_profile_name: None,
|
||||
initialize_args: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_config(&self, cx: &App) -> Option<DebugTaskDefinition> {
|
||||
let request = self.mode.debug_task(cx);
|
||||
|
||||
Some(DebugTaskDefinition {
|
||||
adapter: self.debugger.clone()?.to_string(),
|
||||
label: suggested_label(&request, self.debugger.as_deref()?),
|
||||
request,
|
||||
initialize_args: None,
|
||||
initialize_args: self.initialize_args.clone(),
|
||||
tcp_connection: None,
|
||||
locator: None,
|
||||
stop_on_entry: match self.stop_on_entry {
|
||||
@@ -228,7 +229,7 @@ impl NewSessionModal {
|
||||
weak.update(cx, |this, cx| {
|
||||
this.last_selected_profile_name = Some(SharedString::from(&task.label));
|
||||
this.debugger = Some(task.adapter.clone().into());
|
||||
|
||||
this.initialize_args = task.initialize_args.clone();
|
||||
match &task.request {
|
||||
DebugRequestType::Launch(launch_config) => {
|
||||
this.mode = NewSessionMode::launch(
|
||||
|
||||
@@ -244,10 +244,6 @@ impl language::File for GitBlob {
|
||||
self.worktree_id
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn to_proto(&self, _cx: &App) -> language::proto::File {
|
||||
unimplemented!()
|
||||
}
|
||||
@@ -282,10 +278,6 @@ impl language::File for CommitMetadataFile {
|
||||
self.worktree_id
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn to_proto(&self, _: &App) -> language::proto::File {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -42,13 +42,10 @@ use std::{
|
||||
/// }
|
||||
/// register_action!(Paste);
|
||||
/// ```
|
||||
pub trait Action: 'static + Send {
|
||||
pub trait Action: Any + Send {
|
||||
/// Clone the action into a new box
|
||||
fn boxed_clone(&self) -> Box<dyn Action>;
|
||||
|
||||
/// Cast the action to the any type
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Do a partial equality check on this action and the other
|
||||
fn partial_eq(&self, action: &dyn Action) -> bool;
|
||||
|
||||
@@ -94,9 +91,9 @@ impl std::fmt::Debug for dyn Action {
|
||||
}
|
||||
|
||||
impl dyn Action {
|
||||
/// Get the type id of this action
|
||||
pub fn type_id(&self) -> TypeId {
|
||||
self.as_any().type_id()
|
||||
/// Type-erase Action type.
|
||||
pub fn as_any(&self) -> &dyn Any {
|
||||
self as &dyn Any
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,9 +554,6 @@ macro_rules! __impl_action {
|
||||
::std::boxed::Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
$($items)*
|
||||
}
|
||||
|
||||
@@ -597,10 +597,6 @@ mod tests {
|
||||
Box::new(TestAction)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn build(_value: serde_json::Value) -> anyhow::Result<Box<dyn Action>>
|
||||
where
|
||||
Self: Sized,
|
||||
|
||||
@@ -513,9 +513,8 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4<f32> {
|
||||
let point = input.position.xy - quad.bounds.origin;
|
||||
let center_to_point = point - half_size;
|
||||
|
||||
// Signed distance field threshold for inclusion of pixels. Use of 0.5
|
||||
// instead of 1.0 causes the width of rounded borders to appear more
|
||||
// consistent with straight borders.
|
||||
// Signed distance field threshold for inclusion of pixels. 0.5 is the
|
||||
// minimum distance between the center of the pixel and the edge.
|
||||
let antialias_threshold = 0.5;
|
||||
|
||||
// Radius of the nearest corner
|
||||
@@ -612,24 +611,29 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4<f32> {
|
||||
|
||||
// Dashed border logic when border_style == 1
|
||||
if (quad.border_style == 1) {
|
||||
// Position in "dash space", where each dash period has length 1
|
||||
// Position along the perimeter in "dash space", where each dash
|
||||
// period has length 1
|
||||
var t = 0.0;
|
||||
|
||||
// Total number of dash periods, so that the dash spacing can be
|
||||
// adjusted to evenly divide it
|
||||
var max_t = 0.0;
|
||||
|
||||
// Since border width affects the dash size, the density of dashes
|
||||
// varies, and this is indicated by dash_velocity. It has units
|
||||
// (dash period / pixel). So a dash velocity of (1 / 10) is 1 dash
|
||||
// every 10 pixels.
|
||||
var dash_velocity = 0.0;
|
||||
|
||||
// Border width is proportional to dash size. This is the behavior
|
||||
// used by browsers, but also avoids dashes from different segments
|
||||
// overlapping when dash size is smaller than the border width.
|
||||
//
|
||||
// Dash pattern: (2 * border width) dash, (1 * border width) gap
|
||||
let dash_length_per_width = 2.0;
|
||||
let dash_gap_per_width = 1.0;
|
||||
let dash_period_per_width = dash_length_per_width + dash_gap_per_width;
|
||||
|
||||
// Since the dash size is determined by border width, the density of
|
||||
// dashes varies. Multiplying a pixel distance by this returns a
|
||||
// position in dash space - it has units (dash period / pixels). So
|
||||
// a dash velocity of (1 / 10) is 1 dash every 10 pixels.
|
||||
var dash_velocity = 0.0;
|
||||
|
||||
// Dividing this by the border width gives the dash velocity
|
||||
let dv_numerator = 1.0 / dash_period_per_width;
|
||||
|
||||
@@ -645,8 +649,8 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4<f32> {
|
||||
t = select(point.y, point.x, is_horizontal) * dash_velocity;
|
||||
max_t = select(size.y, size.x, is_horizontal) * dash_velocity;
|
||||
} else {
|
||||
// When corners are rounded, the dashes are laid out around the
|
||||
// whole perimeter.
|
||||
// When corners are rounded, the dashes are laid out clockwise
|
||||
// around the whole perimeter.
|
||||
|
||||
let r_tr = quad.corner_radii.top_right;
|
||||
let r_br = quad.corner_radii.bottom_right;
|
||||
@@ -694,23 +698,34 @@ fn fs_quad(input: QuadVarying) -> @location(0) vec4<f32> {
|
||||
if (is_near_rounded_corner) {
|
||||
let radians = atan2(corner_center_to_point.y,
|
||||
corner_center_to_point.x);
|
||||
let corner_t = radians * corner_radius;
|
||||
let corner_t = radians * corner_radius * dash_velocity;
|
||||
|
||||
if (center_to_point.x >= 0.0) {
|
||||
if (center_to_point.y < 0.0) {
|
||||
dash_velocity = corner_dash_velocity_tr;
|
||||
t = upto_r - corner_t * dash_velocity;
|
||||
// Subtracted because radians is pi/2 to 0 when
|
||||
// going clockwise around the top right corner,
|
||||
// since the y axis has been flipped
|
||||
t = upto_r - corner_t;
|
||||
} else {
|
||||
dash_velocity = corner_dash_velocity_br;
|
||||
t = upto_br + corner_t * dash_velocity;
|
||||
// Added because radians is 0 to pi/2 when going
|
||||
// clockwise around the bottom-right corner
|
||||
t = upto_br + corner_t;
|
||||
}
|
||||
} else {
|
||||
if (center_to_point.y >= 0.0) {
|
||||
dash_velocity = corner_dash_velocity_bl;
|
||||
t = upto_l - corner_t * dash_velocity;
|
||||
// Subtracted because radians is pi/2 to 0 when
|
||||
// going clockwise around the bottom-left corner,
|
||||
// since the x axis has been flipped
|
||||
t = upto_l - corner_t;
|
||||
} else {
|
||||
dash_velocity = corner_dash_velocity_tl;
|
||||
t = upto_tl + corner_t * dash_velocity;
|
||||
// Added because radians is 0 to pi/2 when going
|
||||
// clockwise around the top-left corner, since both
|
||||
// axis were flipped
|
||||
t = upto_tl + corner_t;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -121,7 +121,8 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
|
||||
float2 point = input.position.xy - float2(quad.bounds.origin.x, quad.bounds.origin.y);
|
||||
float2 center_to_point = point - half_size;
|
||||
|
||||
// Signed distance field threshold for inclusion of pixels
|
||||
// Signed distance field threshold for inclusion of pixels. 0.5 is the
|
||||
// minimum distance between the center of the pixel and the edge.
|
||||
const float antialias_threshold = 0.5;
|
||||
|
||||
// Radius of the nearest corner
|
||||
@@ -211,24 +212,29 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
|
||||
|
||||
// Dashed border logic when border_style == 1
|
||||
if (quad.border_style == 1) {
|
||||
// Position in "dash space", where each dash period has length 1
|
||||
// Position along the perimeter in "dash space", where each dash
|
||||
// period has length 1
|
||||
float t = 0.0;
|
||||
|
||||
// Total number of dash periods, so that the dash spacing can be
|
||||
// adjusted to evenly divide it
|
||||
float max_t = 0.0;
|
||||
|
||||
// Since border width affects the dash size, the density of dashes
|
||||
// varies, and this is indicated by dash_velocity. It has units
|
||||
// (dash period / pixel). So a dash velocity of (1 / 10) is 1 dash
|
||||
// every 10 pixels.
|
||||
float dash_velocity = 0.0;
|
||||
|
||||
// Border width is proportional to dash size. This is the behavior
|
||||
// used by browsers, but also avoids dashes from different segments
|
||||
// overlapping when dash size is smaller than the border width.
|
||||
//
|
||||
// Dash pattern: (2 * border width) dash, (1 * border width) gap
|
||||
const float dash_length_per_width = 2.0;
|
||||
const float dash_gap_per_width = 1.0;
|
||||
const float dash_period_per_width = dash_length_per_width + dash_gap_per_width;
|
||||
|
||||
// Since the dash size is determined by border width, the density of
|
||||
// dashes varies. Multiplying a pixel distance by this returns a
|
||||
// position in dash space - it has units (dash period / pixels). So
|
||||
// a dash velocity of (1 / 10) is 1 dash every 10 pixels.
|
||||
float dash_velocity = 0.0;
|
||||
|
||||
// Dividing this by the border width gives the dash velocity
|
||||
const float dv_numerator = 1.0 / dash_period_per_width;
|
||||
|
||||
@@ -244,8 +250,8 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
|
||||
max_t = is_horizontal ? size.x : size.y;
|
||||
max_t *= dash_velocity;
|
||||
} else {
|
||||
// When corners are rounded, the dashes are laid out around the
|
||||
// whole perimeter.
|
||||
// When corners are rounded, the dashes are laid out clockwise
|
||||
// around the whole perimeter.
|
||||
|
||||
float r_tr = quad.corner_radii.top_right;
|
||||
float r_br = quad.corner_radii.bottom_right;
|
||||
@@ -292,23 +298,34 @@ fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
|
||||
|
||||
if (is_near_rounded_corner) {
|
||||
float radians = atan2(corner_center_to_point.y, corner_center_to_point.x);
|
||||
float corner_t = radians * corner_radius;
|
||||
float corner_t = radians * corner_radius * dash_velocity;
|
||||
|
||||
if (center_to_point.x >= 0.0) {
|
||||
if (center_to_point.y < 0.0) {
|
||||
dash_velocity = corner_dash_velocity_tr;
|
||||
t = upto_r - corner_t * dash_velocity;
|
||||
// Subtracted because radians is pi/2 to 0 when
|
||||
// going clockwise around the top right corner,
|
||||
// since the y axis has been flipped
|
||||
t = upto_r - corner_t;
|
||||
} else {
|
||||
dash_velocity = corner_dash_velocity_br;
|
||||
t = upto_br + corner_t * dash_velocity;
|
||||
// Added because radians is 0 to pi/2 when going
|
||||
// clockwise around the bottom-right corner
|
||||
t = upto_br + corner_t;
|
||||
}
|
||||
} else {
|
||||
if (center_to_point.y >= 0.0) {
|
||||
dash_velocity = corner_dash_velocity_bl;
|
||||
t = upto_l - corner_t * dash_velocity;
|
||||
// Subtracted because radians is pi/1 to 0 when
|
||||
// going clockwise around the bottom-left corner,
|
||||
// since the x axis has been flipped
|
||||
t = upto_l - corner_t;
|
||||
} else {
|
||||
dash_velocity = corner_dash_velocity_tl;
|
||||
t = upto_tl + corner_t * dash_velocity;
|
||||
// Added because radians is 0 to pi/2 when going
|
||||
// clockwise around the top-left corner, since both
|
||||
// axis were flipped
|
||||
t = upto_tl + corner_t;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -22,10 +22,6 @@ fn test_action_macros() {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn partial_eq(&self, _action: &dyn gpui::Action) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ pub enum BufferEvent {
|
||||
}
|
||||
|
||||
/// The file associated with a buffer.
|
||||
pub trait File: Send + Sync {
|
||||
pub trait File: Send + Sync + Any {
|
||||
/// Returns the [`LocalFile`] associated with this file, if the
|
||||
/// file is local.
|
||||
fn as_local(&self) -> Option<&dyn LocalFile>;
|
||||
@@ -336,9 +336,6 @@ pub trait File: Send + Sync {
|
||||
/// This is needed for looking up project-specific settings.
|
||||
fn worktree_id(&self, cx: &App) -> WorktreeId;
|
||||
|
||||
/// Converts this file into an [`Any`] trait object.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Converts this file into a protobuf message.
|
||||
fn to_proto(&self, cx: &App) -> rpc::proto::File;
|
||||
|
||||
@@ -4610,10 +4607,6 @@ impl File for TestFile {
|
||||
WorktreeId::from_usize(0)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn to_proto(&self, _: &App) -> rpc::proto::File {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ mod registry;
|
||||
mod request;
|
||||
mod role;
|
||||
mod telemetry;
|
||||
mod tool_output;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub mod fake_provider;
|
||||
@@ -26,6 +27,7 @@ use util::serde::is_default;
|
||||
|
||||
pub use crate::model::*;
|
||||
pub use crate::rate_limiter::*;
|
||||
pub use crate::tool_output::{ToolOutput, StringToolOutput};
|
||||
pub use crate::registry::*;
|
||||
pub use crate::request::*;
|
||||
pub use crate::role::*;
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::io::{Cursor, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::role::Role;
|
||||
use crate::tool_output::ToolOutput;
|
||||
use crate::{LanguageModelToolUse, LanguageModelToolUseId};
|
||||
use base64::write::EncoderWriter;
|
||||
use gpui::{
|
||||
@@ -164,12 +165,13 @@ impl LanguageModelImage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub struct LanguageModelToolResult {
|
||||
pub tool_use_id: LanguageModelToolUseId,
|
||||
pub tool_name: Arc<str>,
|
||||
pub is_error: bool,
|
||||
pub content: Arc<str>,
|
||||
pub tool_output: Option<Arc<ToolOutput>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
|
||||
|
||||
33
crates/language_model/src/tool_output.rs
Normal file
33
crates/language_model/src/tool_output.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use gpui::{AnyElement, App, SharedString, Window};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An enum that represents different types of tool outputs that can be provided to the language model
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ToolOutput {
|
||||
/// A simple string output
|
||||
String {
|
||||
string: SharedString,
|
||||
rendered: Entity<Markdown>,
|
||||
},
|
||||
// Add other tool output types here as variants
|
||||
}
|
||||
|
||||
impl ToolOutput {
|
||||
/// Returns a string that will be given to the model
|
||||
/// as the tool output.
|
||||
pub fn response_for_model(&self) -> SharedString {
|
||||
match self {
|
||||
ToolOutput::String(output) => output.0.clone(),
|
||||
// Handle other variants here
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a custom UI element to render the tool's output.
|
||||
/// Returns None by default to indicate that rendering has not yet been
|
||||
/// implemented for this tool, and the caller should do some default rendering.
|
||||
pub fn render(&self, _window: &mut Window, _cx: &App) -> Option<AnyElement> {
|
||||
match self {
|
||||
ToolOutput::String { string, rendered } => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -991,6 +991,7 @@ impl LspAdapter for PyLspAdapter {
|
||||
util::command::new_smol_command(pip_path.as_path())
|
||||
.arg("install")
|
||||
.arg("python-lsp-server")
|
||||
.arg("-U")
|
||||
.output()
|
||||
.await?
|
||||
.status
|
||||
@@ -1001,6 +1002,7 @@ impl LspAdapter for PyLspAdapter {
|
||||
util::command::new_smol_command(pip_path.as_path())
|
||||
.arg("install")
|
||||
.arg("python-lsp-server[all]")
|
||||
.arg("-U")
|
||||
.output()
|
||||
.await?
|
||||
.status
|
||||
@@ -1011,6 +1013,7 @@ impl LspAdapter for PyLspAdapter {
|
||||
util::command::new_smol_command(pip_path)
|
||||
.arg("install")
|
||||
.arg("pylsp-mypy")
|
||||
.arg("-U")
|
||||
.output()
|
||||
.await?
|
||||
.status
|
||||
|
||||
@@ -746,8 +746,7 @@ pub struct Session {
|
||||
_background_tasks: Vec<Task<()>>,
|
||||
}
|
||||
|
||||
trait CacheableCommand: 'static + Send + Sync {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
trait CacheableCommand: Any + Send + Sync {
|
||||
fn dyn_eq(&self, rhs: &dyn CacheableCommand) -> bool;
|
||||
fn dyn_hash(&self, hasher: &mut dyn Hasher);
|
||||
fn as_any_arc(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
|
||||
@@ -757,12 +756,8 @@ impl<T> CacheableCommand for T
|
||||
where
|
||||
T: DapCommand + PartialEq + Eq + Hash,
|
||||
{
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn dyn_eq(&self, rhs: &dyn CacheableCommand) -> bool {
|
||||
rhs.as_any()
|
||||
(rhs as &dyn Any)
|
||||
.downcast_ref::<Self>()
|
||||
.map_or(false, |rhs| self == rhs)
|
||||
}
|
||||
@@ -795,7 +790,7 @@ impl Eq for RequestSlot {}
|
||||
impl Hash for RequestSlot {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.dyn_hash(state);
|
||||
self.0.as_any().type_id().hash(state)
|
||||
(&*self.0 as &dyn Any).type_id().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1345,7 +1340,7 @@ impl Session {
|
||||
|
||||
fn invalidate_state(&mut self, key: &RequestSlot) {
|
||||
self.requests
|
||||
.entry(key.0.as_any().type_id())
|
||||
.entry((&*key.0 as &dyn Any).type_id())
|
||||
.and_modify(|request_map| {
|
||||
request_map.remove(&key);
|
||||
});
|
||||
|
||||
@@ -2635,7 +2635,8 @@ impl LocalLspStore {
|
||||
.into_iter()
|
||||
.map(|edit| (range_from_lsp(edit.range), edit.new_text))
|
||||
.collect::<Vec<_>>();
|
||||
lsp_edits.sort_by_key(|(range, _)| range.start);
|
||||
|
||||
lsp_edits.sort_by_key(|(range, _)| (range.start, range.end));
|
||||
|
||||
let mut lsp_edits = lsp_edits.into_iter().peekable();
|
||||
let mut edits = Vec::new();
|
||||
|
||||
@@ -2663,6 +2663,62 @@ async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAp
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_edits_from_lsp_with_replacement_followed_by_adjacent_insertion(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
init_test(cx);
|
||||
|
||||
let text = "Path()";
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/dir"),
|
||||
json!({
|
||||
"a.rs": text
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
|
||||
let lsp_store = project.read_with(cx, |project, _| project.lsp_store());
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer(path!("/dir/a.rs"), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Simulate the language server sending us a pair of edits at the same location,
|
||||
// with an insertion following a replacement (which violates the LSP spec).
|
||||
let edits = lsp_store
|
||||
.update(cx, |lsp_store, cx| {
|
||||
lsp_store.as_local_mut().unwrap().edits_from_lsp(
|
||||
&buffer,
|
||||
[
|
||||
lsp::TextEdit {
|
||||
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
|
||||
new_text: "Path".into(),
|
||||
},
|
||||
lsp::TextEdit {
|
||||
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
|
||||
new_text: "from path import Path\n\n\n".into(),
|
||||
},
|
||||
],
|
||||
LanguageServerId(0),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits, None, cx);
|
||||
assert_eq!(buffer.text(), "from path import Path\n\n\nPath()")
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -31,10 +31,9 @@ pub trait RequestMessage: EnvelopedMessage {
|
||||
type Response: EnvelopedMessage;
|
||||
}
|
||||
|
||||
pub trait AnyTypedEnvelope: 'static + Send + Sync {
|
||||
pub trait AnyTypedEnvelope: Any + Send + Sync {
|
||||
fn payload_type_id(&self) -> TypeId;
|
||||
fn payload_type_name(&self) -> &'static str;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>;
|
||||
fn is_background(&self) -> bool;
|
||||
fn original_sender_id(&self) -> Option<PeerId>;
|
||||
@@ -56,10 +55,6 @@ impl<T: EnvelopedMessage> AnyTypedEnvelope for TypedEnvelope<T> {
|
||||
T::NAME
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_any(self: Box<Self>) -> Box<dyn Any + Send + Sync> {
|
||||
self
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::any::Any;
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -1288,11 +1289,8 @@ impl RemoteServerProjects {
|
||||
cx.notify();
|
||||
}));
|
||||
|
||||
let Some(scroll_handle) = scroll_state
|
||||
.scroll_handle()
|
||||
.as_any()
|
||||
.downcast_ref::<ScrollHandle>()
|
||||
else {
|
||||
let handle = &**scroll_state.scroll_handle() as &dyn Any;
|
||||
let Some(scroll_handle) = handle.downcast_ref::<ScrollHandle>() else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use proto::{
|
||||
error::ErrorExt as _,
|
||||
};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
any::{Any, TypeId},
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
@@ -250,8 +250,7 @@ impl AnyProtoClient {
|
||||
let message_type_id = TypeId::of::<M>();
|
||||
let entity_type_id = TypeId::of::<E>();
|
||||
let entity_id_extractor = |envelope: &dyn AnyTypedEnvelope| {
|
||||
envelope
|
||||
.as_any()
|
||||
(envelope as &dyn Any)
|
||||
.downcast_ref::<TypedEnvelope<M>>()
|
||||
.unwrap()
|
||||
.payload
|
||||
@@ -296,8 +295,7 @@ impl AnyProtoClient {
|
||||
let message_type_id = TypeId::of::<M>();
|
||||
let entity_type_id = TypeId::of::<E>();
|
||||
let entity_id_extractor = |envelope: &dyn AnyTypedEnvelope| {
|
||||
envelope
|
||||
.as_any()
|
||||
(envelope as &dyn Any)
|
||||
.downcast_ref::<TypedEnvelope<M>>()
|
||||
.unwrap()
|
||||
.payload
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::{Cell, RefCell},
|
||||
rc::Rc,
|
||||
};
|
||||
@@ -85,8 +84,4 @@ impl ScrollableHandle for TerminalScrollHandle {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,10 +33,6 @@ impl ScrollableHandle for UniformListScrollHandle {
|
||||
fn viewport(&self) -> Bounds<Pixels> {
|
||||
self.0.borrow().base_handle.bounds()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollableHandle for ListState {
|
||||
@@ -66,10 +62,6 @@ impl ScrollableHandle for ListState {
|
||||
fn viewport(&self) -> Bounds<Pixels> {
|
||||
self.viewport_bounds()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollableHandle for ScrollHandle {
|
||||
@@ -107,10 +99,6 @@ impl ScrollableHandle for ScrollHandle {
|
||||
fn viewport(&self) -> Bounds<Pixels> {
|
||||
self.bounds()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -119,12 +107,11 @@ pub struct ContentSize {
|
||||
pub scroll_adjustment: Option<Point<Pixels>>,
|
||||
}
|
||||
|
||||
pub trait ScrollableHandle: Debug + 'static {
|
||||
pub trait ScrollableHandle: Any + Debug {
|
||||
fn content_size(&self) -> Option<ContentSize>;
|
||||
fn set_offset(&self, point: Point<Pixels>);
|
||||
fn offset(&self) -> Point<Pixels>;
|
||||
fn viewport(&self) -> Bounds<Pixels>;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn drag_started(&self) {}
|
||||
fn drag_ended(&self) {}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ use smallvec::{SmallVec, smallvec};
|
||||
use smol::channel::{self, Sender};
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::Borrow as _,
|
||||
cmp::Ordering,
|
||||
collections::hash_map,
|
||||
convert::TryFrom,
|
||||
@@ -3263,10 +3264,6 @@ impl language::File for File {
|
||||
self.worktree.read(cx).id()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn to_proto(&self, cx: &App) -> rpc::proto::File {
|
||||
rpc::proto::File {
|
||||
worktree_id: self.worktree.read(cx).id().to_proto(),
|
||||
@@ -3359,7 +3356,11 @@ impl File {
|
||||
}
|
||||
|
||||
pub fn from_dyn(file: Option<&Arc<dyn language::File>>) -> Option<&Self> {
|
||||
file.and_then(|f| f.as_any().downcast_ref())
|
||||
file.and_then(|f| {
|
||||
let f: &dyn language::File = f.borrow();
|
||||
let f: &dyn Any = f;
|
||||
f.downcast_ref()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn worktree_id(&self, cx: &App) -> WorktreeId {
|
||||
|
||||
Reference in New Issue
Block a user