Hook packs and custom instruments can declare a console widget for REPL-style input. Adds a WidgetConsoleEntry update kind, per-session persistence, an agent-side appendConsole/onConsoleInput API, and a ConsoleView-backed renderer card. Regroups all widget kinds across the codebase as counter, histogram, graph, list, table, hex, console (aggregates, collections, raw, interactive).
214 lines
6.7 KiB
Swift
214 lines
6.7 KiB
Swift
import Foundation
|
|
|
|
public struct InstrumentWidget: Codable, Identifiable, Sendable, Equatable {
|
|
public var id: String
|
|
public var name: String
|
|
public var kind: Kind
|
|
public var persistence: Persistence
|
|
|
|
public init(id: String, name: String, kind: Kind, persistence: Persistence = .none) {
|
|
self.id = id
|
|
self.name = name
|
|
self.kind = kind
|
|
self.persistence = persistence
|
|
}
|
|
|
|
public enum Persistence: String, Codable, Sendable, CaseIterable {
|
|
case none
|
|
case session
|
|
|
|
public var label: String {
|
|
switch self {
|
|
case .none: return "None"
|
|
case .session: return "Session"
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum Kind: Sendable, Equatable {
|
|
case counter(CounterConfig)
|
|
case histogram(HistogramConfig)
|
|
case graph(GraphConfig)
|
|
case list(ListConfig)
|
|
case table(TableConfig)
|
|
case hex(HexConfig)
|
|
case console(ConsoleConfig)
|
|
}
|
|
|
|
public struct CounterConfig: Codable, Sendable, Equatable {
|
|
public var unit: String?
|
|
|
|
public init(unit: String? = nil) {
|
|
self.unit = unit
|
|
}
|
|
}
|
|
|
|
public struct HistogramConfig: Codable, Sendable, Equatable {
|
|
public static let defaultMaxBuckets: Int = 100
|
|
|
|
public var maxBuckets: Int
|
|
|
|
public init(maxBuckets: Int = Self.defaultMaxBuckets) {
|
|
self.maxBuckets = maxBuckets
|
|
}
|
|
}
|
|
|
|
public struct GraphConfig: Codable, Sendable, Equatable {
|
|
public static let defaultMaxPoints: Int = 5_000
|
|
|
|
public var series: [Series]
|
|
public var maxPoints: Int
|
|
|
|
public init(series: [Series] = [], maxPoints: Int = Self.defaultMaxPoints) {
|
|
self.series = series
|
|
self.maxPoints = maxPoints
|
|
}
|
|
}
|
|
|
|
public struct Series: Codable, Identifiable, Sendable, Equatable {
|
|
public var id: String
|
|
public var name: String
|
|
|
|
public init(id: String, name: String) {
|
|
self.id = id
|
|
self.name = name
|
|
}
|
|
}
|
|
|
|
public struct ListConfig: Codable, Sendable, Equatable {
|
|
public static let defaultMaxItems: Int = 1_000
|
|
|
|
public var actions: [Action]
|
|
public var maxItems: Int
|
|
|
|
public init(actions: [Action] = [], maxItems: Int = Self.defaultMaxItems) {
|
|
self.actions = actions
|
|
self.maxItems = maxItems
|
|
}
|
|
}
|
|
|
|
public struct TableConfig: Codable, Sendable, Equatable {
|
|
public static let defaultMaxRows: Int = 1_000
|
|
|
|
public var columns: [Column]
|
|
public var actions: [Action]
|
|
public var maxRows: Int
|
|
|
|
public init(columns: [Column] = [], actions: [Action] = [], maxRows: Int = Self.defaultMaxRows) {
|
|
self.columns = columns
|
|
self.actions = actions
|
|
self.maxRows = maxRows
|
|
}
|
|
}
|
|
|
|
public struct Column: Codable, Identifiable, Sendable, Equatable {
|
|
public enum Alignment: String, Codable, Sendable, CaseIterable {
|
|
case leading
|
|
case trailing
|
|
}
|
|
|
|
public var id: String
|
|
public var name: String
|
|
public var alignment: Alignment
|
|
|
|
public init(id: String, name: String, alignment: Alignment = .leading) {
|
|
self.id = id
|
|
self.name = name
|
|
self.alignment = alignment
|
|
}
|
|
}
|
|
|
|
public struct HexConfig: Codable, Sendable, Equatable {
|
|
public static let defaultMaxBytes: Int = 16_384
|
|
|
|
public var maxBytes: Int
|
|
|
|
public init(maxBytes: Int = Self.defaultMaxBytes) {
|
|
self.maxBytes = maxBytes
|
|
}
|
|
}
|
|
|
|
public struct ConsoleConfig: Codable, Sendable, Equatable {
|
|
public static let defaultMaxEntries: Int = 1_000
|
|
|
|
public var prompt: String?
|
|
public var placeholder: String?
|
|
public var runButtonLabel: String?
|
|
public var maxEntries: Int
|
|
|
|
public init(
|
|
prompt: String? = nil,
|
|
placeholder: String? = nil,
|
|
runButtonLabel: String? = nil,
|
|
maxEntries: Int = Self.defaultMaxEntries
|
|
) {
|
|
self.prompt = prompt
|
|
self.placeholder = placeholder
|
|
self.runButtonLabel = runButtonLabel
|
|
self.maxEntries = maxEntries
|
|
}
|
|
}
|
|
|
|
public struct Action: Codable, Identifiable, Sendable, Equatable {
|
|
public var id: String
|
|
public var name: String
|
|
|
|
public init(id: String, name: String) {
|
|
self.id = id
|
|
self.name = name
|
|
}
|
|
}
|
|
}
|
|
|
|
extension InstrumentWidget.Kind: Codable {
|
|
private enum CodingKeys: String, CodingKey { case kind, config }
|
|
private enum Tag: String, Codable { case counter, histogram, graph, list, table, hex, console }
|
|
|
|
public init(from decoder: Decoder) throws {
|
|
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
switch try c.decode(Tag.self, forKey: .kind) {
|
|
case .counter:
|
|
self = .counter(try c.decode(InstrumentWidget.CounterConfig.self, forKey: .config))
|
|
case .histogram:
|
|
self = .histogram(try c.decode(InstrumentWidget.HistogramConfig.self, forKey: .config))
|
|
case .graph:
|
|
self = .graph(try c.decode(InstrumentWidget.GraphConfig.self, forKey: .config))
|
|
case .list:
|
|
self = .list(try c.decode(InstrumentWidget.ListConfig.self, forKey: .config))
|
|
case .table:
|
|
self = .table(try c.decode(InstrumentWidget.TableConfig.self, forKey: .config))
|
|
case .hex:
|
|
self = .hex(try c.decode(InstrumentWidget.HexConfig.self, forKey: .config))
|
|
case .console:
|
|
self = .console(try c.decode(InstrumentWidget.ConsoleConfig.self, forKey: .config))
|
|
}
|
|
}
|
|
|
|
public func encode(to encoder: Encoder) throws {
|
|
var c = encoder.container(keyedBy: CodingKeys.self)
|
|
switch self {
|
|
case .counter(let cfg):
|
|
try c.encode(Tag.counter, forKey: .kind)
|
|
try c.encode(cfg, forKey: .config)
|
|
case .histogram(let h):
|
|
try c.encode(Tag.histogram, forKey: .kind)
|
|
try c.encode(h, forKey: .config)
|
|
case .graph(let g):
|
|
try c.encode(Tag.graph, forKey: .kind)
|
|
try c.encode(g, forKey: .config)
|
|
case .list(let l):
|
|
try c.encode(Tag.list, forKey: .kind)
|
|
try c.encode(l, forKey: .config)
|
|
case .table(let t):
|
|
try c.encode(Tag.table, forKey: .kind)
|
|
try c.encode(t, forKey: .config)
|
|
case .hex(let h):
|
|
try c.encode(Tag.hex, forKey: .kind)
|
|
try c.encode(h, forKey: .config)
|
|
case .console(let cfg):
|
|
try c.encode(Tag.console, forKey: .kind)
|
|
try c.encode(cfg, forKey: .config)
|
|
}
|
|
}
|
|
}
|