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