Skip to main content

boxxkite_client/
lsp.rs

1//! Agent-invokable language-server completions: `POST /v1/sandboxes/{id}/lsp/start`
2//! and its `/{lsp_id}/open`, `/{lsp_id}/completion`, `/{lsp_id}/stop`
3//! siblings. Mirrors `sdk-python`'s `lsp_start`/`lsp_open`/`lsp_completion`/
4//! `lsp_stop` (`sdk-go` does not wrap these).
5
6use reqwest::Method;
7use serde::{Deserialize, Serialize};
8
9use crate::client::Client;
10use crate::error::BoxxkiteError;
11
12/// `POST /v1/sandboxes/{id}/lsp/start`'s response. `lsp_id` is an opaque
13/// handle to pass to the other LSP calls.
14#[derive(Debug, Clone, Deserialize)]
15pub struct LspStartResult {
16    pub lsp_id: String,
17}
18
19/// `POST /v1/sandboxes/{id}/lsp/{lsp_id}/open`'s response.
20#[derive(Debug, Clone, Deserialize)]
21pub struct LspOpenResult {
22    pub status: String,
23}
24
25/// `POST /v1/sandboxes/{id}/lsp/{lsp_id}/completion`'s response. Each item is
26/// a raw LSP `CompletionItem` object -- left as raw JSON rather than an
27/// over-fitted struct, since the shape is the language server's, not this
28/// API's (matching how `ls`/`glob` leave their entries untyped).
29#[derive(Debug, Clone, Deserialize)]
30pub struct LspCompletionResult {
31    #[serde(default)]
32    pub items: Vec<serde_json::Value>,
33}
34
35/// `POST /v1/sandboxes/{id}/lsp/{lsp_id}/stop`'s response.
36#[derive(Debug, Clone, Deserialize)]
37pub struct LspStopResult {
38    pub status: String,
39}
40
41impl Client {
42    /// `POST /v1/sandboxes/{session_id}/lsp/start` -- start a persistent
43    /// language server (`pyright` for `"python"`,
44    /// `typescript-language-server` for `"typescript"`/`"javascript"`).
45    /// Returns the opaque `lsp_id` handle for the other LSP calls.
46    pub async fn lsp_start(
47        &self,
48        session_id: &str,
49        language: &str,
50    ) -> Result<LspStartResult, BoxxkiteError> {
51        #[derive(Serialize)]
52        struct Body<'a> {
53            language: &'a str,
54        }
55        let builder = self
56            .request(
57                Method::POST,
58                &format!("/v1/sandboxes/{session_id}/lsp/start"),
59            )
60            .json(&Body { language });
61        self.send(builder).await
62    }
63
64    /// `POST /v1/sandboxes/{session_id}/lsp/{lsp_id}/open` -- open (or
65    /// full-document-replace) a document on a running language server.
66    pub async fn lsp_open(
67        &self,
68        session_id: &str,
69        lsp_id: &str,
70        path: &str,
71        content: &str,
72    ) -> Result<LspOpenResult, BoxxkiteError> {
73        #[derive(Serialize)]
74        struct Body<'a> {
75            path: &'a str,
76            content: &'a str,
77        }
78        let builder = self
79            .request(
80                Method::POST,
81                &format!("/v1/sandboxes/{session_id}/lsp/{lsp_id}/open"),
82            )
83            .json(&Body { path, content });
84        self.send(builder).await
85    }
86
87    /// `POST /v1/sandboxes/{session_id}/lsp/{lsp_id}/completion` -- request
88    /// completions at a 0-indexed `(line, character)` position. `path` must
89    /// already be open on this handle (see [`Client::lsp_open`]).
90    pub async fn lsp_completion(
91        &self,
92        session_id: &str,
93        lsp_id: &str,
94        path: &str,
95        line: u32,
96        character: u32,
97    ) -> Result<LspCompletionResult, BoxxkiteError> {
98        #[derive(Serialize)]
99        struct Body<'a> {
100            path: &'a str,
101            line: u32,
102            character: u32,
103        }
104        let builder = self
105            .request(
106                Method::POST,
107                &format!("/v1/sandboxes/{session_id}/lsp/{lsp_id}/completion"),
108            )
109            .json(&Body {
110                path,
111                line,
112                character,
113            });
114        self.send(builder).await
115    }
116
117    /// `POST /v1/sandboxes/{session_id}/lsp/{lsp_id}/stop` -- gracefully shut
118    /// down a running language server.
119    pub async fn lsp_stop(
120        &self,
121        session_id: &str,
122        lsp_id: &str,
123    ) -> Result<LspStopResult, BoxxkiteError> {
124        let builder = self.request(
125            Method::POST,
126            &format!("/v1/sandboxes/{session_id}/lsp/{lsp_id}/stop"),
127        );
128        self.send(builder).await
129    }
130}