description: "The shared API gateway for web GUI host clients: the browser-safe API contract, the fetch carriers, and the host-side gateway service every client shape uses."
English | 中文
Every client of the web GUI host calls one typed API through dsh-host-apiproxy — sessions and history, subagents, workspaces, directory picking, model selection, agent presets, skills, goals, settings, credentials, LLM catalogs, events, and session export — moved over HTTP or in-process by fetch carriers. The contract layer has zero Node dependencies and imports from the browser, so one typed API serves the Web server, Electron, and any future client shape. The shipped Web composition assembles the gateway in dsh-web-app. Choosing a carrier, calling the domain APIs, and configuring the gateway come first; the wire protocol internals live in a collapsible developer section below.
Compose the gateway when a client of the GUI host needs the session, workspace, and configuration APIs: load ApiProxyService, wrap ctx.apiProxy in a carrier, and call the typed domain methods.
toFetchHandler(api) turns the gateway into a pure WHATWG fetch function for an HTTP server (the shipped Web composition exposes it behind /api/… routes), while InProcessApiClient runs the same serialization and validation path in-process — the isomorphic point for callers and tests that need the full wire path without a network.
const client = new InProcessApiClient(toFetchHandler(ctx.apiProxy))
const response = await client.sessions.list({})
The HTTP carrier refuses non-JSON POST bodies with 415 before dispatch, so cross-site simple requests can never run a side-effectful method blind. The browser carrier applies the same Host/Origin checks and signed-cookie authentication to every Host API method (dsh-client-connection); individual Client features may still withhold native or persistent operations on non-loopback pages.
The API is grouped into domains: sessions (list, create, history, prompt, cancel, queue, models, selectModel, rename, fork, search, attachment), subagents, workspace, host (describe, pickDirectory, listDirectory, createDirectory, openPath), skills, agentPresets, goals, settings, credentials, llm, events, and downloads. The sessions, workspace, and events contracts are owned by the Session Controller, Workspace Controller, and API Remotes packages respectively; the remaining domain contracts and the RpcMethodMap live in src/api/.
session.history pages a session's appended message stream (maxMessages counts append-origin user/message and assistant/message events, so model-only replacement copies consume no quota) and keeps each page a contiguous raw event range, which keeps a compaction's log-only summary on the same page as the replacement that cites it. The tail page optionally carries a projections block — the watermark snapshot of every registered projection unit — while the gateway pushes live session/projection frames for units whose state changed. session.search is a bounded content-search projection over the sessions visible through session.list: at most 20 hits, snippets of at most 240 code points, and every hit revalidated against the visible set.
session.list and workspace.list are separate reconnect baselines. Blank sessions stay hidden until the first turn, archiving hides a session from grouping surfaces without touching its log, and registration deletion preserves the directory and session logs. Cold summaries verify blankness by probing a small eligible artifact; a projection-cache miss or stale hint falls back to createdAt, so a recently worked large session may sort lower until the next checkpoint.
GET /api/session.export?sessionId=…&includeDescendants=true streams a ZIP of the session's stored artifact text verbatim, every subagent descendant under subagents/<id>/, and each referenced image under media/<attachmentId>.<ext>. HEAD runs the same root preparation without a body, so browsers detect pre-stream failures before handing the GET to the download manager. The response is chunked as it is produced, and sessionExportCompressionLevel (0–9, default 6) trades CPU and latency against archive size. Missing persistence, session-query, or attachment services answer 500, a backend without per-session raw artifacts 501, and a missing root session 404.
session.models reports the current ModelSelection separately from provider-grouped advisory models, and session.selectModel saves an accepted switch as the deployment default through the shared agent-default-model settings section — a default naming an unavailable provider still reaches the selector as current instead of being silently replaced. Each access resolves an in-process selection first, then the session's latest request/header, then the deployment default. A logged reasoning effort marked as an adapter default remains absent from the restored selection, so the next resolution does not promote it into an explicit choice or record a false header change. agentPreset.list exposes the deployment's preset roster with each row's trust and a broken reason when a preset cannot compose a session; agentPreset.select swaps a blank session's composition and is refused once a turn has run. skill.list serves the composer menu with each skill's modelInvocable flag, and command.execute runs a slash command with pure admission semantics whose outcome rides the logged command/run/command/done pair. The settings.*, credentials.*, and llm.* domains are the configuration-page wire: settings.describe returns each namespace's schema and redacted layered values, settings.mutate is the removal path for a client holding the redacted view, secrets never ride any response, and llm.discoverModels interrogates a provider endpoint the page is still drafting without writing anything.
| Field | Default | Meaning |
|---|---|---|
nativeOpen |
platform-detected | Whether the deployment can hand paths to a native desktop opener |
sessionExportCompressionLevel |
6 |
DEFLATE level for every session-log ZIP entry, 0–9 |
The generated configuration catalog is the exhaustive source for every accepted field and its JSDoc.
Read these when the package-level contract is not enough. They move from the layering decision to the browser-side consumption architecture and the adjacent subsystems.
None, as the wire contract and fetch carriers move already-composed messages and register nothing model-facing.
None; this package neither assembles nor sends a provider request.
These limits define where the gateway is a poor fit; they are current package constraints, not a task backlog.
dsh-api-remotes and the consumer verb is ctx.remote.$on (rationale).RpcReceipt type.RpcMethodMap — prompt.mode: 'inject', job.list, and a describe hostInstanceId are documented reservations; model discovery uses llm.models. An unknown method fails loud at envelope parse rather than getting a not-implemented code.host.describe gains a version negotiation field only when an independently released client exists.native capability, host.pickDirectory reports an actionable error when neither Zenity nor KDialog is installed; the browse backend is the composition-level fallback (see the native backend README).lastPromptAt falls back to createdAt unless an eligible small artifact supplies an exact fold. The bounded blank-verification decision owns this safety direction; an authoritative exact recency index remains scoped in the last-activity-index proposal.