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)] pub fn generate_schema() -> String {
549 let api_doc = ApiDoc::openapi();
550 serde_json::to_string_pretty(&api_doc).unwrap()
551}