Skip to main content

nenjo_tool_api/
async_ops.rs

1//! Shared contracts for model-visible async operation tools.
2//!
3//! Runtime crates own operation scheduling, cancellation, polling, and event
4//! delivery. This module only defines stable tool names, argument DTOs, JSON
5//! schemas, and operation lifecycle enums.
6
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9
10pub const WAIT_OPERATIONS_TOOL_NAME: &str = "wait_operations";
11pub const INSPECT_OPERATIONS_TOOL_NAME: &str = "inspect_operations";
12pub const STOP_OPERATIONS_TOOL_NAME: &str = "stop_operations";
13pub const SEND_OPERATION_INPUT_TOOL_NAME: &str = "send_operation_input";
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17pub enum AsyncOperationKind {
18    Ability,
19    SubAgent,
20    Shell,
21    Media,
22}
23
24impl AsyncOperationKind {
25    pub fn as_str(self) -> &'static str {
26        match self {
27            Self::Ability => "ability",
28            Self::SubAgent => "sub_agent",
29            Self::Shell => "shell",
30            Self::Media => "media",
31        }
32    }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "snake_case")]
37pub enum AsyncOperationStatus {
38    Running,
39    WaitingForInput,
40    Completed,
41    Failed,
42    Stopped,
43}
44
45impl AsyncOperationStatus {
46    pub fn as_str(self) -> &'static str {
47        match self {
48            Self::Running => "running",
49            Self::WaitingForInput => "waiting_for_input",
50            Self::Completed => "completed",
51            Self::Failed => "failed",
52            Self::Stopped => "stopped",
53        }
54    }
55
56    pub fn can_receive_input(self) -> bool {
57        matches!(self, Self::Running | Self::WaitingForInput)
58    }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
62#[serde(rename_all = "snake_case")]
63pub enum AsyncOperationSignalKind {
64    Started,
65    Progress,
66    NeedsInput,
67    Completed,
68    Failed,
69    Stopped,
70}
71
72impl AsyncOperationSignalKind {
73    pub fn as_str(self) -> &'static str {
74        match self {
75            Self::Started => "started",
76            Self::Progress => "progress",
77            Self::NeedsInput => "needs_input",
78            Self::Completed => "completed",
79            Self::Failed => "failed",
80            Self::Stopped => "stopped",
81        }
82    }
83}
84
85#[derive(Debug, Clone, Deserialize)]
86pub struct InspectOperationsArgs {
87    #[serde(default)]
88    pub operations: Vec<String>,
89    #[serde(default)]
90    pub kind: Option<AsyncOperationKind>,
91    #[serde(default)]
92    pub include_transcript: bool,
93    #[serde(default = "default_inspect_limit")]
94    pub limit: usize,
95}
96
97#[derive(Debug, Clone, Deserialize)]
98pub struct StopOperationsArgs {
99    #[serde(default)]
100    pub operations: Vec<String>,
101    #[serde(default)]
102    pub kind: Option<AsyncOperationKind>,
103    pub reason: Option<String>,
104}
105
106#[derive(Debug, Clone, Deserialize)]
107pub struct WaitOperationsArgs {
108    #[serde(default = "default_wait_seconds")]
109    pub seconds: u64,
110    #[serde(default)]
111    pub kind: Option<AsyncOperationKind>,
112    pub reason: Option<String>,
113}
114
115#[derive(Debug, Clone, Deserialize)]
116pub struct SendOperationInputArgs {
117    #[serde(default)]
118    pub operations: Vec<String>,
119    pub message: String,
120}
121
122pub fn inspect_operations_parameters_schema() -> serde_json::Value {
123    json!({
124        "type": "object",
125        "properties": {
126            "operations": {"type": "array", "items": {"type": "string"}},
127            "kind": operation_kind_schema(),
128            "include_transcript": {"type": "boolean"},
129            "limit": {"type": "number", "minimum": 1, "maximum": 50}
130        },
131        "additionalProperties": false
132    })
133}
134
135pub fn stop_operations_parameters_schema() -> serde_json::Value {
136    json!({
137        "type": "object",
138        "properties": {
139            "operations": {"type": "array", "items": {"type": "string"}},
140            "kind": operation_kind_schema(),
141            "reason": {"type": "string"}
142        },
143        "additionalProperties": false
144    })
145}
146
147pub fn wait_operations_parameters_schema() -> serde_json::Value {
148    json!({
149        "type": "object",
150        "properties": {
151            "seconds": {"type": "number", "minimum": 1, "maximum": 30},
152            "kind": operation_kind_schema(),
153            "reason": {"type": "string"}
154        },
155        "additionalProperties": false
156    })
157}
158
159pub fn send_operation_input_parameters_schema() -> serde_json::Value {
160    json!({
161        "type": "object",
162        "properties": {
163            "operations": {"type": "array", "items": {"type": "string"}},
164            "message": {"type": "string"}
165        },
166        "required": ["operations", "message"],
167        "additionalProperties": false
168    })
169}
170
171pub fn operation_kind_schema() -> serde_json::Value {
172    json!({
173        "type": "string",
174        "enum": ["ability", "sub_agent", "shell", "media"]
175    })
176}
177
178fn default_inspect_limit() -> usize {
179    30
180}
181
182fn default_wait_seconds() -> u64 {
183    10
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn async_operation_kind_uses_wire_names() {
192        assert_eq!(AsyncOperationKind::Ability.as_str(), "ability");
193        assert_eq!(AsyncOperationKind::SubAgent.as_str(), "sub_agent");
194        assert_eq!(AsyncOperationKind::Shell.as_str(), "shell");
195        assert_eq!(AsyncOperationKind::Media.as_str(), "media");
196    }
197
198    #[test]
199    fn wait_args_deserialize_with_defaults() {
200        let args: WaitOperationsArgs = serde_json::from_value(json!({})).unwrap();
201
202        assert_eq!(args.seconds, 10);
203        assert_eq!(args.kind, None);
204    }
205}