1use crate::activations::arbor::{NodeId, TreeId};
2use plexus_macros::HandleEnum;
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use uuid::Uuid;
7
8use super::activation::ClaudeCode;
9
10pub type ClaudeCodeId = Uuid;
12
13#[derive(Debug, Clone, HandleEnum)]
22#[handle(plugin_id = "ClaudeCode::PLUGIN_ID", version = "1.0.0")]
23pub enum ClaudeCodeHandle {
24 #[handle(
27 method = "chat",
28 table = "messages",
29 key = "id",
30 key_field = "message_id",
31 strip_prefix = "msg-"
32 )]
33 Message {
34 message_id: String,
36 role: String,
38 name: String,
40 },
41
42 #[handle(method = "passthrough")]
46 Passthrough {
47 event_id: String,
49 event_type: String,
51 },
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
60#[serde(tag = "type")]
61pub enum ResolveResult {
62 #[serde(rename = "resolved_message")]
64 Message {
65 id: String,
66 role: String,
67 content: String,
68 model: Option<String>,
69 name: String,
70 },
71 #[serde(rename = "error")]
73 Error { message: String },
74}
75
76pub type StreamId = Uuid;
78
79pub type MessageId = Uuid;
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
84#[serde(rename_all = "snake_case")]
85pub enum MessageRole {
86 User,
87 Assistant,
88 System,
89}
90
91impl MessageRole {
92 pub fn as_str(&self) -> &'static str {
93 match self {
94 MessageRole::User => "user",
95 MessageRole::Assistant => "assistant",
96 MessageRole::System => "system",
97 }
98 }
99
100 pub fn from_str(s: &str) -> Option<Self> {
101 match s {
102 "user" => Some(MessageRole::User),
103 "assistant" => Some(MessageRole::Assistant),
104 "system" => Some(MessageRole::System),
105 _ => None,
106 }
107 }
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
112#[serde(rename_all = "lowercase")]
113pub enum Model {
114 Opus,
115 Sonnet,
116 Haiku,
117}
118
119impl Model {
120 pub fn as_str(&self) -> &'static str {
121 match self {
122 Model::Opus => "opus",
123 Model::Sonnet => "sonnet",
124 Model::Haiku => "haiku",
125 }
126 }
127
128 pub fn from_str(s: &str) -> Option<Self> {
129 match s.to_lowercase().as_str() {
130 "opus" => Some(Model::Opus),
131 "sonnet" => Some(Model::Sonnet),
132 "haiku" => Some(Model::Haiku),
133 _ => None,
134 }
135 }
136}
137
138#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
141pub struct Position {
142 pub tree_id: TreeId,
144 pub node_id: NodeId,
146}
147
148impl Position {
149 pub fn new(tree_id: TreeId, node_id: NodeId) -> Self {
151 Self { tree_id, node_id }
152 }
153
154 pub fn advance(&self, new_node_id: NodeId) -> Self {
156 Self {
157 tree_id: self.tree_id,
158 node_id: new_node_id,
159 }
160 }
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
165pub struct Message {
166 pub id: MessageId,
167 pub session_id: ClaudeCodeId,
168 pub role: MessageRole,
169 pub content: String,
170 pub created_at: i64,
171 pub model_id: Option<String>,
173 pub input_tokens: Option<i64>,
175 pub output_tokens: Option<i64>,
176 pub cost_usd: Option<f64>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
182pub struct ClaudeCodeConfig {
183 pub id: ClaudeCodeId,
185 pub name: String,
187 pub claude_session_id: Option<String>,
189 pub head: Position,
191 pub working_dir: String,
193 pub model: Model,
195 pub system_prompt: Option<String>,
197 pub mcp_config: Option<Value>,
199 pub loopback_enabled: bool,
201 pub metadata: Option<Value>,
203 pub created_at: i64,
205 pub updated_at: i64,
207}
208
209impl ClaudeCodeConfig {
210 pub fn tree_id(&self) -> TreeId {
212 self.head.tree_id
213 }
214
215 pub fn node_id(&self) -> NodeId {
217 self.head.node_id
218 }
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
223pub struct ClaudeCodeInfo {
224 pub id: ClaudeCodeId,
225 pub name: String,
226 pub model: Model,
227 pub head: Position,
228 pub claude_session_id: Option<String>,
229 pub working_dir: String,
230 pub loopback_enabled: bool,
231 pub created_at: i64,
232}
233
234impl From<&ClaudeCodeConfig> for ClaudeCodeInfo {
235 fn from(config: &ClaudeCodeConfig) -> Self {
236 Self {
237 id: config.id,
238 name: config.name.clone(),
239 model: config.model,
240 head: config.head,
241 claude_session_id: config.claude_session_id.clone(),
242 working_dir: config.working_dir.clone(),
243 loopback_enabled: config.loopback_enabled,
244 created_at: config.created_at,
245 }
246 }
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
251pub struct ChatUsage {
252 pub input_tokens: Option<u64>,
253 pub output_tokens: Option<u64>,
254 pub cost_usd: Option<f64>,
255 pub num_turns: Option<i32>,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
264#[serde(rename_all = "snake_case")]
265pub enum StreamStatus {
266 Running,
268 AwaitingPermission,
270 Complete,
272 Failed,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
278pub struct StreamInfo {
279 pub stream_id: StreamId,
281 pub session_id: ClaudeCodeId,
283 pub status: StreamStatus,
285 pub user_position: Option<Position>,
287 pub event_count: u64,
289 pub read_position: u64,
291 pub started_at: i64,
293 pub ended_at: Option<i64>,
295 pub error: Option<String>,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
301pub struct BufferedEvent {
302 pub seq: u64,
304 pub event: ChatEvent,
306 pub timestamp: i64,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
317#[serde(tag = "type", rename_all = "snake_case")]
318pub enum CreateResult {
319 #[serde(rename = "created")]
320 Ok {
321 id: ClaudeCodeId,
322 head: Position,
323 },
324 #[serde(rename = "error")]
325 Err { message: String },
326}
327
328#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
330#[serde(tag = "type", rename_all = "snake_case")]
331pub enum GetResult {
332 #[serde(rename = "ok")]
333 Ok { config: ClaudeCodeConfig },
334 #[serde(rename = "error")]
335 Err { message: String },
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
340#[serde(tag = "type", rename_all = "snake_case")]
341pub enum ListResult {
342 #[serde(rename = "ok")]
343 Ok { sessions: Vec<ClaudeCodeInfo> },
344 #[serde(rename = "error")]
345 Err { message: String },
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
350#[serde(tag = "type", rename_all = "snake_case")]
351pub enum DeleteResult {
352 #[serde(rename = "deleted")]
353 Ok { id: ClaudeCodeId },
354 #[serde(rename = "error")]
355 Err { message: String },
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
360#[serde(tag = "type", rename_all = "snake_case")]
361pub enum ForkResult {
362 #[serde(rename = "forked")]
363 Ok {
364 id: ClaudeCodeId,
365 head: Position,
366 },
367 #[serde(rename = "error")]
368 Err { message: String },
369}
370
371#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
373#[serde(tag = "type", rename_all = "snake_case")]
374pub enum ChatStartResult {
375 #[serde(rename = "started")]
376 Ok {
377 stream_id: StreamId,
378 session_id: ClaudeCodeId,
379 },
380 #[serde(rename = "error")]
381 Err { message: String },
382}
383
384#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
386#[serde(tag = "type", rename_all = "snake_case")]
387pub enum PollResult {
388 #[serde(rename = "ok")]
389 Ok {
390 status: StreamStatus,
392 events: Vec<BufferedEvent>,
394 read_position: u64,
396 total_events: u64,
398 has_more: bool,
400 },
401 #[serde(rename = "error")]
402 Err { message: String },
403}
404
405#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
407#[serde(tag = "type", rename_all = "snake_case")]
408pub enum StreamListResult {
409 #[serde(rename = "ok")]
410 Ok { streams: Vec<StreamInfo> },
411 #[serde(rename = "error")]
412 Err { message: String },
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
421#[serde(tag = "type", rename_all = "snake_case")]
422pub enum ChatEvent {
423 #[serde(rename = "start")]
425 Start {
426 id: ClaudeCodeId,
427 user_position: Position,
428 },
429
430 #[serde(rename = "content")]
432 Content { text: String },
433
434 #[serde(rename = "thinking")]
436 Thinking { thinking: String },
437
438 #[serde(rename = "tool_use")]
440 ToolUse {
441 tool_name: String,
442 tool_use_id: String,
443 input: Value,
444 },
445
446 #[serde(rename = "tool_result")]
448 ToolResult {
449 tool_use_id: String,
450 output: String,
451 is_error: bool,
452 },
453
454 #[serde(rename = "complete")]
456 Complete {
457 new_head: Position,
458 claude_session_id: String,
459 usage: Option<ChatUsage>,
460 },
461
462 #[serde(rename = "passthrough")]
465 Passthrough {
466 event_type: String,
467 handle: String,
468 data: Value,
469 },
470
471 #[serde(rename = "error")]
473 Err { message: String },
474}
475
476#[derive(Debug, Clone)]
478pub struct ClaudeCodeError {
479 pub message: String,
480}
481
482impl std::fmt::Display for ClaudeCodeError {
483 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
484 write!(f, "{}", self.message)
485 }
486}
487
488impl std::error::Error for ClaudeCodeError {}
489
490impl From<String> for ClaudeCodeError {
491 fn from(s: String) -> Self {
492 Self { message: s }
493 }
494}
495
496impl From<&str> for ClaudeCodeError {
497 fn from(s: &str) -> Self {
498 Self { message: s.to_string() }
499 }
500}
501
502#[derive(Debug, Clone, Deserialize)]
508#[serde(tag = "type")]
509pub enum RawClaudeEvent {
510 #[serde(rename = "system")]
512 System {
513 subtype: Option<String>,
514 #[serde(rename = "session_id")]
515 session_id: Option<String>,
516 model: Option<String>,
517 cwd: Option<String>,
518 tools: Option<Vec<String>>,
519 },
520
521 #[serde(rename = "assistant")]
523 Assistant {
524 message: Option<RawMessage>,
525 },
526
527 #[serde(rename = "user")]
529 User {
530 message: Option<RawMessage>,
531 },
532
533 #[serde(rename = "result")]
535 Result {
536 subtype: Option<String>,
537 session_id: Option<String>,
538 cost_usd: Option<f64>,
539 is_error: Option<bool>,
540 duration_ms: Option<i64>,
541 num_turns: Option<i32>,
542 result: Option<String>,
543 error: Option<String>,
544 },
545
546 #[serde(rename = "stream_event")]
548 StreamEvent {
549 event: StreamEventInner,
550 session_id: Option<String>,
551 },
552
553 #[serde(skip)]
556 Unknown {
557 event_type: String,
558 data: Value,
559 },
560}
561
562#[derive(Debug, Clone, Deserialize)]
564#[serde(tag = "type")]
565pub enum StreamEventInner {
566 #[serde(rename = "message_start")]
567 MessageStart {
568 message: Option<StreamMessage>,
569 },
570
571 #[serde(rename = "content_block_start")]
572 ContentBlockStart {
573 index: usize,
574 content_block: Option<StreamContentBlock>,
575 },
576
577 #[serde(rename = "content_block_delta")]
578 ContentBlockDelta {
579 index: usize,
580 delta: StreamDelta,
581 },
582
583 #[serde(rename = "content_block_stop")]
584 ContentBlockStop {
585 index: usize,
586 },
587
588 #[serde(rename = "message_delta")]
589 MessageDelta {
590 delta: MessageDeltaInfo,
591 },
592
593 #[serde(rename = "message_stop")]
594 MessageStop,
595}
596
597#[derive(Debug, Clone, Deserialize)]
598pub struct StreamMessage {
599 pub model: Option<String>,
600 pub role: Option<String>,
601}
602
603#[derive(Debug, Clone, Deserialize)]
604#[serde(tag = "type")]
605pub enum StreamContentBlock {
606 #[serde(rename = "text")]
607 Text { text: Option<String> },
608
609 #[serde(rename = "tool_use")]
610 ToolUse {
611 id: String,
612 name: String,
613 input: Option<Value>,
614 },
615}
616
617#[derive(Debug, Clone, Deserialize)]
618#[serde(tag = "type")]
619pub enum StreamDelta {
620 #[serde(rename = "text_delta")]
621 TextDelta { text: String },
622
623 #[serde(rename = "input_json_delta")]
624 InputJsonDelta { partial_json: String },
625}
626
627#[derive(Debug, Clone, Deserialize)]
628pub struct MessageDeltaInfo {
629 pub stop_reason: Option<String>,
630 pub stop_sequence: Option<String>,
631}
632
633#[derive(Debug, Clone, Deserialize)]
634pub struct RawMessage {
635 pub id: Option<String>,
636 pub role: Option<String>,
637 pub model: Option<String>,
638 pub content: Option<Vec<RawContentBlock>>,
639}
640
641#[derive(Debug, Clone, Deserialize)]
642#[serde(tag = "type")]
643pub enum RawContentBlock {
644 #[serde(rename = "text")]
645 Text { text: String },
646
647 #[serde(rename = "thinking")]
648 Thinking {
649 thinking: String,
650 #[serde(default)]
651 signature: Option<String>,
652 },
653
654 #[serde(rename = "tool_use")]
655 ToolUse {
656 id: String,
657 name: String,
658 input: Value,
659 },
660
661 #[serde(rename = "tool_result")]
662 ToolResult {
663 tool_use_id: String,
664 content: Option<String>,
665 is_error: Option<bool>,
666 },
667}