1use serde::{Deserialize, Serialize};
2
3pub mod message_type {
8 pub const TEXT: &str = "text";
9 pub const MARKDOWN: &str = "markdown";
10 pub const CODE: &str = "code";
11 pub const IMAGE: &str = "image";
12 pub const FILE: &str = "file";
13 pub const VOICE: &str = "voice"; pub const LOCATION: &str = "location"; pub const ARTIFACT: &str = "artifact"; pub const TOOL_CALL: &str = "tool_call";
17 pub const TOOL_RESULT: &str = "tool_result";
18 pub const SYSTEM_EVENT: &str = "system_event";
20 pub const SYSTEM: &str = "system"; pub const THINKING: &str = "thinking";
22}
23
24pub mod artifact_type {
27 pub const PDF: &str = "pdf";
28 pub const CODE: &str = "code";
29 pub const DOCUMENT: &str = "document";
30 pub const DATASET: &str = "dataset";
31 pub const CHART: &str = "chart";
32 pub const NOTEBOOK: &str = "notebook";
33 pub const LATEX: &str = "latex";
34 pub const OTHER: &str = "other";
35}
36
37#[derive(Debug, Deserialize)]
39pub struct ApiResponse<T> {
40 pub success: Option<bool>,
41 pub ok: Option<bool>,
42 pub data: Option<T>,
43 pub error: Option<ApiError>,
44}
45
46impl<T> ApiResponse<T> {
47 pub fn is_ok(&self) -> bool {
48 self.success.unwrap_or(false) || self.ok.unwrap_or(false)
49 }
50}
51
52#[derive(Debug, Deserialize)]
53pub struct ApiError {
54 pub code: Option<String>,
55 pub message: Option<String>,
56}
57
58#[derive(Debug)]
60pub enum PrismerError {
61 Network(String),
62 Api { status: u16, message: String },
63 Parse(String),
64}
65
66impl std::fmt::Display for PrismerError {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 match self {
69 PrismerError::Network(e) => write!(f, "Network error: {}", e),
70 PrismerError::Api { status, message } => write!(f, "API error {}: {}", status, message),
71 PrismerError::Parse(e) => write!(f, "Parse error: {}", e),
72 }
73 }
74}
75
76impl std::error::Error for PrismerError {}
77
78#[derive(Debug, Serialize, Deserialize)]
80pub struct ContextLoadResult {
81 pub results: Option<Vec<ContextItem>>,
82 #[serde(rename = "processingTime")]
83 pub processing_time: Option<u64>,
84}
85
86#[derive(Debug, Serialize, Deserialize)]
87pub struct ContextItem {
88 pub title: Option<String>,
89 pub url: Option<String>,
90 pub content: Option<String>,
91 pub score: Option<f64>,
92}
93
94#[derive(Debug, Serialize, Deserialize)]
96pub struct ParseResult {
97 #[serde(rename = "taskId")]
98 pub task_id: Option<String>,
99 pub status: Option<String>,
100 pub document: Option<serde_json::Value>,
101}
102
103#[derive(Debug, Serialize, Deserialize)]
105pub struct SignalTag {
106 #[serde(rename = "type")]
107 pub signal_type: String,
108 pub provider: Option<String>,
109 pub stage: Option<String>,
110 pub severity: Option<String>,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct Gene {
115 pub id: String,
116 pub category: Option<String>,
117 pub title: Option<String>,
118 pub signals_match: Option<Vec<serde_json::Value>>,
119 pub strategy: Option<Vec<String>>,
120 pub visibility: Option<String>,
121 pub success_count: Option<i64>,
122 pub failure_count: Option<i64>,
123}
124
125#[derive(Debug, Serialize, Deserialize)]
126pub struct EvolutionAdvice {
127 pub action: String,
128 pub gene: Option<Gene>,
129 pub confidence: Option<f64>,
130 pub signals: Option<Vec<serde_json::Value>>,
131}
132
133#[derive(Debug, Serialize, Deserialize)]
134pub struct EvolutionMetrics {
135 pub standard: Option<serde_json::Value>,
136 pub hypergraph: Option<serde_json::Value>,
137 pub verdict: Option<String>,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct IMTask {
147 pub id: String,
148 pub title: String,
149 pub description: Option<String>,
150 pub capability: Option<String>,
151 pub input: Option<serde_json::Value>,
152 #[serde(rename = "contextUri")]
153 pub context_uri: Option<String>,
154 #[serde(rename = "creatorId")]
155 pub creator_id: String,
156 #[serde(rename = "assigneeId")]
157 pub assignee_id: Option<String>,
158 pub status: String,
159 pub progress: Option<f64>,
160 #[serde(rename = "statusMessage")]
161 pub status_message: Option<String>,
162 #[serde(rename = "conversationId")]
163 pub conversation_id: Option<String>,
164 #[serde(rename = "completedAt")]
165 pub completed_at: Option<String>,
166 #[serde(rename = "ownerId")]
167 pub owner_id: String,
168 #[serde(rename = "ownerType")]
169 pub owner_type: Option<String>,
170 #[serde(rename = "ownerName")]
171 pub owner_name: Option<String>,
172 #[serde(rename = "assigneeType")]
173 pub assignee_type: Option<String>,
174 #[serde(rename = "assigneeName")]
175 pub assignee_name: Option<String>,
176 #[serde(rename = "scheduleType")]
177 pub schedule_type: Option<String>,
178 #[serde(rename = "scheduleCron")]
179 pub schedule_cron: Option<String>,
180 #[serde(rename = "intervalMs")]
181 pub interval_ms: Option<i64>,
182 #[serde(rename = "nextRunAt")]
183 pub next_run_at: Option<String>,
184 #[serde(rename = "lastRunAt")]
185 pub last_run_at: Option<String>,
186 #[serde(rename = "runCount")]
187 pub run_count: Option<i64>,
188 #[serde(rename = "maxRuns")]
189 pub max_runs: Option<i64>,
190 pub result: Option<serde_json::Value>,
191 #[serde(rename = "resultUri")]
192 pub result_uri: Option<String>,
193 pub error: Option<String>,
194 pub budget: Option<f64>,
195 pub cost: Option<f64>,
196 #[serde(rename = "timeoutMs")]
197 pub timeout_ms: Option<i64>,
198 pub deadline: Option<String>,
199 #[serde(rename = "maxRetries")]
200 pub max_retries: Option<i64>,
201 #[serde(rename = "retryDelayMs")]
202 pub retry_delay_ms: Option<i64>,
203 #[serde(rename = "retryCount")]
204 pub retry_count: Option<i64>,
205 pub metadata: Option<serde_json::Value>,
206 #[serde(rename = "createdAt")]
207 pub created_at: String,
208 #[serde(rename = "updatedAt")]
209 pub updated_at: String,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct IMTaskLog {
215 pub id: String,
216 #[serde(rename = "taskId")]
217 pub task_id: String,
218 #[serde(rename = "actorId")]
219 pub actor_id: Option<String>,
220 pub action: String,
221 pub message: Option<String>,
222 pub metadata: Option<serde_json::Value>,
223 #[serde(rename = "createdAt")]
224 pub created_at: String,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct IMTaskDetail {
230 pub task: IMTask,
231 pub logs: Vec<IMTaskLog>,
232}
233
234#[derive(Debug, Serialize, Deserialize)]
240pub struct MessageNewPayload {
241 pub id: String,
242 #[serde(rename = "conversationId")]
243 pub conversation_id: String,
244 pub content: String,
245 #[serde(rename = "type")]
246 pub msg_type: String,
247 #[serde(rename = "senderId")]
248 pub sender_id: String,
249 pub routing: Option<serde_json::Value>,
250 pub metadata: Option<serde_json::Value>,
251 #[serde(rename = "createdAt")]
252 pub created_at: String,
253}
254
255#[derive(Debug, Serialize, Deserialize)]
257pub struct MessageEditPayload {
258 pub id: String,
259 #[serde(rename = "conversationId")]
260 pub conversation_id: String,
261 pub content: String,
262 #[serde(rename = "type")]
263 pub msg_type: String,
264 #[serde(rename = "editedAt")]
265 pub edited_at: String,
266 #[serde(rename = "editedBy")]
267 pub edited_by: String,
268 pub metadata: Option<serde_json::Value>,
269}
270
271#[derive(Debug, Serialize, Deserialize)]
273pub struct MessageDeletedPayload {
274 pub id: String,
275 #[serde(rename = "conversationId")]
276 pub conversation_id: String,
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282 use serde_json::json;
283
284 #[test]
287 fn api_response_is_ok_with_success_true() {
288 let resp: ApiResponse<()> = ApiResponse {
289 success: Some(true),
290 ok: None,
291 data: None,
292 error: None,
293 };
294 assert!(resp.is_ok());
295 }
296
297 #[test]
298 fn api_response_is_ok_with_ok_true() {
299 let resp: ApiResponse<()> = ApiResponse {
300 success: None,
301 ok: Some(true),
302 data: None,
303 error: None,
304 };
305 assert!(resp.is_ok());
306 }
307
308 #[test]
309 fn api_response_is_ok_both_false() {
310 let resp: ApiResponse<()> = ApiResponse {
311 success: Some(false),
312 ok: Some(false),
313 data: None,
314 error: None,
315 };
316 assert!(!resp.is_ok());
317 }
318
319 #[test]
320 fn api_response_is_ok_all_none() {
321 let resp: ApiResponse<()> = ApiResponse {
322 success: None,
323 ok: None,
324 data: None,
325 error: None,
326 };
327 assert!(!resp.is_ok());
328 }
329
330 #[test]
331 fn api_response_deserialize_success_field() {
332 let json_str = r#"{"success": true, "data": {"results": []}}"#;
333 let resp: ApiResponse<ContextLoadResult> = serde_json::from_str(json_str).unwrap();
334 assert!(resp.is_ok());
335 assert!(resp.data.is_some());
336 }
337
338 #[test]
339 fn api_response_deserialize_ok_field() {
340 let json_str = r#"{"ok": true, "data": null}"#;
341 let resp: ApiResponse<serde_json::Value> = serde_json::from_str(json_str).unwrap();
342 assert!(resp.is_ok());
343 }
344
345 #[test]
346 fn api_response_deserialize_with_error() {
347 let json_str = r#"{"success": false, "error": {"code": "UNAUTHORIZED", "message": "Bad key"}}"#;
348 let resp: ApiResponse<()> = serde_json::from_str(json_str).unwrap();
349 assert!(!resp.is_ok());
350 let err = resp.error.unwrap();
351 assert_eq!(err.code.as_deref(), Some("UNAUTHORIZED"));
352 assert_eq!(err.message.as_deref(), Some("Bad key"));
353 }
354
355 #[test]
358 fn error_display_network() {
359 let e = PrismerError::Network("connection refused".to_string());
360 assert_eq!(e.to_string(), "Network error: connection refused");
361 }
362
363 #[test]
364 fn error_display_api() {
365 let e = PrismerError::Api { status: 401, message: "Unauthorized".to_string() };
366 assert_eq!(e.to_string(), "API error 401: Unauthorized");
367 }
368
369 #[test]
370 fn error_display_parse() {
371 let e = PrismerError::Parse("invalid json".to_string());
372 assert_eq!(e.to_string(), "Parse error: invalid json");
373 }
374
375 #[test]
376 fn error_implements_std_error() {
377 let e: Box<dyn std::error::Error> = Box::new(PrismerError::Network("test".into()));
378 assert!(e.to_string().contains("Network error"));
379 }
380
381 #[test]
384 fn context_load_result_roundtrip() {
385 let result = ContextLoadResult {
386 results: Some(vec![ContextItem {
387 title: Some("Test".to_string()),
388 url: Some("https://example.com".to_string()),
389 content: Some("Hello".to_string()),
390 score: Some(0.95),
391 }]),
392 processing_time: Some(123),
393 };
394 let json = serde_json::to_string(&result).unwrap();
395 let decoded: ContextLoadResult = serde_json::from_str(&json).unwrap();
396 assert_eq!(decoded.processing_time, Some(123));
397 assert_eq!(decoded.results.as_ref().unwrap().len(), 1);
398 assert_eq!(decoded.results.as_ref().unwrap()[0].title.as_deref(), Some("Test"));
399 }
400
401 #[test]
402 fn context_load_result_processing_time_rename() {
403 let json_str = r#"{"processingTime": 456, "results": null}"#;
404 let decoded: ContextLoadResult = serde_json::from_str(json_str).unwrap();
405 assert_eq!(decoded.processing_time, Some(456));
406 }
407
408 #[test]
411 fn parse_result_roundtrip() {
412 let result = ParseResult {
413 task_id: Some("task-123".to_string()),
414 status: Some("completed".to_string()),
415 document: Some(json!({"pages": 5})),
416 };
417 let json = serde_json::to_string(&result).unwrap();
418 assert!(json.contains("taskId")); let decoded: ParseResult = serde_json::from_str(&json).unwrap();
420 assert_eq!(decoded.task_id.as_deref(), Some("task-123"));
421 }
422
423 #[test]
426 fn signal_tag_roundtrip() {
427 let tag = SignalTag {
428 signal_type: "error:timeout".to_string(),
429 provider: Some("openai".to_string()),
430 stage: Some("fetch".to_string()),
431 severity: Some("high".to_string()),
432 };
433 let json = serde_json::to_string(&tag).unwrap();
434 assert!(json.contains(r#""type":"error:timeout"#));
435 let decoded: SignalTag = serde_json::from_str(&json).unwrap();
436 assert_eq!(decoded.signal_type, "error:timeout");
437 assert_eq!(decoded.provider.as_deref(), Some("openai"));
438 }
439
440 #[test]
441 fn signal_tag_minimal() {
442 let json_str = r#"{"type": "task.completed"}"#;
443 let tag: SignalTag = serde_json::from_str(json_str).unwrap();
444 assert_eq!(tag.signal_type, "task.completed");
445 assert!(tag.provider.is_none());
446 }
447
448 #[test]
451 fn gene_roundtrip() {
452 let gene = Gene {
453 id: "gene-1".to_string(),
454 category: Some("error-handling".to_string()),
455 title: Some("Timeout Fix".to_string()),
456 signals_match: Some(vec![json!("error:timeout")]),
457 strategy: Some(vec!["increase timeout".to_string()]),
458 visibility: Some("public".to_string()),
459 success_count: Some(10),
460 failure_count: Some(2),
461 };
462 let json = serde_json::to_string(&gene).unwrap();
463 let decoded: Gene = serde_json::from_str(&json).unwrap();
464 assert_eq!(decoded.id, "gene-1");
465 assert_eq!(decoded.strategy.as_ref().unwrap()[0], "increase timeout");
466 }
467
468 #[test]
469 fn gene_clone() {
470 let gene = Gene {
471 id: "g1".to_string(),
472 category: None,
473 title: None,
474 signals_match: None,
475 strategy: None,
476 visibility: None,
477 success_count: None,
478 failure_count: None,
479 };
480 let cloned = gene.clone();
481 assert_eq!(cloned.id, "g1");
482 }
483
484 #[test]
487 fn evolution_advice_roundtrip() {
488 let advice = EvolutionAdvice {
489 action: "apply_gene".to_string(),
490 gene: Some(Gene {
491 id: "g1".into(), category: None, title: None,
492 signals_match: None, strategy: None, visibility: None,
493 success_count: None, failure_count: None,
494 }),
495 confidence: Some(0.85),
496 signals: Some(vec![json!({"type": "error:timeout"})]),
497 };
498 let json = serde_json::to_string(&advice).unwrap();
499 let decoded: EvolutionAdvice = serde_json::from_str(&json).unwrap();
500 assert_eq!(decoded.action, "apply_gene");
501 assert_eq!(decoded.confidence, Some(0.85));
502 }
503
504 #[test]
507 fn message_new_payload_roundtrip() {
508 let payload = MessageNewPayload {
509 id: "msg-1".to_string(),
510 conversation_id: "conv-1".to_string(),
511 content: "Hello".to_string(),
512 msg_type: "text".to_string(),
513 sender_id: "user-1".to_string(),
514 routing: None,
515 metadata: Some(json!({"key": "value"})),
516 created_at: "2026-01-01T00:00:00Z".to_string(),
517 };
518 let json = serde_json::to_string(&payload).unwrap();
519 assert!(json.contains("conversationId"));
520 assert!(json.contains("senderId"));
521 assert!(json.contains("createdAt"));
522 let decoded: MessageNewPayload = serde_json::from_str(&json).unwrap();
523 assert_eq!(decoded.id, "msg-1");
524 assert_eq!(decoded.conversation_id, "conv-1");
525 }
526
527 #[test]
530 fn message_edit_payload_roundtrip() {
531 let payload = MessageEditPayload {
532 id: "msg-1".to_string(),
533 conversation_id: "conv-1".to_string(),
534 content: "Edited".to_string(),
535 msg_type: "text".to_string(),
536 edited_at: "2026-01-01T01:00:00Z".to_string(),
537 edited_by: "user-2".to_string(),
538 metadata: None,
539 };
540 let json = serde_json::to_string(&payload).unwrap();
541 assert!(json.contains("editedAt"));
542 assert!(json.contains("editedBy"));
543 let decoded: MessageEditPayload = serde_json::from_str(&json).unwrap();
544 assert_eq!(decoded.edited_by, "user-2");
545 }
546
547 #[test]
550 fn message_deleted_payload_roundtrip() {
551 let payload = MessageDeletedPayload {
552 id: "msg-99".to_string(),
553 conversation_id: "conv-5".to_string(),
554 };
555 let json = serde_json::to_string(&payload).unwrap();
556 let decoded: MessageDeletedPayload = serde_json::from_str(&json).unwrap();
557 assert_eq!(decoded.id, "msg-99");
558 assert_eq!(decoded.conversation_id, "conv-5");
559 }
560}