b9b5188265
- LiveDSAskClient: S3-owned concrete DSAskClient (GET /search/ask -> decode AIFabric.AskResponse), the piece S2's plan assigned to S3 for the real RemoteDSProvider - AppAIComposition.realRouter(): makeDefaultRouter(client: LiveDSAskClient) — the one-call swap from mock to the real S2 fabric; app default stays mockRouter (offline scaffold) - DSError.from made public (used cross-module by the bridge) swift build + swift test green (71). Sources/AI untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
38 lines
1.5 KiB
Swift
38 lines
1.5 KiB
Swift
import Foundation
|
|
import AIFabric
|
|
import DSKit
|
|
|
|
/// The ONE composition touch-point that names MockAIProvider. When S2 ships real providers,
|
|
/// only this file changes (mockProviders -> realProviders) — AIService, views, and intents stay put.
|
|
public enum AppAIComposition {
|
|
public static func mockProviders(unavailable: Set<AIProviderID> = []) -> [AIProviderID: any AIProvider] {
|
|
var providers: [AIProviderID: any AIProvider] = [:]
|
|
for id in AIProviderID.allCases {
|
|
providers[id] = MockAIProvider(id: id, available: !unavailable.contains(id))
|
|
}
|
|
return providers
|
|
}
|
|
|
|
public static func mockRouter(unavailable: Set<AIProviderID> = []) -> AIRouter {
|
|
AIRouter(
|
|
providers: mockProviders(unavailable: unavailable),
|
|
policy: .default,
|
|
log: { msg in
|
|
#if DEBUG
|
|
print("[route]", msg)
|
|
#endif
|
|
}
|
|
)
|
|
}
|
|
|
|
/// FU-B seam: the real S2 fabric (RemoteDS/LocalMLX/OnDevice/Specialized) via `makeDefaultRouter`,
|
|
/// fed S3's concrete `LiveDSAskClient`. The app default stays `mockRouter` (offline scaffold);
|
|
/// switching to live is THIS one call — AIService, views, and intents are unchanged.
|
|
public static func realRouter(
|
|
base: DSBaseURL = .publicTLS,
|
|
token: @escaping @Sendable () async -> String? = { nil }
|
|
) -> AIRouter {
|
|
makeDefaultRouter(client: LiveDSAskClient(base: base, token: token))
|
|
}
|
|
}
|