Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions Packages/Sources/RxCodeCore/Autopilot/AutopilotConfigModels.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import Foundation

// Codable DTOs mirroring github-pm's autopilot configuration JSON shapes
// (`/api/v1/automation/*`, `/api/v1/preferences`, `/api/v1/repo-setup/*`).
// Arbitrary JSON (schemas, ui schemas, values, merge settings, rulesets) is
// carried as `JSONValue` so RxCodeCore never has to import the form renderer.

public extension JSONValue {
/// A compact JSON string of this value, suitable for feeding the
/// `JSONSchemaForm` renderer (`JSONSchema(jsonString:)`,
/// `FormData.fromJSONString(_:)`).
var rawJSONString: String {
guard let data = try? JSONEncoder().encode(self),
let string = String(data: data, encoding: .utf8)
else { return "null" }
return string
}

/// A human-readable, pretty-printed JSON string for editors.
var prettyJSONString: String {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
guard let data = try? encoder.encode(self),
let string = String(data: data, encoding: .utf8)
else { return rawJSONString }
return string
}

/// A Foundation object tree (`[String: Any]`, `[Any]`, `NSNull`, scalars)
/// suitable for the renderer's `uiSchema: [String: Any]?` parameter.
var foundationObject: Any {
switch self {
case .string(let value): return value
case .number(let value): return value
case .bool(let value): return value
case .object(let value): return value.mapValues { $0.foundationObject }
case .array(let value): return value.map { $0.foundationObject }
case .null: return NSNull()
}
}

/// Decodes a `JSONValue` from raw JSON bytes.
init(jsonData: Data) throws {
self = try JSONDecoder().decode(JSONValue.self, from: jsonData)
}
}

// MARK: - Schema envelope

/// Response of the schema endpoints: a JSON Schema plus an optional RJSF-style
/// ui schema (`/api/v1/automation/schema`, `/api/v1/repo-setup/schema`).
public struct SchemaEnvelope: Codable, Sendable {
public let schema: JSONValue
public let uiSchema: JSONValue?

public init(schema: JSONValue, uiSchema: JSONValue? = nil) {
self.schema = schema
self.uiSchema = uiSchema
}
}

// MARK: - Automation preferences (values)

/// Wrapper for `GET`/`PUT /api/v1/preferences` — the saved automation settings
/// values, opaque to the app (rendered/validated against the schema).
public struct PreferencesValues: Codable, Sendable {
public let values: JSONValue

public init(values: JSONValue) {
self.values = values
}
}
133 changes: 133 additions & 0 deletions Packages/Sources/RxCodeCore/Autopilot/RepoSetupModels.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import Foundation

// Codable DTOs for github-pm's repo-setup templates
// (`/api/v1/repo-setup/templates`). A template bundles GitHub merge settings
// (rendered from a JSON schema) with an optional raw GitHub ruleset.

// MARK: - Template

public struct RepoSetupTemplate: Codable, Sendable, Identifiable, Hashable {
public let id: String
public var name: String
public var enabled: Bool
public var isDefault: Bool
/// GitHub PR merge settings; shape defined by the repo-setup JSON schema.
public var mergeSettings: JSONValue
/// Raw GitHub ruleset JSON, or `nil` when no ruleset is attached.
public var rulesetConfig: JSONValue?

public init(
id: String,
name: String,
enabled: Bool,
isDefault: Bool,
mergeSettings: JSONValue,
rulesetConfig: JSONValue? = nil
) {
self.id = id
self.name = name
self.enabled = enabled
self.isDefault = isDefault
self.mergeSettings = mergeSettings
self.rulesetConfig = rulesetConfig
}
}

public struct RepoSetupTemplateList: Codable, Sendable {
public let items: [RepoSetupTemplate]

public init(items: [RepoSetupTemplate]) {
self.items = items
}
}

