182 lines
4.6 KiB
Swift
182 lines
4.6 KiB
Swift
// CxLLM Studio — iOS Application
|
|
// Services/AgentService.swift — Agent operations service
|
|
|
|
import Foundation
|
|
import CxCode
|
|
|
|
@Observable
|
|
final class AgentService {
|
|
|
|
// MARK: - State
|
|
|
|
private(set) var isHealthy = false
|
|
private(set) var tools: [AgentToolInfo] = []
|
|
private(set) var isRunning = false
|
|
private(set) var isExecutingTool = false
|
|
private(set) var error: String?
|
|
private(set) var lastHealthCheck: Date?
|
|
private(set) var totalExecutions: Int = 0
|
|
private(set) var totalToolCalls: Int = 0
|
|
private(set) var history: [AgentExecution] = []
|
|
|
|
private let gateway: CxGateway
|
|
|
|
init(gateway: CxGateway) {
|
|
self.gateway = gateway
|
|
}
|
|
|
|
// MARK: - Health & Discovery
|
|
|
|
func checkHealth() async {
|
|
do {
|
|
let _ = try await gateway.agent.health()
|
|
isHealthy = true
|
|
error = nil
|
|
lastHealthCheck = Date()
|
|
} catch {
|
|
isHealthy = false
|
|
self.error = error.localizedDescription
|
|
lastHealthCheck = Date()
|
|
}
|
|
}
|
|
|
|
func loadTools() async {
|
|
do {
|
|
tools = try await gateway.agent.listTools()
|
|
error = nil
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func initialize() async {
|
|
await checkHealth()
|
|
if isHealthy {
|
|
await loadTools()
|
|
}
|
|
}
|
|
|
|
// MARK: - Autopilot
|
|
|
|
func runAutopilot(task: String, maxSteps: Int = 10) async throws -> AgentExecution {
|
|
guard isHealthy else {
|
|
throw AgentServiceError.notHealthy
|
|
}
|
|
guard !isRunning else {
|
|
throw AgentServiceError.alreadyRunning
|
|
}
|
|
|
|
isRunning = true
|
|
error = nil
|
|
let startTime = Date()
|
|
|
|
do {
|
|
let result = try await gateway.agent.autoPilot(task: task, maxSteps: maxSteps)
|
|
let duration = Date().timeIntervalSince(startTime)
|
|
let execution = AgentExecution(
|
|
task: task,
|
|
steps: [],
|
|
output: result,
|
|
time: Date(),
|
|
duration: duration,
|
|
success: true
|
|
)
|
|
history.insert(execution, at: 0)
|
|
totalExecutions += 1
|
|
isRunning = false
|
|
return execution
|
|
} catch {
|
|
let duration = Date().timeIntervalSince(startTime)
|
|
let execution = AgentExecution(
|
|
task: task,
|
|
steps: [],
|
|
output: "Error: \(error.localizedDescription)",
|
|
time: Date(),
|
|
duration: duration,
|
|
success: false
|
|
)
|
|
history.insert(execution, at: 0)
|
|
self.error = error.localizedDescription
|
|
isRunning = false
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// MARK: - Tool Execution
|
|
|
|
func callTool(name: String, arguments: [String: Any]) async throws -> String {
|
|
guard !isExecutingTool else {
|
|
throw AgentServiceError.alreadyRunning
|
|
}
|
|
|
|
isExecutingTool = true
|
|
error = nil
|
|
|
|
do {
|
|
let result = try await gateway.agent.callTool(name: name, arguments: arguments)
|
|
totalToolCalls += 1
|
|
isExecutingTool = false
|
|
return result
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
isExecutingTool = false
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// MARK: - History Management
|
|
|
|
func clearHistory() {
|
|
history.removeAll()
|
|
}
|
|
|
|
// MARK: - Computed
|
|
|
|
var toolNames: [String] {
|
|
tools.map(\.name)
|
|
}
|
|
|
|
var successRate: Double {
|
|
guard !history.isEmpty else { return 0 }
|
|
let successes = history.filter(\.success).count
|
|
return Double(successes) / Double(history.count)
|
|
}
|
|
|
|
var averageDuration: TimeInterval {
|
|
guard !history.isEmpty else { return 0 }
|
|
return history.reduce(0.0) { $0 + $1.duration } / Double(history.count)
|
|
}
|
|
}
|
|
|
|
// MARK: - Supporting Types
|
|
|
|
struct AgentStep: Identifiable {
|
|
let id = UUID()
|
|
let index: Int
|
|
let action: String
|
|
let result: String
|
|
let timestamp: Date
|
|
}
|
|
|
|
struct AgentExecution: Identifiable {
|
|
let id = UUID()
|
|
let task: String
|
|
let steps: [AgentStep]
|
|
let output: String
|
|
let time: Date
|
|
let duration: TimeInterval
|
|
let success: Bool
|
|
}
|
|
|
|
enum AgentServiceError: LocalizedError {
|
|
case notHealthy
|
|
case alreadyRunning
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .notHealthy: return "Agent service is not healthy. Check connection."
|
|
case .alreadyRunning: return "An operation is already in progress."
|
|
}
|
|
}
|
|
}
|