13699864d8
- Jarvis integration: manage oAI-Web agents and usage from inside the app (/jarvis command, Settings tab 11) - Model category filter: keyword-based categorisation with popover picker in model selector - Categories shown in ModelInfoView with coloured chips; dot indicators on model rows Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
227 lines
6.3 KiB
Swift
227 lines
6.3 KiB
Swift
//
|
|
// 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)"
|
|
}
|
|
}
|
|
}
|