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 ChangeResolve {
206 pub prefix: String,
207}
208
209#[derive(Clone, Debug, Serialize, Deserialize)]
212pub struct ChangeDiff {
213 pub change: ChangeId,
214 #[serde(default)]
216 pub format: Option<String>,
217}
218
219#[derive(Clone, Debug, Serialize, Deserialize)]
220pub struct Negotiate {
221 pub thread: String,
222 pub to_session: String,
223 pub message: NegotiationMsg,
224}
225
226#[derive(Clone, Debug, Serialize, Deserialize)]
227pub struct Attenuate {
228 pub agent: String,
229 pub scope: Scope,
230 pub expires_ms: i64,
231 #[serde(default)]
233 pub leases: Vec<String>,
234}
235
236#[derive(Clone, Debug, Serialize, Deserialize)]
237pub struct LeaseInterest {
238 pub items: Vec<LeaseItem>,
239 #[serde(default)]
240 pub intent: String,
241}
242
243#[derive(Clone, Debug, Serialize, Deserialize)]
244pub struct Events {
245 #[serde(default)]
246 pub from: u64,
247}
248
249#[derive(Clone, Debug, Serialize, Deserialize)]
250pub struct FactsPreview {
251 pub base: GitId,
252 pub files: Vec<FileChange>,
253}
254
255#[derive(Clone, Debug, Serialize, Deserialize)]
256pub struct TreeQuery {
257 pub commit: GitId,
258 #[serde(default)]
259 pub with_content: bool,
260 #[serde(default)]
261 pub paths: Option<Vec<String>>,
262}
263
264#[derive(Clone, Debug, Serialize, Deserialize)]
265pub struct TreeFile {
266 pub path: String,
267 pub mode: FileMode,
268 pub blob: GitId,
269 #[serde(default)]
271 pub size: u64,
272 #[serde(default, skip_serializing_if = "Option::is_none")]
273 pub content_b64: Option<String>,
274}
275
276#[derive(Clone, Debug, Serialize, Deserialize)]
279pub struct BlobsQuery {
280 pub commit: GitId,
281 pub paths: Vec<String>,
282}
283
284#[derive(Clone, Debug, Serialize, Deserialize)]
285pub struct LogQuery {
286 #[serde(default)]
287 pub from: u64,
288 #[serde(default)]
289 pub limit: Option<u64>,
290 #[serde(default)]
293 pub wait_ms: Option<i64>,
294}
295
296#[derive(Clone, Debug, Default, Serialize, Deserialize)]
299pub struct HookEvent {
300 pub hook_event_name: String,
301 #[serde(default)]
302 pub tool_name: Option<String>,
303 #[serde(default)]
304 pub tool_input: Value,
305 #[serde(default)]
306 pub cwd: Option<String>,
307 pub workspace: String,
309 #[serde(default)]
311 pub harness: Option<String>,
312}
313
314#[derive(Clone, Debug, Serialize, Deserialize)]
316pub struct HarnessSetup {
317 pub harness: String,
318}
319
320#[derive(Clone, Debug, Serialize, Deserialize)]
323pub struct HookInput {
324 pub harness: String,
325 pub input: Value,
326 pub workspace: String,
328}
329
330#[derive(Clone, Debug, Serialize, Deserialize)]
332pub struct LocalFile {
333 pub path: String,
334 #[serde(default)]
336 pub entry: Option<FileEntry>,
337}
338
339#[derive(Clone, Debug, Serialize, Deserialize)]
341pub struct HookStop {
342 pub harness: String,
343 pub files: Vec<LocalFile>,
344}
345
346#[derive(Clone, Debug, Serialize, Deserialize)]
349pub struct McpCall {
350 pub tool: String,
351}
352
353#[derive(Clone, Debug, Default, Serialize, Deserialize)]
355pub struct SessionEnd {
356 #[serde(default)]
357 pub files: Option<Vec<LocalFile>>,
358}
359
360#[derive(Clone, Debug, Serialize, Deserialize)]
361pub struct HookBind {
362 pub harness: String,
363 pub harness_session: String,
364 pub sudo_without_password: bool,
365 #[serde(default)]
367 pub workspace: Option<String>,
368}
369
370#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
371pub struct HookVerdict {
372 pub decision: String,
374 #[serde(default)]
375 pub reason: String,
376}
377
378pub fn read_line_limited(r: &mut impl BufRead, max: usize) -> std::io::Result<Option<String>> {
380 let mut buf = Vec::new();
381 loop {
382 let chunk = r.fill_buf()?;
383 if chunk.is_empty() {
384 return Ok(if buf.is_empty() {
385 None
386 } else {
387 Some(String::from_utf8_lossy(&buf).into_owned())
388 });
389 }
390 match chunk.iter().position(|&b| b == b'\n') {
391 Some(i) => {
392 buf.extend_from_slice(&chunk[..i]);
393 r.consume(i + 1);
394 return Ok(Some(String::from_utf8_lossy(&buf).into_owned()));
395 }
396 None => {
397 let n = chunk.len();
398 buf.extend_from_slice(chunk);
399 r.consume(n);
400 if buf.len() > max {
401 return Err(std::io::Error::new(
402 std::io::ErrorKind::InvalidData,
403 "request too large",
404 ));
405 }
406 }
407 }
408 }
409}
410
411pub fn write_line<T: Serialize>(w: &mut impl Write, v: &T) -> std::io::Result<()> {
412 let mut bytes = serde_json::to_vec(v)?;
413 bytes.push(b'\n');
414 w.write_all(&bytes)?;
415 w.flush()
416}