feat(desktop): route Nous subscribers onto the Tool Gateway from the GUI

When the GUI sets the main provider to Nous via POST /api/model/set, call
the same apply_nous_managed_defaults the CLI uses after model selection, so
GUI/onboarding users land on the Nous Tool Gateway the same way CLI users do
— no separate prompt, no duplicated logic.

Purely additive: apply_nous_managed_defaults skips any tool where the user
has a direct key (FIRECRAWL_API_KEY, FAL_KEY, etc.) or explicit config, so it
never overwrites a user's own setup. Only unconfigured tools get routed.

- web_server.py: in set_model_assignment (scope=main, provider=nous), resolve
  enabled toolsets and apply managed defaults; guarded so a Portal hiccup never
  blocks saving the model. Returns routed tools as gateway_tools.
- onboarding.ts: surface a 'Tool Gateway enabled' toast listing routed tools.
- types/hermes.ts: add gateway_tools to ModelAssignmentResponse.
- tests: cover nous-applies, non-nous-skips, and failure-doesnt-block-save.
This commit is contained in:
emozilla
2026-05-31 05:10:33 -04:00
parent 967bc910b4
commit c3a21c5d49
4 changed files with 138 additions and 2 deletions
+32 -1
View File
@@ -134,6 +134,36 @@ function notifyReady(provider: string) {
notify({ kind: 'success', title: 'Hermes is ready', message: `${provider} connected.` })
}
// Human-friendly labels for tools auto-routed through the Nous Tool Gateway,
// mirroring hermes_cli/nous_subscription._GATEWAY_TOOL_LABELS so the GUI and
// CLI describe the same thing.
const GATEWAY_TOOL_LABELS: Record<string, string> = {
browser: 'browser automation',
image_gen: 'image generation',
tts: 'text-to-speech',
video_gen: 'video generation',
web: 'web search & extract'
}
// When switching to Nous auto-routes unconfigured tools through the Tool
// Gateway, tell the user which ones — same information the CLI prints. Silent
// when nothing changed (subscriber already configured, has own keys, etc.).
function notifyGatewayTools(tools: string[] | undefined) {
if (!tools || tools.length === 0) {
return
}
const labels = tools.map(t => GATEWAY_TOOL_LABELS[t] ?? t)
const list = labels.length === 1 ? labels[0] : `${labels.slice(0, -1).join(', ')} and ${labels[labels.length - 1]}`
notify({
durationMs: 8000,
kind: 'info',
message: `${list} now run through your Nous subscription — no separate API keys needed.`,
title: 'Tool Gateway enabled'
})
}
// After credentials are persisted, ask the backend which provider+models
// are now authenticated. Pick the first curated model for the matching
// provider as a sensible default, persist it via /api/model/set, and
@@ -221,11 +251,12 @@ async function completeWithModelConfirm(
// (3) If they bail out (e.g., refresh the page), they still end up
// with a working config, not an empty-model fallback.
try {
await setModelAssignment({
const res = await setModelAssignment({
scope: 'main',
provider: defaults.providerSlug,
model: defaults.defaultModel
})
notifyGatewayTools(res.gateway_tools)
} catch {
// Persistence failed — still show the confirm card so the user can
// pick something explicitly. The backend will pick its own default
+4
View File
@@ -513,6 +513,10 @@ export interface ModelAssignmentRequest {
}
export interface ModelAssignmentResponse {
/** Toolset keys auto-routed through the Nous Tool Gateway as a result of
* switching the main provider to Nous. Empty unless provider === 'nous'
* and the user is a paid subscriber with unconfigured tools. */
gateway_tools?: string[]
model?: string
ok: boolean
provider?: string