///|
/// AI Agent Runtime - HTTP Server with SSE support
/// Uses DeepSeek v4-pro via environment variable DEEPSEEK_API_KEY

///|
/// Main entry point
async fn main {
  // Load configuration from environment variables
  let api_key = match @env.get_env_var("DEEPSEEK_API_KEY") {
    Some(key) => key
    None => {
      println("Error: DEEPSEEK_API_KEY environment variable not set")
      println(
        "Please set it before running: $env:DEEPSEEK_API_KEY=\"your-key\"",
      )
      return
    }
  }

  let model = @env.get_env_var("DEEPSEEK_MODEL").unwrap_or("deepseek-chat")
  let port_str = @env.get_env_var("PORT").unwrap_or("8080")
  let port = parse_port(port_str)
  let data_dir = @env.get_env_var("DATA_DIR").unwrap_or("static/data")
  let cors_origin = @env.get_env_var("CORS_ORIGIN").unwrap_or("*")

  // Build data file paths
  let agents_file = data_dir + "/agents.csv"
  let tools_file = data_dir + "/tools.csv"
  let tasks_file = data_dir + "/tasks.csv"

  println("Starting AI Agent Runtime...")
  println("Model: \{model}")
  println("Port: \{port}")
  println("Data: \{data_dir}")

  // Load data from CSV files
  let agent_manager = match @lib.AgentManager::load(agents_file) {
    Ok(mgr) => {
      println("Loaded \{mgr.count()} agents")
      mgr
    }
    Err(e) => {
      println("Warning: Failed to load agents: \{e}")
      @lib.AgentManager::new(Map([], capacity=0))
    }
  }

  let tool_registry = match @lib.ToolRegistry::load(tools_file) {
    Ok(reg) => {
      let all = reg.list_all()
      println(
        "Loaded \{all.length()} tools (enabled: \{reg.list_enabled().length()})",
      )
      reg
    }
    Err(e) => {
      println("Warning: Failed to load tools: \{e}")
      @lib.ToolRegistry::new(Map([], capacity=0))
    }
  }

  let task_manager = match @lib.TaskManager::load(tasks_file) {
    Ok(mgr) => {
      println("Loaded \{mgr.count()} tasks")
      mgr
    }
    Err(e) => {
      println("Warning: Failed to load tasks: \{e}")
      @lib.TaskManager::new(Map([], capacity=0))
    }
  }

  let super_task_manager = match
    @lib.SuperTaskManager::load(data_dir + "/super_tasks.csv") {
    Ok(mgr) => {
      println("Loaded \{mgr.count()} super tasks")
      mgr
    }
    Err(e) => {
      println("Warning: Failed to load super tasks: \{e}")
      @lib.SuperTaskManager::new(Map([], capacity=0))
    }
  }

  // Initialize stats manager from agents
  let stats_manager = @lib.StatsManager::new(
    Map([], capacity=agent_manager.count()),
  )
  stats_manager.init(agent_manager)
  println("Tracking stats for \{agent_manager.count()} agents")

  // Query system info for the runtime monitor (used by /api/runtime)
  let total_memory_mb = get_system_info()
  println("System memory: \{total_memory_mb} MB")

  // Create runtime context
  let ctx = RuntimeContext::{
    api_key,
    model,
    agent_manager,
    tool_registry,
    task_manager,
    super_task_manager,
    stats_manager,
    data_dir,
    cors_origin,
    total_memory_mb,
  }

  // Start HTTP server
  println("Server running at http://localhost:\{port}")
  let addr = @socket.Addr::resolve("0.0.0.0", port~)
  @http.Server::Server(addr).run_forever(
    async fn(
      request : @http.Request,
      body : &@io.Reader,
      conn : @http.ServerConnection,
    ) {
      handle_request(ctx, request, body, conn)
    },
    allow_failure=true,
  )
}

///|
/// Runtime context shared across requests
struct RuntimeContext {
  api_key : String
  model : String
  agent_manager : @lib.AgentManager
  tool_registry : @lib.ToolRegistry
  task_manager : @lib.TaskManager
  super_task_manager : @lib.SuperTaskManager
  stats_manager : @lib.StatsManager
  data_dir : String
  cors_origin : String
  total_memory_mb : Int
}

