feat(hermes-v12): provider catalog + auxiliary swap (Phase B)

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>
This commit is contained in:
Alan Wizemann
2026-05-01 12:16:37 +02:00
parent a90a29add8
commit da721fa276
9 changed files with 118 additions and 19 deletions
@@ -258,7 +258,13 @@ public struct VoiceSettings: Sendable, Equatable {
)
}
/// Eight sub-models that share the same provider/model/base_url/api_key/timeout shape.
/// Per-task auxiliary model overrides.
///
/// `flush_memories` was removed entirely in Hermes v0.12 (the underlying
/// task no longer exists), so the corresponding field was dropped here.
/// `curator` was added in v0.12 Curator's review fork uses its own
/// model so users can keep main-model spend separate from background
/// maintenance.
public struct AuxiliarySettings: Sendable, Equatable {
public var vision: AuxiliaryModel
public var webExtract: AuxiliaryModel
@@ -267,7 +273,8 @@ public struct AuxiliarySettings: Sendable, Equatable {
public var skillsHub: AuxiliaryModel
public var approval: AuxiliaryModel
public var mcp: AuxiliaryModel
public var flushMemories: AuxiliaryModel
/// v0.12+; pre-v0.12 Hermes installs ignore this slot.
public var curator: AuxiliaryModel
public init(
@@ -278,7 +285,7 @@ public struct AuxiliarySettings: Sendable, Equatable {
skillsHub: AuxiliaryModel,
approval: AuxiliaryModel,
mcp: AuxiliaryModel,
flushMemories: AuxiliaryModel
curator: AuxiliaryModel
) {
self.vision = vision
self.webExtract = webExtract
@@ -287,7 +294,7 @@ public struct AuxiliarySettings: Sendable, Equatable {
self.skillsHub = skillsHub
self.approval = approval
self.mcp = mcp
self.flushMemories = flushMemories
self.curator = curator
}
public nonisolated static let empty = AuxiliarySettings(
vision: .empty,
@@ -297,7 +304,7 @@ public struct AuxiliarySettings: Sendable, Equatable {
skillsHub: .empty,
approval: .empty,
mcp: .empty,
flushMemories: .empty
curator: .empty
)
}
@@ -122,7 +122,7 @@ public extension HermesConfig {
skillsHub: aux("skills_hub"),
approval: aux("approval"),
mcp: aux("mcp"),
flushMemories: aux("flush_memories")
curator: aux("curator")
)
let security = SecuritySettings(
@@ -425,15 +425,17 @@ public struct ModelCatalogService: Sendable {
// MARK: - Hermes overlay providers
/// The six providers Hermes surfaces via `hermes model` that have no
/// The 11 providers Hermes surfaces via `hermes model` that have no
/// entry in `models_dev_cache.json` (models.dev doesn't mirror them).
/// Mirrors the overlay-only subset of `HERMES_OVERLAYS` in
/// `hermes-agent/hermes_cli/providers.py`. The other ~19 overlay entries
/// `hermes-agent/hermes_cli/providers.py`. The other overlay entries
/// already ship in the cache and only add augmentation (base-URL
/// override, extra env vars) that Scarf doesn't currently display.
///
/// Keep this in sync with the Python side on Hermes version bumps.
static let overlayOnlyProviders: [String: HermesProviderOverlay] = [
/// Keep this in sync with the Python side on Hermes version bumps
/// see `ToolGatewayTests.v012OverlayProvidersCarryCorrectAuthTypes`
/// for the auth-type lock-in.
public static let overlayOnlyProviders: [String: HermesProviderOverlay] = [
"nous": HermesProviderOverlay(
displayName: "Nous Portal",
baseURL: "https://inference-api.nousresearch.com/v1",
@@ -476,6 +478,53 @@ public struct ModelCatalogService: Sendable {
subscriptionGated: false,
docURL: nil
),
// -- v0.12 additions ---------------------------------------------
// Hermes v2026.4.30 added five overlay-only providers that
// models.dev doesn't mirror. Provider IDs match HERMES_OVERLAYS
// verbatim drift here means the picker can't reach them.
"gmi": HermesProviderOverlay(
displayName: "GMI Cloud",
baseURL: "https://api.gmi-serving.com/v1",
authType: .apiKey,
subscriptionGated: false,
docURL: nil
),
"azure-foundry": HermesProviderOverlay(
displayName: "Azure AI Foundry",
// Base URL is per-tenant Hermes resolves it from the
// AZURE_FOUNDRY_BASE_URL env var at runtime. Leave nil so the
// settings UI shows "Tenant URL set via env" instead of a
// misleading default.
baseURL: nil,
authType: .apiKey,
subscriptionGated: false,
docURL: nil
),
"lmstudio": HermesProviderOverlay(
displayName: "LM Studio",
// v0.12 promotes LM Studio from custom-endpoint alias to a
// first-class provider. 1234 is the LM Studio default port;
// users with a non-default port set LM_BASE_URL.
baseURL: "http://127.0.0.1:1234/v1",
authType: .apiKey,
subscriptionGated: false,
docURL: nil
),
"minimax-oauth": HermesProviderOverlay(
displayName: "MiniMax (OAuth)",
baseURL: "https://api.minimax.io/anthropic",
authType: .oauthExternal,
subscriptionGated: false,
docURL: nil
),
"tencent-tokenhub": HermesProviderOverlay(
displayName: "Tencent TokenHub",
// Resolved from TOKENHUB_BASE_URL at runtime.
baseURL: nil,
authType: .apiKey,
subscriptionGated: false,
docURL: nil
),
]
}