# Architecture [完整中文说明](./architecture.zh.md) > Derived from direct inspection of the DSH 0.1.0-rc.6 host APIs, with each > integration fact covered by `tests/api-assumptions.spec.ts`. Updated for the > V2 thin-router design. ## Overview The plugin is a **policy layer over the DSH native `llm` registry**: ```text DSH「模型」设置页 └─ 提供方 / 凭据 / 模型目录 / 模态 / 推理强度 / 重试 │ ▼ ctx.llm 官方注册表 │ ┌──────────┴──────────┐ ▼ ▼ 模型路由薄策略层 DSH 正常模型调用 关键词规则 / 受控工具 会话 request/header 写入 媒体通道(插件独立命名空间) └─ image_gen;以后的视频/音频/3D 只保留接口方向,不伪装为已实现 ``` - **DSH 管模型,插件管策略。** The plugin never registers `mr:` routes, never duplicates the OpenAI-compatible chat adapter, model discovery, the default model, or retry mechanisms. - **No-match changes nothing.** The keyword engine writes the session header ONLY after a rule hit AND a successful exact validation; V1's force-route fallback is removed entirely. - **Subagent selection is repaired at the child boundary.** DSH subagent children do not pass through api-proxy's mainline `installSelection`; after a keyword hit passes exact validation, the plugin lazily installs DSH's official model-selection assembly only on `origin: subagent`, so the validated header governs the child's first request. The per-agent installation is idempotent, disposed with the plugin, and mainline sessions are left untouched. - **Media is isolated.** `mediaProviders` live in their own namespace and never enter the text-model registry; `image_gen` is Beta. - **Credential transport is explicit.** Media endpoints require HTTPS except loopback development hosts. Empty credential references omit Authorization; endpoint URLs cannot embed credentials, query strings, or fragments. ## Host API facts (verified against rc.6) 1. **Provider registration** — service name `llm` (`LlmRuntime`): `ctx.llm.registerAdapter(providers, adapter)` throws `LlmError` code `DUPLICATE_ADAPTER`; `listProviders()`/`listConfigurableProviders()` read the live directory; `llm/adapters-updated` is payload-free. 2. **Exact validation** — `resolveModelInfo(provider, model, signal)` and `resolveCallConfig({provider, model, reasoningEffort}, signal)`; the advisory `listModels()` catalog is NOT a validity source. 3. **Session model selection write channel** — appending a `request/header` event to `agent.session` is the durable selection write (the `/model` channel). Assistant provenance (`source: {kind:'model', provider, model}`) lands in the session log. 4. **Default model** — `ctx.agentDefaultModel.currentSelection()` (settings namespace `agent-default-model`), read-only from the plugin. 5. **Tools** — `ctx.tools.register(ToolDefinition)`; `exec.agent` is available in `execute` (Agent exposes `.ctx` and `.session`). 6. **Settings** — `ctx.settings.register(ns, Config, {base, applies:'live', validate})` + `describe()`/`replace(ns, value, expectedRevision)`; `SettingsConflictError` (code `SETTINGS_CONFLICT`) guards stale writes. 7. **Credentials** — `ctx.credentials.resolve(ref)` / `describe(ref)`; only reference names ever cross the wire. 8. **webServer** — rc.6 service name is `webServer`; `ctx.inject(['webServer'], cb)` + `register({kind:'exact'|'prefix', path, handler})`. 9. **Client wire** — the browser reads the official directory through `ctx.connection.api.llm.providers({})` / `llm.models({})` (unwrap `{result: {ok, ...}}`), and refreshes on the forwarded host events `llm/adapters-updated` / `settings/document-updated` via `ctx.remote.$on`. ## Modules | File | Role | |---|---| | `src/config.ts` | V2 settings schema, validation, `RouteTarget` / media types | | `src/migration.ts` | Pure V1→V2 in-memory migration + `toPersistedV2` | | `src/catalog.ts` | Official directory reader + exact target resolution (stable error codes) | | `src/router.ts` | Pure rule matching + `applyValidatedTarget` (validate-then-write) | | `src/tools.ts` | Allowlist-gated `model_route` + Beta `image_gen` | | `src/media/types.ts` | Isolated media channel types (reserved video/audio/3D) | | `src/media/download.ts` | Restricted downloads: https/hosts/redirects/private-address rejection/size cap/timeouts | | `src/artifacts.ts` | Magic-confirmed image saving | | `src/web.ts` | V2 policy settings backend + artifact preview realpath fence | | `src/index.ts` | Lifecycle: settings, rule listener, tools, Web routes, topology refresh | | `src/client/index.tsx` | 模型中枢 policy page + tool cards | ## Stable error codes `ROUTE_PROVIDER_INACTIVE`, `ROUTE_MODEL_UNRESOLVED`, `ROUTE_REASONING_INVALID`, `ROUTE_NOT_ALLOWED`, `ROUTE_MIGRATION_REQUIRED`, `MEDIA_PROVIDER_UNKNOWN`, `MEDIA_MODEL_NOT_ALLOWED`, `MEDIA_DOWNLOAD_BLOCKED`, `MEDIA_TOO_LARGE`, `MEDIA_NOT_IMAGE`, `ARTIFACT_PATH_BLOCKED`. Routing failures never write the session header. ## Extending media / 扩展媒体能力 1. Implement an adapter against the reserved capability (submit/poll/cancel). 2. Extend `MEDIA_CAPABILITIES` in `src/config.ts` (a compatibility task). 3. Add a tool that consumes the new channel capability, register it in `src/index.ts`. The channel layer, settings page, and Web backend need no structural changes. ## Build reproducibility `pnpm build` = `tsc -p tsconfig.json` (host, ESM → `lib/`) + `tsc -p tsconfig.client.json` (client, CommonJS → `.client-build`) + `scripts/build-client.mjs` (wraps the client into the `window.__ModuleLoader__.load` bundle at `lib/client.js`). `lib/` is committed so a clone installs without building; rebuilds are deterministic from `src/`. Tests: `pnpm test` (vitest, node environment, sequential files).