// // JarvisModels.swift // oAI // // Data models for the Jarvis (oAI-Web) API integration. // // SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (C) 2026 Rune Olsen import Foundation // MARK: - Agent struct JarvisAgent: Identifiable, Codable, Hashable, Sendable { let id: String var name: String var description: String var prompt: String var model: String var enabled: Bool var schedule: String? var canCreateSubagents: Bool var allowedTools: [String] var maxToolCalls: Int? var promptMode: String let createdAt: String? var isRunning: Bool? var lastRunAt: String? var lastRunStatus: String? enum CodingKeys: String, CodingKey { case id, name, description, prompt, model, enabled, schedule case canCreateSubagents = "can_create_subagents" case allowedTools = "allowed_tools" case maxToolCalls = "max_tool_calls" case promptMode = "prompt_mode" case createdAt = "created_at" case isRunning = "is_running" case lastRunAt = "last_run_at" case lastRunStatus = "last_run_status" } } // MARK: - Agent Input (create / update) struct JarvisAgentInput: Codable, Sendable { var name: String var prompt: String var model: String var description: String = "" var enabled: Bool = true var schedule: String? = nil var canCreateSubagents: Bool = false var allowedTools: [String] = [] var maxToolCalls: Int? = nil var promptMode: String = "combined" enum CodingKeys: String, CodingKey { case name, prompt, model, description, enabled, schedule case canCreateSubagents = "can_create_subagents" case allowedTools = "allowed_tools" case maxToolCalls = "max_tool_calls" case promptMode = "prompt_mode" } } // MARK: - Agent Run struct JarvisAgentRun: Identifiable, Codable, Sendable { let id: String let agentId: String? let status: String // "running" | "completed" | "failed" | "stopped" let startedAt: String? let finishedAt: String? let output: String? let error: String? let costUsd: Double? let inputTokens: Int? let outputTokens: Int? let triggerType: String? enum CodingKeys: String, CodingKey { case id, status, output, error case agentId = "agent_id" case startedAt = "started_at" case finishedAt = "finished_at" case costUsd = "cost_usd" case inputTokens = "input_tokens" case outputTokens = "output_tokens" case triggerType = "trigger_type" } var isActive: Bool { status == "running" } var totalTokens: Int { (inputTokens ?? 0) + (outputTokens ?? 0) } var formattedStarted: String { guard let s = startedAt else { return "—" } return isoFormatter.string(from: isoParser.date(from: s) ?? Date()) } var formattedDuration: String? { guard let s = startedAt, let f = finishedAt, let sd = isoParser.date(from: s), let fd = isoParser.date(from: f) else { return nil } let secs = Int(fd.timeIntervalSince(sd)) if secs < 60 { return "\(secs)s" } return "\(secs / 60)m \(secs % 60)s" } } private let isoParser: ISO8601DateFormatter = { let f = ISO8601DateFormatter() f.formatOptions = [.withInternetDateTime, .withFractionalSeconds] return f }() private let isoFormatter: DateFormatter = { let f = DateFormatter() f.dateStyle = .short f.timeStyle = .short return f }() // MARK: - Usage struct JarvisUsageStat: Identifiable, Codable, Sendable { let agentId: String? let agentName: String? let model: String? let runCount: Int? let totalInputTokens: Int? let totalOutputTokens: Int? let totalCostUsd: Double? var id: String { agentId ?? agentName ?? "unknown" } var totalTokens: Int { (totalInputTokens ?? 0) + (totalOutputTokens ?? 0) } var displayName: String { agentName ?? agentId ?? "Unknown" } enum CodingKeys: String, CodingKey { case agentId = "agent_id" case agentName = "agent_name" case model case runCount = "runs" case totalInputTokens = "input_tokens" case totalOutputTokens = "output_tokens" case totalCostUsd = "cost_usd" } } // MARK: - Usage Response (top-level wrapper) struct JarvisUsageResponse: Decodable, Sendable { let summary: JarvisUsageSummary? let byAgent: [JarvisUsageStat]? enum CodingKeys: String, CodingKey { case summary case byAgent = "by_agent" } } struct JarvisUsageSummary: Codable, Sendable { let runs: Int? let inputTokens: Int? let outputTokens: Int? let costUsd: Double? enum CodingKeys: String, CodingKey { case runs case inputTokens = "input_tokens" case outputTokens = "output_tokens" case costUsd = "cost_usd" } } // MARK: - Credits struct JarvisCreditsResponse: Codable, Sendable { let totalCredits: Double? let totalUsage: Double? let balance: Double? enum CodingKeys: String, CodingKey { case totalCredits = "total_credits" case totalUsage = "total_usage" case balance } var remainingBalance: Double? { if let b = balance { return b } if let c = totalCredits, let u = totalUsage { return c - u } return nil } } // MARK: - Queue / System status struct JarvisQueueStatus: Codable, Sendable { let paused: Bool? let queueLength: Int? let runningCount: Int? enum CodingKeys: String, CodingKey { case paused case queueLength = "queue_length" case runningCount = "running_count" } } // MARK: - Errors enum JarvisError: LocalizedError { case invalidURL case noAPIKey case invalidResponse case serverError(Int, String) var errorDescription: String? { switch self { case .invalidURL: return "Invalid Jarvis URL" case .noAPIKey: return "No API key configured — add one in Settings → Jarvis" case .invalidResponse: return "Invalid server response" case .serverError(let c, let m): return "Server error \(c): \(m)" } } }