hermes-agent / apps /desktop /src /api /system.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
a1b6014 verified
Raw History Blame Contribute Delete
9.37 kB
import type {
ActionResponse,
ActionStatusResponse,
AudioSpeakResponse,
AudioTranscriptionResponse,
AudioTtsLeaseResponse,
BackendUpdateCheckResponse,
CuratorStatusResponse,
DebugShareResponse,
ElevenLabsVoicesResponse,
MemoryProviderConfig,
MemoryProviderOAuthStatus,
MemoryStatusResponse
} from '@/types/hermes'
import { capabilityScoped, hermesApi, type ProfileScope, profileScoped } from './client'
export const AUDIO_SPEAK_MIN_REQUEST_TIMEOUT_MS = 180_000
export const AUDIO_SPEAK_MAX_REQUEST_TIMEOUT_MS = 600_000
const AUDIO_SPEAK_TIMEOUT_MS_PER_CHAR = 35
export function audioSpeakRequestTimeoutMs(text: string): number {
const estimated = Math.max(
AUDIO_SPEAK_MIN_REQUEST_TIMEOUT_MS,
Math.ceil(String(text || '').length * AUDIO_SPEAK_TIMEOUT_MS_PER_CHAR)
)
return Math.min(AUDIO_SPEAK_MAX_REQUEST_TIMEOUT_MS, estimated)
}
export const AUDIO_TRANSCRIBE_MIN_REQUEST_TIMEOUT_MS = 180_000
export const AUDIO_TRANSCRIBE_MAX_REQUEST_TIMEOUT_MS = 600_000
// The transcribe payload is the base64 audio data URL itself, so its string
// length tracks clip size. ~0.1ms/char keeps short clips at the floor while
// letting multi-minute recordings scale toward the cap (a base64 char is
// ~0.75 bytes, so at 128kbps ≈ 21k chars/s of audio this budgets ~2s of
// timeout per 1s of audio before the cap clamps it).
const AUDIO_TRANSCRIBE_TIMEOUT_MS_PER_CHAR = 0.1
export function audioTranscribeRequestTimeoutMs(dataUrl: string): number {
const estimated = Math.max(
AUDIO_TRANSCRIBE_MIN_REQUEST_TIMEOUT_MS,
Math.ceil(String(dataUrl || '').length * AUDIO_TRANSCRIBE_TIMEOUT_MS_PER_CHAR)
)
return Math.min(AUDIO_TRANSCRIBE_MAX_REQUEST_TIMEOUT_MS, estimated)
}
// surface=declared serves the curated desktop schema; the dashboard consumes the raw plugin schema.
export function getMemoryProviderConfig(provider: string, profile?: null | string): Promise<MemoryProviderConfig> {
return hermesApi<MemoryProviderConfig>({
...profileScoped(profile),
path: `/api/memory/providers/${encodeURIComponent(provider)}/config?surface=declared`
})
}
export function saveMemoryProviderConfig(
provider: string,
values: Record<string, string>,
profile?: null | string
): Promise<{ ok: boolean }> {
return hermesApi<{ ok: boolean }>({
...profileScoped(profile),
path: `/api/memory/providers/${encodeURIComponent(provider)}/config?surface=declared`,
method: 'PUT',
body: { values }
})
}
// Memory-provider OAuth connect (provider-keyed; 404s for providers without an
// OAuth flow). Profile-scoped: the grant lands in the active profile's config.
export function startMemoryProviderOAuth(
provider: string,
profile?: null | string
): Promise<MemoryProviderOAuthStatus> {
return hermesApi<MemoryProviderOAuthStatus>({
...profileScoped(profile),
path: `/api/memory/providers/${encodeURIComponent(provider)}/oauth/start`,
method: 'POST'
})
}
export function getMemoryProviderOAuthStatus(
provider: string,
profile?: null | string
): Promise<MemoryProviderOAuthStatus> {
return hermesApi<MemoryProviderOAuthStatus>({
...profileScoped(profile),
path: `/api/memory/providers/${encodeURIComponent(provider)}/oauth/status`
})
}
// ---------------------------------------------------------------------------
// Memory data + curator (parity with `hermes memory` / `hermes curator`).
// ---------------------------------------------------------------------------
export function getMemoryStatus(): Promise<MemoryStatusResponse> {
return hermesApi<MemoryStatusResponse>({
...profileScoped(),
path: '/api/memory'
})
}
export function resetMemory(target: 'all' | 'memory' | 'user'): Promise<{ ok: boolean; deleted: string[] }> {
return hermesApi<{ ok: boolean; deleted: string[] }>({
...profileScoped(),
path: '/api/memory/reset',
method: 'POST',
body: { target }
})
}
export function getCuratorStatus(): Promise<CuratorStatusResponse> {
return hermesApi<CuratorStatusResponse>({
...profileScoped(),
path: '/api/curator'
})
}
export function setCuratorPaused(paused: boolean): Promise<{ ok: boolean; paused: boolean }> {
return hermesApi<{ ok: boolean; paused: boolean }>({
...profileScoped(),
path: '/api/curator/paused',
method: 'PUT',
body: { paused }
})
}
export function runCurator(): Promise<ActionResponse> {
return hermesApi<ActionResponse>({
...profileScoped(),
path: '/api/curator/run',
method: 'POST',
body: {}
})
}
export function restartGateway(): Promise<ActionResponse> {
return hermesApi<ActionResponse>({
...profileScoped(),
path: '/api/gateway/restart',
method: 'POST'
})
}
export function updateHermes(): Promise<ActionResponse> {
return hermesApi<ActionResponse>({
...profileScoped(),
path: '/api/hermes/update',
method: 'POST'
})
}
/** Query the connected backend's own update state. In remote mode this is the
* authoritative source for the backend's behind-count + "what's changed",
* distinct from the Electron client clone's git state. */
export function checkHermesUpdate(force = false): Promise<BackendUpdateCheckResponse> {
return hermesApi<BackendUpdateCheckResponse>({
...profileScoped(),
path: `/api/hermes/update/check${force ? '?force=true' : ''}`
})
}
export function getActionStatus(name: string, lines = 200, profile?: ProfileScope): Promise<ActionStatusResponse> {
return window.hermesDesktop.api<ActionStatusResponse>({
...capabilityScoped(profile),
path: `/api/actions/${encodeURIComponent(name)}/status?lines=${Math.max(1, lines)}`
})
}
export function transcribeAudio(dataUrl: string, mimeType?: string): Promise<AudioTranscriptionResponse> {
return hermesApi<AudioTranscriptionResponse>({
path: '/api/audio/transcribe',
method: 'POST',
...profileScoped(),
body: {
data_url: dataUrl,
mime_type: mimeType
},
// Transcription blocks until provider STT, file handling, and response
// encoding finish. Remote providers and long clips regularly exceed the
// default 15s Electron backend timeout.
timeoutMs: audioTranscribeRequestTimeoutMs(dataUrl)
})
}
export function speakText(text: string): Promise<AudioSpeakResponse> {
return hermesApi<AudioSpeakResponse>({
...profileScoped(),
path: '/api/audio/speak',
method: 'POST',
body: { text },
// TTS blocks until provider synthesis, file read, and base64 encoding
// finish. Remote providers and large messages regularly exceed the
// default 15s Electron backend timeout.
timeoutMs: audioSpeakRequestTimeoutMs(text)
})
}
// Acquiring a lease pre-loads the configured TTS engine. For local engines
// that is a model load and, on a fresh install, a voice download — well past
// the default 15s Electron backend timeout.
export const AUDIO_TTS_LEASE_REQUEST_TIMEOUT_MS = 180_000
/**
* Tell the backend a speech-output toggle flipped so it can warm the TTS engine
* (`active: true`) or release it once no surface needs it (`active: false`).
* `lease` names the toggle — `desktop:read-aloud`, `desktop:conversation`.
*/
export function setTtsLease(lease: string, active: boolean): Promise<AudioTtsLeaseResponse> {
return hermesApi<AudioTtsLeaseResponse>({
...profileScoped(),
path: '/api/audio/tts-lease',
method: 'POST',
body: { active, lease },
timeoutMs: AUDIO_TTS_LEASE_REQUEST_TIMEOUT_MS
})
}
export function getElevenLabsVoices(profile?: null | string): Promise<ElevenLabsVoicesResponse> {
return hermesApi<ElevenLabsVoicesResponse>({
path: '/api/audio/elevenlabs/voices',
...profileScoped(profile)
})
}
/** `gh` CLI presence + auth state, for the composer's GitHub skill pill
* (GitHub is deliberately not an MCP — the github/* skills are the
* integration). Backend caches for 5 minutes; `refresh` bypasses. */
export function getGhAuthStatus(refresh = false): Promise<{ available: boolean; authenticated: boolean }> {
return hermesApi<{ available: boolean; authenticated: boolean }>({
...profileScoped(),
path: `/api/git/gh-auth${refresh ? '?refresh=true' : ''}`
})
}
// ---------------------------------------------------------------------------
// Maintenance operations (parity with `hermes doctor` / `hermes security
// audit` / `hermes backup` / `hermes debug share` and the dashboard System
// page). All except debug share are spawn-based background actions tailed via
// getActionStatus().
// ---------------------------------------------------------------------------
export function runDoctor(): Promise<ActionResponse> {
return hermesApi<ActionResponse>({ path: '/api/ops/doctor', method: 'POST', body: {} })
}
export function runSecurityAudit(): Promise<ActionResponse> {
return hermesApi<ActionResponse>({ path: '/api/ops/security-audit', method: 'POST', body: {} })
}
export function runBackup(): Promise<ActionResponse & { archive?: string }> {
return hermesApi<ActionResponse & { archive?: string }>({
path: '/api/ops/backup',
method: 'POST',
body: {}
})
}
export function runDebugShare(): Promise<DebugShareResponse> {
return hermesApi<DebugShareResponse>({
path: '/api/ops/debug-share',
method: 'POST',
body: {},
// Synchronous upload of report + logs to the paste service.
timeoutMs: 120_000
})
}