///|
/// Route requests to appropriate handlers
async fn handle_request(
  ctx : RuntimeContext,
  request : @http.Request,
  body : &@io.Reader,
  conn : @http.ServerConnection,
) -> Unit {
  let path = request.path
  let meth = request.meth

  // Handle CORS preflight
  if meth is Options {
    conn.send_response(204, "No Content", extra_headers={
      "Access-Control-Allow-Origin": ctx.cors_origin,
      "Access-Control-Allow-Methods": "GET, POST, PUT, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization, Accept",
      "Access-Control-Max-Age": "86400",
    })
    return
  }

  // Handle GET routes
  if meth is Get {
    if path == "/" || path == "" {
      handle_index(conn)
    } else if path == "/api/agents" {
      handle_agents_json(ctx, conn)
    } else if path.has_prefix("/api/agents/detail") {
      handle_agent_detail(ctx, path, conn)
    } else if path.has_prefix("/api/agents/stats") {
      handle_agent_stats(ctx, path, conn)
    } else if path == "/api/tools" {
      handle_tools_json(ctx, conn)
    } else if path == "/api/runtime" {
      handle_runtime_json(ctx, conn)
    } else if path.has_prefix("/api/super_tasks/run") {
      handle_super_task_run(ctx, path, conn)
    } else if path == "/api/super_tasks" {
      handle_super_tasks_json(ctx, conn)
    } else if path.has_prefix("/api/tasks") {
      handle_tasks_list(ctx, path, conn)
    } else if path.has_prefix("/api/chat/history") {
      handle_chat_history(ctx, path, conn)
    } else if path.has_prefix("/api/chat/clear") {
      handle_chat_clear(ctx, path, conn)
    } else if path.has_prefix("/api/chat") {
      handle_chat_sse(ctx, path, conn)
    } else {
      conn.send_response(404, "Not Found", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("404 Not Found")
    }
  } else if meth is Post {
    if path == "/api/super_tasks" {
      handle_super_task_create(ctx, body, conn)
    } else if path == "/api/tasks" {
      handle_task_create(ctx, body, conn)
    } else if path == "/api/agents" {
      handle_agent_create(ctx, body, conn)
    } else {
      conn.send_response(404, "Not Found", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("404 Not Found")
    }
  } else if meth is Put {
    if path.has_prefix("/api/tasks") {
      handle_task_update(ctx, path, body, conn)
    } else if path.has_prefix("/api/agents") {
      handle_agent_update(ctx, path, body, conn)
    } else {
      conn.send_response(404, "Not Found", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("404 Not Found")
    }
  } else if meth is Delete {
    if path.has_prefix("/api/super_tasks") {
      handle_super_task_delete(ctx, path, conn)
    } else if path.has_prefix("/api/agents") {
      handle_agent_delete(ctx, path, conn)
    } else if path.has_prefix("/api/tasks") {
      handle_task_delete(ctx, path, conn)
    } else {
      conn.send_response(404, "Not Found", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("404 Not Found")
    }
  } else {
    conn.send_response(405, "Method Not Allowed", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("405 Method Not Allowed")
  }
}

///|
/// Serve the main HTML page
async fn handle_index(conn : @http.ServerConnection) -> Unit {
  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "text/html; charset=utf-8",
  })
  conn.write_string(index_html())
}

///|
/// Handle /api/agents - return JSON list of agents
async fn handle_agents_json(
  ctx : RuntimeContext,
  conn : @http.ServerConnection,
) -> Unit {
  let agents = ctx.agent_manager.list_all()
  let json_parts = agents.map(fn(a) {
    let stats = ctx.stats_manager.get_stats(a.id)
    let stats_json = match stats {
      Some(s) =>
        ",\"stats\":{\"total_conversations\":" +
        s.total_conversations.to_string() +
        ",\"total_input_chars\":" +
        s.total_input_chars.to_string() +
        ",\"total_output_chars\":" +
        s.total_output_chars.to_string() +
        ",\"total_prompt_tokens\":" +
        s.total_prompt_tokens.to_string() +
        ",\"total_completion_tokens\":" +
        s.total_completion_tokens.to_string() +
        ",\"last_active_at\":\"" +
        json_escape(s.last_active_at) +
        "\"}"
      None => ",\"stats\":null"
    }
    "{\"id\":\"" +
    json_escape(a.id) +
    "\",\"name\":\"" +
    json_escape(a.name) +
    "\",\"model\":\"" +
    json_escape(ctx.model) +
    "\",\"max_tokens\":" +
    a.max_tokens.to_string() +
    ",\"tool_ids\":\"" +
    json_escape(a.tool_ids) +
    "\"" +
    stats_json +
    "}"
  })
  let json = "[" + json_parts.join(",") + "]"

  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(json)
}

///|
/// Handle POST /api/agents - create a new agent
async fn handle_agent_create(
  ctx : RuntimeContext,
  body : &@io.Reader,
  conn : @http.ServerConnection,
) -> Unit {
  let body_data = body.read_all() catch {
    _ => {
      conn.send_response(400, "Bad Request", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Cannot read request body")
      return
    }
  }
  let body_str = body_data.text() catch {
    _ => {
      conn.send_response(400, "Bad Request", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Invalid UTF-8 in request body")
      return
    }
  }

  let id = extract_json_str(body_str, "id").unwrap_or("")
  let name = extract_json_str(body_str, "name").unwrap_or("")
  let model = extract_json_str(body_str, "model").unwrap_or(ctx.model)
  let instruction = extract_json_str(body_str, "instruction").unwrap_or("")
  let system_prompt = extract_json_str(body_str, "system_prompt").unwrap_or("")
  let max_tokens_str = extract_json_str(body_str, "max_tokens").unwrap_or(
    "4096",
  )
  let temperature_str = extract_json_str(body_str, "temperature").unwrap_or(
    "0.3",
  )
  let tool_ids = extract_json_str(body_str, "tool_ids").unwrap_or("")

  if id.length() == 0 || name.length() == 0 {
    conn.send_response(400, "Bad Request", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Missing required fields: id, name")
    return
  }

  let max_tokens = @lib.parse_int(max_tokens_str)
  let temperature = parse_temperature(temperature_str)

  let agent = ctx.agent_manager.create_agent(
    id, name, model, instruction, system_prompt, max_tokens, temperature, tool_ids,
  )

  // Initialize stats for the new agent
  ctx.stats_manager.init(ctx.agent_manager)

  // Persist to CSV
  let agents_file = ctx.data_dir + "/agents.csv"
  let _ = ctx.agent_manager.save(agents_file) catch { _ => Ok(()) }

  conn.send_response(201, "Created", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(
    "{\"status\":\"ok\",\"agent_id\":\"" + json_escape(agent.id) + "\"}",
  )
}

///|
/// Handle PUT /api/agents - update an existing agent
async fn handle_agent_update(
  ctx : RuntimeContext,
  path : String,
  body : &@io.Reader,
  conn : @http.ServerConnection,
) -> Unit {
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let agent_id = extract_query_param(query, "id").unwrap_or("")

  let body_data = body.read_all() catch {
    _ => {
      conn.send_response(400, "Bad Request", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Cannot read request body")
      return
    }
  }
  let body_str = body_data.text() catch {
    _ => {
      conn.send_response(400, "Bad Request", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Invalid UTF-8 in request body")
      return
    }
  }

  // Use query param id first, then fall back to body id
  let actual_id = if agent_id.length() > 0 {
    agent_id
  } else {
    extract_json_str(body_str, "id").unwrap_or("")
  }

  if actual_id.length() == 0 {
    conn.send_response(400, "Bad Request", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Missing agent id")
    return
  }

  let name = extract_json_str(body_str, "name").unwrap_or("")
  let model = extract_json_str(body_str, "model").unwrap_or("")
  let instruction = extract_json_str(body_str, "instruction").unwrap_or("")
  let system_prompt = extract_json_str(body_str, "system_prompt").unwrap_or("")
  let max_tokens_str = extract_json_str(body_str, "max_tokens").unwrap_or("")
  let temperature_str = extract_json_str(body_str, "temperature").unwrap_or("")
  let tool_ids = extract_json_str(body_str, "tool_ids").unwrap_or("")

  let max_tokens = if max_tokens_str.length() > 0 {
    @lib.parse_int(max_tokens_str)
  } else {
    0
  }
  let temperature = if temperature_str.length() > 0 {
    parse_temperature(temperature_str)
  } else {
    0.0
  }

  match
    ctx.agent_manager.update_agent(
      actual_id,
      name~,
      model~,
      instruction~,
      system_prompt~,
      max_tokens~,
      temperature~,
      tool_ids~,
    ) {
    Some(_) => {
      // Persist to CSV
      let agents_file = ctx.data_dir + "/agents.csv"
      let _ = ctx.agent_manager.save(agents_file) catch { _ => Ok(()) }

      conn.send_response(200, "OK", extra_headers={
        "Content-Type": "application/json; charset=utf-8",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string(
        "{\"status\":\"ok\",\"agent_id\":\"" + json_escape(actual_id) + "\"}",
      )
    }
    None => {
      conn.send_response(404, "Not Found", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Agent not found: " + actual_id)
    }
  }
}

///|
/// Handle DELETE /api/agents - delete an agent
async fn handle_agent_delete(
  ctx : RuntimeContext,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let agent_id = extract_query_param(query, "id").unwrap_or("")

  if agent_id.length() == 0 {
    conn.send_response(400, "Bad Request", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Missing agent id")
    return
  }

  if ctx.agent_manager.delete_agent(agent_id) {
    // Persist to CSV
    let agents_file = ctx.data_dir + "/agents.csv"
    let _ = ctx.agent_manager.save(agents_file) catch { _ => Ok(()) }

    conn.send_response(200, "OK", extra_headers={
      "Content-Type": "application/json; charset=utf-8",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string(
      "{\"status\":\"ok\",\"message\":\"Agent deleted: " +
      json_escape(agent_id) +
      "\"}",
    )
  } else {
    conn.send_response(404, "Not Found", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Agent not found: " + agent_id)
  }
}

///|
/// Handle DELETE /api/tasks - delete a task
async fn handle_task_delete(
  ctx : RuntimeContext,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let task_id = extract_query_param(query, "id").unwrap_or("")

  if task_id.length() == 0 {
    conn.send_response(400, "Bad Request", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Missing task id")
    return
  }

  // Check if task exists
  let task = ctx.task_manager.find(task_id)
  if task is None {
    conn.send_response(404, "Not Found", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Task not found: " + task_id)
    return
  }

  // Delete the task (need to add this method)
  // For now, just return success
  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(
    "{\"status\":\"ok\",\"message\":\"Task deleted: " +
    json_escape(task_id) +
    "\"}",
  )
}

///|
/// Handle /api/agents/detail - return detailed agent info with tools, tasks, and stats
async fn handle_agent_detail(
  ctx : RuntimeContext,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let agent_id = extract_query_param(query, "agent_id").unwrap_or("")

  if agent_id.length() == 0 {
    conn.send_response(400, "Bad Request", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Missing agent_id parameter")
    return
  }

  // Find the agent
  let agent = match ctx.agent_manager.find(agent_id) {
    Some(a) => a
    None => {
      conn.send_response(404, "Not Found", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Agent not found: " + agent_id)
      return
    }
  }

  // Get tools assigned to this agent
  let tools = agent.get_tools(ctx.tool_registry)
  let tools_json = tools.map(fn(t) {
    "{\"id\":\"" +
    json_escape(t.id) +
    "\",\"name\":\"" +
    json_escape(t.name) +
    "\",\"description\":\"" +
    json_escape(t.description) +
    "\",\"version\":\"" +
    json_escape(t.version) +
    "\",\"enabled\":" +
    (if t.enabled { "true" } else { "false" }) +
    "}"
  })
  let tools_array = "[" + tools_json.join(",") + "]"

  // Get tasks for this agent
  let tasks = ctx.task_manager.list_by_agent(agent_id)
  let tasks_json = tasks.map(fn(t) {
    let status_str = match t.status {
      @lib.Pending => "\"pending\""
      @lib.Running => "\"running\""
      @lib.Completed => "\"completed\""
      @lib.Failed(_) => "\"failed\""
    }
    "{\"id\":\"" +
    json_escape(t.id) +
    "\",\"description\":\"" +
    json_escape(t.description) +
    "\",\"status\":" +
    status_str +
    ",\"priority\":" +
    t.priority.to_string() +
    ",\"created_at\":\"" +
    json_escape(t.created_at) +
    "\",\"updated_at\":\"" +
    json_escape(t.updated_at) +
    "\",\"duration_ms\":" +
    t.duration_ms.to_string() +
    "}"
  })
  let tasks_array = "[" + tasks_json.join(",") + "]"

  // Get stats
  let stats = ctx.stats_manager.get_stats(agent_id)
  let stats_json = match stats {
    Some(s) =>
      "{\"total_conversations\":" +
      s.total_conversations.to_string() +
      ",\"total_input_chars\":" +
      s.total_input_chars.to_string() +
      ",\"total_output_chars\":" +
      s.total_output_chars.to_string() +
      ",\"total_prompt_tokens\":" +
      s.total_prompt_tokens.to_string() +
      ",\"total_completion_tokens\":" +
      s.total_completion_tokens.to_string() +
      ",\"last_active_at\":\"" +
      json_escape(s.last_active_at) +
      "\"}"
    None => "null"
  }

  // Get conversation history summary (count only, to avoid large responses)
  let history = ctx.agent_manager.get_conversation(agent_id)
  let history_count = history.length()

  // Build the full response
  let json = "{\"id\":\"" +
    json_escape(agent.id) +
    "\",\"name\":\"" +
    json_escape(agent.name) +
    "\",\"model\":\"" +
    json_escape(ctx.model) +
    "\",\"instruction\":\"" +
    json_escape(agent.instruction) +
    "\",\"system_prompt\":\"" +
    json_escape(agent.system_prompt) +
    "\",\"max_tokens\":" +
    agent.max_tokens.to_string() +
    ",\"temperature\":" +
    agent.temperature.to_string() +
    ",\"tool_ids\":\"" +
    json_escape(agent.tool_ids) +
    "\",\"tools\":" +
    tools_array +
    ",\"tasks\":" +
    tasks_array +
    ",\"stats\":" +
    stats_json +
    ",\"conversation_count\":" +
    history_count.to_string() +
    "}"

  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(json)
}

///|
/// Handle /api/agents/stats - return agent statistics
async fn handle_agent_stats(
  ctx : RuntimeContext,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let agent_id = extract_query_param(query, "agent_id").unwrap_or("")

  let json = if agent_id.length() > 0 {
    // Return stats for specific agent
    match ctx.stats_manager.get_stats(agent_id) {
      Some(s) =>
        "{\"agent_id\":\"" +
        json_escape(s.agent_id) +
        "\",\"total_conversations\":" +
        s.total_conversations.to_string() +
        ",\"total_input_chars\":" +
        s.total_input_chars.to_string() +
        ",\"total_output_chars\":" +
        s.total_output_chars.to_string() +
        ",\"total_prompt_tokens\":" +
        s.total_prompt_tokens.to_string() +
        ",\"total_completion_tokens\":" +
        s.total_completion_tokens.to_string() +
        ",\"last_active_at\":\"" +
        json_escape(s.last_active_at) +
        "\"}"
      None => "{\"error\":\"Agent not found: " + json_escape(agent_id) + "\"}"
    }
  } else {
    // Return all stats
    let all_stats = ctx.stats_manager.get_all_stats()
    let json_parts = all_stats.map(fn(s) {
      "{\"agent_id\":\"" +
      json_escape(s.agent_id) +
      "\",\"total_conversations\":" +
      s.total_conversations.to_string() +
      ",\"total_input_chars\":" +
      s.total_input_chars.to_string() +
      ",\"total_output_chars\":" +
      s.total_output_chars.to_string() +
      ",\"total_prompt_tokens\":" +
      s.total_prompt_tokens.to_string() +
      ",\"total_completion_tokens\":" +
      s.total_completion_tokens.to_string() +
      ",\"last_active_at\":\"" +
      json_escape(s.last_active_at) +
      "\"}"
    })
    "[" + json_parts.join(",") + "]"
  }

  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(json)
}

///|
/// Handle /api/tools - return JSON list of tools
async fn handle_tools_json(
  ctx : RuntimeContext,
  conn : @http.ServerConnection,
) -> Unit {
  let tools = ctx.tool_registry.list_enabled()
  let json_parts = tools.map(fn(t) {
    "{\"id\":\"" +
    t.id +
    "\",\"name\":\"" +
    t.name +
    "\",\"description\":\"" +
    t.description +
    "\",\"enabled\":true}"
  })
  let json = "[" + json_parts.join(",") + "]"

  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(json)
}

///|
/// Handle /api/runtime - return process memory and CPU usage
async fn handle_runtime_json(
  ctx : RuntimeContext,
  conn : @http.ServerConnection,
) -> Unit {
  let (memory_mb, cpu_ms) = get_process_stats()
  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(
    "{\"memory_mb\":" +
    memory_mb.to_string() +
    ",\"cpu_ms\":" +
    cpu_ms.to_string() +
    ",\"total_memory_mb\":" +
    ctx.total_memory_mb.to_string() +
    "}",
  )
}

///|
/// Query total physical memory in MB via PowerShell (called once at startup)
async fn get_system_info() -> Int {
  let script = "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; $os=Get-CimInstance Win32_OperatingSystem; Write-Output ([math]::Round($os.TotalVisibleMemorySize*1KB/1MB).ToString())"
  let (_, output) = @process.collect_stdout("powershell", [
    "-NoProfile", "-NonInteractive", "-Command", script,
  ]) catch {
    _ => return 0
  }
  let raw = output.text() catch { _ => return 0 }
  parse_clean_int(raw)
}

///|
/// Query the runtime process working set (MB) and CPU time (ms) via PowerShell
async fn get_process_stats() -> (Int, Int) {
  let script = "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; $p=Get-Process -Name main -ErrorAction SilentlyContinue; if($p){Write-Output ([math]::Round($p.WorkingSet64/1MB).ToString() + ';' + [math]::Round($p.CPU*1000).ToString())}else{Write-Output '0;0'}"
  let (_, output) = @process.collect_stdout("powershell", [
    "-NoProfile", "-NonInteractive", "-Command", script,
  ]) catch {
    _ => return (0, 0)
  }
  let raw = output.text() catch { _ => return (0, 0) }
  parse_pair_clean(raw)
}

///|
/// Keep only ASCII digit bytes (tolerates BOM/UTF-16 nulls/whitespace) and parse as int
/// NOTE: s[i] yields a byte; s[i].to_string() gives its decimal value, so convert manually
fn parse_clean_int(s : String) -> Int {
  let mut cleaned = ""
  let mut i = 0
  while i < s.length() {
    let b = s[i]
    if b >= 48 && b <= 57 {
      cleaned = cleaned + (b.to_int() - 48).to_string()
    }
    i = i + 1
  }
  @lib.parse_int(cleaned)
}

///|
/// Parse "a;b" output into a pair of ints, tolerating BOM/UTF-16 nulls/whitespace
fn parse_pair_clean(s : String) -> (Int, Int) {
  let mut cleaned = ""
  let mut i = 0
  while i < s.length() {
    let b = s[i]
    if b >= 48 && b <= 57 {
      cleaned = cleaned + (b.to_int() - 48).to_string()
    } else if b == 59 {
      cleaned = cleaned + ";"
    }
    i = i + 1
  }
  let parts = cleaned.split(";").to_array()
  if parts.length() >= 2 {
    (@lib.parse_int(parts[0].to_owned()), @lib.parse_int(parts[1].to_owned()))
  } else {
    (0, 0)
  }
}

///|
/// Handle /api/chat/history - return conversation history for an agent
async fn handle_chat_history(
  ctx : RuntimeContext,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let agent_id = extract_query_param(query, "agent_id").unwrap_or("")

  if agent_id.length() == 0 {
    conn.send_response(400, "Bad Request", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Missing agent_id parameter")
    return
  }

  let history = ctx.agent_manager.get_conversation(agent_id)
  let json_parts = history.map(fn(msg) {
    "{\"role\":\"" +
    json_escape(msg.role) +
    "\",\"content\":\"" +
    json_escape(msg.content) +
    "\"}"
  })
  let json = "[" + json_parts.join(",") + "]"

  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(json)
}

///|
/// Handle /api/chat/clear - clear conversation history for an agent
async fn handle_chat_clear(
  ctx : RuntimeContext,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let agent_id = extract_query_param(query, "agent_id").unwrap_or("")

  if agent_id.length() == 0 {
    conn.send_response(400, "Bad Request", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Missing agent_id parameter")
    return
  }

  ctx.agent_manager.clear_conversation(agent_id)

  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(
    "{\"status\":\"ok\",\"message\":\"Conversation cleared for " +
    agent_id +
    "\"}",
  )
}

///|
/// Handle /api/chat - SSE streaming chat with agent
async fn handle_chat_sse(
  ctx : RuntimeContext,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  // Parse query parameters from path
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let agent_id = extract_query_param(query, "agent_id").unwrap_or("agent_001")
  let message = extract_query_param(query, "message").unwrap_or("Hello")

  // Find the agent
  let agent = match ctx.agent_manager.find(agent_id) {
    Some(a) => a
    None => {
      conn.send_response(404, "Not Found", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Agent not found: " + agent_id)
      return
    }
  }

  // Build messages for DeepSeek API with stream=true
  let messages = agent.build_prompt(
    message,
    ctx.tool_registry,
    ctx.task_manager,
  )
  let msgs = messages.map(fn(msg) {
    "{\"role\":\"" +
    json_escape(msg.role) +
    "\",\"content\":\"" +
    json_escape(msg.content) +
    "\"}"
  })
  let msg_json = "[" + msgs.join(",") + "]"
  let tools_json = build_tools_json(agent, ctx.tool_registry)
  let body_str = build_chat_body(
    ctx.model,
    msg_json,
    tools_json,
    agent.max_tokens,
    agent.temperature,
  )

  // Send SSE response headers first
  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    "Connection": "keep-alive",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })

  // Store user message in conversation history
  ctx.agent_manager.add_message(agent_id, "user", message)
  let input_chars = message.length()

  // Run model rounds: native function call -> execute tool -> feed result back,
  // up to MAX_TOOL_ROUNDS times. The model may first emit empty/invalid
  // arguments, so follow-up rounds let it correct itself using the tool's
  // error message (and any tool call it issues in a follow-up is executed too).
  // 24 rounds lets a super task (e.g. researching several market indices)
  // run several sequential searches and still produce its final report.
  let all_msg_parts : Array[String] = msgs
  let mut round_body = body_str
  let mut output_chars = 0
  let mut prompt_tokens = 0
  let mut completion_tokens = 0
  let mut rounds = 0
  while rounds < 24 {
    let (resp, tool_name, tool_args, tool_call_id, pt, ct) = stream_completion(
      ctx, round_body, conn,
    )
    if pt > 0 {
      prompt_tokens = pt
    }
    if ct > 0 {
      completion_tokens = ct
    }
    if tool_call_id.length() == 0 {
      // No native tool call: on round 1, fall back to text-based tool detection
      if rounds == 0 {
        match @lib.parse_tool_call(resp) {
          Some((tname, args_json)) => {
            conn.write_string("data: **[Tool Call]: " + tname + "**\n\n")
            conn.flush()
            let tool_result = @lib.execute_tool(tname, args_json, ctx.data_dir) catch {
              e => "错误: 工具执行异常: " + e.to_string()
            }
            conn.write_string(
              "data: **[Tool Result]:**\n" + tool_result + "\n\n",
            )
            conn.flush()
            ctx.agent_manager.add_message(agent_id, "assistant", resp)
            output_chars = output_chars + resp.length()
            break
          }
          None => {
            ctx.agent_manager.add_message(agent_id, "assistant", resp)
            output_chars = output_chars + resp.length()
            break
          }
        }
      }
      ctx.agent_manager.add_message(agent_id, "assistant", resp)
      output_chars = output_chars + resp.length()
      break
    }
    // Native tool call: notify the client and execute the tool
    conn.write_string("data: **[Tool Call]: " + tool_name + "**\n\n")
    conn.flush()
    let tool_result = @lib.execute_tool(tool_name, tool_args, ctx.data_dir) catch {
      e => "错误: 工具执行异常: " + e.to_string()
    }
    conn.write_string("data: **[Tool Result]:**\n" + tool_result + "\n\n")
    conn.flush()

    // Store the assistant tool-call message in conversation history
    ctx.agent_manager.add_message(agent_id, "assistant", resp)
    output_chars = output_chars + resp.length()

    // Feed the tool result back to the model for the next round
    let assistant_tool_msg = "{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"" +
      json_escape(tool_call_id) +
      "\",\"type\":\"function\",\"function\":{\"name\":\"" +
      json_escape(tool_name) +
      "\",\"arguments\":\"" +
      json_escape(tool_args) +
      "\"}}]}"
    let tool_msg = "{\"role\":\"tool\",\"tool_call_id\":\"" +
      json_escape(tool_call_id) +
      "\",\"content\":\"" +
      json_escape(tool_result) +
      "\"}"
    // Keep tools in every follow-up request so the model does not hallucinate
    // tool-call XML/text instead of producing the final answer
    all_msg_parts.push(assistant_tool_msg)
    all_msg_parts.push(tool_msg)
    let follow_json = "[" + all_msg_parts.join(",") + "]"
    round_body = build_chat_body(
      ctx.model,
      follow_json,
      tools_json,
      agent.max_tokens,
      agent.temperature,
    )
    rounds = rounds + 1
  }

  // Record stats with token usage if available
  ctx.stats_manager.record_conversation(
    agent_id,
    input_chars,
    output_chars,
    prompt_tokens~,
    completion_tokens~,
  )
  conn.write_string("data: [DONE]\n\n")
}

///|
/// Build the tools array (OpenAI-style function calling) for the agent's assigned tools.
/// Returns "" when the agent has no tools (tools key is then omitted from the request).
fn build_tools_json(agent : @lib.Agent, registry : @lib.ToolRegistry) -> String {
  let tools = agent.get_tools(registry)
  if tools.length() == 0 {
    return ""
  }
  let tool_jsons = tools.map(fn(t) {
    "{\"type\":\"function\",\"function\":{\"name\":\"" +
    json_escape(t.name) +
    "\",\"description\":\"" +
    json_escape(t.description) +
    "\",\"parameters\":" +
    build_parameters_schema(t.input_schema) +
    "}}"
  })
  "[" + tool_jsons.join(",") + "]"
}

///|
/// Convert a simple properties map like {"expression": "string", "path": "string"}
/// into a valid JSON Schema parameters object with required fields:
/// {"type":"object","properties":{"expression":{"type":"string"}},"required":["expression"]}
/// Marking all keys as required prevents the model from emitting empty arguments {}.
fn build_parameters_schema(input_schema : String) -> String {
  let mut properties = "{"
  let required : Array[String] = []
  let mut first = true
  let mut i = 0
  while i < input_schema.length() {
    if input_schema[i] == 34 { // opening quote of a key
      let mut key_end = i + 1
      while key_end < input_schema.length() && input_schema[key_end] != 34 {
        key_end = key_end + 1
      }
      let key = input_schema[i + 1:key_end].to_owned()
      let mut colon = key_end + 1
      while colon < input_schema.length() && input_schema[colon] != 58 {
        colon = colon + 1
      } // ':'
      let mut vs = colon + 1
      while vs < input_schema.length() && input_schema[vs] != 34 {
        vs = vs + 1
      }
      let mut ve = vs + 1
      while ve < input_schema.length() && input_schema[ve] != 34 {
        ve = ve + 1
      }
      let typ = input_schema[vs + 1:ve].to_owned()
      if !first {
        properties = properties + ","
      }
      properties = properties +
        "\"" +
        json_escape(key) +
        "\":{\"type\":\"" +
        json_escape(typ) +
        "\"}"
      required.push(key)
      first = false
      i = ve + 1
    } else {
      i = i + 1
    }
  }
  properties = properties + "}"
  "{\"type\":\"object\",\"properties\":" +
  properties +
  ",\"required\":[" +
  required.map(fn(k) { "\"" + json_escape(k) + "\"" }).join(",") +
  "]}"
}

///|
/// Build the chat completion request body (stream=true)
fn build_chat_body(
  model : String,
  messages_json : String,
  tools_json : String,
  max_tokens : Int,
  temperature : Float,
) -> String {
  let tools_part = if tools_json.length() > 0 {
    ",\"tools\":" + tools_json
  } else {
    ""
  }
  "{\"model\":\"" +
  json_escape(model) +
  "\",\"messages\":" +
  messages_json +
  tools_part +
  ",\"max_tokens\":" +
  max_tokens.to_string() +
  ",\"temperature\":" +
  temperature.to_string() +
  ",\"stream\":true}"
}

///|
/// Stream one completion request from DeepSeek, forwarding content to the client.
/// Returns (full_response, tool_name, tool_args, tool_call_id, prompt_tokens, completion_tokens).
/// tool_name/tool_args/tool_call_id are non-empty when the model issued a native function call.
async fn stream_completion(
  ctx : RuntimeContext,
  body_str : String,
  conn : @http.ServerConnection,
) -> (String, String, String, String, Int, Int) {
  let url = "https://api.deepseek.com/v1/chat/completions"
  let req_headers = Map([
    ("Content-Type", "application/json"),
    ("Authorization", "Bearer " + ctx.api_key),
  ])

  let mut full_response = ""
  let mut tool_name = ""
  let mut tool_args = ""
  let mut tool_call_id = ""
  let mut prompt_tokens = 0
  let mut completion_tokens = 0
  // Raw JSON chunks that contain a native tool call. The arguments string of a
  // function call is streamed across many SSE chunks, and the JSON escape
  // sequences (e.g. \" inside arguments) can be split at chunk boundaries.
  // Parsing each chunk independently corrupts the value, so we buffer the raw
  // chunks and parse the whole buffer once after the stream finishes.
  let mut tool_calls_raw = ""

  try {
    let client = @http.post_stream(url, headers=req_headers)
    defer client.close()

    client.write(body_str)
    client.flush()
    let resp = client.end_request()

    if resp.code != 200 {
      conn.write_string("data: Error: DeepSeek returned \{resp.code}\n\n")
      return (
        full_response, tool_name, tool_args, tool_call_id, prompt_tokens, completion_tokens,
      )
    }

    // Forward streaming chunks from DeepSeek to client.
    // SSE events can be split across read_some() calls, and a multi-byte UTF-8
    // char (streamed Chinese content) can also be split at a buffer boundary.
    // Decoding lossy + line buffering guarantees no whole buffer (which may
    // contain tool-call payloads) is ever dropped.
    let mut buf = ""
    while client.read_some() is Some(data) {
      buf = buf + @utf8.decode_lossy(data)
      while true {
        match string_find(buf, "\n") {
          None => break
          Some(pos) => {
            let line = buf[0:pos].to_owned()
            buf = buf[pos + 1:].to_owned()
            if line.has_prefix("data: ") {
              let payload = line[6:].to_owned()
              if payload == "[DONE]" {
                // Parse the buffered tool-call JSON now that the stream is complete
                if tool_calls_raw.length() > 0 {
                  match extract_tool_call_id(tool_calls_raw) {
                    Some(id) => tool_call_id = id
                    None => ()
                  }
                  match extract_json_str_value(tool_calls_raw, "name") {
                    Some(name) => tool_name = name
                    None => ()
                  }
                  tool_args = unescape_json_str(
                    extract_all_json_str_values(tool_calls_raw, "arguments"),
                  )
                }
                return (
                  full_response, tool_name, tool_args, tool_call_id, prompt_tokens,
                  completion_tokens,
                )
              }
              // Check for usage info in the payload
              if string_find(payload, "\"usage\"") is Some(_) {
                match extract_usage_tokens(payload) {
                  Some((pt, ct)) => {
                    prompt_tokens = pt
                    completion_tokens = ct
                  }
                  None => ()
                }
              }
              // Extract streamed content
              match extract_content(payload) {
                Some(content) => {
                  full_response = full_response + content
                  conn.write_string("data: " + content + "\n\n")
                }
                None => ()
              }
              // Buffer raw tool-call chunks for end-of-stream parsing
              if string_find(payload, "\"tool_calls\"") is Some(_) {
                tool_calls_raw = tool_calls_raw + payload
              }
            }
          }
        }
      }
      conn.flush()
    }
    // Stream ended without [DONE]: process the final partial line, if any
    if buf.length() > 0 && buf.has_prefix("data: ") {
      let payload = buf[6:].to_owned()
      if string_find(payload, "\"tool_calls\"") is Some(_) {
        tool_calls_raw = tool_calls_raw + payload
      }
      match extract_content(payload) {
        Some(content) => {
          full_response = full_response + content
          conn.write_string("data: " + content + "\n\n")
        }
        None => ()
      }
    }
    // Stream ended without [DONE]: parse any buffered tool-call JSON
    if tool_calls_raw.length() > 0 {
      match extract_tool_call_id(tool_calls_raw) {
        Some(id) => tool_call_id = id
        None => ()
      }
      match extract_json_str_value(tool_calls_raw, "name") {
        Some(name) => tool_name = name
        None => ()
      }
      tool_args = unescape_json_str(
        extract_all_json_str_values(tool_calls_raw, "arguments"),
      )
    }
  } catch {
    _ => conn.write_string("data: Error: Failed to connect to DeepSeek\n\n")
  }
  (
    full_response, tool_name, tool_args, tool_call_id, prompt_tokens, completion_tokens,
  )
}

///|
/// Extract every string value of a JSON key in a raw SSE buffer (e.g. "arguments"),
/// concatenating the raw (still-escaped) values. Used because a function-call
/// arguments string is streamed across many chunks; each chunk carries a valid
/// JSON string fragment, and concatenating the raw fragments lets us unescape
/// once at the end without corrupting escapes split across chunk boundaries.
fn extract_all_json_str_values(json : String, key : String) -> String {
  let search = "\"" + key + "\":"
  let mut result = ""
  let mut search_from = 0
  while search_from <= json.length() {
    match string_find_from(json, search, search_from) {
      None => break
      Some(s) => {
        let mut i = s + search.length()
        while i < json.length() && (json[i] == 32 || json[i] == 9) {
          i = i + 1
        }
        if i < json.length() && json[i] == 34 {
          let mut j = i + 1
          let mut end = -1
          while j < json.length() {
            if json[j] == 92 { // backslash: skip the escaped char
              j = j + 2
            } else if json[j] == 34 { // closing quote
              end = j
              break
            } else {
              j = j + 1
            }
          }
          if end >= 0 {
            result = result + json[i + 1:end].to_owned()
            search_from = end + 1
          } else {
            search_from = s + 1
          }
        } else {
          search_from = s + 1
        }
      }
    }
  }
  result
}

///|
/// Find the first occurrence of a pattern starting from a given byte index
fn string_find_from(s : String, pat : String, from : Int) -> Int? {
  if pat.length() == 0 {
    return Some(from)
  }
  let max_start = s.length() - pat.length()
  let mut i = if from < 0 { 0 } else { from }
  while i <= max_start {
    let mut matched = true
    let mut j = 0
    while j < pat.length() {
      if s[i + j] != pat[j] {
        matched = false
        break
      }
      j = j + 1
    }
    if matched {
      return Some(i)
    }
    i = i + 1
  }
  None
}

///|
/// Extract the tool-call id from a raw tool-call buffer. The buffer contains
/// many "id" keys (chat completion id, tool call id), so we match the
/// "call_" prefix that identifies the tool call.
fn extract_tool_call_id(json : String) -> String? {
  let search = "\"id\":\"call_"
  match string_find(json, search) {
    None => None
    Some(s) => {
      let start = s + 6 // after "\"id\":\""
      let mut end = start
      while end < json.length() && json[end] != 34 {
        end = end + 1
      }
      if end > start {
        Some(json[start:end].to_owned())
      } else {
        None
      }
    }
  }
}

///|
/// Unescape a raw JSON string value (\" \\ \n \r \t) into its plain-text form
fn unescape_json_str(raw : String) -> String {
  let mut result = ""
  let mut i = 0
  while i < raw.length() {
    let ch = raw[i]
    if ch == 92 && i + 1 < raw.length() { // backslash
      let next = raw[i + 1]
      if next == 34 {
        result = result + "\""
        i = i + 2
      } else if next == 92 {
        result = result + "\\"
        i = i + 2
      } else if next == 110 {
        result = result + "\n"
        i = i + 2
      } else if next == 114 {
        result = result + "\r"
        i = i + 2
      } else if next == 116 {
        result = result + "\t"
        i = i + 2
      } else {
        match ch.to_char() {
          Some(c) => result = result + c.to_string()
          None => ()
        }
        i = i + 1
      }
    } else {
      match ch.to_char() {
        Some(c) => result = result + c.to_string()
        None => ()
      }
      i = i + 1
    }
  }
  result
}

///|
/// Extract the string value of a JSON key such as "key":"value" (or "key": "value"),
/// unescaping \" \\ \n \r \t. Works on byte-indexed strings.
fn extract_json_str_value(json : String, key : String) -> String? {
  let search = "\"" + key + "\":"
  let start = match string_find(json, search) {
    None => return None
    Some(s) => s + search.length()
  }
  let mut i = start
  while i < json.length() && (json[i] == 32 || json[i] == 9) {
    i = i + 1
  }
  if i >= json.length() || json[i] != 34 {
    return None
  }
  let mut result = ""
  let mut j = i + 1
  while j < json.length() {
    let ch = json[j]
    if ch == 92 { // backslash
      if j + 1 < json.length() {
        let next = json[j + 1]
        if next == 34 {
          result = result + "\""
          j = j + 2
        } else if next == 92 {
          result = result + "\\"
          j = j + 2
        } else if next == 110 {
          result = result + "\n"
          j = j + 2
        } else if next == 114 {
          result = result + "\r"
          j = j + 2
        } else if next == 116 {
          result = result + "\t"
          j = j + 2
        } else {
          j = j + 1
        }
      } else {
        j = j + 1
      }
    } else if ch == 34 { // closing quote
      return Some(result)
    } else {
      match ch.to_char() {
        Some(c) => result = result + c.to_string()
        None => ()
      }
      j = j + 1
    }
  }
  None
}

///|
/// Handle GET /api/tasks - list tasks, optionally filtered by agent_id
async fn handle_tasks_list(
  ctx : RuntimeContext,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let agent_id = extract_query_param(query, "agent_id").unwrap_or("")

  let tasks = if agent_id.length() > 0 {
    ctx.task_manager.list_by_agent(agent_id)
  } else {
    ctx.task_manager.list_all()
  }

  let json_parts = tasks.map(fn(t) {
    let status_str = match t.status {
      @lib.Pending => "\"pending\""
      @lib.Running => "\"running\""
      @lib.Completed => "\"completed\""
      @lib.Failed(_) => "\"failed\""
    }
    let duration_str = t.duration_ms.to_string()
    "{\"id\":\"" +
    json_escape(t.id) +
    "\",\"agent_id\":\"" +
    json_escape(t.agent_id) +
    "\",\"description\":\"" +
    json_escape(t.description) +
    "\",\"status\":" +
    status_str +
    ",\"priority\":" +
    t.priority.to_string() +
    ",\"created_at\":\"" +
    json_escape(t.created_at) +
    "\",\"updated_at\":\"" +
    json_escape(t.updated_at) +
    "\",\"duration_ms\":" +
    duration_str +
    "}"
  })
  let json = "[" + json_parts.join(",") + "]"

  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(json)
}

///|
/// Handle POST /api/tasks - create a new task
async fn handle_task_create(
  ctx : RuntimeContext,
  body : &@io.Reader,
  conn : @http.ServerConnection,
) -> Unit {
  // Read body
  let body_data = body.read_all() catch {
    _ => {
      conn.send_response(400, "Bad Request", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Cannot read request body")
      return
    }
  }
  let body_str = body_data.text() catch {
    _ => {
      conn.send_response(400, "Bad Request", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Invalid UTF-8 in request body")
      return
    }
  }

  // Parse JSON fields (simple parsing)
  let id = extract_json_str(body_str, "id").unwrap_or(
    "task_" + ctx.task_manager.count().to_string(),
  )
  let agent_id = extract_json_str(body_str, "agent_id").unwrap_or("")
  let description = extract_json_str(body_str, "description").unwrap_or("")
  let priority_str = extract_json_str(body_str, "priority").unwrap_or("0")
  let priority = @lib.parse_int(priority_str)

  if agent_id.length() == 0 || description.length() == 0 {
    conn.send_response(400, "Bad Request", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Missing required fields: agent_id, description")
    return
  }

  let _task = ctx.task_manager.create_task(id, agent_id, description, priority)

  conn.send_response(201, "Created", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(
    "{\"status\":\"ok\",\"task_id\":\"" + json_escape(id) + "\"}",
  )
}

///|
/// Handle GET /api/super_tasks - return JSON list of super tasks
async fn handle_super_tasks_json(
  ctx : RuntimeContext,
  conn : @http.ServerConnection,
) -> Unit {
  let tasks = ctx.super_task_manager.list_all()
  let json_parts = tasks.map(fn(st) {
    "{\"id\":\"" +
    json_escape(st.id) +
    "\",\"name\":\"" +
    json_escape(st.name) +
    "\",\"description\":\"" +
    json_escape(st.description) +
    "\",\"agent_id\":\"" +
    json_escape(st.agent_id) +
    "\",\"created_at\":\"" +
    json_escape(st.created_at) +
    "\"}"
  })
  let json = "[" + json_parts.join(",") + "]"

  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(json)
}

///|
/// Handle POST /api/super_tasks - create a custom super task
async fn handle_super_task_create(
  ctx : RuntimeContext,
  body : &@io.Reader,
  conn : @http.ServerConnection,
) -> Unit {
  let body_data = body.read_all() catch {
    _ => {
      conn.send_response(400, "Bad Request", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Cannot read request body")
      return
    }
  }
  let body_str = body_data.text() catch {
    _ => {
      conn.send_response(400, "Bad Request", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Invalid UTF-8 in request body")
      return
    }
  }

  let name = extract_json_str(body_str, "name").unwrap_or("")
  let description = extract_json_str(body_str, "description").unwrap_or("")
  let agent_id = extract_json_str(body_str, "agent_id").unwrap_or("agent_005")

  if name.length() == 0 || description.length() == 0 {
    conn.send_response(400, "Bad Request", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Missing required fields: name, description")
    return
  }

  // Pick the first free numeric id (st_001 is reserved for the default task)
  let mut n = ctx.super_task_manager.count() + 1
  let mut id = "st_" + n.to_string()
  while ctx.super_task_manager.find(id) is Some(_) {
    n = n + 1
    id = "st_" + n.to_string()
  }
  let st = ctx.super_task_manager.create(id, name, description, agent_id)
  let _ = ctx.super_task_manager.save(ctx.data_dir + "/super_tasks.csv") catch {
    _ => Ok(())
  }

  conn.send_response(201, "Created", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(
    "{\"status\":\"ok\",\"id\":\"" +
    json_escape(st.id) +
    "\",\"name\":\"" +
    json_escape(st.name) +
    "\",\"description\":\"" +
    json_escape(st.description) +
    "\",\"agent_id\":\"" +
    json_escape(st.agent_id) +
    "\",\"created_at\":\"" +
    json_escape(st.created_at) +
    "\"}",
  )
}

///|
/// Handle DELETE /api/super_tasks?id=... - delete a custom super task
async fn handle_super_task_delete(
  ctx : RuntimeContext,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let id = extract_query_param(query, "id").unwrap_or("")

  if id.length() == 0 {
    conn.send_response(400, "Bad Request", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Missing id parameter")
    return
  }
  if id == "st_001" {
    conn.send_response(403, "Forbidden", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("The default super task cannot be deleted")
    return
  }

  if ctx.super_task_manager.delete(id) {
    let _ = ctx.super_task_manager.save(ctx.data_dir + "/super_tasks.csv") catch {
      _ => Ok(())
    }
    conn.send_response(200, "OK", extra_headers={
      "Content-Type": "application/json; charset=utf-8",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string(
      "{\"status\":\"ok\",\"deleted\":\"" + json_escape(id) + "\"}",
    )
  } else {
    conn.send_response(404, "Not Found", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Super task not found: " + id)
  }
}

///|
/// Handle GET /api/super_tasks/run?id=... - execute a super task
/// Reuses the chat SSE pipeline: the task description becomes the user message,
/// and the agent autonomously calls tools (e.g. search_web) until it reports back.
async fn handle_super_task_run(
  ctx : RuntimeContext,
  path : String,
  conn : @http.ServerConnection,
) -> Unit {
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let id = extract_query_param(query, "id").unwrap_or("")
  let agent_override = extract_query_param(query, "agent_id").unwrap_or("")

  let st = match ctx.super_task_manager.find(id) {
    Some(s) => s
    None => {
      conn.send_response(404, "Not Found", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Super task not found: " + id)
      return
    }
  }
  let agent_id = if agent_override.length() > 0 {
    agent_override
  } else {
    st.agent_id
  }
  let guided_message = st.description +
    "\n\n执行指引:请把该任务拆解为依次检索每个指数。每个指数最多搜索1-2次,第一次请用短关键词(例如: 标普500 行情 / KOSPI 行情 / 日经225 行情 / 纳斯达克 行情 / 上证指数 行情),若搜索结果中出现东方财富、新浪财经、英为财情等行情页面,即视为已获得该指数的有效行情信息,立即进入下一个指数,不要重复搜索同一主题,也不要为了追求精确点位反复重试。全部指数检索完毕后,请立即停止调用工具,用 Markdown 表格输出一份最终汇总报告,包含每个指数的走势、振幅、换手、盘内异动等信息(可注明数据为搜索结果快照,实时点位以链接页面为准),不要再调用任何工具。"
  let chat_path = "/api/chat?agent_id=" +
    @lib.url_encode(agent_id) +
    "&message=" +
    @lib.url_encode(guided_message)
  handle_chat_sse(ctx, chat_path, conn)
}

///|
/// Handle PUT /api/tasks - update task status
async fn handle_task_update(
  ctx : RuntimeContext,
  path : String,
  body : &@io.Reader,
  conn : @http.ServerConnection,
) -> Unit {
  let query = match string_find(path, "?") {
    Some(pos) => path[pos + 1:].to_owned()
    None => ""
  }
  let task_id = extract_query_param(query, "id").unwrap_or("")

  // Also try to read from body
  let body_data = body.read_all() catch {
    _ => {
      conn.send_response(400, "Bad Request", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Cannot read request body")
      return
    }
  }
  let body_str = body_data.text() catch {
    _ => {
      conn.send_response(400, "Bad Request", extra_headers={
        "Content-Type": "text/plain",
        "Access-Control-Allow-Origin": ctx.cors_origin,
      })
      conn.write_string("Invalid UTF-8 in request body")
      return
    }
  }

  let actual_id = if task_id.length() > 0 {
    task_id
  } else {
    extract_json_str(body_str, "id").unwrap_or("")
  }
  let status_str = extract_json_str(body_str, "status").unwrap_or("")

  if actual_id.length() == 0 || status_str.length() == 0 {
    conn.send_response(400, "Bad Request", extra_headers={
      "Content-Type": "text/plain",
      "Access-Control-Allow-Origin": ctx.cors_origin,
    })
    conn.write_string("Missing required fields: id, status")
    return
  }

  let new_status = @lib.parse_status(status_str)

  let _ignore = ctx.task_manager.update_status(actual_id, new_status)

  conn.send_response(200, "OK", extra_headers={
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": ctx.cors_origin,
  })
  conn.write_string(
    "{\"status\":\"ok\",\"task_id\":\"" + json_escape(actual_id) + "\"}",
  )
}

///|
/// Extract a simple string value from a JSON string by key
fn extract_json_str(json : String, key : String) -> String? {
  // Try with space after colon first, then without
  let search1 = "\"" + key + "\": \""
  let search2 = "\"" + key + "\":\""
  let start = match string_find(json, search1) {
    Some(s) => Some(s)
    None => string_find(json, search2)
  }
  match start {
    None => None
    Some(s) => {
      let search = if string_find(json, search1) is Some(_) {
        search1
      } else {
        search2
      }
      let value_start = s + search.length()
      let mut result = ""
      let mut i = value_start
      while i < json.length() {
        let ch = json[i]
        if ch == '\\' && i + 1 < json.length() && json[i + 1] == '"' {
          result = result + "\""
          i = i + 2
        } else if ch == '"' {
          break
        } else {
          match ch.to_char() {
            Some(c) => result = result + c.to_string()
            None => ()
          }
          i = i + 1
        }
      }
      Some(result)
    }
  }
}

///|
/// Simple string search for a substring
fn string_find(s : String, pat : String) -> Int? {
  if pat.length() == 0 {
    return Some(0)
  }
  let max_start = s.length() - pat.length()
  let mut i = 0
  while i <= max_start {
    let mut matched = true
    let mut j = 0
    while j < pat.length() {
      if s[i + j] != pat[j] {
        matched = false
        break
      }
      j = j + 1
    }
    if matched {
      return Some(i)
    }
    i = i + 1
  }
  None
}

///|
/// Parse hex char to int (0-15)
fn hex_to_int(ch : UInt16) -> Int {
  if ch >= 48 && ch <= 57 {
    ch.to_int() - 48
  } else if ch >= 97 && ch <= 102 {
    ch.to_int() - 87
  } else if ch >= 65 && ch <= 70 {
    ch.to_int() - 55
  } else {
    0
  }
}

///|
/// Parse temperature string to float
fn parse_temperature(s : String) -> Float {
  let parts = s.split(".")
  let mut int_val = 0
  let int_part = match parts.next() {
    Some(p) => p.to_owned()
    None => return Float::from_int(0)
  }
  let mut neg = false
  let mut j = 0
  if int_part.length() > 0 && int_part[0] == '-' {
    neg = true
    j = 1
  }
  while j < int_part.length() {
    let c = int_part[j]
    if c >= '0' && c <= '9' {
      int_val = int_val * 10 + (c.to_int() - 48)
    } else {
      return Float::from_int(0)
    }
    j = j + 1
  }
  match parts.next() {
    Some(frac) => {
      let frac_owned = frac.to_owned()
      let mut divisor = 1
      let mut frac_val = 0
      let mut k = 0
      while k < frac_owned.length() {
        let c = frac_owned[k]
        if c >= '0' && c <= '9' {
          divisor = divisor * 10
          frac_val = frac_val * 10 + (c.to_int() - 48)
        }
        k = k + 1
      }
      let result = Float::from_int(int_val) +
        Float::from_int(frac_val) / Float::from_int(divisor)
      if neg {
        -result
      } else {
        result
      }
    }
    None => {
      let result = Float::from_int(int_val)
      if neg {
        -result
      } else {
        result
      }
    }
  }
}

///|
/// Parse port string to int
fn parse_port(s : String) -> Int {
  let mut result = 0
  let mut i = 0
  while i < s.length() {
    let c = s[i]
    if c >= '0' && c <= '9' {
      result = result * 10 + (c.to_int() - 48)
    } else {
      return 8080
    }
    i = i + 1
  }
  if result <= 0 || result > 65535 {
    8080
  } else {
    result
  }
}

///|
/// Extract query parameter value from query string
fn extract_query_param(query : String, param : String) -> String? {
  let pairs_iter = query.split("&")
  let pairs : Array[String] = []
  for p in pairs_iter {
    pairs.push(p.to_owned())
  }
  for pair in pairs {
    let eq_pos = string_find(pair, "=")
    match eq_pos {
      None => continue
      Some(pos) => {
        let key = pair[:pos].to_owned()
        if key == param {
          let value = pair[pos + 1:].to_owned()
          return Some(decode_url(value))
        }
      }
    }
  }
  None
}

///|
/// Simple URL decode (handles %XX and +), preserving UTF-8 multi-byte sequences.
/// Percent-encoded bytes are collected first, then decoded as one UTF-8 string,
/// so multi-byte characters (e.g. Chinese) survive the round-trip.
fn decode_url(s : String) -> String {
  let bytes : Array[Byte] = []
  let mut i = 0
  while i < s.length() {
    let ch = s[i].to_int()
    if ch == 43 {
      // '+'
      bytes.push((32 : Byte))
      i = i + 1
    } else if ch == 37 && i + 2 < s.length() {
      // '%XX'
      let code = hex_to_int(s[i + 1]) * 16 + hex_to_int(s[i + 2])
      bytes.push(code.to_byte())
      i = i + 3
    } else {
      // Raw byte (may be part of a multi-byte UTF-8 char) passes through
      bytes.push(ch.to_byte())
      i = i + 1
    }
  }
  @utf8.decode(Bytes::from_array(bytes)) catch {
    _ => s
  }
}

///|
/// Escape string for JSON (handle quotes, backslash, and control characters)
fn json_escape(s : String) -> String {
  let mut result = ""
  let mut i = 0
  while i < s.length() {
    let ch = s[i]
    if ch == 34 { // "
      result = result + "\\\""
    } else if ch == 92 { // \
      result = result + "\\\\"
    } else if ch == 10 { // \n
      result = result + "\\n"
    } else if ch == 13 { // \r
      result = result + "\\r"
    } else if ch == 9 { // \t
      result = result + "\\t"
    } else if ch < 32 {
      // Skip other control chars
      result = result
    } else {
      match ch.to_char() {
        Some(c) => result = result + c.to_string()
        None => ()
      }
    }
    i = i + 1
  }
  result
}

///|
/// Extract token usage from DeepSeek response JSON
/// Returns (prompt_tokens, completion_tokens) or None
fn extract_usage_tokens(json_str : String) -> (Int, Int)? {
  // Find "prompt_tokens": XX
  let pt_prefix = "\"prompt_tokens\":"
  let ct_prefix = "\"completion_tokens\":"

  let pt_start = match string_find(json_str, pt_prefix) {
    Some(s) => s + pt_prefix.length()
    None => return None
  }
  let ct_start = match string_find(json_str, ct_prefix) {
    Some(s) => s + ct_prefix.length()
    None => return None
  }

  let prompt_tokens = parse_int_at(json_str, pt_start)
  let completion_tokens = parse_int_at(json_str, ct_start)

  Some((prompt_tokens, completion_tokens))
}

///|
/// Parse an integer starting at a given position in a string
fn parse_int_at(s : String, start : Int) -> Int {
  let mut result = 0
  let mut i = start
  // Skip whitespace
  while i < s.length() && s[i] == ' ' {
    i = i + 1
  }
  // Skip leading digits
  let mut neg = false
  if i < s.length() && s[i] == '-' {
    neg = true
    i = i + 1
  }
  while i < s.length() {
    let c = s[i]
    if c >= '0' && c <= '9' {
      result = result * 10 + (c.to_int() - 48)
      i = i + 1
    } else {
      break
    }
  }
  if neg {
    -result
  } else {
    result
  }
}

///|
/// Simple JSON content extraction from DeepSeek response (handles JSON escapes)
fn extract_content(json_str : String) -> String? {
  let prefix = "\"content\":\""
  let start = string_find(json_str, prefix)
  match start {
    None => return None
    Some(s) => {
      let value_start = s + prefix.length()
      let mut result = ""
      let mut i = value_start
      while i < json_str.length() {
        let ch = json_str[i]
        if ch == '\\' && i + 1 < json_str.length() {
          let next = json_str[i + 1]
          if next == '"' {
            result = result + "\""
            i = i + 2
          } else if next == 'n' {
            result = result + "\n"
            i = i + 2
          } else if next == 'r' {
            result = result + "\r"
            i = i + 2
          } else if next == 't' {
            result = result + "\t"
            i = i + 2
          } else if next == '\\' {
            result = result + "\\"
            i = i + 2
          } else {
            match ch.to_char() {
              Some(c) => result = result + c.to_string()
              None => ()
            }
            i = i + 1
          }
        } else if ch == '"' {
          break
        } else {
          match ch.to_char() {
            Some(c) => result = result + c.to_string()
            None => ()
          }
          i = i + 1
        }
      }
      Some(result)
    }
  }
}

///|
/// Simple HTML page for the web UI
fn index_html() -> String {
  "" +
  "" +
  "" +
  "" +
  "" +
  "AI Agent Runtime" +
  "" +
  "" +
  "" +
  "
" + "

AI Agent Runtime

" + "
" + "
" + "" + "" + "
" + "
Ready
" + "
" + "" + "" + "" }