Files
hyungi_document_server/Sources/AppFeature/AI/AppAIComposition.swift
T
Hyungi b9b5188265 feat(s3): DSAskClient HTTP bridge + realRouter seam (FU-B)
- 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>
2026-06-05 06:44:18 +09:00

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))
}
}