1use serde::{Deserialize, Serialize};
5
6pub mod agents;
7pub mod answers;
8#[cfg(feature = "client")]
9pub mod client;
10pub mod conventions;
11pub mod credentials;
12pub mod doctor;
13pub mod events;
14pub mod graph;
15pub mod hosting;
16pub mod jobs;
17pub mod ledger;
18pub mod manifests;
19pub mod model;
20pub mod preflight;
21pub mod prompts;
22pub mod search;
23pub mod server;
24pub mod team;
25pub mod view;
26
27mod account;
28mod browse;
29mod records;
30mod repos;
31mod settings;
32
33pub use account::*;
34pub use browse::*;
35pub use records::*;
36pub use repos::*;
37pub use settings::*;
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct Done {
42 pub ok: bool,
43 pub message: String,
44}
45
46impl Done {
47 pub fn new(message: impl Into<String>) -> Done {
48 Done {
49 ok: true,
50 message: message.into(),
51 }
52 }
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct ErrorBody {
58 pub error: ErrorDetail,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ErrorDetail {
64 pub code: String,
65 pub message: String,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub host: Option<String>,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub username: Option<String>,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct Health {
75 pub status: String,
76 pub version: String,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct Ready {
82 pub ready: bool,
83 pub index: bool,
84 pub stores: bool,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct IdRequest {
90 pub id: String,
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 fn same<T: Serialize + serde::de::DeserializeOwned>(json: &str) {
99 let v: T = serde_json::from_str(json).unwrap();
100 assert_eq!(serde_json::to_string(&v).unwrap(), json);
101 }
102
103 #[test]
104 fn answers_keep_their_wire_shape() {
105 same::<Me>(r#"{"mode":"local"}"#);
106 same::<Me>(
107 r#"{"mode":"server","signed_in":false,"email":null,"role":null,"sign_in":"email","sign_in_methods":["oidc","email","code"],"oidc_label":"Microsoft","public_url":"http://localhost:1","gateway_url":"https://mcp.example.com/mcp"}"#,
108 );
109 let older: Me = serde_json::from_str(
110 r#"{"mode":"server","signed_in":false,"email":null,"role":null,"sign_in":"code","public_url":"http://localhost:1"}"#,
111 )
112 .unwrap();
113 assert!(matches!(older, Me::Server { sign_in_methods, .. } if sign_in_methods.is_empty()));
114 same::<DeviceToken>(r#"{"status":"pending"}"#);
115 same::<DeviceToken>(
116 r#"{"status":"approved","token":"t","email":"a@example.com","expires":null}"#,
117 );
118 same::<Secrets>(
119 r#"{"secrets":[{"source":"deployment","name":"llm_api_key","label":"LLM API key"},{"name":"git:github.com","source":"stored","hint":"…abcd","updated":"2026-01-01T00:00:00+00:00","updated_by":"a@example.com","label":"GitHub token for github.com"}],"can_store":true}"#,
120 );
121 same::<IndexState>(r#"{"running":false,"events":[],"queued":[]}"#);
122 same::<IndexState>(
123 r#"{"id":"j","running":false,"cancelled":false,"ok":true,"status":"succeeded","events":[],"started":"s","finished":"f","queued":[{"id":"k","kind":"index","position":1,"started_by":null,"created":"c"}]}"#,
124 );
125 same::<Jobs>(
126 r#"{"jobs":[{"id":"k","kind":"index","started_by":null,"status":"queued","created":"c","started":null,"finished":null,"error":null,"position":1}]}"#,
127 );
128 same::<Invited>(r#"{"ok":true,"message":"Invitation emailed to a@example.com"}"#);
129 same::<ErrorBody>(r#"{"error":{"code":"not_found","message":"no such job"}}"#);
130 same::<Neighbourhood>(r#"{"nodes":[],"edges":[]}"#);
131 }
132}