/** * One-step host startup seam: boot core → assemble ApiProxy → assemble the * fetch handler. The returned RunningHost is shell-agnostic — node:http * (dsh web), in-process injection (dsh -p, tests), an IPC bridge (future * Electron sidecar), and front-door plugin mounting (future dsh acp) all * consume the same shape. */ import type { Context } from 'cordis' import type { ApiProxy } from '@deepseek-ai/dsh-host-apiproxy/api' import { toFetchHandler } from '@deepseek-ai/dsh-host-apiproxy' import { bootHost } from './boot.ts' import type { BootHostOptions, HostDefaults } from './boot.ts' import { createApiProxy } from './api-proxy.ts' /** Options for startHost. */ export interface StartHostOptions { /** * Passed through to bootHost verbatim (persistenceRoot required + * provider?/model?). Future host-level knobs (profile, log sink — any * output added to the assembly MUST be switchable off here) land as * additive fields. */ boot: BootHostOptions } /** Running host handle: the contract impl plus its fetch carrier and root ctx. */ export interface RunningHost { /** Contract implementation (direct calls for in-process consumers; the input of an IPC adapter). */ api: ApiProxy /** WHATWG-fetch-shaped carrier (web shell bridges it to node:http; host-side endpoint of an IPC bridge). */ handler: { fetch: typeof fetch } /** Host-level default routing (describe and every shell share this single source). */ defaults: HostDefaults /** * Root context — a formal seam, not an escape hatch: (1) the mount point for * protocol front-door plugins (`dsh acp` = startHost() → ctx.plugin(uiAcp, config)); * (2) headless session-event subscription. Discipline: consuming clients must * not bypass `api` through ctx; shells must not ctx.plugin to alter the * assembly (mounting a front door is the shell's own shape, not an assembly change). */ ctx: Context /** Single shutdown exit (ctx.fiber.dispose()). Idempotent: a second call returns the same promise. */ dispose(): Promise } /** * Boot the host and assemble its consumption surfaces in one step. * @param options - boot passthrough (see StartHostOptions). * @returns the running host handle shared by every shell shape. */ export async function startHost(options: StartHostOptions): Promise { const host = await bootHost(options.boot) const api = createApiProxy(host.ctx, host.defaults) const handler = toFetchHandler(api) let disposing: Promise | undefined return { api, handler, defaults: host.defaults, ctx: host.ctx, dispose: () => (disposing ??= host.dispose()) } }