1#![allow(dead_code)]
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use utoipa::{
6 Modify, OpenApi, ToSchema,
7 openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme},
8};
9use uuid::Uuid;
10
11#[derive(Debug, Serialize, ToSchema)]
12pub struct HealthResponseDoc {
13 pub status: String,
14 pub version: String,
15 pub uptime_seconds: u64,
16}
17
18#[derive(Debug, Serialize, ToSchema)]
19pub struct ErrorResponseDoc {
20 pub error: String,
21 pub code: String,
22 pub request_id: String,
23}
24
25#[derive(Debug, Serialize, Deserialize, ToSchema)]
26#[serde(rename_all = "snake_case")]
27pub enum RunStateDoc {
28 Idle,
29 Starting,
30 Running,
31 Failed,
32}
33
34#[derive(Debug, Serialize, Deserialize, ToSchema)]
35pub struct RunStatusDoc {
36 pub state: RunStateDoc,
37 pub run_id: Option<Uuid>,
38}
39
40#[derive(Debug, Serialize, Deserialize, ToSchema)]
41pub struct SessionDoc {
42 pub id: Uuid,
43 pub title: String,
44 pub cwd: Option<String>,
45 pub created_at: chrono::DateTime<chrono::Utc>,
46 pub updated_at: chrono::DateTime<chrono::Utc>,
47 pub run_status: RunStatusDoc,
48}
49
50#[derive(Debug, Serialize, Deserialize, ToSchema)]
51pub struct SessionsResponseDoc {
52 pub sessions: Vec<SessionDoc>,
53 pub total: usize,
54}
55
56#[derive(Debug, Serialize, Deserialize, ToSchema)]
57pub struct SessionDetailResponseDoc {
58 pub session: SessionDoc,
59 pub config: ConfigResponseDoc,
60}
61
62#[derive(Debug, Serialize, Deserialize, ToSchema)]
63pub struct CreateSessionBodyDoc {
64 pub title: String,
65 pub cwd: Option<String>,
66}
67
68#[derive(Debug, Serialize, Deserialize, ToSchema)]
69#[serde(rename_all = "UPPERCASE")]
70pub enum SessionVisibilityDoc {
71 Private,
72 Public,
73}
74
75#[derive(Debug, Serialize, Deserialize, ToSchema)]
76pub struct UpdateSessionBodyDoc {
77 pub title: Option<String>,
78 pub visibility: Option<SessionVisibilityDoc>,
79}
80
81#[derive(Debug, Serialize, Deserialize, ToSchema)]
82#[serde(rename_all = "snake_case")]
83pub enum SessionMessageTypeDoc {
84 Message,
85 Steering,
86 FollowUp,
87}
88
89#[derive(Debug, Serialize, Deserialize, ToSchema)]
90#[serde(rename_all = "lowercase")]
91pub enum MessageRoleDoc {
92 System,
93 User,
94 Assistant,
95 Tool,
96}
97
98#[derive(Debug, Serialize, Deserialize, ToSchema)]
99#[serde(rename_all = "lowercase")]
100pub enum ImageDetailDoc {
101 Low,
102 High,
103 Auto,
104}
105
106#[derive(Debug, Serialize, Deserialize, ToSchema)]
107#[serde(tag = "type", rename_all = "lowercase")]
108pub enum CacheControlDoc {
109 Ephemeral {
110 #[serde(skip_serializing_if = "Option::is_none")]
111 ttl: Option<String>,
112 },
113}
114
115#[derive(Debug, Serialize, Deserialize, ToSchema)]
116pub struct AnthropicMessageOptionsDoc {
117 #[serde(skip_serializing_if = "Option::is_none")]
118 pub cache_control: Option<CacheControlDoc>,
119}
120
121#[derive(Debug, Serialize, Deserialize, ToSchema)]
122pub struct MessageProviderOptionsDoc {
123 #[serde(skip_serializing_if = "Option::is_none")]
124 pub anthropic: Option<AnthropicMessageOptionsDoc>,
125}
126
127#[derive(Debug, Serialize, Deserialize, ToSchema)]
128pub struct AnthropicContentPartOptionsDoc {
129 #[serde(skip_serializing_if = "Option::is_none")]
130 pub cache_control: Option<CacheControlDoc>,
131}
132
133#[derive(Debug, Serialize, Deserialize, ToSchema)]
134pub struct ContentPartProviderOptionsDoc {
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub anthropic: Option<AnthropicContentPartOptionsDoc>,
137}
138
139#[derive(Debug, Serialize, Deserialize, ToSchema)]
140#[serde(tag = "type", rename_all = "snake_case")]
141pub enum ContentPartDoc {
142 Text {
143 text: String,
144 #[serde(skip_serializing_if = "Option::is_none")]
145 provider_options: Option<ContentPartProviderOptionsDoc>,
146 },
147 Image {
148 url: String,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 detail: Option<ImageDetailDoc>,
151 #[serde(skip_serializing_if = "Option::is_none")]
152 provider_options: Option<ContentPartProviderOptionsDoc>,
153 },
154 ToolCall {
155 id: String,
156 name: String,
157 #[schema(value_type = Object)]
158 arguments: serde_json::Value,
159 #[serde(skip_serializing_if = "Option::is_none")]
160 provider_options: Option<ContentPartProviderOptionsDoc>,
161 #[serde(skip_serializing_if = "Option::is_none")]
162 #[schema(value_type = Object)]
163 metadata: Option<serde_json::Value>,
164 },
165 ToolResult {
166 tool_call_id: String,
167 #[schema(value_type = Object)]
168 content: serde_json::Value,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 provider_options: Option<ContentPartProviderOptionsDoc>,
171 },
172}
173
174#[derive(Debug, Serialize, Deserialize, ToSchema)]
175#[serde(untagged)]
176pub enum MessageContentDoc {
177 Text(String),
178 Parts(Vec<ContentPartDoc>),
179}
180
181#[derive(Debug, Serialize, Deserialize, ToSchema)]
182pub struct StakaiMessageDoc {
183 pub role: MessageRoleDoc,
184 pub content: MessageContentDoc,
185 pub name: Option<String>,
186 pub provider_options: Option<MessageProviderOptionsDoc>,
187}
188
189#[derive(Debug, Serialize, Deserialize, ToSchema)]
190pub struct CallerContextInputDoc {
191 pub name: String,
192 pub content: String,
193 pub priority: Option<String>,
194}
195
196#[derive(Debug, Serialize, Deserialize, ToSchema)]
197pub struct SessionMessageRequestDoc {
198 pub message: StakaiMessageDoc,
199 #[serde(rename = "type")]
200 pub message_type: Option<SessionMessageTypeDoc>,
201 pub run_id: Option<Uuid>,
202 pub model: Option<String>,
203 pub sandbox: Option<bool>,
204 pub context: Option<Vec<CallerContextInputDoc>>,
205}
206
207#[derive(Debug, Serialize, Deserialize, ToSchema)]
208pub struct SessionMessageResponseDoc {
209 pub run_id: Uuid,
210}
211
212#[derive(Debug, Serialize, Deserialize, ToSchema)]
213pub struct SessionMessagesResponseDoc {
214 pub messages: Vec<StakaiMessageDoc>,
215 pub total: usize,
216}
217
218#[derive(Debug, Serialize, Deserialize, ToSchema)]
219pub struct ProposedToolCallDoc {
220 pub id: String,
221 pub name: String,
222 #[schema(value_type = Object)]
223 pub arguments: serde_json::Value,
224 #[schema(value_type = Object)]
225 pub metadata: Option<serde_json::Value>,
226}
227
228#[derive(Debug, Serialize, Deserialize, ToSchema)]
229pub struct PendingToolsResponseDoc {
230 pub run_id: Option<Uuid>,
231 pub tool_calls: Vec<ProposedToolCallDoc>,
232}
233
234#[derive(Debug, Serialize, Deserialize, ToSchema)]
235#[serde(rename_all = "snake_case")]
236pub enum DecisionActionDoc {
237 Accept,
238 Reject,
239 CustomResult,
240}
241
242#[derive(Debug, Serialize, Deserialize, ToSchema)]
243pub struct DecisionInputDoc {
244 pub action: DecisionActionDoc,
245 pub content: Option<String>,
246}
247
248#[derive(Debug, Serialize, Deserialize, ToSchema)]
249pub struct ToolDecisionRequestDoc {
250 pub run_id: Uuid,
251 #[serde(flatten)]
252 pub decision: DecisionInputDoc,
253}
254
255#[derive(Debug, Serialize, Deserialize, ToSchema)]
256pub struct ToolDecisionsRequestDoc {
257 pub run_id: Uuid,
258 pub decisions: HashMap<String, DecisionInputDoc>,
259}
260
261#[derive(Debug, Serialize, Deserialize, ToSchema)]
262pub struct ToolDecisionResponseDoc {
263 pub accepted: bool,
264 pub run_id: Uuid,
265}
266
267#[derive(Debug, Serialize, Deserialize, ToSchema)]
268pub struct CancelRequestDoc {
269 pub run_id: Uuid,
270}
271
272#[derive(Debug, Serialize, Deserialize, ToSchema)]
273pub struct CancelResponseDoc {
274 pub cancelled: bool,
275 pub run_id: Uuid,
276}
277
278#[derive(Debug, Serialize, Deserialize, ToSchema)]
279pub struct ModelSwitchRequestDoc {
280 pub run_id: Uuid,
281 pub model: String,
282}
283
284#[derive(Debug, Serialize, Deserialize, ToSchema)]
285pub struct ModelSwitchResponseDoc {
286 pub accepted: bool,
287 pub run_id: Uuid,
288 pub model: String,
289}
290
291#[derive(Debug, Serialize, Deserialize, ToSchema)]
292#[serde(rename_all = "snake_case")]
293pub enum AutoApproveModeDoc {
294 None,
295 All,
296 Custom,
297}
298
299#[derive(Debug, Serialize, Deserialize, ToSchema)]
300pub struct ConfigResponseDoc {
301 pub default_model: Option<String>,
302 pub auto_approve_mode: AutoApproveModeDoc,
303}
304
305#[derive(Debug, Serialize, Deserialize, ToSchema)]
306pub struct ModelCostDoc {
307 pub input: f64,
308 pub output: f64,
309 pub cache_read: Option<f64>,
310 pub cache_write: Option<f64>,
311}
312
313#[derive(Debug, Serialize, Deserialize, ToSchema)]
314pub struct ModelLimitDoc {
315 pub context: u64,
316 pub output: u64,
317}
318
319#[derive(Debug, Serialize, Deserialize, ToSchema)]
320pub struct ModelDoc {
321 pub id: String,
322 pub name: String,
323 pub provider: String,
324 pub reasoning: bool,
325 pub cost: Option<ModelCostDoc>,
326 pub limit: ModelLimitDoc,
327 pub release_date: Option<String>,
328}
329
330#[derive(Debug, Serialize, Deserialize, ToSchema)]
331pub struct ModelsResponseDoc {
332 pub models: Vec<ModelDoc>,
333}
334
335#[derive(Debug, Serialize, Deserialize, ToSchema)]
336pub struct EventEnvelopeDoc {
337 pub id: u64,
338 pub session_id: Uuid,
339 pub run_id: Option<Uuid>,
340 pub timestamp: chrono::DateTime<chrono::Utc>,
341 #[schema(value_type = Object)]
342 pub event: serde_json::Value,
343}
344
345#[derive(Debug, Serialize, Deserialize, ToSchema)]
346pub struct GapDetectedDoc {
347 pub requested_after_id: u64,
348 pub oldest_available_id: u64,
349 pub newest_available_id: u64,
350 pub resume_hint: String,
351}
352
353#[utoipa::path(
354 get,
355 path = "/v1/health",
356 responses((status = 200, body = HealthResponseDoc))
357)]
358fn health_doc() {}
359
360#[utoipa::path(
361 get,
362 path = "/v1/openapi.json",
363 responses((status = 200, description = "OpenAPI 3.1 specification document"))
364)]
365fn openapi_doc() {}
366
367#[utoipa::path(
368 get,
369 path = "/v1/sessions",
370 security(("bearer_auth" = [])),
371 params(
372 ("limit" = Option<u32>, Query, description = "page size"),
373 ("offset" = Option<u32>, Query, description = "page offset"),
374 ("search" = Option<String>, Query, description = "title query"),
375 ("status" = Option<String>, Query, description = "ACTIVE or DELETED")
376 ),
377 responses(
378 (status = 200, body = SessionsResponseDoc),
379 (status = 401, body = ErrorResponseDoc)
380 )
381)]
382fn list_sessions_doc() {}
383
384#[utoipa::path(
385 post,
386 path = "/v1/sessions",
387 security(("bearer_auth" = [])),
388 request_body = CreateSessionBodyDoc,
389 responses(
390 (status = 201, body = SessionDoc),
391 (status = 401, body = ErrorResponseDoc),
392 (status = 409, body = ErrorResponseDoc)
393 )
394)]
395fn create_session_doc() {}
396
397#[utoipa::path(
398 get,
399 path = "/v1/sessions/{id}",
400 security(("bearer_auth" = [])),
401 params(("id" = Uuid, Path, description = "session id")),
402 responses(
403 (status = 200, body = SessionDetailResponseDoc),
404 (status = 401, body = ErrorResponseDoc),
405 (status = 404, body = ErrorResponseDoc)
406 )
407)]
408fn get_session_doc() {}
409
410#[utoipa::path(
411 patch,
412 path = "/v1/sessions/{id}",
413 security(("bearer_auth" = [])),
414 params(("id" = Uuid, Path, description = "session id")),
415 request_body = UpdateSessionBodyDoc,
416 responses(
417 (status = 200, body = SessionDetailResponseDoc),
418 (status = 401, body = ErrorResponseDoc),
419 (status = 404, body = ErrorResponseDoc)
420 )
421)]
422fn update_session_doc() {}
423
424#[utoipa::path(
425 delete,
426 path = "/v1/sessions/{id}",
427 security(("bearer_auth" = [])),
428 params(("id" = Uuid, Path, description = "session id")),
429 responses(
430 (status = 204, description = "Session deleted"),
431 (status = 401, body = ErrorResponseDoc),
432 (status = 404, body = ErrorResponseDoc)
433 )
434)]
435fn delete_session_doc() {}
436
437#[utoipa::path(
438 post,
439 path = "/v1/sessions/{id}/messages",
440 security(("bearer_auth" = [])),
441 params(("id" = Uuid, Path, description = "session id")),
442 request_body = SessionMessageRequestDoc,
443 responses(
444 (status = 200, body = SessionMessageResponseDoc),
445 (status = 401, body = ErrorResponseDoc),
446 (status = 404, body = ErrorResponseDoc),
447 (status = 409, body = ErrorResponseDoc)
448 )
449)]
450fn post_messages_doc() {}
451
452#[utoipa::path(
453 get,
454 path = "/v1/sessions/{id}/messages",
455 security(("bearer_auth" = [])),
456 params(
457 ("id" = Uuid, Path, description = "session id"),
458 ("limit" = Option<usize>, Query, description = "page size"),
459 ("offset" = Option<usize>, Query, description = "page offset")
460 ),
461 responses(
462 (status = 200, body = SessionMessagesResponseDoc),
463 (status = 401, body = ErrorResponseDoc),
464 (status = 404, body = ErrorResponseDoc)
465 )
466)]
467fn get_messages_doc() {}
468
469#[utoipa::path(
470 get,
471 path = "/v1/sessions/{id}/events",
472 security(("bearer_auth" = [])),
473 params(
474 ("id" = Uuid, Path, description = "session id"),
475 ("Last-Event-ID" = Option<u64>, Header, description = "Replay cursor; stream events with id > Last-Event-ID")
476 ),
477 responses((
478 status = 200,
479 description = "text/event-stream of EventEnvelope frames and optional gap_detected control event",
480 content_type = "text/event-stream"
481 ))
482)]
483fn events_doc() {}
484
485#[utoipa::path(
486 get,
487 path = "/v1/sessions/{id}/tools/pending",
488 security(("bearer_auth" = [])),
489 params(("id" = Uuid, Path, description = "session id")),
490 responses(
491 (status = 200, body = PendingToolsResponseDoc),
492 (status = 401, body = ErrorResponseDoc)
493 )
494)]
495fn pending_tools_doc() {}
496
497#[utoipa::path(
498 post,
499 path = "/v1/sessions/{id}/tools/{tool_call_id}/decision",
500 security(("bearer_auth" = [])),
501 params(
502 ("id" = Uuid, Path, description = "session id"),
503 ("tool_call_id" = String, Path, description = "tool call id")
504 ),
505 request_body = ToolDecisionRequestDoc,
506 responses(
507 (status = 200, body = ToolDecisionResponseDoc),
508 (status = 401, body = ErrorResponseDoc),
509 (status = 409, body = ErrorResponseDoc)
510 )
511)]
512fn tool_decision_doc() {}
513
514#[utoipa::path(
515 post,
516 path = "/v1/sessions/{id}/tools/decisions",
517 security(("bearer_auth" = [])),
518 params(("id" = Uuid, Path, description = "session id")),
519 request_body = ToolDecisionsRequestDoc,
520 responses(
521 (status = 200, body = ToolDecisionResponseDoc),
522 (status = 401, body = ErrorResponseDoc),
523 (status = 409, body = ErrorResponseDoc)
524 )
525)]
526fn tool_decisions_doc() {}
527
528#[utoipa::path(
529 post,
530 path = "/v1/sessions/{id}/tools/resolve",
531 security(("bearer_auth" = [])),
532 params(("id" = Uuid, Path, description = "session id")),
533 request_body = ToolDecisionsRequestDoc,
534 responses(
535 (status = 200, body = ToolDecisionResponseDoc),
536 (status = 401, body = ErrorResponseDoc),
537 (status = 409, body = ErrorResponseDoc)
538 )
539)]
540fn tool_resolve_doc() {}
541
542#[utoipa::path(
543 post,
544 path = "/v1/sessions/{id}/cancel",
545 security(("bearer_auth" = [])),
546 params(("id" = Uuid, Path, description = "session id")),
547 request_body = CancelRequestDoc,
548 responses(
549 (status = 200, body = CancelResponseDoc),
550 (status = 401, body = ErrorResponseDoc),
551 (status = 409, body = ErrorResponseDoc)
552 )
553)]
554fn cancel_doc() {}
555
556#[utoipa::path(
557 post,
558 path = "/v1/sessions/{id}/model",
559 security(("bearer_auth" = [])),
560 params(("id" = Uuid, Path, description = "session id")),
561 request_body = ModelSwitchRequestDoc,
562 responses(
563 (status = 200, body = ModelSwitchResponseDoc),
564 (status = 401, body = ErrorResponseDoc),
565 (status = 409, body = ErrorResponseDoc)
566 )
567)]
568fn switch_model_doc() {}
569
570#[utoipa::path(
571 get,
572 path = "/v1/models",
573 security(("bearer_auth" = [])),
574 responses(
575 (status = 200, body = ModelsResponseDoc),
576 (status = 401, body = ErrorResponseDoc)
577 )
578)]
579fn models_doc() {}
580
581#[utoipa::path(
582 get,
583 path = "/v1/config",
584 security(("bearer_auth" = [])),
585 responses(
586 (status = 200, body = ConfigResponseDoc),
587 (status = 401, body = ErrorResponseDoc)
588 )
589)]
590fn config_doc() {}
591
592struct SecurityAddon;
593
594impl Modify for SecurityAddon {
595 fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
596 let components = openapi.components.get_or_insert_with(Default::default);
597 components.add_security_scheme(
598 "bearer_auth",
599 SecurityScheme::Http(
600 HttpBuilder::new()
601 .scheme(HttpAuthScheme::Bearer)
602 .bearer_format("Bearer")
603 .build(),
604 ),
605 );
606 }
607}
608
609#[derive(OpenApi)]
610#[openapi(
611 paths(
612 health_doc,
613 openapi_doc,
614 list_sessions_doc,
615 create_session_doc,
616 get_session_doc,
617 update_session_doc,
618 delete_session_doc,
619 post_messages_doc,
620 get_messages_doc,
621 events_doc,
622 pending_tools_doc,
623 tool_decision_doc,
624 tool_decisions_doc,
625 tool_resolve_doc,
626 cancel_doc,
627 switch_model_doc,
628 models_doc,
629 config_doc,
630 ),
631 components(
632 schemas(
633 HealthResponseDoc,
634 ErrorResponseDoc,
635 RunStateDoc,
636 RunStatusDoc,
637 SessionDoc,
638 SessionsResponseDoc,
639 SessionDetailResponseDoc,
640 CreateSessionBodyDoc,
641 SessionVisibilityDoc,
642 UpdateSessionBodyDoc,
643 SessionMessageTypeDoc,
644 MessageRoleDoc,
645 ImageDetailDoc,
646 CacheControlDoc,
647 AnthropicMessageOptionsDoc,
648 MessageProviderOptionsDoc,
649 AnthropicContentPartOptionsDoc,
650 ContentPartProviderOptionsDoc,
651 ContentPartDoc,
652 MessageContentDoc,
653 StakaiMessageDoc,
654 SessionMessageRequestDoc,
655 SessionMessageResponseDoc,
656 SessionMessagesResponseDoc,
657 ProposedToolCallDoc,
658 PendingToolsResponseDoc,
659 DecisionActionDoc,
660 DecisionInputDoc,
661 ToolDecisionRequestDoc,
662 ToolDecisionsRequestDoc,
663 ToolDecisionResponseDoc,
664 CancelRequestDoc,
665 CancelResponseDoc,
666 ModelSwitchRequestDoc,
667 ModelSwitchResponseDoc,
668 AutoApproveModeDoc,
669 ConfigResponseDoc,
670 ModelCostDoc,
671 ModelLimitDoc,
672 ModelDoc,
673 ModelsResponseDoc,
674 EventEnvelopeDoc,
675 GapDetectedDoc,
676 )
677 ),
678 modifiers(&SecurityAddon),
679 tags(
680 (name = "server", description = "Stakpak server runtime APIs")
681 )
682)]
683pub struct ApiDoc;
684
685pub fn generate_openapi() -> utoipa::openapi::OpenApi {
686 ApiDoc::openapi()
687}