Files
hermes-agent/apps/desktop/src/api/models.ts
T
Brooklyn Nicholson aa20dbe73e refactor(desktop): split src/hermes.ts into src/api/ domain modules
2,248 lines of gateway REST client become twelve modules by domain, with
hermes.ts left as a barrel so all 144 importers stay put. The import
graph is a star — every domain module imports only ./client, and client
imports nothing back — so there are no cycles.

The barrel names client's public exports rather than re-exporting it
wholesale. Splitting a module forces its private helpers into exports so
siblings can reach them, and export * would then republish them:
profileScoped, connectionScoped and capabilityScoped were private to
hermes.ts and have to stay that way, or a call site can assemble its own
request scope and drift from the api layer.
2026-08-19 10:50:51 -05:00

130 lines
3.4 KiB
TypeScript

import type {
AnalyticsResponse,
AuxiliaryModelsResponse,
MoaConfigResponse,
ModelAssignmentRequest,
ModelAssignmentResponse,
ModelInfoResponse,
ModelOptionsResponse
} from '@/types/hermes'
import { capabilityScoped, hermesApi, type ProfileScope, profileScoped, STARTUP_REQUEST_TIMEOUT_MS } from './client'
export function getGlobalModelInfo(profile?: null | string): Promise<ModelInfoResponse> {
return hermesApi<ModelInfoResponse>({
...profileScoped(profile),
path: '/api/model/info',
timeoutMs: STARTUP_REQUEST_TIMEOUT_MS
})
}
export function getUsageAnalytics(days = 30, profile?: ProfileScope): Promise<AnalyticsResponse> {
return window.hermesDesktop.api<AnalyticsResponse>({
...capabilityScoped(profile),
path: `/api/analytics/usage?days=${Math.max(1, Math.floor(days))}`
})
}
export function getGlobalModelOptions(
opts?: {
refresh?: boolean
includeUnconfigured?: boolean
explicitOnly?: boolean
},
profile?: null | string
): Promise<ModelOptionsResponse> {
const params = new URLSearchParams()
if (opts?.refresh) {
params.set('refresh', '1')
}
if (opts?.includeUnconfigured) {
params.set('include_unconfigured', '1')
}
if (opts?.explicitOnly !== false) {
params.set('explicit_only', '1')
}
return hermesApi<ModelOptionsResponse>({
...profileScoped(profile),
path: params.size > 0 ? `/api/model/options?${params.toString()}` : '/api/model/options',
timeoutMs: STARTUP_REQUEST_TIMEOUT_MS
})
}
export interface RecommendedDefaultModel {
provider: string
model: string
/** True/false for Nous (free vs paid tier); null for other providers. */
free_tier: boolean | null
}
// Recommended default model for a freshly-authenticated provider. Mirrors the
// curation `hermes model` does — for Nous it honors the free/paid tier so a
// free user gets a free model instead of a paid default.
export function getRecommendedDefaultModel(
provider: string,
profile?: null | string
): Promise<RecommendedDefaultModel> {
return hermesApi<RecommendedDefaultModel>({
...profileScoped(profile),
path: `/api/model/recommended-default?provider=${encodeURIComponent(provider)}`
})
}
export function setGlobalModel(
provider: string,
model: string
): Promise<{ ok: boolean; provider: string; model: string }> {
return hermesApi<{ ok: boolean; provider: string; model: string }>({
...profileScoped(),
path: '/api/model/set',
method: 'POST',
body: {
scope: 'main',
provider,
model
}
})
}
export function getAuxiliaryModels(profile?: null | string): Promise<AuxiliaryModelsResponse> {
return hermesApi<AuxiliaryModelsResponse>({
...profileScoped(profile),
path: '/api/model/auxiliary'
})
}
export function getMoaModels(profile?: null | string): Promise<MoaConfigResponse> {
return hermesApi<MoaConfigResponse>({
...profileScoped(profile),
path: '/api/model/moa'
})
}
export function saveMoaModels(
body: MoaConfigResponse,
profile?: null | string
): Promise<MoaConfigResponse & { ok: boolean }> {
return hermesApi<MoaConfigResponse & { ok: boolean }>({
...profileScoped(profile),
path: '/api/model/moa',
method: 'PUT',
body
})
}
export function setModelAssignment(
body: ModelAssignmentRequest,
profile?: null | string
): Promise<ModelAssignmentResponse> {
return hermesApi<ModelAssignmentResponse>({
...profileScoped(profile),
path: '/api/model/set',
method: 'POST',
body
})
}