mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
da721fa276
Adds the five v0.12 inference providers to ModelCatalogService.overlayOnlyProviders so the model picker reaches them. IDs match HERMES_OVERLAYS verbatim: - gmi → GMI Cloud (api_key) - azure-foundry → Azure AI Foundry (api_key) - lmstudio → LM Studio (api_key, promoted from custom-endpoint alias) - minimax-oauth → MiniMax (OAuth, oauth_external) - tencent-tokenhub → Tencent TokenHub (api_key) Auxiliary tasks: drop the `flush_memories` row (Hermes removed it entirely in v0.12) and add `auxiliary.curator` so users can configure the model the autonomous curator's review fork uses. The Curator row is gated on HermesCapabilities.hasCuratorAux, so v0.11 hosts don't see a control that writes a key Hermes ignores. AuxiliarySettings, the YAML parser, and HealthViewModel's Tool Gateway breakdown are all updated. Side fixes: - CredentialPoolsGatingTests was missing `import ScarfCore` after ModelCatalogService moved to the package (broke the test target's compile against pure-Mac scarf). - Promoted `ModelCatalogService.overlayOnlyProviders` to public so the new `v012OverlayProvidersCarryCorrectAuthTypes` lock-in test can reach it. Tests: 14 ToolGateway tests pass; 209 ScarfCore tests pass; both Mac and iOS schemes build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
86 lines
3.8 KiB
Swift
86 lines
3.8 KiB
Swift
import Testing
|
|
import Foundation
|
|
import ScarfCore
|
|
@testable import scarf
|
|
|
|
/// Tests that ``CredentialPoolsOAuthGate`` steers each known provider to
|
|
/// the right OAuth flow. The regression this prevents: a user hitting the
|
|
/// "Start OAuth" button for nous / openai-codex / qwen-oauth /
|
|
/// google-gemini-cli / copilot-acp and watching the UI stall silently.
|
|
@Suite struct CredentialPoolsGatingTests {
|
|
|
|
/// Synthesize a ModelCatalogService over a minimal fixture cache so
|
|
/// tests don't depend on the live `~/.hermes/models_dev_cache.json`.
|
|
private func makeCatalog() throws -> ModelCatalogService {
|
|
let dir = FileManager.default.temporaryDirectory.appendingPathComponent("scarf-cpgate-\(UUID().uuidString)")
|
|
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
let path = dir.appendingPathComponent("models_dev_cache.json").path
|
|
// Include anthropic so the .ok path has a recognizable provider.
|
|
let json = """
|
|
{
|
|
"anthropic": {
|
|
"name": "Anthropic",
|
|
"models": { "claude-sonnet-4-5": { "name": "Claude Sonnet 4.5" } }
|
|
}
|
|
}
|
|
"""
|
|
try json.write(toFile: path, atomically: true, encoding: .utf8)
|
|
return ModelCatalogService(path: path)
|
|
}
|
|
|
|
@Test func nousRoutesToDedicatedSignInFlow() throws {
|
|
let catalog = try makeCatalog()
|
|
#expect(CredentialPoolsOAuthGate.resolve(providerID: "nous", catalog: catalog) == .useNousSignIn)
|
|
// Whitespace + case insensitivity should also work — users who type
|
|
// "Nous " shouldn't fall through to the generic flow.
|
|
#expect(CredentialPoolsOAuthGate.resolve(providerID: " Nous ", catalog: catalog) == .useNousSignIn)
|
|
}
|
|
|
|
@Test func deviceCodeAndExternalProvidersRouteToCLI() throws {
|
|
let catalog = try makeCatalog()
|
|
// `openai-codex` is .oauthExternal in the overlay table.
|
|
if case .useCLI(let provider) = CredentialPoolsOAuthGate.resolve(providerID: "openai-codex", catalog: catalog) {
|
|
#expect(provider == "openai-codex")
|
|
} else {
|
|
Issue.record("openai-codex should route to .useCLI")
|
|
}
|
|
// `qwen-oauth` is .oauthExternal.
|
|
if case .useCLI = CredentialPoolsOAuthGate.resolve(providerID: "qwen-oauth", catalog: catalog) {
|
|
// ok
|
|
} else {
|
|
Issue.record("qwen-oauth should route to .useCLI")
|
|
}
|
|
// `google-gemini-cli` is .oauthExternal.
|
|
if case .useCLI = CredentialPoolsOAuthGate.resolve(providerID: "google-gemini-cli", catalog: catalog) {
|
|
// ok
|
|
} else {
|
|
Issue.record("google-gemini-cli should route to .useCLI")
|
|
}
|
|
// `copilot-acp` is .externalProcess.
|
|
if case .useCLI = CredentialPoolsOAuthGate.resolve(providerID: "copilot-acp", catalog: catalog) {
|
|
// ok
|
|
} else {
|
|
Issue.record("copilot-acp should route to .useCLI")
|
|
}
|
|
}
|
|
|
|
@Test func pkceProvidersPassThroughAsOK() throws {
|
|
let catalog = try makeCatalog()
|
|
// Anthropic is a standard PKCE provider in Hermes — must not be gated.
|
|
#expect(CredentialPoolsOAuthGate.resolve(providerID: "anthropic", catalog: catalog) == .ok)
|
|
}
|
|
|
|
@Test func unknownProvidersDefaultToOK() throws {
|
|
let catalog = try makeCatalog()
|
|
// Providers we don't know about shouldn't be blocked — users with
|
|
// custom setups need the escape hatch.
|
|
#expect(CredentialPoolsOAuthGate.resolve(providerID: "custom-provider-xyz", catalog: catalog) == .ok)
|
|
}
|
|
|
|
@Test func emptyProviderReturnsProviderEmpty() throws {
|
|
let catalog = try makeCatalog()
|
|
#expect(CredentialPoolsOAuthGate.resolve(providerID: "", catalog: catalog) == .providerEmpty)
|
|
#expect(CredentialPoolsOAuthGate.resolve(providerID: " ", catalog: catalog) == .providerEmpty)
|
|
}
|
|
}
|