tandem-server 0.4.23

HTTP server for Tandem engine APIs
Documentation
use axum::extract::State;
use axum::http::StatusCode;
use axum::Json;
use serde::Deserialize;
use serde_json::{json, Value};
use tandem_workflows::MissionBlueprint;

use super::*;

#[derive(Debug, Deserialize)]
pub(super) struct MissionBuilderGenerateDraftRequest {
    pub intent: String,
    pub workspace_root: String,
    #[serde(default)]
    pub archetype_id: Option<String>,
    #[serde(default)]
    pub creator_id: Option<String>,
}

#[derive(Debug, Deserialize)]
pub(super) struct MissionBuilderPreviewRequest {
    pub blueprint: MissionBlueprint,
    #[serde(default)]
    pub schedule: Option<crate::AutomationV2Schedule>,
}

#[derive(Debug, Deserialize)]
pub(super) struct MissionBuilderApplyRequest {
    pub blueprint: MissionBlueprint,
    #[serde(default)]
    pub creator_id: Option<String>,
    #[serde(default)]
    pub schedule: Option<crate::AutomationV2Schedule>,
}

pub(super) async fn mission_builder_preview(
    State(_state): State<AppState>,
    Json(input): Json<MissionBuilderPreviewRequest>,
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
    let preview = compile_blueprint_preview(input.blueprint, input.schedule, "mission_builder")?;
    Ok(Json(
        serde_json::to_value(preview).unwrap_or_else(|_| json!({})),
    ))
}

pub(super) async fn mission_builder_generate_draft(
    State(state): State<AppState>,
    Json(input): Json<MissionBuilderGenerateDraftRequest>,
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
    let intent = input.intent.trim();
    if intent.is_empty() {
        return Err((
            StatusCode::BAD_REQUEST,
            Json(json!({
                "error": "intent is required",
                "code": "MISSION_BUILDER_INVALID",
            })),
        ));
    }
    let workspace_root =
        crate::normalize_absolute_workspace_root(&input.workspace_root).map_err(|error| {
            (
                StatusCode::BAD_REQUEST,
                Json(json!({
                    "error": error,
                    "code": "MISSION_BUILDER_INVALID",
                })),
            )
        })?;
    let generated = mission_builder_host::generate_mission_draft(
        &state,
        intent,
        &workspace_root,
        input.archetype_id.as_deref(),
    )
    .await
    .map_err(|error| {
        (
            StatusCode::BAD_REQUEST,
            Json(json!({
                "error": error,
                "code": "MISSION_BUILDER_GENERATION_FAILED",
            })),
        )
    })?;
    let preview = compile_blueprint_preview(
        generated.blueprint.clone(),
        generated.suggested_schedule.as_ref().and_then(|value| {
            tandem_plan_compiler::api::schedule_from_value(
                value,
                crate::RoutineMisfirePolicy::RunOnce,
            )
        }),
        input.creator_id.as_deref().unwrap_or("mission_builder"),
    )?;
    Ok(Json(json!({
        "blueprint": preview.blueprint,
        "suggested_schedule": preview.automation.schedule,
        "validation": preview.validation,
        "generation_warnings": generated.generation_warnings,
    })))
}

pub(super) async fn mission_builder_apply(
    State(state): State<AppState>,
    Json(input): Json<MissionBuilderApplyRequest>,
) -> Result<Json<Value>, (StatusCode, Json<Value>)> {
    let creator_id = input
        .creator_id
        .as_deref()
        .unwrap_or("mission_builder")
        .to_string();
    let preview = compile_blueprint_preview(input.blueprint, input.schedule, &creator_id)?;
    let stored = state
        .put_automation_v2(preview.automation.clone())
        .await
        .map_err(|error| {
            (
                StatusCode::BAD_REQUEST,
                Json(json!({
                    "error": error.to_string(),
                    "code": "MISSION_BUILDER_APPLY_FAILED",
                })),
            )
        })?;
    Ok(Json(json!({
        "ok": true,
        "automation": stored,
        "mission_spec": preview.mission_spec,
        "work_items": preview.work_items,
        "node_previews": preview.node_previews,
        "validation": preview.validation,
    })))
}

fn compile_blueprint_preview(
    blueprint: MissionBlueprint,
    schedule: Option<crate::AutomationV2Schedule>,
    creator_id: &str,
) -> Result<mission_builder_runtime::MissionCompilePreview, (StatusCode, Json<Value>)> {
    let preview = tandem_plan_compiler::api::compile_mission_blueprint_preview(blueprint.clone())
        .map_err(|validation| {
        (
            StatusCode::BAD_REQUEST,
            Json(json!({
                "error": "mission blueprint validation failed",
                "code": "MISSION_BLUEPRINT_INVALID",
                "validation": validation,
            })),
        )
    })?;
    let mut automation =
        mission_builder_runtime::compile_to_automation(blueprint.clone(), schedule, creator_id);
    automation.knowledge = preview.mission_spec.knowledge.clone();

    Ok(mission_builder_runtime::MissionCompilePreview {
        blueprint,
        automation,
        mission_spec: preview.mission_spec,
        work_items: preview.work_items,
        node_previews: preview.node_previews,
        validation: preview.validation,
    })
}