Skip to main content

stakpak_server/
openapi.rs

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}