systemprompt-cli 0.2.1

Unified CLI for systemprompt.io AI governance: agent orchestration, MCP governance, analytics, profiles, cloud deploy, and self-hosted operations.
Documentation
use anyhow::{Context, Result, anyhow};
use clap::Args;
use reqwest::Client;
use systemprompt_agent::models::a2a::jsonrpc::{
    JSON_RPC_VERSION_2_0, JsonRpcResponse, Request, RequestId,
};
use systemprompt_agent::models::a2a::protocol::TaskQueryParams;
use systemprompt_identifiers::TaskId;
use systemprompt_models::a2a::{Task, methods};

use crate::CliConfig;
use crate::interactive::resolve_required;
use crate::session::get_or_create_session;
use crate::shared::CommandResult;

#[derive(Debug, Args)]
pub struct TaskArgs {
    #[arg(help = "Agent name that processed the task")]
    pub agent: Option<String>,

    #[arg(long = "task-id", help = "Task ID to retrieve")]
    pub task: Option<String>,

    #[arg(long, help = "Number of history messages to retrieve")]
    pub history_length: Option<u32>,

    #[arg(long, help = "Gateway URL (default: http://localhost:8080)")]
    pub url: Option<String>,

    #[arg(
        long,
        env = "SYSTEMPROMPT_TOKEN",
        help = "Bearer token for authentication"
    )]
    pub token: Option<String>,

    #[arg(long, default_value = "30", help = "Timeout in seconds")]
    pub timeout: u64,
}

pub async fn execute(args: TaskArgs, config: &CliConfig) -> Result<CommandResult<Task>> {
    let session_ctx = get_or_create_session(config).await?;

    let agent = resolve_required(args.agent, "agent", config, || {
        Err(anyhow!("Agent name is required"))
    })?;

    let task_id = resolve_required(args.task, "task-id", config, || {
        Err(anyhow!("Task ID is required. Use --task-id"))
    })?;

    let base_url = args.url.as_deref().unwrap_or_else(|| session_ctx.api_url());
    let agent_url = format!("{}/api/v1/agents/{}", base_url.trim_end_matches('/'), agent);

    let auth_token = args
        .token
        .as_deref()
        .unwrap_or_else(|| session_ctx.session_token().as_str());

    let request_id = RequestId::String(uuid::Uuid::new_v4().to_string());

    let request = Request {
        jsonrpc: JSON_RPC_VERSION_2_0.to_string(),
        method: methods::GET_TASK.to_string(),
        params: TaskQueryParams {
            id: TaskId::new(task_id.clone()),
            history_length: args.history_length,
        },
        id: request_id,
    };

    let client = Client::builder()
        .timeout(std::time::Duration::from_secs(args.timeout))
        .build()
        .context("Failed to create HTTP client")?;

    let request_builder = client
        .post(&agent_url)
        .header("Content-Type", "application/json")
        .header("Authorization", format!("Bearer {}", auth_token));

    let response = request_builder
        .json(&request)
        .send()
        .await
        .with_context(|| format!("Failed to get task from agent at {}", agent_url))?;

    if !response.status().is_success() {
        let status = response.status();
        let body = response.text().await.unwrap_or_else(|_| String::new());
        anyhow::bail!("Agent request failed with status {}: {}", status, body);
    }

    let json_response: JsonRpcResponse<Task> = response
        .json()
        .await
        .context("Failed to parse agent response")?;

    if json_response.jsonrpc != JSON_RPC_VERSION_2_0 {
        anyhow::bail!(
            "Invalid JSON-RPC version: expected {}, got {}",
            JSON_RPC_VERSION_2_0,
            json_response.jsonrpc
        );
    }

    if let Some(error) = json_response.error {
        let details = error
            .data
            .map_or_else(String::new, |d| format!("\n\nDetails: {}", d));
        anyhow::bail!(
            "Agent returned error ({}): {}{}",
            error.code,
            error.message,
            details
        );
    }

    let task = json_response
        .result
        .ok_or_else(|| anyhow!("No result in agent response"))?;

    Ok(CommandResult::card(task).with_title("Task Details"))
}