1use std::io::{BufRead, Write};
7
8use lonca_core::ops::{Claim, Decision, HumanSignature, LeaseItem, NegotiationMsg, Op, Scope};
9use lonca_core::{ChangeId, FileEntry, FileMode, GitId};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13pub const MAX_LINE: usize = 96 * 1024 * 1024;
15
16#[derive(Clone, Debug, Serialize, Deserialize)]
17pub struct Request {
18 pub id: u64,
19 pub method: String,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
21 pub repo: Option<String>,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub token: Option<String>,
25 #[serde(default)]
26 pub params: Value,
27}
28
29#[derive(Clone, Debug, Serialize, Deserialize)]
30pub struct ErrorBody {
31 pub code: String,
32 pub message: String,
33}
34
35#[derive(Clone, Debug, Serialize, Deserialize)]
36pub struct Response {
37 pub id: u64,
38 pub ok: bool,
39 #[serde(default, skip_serializing_if = "Value::is_null")]
40 pub result: Value,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub error: Option<ErrorBody>,
43}
44
45impl Response {
46 pub fn ok(id: u64, result: Value) -> Self {
47 Self {
48 id,
49 ok: true,
50 result,
51 error: None,
52 }
53 }
54
55 pub fn err(id: u64, code: &str, message: impl Into<String>) -> Self {
56 Self {
57 id,
58 ok: false,
59 result: Value::Null,
60 error: Some(ErrorBody {
61 code: code.into(),
62 message: message.into(),
63 }),
64 }
65 }
66}
67
68#[derive(Clone, Debug, Serialize, Deserialize)]
70pub struct Logged {
71 pub seq: u64,
72 pub decision: Decision,
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize)]
77pub struct FileChange {
78 pub path: String,
79 #[serde(default)]
81 pub content_b64: Option<String>,
82 #[serde(default)]
83 pub mode: Option<FileMode>,
84}
85
86#[derive(Clone, Debug, Serialize, Deserialize)]
87pub struct HumanSubmit {
88 pub op: Op,
89 pub sig: HumanSignature,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub workspace: Option<String>,
94}
95
96#[derive(Clone, Debug, Serialize, Deserialize)]
98pub struct PrepareIsolate {
99 pub session: String,
100 pub base: GitId,
102}
103
104#[derive(Clone, Debug, Serialize, Deserialize)]
109pub struct HumanLogin {
110 pub sig: HumanSignature,
111 #[serde(default)]
113 pub ttl_ms: Option<i64>,
114}
115
116#[derive(Clone, Debug, Serialize, Deserialize)]
119pub struct Restore {
120 pub repo: String,
121 pub from: String,
122}
123
124#[derive(Clone, Debug, Serialize, Deserialize)]
125pub struct PrepareChange {
126 pub base: GitId,
127 pub files: Vec<FileChange>,
128 #[serde(default)]
129 pub change: Option<ChangeId>,
130 pub author: String,
131 #[serde(default)]
132 pub intent: String,
133 #[serde(default)]
134 pub revert_of: Option<ChangeId>,
135}
136
137#[derive(Clone, Debug, Serialize, Deserialize)]
138pub struct PreparePolicy {
139 pub config: String,
140 pub policy: String,
141}
142
143#[derive(Clone, Debug, Serialize, Deserialize)]
144pub struct PrepareRemote {
145 pub remote: String,
146 #[serde(default)]
147 pub branch: Option<String>,
148}
149
150#[derive(Clone, Debug, Serialize, Deserialize)]
151pub struct LeaseAcquire {
152 #[serde(default)]
153 pub set: Option<String>,
154 pub items: Vec<LeaseItem>,
155 pub intent: String,
156 pub ttl_ms: i64,
157}
158
159#[derive(Clone, Debug, Serialize, Deserialize)]
160pub struct SetRef {
161 pub set: String,
162 #[serde(default)]
163 pub ttl_ms: Option<i64>,
164}
165
166#[derive(Clone, Debug, Serialize, Deserialize)]
167pub struct ProposeChange {
168 #[serde(default)]
169 pub change: Option<ChangeId>,
170 pub base: GitId,
171 pub files: Vec<FileChange>,
172 pub claim: Claim,
173}
174
175#[derive(Clone, Debug, Serialize, Deserialize)]
176pub struct ChangeRef {
177 pub change: ChangeId,
178}
179
180#[derive(Clone, Debug, Default, Serialize, Deserialize)]
183pub struct SecurityEvents {
184 #[serde(default)]
185 pub since_ms: Option<i64>,
186}
187
188#[derive(Clone, Debug, Serialize, Deserialize)]
191pub struct TrunkBundle {
192 #[serde(default)]
193 pub have: Vec<GitId>,
194}
195
196#[derive(Clone, Debug, Serialize, Deserialize)]
198pub struct TrunkDistance {
199 pub base: GitId,
200}
201
202#[derive(Clone, Debug, Serialize, Deserialize)]
205pub struct ChangeDiff {
206 pub change: ChangeId,
207 #[serde(default)]
209 pub format: Option<String>,
210}
211
212#[derive(Clone, Debug, Serialize, Deserialize)]
213pub struct Negotiate {
214 pub thread: String,
215 pub to_session: String,
216 pub message: NegotiationMsg,
217}
218
219#[derive(Clone, Debug, Serialize, Deserialize)]
220pub struct Attenuate {
221 pub agent: String,
222 pub scope: Scope,
223 pub expires_ms: i64,
224 #[serde(default)]
226 pub leases: Vec<String>,
227}
228
229#[derive(Clone, Debug, Serialize, Deserialize)]
230pub struct LeaseInterest {
231 pub items: Vec<LeaseItem>,
232 #[serde(default)]
233 pub intent: String,
234}
235
236#[derive(Clone, Debug, Serialize, Deserialize)]
237pub struct Events {
238 #[serde(default)]
239 pub from: u64,
240}
241
242#[derive(Clone, Debug, Serialize, Deserialize)]
243pub struct FactsPreview {
244 pub base: GitId,
245 pub files: Vec<FileChange>,
246}
247
248#[derive(Clone, Debug, Serialize, Deserialize)]
249pub struct TreeQuery {
250 pub commit: GitId,
251 #[serde(default)]
252 pub with_content: bool,
253 #[serde(default)]
254 pub paths: Option<Vec<String>>,
255}
256
257#[derive(Clone, Debug, Serialize, Deserialize)]
258pub struct TreeFile {
259 pub path: String,
260 pub mode: FileMode,
261 pub blob: GitId,
262 #[serde(default)]
264 pub size: u64,
265 #[serde(default, skip_serializing_if = "Option::is_none")]
266 pub content_b64: Option<String>,
267}
268
269#[derive(Clone, Debug, Serialize, Deserialize)]
272pub struct BlobsQuery {
273 pub commit: GitId,
274 pub paths: Vec<String>,
275}
276
277#[derive(Clone, Debug, Serialize, Deserialize)]
278pub struct LogQuery {
279 #[serde(default)]
280 pub from: u64,
281 #[serde(default)]
282 pub limit: Option<u64>,
283 #[serde(default)]
286 pub wait_ms: Option<i64>,
287}
288
289#[derive(Clone, Debug, Default, Serialize, Deserialize)]
292pub struct HookEvent {
293 pub hook_event_name: String,
294 #[serde(default)]
295 pub tool_name: Option<String>,
296 #[serde(default)]
297 pub tool_input: Value,
298 #[serde(default)]
299 pub cwd: Option<String>,
300 pub workspace: String,
302 #[serde(default)]
304 pub harness: Option<String>,
305}
306
307#[derive(Clone, Debug, Serialize, Deserialize)]
309pub struct HarnessSetup {
310 pub harness: String,
311}
312
313#[derive(Clone, Debug, Serialize, Deserialize)]
316pub struct HookInput {
317 pub harness: String,
318 pub input: Value,
319 pub workspace: String,
321}
322
323#[derive(Clone, Debug, Serialize, Deserialize)]
325pub struct LocalFile {
326 pub path: String,
327 #[serde(default)]
329 pub entry: Option<FileEntry>,
330}
331
332#[derive(Clone, Debug, Serialize, Deserialize)]
334pub struct HookStop {
335 pub harness: String,
336 pub files: Vec<LocalFile>,
337}
338
339#[derive(Clone, Debug, Serialize, Deserialize)]
342pub struct McpCall {
343 pub tool: String,
344}
345
346#[derive(Clone, Debug, Default, Serialize, Deserialize)]
348pub struct SessionEnd {
349 #[serde(default)]
350 pub files: Option<Vec<LocalFile>>,
351}
352
353#[derive(Clone, Debug, Serialize, Deserialize)]
354pub struct HookBind {
355 pub harness: String,
356 pub harness_session: String,
357 pub sudo_without_password: bool,
358 #[serde(default)]
360 pub workspace: Option<String>,
361}
362
363#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
364pub struct HookVerdict {
365 pub decision: String,
367 #[serde(default)]
368 pub reason: String,
369}
370
371pub fn read_line_limited(r: &mut impl BufRead, max: usize) -> std::io::Result<Option<String>> {
373 let mut buf = Vec::new();
374 loop {
375 let chunk = r.fill_buf()?;
376 if chunk.is_empty() {
377 return Ok(if buf.is_empty() {
378 None
379 } else {
380 Some(String::from_utf8_lossy(&buf).into_owned())
381 });
382 }
383 match chunk.iter().position(|&b| b == b'\n') {
384 Some(i) => {
385 buf.extend_from_slice(&chunk[..i]);
386 r.consume(i + 1);
387 return Ok(Some(String::from_utf8_lossy(&buf).into_owned()));
388 }
389 None => {
390 let n = chunk.len();
391 buf.extend_from_slice(chunk);
392 r.consume(n);
393 if buf.len() > max {
394 return Err(std::io::Error::new(
395 std::io::ErrorKind::InvalidData,
396 "request too large",
397 ));
398 }
399 }
400 }
401 }
402}
403
404pub fn write_line<T: Serialize>(w: &mut impl Write, v: &T) -> std::io::Result<()> {
405 let mut bytes = serde_json::to_vec(v)?;
406 bytes.push(b'\n');
407 w.write_all(&bytes)?;
408 w.flush()
409}