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)]
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}