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 SessionMessageRequestDoc {
191 pub message: StakaiMessageDoc,
192 #[serde(rename = "type")]
193 pub message_type: Option<SessionMessageTypeDoc>,
194 pub run_id: Option<Uuid>,
195 pub model: Option<String>,
196}
197
198#[derive(Debug, Serialize, Deserialize, ToSchema)]
199pub struct SessionMessageResponseDoc {
200 pub run_id: Uuid,
201}
202
203#[derive(Debug, Serialize, Deserialize, ToSchema)]
204pub struct SessionMessagesResponseDoc {
205 pub messages: Vec<StakaiMessageDoc>,
206 pub total: usize,
207}
208
209#[derive(Debug, Serialize, Deserialize, ToSchema)]
210pub struct ProposedToolCallDoc {
211 pub id: String,
212 pub name: String,
213 #[schema(value_type = Object)]
214 pub arguments: serde_json::Value,
215 #[schema(value_type = Object)]
216 pub metadata: Option<serde_json::Value>,
217}
218
219#[derive(Debug, Serialize, Deserialize, ToSchema)]
220pub struct PendingToolsResponseDoc {
221 pub run_id: Option<Uuid>,
222 pub tool_calls: Vec<ProposedToolCallDoc>,
223}
224
225#[derive(Debug, Serialize, Deserialize, ToSchema)]
226#[serde(rename_all = "snake_case")]
227pub enum DecisionActionDoc {
228 Accept,
229 Reject,
230 CustomResult,
231}
232
233#[derive(Debug, Serialize, Deserialize, ToSchema)]
234pub struct DecisionInputDoc {
235 pub action: DecisionActionDoc,
236 pub content: Option<String>,
237}
238
239#[derive(Debug, Serialize, Deserialize, ToSchema)]
240pub struct ToolDecisionRequestDoc {
241 pub run_id: Uuid,
242 #[serde(flatten)]
243 pub decision: DecisionInputDoc,
244}
245
246#[derive(Debug, Serialize, Deserialize, ToSchema)]
247pub struct ToolDecisionsRequestDoc {
248 pub run_id: Uuid,
249 pub decisions: HashMap<String, DecisionInputDoc>,
250}
251
252#[derive(Debug, Serialize, Deserialize, ToSchema)]
253pub struct ToolDecisionResponseDoc {
254 pub accepted: bool,
255 pub run_id: Uuid,
256}
257
258#[derive(Debug, Serialize, Deserialize, ToSchema)]
259pub struct CancelRequestDoc {
260 pub run_id: Uuid,
261}
262
263#[derive(Debug, Serialize, Deserialize, ToSchema)]
264pub struct CancelResponseDoc {
265 pub cancelled: bool,
266 pub run_id: Uuid,
267}
268
269#[derive(Debug, Serialize, Deserialize, ToSchema)]
270pub struct ModelSwitchRequestDoc {
271 pub run_id: Uuid,
272 pub model: String,
273}
274
275#[derive(Debug, Serialize, Deserialize, ToSchema)]
276pub struct ModelSwitchResponseDoc {
277 pub accepted: bool,
278 pub run_id: Uuid,
279 pub model: String,
280}
281
282#[derive(Debug, Serialize, Deserialize, ToSchema)]
283#[serde(rename_all = "snake_case")]
284pub enum AutoApproveModeDoc {
285 None,
286 All,
287 Custom,
288}
289
290#[derive(Debug, Serialize, Deserialize, ToSchema)]
291pub struct ConfigResponseDoc {
292 pub default_model: Option<String>,
293 pub auto_approve_mode: AutoApproveModeDoc,
294}
295
296#[derive(Debug, Serialize, Deserialize, ToSchema)]
297pub struct ModelCostDoc {
298 pub input: f64,
299 pub output: f64,
300 pub cache_read: Option<f64>,
301 pub cache_write: Option<f64>,
302}
303
304#[derive(Debug, Serialize, Deserialize, ToSchema)]
305pub struct ModelLimitDoc {
306 pub context: u64,
307 pub output: u64,
308}
309
310#[derive(Debug, Serialize, Deserialize, ToSchema)]
311pub struct ModelDoc {
312 pub id: String,
313 pub name: String,
314 pub provider: String,
315 pub reasoning: bool,
316 pub cost: Option<ModelCostDoc>,
317 pub limit: ModelLimitDoc,
318 pub release_date: Option<String>,
319}
320
321#[derive(Debug, Serialize, Deserialize, ToSchema)]
322pub struct ModelsResponseDoc {
323 pub models: Vec<ModelDoc>,
324}
325
326#[derive(Debug, Serialize, Deserialize, ToSchema)]
327pub struct EventEnvelopeDoc {
328 pub id: u64,
329 pub session_id: Uuid,
330 pub run_id: Option<Uuid>,
331 pub timestamp: chrono::DateTime<chrono::Utc>,
332 #[schema(value_type = Object)]
333 pub event: serde_json::Value,
334}
335
336#[derive(Debug, Serialize, Deserialize, ToSchema)]
337pub struct GapDetectedDoc {
338 pub requested_after_id: u64,
339 pub oldest_available_id: u64,
340 pub newest_available_id: u64,
341 pub resume_hint: String,
342}
343
344#[utoipa::path(
345 get,
346 path = "/v1/health",
347 responses((status = 200, body = HealthResponseDoc))
348)]
349fn health_doc() {}
350
351#[utoipa::path(
352 get,
353 path = "/v1/openapi.json",
354 responses((status = 200, description = "OpenAPI 3.1 specification document"))
355)]
356fn openapi_doc() {}
357
358#[utoipa::path(
359 get,
360 path = "/v1/sessions",
361 security(("bearer_auth" = [])),
362 params(
363 ("limit" = Option<u32>, Query, description = "page size"),
364 ("offset" = Option<u32>, Query, description = "page offset"),
365 ("search" = Option<String>, Query, description = "title query"),
366 ("status" = Option<String>, Query, description = "ACTIVE or DELETED")
367 ),
368 responses(
369 (status = 200, body = SessionsResponseDoc),
370 (status = 401, body = ErrorResponseDoc)
371 )
372)]
373fn list_sessions_doc() {}
374
375#[utoipa::path(
376 post,
377 path = "/v1/sessions",
378 security(("bearer_auth" = [])),
379 request_body = CreateSessionBodyDoc,
380 responses(
381 (status = 201, body = SessionDoc),
382 (status = 401, body = ErrorResponseDoc),
383 (status = 409, body = ErrorResponseDoc)
384 )
385)]
386fn create_session_doc() {}
387
388#[utoipa::path(
389 get,
390 path = "/v1/sessions/{id}",
391 security(("bearer_auth" = [])),
392 params(("id" = Uuid, Path, description = "session id")),
393 responses(
394 (status = 200, body = SessionDetailResponseDoc),
395 (status = 401, body = ErrorResponseDoc),
396 (status = 404, body = ErrorResponseDoc)
397 )
398)]
399fn get_session_doc() {}
400
401#[utoipa::path(
402 patch,
403 path = "/v1/sessions/{id}",
404 security(("bearer_auth" = [])),
405 params(("id" = Uuid, Path, description = "session id")),
406 request_body = UpdateSessionBodyDoc,
407 responses(
408 (status = 200, body = SessionDetailResponseDoc),
409 (status = 401, body = ErrorResponseDoc),
410 (status = 404, body = ErrorResponseDoc)
411 )
412)]
413fn update_session_doc() {}
414
415#[utoipa::path(
416 delete,
417 path = "/v1/sessions/{id}",
418 security(("bearer_auth" = [])),
419 params(("id" = Uuid, Path, description = "session id")),
420 responses(
421 (status = 204, description = "Session deleted"),
422 (status = 401, body = ErrorResponseDoc),
423 (status = 404, body = ErrorResponseDoc)
424 )
425)]
426fn delete_session_doc() {}
427
428#[utoipa::path(
429 post,
430 path = "/v1/sessions/{id}/messages",
431 security(("bearer_auth" = [])),
432 params(("id" = Uuid, Path, description = "session id")),
433 request_body = SessionMessageRequestDoc,
434 responses(
435 (status = 200, body = SessionMessageResponseDoc),
436 (status = 401, body = ErrorResponseDoc),
437 (status = 404, body = ErrorResponseDoc),
438 (status = 409, body = ErrorResponseDoc)
439 )
440)]
441fn post_messages_doc() {}
442
443#[utoipa::path(
444 get,
445 path = "/v1/sessions/{id}/messages",
446 security(("bearer_auth" = [])),
447 params(
448 ("id" = Uuid, Path, description = "session id"),
449 ("limit" = Option<usize>, Query, description = "page size"),
450 ("offset" = Option<usize>, Query, description = "page offset")
451 ),
452 responses(
453 (status = 200, body = SessionMessagesResponseDoc),
454 (status = 401, body = ErrorResponseDoc),
455 (status = 404, body = ErrorResponseDoc)
456 )
457)]
458fn get_messages_doc() {}
459
460#[utoipa::path(
461 get,
462 path = "/v1/sessions/{id}/events",
463 security(("bearer_auth" = [])),
464 params(
465 ("id" = Uuid, Path, description = "session id"),
466 ("Last-Event-ID" = Option<u64>, Header, description = "Replay cursor; stream events with id > Last-Event-ID")
467 ),
468 responses((
469 status = 200,
470 description = "text/event-stream of EventEnvelope frames and optional gap_detected control event",
471 content_type = "text/event-stream"
472 ))
473)]
474fn events_doc() {}
475
476#[utoipa::path(
477 get,
478 path = "/v1/sessions/{id}/tools/pending",
479 security(("bearer_auth" = [])),
480 params(("id" = Uuid, Path, description = "session id")),
481 responses(
482 (status = 200, body = PendingToolsResponseDoc),
483 (status = 401, body = ErrorResponseDoc)
484 )
485)]
486fn pending_tools_doc() {}
487
488#[utoipa::path(
489 post,
490 path = "/v1/sessions/{id}/tools/{tool_call_id}/decision",
491 security(("bearer_auth" = [])),
492 params(
493 ("id" = Uuid, Path, description = "session id"),
494 ("tool_call_id" = String, Path, description = "tool call id")
495 ),
496 request_body = ToolDecisionRequestDoc,
497 responses(
498 (status = 200, body = ToolDecisionResponseDoc),
499 (status = 401, body = ErrorResponseDoc),
500 (status = 409, body = ErrorResponseDoc)
501 )
502)]
503fn tool_decision_doc() {}
504
505#[utoipa::path(
506 post,
507 path = "/v1/sessions/{id}/tools/decisions",
508 security(("bearer_auth" = [])),
509 params(("id" = Uuid, Path, description = "session id")),
510 request_body = ToolDecisionsRequestDoc,
511 responses(
512 (status = 200, body = ToolDecisionResponseDoc),
513 (status = 401, body = ErrorResponseDoc),
514 (status = 409, body = ErrorResponseDoc)
515 )
516)]
517fn tool_decisions_doc() {}
518
519#[utoipa::path(
520 post,
521 path = "/v1/sessions/{id}/tools/resolve",
522 security(("bearer_auth" = [])),
523 params(("id" = Uuid, Path, description = "session id")),
524 request_body = ToolDecisionsRequestDoc,
525 responses(
526 (status = 200, body = ToolDecisionResponseDoc),
527 (status = 401, body = ErrorResponseDoc),
528 (status = 409, body = ErrorResponseDoc)
529 )
530)]
531fn tool_resolve_doc() {}
532
533#[utoipa::path(
534 post,
535 path = "/v1/sessions/{id}/cancel",
536 security(("bearer_auth" = [])),
537 params(("id" = Uuid, Path, description = "session id")),
538 request_body = CancelRequestDoc,
539 responses(
540 (status = 200, body = CancelResponseDoc),
541 (status = 401, body = ErrorResponseDoc),
542 (status = 409, body = ErrorResponseDoc)
543 )
544)]
545fn cancel_doc() {}
546
547#[utoipa::path(
548 post,
549 path = "/v1/sessions/{id}/model",
550 security(("bearer_auth" = [])),
551 params(("id" = Uuid, Path, description = "session id")),
552 request_body = ModelSwitchRequestDoc,
553 responses(
554 (status = 200, body = ModelSwitchResponseDoc),
555 (status = 401, body = ErrorResponseDoc),
556 (status = 409, body = ErrorResponseDoc)
557 )
558)]
559fn switch_model_doc() {}
560
561#[utoipa::path(
562 get,
563 path = "/v1/models",
564 security(("bearer_auth" = [])),
565 responses(
566 (status = 200, body = ModelsResponseDoc),
567 (status = 401, body = ErrorResponseDoc)
568 )
569)]
570fn models_doc() {}
571
572#[utoipa::path(
573 get,
574 path = "/v1/config",
575 security(("bearer_auth" = [])),
576 responses(
577 (status = 200, body = ConfigResponseDoc),
578 (status = 401, body = ErrorResponseDoc)
579 )
580)]
581fn config_doc() {}
582
583struct SecurityAddon;
584
585impl Modify for SecurityAddon {
586 fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
587 let components = openapi.components.get_or_insert_with(Default::default);
588 components.add_security_scheme(
589 "bearer_auth",
590 SecurityScheme::Http(
591 HttpBuilder::new()
592 .scheme(HttpAuthScheme::Bearer)
593 .bearer_format("Bearer")
594 .build(),
595 ),
596 );
597 }
598}
599
600#[derive(OpenApi)]
601#[openapi(
602 paths(
603 health_doc,
604 openapi_doc,
605 list_sessions_doc,
606 create_session_doc,
607 get_session_doc,
608 update_session_doc,
609 delete_session_doc,
610 post_messages_doc,
611 get_messages_doc,
612 events_doc,
613 pending_tools_doc,
614 tool_decision_doc,
615 tool_decisions_doc,
616 tool_resolve_doc,
617 cancel_doc,
618 switch_model_doc,
619 models_doc,
620 config_doc,
621 ),
622 components(
623 schemas(
624 HealthResponseDoc,
625 ErrorResponseDoc,
626 RunStateDoc,
627 RunStatusDoc,
628 SessionDoc,
629 SessionsResponseDoc,
630 SessionDetailResponseDoc,
631 CreateSessionBodyDoc,
632 SessionVisibilityDoc,
633 UpdateSessionBodyDoc,
634 SessionMessageTypeDoc,
635 MessageRoleDoc,
636 ImageDetailDoc,
637 CacheControlDoc,
638 AnthropicMessageOptionsDoc,
639 MessageProviderOptionsDoc,
640 AnthropicContentPartOptionsDoc,
641 ContentPartProviderOptionsDoc,
642 ContentPartDoc,
643 MessageContentDoc,
644 StakaiMessageDoc,
645 SessionMessageRequestDoc,
646 SessionMessageResponseDoc,
647 SessionMessagesResponseDoc,
648 ProposedToolCallDoc,
649 PendingToolsResponseDoc,
650 DecisionActionDoc,
651 DecisionInputDoc,
652 ToolDecisionRequestDoc,
653 ToolDecisionsRequestDoc,
654 ToolDecisionResponseDoc,
655 CancelRequestDoc,
656 CancelResponseDoc,
657 ModelSwitchRequestDoc,
658 ModelSwitchResponseDoc,
659 AutoApproveModeDoc,
660 ConfigResponseDoc,
661 ModelCostDoc,
662 ModelLimitDoc,
663 ModelDoc,
664 ModelsResponseDoc,
665 EventEnvelopeDoc,
666 GapDetectedDoc,
667 )
668 ),
669 modifiers(&SecurityAddon),
670 tags(
671 (name = "server", description = "Stakpak server runtime APIs")
672 )
673)]
674pub struct ApiDoc;
675
676pub fn generate_openapi() -> utoipa::openapi::OpenApi {
677 ApiDoc::openapi()
678}