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, Default, Serialize, Deserialize)]
254pub struct Watch {
255 #[serde(default, skip_serializing_if = "Option::is_none")]
257 pub from: Option<u64>,
258 #[serde(default, skip_serializing_if = "Option::is_none")]
261 pub sessions: Option<Vec<String>>,
262 #[serde(default, skip_serializing_if = "Vec::is_empty")]
264 pub reserved: Vec<String>,
265 #[serde(default, skip_serializing_if = "Option::is_none")]
268 pub wait_ms: Option<i64>,
269}
270
271impl Watch {
272 pub fn after(&self, answer: &Watched) -> Self {
274 Self {
275 from: Some(answer.next),
276 reserved: answer.reserved.clone(),
277 ..self.clone()
278 }
279 }
280}
281
282#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
284pub struct Watched {
285 pub events: Vec<WatchedEvent>,
286 pub next: u64,
288 #[serde(default)]
291 pub reserved: Vec<String>,
292 #[serde(default)]
295 pub ended: Vec<EndedSession>,
296}
297
298#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
301pub struct WatchedEvent {
302 pub session: String,
303 pub kind: String,
307 #[serde(default, skip_serializing_if = "Option::is_none")]
310 pub seq: Option<u64>,
311 #[serde(default, skip_serializing_if = "Option::is_none")]
312 pub change: Option<ChangeId>,
313 #[serde(default, skip_serializing_if = "Option::is_none")]
315 pub from: Option<String>,
316 #[serde(default, skip_serializing_if = "Option::is_none")]
318 pub reservation: Option<String>,
319 #[serde(default, skip_serializing_if = "Option::is_none")]
321 pub until_ms: Option<i64>,
322}
323
324#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
325pub struct EndedSession {
326 pub session: String,
327 pub reason: String,
330}
331
332#[derive(Clone, Debug, Serialize, Deserialize)]
333pub struct FactsPreview {
334 pub base: GitId,
335 pub files: Vec<FileChange>,
336}
337
338#[derive(Clone, Debug, Serialize, Deserialize)]
339pub struct TreeQuery {
340 pub commit: GitId,
341 #[serde(default)]
342 pub with_content: bool,
343 #[serde(default)]
344 pub paths: Option<Vec<String>>,
345}
346
347#[derive(Clone, Debug, Serialize, Deserialize)]
348pub struct TreeFile {
349 pub path: String,
350 pub mode: FileMode,
351 pub blob: GitId,
352 #[serde(default)]
354 pub size: u64,
355 #[serde(default, skip_serializing_if = "Option::is_none")]
356 pub content_b64: Option<String>,
357}
358
359#[derive(Clone, Debug, Serialize, Deserialize)]
362pub struct BlobsQuery {
363 pub commit: GitId,
364 pub paths: Vec<String>,
365}
366
367#[derive(Clone, Debug, Serialize, Deserialize)]
368pub struct LogQuery {
369 #[serde(default)]
370 pub from: u64,
371 #[serde(default)]
372 pub limit: Option<u64>,
373 #[serde(default)]
376 pub wait_ms: Option<i64>,
377}
378
379#[derive(Clone, Debug, Default, Serialize, Deserialize)]
382pub struct HookEvent {
383 pub hook_event_name: String,
384 #[serde(default)]
385 pub tool_name: Option<String>,
386 #[serde(default)]
387 pub tool_input: Value,
388 #[serde(default)]
389 pub cwd: Option<String>,
390 pub workspace: String,
392 #[serde(default)]
394 pub harness: Option<String>,
395}
396
397#[derive(Clone, Debug, Serialize, Deserialize)]
399pub struct HarnessSetup {
400 pub harness: String,
401}
402
403#[derive(Clone, Debug, Serialize, Deserialize)]
406pub struct HookInput {
407 pub harness: String,
408 pub input: Value,
409 pub workspace: String,
411}
412
413#[derive(Clone, Debug, Serialize, Deserialize)]
415pub struct LocalFile {
416 pub path: String,
417 #[serde(default)]
419 pub entry: Option<FileEntry>,
420}
421
422#[derive(Clone, Debug, Serialize, Deserialize)]
424pub struct HookStop {
425 pub harness: String,
426 pub files: Vec<LocalFile>,
427}
428
429#[derive(Clone, Debug, Serialize, Deserialize)]
432pub struct McpCall {
433 pub tool: String,
434}
435
436#[derive(Clone, Debug, Default, Serialize, Deserialize)]
438pub struct SessionEnd {
439 #[serde(default)]
440 pub files: Option<Vec<LocalFile>>,
441}
442
443#[derive(Clone, Debug, Serialize, Deserialize)]
444pub struct HookBind {
445 pub harness: String,
446 pub harness_session: String,
447 pub sudo_without_password: bool,
448 #[serde(default)]
450 pub workspace: Option<String>,
451}
452
453#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
454pub struct HookVerdict {
455 pub decision: String,
457 #[serde(default)]
458 pub reason: String,
459}
460
461pub fn read_line_limited(r: &mut impl BufRead, max: usize) -> std::io::Result<Option<String>> {
463 let mut buf = Vec::new();
464 loop {
465 let chunk = r.fill_buf()?;
466 if chunk.is_empty() {
467 return Ok(if buf.is_empty() {
468 None
469 } else {
470 Some(String::from_utf8_lossy(&buf).into_owned())
471 });
472 }
473 match chunk.iter().position(|&b| b == b'\n') {
474 Some(i) => {
475 buf.extend_from_slice(&chunk[..i]);
476 r.consume(i + 1);
477 return Ok(Some(String::from_utf8_lossy(&buf).into_owned()));
478 }
479 None => {
480 let n = chunk.len();
481 buf.extend_from_slice(chunk);
482 r.consume(n);
483 if buf.len() > max {
484 return Err(std::io::Error::new(
485 std::io::ErrorKind::InvalidData,
486 "request too large",
487 ));
488 }
489 }
490 }
491 }
492}
493
494pub fn write_line<T: Serialize>(w: &mut impl Write, v: &T) -> std::io::Result<()> {
495 let mut bytes = serde_json::to_vec(v)?;
496 bytes.push(b'\n');
497 w.write_all(&bytes)?;
498 w.flush()
499}