Watch: notification switches, a model list and Ask before acting on new threads, and Ask before acting on a thread
This commit is contained in:
1 parent
3cc7e40852
commit
6bc9cac6ca
14 files changed
+623
-81
No files matched your search
@@ -265,6 +265,14 @@ struct ThreadDetail: Codable, Hashable, Sendable {
|
||||
/// Oldest first, at most 20.
|
||||
var messages: [Message]
|
||||
var canStop: Bool
|
||||
/// True when the thread asks before each action (T3's approval-required mode). Older gateways
|
||||
/// leave it out, which decodes as nil, and the screen hides the switch.
|
||||
var askFirst: Bool? = nil
|
||||
}
|
||||
|
||||
/// `POST /v1/threads/:hostId/:threadId/mode`.
|
||||
struct ThreadModeRequest: Codable, Sendable {
|
||||
var askFirst: Bool
|
||||
}
|
||||
|
||||
struct ProjectsResponse: Codable, Hashable, Sendable {
|
||||
@@ -274,20 +282,85 @@ struct ProjectsResponse: Codable, Hashable, Sendable {
|
||||
var status: HostStatus
|
||||
var error: String?
|
||||
var projects: [Project]
|
||||
/// The providers that can start a thread now, each with its models. Empty from older
|
||||
/// gateways, which only start threads on the project's default model.
|
||||
var providers: [AgentProvider] = []
|
||||
|
||||
/// Projects a new thread can start in. A project without a default model is not offered.
|
||||
var launchable: [Project] { projects.filter { $0.model != nil } }
|
||||
|
||||
func provider(_ instanceId: String) -> AgentProvider? {
|
||||
providers.first { $0.instanceId == instanceId }
|
||||
}
|
||||
|
||||
/// True when this host still offers the model.
|
||||
func offers(_ model: ModelRef) -> Bool {
|
||||
provider(model.instanceId)?.models.contains { $0.slug == model.slug } ?? false
|
||||
}
|
||||
}
|
||||
|
||||
struct Project: Codable, Hashable, Sendable, Identifiable {
|
||||
var id: String
|
||||
var title: String
|
||||
/// The model a new thread uses by default, such as `claude-opus-5-5`.
|
||||
var model: String?
|
||||
/// The provider `model` belongs to. Older gateways leave it out.
|
||||
var instanceId: String? = nil
|
||||
|
||||
/// The default model as a pick, when the gateway names its provider.
|
||||
var defaultModel: ModelRef? {
|
||||
guard let model, let instanceId else { return nil }
|
||||
return ModelRef(instanceId: instanceId, slug: model)
|
||||
}
|
||||
}
|
||||
|
||||
var hosts: [Host]
|
||||
}
|
||||
|
||||
extension ProjectsResponse.Host {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id, label, status, error, projects, providers
|
||||
}
|
||||
|
||||
/// `providers` is newer than the rest, so a gateway that leaves it out offers no model list.
|
||||
init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.init(
|
||||
id: try container.decode(String.self, forKey: .id),
|
||||
label: try container.decode(String.self, forKey: .label),
|
||||
status: try container.decode(HostStatus.self, forKey: .status),
|
||||
error: try container.decodeIfPresent(String.self, forKey: .error),
|
||||
projects: try container.decode([ProjectsResponse.Project].self, forKey: .projects),
|
||||
providers: try container.decodeIfPresent([AgentProvider].self, forKey: .providers) ?? [])
|
||||
}
|
||||
}
|
||||
|
||||
/// An agent provider on a host, such as Claude or Codex, with the models a new thread can use.
|
||||
struct AgentProvider: Codable, Hashable, Sendable, Identifiable {
|
||||
struct Model: Codable, Hashable, Sendable, Identifiable {
|
||||
var slug: String
|
||||
/// Such as "Claude Opus 5.5".
|
||||
var name: String
|
||||
|
||||
var id: String { slug }
|
||||
}
|
||||
|
||||
var instanceId: String
|
||||
/// Such as "Claude" or "Codex".
|
||||
var name: String
|
||||
/// False when the provider cannot run in approval-required mode, so Ask before acting is hidden.
|
||||
var canAskFirst: Bool
|
||||
var models: [Model]
|
||||
|
||||
var id: String { instanceId }
|
||||
}
|
||||
|
||||
/// A model picked for a new thread: a slug and the provider instance it runs on.
|
||||
struct ModelRef: Codable, Hashable, Sendable {
|
||||
var instanceId: String
|
||||
var slug: String
|
||||
}
|
||||
|
||||
/// How a route reaches its host, guessed by the gateway from the URL.
|
||||
struct RouteKind: RawRepresentable, Codable, Hashable, Sendable {
|
||||
let rawValue: String
|
||||
@@ -386,6 +459,8 @@ struct StatusResponse: Codable, Hashable, Sendable {
|
||||
/// True once the watch has sent its APNs token.
|
||||
var pushRegistered: Bool
|
||||
var apnsEnvironment: PushRegistration.Environment?
|
||||
/// Which pushes this watch gets. Older gateways leave it out, and the screen hides the switches.
|
||||
var notifications: NotificationPrefs? = nil
|
||||
}
|
||||
|
||||
var hosts: [HostStatusDetail]
|
||||
@@ -393,6 +468,34 @@ struct StatusResponse: Codable, Hashable, Sendable {
|
||||
var device: Device
|
||||
}
|
||||
|
||||
/// Which pushes a watch wants. `needsMe` covers approvals and questions on your own threads, and
|
||||
/// `subagents` covers the same from subagents. Host problems and test pushes always go out.
|
||||
struct NotificationPrefs: Codable, Hashable, Sendable {
|
||||
/// One switch, named as `PUT /v1/device/notifications` names it.
|
||||
enum Key: String, CaseIterable, Sendable {
|
||||
case needsMe, finished, failed, subagents
|
||||
|
||||
var keyPath: WritableKeyPath<NotificationPrefs, Bool> {
|
||||
switch self {
|
||||
case .needsMe: \.needsMe
|
||||
case .finished: \.finished
|
||||
case .failed: \.failed
|
||||
case .subagents: \.subagents
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var needsMe: Bool
|
||||
var finished: Bool
|
||||
var failed: Bool
|
||||
var subagents: Bool
|
||||
|
||||
subscript(key: Key) -> Bool {
|
||||
get { self[keyPath: key.keyPath] }
|
||||
set { self[keyPath: key.keyPath] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
struct PairRequest: Codable, Sendable {
|
||||
var code: String
|
||||
var deviceName: String
|
||||
@@ -453,6 +556,10 @@ struct NewThreadRequest: Codable, Sendable {
|
||||
var hostId: String
|
||||
var projectId: String
|
||||
var text: String
|
||||
/// A model from the host's `providers`. Nil uses the project's default.
|
||||
var model: ModelRef? = nil
|
||||
/// Starts the thread in approval-required mode. Nil or false uses the host's default mode.
|
||||
var askFirst: Bool? = nil
|
||||
}
|
||||
|
||||
struct NewThreadResponse: Codable, Sendable {
|
||||
|
||||
Reference in new issue
Block a user