scripting_tool.rs

 1mod session;
 2
 3pub(crate) use session::*;
 4
 5use assistant_tool::{Tool, ToolRegistry};
 6use gpui::{App, AppContext as _, Task, WeakEntity, Window};
 7use schemars::JsonSchema;
 8use serde::Deserialize;
 9use std::sync::Arc;
10use workspace::Workspace;
11
12pub fn init(cx: &App) {
13    let registry = ToolRegistry::global(cx);
14    registry.register_tool(ScriptingTool);
15}
16
17#[derive(Debug, Deserialize, JsonSchema)]
18struct ScriptingToolInput {
19    lua_script: String,
20}
21
22struct ScriptingTool;
23
24impl Tool for ScriptingTool {
25    fn name(&self) -> String {
26        "lua-interpreter".into()
27    }
28
29    fn description(&self) -> String {
30        r#"You can write a Lua script and I'll run it on my code base and tell you what its output was,
31including both stdout as well as the git diff of changes it made to the filesystem. That way,
32you can get more information about the code base, or make changes to the code base directly.
33The lua script will have access to `io` and it will run with the current working directory being in
34the root of the code base, so you can use it to explore, search, make changes, etc. You can also have
35the script print things, and I'll tell you what the output was. Note that `io` only has `open`, and
36then the file it returns only has the methods read, write, and close - it doesn't have popen or
37anything else. Also, I'm going to be putting this Lua script into JSON, so please don't use Lua's
38double quote syntax for string literals - use one of Lua's other syntaxes for string literals, so I
39don't have to escape the double quotes. There will be a global called `search` which accepts a regex
40(it's implemented using Rust's regex crate, so use that regex syntax) and runs that regex on the contents
41of every file in the code base (aside from gitignored files), then returns an array of tables with two
42fields: "path" (the path to the file that had the matches) and "matches" (an array of strings, with each
43string being a match that was found within the file)."#.into()
44    }
45
46    fn input_schema(&self) -> serde_json::Value {
47        let schema = schemars::schema_for!(ScriptingToolInput);
48        serde_json::to_value(&schema).unwrap()
49    }
50
51    fn run(
52        self: Arc<Self>,
53        input: serde_json::Value,
54        workspace: WeakEntity<Workspace>,
55        _window: &mut Window,
56        cx: &mut App,
57    ) -> Task<anyhow::Result<String>> {
58        let input = match serde_json::from_value::<ScriptingToolInput>(input) {
59            Err(err) => return Task::ready(Err(err.into())),
60            Ok(input) => input,
61        };
62        let Ok(project) = workspace.read_with(cx, |workspace, _cx| workspace.project().clone())
63        else {
64            return Task::ready(Err(anyhow::anyhow!("No project found")));
65        };
66
67        let session = cx.new(|cx| Session::new(project, cx));
68        let lua_script = input.lua_script;
69        let script = session.update(cx, |session, cx| session.run_script(lua_script, cx));
70        cx.spawn(|_cx| async move {
71            let output = script.await?.stdout;
72            drop(session);
73            Ok(format!("The script output the following:\n{output}"))
74        })
75    }
76}