feat(account): add managed credits and cloud gateway

Introduce optional Apple account-backed credits with scoped gateway access while preserving local and BYOK paths. Refresh assistant behavior, tests, privacy disclosures, docs, and the website for the 2.0 experience.
This commit is contained in:
Rocky
2026-08-20 11:43:21 +08:00
parent 0f9280bd00
commit 498f407585
301 changed files with 19221 additions and 10891 deletions
@@ -26,6 +26,7 @@ public protocol ConfigurationStore: Sendable {
var asrModel: String { get }
var engineMode: String { get }
var credentialSource: CredentialSource { get }
var polishIntensity: PolishIntensity { get }
var aiResponseLength: AIResponseLength { get }
var llmThinkingEnabled: Bool { get }
@@ -39,5 +40,14 @@ public protocol ConfigurationStore: Sendable {
/// Provider-specific ASR caches (e.g. Alibaba Fun-ASR vocabulary IDs).
var cloudASRPersistence: UserDefaults { get }
func makeClient() -> LLMClient
func makeClient(taskKind: ManagedGatewayTaskKind?) -> LLMClient
}
public extension ConfigurationStore {
/// Existing stores and test doubles remain BYOK unless they opt in.
var credentialSource: CredentialSource { .byok }
func makeClient() -> LLMClient {
makeClient(taskKind: nil)
}
}
@@ -16,6 +16,7 @@ public struct LiveConfigurationSnapshot {
public let asrApiKey: String
public let asrModel: String
public let engineMode: String
public let credentialSource: CredentialSource
public let polishIntensity: PolishIntensity
public let aiResponseLength: AIResponseLength
public let llmThinkingEnabled: Bool
@@ -35,6 +36,7 @@ public struct LiveConfigurationSnapshot {
asrApiKey: String,
asrModel: String,
engineMode: String,
credentialSource: CredentialSource = .byok,
polishIntensity: PolishIntensity,
aiResponseLength: AIResponseLength = .default,
llmThinkingEnabled: Bool,
@@ -53,6 +55,7 @@ public struct LiveConfigurationSnapshot {
self.asrApiKey = asrApiKey
self.asrModel = asrModel
self.engineMode = engineMode
self.credentialSource = credentialSource
self.polishIntensity = polishIntensity
self.aiResponseLength = aiResponseLength
self.llmThinkingEnabled = llmThinkingEnabled
@@ -75,6 +78,7 @@ public struct LiveConfigurationSnapshot {
asrApiKey: config.asrApiKey,
asrModel: config.asrModel,
engineMode: config.engineMode,
credentialSource: config.credentialSource,
polishIntensity: config.polishIntensity,
aiResponseLength: config.aiResponseLength,
llmThinkingEnabled: config.llmThinkingEnabled,
@@ -110,6 +114,7 @@ public struct LiveConfigurationStore: ConfigurationStore, @unchecked Sendable {
public var asrApiKey: String { snapshot.asrApiKey }
public var asrModel: String { snapshot.asrModel }
public var engineMode: String { snapshot.engineMode }
public var credentialSource: CredentialSource { snapshot.credentialSource }
public var polishIntensity: PolishIntensity { snapshot.polishIntensity }
public var aiResponseLength: AIResponseLength { snapshot.aiResponseLength }
public var llmThinkingEnabled: Bool { snapshot.llmThinkingEnabled }
@@ -119,8 +124,15 @@ public struct LiveConfigurationStore: ConfigurationStore, @unchecked Sendable {
public var detectedAppContext: (context: AppContext, observedAt: Date)? { snapshot.detectedAppContext }
public var cloudASRPersistence: UserDefaults { snapshot.cloudASRPersistence }
public func makeClient() -> LLMClient {
LLMClientFactory.make(
public func makeClient(taskKind: ManagedGatewayTaskKind?) -> LLMClient {
if credentialSource == .managed {
return ManagedLLMClient(
capability: .polish,
taskKind: taskKind,
grants: GatewayGrantCoordinator()
)
}
return LLMClientFactory.make(
providerId: providerId,
baseURL: baseURL,
apiKey: apiKey,