Files
luma/Sources/LumaCore/Models/HookPackConfig.swift
Ole André Vadla Ravnås 029a403c71 Round-trip custom instruments as hookpacks
Hookpack manifest mirrors CustomInstrumentDef: rich features,
TypeScript entry, optional file-based icon. Folder name is the
pack id.

HookPackConfig holds FeatureState per feature, producing the
same agent-JSON shape as custom instruments. Hookpack sources
compile through the shared TypeScript pipeline.

Import sits next to "New Custom Instrument..."; Export on the
custom-instrument context menu. Both UIs.
2026-05-10 16:15:56 +02:00

50 lines
1.7 KiB
Swift

import Foundation
public struct HookPackConfig: Codable, Equatable, Sendable {
public let packId: String
public var features: [String: FeatureState]
public init(packId: String, features: [String: FeatureState] = [:]) {
self.packId = packId
self.features = features
}
public static func decode(from data: Data) throws -> HookPackConfig {
try JSONDecoder().decode(HookPackConfig.self, from: data)
}
public func encode() -> Data {
try! JSONEncoder().encode(self)
}
public mutating func normalize(against features: [CustomInstrumentDef.Feature]) {
var newFeatures: [String: FeatureState] = [:]
for feature in features {
if let existing = self.features[feature.id], existing.value.matches(schema: feature.schema) {
newFeatures[feature.id] = existing
} else {
newFeatures[feature.id] = FeatureState(
enabled: feature.enabledByDefault,
value: feature.schema.defaultValue
)
}
}
self.features = newFeatures
}
public func normalized(against features: [CustomInstrumentDef.Feature]) -> HookPackConfig {
var copy = self
copy.normalize(against: features)
return copy
}
public func toAgentJSON(features: [CustomInstrumentDef.Feature]) -> [String: Any] {
let entries = features.compactMap { feature -> (String, Any)? in
guard let state = self.features[feature.id] else { return nil }
if feature.optional && !state.enabled { return nil }
return (feature.id, state.value.toJSONNative())
}
return ["features": Dictionary(uniqueKeysWithValues: entries)]
}
}