Files
hapi/ios/Packages/HapiKit/Sources/HapiProtocol/Models/AgentModels.swift
T
weishuandClaude Opus 5.5 d01f03eef7 feat(ios): pick models for every agent when creating a session
The create form only offered models for Claude and Codex, so every
other agent spawned on its default model. The form now loads the
catalogs web uses. Agy, cursor, dsh and pi read
/machines/:id/<flavor>-models. Copilot, grok, kimi and opencode probe
the same route with cwd once the directory exists; the view refetches
when the typed path turns out to exist.

Option rows follow the web builders:
- Copilot and cursor label the default row "Auto".
- Kimi and pi prefix the provider; pi values are provider/modelId.
- OpenCode marks the agent's own default.
- Cursor shows a flat list of ACP ids, without the variant drill-down.

Grok efforts and pi thinking levels follow the selected model, and an
OpenCode variant becomes the reasoning effort. Grok's auto permission
mode is hidden when the probe reports it unsupported. A restored
selection the catalog no longer offers falls back to Default, and
Create waits while a non-default selection is still being validated.

The spawn body sends model for all of these agents, and cursor always
sends it, with 'auto' as the default. Grok and pi send effort; opencode
sends modelReasoningEffort.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:00:00 +08:00

124 lines
4.5 KiB
Swift

import Foundation
/// One row of a `<flavor>-models` catalog for the dynamically discovered
/// agents (agy, copilot, cursor, dsh, grok, kimi, opencode, pi). The union of
/// the per-flavor `*ModelSummary` shapes in `shared/src/apiTypes.ts`; fields a
/// flavor does not send stay nil.
public struct AgentModelSummary: Codable, Equatable, Sendable {
/// Opaque wire id, sent back verbatim (dsh ids are JSON tuples).
public var modelId: String
public var name: String?
/// Kimi/Pi provider. Pi spawns as `provider/modelId` and switches with a
/// `{provider, modelId}` reference.
public var provider: String?
/// Grok: efforts the model supports.
public var reasoningEfforts: [AgentReasoningEffortOption]?
/// Pi: `false` = the model has no thinking levels.
public var reasoning: Bool?
/// Pi: thinking level → provider value; `null` marks an unsupported level.
public var thinkingLevelMap: [String: String?]?
public init(
modelId: String,
name: String? = nil,
provider: String? = nil,
reasoningEfforts: [AgentReasoningEffortOption]? = nil,
reasoning: Bool? = nil,
thinkingLevelMap: [String: String?]? = nil
) {
self.modelId = modelId
self.name = name
self.provider = provider
self.reasoningEfforts = reasoningEfforts
self.reasoning = reasoning
self.thinkingLevelMap = thinkingLevelMap
}
}
/// `GrokReasoningEffortOption` / `OpencodeReasoningEffortOption`.
public struct AgentReasoningEffortOption: Codable, Equatable, Sendable {
public var value: String
public var name: String?
public init(value: String, name: String? = nil) {
self.value = value
self.name = name
}
}
/// `GET /api/machines/:id/<flavor>-models` and
/// `GET /api/sessions/:id/<flavor>-models`. RPC envelope: a CLI-side failure
/// is HTTP 200 `success: false`; agy may pair `success: true` with an
/// `error` (a stale but usable catalog).
public struct AgentModelsResponse: Codable, Equatable, Sendable {
public var success: Bool
public var availableModels: [AgentModelSummary]?
/// Cursor CLI SKUs (variant rows); the flat iOS picker does not use them.
public var cliModelSkus: [AgentModelSummary]?
/// The agent's own default (opencode/grok/copilot/kimi/dsh probes) or
/// the running model (session routes).
public var currentModelId: String?
/// Grok: `false` disables the `auto` permission mode.
public var autoPermissionModeSupported: Bool?
public var error: String?
public init(
success: Bool,
availableModels: [AgentModelSummary]? = nil,
cliModelSkus: [AgentModelSummary]? = nil,
currentModelId: String? = nil,
autoPermissionModeSupported: Bool? = nil,
error: String? = nil
) {
self.success = success
self.availableModels = availableModels
self.cliModelSkus = cliModelSkus
self.currentModelId = currentModelId
self.autoPermissionModeSupported = autoPermissionModeSupported
self.error = error
}
}
/// `GET /api/machines/:id/opencode-model-variants`.
public struct OpencodeModelVariantsResponse: Codable, Equatable, Sendable {
public var success: Bool
/// Variant names keyed by `providerId/modelId`.
public var variants: [String: [String]]?
public var error: String?
public init(success: Bool, variants: [String: [String]]? = nil, error: String? = nil) {
self.success = success
self.variants = variants
self.error = error
}
}
/// `GET /api/sessions/:id/grok-reasoning-effort-options` and
/// `GET /api/sessions/:id/opencode-reasoning-effort-options`.
public struct ReasoningEffortOptionsResponse: Codable, Equatable, Sendable {
public var success: Bool
public var options: [AgentReasoningEffortOption]?
public var currentValue: String?
/// OpenCode: the model the options belong to, and the requested model.
/// The options are stale while they differ.
public var currentModelId: String?
public var targetModelId: String?
public var error: String?
public init(
success: Bool,
options: [AgentReasoningEffortOption]? = nil,
currentValue: String? = nil,
currentModelId: String? = nil,
targetModelId: String? = nil,
error: String? = nil
) {
self.success = success
self.options = options
self.currentValue = currentValue
self.currentModelId = currentModelId
self.targetModelId = targetModelId
self.error = error
}
}