/// Create/update payload for a repo-setup template.
public struct RepoSetupTemplateInput: Codable, Sendable {
public var name: String
public var enabled: Bool
public var isDefault: Bool
public var mergeSettings: JSONValue
public var rulesetConfig: JSONValue?

public init(
name: String,
enabled: Bool,
isDefault: Bool,
mergeSettings: JSONValue,
rulesetConfig: JSONValue? = nil
) {
self.name = name
self.enabled = enabled
self.isDefault = isDefault
self.mergeSettings = mergeSettings
self.rulesetConfig = rulesetConfig
}
}

// MARK: - GitHub ruleset validation

/// A validated summary of a raw GitHub ruleset JSON, mirroring the webapp's
/// upload check (`components/repo-setup/template-form.tsx`): the JSON must be an
/// object carrying `name`, `target`, `enforcement`, and `rules`.
public struct GitHubRulesetSummary: Sendable, Equatable {
public let name: String
public let target: String
public let enforcement: String
public let ruleCount: Int
/// The parsed ruleset, ready to store as a template's `rulesetConfig`.
public let value: JSONValue

public enum ValidationError: Error, Equatable, LocalizedError {
case empty
case invalidJSON
case notAnObject
case missingFields([String])

public var errorDescription: String? {
switch self {
case .empty:
return "Paste or import a GitHub ruleset JSON."
case .invalidJSON:
return "Invalid JSON. Please check the file format."
case .notAnObject:
return "A ruleset must be a JSON object."
case .missingFields(let fields):
return "Invalid ruleset format. Missing: \(fields.joined(separator: ", ")). Expected name, target, enforcement, and rules."
}
}
}

/// Parses and validates a raw GitHub ruleset JSON string.
public static func validate(rawJSON: String) -> Result<GitHubRulesetSummary, ValidationError> {
let trimmed = rawJSON.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return .failure(.empty) }
guard let data = trimmed.data(using: .utf8),
let parsed = try? JSONValue(jsonData: data)
else { return .failure(.invalidJSON) }
guard case .object(let fields) = parsed else { return .failure(.notAnObject) }

var missing: [String] = []
let name = fields["name"]?.stringValue
let target = fields["target"]?.stringValue
let enforcement = fields["enforcement"]?.stringValue
let rules = fields["rules"]?.arrayValue

if name == nil { missing.append("name") }
if target == nil { missing.append("target") }
if enforcement == nil { missing.append("enforcement") }
if rules == nil { missing.append("rules") }
guard missing.isEmpty,
let name, let target, let enforcement, let rules
else { return .failure(.missingFields(missing)) }

return .success(
GitHubRulesetSummary(
name: name,
target: target,
enforcement: enforcement,
ruleCount: rules.count,
value: parsed
)
)
}
}
18 changes: 18 additions & 0 deletions Packages/Sources/RxCodeCore/Backend/IDEToolRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,24 @@ public enum IDEToolRegistry {
]),
])
),
IDETool(
name: "ide__setup_release",
description: "Register the repository with the release service and (optionally) install its RELEASE_TOKEN GitHub Actions secret in one step, so the repo's semantic-release CI can create releases. Use this when setting up release publishing. If the repository isn't registered yet, it's registered automatically (the RxLab GitHub App must be installed on it) and its workflows are scanned. The RELEASE_TOKEN is user-supplied: ask the user for a GitHub token with `contents:write`, then pass it as `release_token` so it's installed as the repo's `RELEASE_TOKEN` secret. If the call fails with a permission error, tell the user to re-authorize the RxLab GitHub App (Actions secrets: read & write) and retry.",
visibility: .alwaysIDEOnly,
inputSchema: .object([
"type": .string("object"),
"properties": .object([
"repository": .object([
"type": .string("string"),
"description": .string("Optional `owner/repo` full name. Omit to use the current project's repository."),
]),
"release_token": .object([
"type": .string("string"),
"description": .string("Optional user-supplied GitHub token (e.g. a PAT with `contents:write`) to install as the repo's `RELEASE_TOKEN` Actions secret. Omit to only register the repository without installing a secret."),
]),
]),
])
),
]

/// Returns the tools that should be exposed to an agent whose declared
Expand Down
Loading
Loading