1use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone)]
6pub struct ClientConfig {
7 pub directory_url: String,
8 pub timeout_ms: u64,
10 pub region: Option<String>,
11 pub node_token: Option<String>,
12 pub use_confidentiality: bool,
14}
15
16impl Default for ClientConfig {
17 fn default() -> Self {
18 Self {
19 directory_url: "https://iicp.network/api".into(),
20 timeout_ms: 30_000,
21 region: None,
22 node_token: None,
23 use_confidentiality: false,
24 }
25 }
26}
27
28#[derive(Debug, Default, Clone)]
30pub struct DiscoverOptions {
31 pub region: Option<String>,
32 pub model: Option<String>,
33 pub min_reputation: Option<f64>,
34 pub limit: Option<u32>,
35}
36
37#[derive(Debug, Clone, Deserialize)]
39pub struct CxPublicKey {
40 pub algorithm: String,
41 pub key: String,
43 pub key_id: String,
45}
46
47#[derive(Debug, Clone, Deserialize)]
49pub struct Node {
50 pub node_id: String,
51 pub endpoint: String,
52 pub score: f64,
53 pub available: bool,
54 pub region: String,
55 pub models: Option<Vec<String>>,
56 pub cip_policy: Option<CipPolicy>,
57 #[serde(default)]
60 pub health_label: Option<String>,
61 #[serde(default)]
63 pub exposure_mode: Option<String>,
64 #[serde(default)]
67 pub cx_public_key: Option<CxPublicKey>,
68 #[serde(default)]
71 pub transport: Vec<String>,
72}
73
74#[derive(Debug, Clone, Deserialize)]
76pub struct CipPolicy {
77 pub allow_remote_inference: bool,
78}
79
80#[derive(Debug, Clone, Deserialize)]
82pub struct NodeList {
83 pub nodes: Vec<Node>,
84 pub count: u32,
85}
86
87#[derive(Debug, Clone, Serialize)]
89pub struct TaskRequest {
90 pub task_id: String,
91 pub intent: String,
92 pub payload: serde_json::Value,
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub constraints: Option<TaskConstraints>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 pub auth: Option<TaskAuth>,
97}
98
99#[derive(Debug, Clone, Serialize)]
101pub struct TaskConstraints {
102 #[serde(skip_serializing_if = "Option::is_none")]
103 pub timeout_ms: Option<u64>,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub max_tokens: Option<u32>,
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub model: Option<String>,
108}
109
110#[derive(Debug, Clone, Serialize)]
112pub struct TaskAuth {
113 pub token: String,
114}
115
116#[derive(Debug, Clone, Deserialize)]
118pub struct TaskResponse {
119 pub task_id: String,
120 pub status: String,
121 pub result: Option<serde_json::Value>,
122 pub metrics: Option<TaskMetrics>,
123}
124
125#[derive(Debug, Clone, Deserialize)]
127pub struct TaskMetrics {
128 pub latency_ms: Option<f64>,
129 pub tokens_used: Option<u32>,
130 pub node_id: Option<String>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct ChatMessage {
136 pub role: String,
137 pub content: String,
138}
139
140#[derive(Debug, Default, Clone)]
142pub struct ChatOptions {
143 pub model: Option<String>,
144 pub max_tokens: Option<u32>,
145 pub timeout_ms: Option<u64>,
146 pub temperature: Option<f64>,
147}
148
149#[derive(Debug, Clone, Deserialize, Default)]
151pub struct ChatResponse {
152 pub choices: Vec<ChatChoice>,
153 pub usage: Option<ChatUsage>,
154 #[serde(default)]
156 pub task_id: String,
157 #[serde(default)]
159 pub node_id: Option<String>,
160}
161
162#[derive(Debug, Clone, Deserialize)]
164pub struct ChatChoice {
165 pub message: ChatMessage,
166 pub finish_reason: Option<String>,
167}
168
169#[derive(Debug, Clone, Deserialize)]
171pub struct ChatUsage {
172 pub total_tokens: Option<u32>,
173 pub prompt_tokens: Option<u32>,
174 pub completion_tokens: Option<u32>,
175}
176
177#[cfg(test)]
178mod tests {
179 use super::Node;
180
181 #[test]
183 fn node_parses_health_label_and_exposure_mode() {
184 let json = r#"{"node_id":"n1","endpoint":"https://x","score":0.9,"available":true,"region":"eu","health_label":"healthy","exposure_mode":"ipv4_public_direct","transport":["https","iicp-native"]}"#;
185 let n: Node = serde_json::from_str(json).unwrap();
186 assert_eq!(n.health_label.as_deref(), Some("healthy"));
187 assert_eq!(n.exposure_mode.as_deref(), Some("ipv4_public_direct"));
188 assert_eq!(n.transport, vec!["https", "iicp-native"]);
190 }
191
192 #[test]
194 fn node_health_fields_default_none_for_old_directory() {
195 let json =
196 r#"{"node_id":"n1","endpoint":"https://x","score":0.5,"available":true,"region":"eu"}"#;
197 let n: Node = serde_json::from_str(json).unwrap();
198 assert!(n.health_label.is_none());
199 assert!(n.exposure_mode.is_none());
200 }
201}