Skip to main content

aster_server/
openapi.rs

1use aster::agents::extension::Envs;
2use aster::agents::extension::ToolInfo;
3use aster::agents::ExtensionConfig;
4use aster::config::permission::PermissionLevel;
5use aster::config::ExtensionEntry;
6use aster::conversation::Conversation;
7use aster::model::ModelConfig;
8use aster::permission::permission_confirmation::PrincipalType;
9use aster::providers::base::{ConfigKey, ModelInfo, ProviderMetadata, ProviderType};
10use aster::session::{Session, SessionInsights, SessionType};
11use rmcp::model::{
12    Annotations, Content, EmbeddedResource, Icon, ImageContent, JsonObject, RawAudioContent,
13    RawEmbeddedResource, RawImageContent, RawResource, RawTextContent, ResourceContents, Role,
14    TextContent, Tool, ToolAnnotations,
15};
16use utoipa::{OpenApi, ToSchema};
17
18use aster::config::declarative_providers::{
19    DeclarativeProviderConfig, LoadedProvider, ProviderEngine,
20};
21use aster::conversation::message::{
22    ActionRequired, ActionRequiredData, FrontendToolRequest, Message, MessageContent,
23    MessageMetadata, RedactedThinkingContent, SystemNotificationContent, SystemNotificationType,
24    ThinkingContent, TokenState, ToolConfirmationRequest, ToolRequest, ToolResponse,
25};
26
27use crate::routes::recipe_utils::RecipeManifest;
28use crate::routes::reply::MessageEvent;
29use utoipa::openapi::schema::{
30    AdditionalProperties, AnyOfBuilder, ArrayBuilder, ObjectBuilder, OneOfBuilder, Schema,
31    SchemaFormat, SchemaType,
32};
33use utoipa::openapi::{AllOfBuilder, Ref, RefOr};
34
35macro_rules! derive_utoipa {
36    ($inner_type:ident as $schema_name:ident) => {
37        struct $schema_name {}
38
39        impl<'__s> ToSchema<'__s> for $schema_name {
40            fn schema() -> (&'__s str, utoipa::openapi::RefOr<utoipa::openapi::Schema>) {
41                let settings = rmcp::schemars::generate::SchemaSettings::openapi3();
42                let generator = settings.into_generator();
43                let schema = generator.into_root_schema_for::<$inner_type>();
44                let schema = convert_schemars_to_utoipa(schema);
45                (stringify!($inner_type), schema)
46            }
47
48            fn aliases() -> Vec<(&'__s str, utoipa::openapi::schema::Schema)> {
49                Vec::new()
50            }
51        }
52    };
53}
54
55fn convert_schemars_to_utoipa(schema: rmcp::schemars::Schema) -> RefOr<Schema> {
56    if let Some(true) = schema.as_bool() {
57        return RefOr::T(Schema::Object(ObjectBuilder::new().build()));
58    }
59
60    if let Some(false) = schema.as_bool() {
61        return RefOr::T(Schema::Object(ObjectBuilder::new().build()));
62    }
63
64    if let Some(obj) = schema.as_object() {
65        return convert_json_object_to_utoipa(obj);
66    }
67
68    RefOr::T(Schema::Object(ObjectBuilder::new().build()))
69}
70
71fn convert_json_object_to_utoipa(
72    obj: &serde_json::Map<String, serde_json::Value>,
73) -> RefOr<Schema> {
74    use serde_json::Value;
75
76    if let Some(Value::String(reference)) = obj.get("$ref") {
77        return RefOr::Ref(Ref::new(reference.clone()));
78    }
79
80    if let Some(Value::Array(one_of)) = obj.get("oneOf") {
81        let mut builder = OneOfBuilder::new();
82        for item in one_of {
83            if let Ok(schema) = rmcp::schemars::Schema::try_from(item.clone()) {
84                builder = builder.item(convert_schemars_to_utoipa(schema));
85            }
86        }
87        return RefOr::T(Schema::OneOf(builder.build()));
88    }
89
90    if let Some(Value::Array(all_of)) = obj.get("allOf") {
91        let mut builder = AllOfBuilder::new();
92        for item in all_of {
93            if let Ok(schema) = rmcp::schemars::Schema::try_from(item.clone()) {
94                builder = builder.item(convert_schemars_to_utoipa(schema));
95            }
96        }
97        return RefOr::T(Schema::AllOf(builder.build()));
98    }
99
100    if let Some(Value::Array(any_of)) = obj.get("anyOf") {
101        let mut builder = AnyOfBuilder::new();
102        for item in any_of {
103            if let Ok(schema) = rmcp::schemars::Schema::try_from(item.clone()) {
104                builder = builder.item(convert_schemars_to_utoipa(schema));
105            }
106        }
107        return RefOr::T(Schema::AnyOf(builder.build()));
108    }
109
110    match obj.get("type") {
111        Some(Value::String(type_str)) => convert_typed_schema(type_str, obj),
112        Some(Value::Array(types)) => {
113            let mut builder = AnyOfBuilder::new();
114            for type_val in types {
115                if let Value::String(type_str) = type_val {
116                    builder = builder.item(convert_typed_schema(type_str, obj));
117                }
118            }
119            RefOr::T(Schema::AnyOf(builder.build()))
120        }
121        None => RefOr::T(Schema::Object(ObjectBuilder::new().build())),
122        _ => RefOr::T(Schema::Object(ObjectBuilder::new().build())),
123    }
124}
125
126fn convert_typed_schema(
127    type_str: &str,
128    obj: &serde_json::Map<String, serde_json::Value>,
129) -> RefOr<Schema> {
130    use serde_json::Value;
131
132    match type_str {
133        "object" => {
134            let mut object_builder = ObjectBuilder::new();
135
136            if let Some(Value::Object(properties)) = obj.get("properties") {
137                for (name, prop_value) in properties {
138                    if let Ok(prop_schema) = rmcp::schemars::Schema::try_from(prop_value.clone()) {
139                        let prop = convert_schemars_to_utoipa(prop_schema);
140                        object_builder = object_builder.property(name, prop);
141                    }
142                }
143            }
144
145            if let Some(Value::Array(required)) = obj.get("required") {
146                for req in required {
147                    if let Value::String(field_name) = req {
148                        object_builder = object_builder.required(field_name);
149                    }
150                }
151            }
152
153            if let Some(additional) = obj.get("additionalProperties") {
154                match additional {
155                    Value::Bool(false) => {
156                        object_builder = object_builder
157                            .additional_properties(Some(AdditionalProperties::FreeForm(false)));
158                    }
159                    Value::Bool(true) => {
160                        object_builder = object_builder
161                            .additional_properties(Some(AdditionalProperties::FreeForm(true)));
162                    }
163                    _ => {
164                        if let Ok(schema) = rmcp::schemars::Schema::try_from(additional.clone()) {
165                            let schema = convert_schemars_to_utoipa(schema);
166                            object_builder = object_builder
167                                .additional_properties(Some(AdditionalProperties::RefOr(schema)));
168                        }
169                    }
170                }
171            }
172
173            RefOr::T(Schema::Object(object_builder.build()))
174        }
175        "array" => {
176            let mut array_builder = ArrayBuilder::new();
177
178            if let Some(items) = obj.get("items") {
179                match items {
180                    Value::Object(_) | Value::Bool(_) => {
181                        if let Ok(item_schema) = rmcp::schemars::Schema::try_from(items.clone()) {
182                            let item_schema = convert_schemars_to_utoipa(item_schema);
183                            array_builder = array_builder.items(item_schema);
184                        }
185                    }
186                    Value::Array(item_schemas) => {
187                        let mut any_of = AnyOfBuilder::new();
188                        for item in item_schemas {
189                            if let Ok(schema) = rmcp::schemars::Schema::try_from(item.clone()) {
190                                any_of = any_of.item(convert_schemars_to_utoipa(schema));
191                            }
192                        }
193                        let any_of_schema = RefOr::T(Schema::AnyOf(any_of.build()));
194                        array_builder = array_builder.items(any_of_schema);
195                    }
196                    _ => {}
197                }
198            }
199
200            if let Some(Value::Number(min_items)) = obj.get("minItems") {
201                if let Some(min) = min_items.as_u64() {
202                    array_builder = array_builder.min_items(Some(min as usize));
203                }
204            }
205            if let Some(Value::Number(max_items)) = obj.get("maxItems") {
206                if let Some(max) = max_items.as_u64() {
207                    array_builder = array_builder.max_items(Some(max as usize));
208                }
209            }
210
211            RefOr::T(Schema::Array(array_builder.build()))
212        }
213        "string" => {
214            let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::String);
215
216            if let Some(Value::Number(min_length)) = obj.get("minLength") {
217                if let Some(min) = min_length.as_u64() {
218                    object_builder = object_builder.min_length(Some(min as usize));
219                }
220            }
221            if let Some(Value::Number(max_length)) = obj.get("maxLength") {
222                if let Some(max) = max_length.as_u64() {
223                    object_builder = object_builder.max_length(Some(max as usize));
224                }
225            }
226            if let Some(Value::String(pattern)) = obj.get("pattern") {
227                object_builder = object_builder.pattern(Some(pattern.clone()));
228            }
229            if let Some(Value::String(format)) = obj.get("format") {
230                object_builder = object_builder.format(Some(SchemaFormat::Custom(format.clone())));
231            }
232
233            RefOr::T(Schema::Object(object_builder.build()))
234        }
235        "number" => {
236            let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::Number);
237
238            if let Some(Value::Number(minimum)) = obj.get("minimum") {
239                if let Some(min) = minimum.as_f64() {
240                    object_builder = object_builder.minimum(Some(min));
241                }
242            }
243            if let Some(Value::Number(maximum)) = obj.get("maximum") {
244                if let Some(max) = maximum.as_f64() {
245                    object_builder = object_builder.maximum(Some(max));
246                }
247            }
248            if let Some(Value::Number(exclusive_minimum)) = obj.get("exclusiveMinimum") {
249                if let Some(min) = exclusive_minimum.as_f64() {
250                    object_builder = object_builder.exclusive_minimum(Some(min));
251                }
252            }
253            if let Some(Value::Number(exclusive_maximum)) = obj.get("exclusiveMaximum") {
254                if let Some(max) = exclusive_maximum.as_f64() {
255                    object_builder = object_builder.exclusive_maximum(Some(max));
256                }
257            }
258            if let Some(Value::Number(multiple_of)) = obj.get("multipleOf") {
259                if let Some(mult) = multiple_of.as_f64() {
260                    object_builder = object_builder.multiple_of(Some(mult));
261                }
262            }
263
264            RefOr::T(Schema::Object(object_builder.build()))
265        }
266        "integer" => {
267            let mut object_builder = ObjectBuilder::new().schema_type(SchemaType::Integer);
268
269            if let Some(Value::Number(minimum)) = obj.get("minimum") {
270                if let Some(min) = minimum.as_f64() {
271                    object_builder = object_builder.minimum(Some(min));
272                }
273            }
274            if let Some(Value::Number(maximum)) = obj.get("maximum") {
275                if let Some(max) = maximum.as_f64() {
276                    object_builder = object_builder.maximum(Some(max));
277                }
278            }
279            if let Some(Value::Number(exclusive_minimum)) = obj.get("exclusiveMinimum") {
280                if let Some(min) = exclusive_minimum.as_f64() {
281                    object_builder = object_builder.exclusive_minimum(Some(min));
282                }
283            }
284            if let Some(Value::Number(exclusive_maximum)) = obj.get("exclusiveMaximum") {
285                if let Some(max) = exclusive_maximum.as_f64() {
286                    object_builder = object_builder.exclusive_maximum(Some(max));
287                }
288            }
289            if let Some(Value::Number(multiple_of)) = obj.get("multipleOf") {
290                if let Some(mult) = multiple_of.as_f64() {
291                    object_builder = object_builder.multiple_of(Some(mult));
292                }
293            }
294
295            RefOr::T(Schema::Object(object_builder.build()))
296        }
297        "boolean" => RefOr::T(Schema::Object(
298            ObjectBuilder::new()
299                .schema_type(SchemaType::Boolean)
300                .build(),
301        )),
302        "null" => RefOr::T(Schema::Object(
303            ObjectBuilder::new().schema_type(SchemaType::String).build(),
304        )),
305        _ => RefOr::T(Schema::Object(ObjectBuilder::new().build())),
306    }
307}
308
309derive_utoipa!(Role as RoleSchema);
310derive_utoipa!(Content as ContentSchema);
311derive_utoipa!(EmbeddedResource as EmbeddedResourceSchema);
312derive_utoipa!(ImageContent as ImageContentSchema);
313derive_utoipa!(TextContent as TextContentSchema);
314derive_utoipa!(RawTextContent as RawTextContentSchema);
315derive_utoipa!(RawImageContent as RawImageContentSchema);
316derive_utoipa!(RawAudioContent as RawAudioContentSchema);
317derive_utoipa!(RawEmbeddedResource as RawEmbeddedResourceSchema);
318derive_utoipa!(RawResource as RawResourceSchema);
319derive_utoipa!(Tool as ToolSchema);
320derive_utoipa!(ToolAnnotations as ToolAnnotationsSchema);
321derive_utoipa!(Annotations as AnnotationsSchema);
322derive_utoipa!(ResourceContents as ResourceContentsSchema);
323derive_utoipa!(JsonObject as JsonObjectSchema);
324derive_utoipa!(Icon as IconSchema);
325
326#[derive(OpenApi)]
327#[openapi(
328    paths(
329        super::routes::status::status,
330        super::routes::status::diagnostics,
331        super::routes::mcp_ui_proxy::mcp_ui_proxy,
332        super::routes::config_management::backup_config,
333        super::routes::config_management::detect_provider,
334        super::routes::config_management::recover_config,
335        super::routes::config_management::validate_config,
336        super::routes::config_management::init_config,
337        super::routes::config_management::upsert_config,
338        super::routes::config_management::remove_config,
339        super::routes::config_management::read_config,
340        super::routes::config_management::add_extension,
341        super::routes::config_management::remove_extension,
342        super::routes::config_management::get_extensions,
343        super::routes::config_management::read_all_config,
344        super::routes::config_management::providers,
345        super::routes::config_management::get_provider_models,
346        super::routes::config_management::get_slash_commands,
347        super::routes::config_management::upsert_permissions,
348        super::routes::config_management::create_custom_provider,
349        super::routes::config_management::get_custom_provider,
350        super::routes::config_management::update_custom_provider,
351        super::routes::config_management::remove_custom_provider,
352        super::routes::config_management::check_provider,
353        super::routes::config_management::set_config_provider,
354        super::routes::config_management::get_pricing,
355        super::routes::agent::start_agent,
356        super::routes::agent::resume_agent,
357        super::routes::agent::get_tools,
358        super::routes::agent::read_resource,
359        super::routes::agent::call_tool,
360        super::routes::agent::update_from_session,
361        super::routes::agent::agent_add_extension,
362        super::routes::agent::agent_remove_extension,
363        super::routes::agent::update_agent_provider,
364        super::routes::action_required::confirm_tool_action,
365        super::routes::reply::reply,
366        super::routes::session::list_sessions,
367        super::routes::session::get_session,
368        super::routes::session::get_session_insights,
369        super::routes::session::update_session_name,
370        super::routes::session::delete_session,
371        super::routes::session::export_session,
372        super::routes::session::import_session,
373        super::routes::session::update_session_user_recipe_values,
374        super::routes::session::edit_message,
375        super::routes::schedule::create_schedule,
376        super::routes::schedule::list_schedules,
377        super::routes::schedule::delete_schedule,
378        super::routes::schedule::update_schedule,
379        super::routes::schedule::run_now_handler,
380        super::routes::schedule::pause_schedule,
381        super::routes::schedule::unpause_schedule,
382        super::routes::schedule::kill_running_job,
383        super::routes::schedule::inspect_running_job,
384        super::routes::schedule::sessions_handler,
385        super::routes::recipe::create_recipe,
386        super::routes::recipe::encode_recipe,
387        super::routes::recipe::decode_recipe,
388        super::routes::recipe::scan_recipe,
389        super::routes::recipe::list_recipes,
390        super::routes::recipe::delete_recipe,
391        super::routes::recipe::schedule_recipe,
392        super::routes::recipe::set_recipe_slash_command,
393        super::routes::recipe::save_recipe,
394        super::routes::recipe::parse_recipe,
395        super::routes::recipe::recipe_to_yaml,
396        super::routes::setup::start_openrouter_setup,
397        super::routes::setup::start_tetrate_setup,
398        super::routes::tunnel::start_tunnel,
399        super::routes::tunnel::stop_tunnel,
400        super::routes::tunnel::get_tunnel_status,
401        super::routes::telemetry::send_telemetry_event,
402    ),
403    components(schemas(
404        super::routes::config_management::UpsertConfigQuery,
405        super::routes::config_management::ConfigKeyQuery,
406        super::routes::config_management::DetectProviderRequest,
407        super::routes::config_management::DetectProviderResponse,
408        super::routes::config_management::ConfigResponse,
409        super::routes::config_management::ProvidersResponse,
410        super::routes::config_management::ProviderDetails,
411        super::routes::config_management::SlashCommandsResponse,
412        super::routes::config_management::SlashCommand,
413        super::routes::config_management::CommandType,
414        super::routes::config_management::ExtensionResponse,
415        super::routes::config_management::ExtensionQuery,
416        super::routes::config_management::ToolPermission,
417        super::routes::config_management::UpsertPermissionsQuery,
418        super::routes::config_management::UpdateCustomProviderRequest,
419        super::routes::config_management::CheckProviderRequest,
420        super::routes::config_management::SetProviderRequest,
421        super::routes::config_management::PricingQuery,
422        super::routes::config_management::PricingResponse,
423        super::routes::config_management::PricingData,
424        super::routes::action_required::ConfirmToolActionRequest,
425        super::routes::reply::ChatRequest,
426        super::routes::session::ImportSessionRequest,
427        super::routes::session::SessionListResponse,
428        super::routes::session::UpdateSessionNameRequest,
429        super::routes::session::UpdateSessionUserRecipeValuesRequest,
430        super::routes::session::UpdateSessionUserRecipeValuesResponse,
431        super::routes::session::EditType,
432        super::routes::session::EditMessageRequest,
433        super::routes::session::EditMessageResponse,
434        Message,
435        MessageContent,
436        MessageMetadata,
437        TokenState,
438        ContentSchema,
439        EmbeddedResourceSchema,
440        ImageContentSchema,
441        AnnotationsSchema,
442        TextContentSchema,
443        RawTextContentSchema,
444        RawImageContentSchema,
445        RawAudioContentSchema,
446        RawEmbeddedResourceSchema,
447        RawResourceSchema,
448        ToolResponse,
449        ToolRequest,
450        ToolConfirmationRequest,
451        ActionRequired,
452        ActionRequiredData,
453        ThinkingContent,
454        RedactedThinkingContent,
455        FrontendToolRequest,
456        ResourceContentsSchema,
457        SystemNotificationType,
458        SystemNotificationContent,
459        MessageEvent,
460        JsonObjectSchema,
461        RoleSchema,
462        ProviderMetadata,
463        ProviderType,
464        LoadedProvider,
465        ProviderEngine,
466        DeclarativeProviderConfig,
467        ExtensionEntry,
468        ExtensionConfig,
469        ConfigKey,
470        Envs,
471        RecipeManifest,
472        ToolSchema,
473        ToolAnnotationsSchema,
474        ToolInfo,
475        PermissionLevel,
476        PrincipalType,
477        ModelInfo,
478        ModelConfig,
479        Session,
480        SessionInsights,
481        SessionType,
482        Conversation,
483        IconSchema,
484        aster::session::extension_data::ExtensionData,
485        super::routes::schedule::CreateScheduleRequest,
486        super::routes::schedule::UpdateScheduleRequest,
487        super::routes::schedule::KillJobResponse,
488        super::routes::schedule::InspectJobResponse,
489        aster::scheduler::ScheduledJob,
490        super::routes::schedule::RunNowResponse,
491        super::routes::schedule::ListSchedulesResponse,
492        super::routes::schedule::SessionsQuery,
493        super::routes::schedule::SessionDisplayInfo,
494        super::routes::recipe::CreateRecipeRequest,
495        super::routes::recipe::AuthorRequest,
496        super::routes::recipe::CreateRecipeResponse,
497        super::routes::recipe::EncodeRecipeRequest,
498        super::routes::recipe::EncodeRecipeResponse,
499        super::routes::recipe::DecodeRecipeRequest,
500        super::routes::recipe::DecodeRecipeResponse,
501        super::routes::recipe::ScanRecipeRequest,
502        super::routes::recipe::ScanRecipeResponse,
503        super::routes::recipe::ListRecipeResponse,
504        super::routes::recipe::ScheduleRecipeRequest,
505        super::routes::recipe::SetSlashCommandRequest,
506        super::routes::recipe::DeleteRecipeRequest,
507        super::routes::recipe::SaveRecipeRequest,
508        super::routes::recipe::SaveRecipeResponse,
509        super::routes::errors::ErrorResponse,
510        super::routes::recipe::ParseRecipeRequest,
511        super::routes::recipe::ParseRecipeResponse,
512        super::routes::recipe::RecipeToYamlRequest,
513        super::routes::recipe::RecipeToYamlResponse,
514        aster::recipe::Recipe,
515        aster::recipe::Author,
516        aster::recipe::Settings,
517        aster::recipe::RecipeParameter,
518        aster::recipe::RecipeParameterInputType,
519        aster::recipe::RecipeParameterRequirement,
520        aster::recipe::Response,
521        aster::recipe::SubRecipe,
522        aster::agents::types::RetryConfig,
523        aster::agents::types::SuccessCheck,
524        super::routes::agent::UpdateProviderRequest,
525        super::routes::agent::GetToolsQuery,
526        super::routes::agent::ReadResourceRequest,
527        super::routes::agent::ReadResourceResponse,
528        super::routes::agent::CallToolRequest,
529        super::routes::agent::CallToolResponse,
530        super::routes::agent::StartAgentRequest,
531        super::routes::agent::ResumeAgentRequest,
532        super::routes::agent::UpdateFromSessionRequest,
533        super::routes::agent::AddExtensionRequest,
534        super::routes::agent::RemoveExtensionRequest,
535        super::routes::setup::SetupResponse,
536        super::tunnel::TunnelInfo,
537        super::tunnel::TunnelState,
538        super::routes::telemetry::TelemetryEventRequest,
539        aster::aster_apps::McpAppResource,
540        aster::aster_apps::CspMetadata,
541        aster::aster_apps::UiMetadata,
542        aster::aster_apps::ResourceMetadata,
543    ))
544)]
545pub struct ApiDoc;
546
547#[allow(dead_code)] // Used by generate_schema binary
548pub fn generate_schema() -> String {
549    let api_doc = ApiDoc::openapi();
550    serde_json::to_string_pretty(&api_doc).unwrap()
551}