From bb61dc13f221fb9052a52a0c7e337fbd8e4c5898 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:48:29 +0800 Subject: [PATCH] refactor(api): colocate gateway and remote assembly --- ...08-02-typert-remote-method-calls.i18n.yaml | 4 +- .../2026-08-02-typert-remote-method-calls.md | 24 ++- ...026-08-02-typert-remote-method-calls.zh.md | 24 ++- AGENTS.md | 1 + apps/cli/composition.md | 4 +- apps/web/tests/assembled-boot.ts | 6 +- docs/api-gateway.i18n.yaml | 4 +- docs/api-gateway.md | 21 +- docs/api-gateway.zh.md | 21 +- docs/architecture.i18n.yaml | 4 +- docs/architecture.md | 2 +- docs/architecture.zh.md | 2 +- docs/capability-seams.md | 4 +- docs/config-catalog.md | 4 +- docs/cordis-catalog/services.md | 2 +- docs/core-data-structures/typert.i18n.yaml | 4 +- docs/core-data-structures/typert.md | 8 +- docs/core-data-structures/typert.zh.md | 8 +- docs/development.i18n.yaml | 4 +- docs/development.md | 2 +- docs/development.zh.md | 2 +- docs/event-producer-consumer.md | 2 +- docs/module-graph.md | 39 +++- knip.json | 2 +- packages/README.i18n.yaml | 4 +- packages/README.md | 1 + packages/README.zh.md | 1 + .../{client/remotes => api}/README.i18n.yaml | 6 +- packages/api/README.md | 17 ++ packages/api/README.zh.md | 17 ++ .../gateway}/README.i18n.yaml | 6 +- .../api-gateway => api/gateway}/README.md | 6 +- .../api-gateway => api/gateway}/README.zh.md | 6 +- .../api-gateway => api/gateway}/package.json | 4 +- .../gateway}/src/client/index.ts | 16 +- .../api-gateway => api/gateway}/src/index.ts | 2 +- .../gateway}/src/invariant.ts | 8 +- .../api-gateway => api/gateway}/src/types.ts | 2 +- .../gateway}/tests/client.spec.ts | 0 .../gateway}/tests/gateway.spec.ts | 2 +- .../api-gateway => api/gateway}/tsconfig.json | 0 packages/api/gateway/tsdown.config.ts | 3 + packages/api/remotes/README.i18n.yaml | 6 + packages/api/remotes/README.md | 25 +++ packages/api/remotes/README.zh.md | 25 +++ packages/{client => api}/remotes/package.json | 19 +- packages/api/remotes/src/agent-lookup.ts | 193 ++++++++++++++++++ .../remotes/src/client/index.ts | 11 +- packages/api/remotes/src/index.ts | 18 ++ .../{client => api}/remotes/src/invariant.ts | 8 +- .../remotes/tests/built-lib.e2e.ts | 16 +- .../{client => api}/remotes/tsconfig.json | 14 +- packages/api/remotes/tsdown.config.ts | 3 + packages/bundle/base/cordis.patch.yml | 2 +- packages/bundle/base/package.json | 2 +- packages/bundle/web-app/cordis.patch.yml | 4 +- packages/bundle/web-app/package.json | 2 +- packages/client/remotes/README.md | 22 -- packages/client/remotes/README.zh.md | 22 -- packages/client/remotes/src/index.ts | 4 - packages/client/remotes/tsdown.config.ts | 3 - packages/client/runtime/package.json | 6 +- packages/client/runtime/src/client/index.ts | 2 +- packages/client/runtime/tsconfig.json | 2 +- packages/client/ui-goal/package.json | 6 +- packages/client/ui-goal/src/client/index.ts | 2 +- packages/client/ui-goal/tsconfig.json | 2 +- packages/host/api-gateway/tsdown.config.ts | 3 - packages/host/apiproxy/package.json | 5 +- packages/host/apiproxy/src/api-proxy.ts | 161 ++------------- packages/host/apiproxy/tsconfig.json | 9 +- packages/typert/type-meta/src/index.ts | 1 + packages/typert/type-meta/src/types.ts | 10 + pnpm-lock.yaml | 130 ++++++------ scripts/gen-cordis-catalog.ts | 2 +- scripts/run-gates.ts | 2 +- scripts/type-equiv.manifest.json | 10 +- .../verify-package-readme-model-experience.ts | 4 +- tsconfig.base.json | 12 +- tsconfig.client.json | 4 +- tsconfig.host.json | 2 +- vitest.config.ts | 4 +- 82 files changed, 645 insertions(+), 432 deletions(-) rename packages/{client/remotes => api}/README.i18n.yaml (56%) create mode 100644 packages/api/README.md create mode 100644 packages/api/README.zh.md rename packages/{host/api-gateway => api/gateway}/README.i18n.yaml (56%) rename packages/{host/api-gateway => api/gateway}/README.md (86%) rename packages/{host/api-gateway => api/gateway}/README.zh.md (86%) rename packages/{host/api-gateway => api/gateway}/package.json (92%) rename packages/{host/api-gateway => api/gateway}/src/client/index.ts (96%) rename packages/{host/api-gateway => api/gateway}/src/index.ts (99%) rename packages/{host/api-gateway => api/gateway}/src/invariant.ts (77%) rename packages/{host/api-gateway => api/gateway}/src/types.ts (97%) rename packages/{host/api-gateway => api/gateway}/tests/client.spec.ts (100%) rename packages/{host/api-gateway => api/gateway}/tests/gateway.spec.ts (99%) rename packages/{host/api-gateway => api/gateway}/tsconfig.json (100%) create mode 100644 packages/api/gateway/tsdown.config.ts create mode 100644 packages/api/remotes/README.i18n.yaml create mode 100644 packages/api/remotes/README.md create mode 100644 packages/api/remotes/README.zh.md rename packages/{client => api}/remotes/package.json (64%) create mode 100644 packages/api/remotes/src/agent-lookup.ts rename packages/{client => api}/remotes/src/client/index.ts (64%) create mode 100644 packages/api/remotes/src/index.ts rename packages/{client => api}/remotes/src/invariant.ts (70%) rename packages/{client => api}/remotes/tests/built-lib.e2e.ts (95%) rename packages/{client => api}/remotes/tsconfig.json (63%) create mode 100644 packages/api/remotes/tsdown.config.ts delete mode 100644 packages/client/remotes/README.md delete mode 100644 packages/client/remotes/README.zh.md delete mode 100644 packages/client/remotes/src/index.ts delete mode 100644 packages/client/remotes/tsdown.config.ts delete mode 100644 packages/host/api-gateway/tsdown.config.ts diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml index c76dabca3c..9ba0cf8dc1 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md -2026-08-02-typert-remote-method-calls.md: d91f6f173c1b56efcd21d3136392837e61f54aae -2026-08-02-typert-remote-method-calls.zh.md: 0c548522d1137f0e0002a740d12ca0b796da5e39 +2026-08-02-typert-remote-method-calls.md: c4f3a5b94bf25b4581b9430cfcb4f02f707e0749 +2026-08-02-typert-remote-method-calls.zh.md: e11d8ebe42d44cc9805e942a31f13f7ae847815a diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md index d91f6f173c..c4f3a5b94b 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md @@ -20,7 +20,9 @@ A business Service extends `GatewayService` and declares callable methods with ` The Remote consumer projection contains `.d.ts`, `.d.ts.map`, and `.js` files. The `.d.ts` exposes only methods marked with a Remote decorator and refers to the business package's single public type symbols. The `.d.ts.map` navigates consumer API methods back to their Host business method implementations. The `.js` carries endpoint, parameter, Context, and Zod information for the same contract. At the assembly layer, the Browser Client mounts the required Remote JS contributions onto the Client API Service. The projection and API abstraction remain platform-independent so that a future TUI can reuse them. -`@deepseek-ai/dsh-host-api-gateway`, located at `packages/host/api-gateway`, provides two symmetric faces: its default entry provides Host `ctx.typertGateway`, while its `/client` entry provides consumer-side `ctx.api`. Each side consumes a locally generated `InvocationDescriptor` from the same model; descriptors are not sent over the wire. The Remote data protocol runs over Connection's shared `/api` RPC channel. The business calling interface does not change when Connection migrates from HTTP to WebSocket. +`@deepseek-ai/dsh-api-gateway`, located at `packages/api/gateway`, provides two symmetric faces: its default entry provides Host `ctx.typertGateway`, while its `/client` entry provides consumer-side `ctx.api`. Each side consumes a locally generated `InvocationDescriptor` from the same model; descriptors are not sent over the wire. The Remote data protocol runs over Connection's shared `/api` RPC channel. The business calling interface does not change when Connection migrates from HTTP to WebSocket. + +`@deepseek-ai/dsh-api-remotes`, located at `packages/api/remotes`, is the BFF layer above the Gateway. Its Host entry owns Agent/Session identity resolution and TypeRT lookup configuration; its `/client` entry selects the generated Remote contributions exposed by the application. The Client entry consumes the shared `TypeRTClientApi` contract through Cordis rather than importing the concrete Gateway implementation. ## Components and Cordis services @@ -29,10 +31,10 @@ The Remote consumer projection contains `.d.ts`, `.d.ts.map`, and `.js` files. T | `@deepseek-ai/dsh-type-meta` | Declares only the minimal `ctx.typert` protocol | `GatewayService`, decorators, binding fallback, descriptors, lookup/Context, and the Remote map; no dependency on the compiler, Zod, Connection, or Browser | | TypeRT registry | `ctx.typert` | Separately stores reflection for the current environment, imported Remote contributions, lookup providers, and Context providers | | TypeRT generator/loader | No new business service | Generates three kinds of `lib` artifacts from the Host/Client Programs and registers the current environment's artifacts with `ctx.typert` | -| Host API Gateway's Host face | `ctx.typertGateway` | Associates Host definitions with live Services, decodes parameters, resolves receivers, invokes methods, and encodes results | +| API Gateway's Host face | `ctx.typertGateway` | Associates Host definitions with live Services, decodes parameters, resolves receivers, invokes methods, and encodes results | | Connection | `ctx.connection` | Exclusively owns the HTTP Server/future WebSocket, the shared `/api` route, RPC envelope, rpcId, serialization, trust, error transport, TypeRT interception, and legacy API Proxy fallback | -| Host API Gateway's Client face | `ctx.api` | Mounts Remote contributions, materializes root and scoped APIs, and delegates canonical calls to `ctx.connection.rpc` | -| Client Remotes | No new service | Serves as the only Remote facade for Client business code, selecting and mounting `/remote` contributions while exposing the Gateway Client face and the selected API declarations | +| API Gateway's Client face | `ctx.api` | Mounts Remote contributions, materializes root and scoped APIs, and delegates canonical calls to `ctx.connection.rpc` | +| API Remotes | No new service | Owns Host Agent/Session lookup policy and serves as the only Client business facade, selecting and mounting `/remote` contributions while exposing the selected API declarations | | Agent/Session owning packages | Existing domain services | Provide both static interface merges and runtime lookup/Context providers | | Business packages such as Goal | Existing business Services | Declare only bindings, Remote methods, and canonical DTOs, and export the generated `/remote` subpath | @@ -162,7 +164,7 @@ Every registration returns a disposer owned by the caller's Cordis fiber. Client The lookup registry retains the stable wire declaration after its live resolver unloads. SRC parsing continues to classify the parameter as a lookup, while invocation fails with `lookup-unavailable`; it never reclassifies the incoming ID as an ordinary JSON business object. Re-registering the same key with different parameter, wire, or canonical type symbols fails for the lifetime of that TypeRT Service. -Business-object packages own stable declarations and default resolvers through `register()`; Host composition supplies an effect-scoped asynchronous policy for the same key through `configure()`. Configuration may precede provider registration, but does not by itself make a lookup available without a live provider; unloading the configuration restores the provider's default resolver. The standard Web Host's API Proxy configures the same `agentFor()` for `agent` and `session`: live Agents are reused, ordinary cold sessions are resumed automatically, concurrent resumes are deduplicated by Session ID, and the subagent ownership fence returns the existing `agent-busy`. The `session` resolver returns the resolved Agent's Session, so the two parameter kinds do not create separate resume lifecycles. +Business-object packages own stable declarations and default resolvers through `register()`; Host composition supplies an effect-scoped asynchronous policy for the same key through `configure()`. Configuration may precede provider registration, but does not by itself make a lookup available without a live provider; unloading the configuration restores the provider's default resolver. API Remotes creates the shared `agentFor()` resolver for `agent` and `session`: live Agents are reused, ordinary cold sessions are resumed automatically, concurrent resumes are deduplicated by Session ID, and the subagent ownership fence returns the existing `agent-busy`. The standard Web API Proxy supplies its Agent defaults and scope setup and consumes that resolver for legacy methods. The `session` resolver returns the resolved Agent's Session, so the two parameter kinds do not create separate resume lifecycles. The registry's Host root entry has the complete `TypeRTService` interface merge. The registry implementation shared by Host and Client lives in a separate module without environment declarations. The registry's `/client` entry imports only that shared implementation and does not pass through the Host root entry, so it cannot bring Host Cordis declarations into the Client Program. @@ -295,7 +297,7 @@ TypeRT.local 当前环境自己的反射模型 TypeRT.remotes 已导入的 Remote contribution ``` -`@deepseek-ai/dsh-client-remotes/client` centrally loads the required Remote contributions: +`@deepseek-ai/dsh-api-remotes/client` centrally loads the required Remote contributions: ```text import goalsRemote from '@deepseek-ai/dsh-goal/remote' @@ -305,7 +307,7 @@ ctx.api.mount(goalsRemote) ctx.api.mount(sessionsRemote) ``` -Client business packages depend only on `@deepseek-ai/dsh-client-remotes/client`, not directly on the Host API Gateway or the runtime entry of each business `/remote`. Client Remotes itself depends on the Gateway Client face and re-exports declarations so the selected Remote map reaches business compilation. Adding or removing a complete Client capability changes only this assembly point. +Client business packages depend only on `@deepseek-ai/dsh-api-remotes/client`, not directly on the API Gateway or the runtime entry of each business `/remote`. API Remotes consumes the shared `TypeRTClientApi` contract and Cordis `ctx.api` service, then re-exports declarations so the selected Remote map reaches business compilation. Adding or removing a complete Client capability changes only this assembly point. `ctx.api.mount()` registers a contribution with `TypeRT.remotes`, and its disposer is owned by the Cordis fiber that called the method. Duplicate endpoints, conflicting invocation modes for the same namespace and method, or conflicts between a descriptor and an existing type identity fail immediately. @@ -449,11 +451,11 @@ The Gateway registers only its ownership matcher and RPC handler with Connection - `@deepseek-ai/dsh-type-meta`: lightweight protocols for decorators, bindings, lookup, Remote Context, and descriptors. - TypeRT generator: analyzes Host/Client Programs, generates local faces and Remote consumer projections, and emits canonical symbol/Zod information. - TypeRT runtime: separately stores the current environment's local reflection and imported Remote contributions. -- `@deepseek-ai/dsh-host-api-gateway`: its default entry associates Host definitions with Services, claims Remote endpoints, performs lookup, resolves Context receivers, invokes methods, encodes results, and registers an `/api` interceptor with Connection; its `/client` entry mounts Remote contributions, creates strict API methods, and delegates calls to `ctx.connection.rpc`. The entries share the Remote protocol but do not import each other's Cordis interface merges. -- `@deepseek-ai/dsh-client-remotes`: the only Remote facade depended on by Client business code; directly depends on the Gateway Client face, selects `/remote` contributions, and exposes the merged API types to business packages. +- `@deepseek-ai/dsh-api-gateway`: its default entry associates Host definitions with Services, claims Remote endpoints, performs lookup, resolves Context receivers, invokes methods, encodes results, and registers an `/api` interceptor with Connection; its `/client` entry mounts Remote contributions, creates strict API methods, and delegates calls to `ctx.connection.rpc`. The entries share the Remote protocol but do not import each other's Cordis interface merges. +- `@deepseek-ai/dsh-api-remotes`: the BFF layer; owns the Host Agent/Session resolver, selects Client `/remote` contributions, and exposes the merged API types to business packages through the shared `TypeRTClientApi` contract. - Connection: owns the single HTTP Server/future WebSocket carrier, shared `/api` route and composite FetchHandler, API Proxy fallback, RPC envelope, rpcId, serialization, trust, and error transport. - Business-object packages such as Agent/Session: own lookup, Context providers, canonical ID types, and public type-only entries. -- API Proxy Host composition: configures cold resume, concurrent deduplication, and subagent ownership policy for `agent`/`session` lookups through the existing `agentFor()`. +- API Proxy Host composition: supplies Web Agent defaults and scope setup to API Remotes and consumes the same `agentFor()` for legacy methods. - Business Service packages: declare bindings, Remote methods, and their request/result types, and export the generated `/remote` subpath. ## Shipped scope and deferred work @@ -462,6 +464,8 @@ The shipped vertical path is `@deepseek-ai/dsh-goal/remote → Browser Client AP Connection supplies the shared-channel interceptor and current HTTP carrier mapping. WebSocket migration, the TUI runtime and carrier, TUI Agent Scope wiring, Permission/Approval state machines, Session event streams, call authorization, retries, idempotency, and cross-version protocol compatibility remain outside this decision. +The package topology is `api/remotes → api/gateway → client/connection → host/webserver`. Connection and WebServer retain their existing paths in this change; moving them later to `api/connection` and `api/webserver` changes package placement rather than these service boundaries. The legacy API Proxy likewise remains under `host/apiproxy` as the fallback for methods not yet migrated to Remote. + ## Alternatives considered **Continue using the central API Proxy package.** This would require business methods, Host routes, and Client interfaces to be declared repeatedly in several locations. It would also keep direct calls, stateful interactions, and event streams tied to the same lifecycle, so this alternative is rejected. diff --git a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md index 0c548522d1..e11d8ebe42 100644 --- a/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.zh.md @@ -20,7 +20,9 @@ Host 与 Browser Client 使用独立的 TypeScript Program,因为两边会以 Remote 消费端投影同时包含 `.d.ts`、`.d.ts.map` 和 `.js`。`.d.ts` 只暴露被 Remote decorator 标记的方法,并引用业务包唯一的公共类型符号;`.d.ts.map` 把消费端 API 方法导航回 Host 业务方法实现;`.js` 携带同一契约的 endpoint、参数、Context 和 Zod 信息。Browser Client 在 assembly 层把需要的 Remote JS 贡献集中挂到 Client API Service;该投影和 API 抽象保持平台无关,以便未来 TUI 复用。 -`@deepseek-ai/dsh-host-api-gateway` 在 `packages/host/api-gateway` 内提供对称的两个 face:默认入口提供 Host `ctx.typertGateway`,`/client` 入口提供消费端 `ctx.api`。两边各自在本地消费由同一模型生成的 `InvocationDescriptor`,descriptor 不通过 wire 发送。Remote 数据协议运行在 Connection 共享的 `/api` RPC channel 上;业务调用界面不随 Connection 从 HTTP 迁移到 WebSocket 而改变。 +`@deepseek-ai/dsh-api-gateway` 位于 `packages/api/gateway`,提供对称的两个 face:默认入口提供 Host `ctx.typertGateway`,`/client` 入口提供消费端 `ctx.api`。两边各自在本地消费由同一模型生成的 `InvocationDescriptor`,descriptor 不通过 wire 发送。Remote 数据协议运行在 Connection 共享的 `/api` RPC channel 上;业务调用界面不随 Connection 从 HTTP 迁移到 WebSocket 而改变。 + +`@deepseek-ai/dsh-api-remotes` 位于 `packages/api/remotes`,是 Gateway 上层的 BFF 层。其 Host 入口负责 Agent/Session 身份解析与 TypeRT lookup 配置;`/client` 入口选择应用对外暴露的生成 Remote contribution。Client 入口通过 Cordis 消费共享的 `TypeRTClientApi` 契约,而不导入具体 Gateway 实现。 ## 组件和 Cordis 服务 @@ -29,10 +31,10 @@ Remote 消费端投影同时包含 `.d.ts`、`.d.ts.map` 和 `.js`。`.d.ts` 只 | `@deepseek-ai/dsh-type-meta` | 只声明 `ctx.typert` 的最小协议 | `GatewayService`、decorator、binding 回退、descriptor、lookup/Context 和 Remote map;不依赖 compiler、Zod、Connection 或 Browser | | TypeRT registry | `ctx.typert` | 分开保存当前环境 reflection、导入的 Remote contribution、lookup provider 和 Context provider | | TypeRT generator/loader | 无新增业务服务 | 从 Host/Client Program 生成三类 `lib` 产物,并把当前环境产物注册到 `ctx.typert` | -| Host API Gateway 的 Host face | `ctx.typertGateway` | 关联 Host definition 与活 Service,解码参数、解析 receiver、调用方法和编码结果 | +| API Gateway 的 Host face | `ctx.typertGateway` | 关联 Host definition 与活 Service,解码参数、解析 receiver、调用方法和编码结果 | | Connection | `ctx.connection` | 独占 HTTP Server/未来 WebSocket、共享 `/api` route、RPC envelope、rpcId、序列化、trust、错误传输、TypeRT 拦截和旧 API Proxy 回退 | -| Host API Gateway 的 Client face | `ctx.api` | mount Remote contribution,实体化根 API 和 scoped API,把规范调用交给 `ctx.connection.rpc` | -| Client Remotes | 无新增服务 | 作为 Client 业务的唯一 Remote facade,选择并挂载 `/remote` contribution,同时传递 Gateway Client face 和所选 API 的类型声明 | +| API Gateway 的 Client face | `ctx.api` | mount Remote contribution,实体化根 API 和 scoped API,把规范调用交给 `ctx.connection.rpc` | +| API Remotes | 无新增服务 | 负责 Host Agent/Session lookup 策略,并作为 Client 业务的唯一 facade,选择并挂载 `/remote` contribution,同时暴露所选 API 声明 | | Agent/Session owning 包 | 既有领域服务 | 同时提供静态 interface merge 与运行时 lookup/Context provider | | Goal 等业务包 | 既有业务 Service | 只声明 binding、Remote 方法和唯一 DTO,并导出生成的 `/remote` 子路径 | @@ -162,7 +164,7 @@ ctx.typert.contexts Host Context resolver 与 Client Context binder lookup 注册表会在活 resolver 卸载后保留稳定的 wire 声明。SRC 解析仍会把该参数归类为 lookup,而调用会以 `lookup-unavailable` 失败;系统绝不会把传入的 ID 重新归类为普通 JSON 业务对象。在同一个 TypeRT Service 的生命周期内,以不同参数、wire 或规范类型 symbol 重新注册同一 key 会直接失败。 -业务对象包通过 `register()` 拥有稳定声明和默认 resolver;Host 组合通过 `configure()` 为同一个 key 提供 effect-scoped 异步策略。配置可以先于 provider 注册,但没有活 provider 时不会单独形成可用 lookup;配置卸载后恢复 provider 默认 resolver。标准 Web Host 的 API Proxy 为 `agent` 和 `session` 配置同一套 `agentFor()`:live Agent 直接复用,普通冷会话自动恢复,并发恢复按 Session ID 去重,subagent ownership fence 则返回既有 `agent-busy`。`session` resolver 返回解析所得 Agent 的 Session,因而两种参数不会产生两套恢复生命周期。 +业务对象包通过 `register()` 拥有稳定声明和默认 resolver;Host 组合通过 `configure()` 为同一个 key 提供 effect-scoped 异步策略。配置可以先于 provider 注册,但没有活 provider 时不会单独形成可用 lookup;配置卸载后恢复 provider 默认 resolver。API Remotes 为 `agent` 和 `session` 创建共享的 `agentFor()` resolver:live Agent 直接复用,普通冷会话自动恢复,并发恢复按 Session ID 去重,subagent ownership fence 则返回既有 `agent-busy`。标准 Web API Proxy 提供 Agent 默认值和 scope 设置,并让旧方法使用该 resolver。`session` resolver 返回解析所得 Agent 的 Session,因而两种参数不会产生两套恢复生命周期。 Registry 的 Host 根入口拥有完整 `TypeRTService` interface merge;Host 与 Client 共用的 registry 实现位于无环境声明的独立模块。Registry `/client` 入口只引用该共享实现,不经过 Host 根入口,因此不会把 Host Cordis 声明带入 Client Program。 @@ -295,7 +297,7 @@ TypeRT.local 当前环境自己的反射模型 TypeRT.remotes 已导入的 Remote contribution ``` -`@deepseek-ai/dsh-client-remotes/client` 集中加载需要的 Remote contribution: +`@deepseek-ai/dsh-api-remotes/client` 集中加载需要的 Remote contribution: ```text import goalsRemote from '@deepseek-ai/dsh-goal/remote' @@ -305,7 +307,7 @@ ctx.api.mount(goalsRemote) ctx.api.mount(sessionsRemote) ``` -Client 业务包只引用 `@deepseek-ai/dsh-client-remotes/client`,不直接依赖 Host API Gateway 或各业务 `/remote` 运行时入口。Client Remotes 自己依赖 Gateway Client face,并通过声明 re-export 把所选 Remote map 传给业务编译;新增或移除整套 Client 能力只修改这一处 assembly。 +Client 业务包只引用 `@deepseek-ai/dsh-api-remotes/client`,不直接依赖 API Gateway 或各业务 `/remote` 运行时入口。API Remotes 消费共享的 `TypeRTClientApi` 契约和 Cordis `ctx.api` 服务,再重新导出声明,使所选 Remote map 进入业务编译;新增或移除整套 Client 能力只修改这一处 assembly。 `ctx.api.mount()` 把 contribution 注册到 `TypeRT.remotes`,并由调用该方法的 Cordis fiber 持有 disposer。endpoint 重复、同一 namespace/method 模式冲突或 descriptor 与现有类型身份冲突时直接失败。 @@ -449,11 +451,11 @@ Gateway 只向 Connection 注册 ownership matcher 和 RPC handler,不注册 H - `@deepseek-ai/dsh-type-meta`:轻量 decorator、binding、lookup、Remote Context 和 descriptor 协议。 - TypeRT generator:分析 Host/Client Program,生成本地 face 和 Remote 消费端投影,并生成规范 symbol/Zod 信息。 - TypeRT runtime:分别保存当前环境的 local reflection 与导入的 Remote contribution。 -- `@deepseek-ai/dsh-host-api-gateway`:默认入口关联 Host definition 与 Service,认领 Remote endpoint,执行 lookup、Context receiver 解析、调用和结果编码,并向 Connection 注册 `/api` interceptor;`/client` 入口挂载 Remote contribution,创建严格 API 方法,并把调用交给 `ctx.connection.rpc`。两个入口共享 Remote 协议,但不互相导入各自的 Cordis interface merge。 -- `@deepseek-ai/dsh-client-remotes`:Client 业务唯一依赖的 Remote facade;直接依赖 Gateway Client face,选择 `/remote` contributions,并向业务包传递合并后的 API 类型。 +- `@deepseek-ai/dsh-api-gateway`:默认入口关联 Host definition 与 Service,认领 Remote endpoint,执行 lookup、Context receiver 解析、调用和结果编码,并向 Connection 注册 `/api` interceptor;`/client` 入口挂载 Remote contribution,创建严格 API 方法,并把调用交给 `ctx.connection.rpc`。两个入口共享 Remote 协议,但不互相导入各自的 Cordis interface merge。 +- `@deepseek-ai/dsh-api-remotes`:BFF 层;负责 Host Agent/Session resolver,选择 Client `/remote` contribution,并通过共享的 `TypeRTClientApi` 契约向业务包暴露合并后的 API 类型。 - Connection:拥有唯一 HTTP Server/未来 WebSocket carrier、共享 `/api` route 与复合 FetchHandler、API Proxy 回退、RPC envelope、rpcId、序列化、trust 和错误传输。 - Agent/Session 等业务对象包:拥有 lookup、Context provider、唯一 ID 类型和纯类型公共出口。 -- API Proxy Host 组合:用既有 `agentFor()` 配置 `agent`/`session` lookup 的冷恢复、并发去重和 subagent ownership 策略。 +- API Proxy Host 组合:向 API Remotes 提供 Web Agent 默认值和 scope 设置,并让旧方法使用同一个 `agentFor()`。 - 业务 Service 包:声明 binding、Remote 方法及其 request/result 类型,并导出生成的 `/remote` 子路径。 ## 已交付范围与后续工作 @@ -462,6 +464,8 @@ Gateway 只向 Connection 注册 ownership matcher 和 RPC handler,不注册 H Connection 提供共享 channel interceptor 与当前 HTTP carrier 映射。WebSocket 迁移、TUI runtime 与 carrier、TUI Agent Scope 接线、Permission/Approval 状态机、Session 事件流、调用授权、重试、幂等及跨版本协议兼容均不属于本决策。 +包拓扑为 `api/remotes → api/gateway → client/connection → host/webserver`。Connection 与 WebServer 在本次变更中保留既有路径;后续将它们移到 `api/connection` 和 `api/webserver` 只会改变包位置,不会改变这些服务边界。旧 API Proxy 同样保留在 `host/apiproxy` 下,作为尚未迁移到 Remote 的方法的回退路径。 + ## Alternatives considered **继续使用中央 API Proxy 包。** 该方案要求业务方法、Host 路由和 Client 接口在多个位置重复声明,也会继续把直接调用、带状态交互和事件流绑在同一生命周期中,因此不采用。 diff --git a/AGENTS.md b/AGENTS.md index 0d27b20df0..c265d3cf32 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,6 +12,7 @@ DeepSeek Harness SDK is a plugin-based agent harness on vendored Cordis: **every vendor/ Vendored Cordis source — manifest + sync procedure in vendor/README.md packages/ @deepseek-ai/dsh- workspaces at packages/// core/ product API spine: session, system-prompt, tools, agent, agent-loop + api/ Remote BFF assembly and TypeRT RPC gateway typert/ type graph generator, loader, and runtime registry llm/ LLM seam + DeepSeek adapters (direct-fetch + pi-ai design twin) bash/ bash executor seam + local/pwsh impls + model-facing shell tools diff --git a/apps/cli/composition.md b/apps/cli/composition.md index 0246f6163f..45dc52561a 100644 --- a/apps/cli/composition.md +++ b/apps/cli/composition.md @@ -22,7 +22,7 @@ flowchart LR cfg --> plugin_dsh_base_typert plugin_dsh_base_typert_loader["typert-loader
@deepseek-ai/dsh-typert-loader"] cfg --> plugin_dsh_base_typert_loader - plugin_dsh_base_typert_gateway["typert-gateway
@deepseek-ai/dsh-host-api-gateway"] + plugin_dsh_base_typert_gateway["typert-gateway
@deepseek-ai/dsh-api-gateway"] cfg --> plugin_dsh_base_typert_gateway plugin_dsh_base_session_title["session-title
@deepseek-ai/dsh-session-title"] cfg --> plugin_dsh_base_session_title @@ -167,7 +167,7 @@ flowchart LR | `session` | `@deepseek-ai/dsh-session` | | `typert` | `@deepseek-ai/dsh-typert-registry` | | `typert-loader` | `@deepseek-ai/dsh-typert-loader` | -| `typert-gateway` | `@deepseek-ai/dsh-host-api-gateway` | +| `typert-gateway` | `@deepseek-ai/dsh-api-gateway` | | `session-title` | `@deepseek-ai/dsh-session-title` | | `session-title-llm` | `@deepseek-ai/dsh-session-title-first-message-llm` | | `user-interaction` | `@deepseek-ai/dsh-user-interaction` | diff --git a/apps/web/tests/assembled-boot.ts b/apps/web/tests/assembled-boot.ts index ebb2aa513a..729428e47b 100644 --- a/apps/web/tests/assembled-boot.ts +++ b/apps/web/tests/assembled-boot.ts @@ -18,9 +18,9 @@ import { AppWebEntry } from '@deepseek-ai/dsh-client-web' const PLUGINS: readonly (WebBootEntry & { bundlePath: string })[] = [ { id: '@deepseek-ai/dsh-typert-registry', bundlePath: 'packages/typert/registry/lib/client.js', url: '/plugins/typert-registry.js', rev: 'fx', inject: [], immediately: true }, { id: '@deepseek-ai/dsh-client-connection', bundlePath: 'packages/client/connection/lib/client.js', url: '/plugins/connection.js', rev: 'fx', inject: [], immediately: true }, - { id: '@deepseek-ai/dsh-host-api-gateway', bundlePath: 'packages/host/api-gateway/lib/client.js', url: '/plugins/api-gateway.js', rev: 'fx', inject: ['@deepseek-ai/dsh-typert-registry', '@deepseek-ai/dsh-client-connection'], immediately: true }, - { id: '@deepseek-ai/dsh-client-remotes', bundlePath: 'packages/client/remotes/lib/client.js', url: '/plugins/client-remotes.js', rev: 'fx', inject: ['@deepseek-ai/dsh-host-api-gateway'], immediately: true }, - { id: '@deepseek-ai/dsh-client-runtime', bundlePath: 'packages/client/runtime/lib/client.js', url: '/plugins/runtime.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-connection', '@deepseek-ai/dsh-client-remotes', '@deepseek-ai/dsh-typert-registry'], immediately: true }, + { id: '@deepseek-ai/dsh-api-gateway', bundlePath: 'packages/api/gateway/lib/client.js', url: '/plugins/api-gateway.js', rev: 'fx', inject: ['@deepseek-ai/dsh-typert-registry', '@deepseek-ai/dsh-client-connection'], immediately: true }, + { id: '@deepseek-ai/dsh-api-remotes', bundlePath: 'packages/api/remotes/lib/client.js', url: '/plugins/api-remotes.js', rev: 'fx', inject: ['@deepseek-ai/dsh-api-gateway'], immediately: true }, + { id: '@deepseek-ai/dsh-client-runtime', bundlePath: 'packages/client/runtime/lib/client.js', url: '/plugins/runtime.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-connection', '@deepseek-ai/dsh-api-remotes', '@deepseek-ai/dsh-typert-registry'], immediately: true }, { id: '@deepseek-ai/dsh-client-ui-theme', bundlePath: 'packages/client/ui-theme/lib/client.js', url: '/plugins/ui-theme.js', rev: 'fx', inject: [], immediately: true }, { id: '@deepseek-ai/dsh-client-locale', bundlePath: 'packages/client/locale/lib/client.js', url: '/plugins/locale.js', rev: 'fx', inject: [], immediately: true }, { id: '@deepseek-ai/dsh-client-ui-layout', bundlePath: 'packages/client/ui-layout/lib/client.js', url: '/plugins/ui-layout.js', rev: 'fx', inject: ['@deepseek-ai/dsh-client-runtime'] }, diff --git a/docs/api-gateway.i18n.yaml b/docs/api-gateway.i18n.yaml index 58891890d3..05038eb8b9 100644 --- a/docs/api-gateway.i18n.yaml +++ b/docs/api-gateway.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/api-gateway.md -api-gateway.md: 2e0717fd7b0e5b9ca33d650ffad7ac454046f780 -api-gateway.zh.md: 4d1beebf92cae702dac323cdd974b6220091a214 +api-gateway.md: 090758d58306d5ea806567f0de710a1c1f5ed747 +api-gateway.zh.md: 9d7286b6b86918f3bc1e7a6cdd9bdf04447abc57 diff --git a/docs/api-gateway.md b/docs/api-gateway.md index 2e0717fd7b..090758d583 100644 --- a/docs/api-gateway.md +++ b/docs/api-gateway.md @@ -61,7 +61,7 @@ The Client uses concrete functions on ordinary objects, not a JavaScript Proxy. import type { SessionId } from '@deepseek-ai/dsh-session/types' import type { AgentContext } from '@deepseek-ai/dsh-client-runtime/client' import type { Context } from 'cordis' -import type {} from '@deepseek-ai/dsh-client-remotes/client' +import type {} from '@deepseek-ai/dsh-api-remotes/client' declare const ctx: Context declare const agentCtx: AgentContext @@ -71,9 +71,9 @@ await ctx.api.goals.create(agentId, { objective: 'ship it' }) await agentCtx.goals.create({ objective: 'ship it' }) ``` -Client applications assemble only `@deepseek-ai/dsh-client-remotes`. That package imports the `/remote` subpaths of selected business packages as runtime values, mounts their contributions on `ctx.api`, and re-exports the declaration merges from the same files. Adding a Host Remote package is an explicit choice by the Client composition owner; business components do not need to load the Host API Gateway or the business package's Remote JS separately. +Client applications assemble only `@deepseek-ai/dsh-api-remotes`. That package imports the `/remote` subpaths of selected business packages as runtime values, mounts their contributions on `ctx.api`, and re-exports the declaration merges from the same files. Adding a Host Remote package is an explicit choice by the Client composition owner; business components do not need to load the TypeRT Gateway or the business package's Remote JS separately. -A future TUI can assemble the same React-independent `client-remotes` and `ctx.api` contract, so the Host methods visible to it are likewise limited to the Remote methods selected at generation time. This document does not define or implement the TUI composition. +A future TUI can assemble the same React-independent `api-remotes` and `ctx.api` contract, so the Host methods visible to it are likewise limited to the Remote methods selected at generation time. This document does not define or implement the TUI composition. ## Component responsibilities @@ -82,12 +82,13 @@ A future TUI can assemble the same React-independent `client-remotes` and `ctx.a | Shared | `@deepseek-ai/dsh-type-meta` | Declares decorators, Gateway bindings, merge-extensible protocol maps, invocation descriptors, and provider types; starts no TypeScript analysis and registers no Cordis services | | Build | `@deepseek-ai/dsh-typert-generator` | Strictly analyzes Remote signatures, the type graph, lookups, Contexts, and source locations from the Host `ts.Program`, then generates Host and Host-for-Client artifacts | | Host | `@deepseek-ai/dsh-typert-registry` and Loader | Places generated Host descriptors, schemas, and business-package registrations in `ctx.typert`, and holds lookup and Context providers | -| Host | `@deepseek-ai/dsh-host-api-gateway` | Provides `ctx.typertGateway`, claims Remote endpoints, resolves objects or Contexts, invokes live Cordis services, and validates boundaries | -| Client | `@deepseek-ai/dsh-host-api-gateway/client` | Provides `ctx.api`, mounts generated descriptors as concrete methods, and initiates, validates, and cancels calls through the Connection | -| Client | `@deepseek-ai/dsh-client-remotes/client` | Explicitly selects and mounts the `/remote` contributions allowed by the application and brings the corresponding declaration merges into business code | +| Host | `@deepseek-ai/dsh-api-remotes` | Owns the application Agent/Session identity policy and configures the corresponding TypeRT lookups | +| Host | `@deepseek-ai/dsh-api-gateway` | Provides `ctx.typertGateway`, claims Remote endpoints, resolves objects or Contexts, invokes live Cordis services, and validates boundaries | +| Client | `@deepseek-ai/dsh-api-gateway/client` | Provides `ctx.api`, mounts generated descriptors as concrete methods, and initiates, validates, and cancels calls through the Connection | +| Client | `@deepseek-ai/dsh-api-remotes/client` | Explicitly selects and mounts the `/remote` contributions allowed by the application and brings the corresponding declaration merges into business code | | Both | `@deepseek-ai/dsh-client-connection` | Provides the RPC carrier, request correlation, trust boundary, cancellation, response envelope, and current `/api` HTTP bridge | -The Host API Gateway package owns the Host dispatcher and Client API as peer entries, but the two builds never enter the same `ts.Program`. The Host entry does not import the Client Cordis `Context` merge, and the Client entry does not import the Host Gateway service. +The API Gateway package owns the Host dispatcher and Client API as peer entries, but the two builds never enter the same `ts.Program`. The Host entry does not import the Client Cordis `Context` merge, and the Client entry does not import the Host Gateway service. ## Strict generation pipeline @@ -99,7 +100,7 @@ Each contributing business package writes generated files to its own `lib/` dire |---|---|---| | `typert.host.js` | Host Loader | Runtime reflection for the Host face, strict invocation descriptors, and schema registration values | | `typert.host.d.ts` | Host type system | Generated declarations for the Host face | -| `typert.remote-client.js` | `client-remotes` | A mountable `TypeRTRemoteContribution` containing strict descriptors and runtime codecs | +| `typert.remote-client.js` | `api-remotes` | A mountable `TypeRTRemoteContribution` containing strict descriptors and runtime codecs | | `typert.remote-client.d.ts` | Client type system | Declaration merges for `TypeRTRemoteNamespaceMap` and `TypeRTRemoteContextMap`, plus Client-safe type references | | `typert.remote-client.d.ts.map` | Editor | Maps generated method properties back to Remote method declarations in the Host package | @@ -117,7 +118,7 @@ The Connection performs the unified trust check for `/api` before the HTTP bridg For every call, the Gateway resolves the descriptor and live service from the current registries instead of caching business objects. It requires the fields in `args` to match the descriptor exactly, validates wire values with codecs, resolves objects or receivers through registered lookup or Context providers, invokes the service method targeted by the binding, and validates the return value. A missing provider, unknown identity, binding mismatch, missing or extra argument, schema failure, or missing method fails at the boundary before entering or after leaving business code. -The lookup provider's `register()` supplies both the stable declaration and the default resolver; `configure()` supplies a resolver owned by Host composition that may execute asynchronously and is scoped to an effect lifetime. Configuration may precede provider mounting; without a provider, invocation still fails with `lookup-unavailable`, and unloading the configuration restores the provider's default policy. The standard Web Host's API Proxy configures the same `agentFor()` semantics for `agent` and `session`: it reuses a live Agent, automatically resumes ordinary cold sessions, deduplicates concurrent resumes, and rejects identities owned by subagent routing; the `session` lookup returns that Agent's Session. Resume failures and ownership fences pass through unchanged as existing RPC errors rather than being collapsed into the Gateway's `internal` error. +The lookup provider's `register()` supplies both the stable declaration and the default resolver; `configure()` supplies a resolver owned by Host composition that may execute asynchronously and is scoped to an effect lifetime. Configuration may precede provider mounting; without a provider, invocation still fails with `lookup-unavailable`, and unloading the configuration restores the provider's default policy. API Remotes owns the standard `agentFor()` semantics for `agent` and `session`: it reuses a live Agent, automatically resumes ordinary cold sessions, deduplicates concurrent resumes, and rejects identities owned by subagent routing; the `session` lookup returns that Agent's Session. The Web API Proxy supplies its Agent defaults and scope setup, then consumes the same resolver for legacy methods. Resume failures and ownership fences pass through unchanged as existing RPC errors rather than being collapsed into the Gateway's `internal` error. Unloading a Client contribution removes its descriptors and concrete methods together, aborts its in-flight calls, and makes stale method handles retained by external code reject further calls. A strict endpoint withdrawn on the Host also does not degrade to SRC inference, preventing a hot unload from silently weakening validation. @@ -158,4 +159,6 @@ The running Client watcher consumes these generated files when it rebundles; wit Remote handles only unary method calls with one request and one result. Session event streams, pagination, incremental reduce, projection, and entity substreams require a separate data protocol and registration model; even when they reuse the Connection, they must not masquerade as Remote methods or enter invocation descriptors. +The API layers are organized as `remotes → gateway → connection → webserver`. The BFF and TypeRT RPC layers live under `packages/api`; Connection and WebServer remain at `packages/client/connection` and `packages/host/webserver`, with service contracts that permit a later package-only move to `packages/api`. The legacy API Proxy remains at `packages/host/apiproxy` as the fallback for endpoints not yet migrated to Remote. + Lookup policy is currently configured per key, so all `agent` or `session` parameters share the cold-resume behavior. If a Remote endpoint must accept live objects only, an explicit per-parameter or per-endpoint policy must be added later; the business method must not guess whether the object came from restoration. diff --git a/docs/api-gateway.zh.md b/docs/api-gateway.zh.md index 4d1beebf92..9d7286b6b8 100644 --- a/docs/api-gateway.zh.md +++ b/docs/api-gateway.zh.md @@ -61,7 +61,7 @@ Client 使用普通对象上的具体函数,不使用 JavaScript Proxy。直 import type { SessionId } from '@deepseek-ai/dsh-session/types' import type { AgentContext } from '@deepseek-ai/dsh-client-runtime/client' import type { Context } from 'cordis' -import type {} from '@deepseek-ai/dsh-client-remotes/client' +import type {} from '@deepseek-ai/dsh-api-remotes/client' declare const ctx: Context declare const agentCtx: AgentContext @@ -71,9 +71,9 @@ await ctx.api.goals.create(agentId, { objective: 'ship it' }) await agentCtx.goals.create({ objective: 'ship it' }) ``` -Client 应用只装配 `@deepseek-ai/dsh-client-remotes`。该包以运行时值导入被选业务包的 `/remote` 子路径,并向 `ctx.api` 挂载贡献,同时重新导出相同文件中的声明合并。增加一个 Host Remote 包是 Client 组合所有者的显式选择;业务组件不需要分别加载 Host API Gateway 或业务包的 Remote JS。 +Client 应用只装配 `@deepseek-ai/dsh-api-remotes`。该包以运行时值导入被选业务包的 `/remote` 子路径,并向 `ctx.api` 挂载贡献,同时重新导出相同文件中的声明合并。增加一个 Host Remote 包是 Client 组合所有者的显式选择;业务组件不需要分别加载 TypeRT Gateway 或业务包的 Remote JS。 -未来的 TUI 可以装配同一个不依赖 React 的 `client-remotes` 与 `ctx.api` 契约,因此它能看到的 Host 方法同样只限于生成时选择的 Remote 方法。本文不定义或实现 TUI 组合。 +未来的 TUI 可以装配同一个不依赖 React 的 `api-remotes` 与 `ctx.api` 契约,因此它能看到的 Host 方法同样只限于生成时选择的 Remote 方法。本文不定义或实现 TUI 组合。 ## 组件职责 @@ -82,12 +82,13 @@ Client 应用只装配 `@deepseek-ai/dsh-client-remotes`。该包以运行时值 | 共享 | `@deepseek-ai/dsh-type-meta` | 声明 decorator、Gateway binding、可合并协议映射、调用描述符及提供方类型;不启动 TypeScript 分析,也不注册 Cordis 服务 | | 构建 | `@deepseek-ai/dsh-typert-generator` | 从 Host `ts.Program` 严格分析 Remote 签名、类型图、lookup、Context 与源码位置,并生成 Host 和 Host-for-Client 产物 | | Host | `@deepseek-ai/dsh-typert-registry` 与 Loader | 把生成的 Host 描述符、schema 及业务包注册项放入 `ctx.typert`,并持有 lookup 与 Context 提供方 | -| Host | `@deepseek-ai/dsh-host-api-gateway` | 提供 `ctx.typertGateway`,认领 Remote endpoint,解析对象或 Context,调用实时 Cordis Service 并校验边界 | -| Client | `@deepseek-ai/dsh-host-api-gateway/client` | 提供 `ctx.api`,把生成的描述符挂成具体方法,并通过 Connection 发起、校验和取消调用 | -| Client | `@deepseek-ai/dsh-client-remotes/client` | 显式选择并挂载本应用允许使用的 `/remote` 贡献,向业务代码带入对应的声明合并 | +| Host | `@deepseek-ai/dsh-api-remotes` | 负责应用的 Agent/Session 身份策略,并配置对应的 TypeRT lookup | +| Host | `@deepseek-ai/dsh-api-gateway` | 提供 `ctx.typertGateway`,认领 Remote endpoint,解析对象或 Context,调用实时 Cordis Service 并校验边界 | +| Client | `@deepseek-ai/dsh-api-gateway/client` | 提供 `ctx.api`,把生成的描述符挂成具体方法,并通过 Connection 发起、校验和取消调用 | +| Client | `@deepseek-ai/dsh-api-remotes/client` | 显式选择并挂载本应用允许使用的 `/remote` 贡献,向业务代码带入对应的声明合并 | | 双侧 | `@deepseek-ai/dsh-client-connection` | 提供 RPC carrier、请求关联、信任边界、取消、响应 envelope 与当前 `/api` HTTP bridge | -Host API Gateway 包同时拥有 Host dispatcher 与 Client API 两个对等入口,但两侧构建不会进入同一个 `ts.Program`。Host 入口不导入 Client 的 Cordis `Context` 合并,Client 入口也不导入 Host Gateway 服务。 +API Gateway 包同时拥有 Host dispatcher 与 Client API 两个对等入口,但两侧构建不会进入同一个 `ts.Program`。Host 入口不导入 Client 的 Cordis `Context` 合并,Client 入口也不导入 Host Gateway 服务。 ## 严格生成链路 @@ -99,7 +100,7 @@ Host API Gateway 包同时拥有 Host dispatcher 与 Client API 两个对等入 |---|---|---| | `typert.host.js` | Host Loader | Host face 的运行时反射、严格调用描述符和 schema 注册值 | | `typert.host.d.ts` | Host 类型系统 | Host face 的生成声明 | -| `typert.remote-client.js` | `client-remotes` | 可挂载的 `TypeRTRemoteContribution`,包含严格描述符与运行时 codec | +| `typert.remote-client.js` | `api-remotes` | 可挂载的 `TypeRTRemoteContribution`,包含严格描述符与运行时 codec | | `typert.remote-client.d.ts` | Client 类型系统 | `TypeRTRemoteNamespaceMap` 与 `TypeRTRemoteContextMap` 的声明合并及 Client-safe 类型引用 | | `typert.remote-client.d.ts.map` | 编辑器 | 将生成的方法属性映射回 Host 包中的 Remote 方法声明 | @@ -117,7 +118,7 @@ Connection 在 HTTP bridge 之前执行 `/api` 的统一信任检查,再在共 Gateway 每次调用都从当前注册表解析描述符和实时 Service,不缓存业务对象。它要求 `args` 的字段集合与描述符完全一致,先用 codec 校验 wire 值,再通过注册的 lookup 或 Context provider 解析对象或接收者,最后调用 binding 指向的 Service 方法并校验返回值。缺少 provider、identity 未命中、binding 不一致、参数多缺、schema 失败和方法不存在都在进入或离开业务边界时失败。 -lookup provider 的 `register()` 同时提供稳定声明和默认 resolver;`configure()` 提供由 Host 组合拥有、可异步执行且受 effect 生命周期约束的 resolver。配置可以先于 provider 挂载;没有 provider 时调用仍以 `lookup-unavailable` 失败,配置卸载后则恢复 provider 默认策略。标准 Web Host 的 API Proxy 为 `agent` 与 `session` 配置同一套 `agentFor()` 语义:复用 live Agent,自动恢复普通冷会话,对并发恢复去重,并拒绝由 subagent routing 拥有的 identity;`session` lookup 返回该 Agent 的 Session。恢复失败和 ownership fence 通过既有 RPC error 原样返回,不折叠为 Gateway 的 `internal` 错误。 +lookup provider 的 `register()` 同时提供稳定声明和默认 resolver;`configure()` 提供由 Host 组合拥有、可异步执行且受 effect 生命周期约束的 resolver。配置可以先于 provider 挂载;没有 provider 时调用仍以 `lookup-unavailable` 失败,配置卸载后则恢复 provider 默认策略。API Remotes 负责 `agent` 与 `session` 的标准 `agentFor()` 语义:复用 live Agent,自动恢复普通冷会话,对并发恢复去重,并拒绝由 subagent routing 拥有的 identity;`session` lookup 返回该 Agent 的 Session。Web API Proxy 提供 Agent 默认值与 scope 设置,再让旧方法使用同一个 resolver。恢复失败和 ownership fence 通过既有 RPC error 原样返回,不折叠为 Gateway 的 `internal` 错误。 Client 卸载一个贡献时会一起移除描述符和具体方法,中止其进行中的调用,并使外部仍持有的旧方法句柄拒绝继续调用。Host 上已经注册过的严格 endpoint 被撤回后也不会降级到 SRC 推断,以免热卸载悄然降低校验强度。 @@ -158,4 +159,6 @@ pnpm run build:lib:contracts Remote 只处理有单个请求与单个结果的一元方法调用。Session event stream、分页、增量 reduce、projection 和实体子流需要独立的数据协议与注册模型;即使它们复用 Connection,也不应伪装成 Remote 方法或放入调用描述符。 +API 各层按 `remotes → gateway → connection → webserver` 组织。BFF 与 TypeRT RPC 层位于 `packages/api`;Connection 与 WebServer 仍位于 `packages/client/connection` 和 `packages/host/webserver`,其服务契约允许未来只移动包,将它们放到 `packages/api`。旧 API Proxy 仍位于 `packages/host/apiproxy`,作为尚未迁移到 Remote 的 endpoint 的回退路径。 + 当前 lookup 策略按 key 配置,因此所有 `agent` 或 `session` 参数共享冷恢复行为。某个 Remote endpoint 若必须只接受 live 对象,需要后续增加显式的逐参数或逐 endpoint 策略,不能通过业务方法内部猜测恢复来源。 diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 774bc296b1..0164acefcf 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/architecture.md -architecture.md: db5991d98dfbc6b04992d62d5a465c375c9a78b8 -architecture.zh.md: 2eb8c3834a6ffc3283c8aa669be481b534bb5914 +architecture.md: 35a73d4a307f5f48cc41cc496742a2ac210e8877 +architecture.zh.md: 185958221a477bb690e3ab5c91c33ba892ab2d73 diff --git a/docs/architecture.md b/docs/architecture.md index db5991d98d..35a73d4a30 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -48,7 +48,7 @@ Harnesses are [Cordis](cordis-primer.md) contexts; packages contribute services, | `ctx.credentials` | [`credentials/`](../packages/credentials/README.md) | named secret references resolved per operation, never inlined in configuration | | `ctx.directoryPicker` | [`host/directory-picker`](../packages/host/directory-picker/README.md) | GUI-host directory picking (`native`/`browse` interactions) | | `ctx.typert` | [`typert/registry`](../packages/typert/registry/README.md) | runtime registry for generated package reflection and live Zod schemas | -| `ctx.typertGateway` | [`host/api-gateway`](../packages/host/api-gateway/README.md) | dispatches TypeRT Remote unary calls through the [API Gateway](api-gateway.md) | +| `ctx.typertGateway` | [`api/gateway`](../packages/api/gateway/README.md) | dispatches TypeRT Remote unary calls through the [API Gateway](api-gateway.md) | | `ctx.invariants` | [`support/invariants`](../packages/support/invariants/README.md) | package-name-selected registry of package-owned runtime checks | ## Event diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index 2eb8c3834a..185958221a 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -48,7 +48,7 @@ | `ctx.credentials` | [`credentials/`](../packages/credentials/README.md) | 具名密钥引用,按操作解析,绝不内联进配置 | | `ctx.directoryPicker` | [`host/directory-picker`](../packages/host/directory-picker/README.md) | GUI 宿主目录选取(`native`/`browse` 交互) | | `ctx.typert` | [`typert/registry`](../packages/typert/registry/README.md) | 生成的包反射和实时 Zod schema 的运行时注册表 | -| `ctx.typertGateway` | [`host/api-gateway`](../packages/host/api-gateway/README.md) | 通过 [API Gateway](api-gateway.md) 分发 TypeRT Remote 一元调用 | +| `ctx.typertGateway` | [`api/gateway`](../packages/api/gateway/README.md) | 通过 [API Gateway](api-gateway.md) 分发 TypeRT Remote 一元调用 | | `ctx.invariants` | [`support/invariants`](../packages/support/invariants/README.md) | 按包名筛选包自有运行时检查的注册表 | ## 事件 diff --git a/docs/capability-seams.md b/docs/capability-seams.md index 18839bf3c2..2de89ef94b 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -367,8 +367,8 @@ flowchart LR | `ctx.toolResultPrune` | `core` | [`compact-tool-result-prune`](../packages/compact/compact-tool-result-prune) | - | [`compact-basic`](../packages/compact/compact-basic) | - | Rewrites oversized current tool results through replayable single-node surface replacements before summary compaction. | | `ctx.sessions` | `core` | [`session`](../packages/core/session) | - | [`agent-loop`](../packages/core/agent-loop), [`agent`](../packages/core/agent), [`cli-demo`](../packages/examples/cli-demo), [`session-persistence`](../packages/session-persistence/session-persistence), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`invariants`](../packages/support/invariants) | - | Owns append-only Session instances and emits the durable session event feed. | | `ctx.invariants` | `core` | [`invariants`](../packages/support/invariants) | - | [`session`](../packages/core/session), [`agent`](../packages/core/agent), [`scope`](../packages/core/scope), [`agent-loop`](../packages/core/agent-loop) | - | Companion subpaths register owner-local checks; the service owns selection, uniqueness, child fibers, and package-attributed failures. | -| `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader), `api-gateway` | - | Plugins register live zod contributions directly or through dsh-typert-loader; the API gateway consumes invocation descriptors and providers, while other runtime consumers query schemas and reflection metadata at their own edges. | -| `ctx.typertGateway` | `core` | `api-gateway` | - | - | - | Associates generated Remote descriptors with live Cordis services, resolves registered identities, and exposes unary calls through the shared Connection RPC carrier. | +| `ctx.typert` | `core` | [`typert-registry`](../packages/typert/registry) | - | [`typert-loader`](../packages/typert/loader), [`api-gateway`](../packages/api/gateway) | - | Plugins register live zod contributions directly or through dsh-typert-loader; the API gateway consumes invocation descriptors and providers, while other runtime consumers query schemas and reflection metadata at their own edges. | +| `ctx.typertGateway` | `core` | [`api-gateway`](../packages/api/gateway) | - | - | - | Associates generated Remote descriptors with live Cordis services, resolves registered identities, and exposes unary calls through the shared Connection RPC carrier. | | `ctx.sessionPersistence` | `seam` | [`session-persistence`](../packages/session-persistence/session-persistence) | [`session-persistence-jsonl`](../packages/session-persistence/session-persistence-jsonl), [`session-persistence-sqlite`](../packages/session-persistence/session-persistence-sqlite) | [`agent-loop`](../packages/core/agent-loop), [`tool-bash`](../packages/bash/tool-bash), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`session-query`](../packages/session-query/session-query), [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | - | Backends persist the same SessionEvent vocabulary; apps choose a backend at composition time. | | `ctx.settings` | `seam` | [`settings`](../packages/settings/settings) | [`settings-local`](../packages/settings/settings-local) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `apiproxy` | - | Plugins register namespace schemas and resolve layered values; providers store the raw document. The LLM adapters register their entry config as the composition base under the user section; the web gateway serves redacted layered descriptors and writes the user layer. | | `ctx.credentials` | `seam` | [`credentials`](../packages/credentials/credentials) | [`credentials-local`](../packages/credentials/credentials-local) | [`llm-deepseek`](../packages/llm/llm-deepseek), [`llm-pi-ai`](../packages/llm/llm-pi-ai), `apiproxy` | - | Configuration carries references to secrets; providers own the values. Consumers resolve per operation, so a rotated credential reaches the very next request; the web gateway exposes value-free views and write-only storage. | diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 77baad512d..21f38d18e2 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -2520,9 +2520,10 @@ Source: [`packages/context/workspace-context/src/config.ts:18`](../packages/cont These load from a `cordis.yml` entry with no `config:` block; they declare no config surface. - `@deepseek-ai/dsh-agent` ([`packages/core/agent/src/index.ts`](../packages/core/agent/src/index.ts)) +- `@deepseek-ai/dsh-api-gateway` — requires `typert` ([`packages/api/gateway/src/index.ts`](../packages/api/gateway/src/index.ts)) +- `@deepseek-ai/dsh-api-remotes` ([`packages/api/remotes/src/index.ts`](../packages/api/remotes/src/index.ts)) - `@deepseek-ai/dsh-client-locale` ([`packages/client/locale/src/index.ts`](../packages/client/locale/src/index.ts)) - `@deepseek-ai/dsh-client-modules` — requires `httpServer` · `loader` ([`packages/client/modules/src/index.ts`](../packages/client/modules/src/index.ts)) -- `@deepseek-ai/dsh-client-remotes` ([`packages/client/remotes/src/index.ts`](../packages/client/remotes/src/index.ts)) - `@deepseek-ai/dsh-client-runtime` ([`packages/client/runtime/src/index.ts`](../packages/client/runtime/src/index.ts)) - `@deepseek-ai/dsh-client-ui-command` ([`packages/client/ui-command/src/index.ts`](../packages/client/ui-command/src/index.ts)) - `@deepseek-ai/dsh-client-ui-conversation` ([`packages/client/ui-conversation/src/index.ts`](../packages/client/ui-conversation/src/index.ts)) @@ -2549,7 +2550,6 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co - `@deepseek-ai/dsh-commands` ([`packages/ui/commands/src/index.ts`](../packages/ui/commands/src/index.ts)) - `@deepseek-ai/dsh-fs-policy` ([`packages/fs/fs-policy/src/index.ts`](../packages/fs/fs-policy/src/index.ts)) - `@deepseek-ai/dsh-goal-session` — requires `agents` · `goals` · `sessions` ([`packages/goal/goal-session/src/index.ts`](../packages/goal/goal-session/src/index.ts)) -- `@deepseek-ai/dsh-host-api-gateway` — requires `typert` ([`packages/host/api-gateway/src/index.ts`](../packages/host/api-gateway/src/index.ts)) - `@deepseek-ai/dsh-host-directory-picker-auto` — requires `httpServer` · `loader` ([`packages/host/directory-picker-auto/src/index.ts`](../packages/host/directory-picker-auto/src/index.ts)) - `@deepseek-ai/dsh-host-directory-picker-native` ([`packages/host/directory-picker-native/src/index.ts`](../packages/host/directory-picker-native/src/index.ts)) - `@deepseek-ai/dsh-llm` ([`packages/llm/llm/src/index.ts`](../packages/llm/llm/src/index.ts)) diff --git a/docs/cordis-catalog/services.md b/docs/cordis-catalog/services.md index 8acd669131..cf763dca98 100644 --- a/docs/cordis-catalog/services.md +++ b/docs/cordis-catalog/services.md @@ -2609,7 +2609,7 @@ Resolve strict generated definitions or conservative SRC markers against current async invoke(request: InvokeRemoteRequest): Promise ``` -Source: [`packages/host/api-gateway/src/index.ts:78`](../../packages/host/api-gateway/src/index.ts) +Source: [`packages/api/gateway/src/index.ts:78`](../../packages/api/gateway/src/index.ts) ## `ctx.userInteraction` — `UserInteractionService` diff --git a/docs/core-data-structures/typert.i18n.yaml b/docs/core-data-structures/typert.i18n.yaml index 5b0b70de54..a6e1eb5415 100644 --- a/docs/core-data-structures/typert.i18n.yaml +++ b/docs/core-data-structures/typert.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/core-data-structures/typert.md -typert.md: 1ff0fe80e483d481f686336c86038cdd169ecdbc -typert.zh.md: 3cc0aa26406e01db5a6c05074210fc9d40b8ec00 +typert.md: a61ed8587833e03fd5c1246311e62a6ffaeb3bd0 +typert.zh.md: 18c24018f4abd644cf35185c2bd06b6980195481 diff --git a/docs/core-data-structures/typert.md b/docs/core-data-structures/typert.md index 1ff0fe80e4..a61ed85878 100644 --- a/docs/core-data-structures/typert.md +++ b/docs/core-data-structures/typert.md @@ -2,7 +2,7 @@ English | [中文](typert.zh.md) -Types shared by generated Remote artifacts, the Host Gateway, and consumer API assemblies. The [TypeRT Gateway Agent Note](../../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) owns the architecture and transport decisions; this page records the literal public contracts from [`dsh-type-meta`](../../packages/typert/type-meta/src/types.ts) and [`dsh-host-api-gateway`](../../packages/host/api-gateway/src/types.ts). +Types shared by generated Remote artifacts, the Host Gateway, and consumer API assemblies. The [TypeRT Gateway Agent Note](../../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) owns the architecture and transport decisions; this page records the literal public contracts from [`dsh-type-meta`](../../packages/typert/type-meta/src/types.ts) and [`dsh-api-gateway`](../../packages/api/gateway/src/types.ts). ## Lookup and Context declarations @@ -126,7 +126,7 @@ interface TypeRTService { } ``` -Generated consumer declarations merge direct namespaces into the map inherited by `ClientApi`. +Generated consumer declarations merge direct namespaces into the map inherited by `TypeRTClientApi`. ```ts type-equiv /** Merge-extensible direct namespace surface generated for Client API services. */ @@ -191,8 +191,8 @@ interface TypertGateway { `ctx.api` exposes only namespaces contributed by imported `/remote` artifacts. Mounting installs the generated descriptors and concrete root/scoped methods as one fiber-owned operation; no JavaScript Proxy or Host Service type enters the consumer. ```ts type-equiv -/** Typed API service augmented by generated direct Remote namespaces. */ -interface ClientApi extends TypeRTRemoteNamespaceMap { +/** Client API capability implemented by the Gateway and consumed by Remote assemblies. */ +interface TypeRTClientApi extends TypeRTRemoteNamespaceMap { /** * Mount one generated Host-for-Client contribution in the caller's fiber. * @param contribution - explicitly selected Remote package artifact. diff --git a/docs/core-data-structures/typert.zh.md b/docs/core-data-structures/typert.zh.md index 3cc0aa2640..18c24018f4 100644 --- a/docs/core-data-structures/typert.zh.md +++ b/docs/core-data-structures/typert.zh.md @@ -2,7 +2,7 @@ [English](typert.md) | 中文 -以下类型由生成的 Remote 产物、Host Gateway 与消费方 API assembly 共用。[TypeRT Gateway Agent Note](../../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) 负责架构与传输决策;本页记录 [`dsh-type-meta`](../../packages/typert/type-meta/src/types.ts) 和 [`dsh-host-api-gateway`](../../packages/host/api-gateway/src/types.ts) 中公共契约的字面定义。 +以下类型由生成的 Remote 产物、Host Gateway 与消费方 API assembly 共用。[TypeRT Gateway Agent Note](../../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md) 负责架构与传输决策;本页记录 [`dsh-type-meta`](../../packages/typert/type-meta/src/types.ts) 和 [`dsh-api-gateway`](../../packages/api/gateway/src/types.ts) 中公共契约的字面定义。 ## Lookup 与 Context 声明 @@ -126,7 +126,7 @@ interface TypeRTService { } ``` -生成的消费方声明会把 direct namespace 合并到 `ClientApi` 继承的 map 中。 +生成的消费方声明会把 direct namespace 合并到 `TypeRTClientApi` 继承的 map 中。 ```ts type-equiv /** Merge-extensible direct namespace surface generated for Client API services. */ @@ -191,8 +191,8 @@ interface TypertGateway { `ctx.api` 只暴露由已导入 `/remote` 产物贡献的 namespace。挂载会把生成的 descriptor 与具体的 root/scoped 方法作为一项由 fiber 持有的操作统一注册;JavaScript Proxy 与 Host 服务类型都不会进入消费方。 ```ts type-equiv -/** Typed API service augmented by generated direct Remote namespaces. */ -interface ClientApi extends TypeRTRemoteNamespaceMap { +/** Client API capability implemented by the Gateway and consumed by Remote assemblies. */ +interface TypeRTClientApi extends TypeRTRemoteNamespaceMap { /** * Mount one generated Host-for-Client contribution in the caller's fiber. * @param contribution - explicitly selected Remote package artifact. diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index 2ea336b1f0..b0809af72e 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/development.md -development.md: d480f548dd24ea81d132e4b4c0cc364ce1b0cd53 -development.zh.md: 08ef7fd2d3da7db83eb3ca4dff9f9c85f6d7cb5e +development.md: f832956c4c7cbde96613a69db6c636a2246786a7 +development.zh.md: 3ae70e7135ad5faee0e37d99f55cdb41373aab2c diff --git a/docs/development.md b/docs/development.md index d480f548dd..f832956c4c 100644 --- a/docs/development.md +++ b/docs/development.md @@ -62,7 +62,7 @@ Host and client stay two aggregate programs because both sides declaration-merge Static analysis and tests resolve workspace imports through the base `paths` map to `src` and must pass on a clean tree; gates that consume built `lib/` output declare that dependency explicitly. Decision record: [solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md); the tsc-first emit pipeline is the [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md). -Business services declare callable methods on the Host with `@Remote` or `@RemoteContext`; the Host build generates Host-for-Client types and runtime contributions, and the Client's `client-remotes` composition loads those contributions and mounts concrete methods on `ctx.api` or the corresponding scoped Context. See [API Gateway](api-gateway.md) for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order. +Business services declare callable methods on the Host with `@Remote` or `@RemoteContext`; the Host build generates Host-for-Client types and runtime contributions, and the Client's `api-remotes` composition loads those contributions and mounts concrete methods on `ctx.api` or the corresponding scoped Context. See [API Gateway](api-gateway.md) for the generated artifacts on both sides, their assembly relationships, the SRC development fallback, and the Web build order. If a relevant local check consumes built package output, build once first: diff --git a/docs/development.zh.md b/docs/development.zh.md index 08ef7fd2d3..3ae70e7135 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -62,7 +62,7 @@ host 与 client 保持两个聚合 program,是因为两侧在相同键下以 静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。决策记录:[solution-root note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md);tsc-first 发射管线见 [ts-build-config note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md)。 -业务 Service 在 Host 使用 `@Remote` 或 `@RemoteContext` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `client-remotes` 组合加载这些贡献并将具体方法挂载到 `ctx.api` 或对应的作用域 Context。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。 +业务 Service 在 Host 使用 `@Remote` 或 `@RemoteContext` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `api-remotes` 组合加载这些贡献并将具体方法挂载到 `ctx.api` 或对应的作用域 Context。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。 如果相关的本地检查需要使用构建后的包产物,请先构建一次: diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 34f4d37ffd..92bf908613 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -66,7 +66,7 @@ This matrix shows which packages dispatch each harness-owned event and which pac | `credentials/changed` | `runtime` (`emit`) | `ui-models` | | `internal/dispatch` | - | [`commands`](../packages/ui/commands), [`compact`](../packages/compact/compact), [`fs`](../packages/fs/fs), [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hook-protocol`](../packages/hooks/hook-protocol), [`llm-retry`](../packages/llm/llm-retry), [`permission`](../packages/ui/permission), [`plan-mode`](../packages/plan/plan-mode), [`pty-local`](../packages/pty/pty-local), `runtime`, [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-title`](../packages/session-title/session-title), [`subagent`](../packages/subagent/subagent), [`time-context`](../packages/context/time-context), [`tool-todo`](../packages/todo/tool-todo), [`tools`](../packages/core/tools), [`user-approval`](../packages/ui/user-approval), [`workflow`](../packages/workflow/workflow) | | `internal/plugin` | - | `hmr`, `loader`, `modules`, `webserver` | -| `internal/service` | - | `api-gateway` | +| `internal/service` | - | `gateway` | | `internal/status` | - | [`agent`](../packages/core/agent) | | `locale/change` | `locale` (`emit`) | `locale` | | `models/changed` | `runtime` (`emit`) | `ui-models` | diff --git a/docs/module-graph.md b/docs/module-graph.md index ac46e968b3..43923e0865 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -146,6 +146,10 @@ flowchart TD pkg_user_approval["user-approval"] pkg_user_interaction["user-interaction"] end + subgraph group_api["packages/api"] + pkg_api_gateway["api-gateway"] + pkg_api_remotes["api-remotes"] + end subgraph group_bundle["packages/bundle"] pkg_base["base"] pkg_headless["headless"] @@ -156,7 +160,6 @@ flowchart TD pkg_client_hmr["client-hmr"] pkg_client_locale["client-locale"] pkg_client_modules["client-modules"] - pkg_client_remotes["client-remotes"] pkg_client_runtime["client-runtime"] pkg_client_schema_form["client-schema-form"] pkg_client_test_runtime["client-test-runtime"] @@ -212,7 +215,6 @@ flowchart TD end subgraph group_host["packages/host"] pkg_frontend_static["frontend-static"] - pkg_host_api_gateway["host-api-gateway"] pkg_host_apiproxy["host-apiproxy"] pkg_host_directory_picker["host-directory-picker"] pkg_host_directory_picker_auto["host-directory-picker-auto"] @@ -367,13 +369,13 @@ flowchart TD pkg_system_prompt --> pkg_scope pkg_web --> pkg_invariants pkg_web --> pkg_llm + pkg_api_gateway --> pkg_client_connection + pkg_api_gateway --> pkg_invariants + pkg_api_gateway --> pkg_typert_registry pkg_credentials_local --> pkg_atomic_write pkg_credentials_local --> pkg_credentials pkg_credentials_local --> pkg_invariants pkg_credentials_local --> pkg_paths - pkg_host_api_gateway --> pkg_client_connection - pkg_host_api_gateway --> pkg_invariants - pkg_host_api_gateway --> pkg_typert_registry pkg_lsp --> pkg_brand pkg_lsp --> pkg_invariants pkg_lsp --> pkg_llm @@ -614,6 +616,7 @@ flowchart TD pkg_permission --> pkg_session_projection pkg_permission --> pkg_settings pkg_permission --> pkg_user_approval +<<<<<<< HEAD <<<<<<< HEAD pkg_client_ui_conversation --> pkg_client_locale pkg_client_ui_conversation --> pkg_client_runtime @@ -630,6 +633,14 @@ flowchart TD pkg_client_remotes --> pkg_host_api_gateway pkg_client_remotes --> pkg_invariants >>>>>>> d6e02ebbcc (feat(goal): add TypeRT gateway example) +======= + pkg_api_remotes --> pkg_agent + pkg_api_remotes --> pkg_goal + pkg_api_remotes --> pkg_invariants + pkg_api_remotes --> pkg_session + pkg_api_remotes --> pkg_session_persistence + pkg_api_remotes --> pkg_typert_registry +>>>>>>> c671d9e48c (refactor(api): colocate gateway and remote assembly) pkg_pty_local --> pkg_agent pkg_pty_local --> pkg_invariants pkg_pty_local --> pkg_pty @@ -783,6 +794,7 @@ flowchart TD pkg_tool_ask_user --> pkg_invariants pkg_tool_ask_user --> pkg_tools pkg_tool_ask_user --> pkg_user_interaction +<<<<<<< HEAD <<<<<<< HEAD pkg_client_ui_command --> pkg_client_connection pkg_client_ui_command --> pkg_client_locale @@ -815,6 +827,9 @@ flowchart TD pkg_client_ui_skill --> pkg_invariants ======= pkg_client_runtime --> pkg_client_remotes +======= + pkg_client_runtime --> pkg_api_remotes +>>>>>>> c671d9e48c (refactor(api): colocate gateway and remote assembly) pkg_client_runtime --> pkg_invariants pkg_client_runtime --> pkg_type_meta pkg_client_runtime --> pkg_typert_registry @@ -1132,8 +1147,8 @@ flowchart TD pkg_client_ui_command --> pkg_client_ui_slash pkg_client_ui_command --> pkg_client_ui_slots pkg_client_ui_command --> pkg_invariants + pkg_client_ui_goal --> pkg_api_remotes pkg_client_ui_goal --> pkg_client_locale - pkg_client_ui_goal --> pkg_client_remotes pkg_client_ui_goal --> pkg_client_runtime pkg_client_ui_goal --> pkg_client_ui_conversation pkg_client_ui_goal --> pkg_client_ui_primitives @@ -1231,8 +1246,8 @@ flowchart TD | [`session`](../packages/core/session) | `core` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`type-meta`](../packages/typert/type-meta) | | [`system-prompt`](../packages/core/system-prompt) | `core` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope) | | [`web`](../packages/web/web) | `web` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) | +| [`api-gateway`](../packages/api/gateway) | `api` | [`client-connection`](../packages/client/connection), [`invariants`](../packages/support/invariants), [`typert-registry`](../packages/typert/registry) | | [`credentials-local`](../packages/credentials/credentials-local) | `credentials` | [`atomic-write`](../packages/util/atomic-write), [`credentials`](../packages/credentials/credentials), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths) | -| [`host-api-gateway`](../packages/host/api-gateway) | `host` | [`client-connection`](../packages/client/connection), [`invariants`](../packages/support/invariants), [`typert-registry`](../packages/typert/registry) | | [`lsp`](../packages/lsp/lsp) | `lsp` | [`brand`](../packages/util/brand), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) | | [`sandbox`](../packages/sandbox/sandbox) | `sandbox` | [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm) | | [`settings-local`](../packages/settings/settings-local) | `settings` | [`atomic-write`](../packages/util/atomic-write), [`invariants`](../packages/support/invariants), [`paths`](../packages/util/paths), [`settings`](../packages/settings/settings) | @@ -1295,11 +1310,15 @@ flowchart TD | [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`user-approval`](../packages/ui/user-approval) | | [`permission`](../packages/ui/permission) | `ui` | [`bash`](../packages/bash/bash), [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-projection`](../packages/session-projection/session-projection), [`settings`](../packages/settings/settings), [`user-approval`](../packages/ui/user-approval) | <<<<<<< HEAD +<<<<<<< HEAD | [`client-ui-conversation`](../packages/client/ui-conversation) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`token-meter`](../packages/llm/token-meter) | | [`command-feedback`](../packages/feedback/command-feedback) | `feedback` | [`commands`](../packages/ui/commands), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session) | ======= | [`client-remotes`](../packages/client/remotes) | `client` | [`goal`](../packages/goal/goal), [`host-api-gateway`](../packages/host/api-gateway), [`invariants`](../packages/support/invariants) | >>>>>>> d6e02ebbcc (feat(goal): add TypeRT gateway example) +======= +| [`api-remotes`](../packages/api/remotes) | `api` | [`agent`](../packages/core/agent), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`typert-registry`](../packages/typert/registry) | +>>>>>>> c671d9e48c (refactor(api): colocate gateway and remote assembly) | [`pty-local`](../packages/pty/pty-local) | `pty` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`pty`](../packages/pty/pty), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subprocess`](../packages/subprocess/subprocess) | | [`tasks-local`](../packages/tasks/tasks-local) | `tasks` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tasks`](../packages/tasks/tasks), [`timeout`](../packages/util/timeout) | | [`agent-loop`](../packages/core/agent-loop) | `core` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session-persistence/session-persistence), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | @@ -1326,6 +1345,7 @@ flowchart TD | [`agent-loop-testkit`](../packages/support/agent-loop-testkit) | `support` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`tool-ask-user`](../packages/ui/tool-ask-user) | `ui` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools), [`user-interaction`](../packages/ui/user-interaction) | <<<<<<< HEAD +<<<<<<< HEAD | [`client-ui-command`](../packages/client/ui-command) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | | [`client-ui-deliverables`](../packages/client/ui-deliverables) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | | [`client-ui-goal`](../packages/client/ui-goal) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) | @@ -1333,6 +1353,9 @@ flowchart TD ======= | [`client-runtime`](../packages/client/runtime) | `client` | [`client-remotes`](../packages/client/remotes), [`invariants`](../packages/support/invariants), [`type-meta`](../packages/typert/type-meta), [`typert-registry`](../packages/typert/registry) | >>>>>>> d6e02ebbcc (feat(goal): add TypeRT gateway example) +======= +| [`client-runtime`](../packages/client/runtime) | `client` | [`api-remotes`](../packages/api/remotes), [`invariants`](../packages/support/invariants), [`type-meta`](../packages/typert/type-meta), [`typert-registry`](../packages/typert/registry) | +>>>>>>> c671d9e48c (refactor(api): colocate gateway and remote assembly) | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compact`](../packages/compact/compact), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`retention`](../packages/util/retention), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query) | | [`workspace-context`](../packages/context/workspace-context) | `context` | [`agent`](../packages/core/agent), [`fs`](../packages/fs/fs), [`invariants`](../packages/support/invariants), [`llm`](../packages/llm/llm), [`paths`](../packages/util/paths), [`session`](../packages/core/session), [`tools`](../packages/core/tools) | | [`repeat-tool-guard`](../packages/guard/repeat-tool-guard) | `guard` | [`agent`](../packages/core/agent), [`invariants`](../packages/support/invariants), [`tools`](../packages/core/tools) | @@ -1383,7 +1406,7 @@ flowchart TD | [`host-directory-picker-browse`](../packages/host/directory-picker-browse) | `host` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/support/invariants) | | [`host-directory-picker-native`](../packages/host/directory-picker-native) | `host` | [`client-runtime`](../packages/client/runtime), [`client-ui-slots`](../packages/client/ui-slots), [`client-ui-workspace`](../packages/client/ui-workspace), [`invariants`](../packages/support/invariants) | | [`client-ui-command`](../packages/client/ui-command) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants) | -| [`client-ui-goal`](../packages/client/ui-goal) | `client` | [`client-locale`](../packages/client/locale), [`client-remotes`](../packages/client/remotes), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) | +| [`client-ui-goal`](../packages/client/ui-goal) | `client` | [`api-remotes`](../packages/api/remotes), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`goal`](../packages/goal/goal), [`invariants`](../packages/support/invariants) | | [`client-ui-plan`](../packages/client/ui-plan) | `client` | [`client-connection`](../packages/client/connection), [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`plan-mode`](../packages/plan/plan-mode) | | [`client-ui-subagent`](../packages/client/ui-subagent) | `client` | [`client-locale`](../packages/client/locale), [`client-runtime`](../packages/client/runtime), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-primitives`](../packages/client/ui-primitives), [`client-ui-slash`](../packages/client/ui-slash), [`client-ui-slots`](../packages/client/ui-slots), [`invariants`](../packages/support/invariants), [`subagent`](../packages/subagent/subagent), [`token-meter`](../packages/llm/token-meter) | | [`host-directory-picker-auto`](../packages/host/directory-picker-auto) | `host` | [`host-directory-picker-browse`](../packages/host/directory-picker-browse), [`host-directory-picker-native`](../packages/host/directory-picker-native), [`host-webserver`](../packages/host/webserver), [`invariants`](../packages/support/invariants) | diff --git a/knip.json b/knip.json index 3ce9a32d99..3d7836104f 100644 --- a/knip.json +++ b/knip.json @@ -115,7 +115,7 @@ "tests/**/*.ts" ] }, - "packages/client/remotes": { + "packages/api/remotes": { "entry": [ "tests/**/*.e2e.ts" ], diff --git a/packages/README.i18n.yaml b/packages/README.i18n.yaml index d10f476f79..8aa9b92b91 100644 --- a/packages/README.i18n.yaml +++ b/packages/README.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write packages/README.md -README.md: 8fbb6069a784a5bd45423a4e1ae11834a597750d -README.zh.md: 42a8d691344c716021188df6fd870a841d543f36 +README.md: 229feae568ba6e40a9c633696097eff46fd5bc95 +README.zh.md: b84aef020a7e3edf305df709d399fbc7b093b6a3 diff --git a/packages/README.md b/packages/README.md index 8fbb6069a7..229feae568 100644 --- a/packages/README.md +++ b/packages/README.md @@ -11,6 +11,7 @@ Packages live at `packages///`; groups are containers, while names r | Group | Role | Release expectation | |---|---|---| | [`core/`](core/README.md) | Product API spine: sessions, prompts, tools, agent services, and the concrete loop | Product — stable surface | +| [`api/`](api/README.md) | Remote BFF assembly and TypeRT RPC gateway | Product — stable surface | | [`typert/`](typert/README.md) | Type graph generation, artifact loading, and runtime registry | Product — stable surface | | [`goal/`](goal/README.md) | Same-session goal persistence and lifecycle | Product — stable surface | | [`feedback/`](feedback/README.md) | Human feedback | Product — stable surface | diff --git a/packages/README.zh.md b/packages/README.zh.md index 42a8d69134..b84aef020a 100644 --- a/packages/README.zh.md +++ b/packages/README.zh.md @@ -11,6 +11,7 @@ | 组 | 职责 | 发布预期 | |---|---|---| | [`core/`](core/README.md) | 产品 API 主干:会话、提示词、工具、agent(智能体)服务与具体循环 | 产品:稳定表面 | +| [`api/`](api/README.md) | Remote BFF 装配与 TypeRT RPC Gateway | 产品:稳定表面 | | [`typert/`](typert/README.md) | 类型图生成、产物加载与运行时注册表 | 产品:稳定表面 | | [`goal/`](goal/README.md) | 同会话 goal 的持久化与生命周期 | 产品:稳定表面 | | [`feedback/`](feedback/README.md) | 人类反馈 | 产品:稳定表面 | diff --git a/packages/client/remotes/README.i18n.yaml b/packages/api/README.i18n.yaml similarity index 56% rename from packages/client/remotes/README.i18n.yaml rename to packages/api/README.i18n.yaml index 86f2aded18..855eeb8eaa 100644 --- a/packages/client/remotes/README.i18n.yaml +++ b/packages/api/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/client/remotes/README.md -README.md: e29188b8e3ae5ecefe194f1355558e9bdeaae7dd -README.zh.md: e6425ab190a28e0a38c3713c4e21645789a8f00c +# pnpm run verify-translation-pairing --write packages/api/README.md +README.md: 0dcded5922fea1ea6676315029ba0eadd74dd3df +README.zh.md: 1b9bb9133a955d0cbef0ca91728aab1545831d94 diff --git a/packages/api/README.md b/packages/api/README.md new file mode 100644 index 0000000000..0dcded5922 --- /dev/null +++ b/packages/api/README.md @@ -0,0 +1,17 @@ +# api/ — Remote API layers + +English | [中文](README.zh.md) + +The application-facing Remote stack. `remotes` owns BFF policy and the selected business API, while `gateway` implements the TypeRT unary RPC endpoints shared by Host and Client environments. + +| Package | Role | ctx key | +|---|---|---| +| [`remotes/`](remotes/README.md) | Host Agent/Session lookup policy and Client Remote contribution assembly | no service; configures `ctx.typert` and consumes `ctx.api` | +| [`gateway/`](gateway/README.md) | Host TypeRT dispatcher and Client API endpoint | `ctx.typertGateway` / `ctx.api` | + +The runtime dependency direction is `remotes → gateway → connection → webserver`: the BFF consumes the shared `TypeRTClientApi` contract, Gateway delegates transport to Connection, and Connection mounts on the HTTP server. Cordis service injection and Client module metadata preserve this order without importing the concrete Gateway from the Remotes Client entry. + +## Known Limitations and Deferred Work + +- Connection and WebServer remain at [`client/connection`](../client/connection/README.md) and [`host/webserver`](../host/webserver/README.md); a later package-only move can place them under `api/connection` and `api/webserver` without changing their service contracts. +- The legacy API Proxy remains at [`host/apiproxy`](../host/apiproxy/README.md) as the fallback for methods not yet migrated to Remote. It consumes the Host resolver owned by `api-remotes` so migrated and legacy methods retain one Agent/Session identity policy. diff --git a/packages/api/README.zh.md b/packages/api/README.zh.md new file mode 100644 index 0000000000..1b9bb9133a --- /dev/null +++ b/packages/api/README.zh.md @@ -0,0 +1,17 @@ +# api/:Remote API 层 + +[English](README.md) | 中文 + +面向应用的 Remote 技术栈。`remotes` 负责 BFF 策略和选定的业务 API,`gateway` 则实现 Host 与 Client 环境共用的 TypeRT 一元 RPC endpoint。 + +| 包 | 职责 | ctx key | +|---|---|---| +| [`remotes/`](remotes/README.md) | Host Agent/Session lookup 策略与 Client Remote contribution 装配 | 无服务;配置 `ctx.typert` 并消费 `ctx.api` | +| [`gateway/`](gateway/README.md) | Host TypeRT 分发器与 Client API endpoint | `ctx.typertGateway` / `ctx.api` | + +运行时依赖方向为 `remotes → gateway → connection → webserver`:BFF 消费共享的 `TypeRTClientApi` 契约,Gateway 把传输交给 Connection,Connection 再挂载到 HTTP server。Cordis 服务注入与 Client 模块元数据在不让 Remotes Client 入口导入具体 Gateway 实现的前提下维持该顺序。 + +## 已知限制与延期工作 + +- Connection 与 WebServer 仍位于 [`client/connection`](../client/connection/README.md) 和 [`host/webserver`](../host/webserver/README.md);后续可以只移动包,将它们放到 `api/connection` 和 `api/webserver` 下,而无需改变服务契约。 +- 旧 API Proxy 仍位于 [`host/apiproxy`](../host/apiproxy/README.md),作为尚未迁移到 Remote 的方法的回退路径。它使用由 `api-remotes` 持有的 Host resolver,使已迁移与旧方法共用同一套 Agent/Session 身份策略。 diff --git a/packages/host/api-gateway/README.i18n.yaml b/packages/api/gateway/README.i18n.yaml similarity index 56% rename from packages/host/api-gateway/README.i18n.yaml rename to packages/api/gateway/README.i18n.yaml index 8d8d699c7a..41bbb0621f 100644 --- a/packages/host/api-gateway/README.i18n.yaml +++ b/packages/api/gateway/README.i18n.yaml @@ -1,6 +1,6 @@ # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: -# pnpm run verify-translation-pairing --write packages/host/api-gateway/README.md -README.md: eb48c29628d39e381235b1f72754eb114960b1ad -README.zh.md: e53bb6c216e42fe2e970bf2cb80eac9ea7426497 +# pnpm run verify-translation-pairing --write packages/api/gateway/README.md +README.md: 9e3d4d89788bbc6edebfc0c0127999fed3ed9261 +README.zh.md: 9bbd46c71185a2fbf8da163565d6c19141c079ca diff --git a/packages/host/api-gateway/README.md b/packages/api/gateway/README.md similarity index 86% rename from packages/host/api-gateway/README.md rename to packages/api/gateway/README.md index eb48c29628..9e3d4d8978 100644 --- a/packages/host/api-gateway/README.md +++ b/packages/api/gateway/README.md @@ -1,8 +1,8 @@ -# @deepseek-ai/dsh-host-api-gateway +# @deepseek-ai/dsh-api-gateway English | [中文](README.zh.md) -Two-sided Remote control for Host and Client Cordis environments. The Host entry provides `ctx.typertGateway`, while `@deepseek-ai/dsh-host-api-gateway/client` provides `ctx.api`; both consume the same generated `InvocationDescriptor` contract and leave transport, request correlation, trust, and response envelopes to Connection. +Two-sided TypeRT RPC endpoint for Host and Client Cordis environments. The Host entry provides `ctx.typertGateway`, while `@deepseek-ai/dsh-api-gateway/client` provides `ctx.api`; both consume the same generated `InvocationDescriptor` contract and leave business selection to API Remotes and transport, request correlation, trust, and response envelopes to Connection. ## Host service: `TypertGatewayService` (ctx key: `typertGateway`) @@ -20,7 +20,7 @@ A cancellation-aware Remote method declares `signal: AbortSignal` as its final H Each call validates positional inputs, constructs the descriptor's exact named `args`, and sends it through `ctx.connection.rpc.call('/api', endpoint, ...)`. Generated cancellation-aware methods accept a final optional `AbortSignal`; the Client combines it with the contribution mount lifetime before calling Connection. The returned value is validated before reaching application code. Withdrawing a contribution removes its descriptors and methods together, aborts in-flight calls, and makes retained method handles reject. -Generated declaration merges provide the TypeScript API. The Client entry contains no Host Service or Host Cordis interface merge, and method lookup and invocation use ordinary objects and functions rather than a JavaScript Proxy. +Generated declaration merges provide the TypeScript API through the shared `TypeRTClientApi` contract. The Client entry contains no Host Service or Host Cordis interface merge, and method lookup and invocation use ordinary objects and functions rather than a JavaScript Proxy. ## Model Experience diff --git a/packages/host/api-gateway/README.zh.md b/packages/api/gateway/README.zh.md similarity index 86% rename from packages/host/api-gateway/README.zh.md rename to packages/api/gateway/README.zh.md index e53bb6c216..9bbd46c711 100644 --- a/packages/host/api-gateway/README.zh.md +++ b/packages/api/gateway/README.zh.md @@ -1,8 +1,8 @@ -# @deepseek-ai/dsh-host-api-gateway +# @deepseek-ai/dsh-api-gateway [English](README.md) | 中文 -为 Host 与 Client 两侧的 Cordis 环境提供 Remote 控制。Host 入口提供 `ctx.typertGateway`,`@deepseek-ai/dsh-host-api-gateway/client` 则提供 `ctx.api`;两者使用同一份生成的 `InvocationDescriptor` 契约,并将传输、请求关联、信任和响应封装交由 Connection 处理。 +为 Host 与 Client 两侧的 Cordis 环境提供 TypeRT RPC endpoint。Host 入口提供 `ctx.typertGateway`,`@deepseek-ai/dsh-api-gateway/client` 则提供 `ctx.api`;两者使用同一份生成的 `InvocationDescriptor` 契约,并将业务选择交给 API Remotes,将传输、请求关联、信任和响应封装交给 Connection。 ## Host 服务:`TypertGatewayService`(ctx key:`typertGateway`) @@ -20,7 +20,7 @@ Connection 可用时,Host 入口会在 Connection 共享的 `/api` FetchHandle 每次调用都会校验位置参数,构造与描述符完全匹配的具名 `args`,再通过 `ctx.connection.rpc.call('/api', endpoint, ...)` 发送。生成的支持取消的方法接受最后一个可选 `AbortSignal`;Client 会在调用 Connection 前将它与贡献项的挂载生命周期合并。返回值经过校验后才会交给应用代码。撤回贡献项会同时移除其描述符和方法、中止正在进行的调用,并使外部仍持有的方法句柄在调用时返回拒绝。 -生成的声明合并提供 TypeScript API。Client 入口不包含 Host 服务或 Host Cordis 接口合并;方法查找和调用使用普通对象与函数,而不使用 JavaScript Proxy。 +生成的声明合并通过共享的 `TypeRTClientApi` 契约提供 TypeScript API。Client 入口不包含 Host 服务或 Host Cordis 接口合并;方法查找和调用使用普通对象与函数,而不使用 JavaScript Proxy。 ## 模型体验 diff --git a/packages/host/api-gateway/package.json b/packages/api/gateway/package.json similarity index 92% rename from packages/host/api-gateway/package.json rename to packages/api/gateway/package.json index 794ae323aa..fa351d84bf 100644 --- a/packages/host/api-gateway/package.json +++ b/packages/api/gateway/package.json @@ -1,6 +1,6 @@ { - "name": "@deepseek-ai/dsh-host-api-gateway", - "description": "Host dispatcher and Client API for TypeRT Remote invocations", + "name": "@deepseek-ai/dsh-api-gateway", + "description": "TypeRT Remote Host dispatcher and Client API endpoint", "version": "0.0.1", "private": true, "type": "module", diff --git a/packages/host/api-gateway/src/client/index.ts b/packages/api/gateway/src/client/index.ts similarity index 96% rename from packages/host/api-gateway/src/client/index.ts rename to packages/api/gateway/src/client/index.ts index 5503fc3dcf..bafffa80f7 100644 --- a/packages/host/api-gateway/src/client/index.ts +++ b/packages/api/gateway/src/client/index.ts @@ -9,10 +9,9 @@ import type { Context } from 'cordis' import type { ConnectionHandle, RpcError } from '@deepseek-ai/dsh-client-connection/client' import type { InvocationDescriptor, + TypeRTClientApi, TypeRTCodec, - TypeRTDisposer, TypeRTRemoteContribution, - TypeRTRemoteNamespaceMap, } from '@deepseek-ai/dsh-type-meta' type RemoteMethod = (...args: unknown[]) => Promise @@ -40,14 +39,7 @@ interface ScopedProjection { } /** Typed API service augmented by generated direct Remote namespaces. */ -export interface ClientApi extends TypeRTRemoteNamespaceMap { - /** - * Mount one generated Host-for-Client contribution in the caller's fiber. - * @param contribution - explicitly selected Remote package artifact. - * @returns disposer withdrawing descriptors and concrete methods together. - */ - mount(contribution: TypeRTRemoteContribution): TypeRTDisposer -} +export type ClientApi = TypeRTClientApi declare module 'cordis' { interface Context { @@ -67,7 +59,7 @@ export function apply(ctx: Context): void { new ClientApiService(ctx) } -class ClientApiService extends Service implements ClientApi { +class ClientApiService extends Service implements TypeRTClientApi { private readonly ownerCtx: Context private readonly direct = new Map() private readonly scoped = new Map() @@ -77,7 +69,7 @@ class ClientApiService extends Service implements ClientApi { this.ownerCtx = ctx } - mount(contribution: TypeRTRemoteContribution): TypeRTDisposer { + mount(contribution: TypeRTRemoteContribution): ReturnType { this.validateContribution(contribution) const callerCtx = this.ctx const disposeRemote = callerCtx.typert.remotes.register(contribution) diff --git a/packages/host/api-gateway/src/index.ts b/packages/api/gateway/src/index.ts similarity index 99% rename from packages/host/api-gateway/src/index.ts rename to packages/api/gateway/src/index.ts index 8ea26b5990..13cf460f4d 100644 --- a/packages/host/api-gateway/src/index.ts +++ b/packages/api/gateway/src/index.ts @@ -1,7 +1,7 @@ /** * Live TypeRT Remote dispatch over Cordis Services and registered providers. * Transport, request correlation, and response envelopes belong to Connection. - * @module @deepseek-ai/dsh-host-api-gateway + * @module @deepseek-ai/dsh-api-gateway */ import { Context, Service, symbols } from 'cordis' diff --git a/packages/host/api-gateway/src/invariant.ts b/packages/api/gateway/src/invariant.ts similarity index 77% rename from packages/host/api-gateway/src/invariant.ts rename to packages/api/gateway/src/invariant.ts index 65c94b4ac4..711c4edab5 100644 --- a/packages/host/api-gateway/src/invariant.ts +++ b/packages/api/gateway/src/invariant.ts @@ -1,16 +1,16 @@ /** - * Package-owned invariant companion for `@deepseek-ai/dsh-host-api-gateway`. - * @module @deepseek-ai/dsh-host-api-gateway/invariant + * Package-owned invariant companion for `@deepseek-ai/dsh-api-gateway`. + * @module @deepseek-ai/dsh-api-gateway/invariant */ /* jscpd:ignore-start */ import type { Context } from 'cordis' import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' -const PACKAGE_NAME = '@deepseek-ai/dsh-host-api-gateway' +const PACKAGE_NAME = '@deepseek-ai/dsh-api-gateway' /** Cordis companion plugin name. */ -export const name = 'host-api-gateway-invariant' +export const name = 'api-gateway-invariant' /** Service required before the companion can reserve package ownership. */ export const inject = ['invariants'] diff --git a/packages/host/api-gateway/src/types.ts b/packages/api/gateway/src/types.ts similarity index 97% rename from packages/host/api-gateway/src/types.ts rename to packages/api/gateway/src/types.ts index f4bb276c22..0917ba2ca6 100644 --- a/packages/host/api-gateway/src/types.ts +++ b/packages/api/gateway/src/types.ts @@ -1,6 +1,6 @@ /** * Carrier-independent TypeRT Gateway request, service, and error contracts. - * @module @deepseek-ai/dsh-host-api-gateway/types + * @module @deepseek-ai/dsh-api-gateway/types */ /** One Remote method request after a carrier has decoded its envelope. */ diff --git a/packages/host/api-gateway/tests/client.spec.ts b/packages/api/gateway/tests/client.spec.ts similarity index 100% rename from packages/host/api-gateway/tests/client.spec.ts rename to packages/api/gateway/tests/client.spec.ts diff --git a/packages/host/api-gateway/tests/gateway.spec.ts b/packages/api/gateway/tests/gateway.spec.ts similarity index 99% rename from packages/host/api-gateway/tests/gateway.spec.ts rename to packages/api/gateway/tests/gateway.spec.ts index 0871dc2761..d784a1ac2f 100644 --- a/packages/host/api-gateway/tests/gateway.spec.ts +++ b/packages/api/gateway/tests/gateway.spec.ts @@ -16,7 +16,7 @@ import { type TypeRTLookupProvider, } from '@deepseek-ai/dsh-type-meta' import TypertRegistry, { type TypertContribution } from '@deepseek-ai/dsh-typert-registry' -import TypertGatewayService, { TypertGatewayError } from '@deepseek-ai/dsh-host-api-gateway' +import TypertGatewayService, { TypertGatewayError } from '@deepseek-ai/dsh-api-gateway' interface FixtureAgent { readonly id: string diff --git a/packages/host/api-gateway/tsconfig.json b/packages/api/gateway/tsconfig.json similarity index 100% rename from packages/host/api-gateway/tsconfig.json rename to packages/api/gateway/tsconfig.json diff --git a/packages/api/gateway/tsdown.config.ts b/packages/api/gateway/tsdown.config.ts new file mode 100644 index 0000000000..f9049b6067 --- /dev/null +++ b/packages/api/gateway/tsdown.config.ts @@ -0,0 +1,3 @@ +import { clientBundle } from '../../client/tsdown.client.ts' + +export default clientBundle('@deepseek-ai/dsh-api-gateway', ['lib/types/index.js', 'lib/types/invariant.js']) diff --git a/packages/api/remotes/README.i18n.yaml b/packages/api/remotes/README.i18n.yaml new file mode 100644 index 0000000000..c3c13a8049 --- /dev/null +++ b/packages/api/remotes/README.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write packages/api/remotes/README.md +README.md: cf54a56a849246d4efdca09cadd42e157064bdee +README.zh.md: 5cd7ef21c926440ca4df6d88ee4adfe87defcc3f diff --git a/packages/api/remotes/README.md b/packages/api/remotes/README.md new file mode 100644 index 0000000000..cf54a56a84 --- /dev/null +++ b/packages/api/remotes/README.md @@ -0,0 +1,25 @@ +# @deepseek-ai/dsh-api-remotes + +English | [中文](README.zh.md) + +Two-sided BFF for Host Remote capabilities selected by this application. The Host entry owns Agent/Session identity policy; the Client entry imports generated `/remote` artifacts as runtime values, mounts each contribution through `ctx.api`, and re-exports their declaration merges. Client business packages depend on this facade rather than the Gateway implementation or individual Remote runtime entries. + +`createApiRemoteAgentResolver()` reuses live Agents, resumes ordinary cold sessions, deduplicates concurrent resumes, preserves the subagent ownership fence, and configures the same resolver for TypeRT `agent` and `session` lookups. The standard Web API Proxy supplies its Agent defaults and scope setup, then uses the returned resolver for legacy methods, so migrated and unmigrated methods share one policy implementation. + +The current Client assembly mounts only the Goal Remote contribution. Cordis effect ownership withdraws every contribution when this assembly unloads, while `@deepseek-ai/dsh-api-gateway/client` owns descriptor validation, concrete root and scoped methods, invocation, and cancellation. The Client entry consumes the shared `TypeRTClientApi` interface through Cordis and does not import the concrete Gateway. + +This package contains no transport or Host service discovery logic. Its Client face can be reused by Web or a future TUI that provides the same React-free `ctx.api` contract. + +## Model Experience + +None, as this BFF selects Remote application methods and identity policy but registers no model surface. + +#### KV Cache effect + +No direct effect; mounted Host capabilities own any model-visible behavior they trigger. + +## Known Limitations and Deferred Work + +- The capability set is fixed by explicit build-time value imports; the Client does not discover the Host's active Services or Remote definitions at runtime. +- Additional capabilities require an explicit `/remote` value import and mount in this assembly. +- The standard Web Host supplies resume defaults and Agent-scope setup from the legacy API Proxy until that remaining BFF configuration moves into `api-remotes`. diff --git a/packages/api/remotes/README.zh.md b/packages/api/remotes/README.zh.md new file mode 100644 index 0000000000..5cd7ef21c9 --- /dev/null +++ b/packages/api/remotes/README.zh.md @@ -0,0 +1,25 @@ +# @deepseek-ai/dsh-api-remotes + +[English](README.md) | 中文 + +为本应用选定的 Host Remote 能力提供双侧 BFF。Host 入口负责 Agent/Session 身份策略;Client 入口以运行时值形式导入生成的 `/remote` 产物,通过 `ctx.api` 挂载每项贡献,并重新导出对应的声明合并。Client 业务包依赖该外观,而不依赖 Gateway 实现或单独的 Remote 运行时入口。 + +`createApiRemoteAgentResolver()` 会复用 live Agent、恢复普通冷会话、对并发恢复去重、保留 subagent ownership fence,并为 TypeRT `agent` 和 `session` lookup 配置同一个 resolver。标准 Web API Proxy 提供 Agent 默认值和 scope 设置,再将返回的 resolver 用于旧方法,使已迁移与未迁移方法共用同一份策略实现。 + +当前 Client 组合仅挂载 Goal Remote 贡献。该组合卸载时,Cordis effect 的所有权机制会撤回所有贡献;`@deepseek-ai/dsh-api-gateway/client` 负责描述符校验、具体的根级方法和作用域方法、调用与取消。Client 入口通过 Cordis 消费共享的 `TypeRTClientApi` 接口,不导入具体 Gateway。 + +本包不包含传输逻辑或 Host 服务发现逻辑。Web 或未来的 TUI 只要提供同一份不依赖 React 的 `ctx.api` 契约,均可复用其 Client face。 + +## 模型体验 + +无,因为该 BFF 只选择 Remote 应用方法和身份策略,不注册任何模型接口。 + +#### KV Cache 影响 + +无直接影响;其触发的任何模型可见行为均由已挂载的 Host 能力负责。 + +## 已知限制与暂缓事项 + +- 能力集合由构建时显式导入的值固定确定;Client 不会在运行时发现 Host 中已启用的服务或 Remote 定义。 +- 若要增加能力,必须显式导入相应的 `/remote` 值并在此组合中挂载。 +- 在剩余 BFF 配置迁移到 `api-remotes` 之前,标准 Web Host 仍从旧 API Proxy 提供恢复默认值与 Agent scope 设置。 diff --git a/packages/client/remotes/package.json b/packages/api/remotes/package.json similarity index 64% rename from packages/client/remotes/package.json rename to packages/api/remotes/package.json index ba4e7b6a01..0a1e3ec71d 100644 --- a/packages/client/remotes/package.json +++ b/packages/api/remotes/package.json @@ -1,6 +1,6 @@ { - "name": "@deepseek-ai/dsh-client-remotes", - "description": "Platform-neutral assembly of explicitly selected Host Remote contributions", + "name": "@deepseek-ai/dsh-api-remotes", + "description": "Remote BFF assembly and Host Agent/Session lookup policy", "version": "0.0.1", "private": true, "type": "module", @@ -24,7 +24,7 @@ }, "dshClient": { "inject": [ - "@deepseek-ai/dsh-host-api-gateway" + "@deepseek-ai/dsh-api-gateway" ], "platform": "web", "immediately": true @@ -40,16 +40,25 @@ "lib/client.js", "lib/types/**/*.d.ts" ], + "dependencies": { + "@deepseek-ai/dsh-type-meta": "workspace:^" + }, "peerDependencies": { - "@deepseek-ai/dsh-host-api-gateway": "^0.0.1", + "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-goal": "^0.0.1", "@deepseek-ai/dsh-invariants": "^0.0.1", + "@deepseek-ai/dsh-session": "^0.0.1", + "@deepseek-ai/dsh-session-persistence": "^0.0.1", + "@deepseek-ai/dsh-typert-registry": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { - "@deepseek-ai/dsh-host-api-gateway": "workspace:^", + "@deepseek-ai/dsh-agent": "workspace:^", "@deepseek-ai/dsh-goal": "workspace:^", "@deepseek-ai/dsh-invariants": "workspace:^", + "@deepseek-ai/dsh-session": "workspace:^", + "@deepseek-ai/dsh-session-persistence": "workspace:^", + "@deepseek-ai/dsh-typert-registry": "workspace:^", "cordis": "^4.0.0-rc.7" } } diff --git a/packages/api/remotes/src/agent-lookup.ts b/packages/api/remotes/src/agent-lookup.ts new file mode 100644 index 0000000000..e3a5b27df8 --- /dev/null +++ b/packages/api/remotes/src/agent-lookup.ts @@ -0,0 +1,193 @@ +/** Host BFF policy for resolving Remote Agent and Session identities. */ + +import type { Context } from 'cordis' +import type { Agent, AgentOptions, AgentSetup } from '@deepseek-ai/dsh-agent' +import type { Session, SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session' +import type {} from '@deepseek-ai/dsh-session-persistence' +import { TypeRTLookupFailure } from '@deepseek-ai/dsh-type-meta' +import type {} from '@deepseek-ai/dsh-typert-registry' + +/** Caller-facing failures preserved by the Gateway's RPC adapter. */ +export type ApiRemoteLookupError = + | { readonly code: 'agent-busy'; readonly message: string; readonly details: { readonly reason: string } } + | { readonly code: 'session-not-found'; readonly message: string; readonly details: { readonly sessionId: SessionId } } + | { readonly code: 'internal'; readonly message: string; readonly details: Record } + +/** Result of resolving one session identity to its live Agent. */ +export type ApiRemoteAgentResult = + | { readonly agent: Agent } + | { readonly error: ApiRemoteLookupError } + +/** Resume configuration supplied by the owning Host composition. */ +export interface ApiRemoteAgentOptions { + /** Per-Agent defaults used when a cold identity must resume. */ + readonly agentOptions?: AgentOptions + /** Host-specific Agent-scope composition completed before publication. */ + readonly setup?: AgentSetup +} + +/** Cold identity absent from the durable session store. */ +export class ApiRemoteSessionNotFound extends Error {} + +/** Session identity whose lifecycle belongs to subagent routing. */ +export class ApiRemoteSubagentSessionOwnership extends Error { + /** + * Construct the ownership fence. + * @param sessionId - identity reserved to subagent routing. + */ + constructor(readonly sessionId: SessionId) { + super(`session "${sessionId}" is a subagent session; use subagent delivery`) + } +} + +/** + * Test whether generic Host routing must leave an identity to subagent routing. + * @param ctx - Host Context carrying the live Agent registry. + * @param session - attached or live Session metadata. + * @param agent - live Agent when one is registered. + * @returns whether generic Remote and legacy API calls must reject the identity. + */ +export function hasApiRemoteSubagentOwner( + ctx: Context, + session: Pick, + agent: Agent | undefined, +): boolean { + if (session.header.origin === 'subagent') return true + const parentId = session.header.parentSession + if (parentId === undefined || agent === undefined) return false + const parent = ctx.agents.get(parentId) + return parent !== undefined && ctx.agents.isOwnedBy(agent.id, parent) +} + +/** + * Build the stable caller-facing ownership rejection. + * @param sessionId - identity reserved to subagent routing. + * @returns the existing `agent-busy` RPC shape. + */ +export function apiRemoteSubagentOwnershipError(sessionId: SessionId): ApiRemoteLookupError { + return { + code: 'agent-busy', + message: `session "${sessionId}" is owned by subagent routing`, + details: { reason: 'use subagent delivery for this child session' }, + } +} + +/** + * Inspect one cold served session without repairing, resuming, or publishing it. + * @param ctx - Host Context carrying the optional persistence provider. + * @param sessionId - durable identity to inspect. + * @returns detached metadata and events for a servable session. + * @throws {@link ApiRemoteSessionNotFound} when the identity has no project-backed session. + */ +export async function inspectApiRemoteSession( + ctx: Context, + sessionId: SessionId, +): Promise<{ meta: SessionHeader; events: SessionEvent[] }> { + const persistence = ctx.get('sessionPersistence') + if (persistence === undefined) { + throw new Error('session persistence is not configured (load a dsh-session-persistence backend)') + } + const meta = (await persistence.list()).find(candidate => candidate.id === sessionId) + if (meta === undefined || meta.cwd === undefined) { + throw new ApiRemoteSessionNotFound(`session "${sessionId}" not found`) + } + const inspected = await persistence.inspect(sessionId) + if (inspected.meta.cwd === undefined) { + throw new ApiRemoteSessionNotFound(`session "${sessionId}" not found`) + } + return { meta: inspected.meta, events: [...inspected.events] } +} + +/** + * Create the Host's shared Agent resolver and configure Agent/Session TypeRT lookups. + * Live Agents are reused, ordinary cold sessions resume once per identity, and + * subagent-owned identities retain the legacy `agent-busy` fence. + * @param ctx - owning Host Context. + * @param options - defaults and Agent-scope setup used only for cold resume. + * @returns resolver shared by legacy API Proxy methods and TypeRT lookups. + */ +export function createApiRemoteAgentResolver( + ctx: Context, + options: ApiRemoteAgentOptions, +): (sessionId: SessionId) => Promise { + const resumes = new Map>() + + const fencedLiveAgent = (sessionId: SessionId): ApiRemoteAgentResult | undefined => { + const live = ctx.agents.get(sessionId) + if (live === undefined) return undefined + if (hasApiRemoteSubagentOwner(ctx, live.session, live)) { + return { error: apiRemoteSubagentOwnershipError(sessionId) } + } + return { agent: live } + } + + const agentFor = async (sessionId: SessionId): Promise => { + const fenced = fencedLiveAgent(sessionId) + if (fenced !== undefined) return fenced + const attached = ctx.sessions.get(sessionId) + if (attached !== undefined && hasApiRemoteSubagentOwner(ctx, attached, undefined)) { + return { error: apiRemoteSubagentOwnershipError(sessionId) } + } + let resume = resumes.get(sessionId) + if (resume === undefined) { + resume = (async () => { + try { + const inspected = await inspectApiRemoteSession(ctx, sessionId) + if (hasApiRemoteSubagentOwner(ctx, { header: inspected.meta }, undefined)) { + throw new ApiRemoteSubagentSessionOwnership(sessionId) + } + const publishedSession = ctx.sessions.get(sessionId) + const publishedAgent = ctx.agents.get(sessionId) + if (publishedSession !== undefined + && hasApiRemoteSubagentOwner(ctx, publishedSession, publishedAgent)) { + throw new ApiRemoteSubagentSessionOwnership(sessionId) + } + const handle = await ctx.agents.resume({ + resumeSessionId: sessionId, + ...options.agentOptions === undefined ? {} : { agentOptions: options.agentOptions }, + ...options.setup === undefined ? {} : { setup: options.setup }, + }) + return handle.agent + } finally { + resumes.delete(sessionId) + } + })() + resumes.set(sessionId, resume) + } + try { + return { agent: await resume } + } catch (error: unknown) { + if (error instanceof ApiRemoteSessionNotFound) { + return { error: { code: 'session-not-found', message: error.message, details: { sessionId } } } + } + if (error instanceof ApiRemoteSubagentSessionOwnership) { + return { error: apiRemoteSubagentOwnershipError(error.sessionId) } + } + const fenced = fencedLiveAgent(sessionId) + if (fenced !== undefined) return fenced + const attached = ctx.sessions.get(sessionId) + if (attached !== undefined && hasApiRemoteSubagentOwner(ctx, attached, undefined)) { + return { error: apiRemoteSubagentOwnershipError(sessionId) } + } + return { + error: { + code: 'internal', + message: `resume failed for session "${sessionId}": ${String(error)}`, + details: {}, + }, + } + } + } + + ctx.inject(['typert'], (typeCtx) => { + const resolveAgent = async (sessionId: SessionId): Promise => { + const found = await agentFor(sessionId) + if ('error' in found) throw new TypeRTLookupFailure(found.error) + return found.agent + } + typeCtx.typert.lookups.configure('agent', resolveAgent) + typeCtx.typert.lookups.configure('session', async sessionId => (await resolveAgent(sessionId)).session) + }) + + return agentFor +} diff --git a/packages/client/remotes/src/client/index.ts b/packages/api/remotes/src/client/index.ts similarity index 64% rename from packages/client/remotes/src/client/index.ts rename to packages/api/remotes/src/client/index.ts index 09757b5e9e..1bc36b62ee 100644 --- a/packages/client/remotes/src/client/index.ts +++ b/packages/api/remotes/src/client/index.ts @@ -1,12 +1,19 @@ /** Platform-neutral assembly of generated Host Remote contributions. */ import type { Context } from 'cordis' -import type {} from '@deepseek-ai/dsh-host-api-gateway/client' import goalsRemote from '@deepseek-ai/dsh-goal/remote' +import type { TypeRTClientApi } from '@deepseek-ai/dsh-type-meta' -export type { ClientApi } from '@deepseek-ai/dsh-host-api-gateway/client' +export type { TypeRTClientApi as ClientApi } from '@deepseek-ai/dsh-type-meta' export type {} from '@deepseek-ai/dsh-goal/remote' +declare module 'cordis' { + interface Context { + /** Generated direct Remote namespaces selected by this Client assembly. */ + api: TypeRTClientApi + } +} + /** Required service: the typed Client API contribution mount. */ export const inject = ['api'] diff --git a/packages/api/remotes/src/index.ts b/packages/api/remotes/src/index.ts new file mode 100644 index 0000000000..4cd70f4a78 --- /dev/null +++ b/packages/api/remotes/src/index.ts @@ -0,0 +1,18 @@ +/** Host BFF entry and Loader shell for the Remote contribution assembly. */ + +export { + ApiRemoteSessionNotFound, + ApiRemoteSubagentSessionOwnership, + apiRemoteSubagentOwnershipError, + createApiRemoteAgentResolver, + hasApiRemoteSubagentOwner, + inspectApiRemoteSession, +} from './agent-lookup.ts' +export type { + ApiRemoteAgentOptions, + ApiRemoteAgentResult, + ApiRemoteLookupError, +} from './agent-lookup.ts' + +/** Host plugin body; the selected contributions mount only in Client environments. */ +export function apply(): void {} diff --git a/packages/client/remotes/src/invariant.ts b/packages/api/remotes/src/invariant.ts similarity index 70% rename from packages/client/remotes/src/invariant.ts rename to packages/api/remotes/src/invariant.ts index 1a6b0ba237..3310bed11f 100644 --- a/packages/client/remotes/src/invariant.ts +++ b/packages/api/remotes/src/invariant.ts @@ -1,17 +1,17 @@ -/** Package-owned invariant companion for `@deepseek-ai/dsh-client-remotes`. */ +/** Package-owned invariant companion for `@deepseek-ai/dsh-api-remotes`. */ /* jscpd:ignore-start */ import type { Context } from 'cordis' import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants' -const PACKAGE_NAME = '@deepseek-ai/dsh-client-remotes' +const PACKAGE_NAME = '@deepseek-ai/dsh-api-remotes' /** Cordis companion plugin name. */ -export const name = 'client-remotes-invariant' +export const name = 'api-remotes-invariant' /** Service required before the companion can reserve package ownership. */ export const inject = ['invariants'] -/** No runtime invariant: the API service owns contribution and method lifecycle atomically. */ +/** No runtime invariant: TypeRT and the Agent/Session registries own the observed relationships. */ const install: InvariantInstaller = () => {} /** diff --git a/packages/client/remotes/tests/built-lib.e2e.ts b/packages/api/remotes/tests/built-lib.e2e.ts similarity index 95% rename from packages/client/remotes/tests/built-lib.e2e.ts rename to packages/api/remotes/tests/built-lib.e2e.ts index 0cee3eb245..b8f6c81e98 100644 --- a/packages/client/remotes/tests/built-lib.e2e.ts +++ b/packages/api/remotes/tests/built-lib.e2e.ts @@ -17,13 +17,13 @@ const artifactUrl = (path: string): string => pathToFileURL(artifact(path)).href const requiredArtifacts = [ 'packages/client/connection/lib/client.js', 'packages/client/connection/lib/index.js', - 'packages/client/remotes/lib/client.js', + 'packages/api/remotes/lib/client.js', 'packages/core/agent/lib/index.js', 'packages/core/session/lib/index.js', 'packages/goal/goal/lib/index.js', 'packages/goal/goal/lib/typert.host.js', - 'packages/host/api-gateway/lib/client.js', - 'packages/host/api-gateway/lib/index.js', + 'packages/api/gateway/lib/client.js', + 'packages/api/gateway/lib/index.js', 'packages/typert/registry/lib/client.js', 'packages/typert/registry/lib/index.js', ].every(path => existsSync(artifact(path))) @@ -32,15 +32,15 @@ describe.skipIf(!requiredArtifacts)('Goal Remote built LIB chain', () => { it('runs root and Agent-scoped calls through generated bundles and real HTTP', async () => { const urls = Object.fromEntries(Object.entries({ agent: 'packages/core/agent/lib/index.js', - apiGatewayClient: 'packages/host/api-gateway/lib/client.js', - apiGatewayHost: 'packages/host/api-gateway/lib/index.js', + apiGatewayClient: 'packages/api/gateway/lib/client.js', + apiGatewayHost: 'packages/api/gateway/lib/index.js', connectionClient: 'packages/client/connection/lib/client.js', connectionHost: 'packages/client/connection/lib/index.js', goal: 'packages/goal/goal/lib/index.js', goalTypert: 'packages/goal/goal/lib/typert.host.js', registryClient: 'packages/typert/registry/lib/client.js', registryHost: 'packages/typert/registry/lib/index.js', - remotesClient: 'packages/client/remotes/lib/client.js', + remotesClient: 'packages/api/remotes/lib/client.js', session: 'packages/core/session/lib/index.js', }).map(([key, path]) => [key, artifactUrl(path)])) const script = ` @@ -131,8 +131,8 @@ describe.skipIf(!requiredArtifacts)('Goal Remote built LIB chain', () => { for (const id of [ '@deepseek-ai/dsh-typert-registry', '@deepseek-ai/dsh-client-connection', - '@deepseek-ai/dsh-host-api-gateway', - '@deepseek-ai/dsh-client-remotes', + '@deepseek-ai/dsh-api-gateway', + '@deepseek-ai/dsh-api-remotes', ]) { const plugin = instantiate(id) await client.plugin({ inject: plugin.inject, apply: plugin.apply }) diff --git a/packages/client/remotes/tsconfig.json b/packages/api/remotes/tsconfig.json similarity index 63% rename from packages/client/remotes/tsconfig.json rename to packages/api/remotes/tsconfig.json index c99a5fce19..148804dc0f 100644 --- a/packages/client/remotes/tsconfig.json +++ b/packages/api/remotes/tsconfig.json @@ -12,7 +12,19 @@ "path": "../../../vendor/cordis" }, { - "path": "../../host/api-gateway" + "path": "../../core/agent" + }, + { + "path": "../../core/session" + }, + { + "path": "../../session-persistence/session-persistence" + }, + { + "path": "../../typert/type-meta" + }, + { + "path": "../../typert/registry" }, { "path": "../../ui/commands" diff --git a/packages/api/remotes/tsdown.config.ts b/packages/api/remotes/tsdown.config.ts new file mode 100644 index 0000000000..287b2c7975 --- /dev/null +++ b/packages/api/remotes/tsdown.config.ts @@ -0,0 +1,3 @@ +import { clientBundle } from '../../client/tsdown.client.ts' + +export default clientBundle('@deepseek-ai/dsh-api-remotes', ['lib/types/index.js', 'lib/types/invariant.js']) diff --git a/packages/bundle/base/cordis.patch.yml b/packages/bundle/base/cordis.patch.yml index 0b1cc43a50..11b23c27be 100644 --- a/packages/bundle/base/cordis.patch.yml +++ b/packages/bundle/base/cordis.patch.yml @@ -41,7 +41,7 @@ name: '@deepseek-ai/dsh-typert-loader' - id: typert-gateway - name: '@deepseek-ai/dsh-host-api-gateway' + name: '@deepseek-ai/dsh-api-gateway' - id: session-title name: '@deepseek-ai/dsh-session-title' diff --git a/packages/bundle/base/package.json b/packages/bundle/base/package.json index 2ec17d9c66..9895d8834c 100644 --- a/packages/bundle/base/package.json +++ b/packages/bundle/base/package.json @@ -49,7 +49,7 @@ "@deepseek-ai/dsh-fs-sandbox": "workspace:^", "@deepseek-ai/dsh-goal": "workspace:^", "@deepseek-ai/dsh-goal-session": "workspace:^", - "@deepseek-ai/dsh-host-api-gateway": "workspace:^", + "@deepseek-ai/dsh-api-gateway": "workspace:^", "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-llm-deepseek": "workspace:^", "@deepseek-ai/dsh-llm-pi-ai": "workspace:^", diff --git a/packages/bundle/web-app/cordis.patch.yml b/packages/bundle/web-app/cordis.patch.yml index 001c43948d..dc3212a36c 100644 --- a/packages/bundle/web-app/cordis.patch.yml +++ b/packages/bundle/web-app/cordis.patch.yml @@ -124,8 +124,8 @@ - id: connection name: '@deepseek-ai/dsh-client-connection' - - id: client-remotes - name: '@deepseek-ai/dsh-client-remotes' + - id: api-remotes + name: '@deepseek-ai/dsh-api-remotes' - id: client-runtime name: '@deepseek-ai/dsh-client-runtime' diff --git a/packages/bundle/web-app/package.json b/packages/bundle/web-app/package.json index 89b5e8e2a7..4e2d9cf70b 100644 --- a/packages/bundle/web-app/package.json +++ b/packages/bundle/web-app/package.json @@ -36,7 +36,7 @@ "@deepseek-ai/dsh-client-hmr": "workspace:^", "@deepseek-ai/dsh-client-locale": "workspace:^", "@deepseek-ai/dsh-client-modules": "workspace:^", - "@deepseek-ai/dsh-client-remotes": "workspace:^", + "@deepseek-ai/dsh-api-remotes": "workspace:^", "@deepseek-ai/dsh-client-runtime": "workspace:^", "@deepseek-ai/dsh-client-ui-command": "workspace:^", "@deepseek-ai/dsh-client-ui-conversation": "workspace:^", diff --git a/packages/client/remotes/README.md b/packages/client/remotes/README.md deleted file mode 100644 index e29188b8e3..0000000000 --- a/packages/client/remotes/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# @deepseek-ai/dsh-client-remotes - -English | [中文](README.zh.md) - -Platform-neutral Client facade for Host Remote capabilities selected by this application. Its Client entry imports generated `/remote` artifacts as runtime values, mounts each contribution through `ctx.api`, and re-exports their declaration merges. Client business packages depend on this facade rather than the Host API Gateway or individual Remote runtime entries. - -The current assembly mounts only the Goal Remote contribution. Cordis effect ownership withdraws every contribution when this assembly unloads, while the Client face of `@deepseek-ai/dsh-host-api-gateway` owns descriptor validation, concrete root and scoped methods, invocation, and cancellation. - -This package contains no transport or Host discovery logic. It can be reused by Web or a future TUI Client that provides the same React-free `ctx.api` contract. - -## Model Experience - -None, as this Client assembly selects Remote application methods and registers no model surface. - -#### KV Cache effect - -No direct effect; mounted Host capabilities own any model-visible behavior they trigger. - -## Known Limitations and Deferred Work - -- The capability set is fixed by explicit build-time value imports; the Client does not discover the Host's active Services or Remote definitions at runtime. -- Additional capabilities require an explicit `/remote` value import and mount in this assembly. diff --git a/packages/client/remotes/README.zh.md b/packages/client/remotes/README.zh.md deleted file mode 100644 index e6425ab190..0000000000 --- a/packages/client/remotes/README.zh.md +++ /dev/null @@ -1,22 +0,0 @@ -# @deepseek-ai/dsh-client-remotes - -[English](README.md) | 中文 - -为本应用选定的 Host Remote 能力提供平台无关的 Client 外观。其 Client 入口以运行时值形式导入生成的 `/remote` 产物,通过 `ctx.api` 挂载每项贡献,并重新导出对应的声明合并。Client 业务包依赖此外观,而不依赖 Host API Gateway 或单独的 Remote 运行时入口。 - -当前组合仅挂载 Goal Remote 贡献。该组合卸载时,Cordis effect 的所有权机制会撤回所有贡献;`@deepseek-ai/dsh-host-api-gateway` 的 Client 侧负责描述符校验、具体的根级方法和作用域方法、调用与取消。 - -本包不包含传输逻辑或 Host 发现逻辑。Web 和未来的 TUI Client 只要提供同一份不依赖 React 的 `ctx.api` 契约,均可复用本包。 - -## 模型体验 - -无,因为此 Client 组合只选择应用的 Remote 方法,不注册任何模型接口。 - -#### KV Cache 影响 - -无直接影响;其触发的任何模型可见行为均由已挂载的 Host 能力负责。 - -## 已知限制与暂缓事项 - -- 能力集合由构建时显式导入的值固定确定;Client 不会在运行时发现 Host 中已启用的服务或 Remote 定义。 -- 若要增加能力,必须显式导入相应的 `/remote` 值并在此组合中挂载。 diff --git a/packages/client/remotes/src/index.ts b/packages/client/remotes/src/index.ts deleted file mode 100644 index c8c4ff20be..0000000000 --- a/packages/client/remotes/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** Host Loader entry for the Client Remote contribution assembly. */ - -/** Host plugin body; the selected contributions mount only in Client environments. */ -export function apply(): void {} diff --git a/packages/client/remotes/tsdown.config.ts b/packages/client/remotes/tsdown.config.ts deleted file mode 100644 index 20fa098462..0000000000 --- a/packages/client/remotes/tsdown.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { clientBundle } from '../tsdown.client.ts' - -export default clientBundle('@deepseek-ai/dsh-client-remotes', ['lib/types/index.js', 'lib/types/invariant.js']) diff --git a/packages/client/runtime/package.json b/packages/client/runtime/package.json index cc51aa772d..711510b705 100644 --- a/packages/client/runtime/package.json +++ b/packages/client/runtime/package.json @@ -25,7 +25,7 @@ "dshClient": { "inject": [ "@deepseek-ai/dsh-client-connection", - "@deepseek-ai/dsh-client-remotes", + "@deepseek-ai/dsh-api-remotes", "@deepseek-ai/dsh-typert-registry" ], "platform": "web", @@ -49,14 +49,14 @@ }, "peerDependencies": { "@deepseek-ai/dsh-invariants": "^0.0.1", - "@deepseek-ai/dsh-client-remotes": "^0.0.1", + "@deepseek-ai/dsh-api-remotes": "^0.0.1", "@deepseek-ai/dsh-type-meta": "^0.0.1", "@deepseek-ai/dsh-typert-registry": "^0.0.1", "cordis": "^4.0.0-rc.7" }, "devDependencies": { "@deepseek-ai/dsh-invariants": "workspace:^", - "@deepseek-ai/dsh-client-remotes": "workspace:^", + "@deepseek-ai/dsh-api-remotes": "workspace:^", "@deepseek-ai/dsh-timeout": "workspace:^", "@deepseek-ai/dsh-type-meta": "workspace:^", "@deepseek-ai/dsh-typert-registry": "workspace:^", diff --git a/packages/client/runtime/src/client/index.ts b/packages/client/runtime/src/client/index.ts index f1efd6a65d..a9d2bb0d7d 100644 --- a/packages/client/runtime/src/client/index.ts +++ b/packages/client/runtime/src/client/index.ts @@ -1,7 +1,7 @@ /** Browser runtime services for slots, sessions, workspaces, and connection-stream delivery. */ import type { Context } from 'cordis' import type { ConnectionHandle, SessionId } from '@deepseek-ai/dsh-client-connection/client' -import type {} from '@deepseek-ai/dsh-client-remotes/client' +import type {} from '@deepseek-ai/dsh-api-remotes/client' import type { TypeRTContext } from '@deepseek-ai/dsh-type-meta' import type { MaybeSnapshotSelectorHook, SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots' import { SlotsService } from './slots.ts' diff --git a/packages/client/runtime/tsconfig.json b/packages/client/runtime/tsconfig.json index 85ba61d41a..efbf7c26d7 100644 --- a/packages/client/runtime/tsconfig.json +++ b/packages/client/runtime/tsconfig.json @@ -21,7 +21,7 @@ "path": "../connection" }, { - "path": "../remotes" + "path": "../../api/remotes" }, { "path": "../../host/apiproxy" diff --git a/packages/client/ui-goal/package.json b/packages/client/ui-goal/package.json index 4c26405bd8..63410707a0 100644 --- a/packages/client/ui-goal/package.json +++ b/packages/client/ui-goal/package.json @@ -25,7 +25,7 @@ "dshClient": { "inject": [ "@deepseek-ai/dsh-client-runtime", - "@deepseek-ai/dsh-client-remotes", + "@deepseek-ai/dsh-api-remotes", "@deepseek-ai/dsh-client-locale", "@deepseek-ai/dsh-client-ui-conversation" ], @@ -38,7 +38,7 @@ "license": "BSD-3-Clause", "peerDependencies": { "@deepseek-ai/dsh-client-locale": "^0.0.1", - "@deepseek-ai/dsh-client-remotes": "^0.0.1", + "@deepseek-ai/dsh-api-remotes": "^0.0.1", "@deepseek-ai/dsh-client-runtime": "^0.0.1", "@deepseek-ai/dsh-client-ui-conversation": "^0.0.1", "@deepseek-ai/dsh-client-ui-primitives": "^0.0.1", @@ -50,7 +50,7 @@ }, "devDependencies": { "@deepseek-ai/dsh-client-locale": "workspace:^", - "@deepseek-ai/dsh-client-remotes": "workspace:^", + "@deepseek-ai/dsh-api-remotes": "workspace:^", "@deepseek-ai/dsh-client-runtime": "workspace:^", "@deepseek-ai/dsh-client-test-runtime": "workspace:^", "@deepseek-ai/dsh-client-ui-conversation": "workspace:^", diff --git a/packages/client/ui-goal/src/client/index.ts b/packages/client/ui-goal/src/client/index.ts index 8fcfd292d2..2e49d5b6b8 100644 --- a/packages/client/ui-goal/src/client/index.ts +++ b/packages/client/ui-goal/src/client/index.ts @@ -10,7 +10,7 @@ */ import type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client' // Type-only: pulls the generated Remote API and ctx.api merge through the Client assembly boundary. -import type {} from '@deepseek-ai/dsh-client-remotes/client' +import type {} from '@deepseek-ai/dsh-api-remotes/client' // Type-only: pulls the ui-conversation SlotMap merge (the input.dock entry). import type {} from '@deepseek-ai/dsh-client-ui-conversation/client' // Type-only: pulls the locale plugin's Context merge (ctx.locale). diff --git a/packages/client/ui-goal/tsconfig.json b/packages/client/ui-goal/tsconfig.json index 2bb4070b18..263dfceb26 100644 --- a/packages/client/ui-goal/tsconfig.json +++ b/packages/client/ui-goal/tsconfig.json @@ -15,7 +15,7 @@ "path": "../locale" }, { - "path": "../remotes" + "path": "../../api/remotes" }, { "path": "../runtime" diff --git a/packages/host/api-gateway/tsdown.config.ts b/packages/host/api-gateway/tsdown.config.ts deleted file mode 100644 index 1f95a1f2c5..0000000000 --- a/packages/host/api-gateway/tsdown.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { clientBundle } from '../../client/tsdown.client.ts' - -export default clientBundle('@deepseek-ai/dsh-host-api-gateway', ['lib/types/index.js', 'lib/types/invariant.js']) diff --git a/packages/host/apiproxy/package.json b/packages/host/apiproxy/package.json index ce740a025f..91d03b4448 100644 --- a/packages/host/apiproxy/package.json +++ b/packages/host/apiproxy/package.json @@ -39,6 +39,7 @@ "license": "BSD-3-Clause", "dependencies": { "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-api-remotes": "workspace:^", "@deepseek-ai/dsh-brand": "workspace:^", "@deepseek-ai/dsh-commands": "workspace:^", "@deepseek-ai/dsh-credentials": "workspace:^", @@ -56,8 +57,6 @@ "@deepseek-ai/dsh-skill": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", - "@deepseek-ai/dsh-type-meta": "workspace:^", - "@deepseek-ai/dsh-typert-registry": "workspace:^", "@deepseek-ai/dsh-user-approval": "workspace:^", "@deepseek-ai/dsh-user-interaction": "workspace:^", "@deepseek-ai/dsh-workspace": "workspace:^", @@ -71,6 +70,8 @@ "devDependencies": { "@deepseek-ai/dsh-storage": "workspace:^", "@deepseek-ai/dsh-storage-domain": "workspace:^", + "@deepseek-ai/dsh-type-meta": "workspace:^", + "@deepseek-ai/dsh-typert-registry": "workspace:^", "cordis": "^4.0.0-rc.7", "@deepseek-ai/dsh-invariants": "workspace:^" } diff --git a/packages/host/apiproxy/src/api-proxy.ts b/packages/host/apiproxy/src/api-proxy.ts index f2c199feca..e4b715a0c4 100644 --- a/packages/host/apiproxy/src/api-proxy.ts +++ b/packages/host/apiproxy/src/api-proxy.ts @@ -19,9 +19,6 @@ import { SessionQueryError, type SessionSearchCursor } from '@deepseek-ai/dsh-se import { SubagentError } from '@deepseek-ai/dsh-subagent' import type { SubagentListEntry as CatalogSubagentListEntry } from '@deepseek-ai/dsh-subagent' import type { Workspace, WorkspaceRecord } from '@deepseek-ai/dsh-workspace' -import { TypeRTLookupFailure } from '@deepseek-ai/dsh-type-meta' -// Type-only: resolves the optional `ctx.typert` lookup-policy composition. -import type {} from '@deepseek-ai/dsh-typert-registry' import { workspaceDomainState, workspaceRecord, WorkspaceId as brandWorkspaceId, WorkspaceMoveInvalidError, WorkspaceUnknownSessionError, @@ -72,6 +69,14 @@ import type { } from '@deepseek-ai/dsh-user-interaction' import { UserInteractionError } from '@deepseek-ai/dsh-user-interaction' import { DirectoryPickerError } from '@deepseek-ai/dsh-host-directory-picker' +import { + ApiRemoteSessionNotFound as SessionNotFound, + ApiRemoteSubagentSessionOwnership as SubagentSessionOwnership, + apiRemoteSubagentOwnershipError, + createApiRemoteAgentResolver, + hasApiRemoteSubagentOwner, + inspectApiRemoteSession, +} from '@deepseek-ai/dsh-api-remotes' import { openNativePath, openNativeTextFile } from './native-path-opener.ts' /** Page size when history is called without maxMessages. */ @@ -666,19 +671,6 @@ async function catalogChild( } } -/** - * Thrown by the cold-resume path when the id names no servable session - * (absent from the store, or a pre-project legacy log without a cwd). - */ -class SessionNotFound extends Error {} - -/** Session identity whose lifecycle belongs to subagent routing, not generic Host resume. */ -class SubagentSessionOwnership extends Error { - constructor(readonly sessionId: SessionId) { - super(`session "${sessionId}" is a subagent session; use subagent delivery`) - } -} - /** Requested identity already belongs to a session with another project cwd. */ class SessionCwdConflict extends Error { constructor( @@ -752,8 +744,6 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro } type WebLlmTargetRef = AgentLlmTargetRef & { current: AgentLlmTarget } const targets = new WeakMap() - /** Implicit resume of cold sessions, deduplicating concurrent calls (follows the jsonrpc sessionCreations precedent). */ - const resumes = new Map>() /** Client-chosen identity creation/resume, deduplicated across concurrent retries. */ const sessionCreations = new Map>() /** Serializes path ownership and explicit title checks with Workspace mutations. */ @@ -811,6 +801,16 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro targetFor(agent) } + const hasSubagentOwner = ( + session: Pick, + agent: Agent | undefined, + ): boolean => hasApiRemoteSubagentOwner(ctx, session, agent) + const subagentOwnershipError = (sessionId: SessionId): RpcError => + apiRemoteSubagentOwnershipError(sessionId) + const inspectServable = (sessionId: SessionId): Promise<{ meta: SessionHeader; events: SessionEvent[] }> => + inspectApiRemoteSession(ctx, sessionId) + const agentFor = createApiRemoteAgentResolver(ctx, { agentOptions, setup: installTarget }) + /** Send one transient frame to every connected mux consumer. */ function broadcast(payload: MuxFrame): void { const envelope = frame(payload) @@ -992,131 +992,6 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro }) } - /** - * Generic Host interaction cannot claim a durably classified subagent - * (`origin: 'subagent'` in the header) or an Agent runtime-owned by its - * live parent. - */ - function hasSubagentOwner( - session: Pick, - agent: Agent | undefined, - ): boolean { - if (session.header.origin === 'subagent') return true - const parentId = session.header.parentSession - if (parentId === undefined || agent === undefined) return false - const parent = ctx.agents.get(parentId) - return parent !== undefined && ctx.agents.isOwnedBy(agent.id, parent) - } - - /** Stable generic-Host error for an identity reserved to subagent routing. */ - function subagentOwnershipError(sessionId: SessionId): RpcError { - return { - code: 'agent-busy', - message: `session "${sessionId}" is owned by subagent routing`, - details: { reason: 'use subagent delivery for this child session' }, - } - } - - /** Inspect one cold served session without repairing, resuming, or publishing it. */ - async function inspectServable(sessionId: SessionId): Promise<{ meta: SessionHeader; events: SessionEvent[] }> { - const persistence = ctx.get('sessionPersistence') - if (persistence === undefined) { - throw new Error('session persistence is not configured (load a dsh-session-persistence backend)') - } - const meta = (await persistence.list()).find(m => m.id === sessionId) - if (meta === undefined || meta.cwd === undefined) throw new SessionNotFound(`session "${sessionId}" not found`) - const inspected = await persistence.inspect(sessionId) - if (inspected.meta.cwd === undefined) throw new SessionNotFound(`session "${sessionId}" not found`) - return { meta: inspected.meta, events: [...inspected.events] } - } - - /** - * Resolve one live registered identity through the subagent-ownership - * fence: subagent-owned agents answer `agent-busy`, plain agents pass. - * Fences the live agent's own session rather than trusting a - * "registered ⇒ attached-store" invariant — a registered subagent whose - * session is ever absent from the attached store must still not be handed - * out through generic Host routing. `undefined` means no live agent. - */ - function fencedLiveAgent(sessionId: SessionId): { agent: Agent } | { error: RpcError } | undefined { - const live = ctx.agents.get(sessionId) - if (live === undefined) return undefined - if (hasSubagentOwner(live.session, live)) return { error: subagentOwnershipError(sessionId) } - return { agent: live } - } - - async function agentFor(sessionId: SessionId): Promise<{ agent: Agent } | { error: RpcError }> { - const fenced = fencedLiveAgent(sessionId) - if (fenced !== undefined) return fenced - const attached = ctx.sessions.get(sessionId) - if (attached !== undefined && hasSubagentOwner(attached, undefined)) { - return { error: subagentOwnershipError(sessionId) } - } - let resume = resumes.get(sessionId) - if (resume === undefined) { - resume = (async () => { - try { - const inspected = await inspectServable(sessionId) - if (hasSubagentOwner({ header: inspected.meta }, undefined)) { - throw new SubagentSessionOwnership(sessionId) - } - const publishedSession = ctx.sessions.get(sessionId) - const publishedAgent = ctx.agents.get(sessionId) - if (publishedSession !== undefined && hasSubagentOwner(publishedSession, publishedAgent)) { - throw new SubagentSessionOwnership(sessionId) - } - const handle = await ctx.agents.resume({ - resumeSessionId: sessionId, - agentOptions: agentOptions(), - setup: installTarget, - }) - return handle.agent - } finally { - resumes.delete(sessionId) - } - })() - resumes.set(sessionId, resume) - } - try { - return { agent: await resume } - } catch (error: unknown) { - if (error instanceof SessionNotFound) { - return { error: { code: 'session-not-found', message: error.message, details: { sessionId } } } - } - if (error instanceof SubagentSessionOwnership) { - return { error: subagentOwnershipError(error.sessionId) } - } - // A concurrent publish can win the identity between the pre-resume - // re-check and `ctx.agents.resume` publication; the ID-collision - // rejection falls through here. Mirror ensureSession's `.catch` in - // full: classify a subagent-owned winner into the stable ownership - // error, and hand a clean plain-agent winner straight back. - const fenced = fencedLiveAgent(sessionId) - if (fenced !== undefined) return fenced - const attached = ctx.sessions.get(sessionId) - if (attached !== undefined && hasSubagentOwner(attached, undefined)) { - return { error: subagentOwnershipError(sessionId) } - } - // The internal details slot is contractually {}; the reason rides the message. - return { error: { code: 'internal', message: `resume failed for session "${sessionId}": ${String(error)}`, details: {} } } - } - } - - // Remote object parameters use the same identity policy as API Proxy methods: - // ordinary cold sessions resume once, while subagent-owned identities retain - // their stable caller-facing rejection. The provider packages continue to - // own wire declarations and live-only defaults; this Host composition owns - // the broader lookup policy. - ctx.inject(['typert'], (typeCtx) => { - const resolveAgent = async (sessionId: SessionId): Promise => { - const found = await agentFor(sessionId) - if ('error' in found) throw new TypeRTLookupFailure(found.error) - return found.agent - } - typeCtx.typert.lookups.configure('agent', resolveAgent) - typeCtx.typert.lookups.configure('session', async sessionId => (await resolveAgent(sessionId)).session) - }) - type SessionReadState = { id: SessionId header: SessionHeader diff --git a/packages/host/apiproxy/tsconfig.json b/packages/host/apiproxy/tsconfig.json index 23c170f4fd..912f2cd794 100644 --- a/packages/host/apiproxy/tsconfig.json +++ b/packages/host/apiproxy/tsconfig.json @@ -23,6 +23,9 @@ { "path": "../../../vendor/schemastery" }, + { + "path": "../../api/remotes" + }, { "path": "../../util/brand" }, @@ -38,12 +41,6 @@ { "path": "../../core/tools" }, - { - "path": "../../typert/type-meta" - }, - { - "path": "../../typert/registry" - }, { "path": "../../session-persistence/session-persistence" }, diff --git a/packages/typert/type-meta/src/index.ts b/packages/typert/type-meta/src/index.ts index 7ded29fa4a..1418c9d7f2 100644 --- a/packages/typert/type-meta/src/index.ts +++ b/packages/typert/type-meta/src/index.ts @@ -41,6 +41,7 @@ export type { InvocationDescriptor, InvocationParameterDescriptor, InvocationSourceLocation, + TypeRTClientApi, TypeRTClientContextBinder, TypeRTCodec, TypeRTContext, diff --git a/packages/typert/type-meta/src/types.ts b/packages/typert/type-meta/src/types.ts index 7831c08e37..b65690115f 100644 --- a/packages/typert/type-meta/src/types.ts +++ b/packages/typert/type-meta/src/types.ts @@ -176,6 +176,16 @@ export interface TypeRTRemoteContribution { readonly descriptors: readonly InvocationDescriptor[] } +/** Client API capability implemented by the Gateway and consumed by Remote assemblies. */ +export interface TypeRTClientApi extends TypeRTRemoteNamespaceMap { + /** + * Mount one generated Host-for-Client contribution in the caller's fiber. + * @param contribution - explicitly selected Remote package artifact. + * @returns disposer withdrawing descriptors and concrete methods together. + */ + mount(contribution: TypeRTRemoteContribution): TypeRTDisposer +} + /** * Resolve one validated wire identity, synchronously or asynchronously. * @param id - validated wire identity. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e0adfb22c1..bd4ff1ea14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -621,6 +621,59 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis + packages/api/gateway: + dependencies: + '@deepseek-ai/dsh-type-meta': + specifier: workspace:^ + version: link:../../typert/type-meta + devDependencies: + '@deepseek-ai/dsh-client-connection': + specifier: workspace:^ + version: link:../../client/connection + '@deepseek-ai/dsh-host-webserver': + specifier: workspace:^ + version: link:../../host/webserver + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-typert-registry': + specifier: workspace:^ + version: link:../../typert/registry + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + zod: + specifier: ^4.4.3 + version: 4.4.3 + + packages/api/remotes: + dependencies: + '@deepseek-ai/dsh-type-meta': + specifier: workspace:^ + version: link:../../typert/type-meta + devDependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-goal': + specifier: workspace:^ + version: link:../../goal/goal + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-session-persistence': + specifier: workspace:^ + version: link:../../session-persistence/session-persistence + '@deepseek-ai/dsh-typert-registry': + specifier: workspace:^ + version: link:../../typert/registry + cordis: + specifier: ^4.0.0-rc.7 + version: link:../../../vendor/cordis + packages/bash/bash: devDependencies: '@deepseek-ai/dsh-invariants': @@ -877,6 +930,9 @@ importers: '@deepseek-ai/dsh-agent-loop': specifier: workspace:^ version: link:../../core/agent-loop + '@deepseek-ai/dsh-api-gateway': + specifier: workspace:^ + version: link:../../api/gateway '@deepseek-ai/dsh-bash-env': specifier: workspace:^ version: link:../../bash/bash-env @@ -916,9 +972,6 @@ importers: '@deepseek-ai/dsh-goal-session': specifier: workspace:^ version: link:../../goal/goal-session - '@deepseek-ai/dsh-host-api-gateway': - specifier: workspace:^ - version: link:../../host/api-gateway '@deepseek-ai/dsh-llm': specifier: workspace:^ version: link:../../llm/llm @@ -1119,6 +1172,9 @@ importers: packages/bundle/web-app: dependencies: + '@deepseek-ai/dsh-api-remotes': + specifier: workspace:^ + version: link:../../api/remotes '@deepseek-ai/dsh-client-connection': specifier: workspace:^ version: link:../../client/connection @@ -1131,9 +1187,6 @@ importers: '@deepseek-ai/dsh-client-modules': specifier: workspace:^ version: link:../../client/modules - '@deepseek-ai/dsh-client-remotes': - specifier: workspace:^ - version: link:../../client/remotes '@deepseek-ai/dsh-client-runtime': specifier: workspace:^ version: link:../../client/runtime @@ -1348,21 +1401,6 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/client/remotes: - devDependencies: - '@deepseek-ai/dsh-goal': - specifier: workspace:^ - version: link:../../goal/goal - '@deepseek-ai/dsh-host-api-gateway': - specifier: workspace:^ - version: link:../../host/api-gateway - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - packages/client/runtime: dependencies: '@deepseek-ai/dsh-client-connection': @@ -1405,9 +1443,9 @@ importers: specifier: ~4.4.7 version: 4.4.7(@types/react@18.3.31)(immer@10.2.0)(react@18.3.1) devDependencies: - '@deepseek-ai/dsh-client-remotes': + '@deepseek-ai/dsh-api-remotes': specifier: workspace:^ - version: link:../remotes + version: link:../../api/remotes '@deepseek-ai/dsh-invariants': specifier: workspace:^ version: link:../../support/invariants @@ -1617,12 +1655,12 @@ importers: packages/client/ui-goal: devDependencies: + '@deepseek-ai/dsh-api-remotes': + specifier: workspace:^ + version: link:../../api/remotes '@deepseek-ai/dsh-client-locale': specifier: workspace:^ version: link:../locale - '@deepseek-ai/dsh-client-remotes': - specifier: workspace:^ - version: link:../remotes '@deepseek-ai/dsh-client-runtime': specifier: workspace:^ version: link:../runtime @@ -3757,36 +3795,14 @@ importers: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis - packages/host/api-gateway: - dependencies: - '@deepseek-ai/dsh-type-meta': - specifier: workspace:^ - version: link:../../typert/type-meta - devDependencies: - '@deepseek-ai/dsh-client-connection': - specifier: workspace:^ - version: link:../../client/connection - '@deepseek-ai/dsh-host-webserver': - specifier: workspace:^ - version: link:../webserver - '@deepseek-ai/dsh-invariants': - specifier: workspace:^ - version: link:../../support/invariants - '@deepseek-ai/dsh-typert-registry': - specifier: workspace:^ - version: link:../../typert/registry - cordis: - specifier: ^4.0.0-rc.7 - version: link:../../../vendor/cordis - zod: - specifier: ^4.4.3 - version: 4.4.3 - packages/host/apiproxy: dependencies: '@deepseek-ai/dsh-agent': specifier: workspace:^ version: link:../../core/agent + '@deepseek-ai/dsh-api-remotes': + specifier: workspace:^ + version: link:../../api/remotes '@deepseek-ai/dsh-brand': specifier: workspace:^ version: link:../../util/brand @@ -3838,12 +3854,6 @@ importers: '@deepseek-ai/dsh-tools': specifier: workspace:^ version: link:../../core/tools - '@deepseek-ai/dsh-type-meta': - specifier: workspace:^ - version: link:../../typert/type-meta - '@deepseek-ai/dsh-typert-registry': - specifier: workspace:^ - version: link:../../typert/registry '@deepseek-ai/dsh-user-approval': specifier: workspace:^ version: link:../../ui/user-approval @@ -3869,6 +3879,12 @@ importers: '@deepseek-ai/dsh-storage-domain': specifier: workspace:^ version: link:../../storage/storage-domain + '@deepseek-ai/dsh-type-meta': + specifier: workspace:^ + version: link:../../typert/type-meta + '@deepseek-ai/dsh-typert-registry': + specifier: workspace:^ + version: link:../../typert/registry cordis: specifier: ^4.0.0-rc.7 version: link:../../../vendor/cordis diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts index 088329e83d..54581a8605 100644 --- a/scripts/gen-cordis-catalog.ts +++ b/scripts/gen-cordis-catalog.ts @@ -289,7 +289,7 @@ export const TYPE_LINK_EXEMPTIONS: Readonly> = { ThemeTokens: 'service-local token dictionary is owned by packages/client/ui-theme/src/index.ts', Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts', InvariantRegistration: 'service-local lifecycle handle is owned by packages/support/invariants/README.md', - InvokeRemoteRequest: 'gateway invocation contract is owned by packages/host/api-gateway/README.md', + InvokeRemoteRequest: 'gateway invocation contract is owned by packages/api/gateway/README.md', PresetOption: 'deployment menu metadata is owned by packages/ui/permission/README.md', PresetSpec: 'deployment preset composition is owned by packages/ui/permission/README.md', KnobState: 'projection unit state shape is owned by packages/ui/permission/README.md', diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index 6d6a76e476..9adc3d9768 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -601,7 +601,7 @@ function builtBinSmokeGate(needs: string[] = ['build']): Gate { 'packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts', 'packages/subagent/subagent-codex/tests/loader-composition.e2e.ts', 'packages/subagent/subagent-claude-code/tests/loader-composition.e2e.ts', - 'packages/client/remotes/tests/built-lib.e2e.ts', + 'packages/api/remotes/tests/built-lib.e2e.ts', // The worker-entry packages' built bundles: the only automated proof // that lib/index.js resolves its sibling lib/worker.cjs under plain node // (the e2e lane runs unbuilt, so these files self-skip there). diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json index edadc3f134..ecb13f167c 100644 --- a/scripts/type-equiv.manifest.json +++ b/scripts/type-equiv.manifest.json @@ -1538,22 +1538,22 @@ { "doc": "docs/core-data-structures/typert.md", "symbol": "InvokeRemoteRequest", - "source": "packages/host/api-gateway/src/types.ts" + "source": "packages/api/gateway/src/types.ts" }, { "doc": "docs/core-data-structures/typert.md", "symbol": "TypertGatewayErrorCode", - "source": "packages/host/api-gateway/src/types.ts" + "source": "packages/api/gateway/src/types.ts" }, { "doc": "docs/core-data-structures/typert.md", "symbol": "TypertGateway", - "source": "packages/host/api-gateway/src/types.ts" + "source": "packages/api/gateway/src/types.ts" }, { "doc": "docs/core-data-structures/typert.md", - "symbol": "ClientApi", - "source": "packages/host/api-gateway/src/client/index.ts" + "symbol": "TypeRTClientApi", + "source": "packages/typert/type-meta/src/types.ts" } ] } diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 78745dbed1..e131d6c403 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -57,7 +57,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/client/web-react': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' }, 'packages/client/schema-form': { kind: 'none', reason: 'Browser-side form-rendering library; registers no model surface.' }, 'packages/client/connection': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' }, - 'packages/client/remotes': { kind: 'none', reason: 'Client-side Remote assembly; selected business methods own any model-visible effect.' }, + 'packages/api/remotes': { kind: 'none', reason: 'The Remote BFF selects business methods and identity policy; selected services own any model-visible effect.' }, 'packages/client/runtime': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' }, 'packages/client/ui-layout': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' }, 'packages/client/ui-sidebar': { kind: 'none', reason: 'Browser-side UI plugin layer; registers no model surface.' }, @@ -126,7 +126,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly> = { 'packages/support/loader-smoke': { kind: 'none', reason: 'The test harness observes child-process streams without changing live requests.' }, 'packages/support/llm-mock-server': { kind: 'none', reason: 'The test server substitutes provider wire behavior without invoking a real model.' }, 'packages/support/llm-replay': { kind: 'none', reason: 'The keyless adapter invokes no provider model.' }, - 'packages/host/api-gateway': { kind: 'none', reason: 'Remote dispatch infrastructure; invoked business methods own any model-visible effect.' }, + 'packages/api/gateway': { kind: 'none', reason: 'Remote dispatch infrastructure; invoked business methods own any model-visible effect.' }, 'packages/typert/type-meta': { kind: 'none', reason: 'Compiler-independent Remote protocol declarations; registers no model surface.' }, 'packages/typert/generator': { kind: 'none', reason: 'The build-time generator runs outside any agent runtime and touches no model request.' }, 'packages/tasks/tasks': { kind: 'indirect', reason: 'Producer and control-surface plugins own all model rendering over the task registry.' }, diff --git a/tsconfig.base.json b/tsconfig.base.json index ce4fca35f9..b9907348fa 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -41,10 +41,10 @@ "@deepseek-ai/dsh-invariants": ["./packages/support/invariants/src/index.ts"], "@deepseek-ai/dsh-typert-registry": ["./packages/typert/registry/src/index.ts"], "@deepseek-ai/dsh-typert-registry/client": ["./packages/typert/registry/src/client/index.ts"], - "@deepseek-ai/dsh-host-api-gateway": ["./packages/host/api-gateway/src/index.ts"], - "@deepseek-ai/dsh-host-api-gateway/client": ["./packages/host/api-gateway/src/client/index.ts"], - "@deepseek-ai/dsh-host-api-gateway/invariant": ["./packages/host/api-gateway/src/invariant.ts"], - "@deepseek-ai/dsh-host-api-gateway/types": ["./packages/host/api-gateway/src/types.ts"], + "@deepseek-ai/dsh-api-gateway": ["./packages/api/gateway/src/index.ts"], + "@deepseek-ai/dsh-api-gateway/client": ["./packages/api/gateway/src/client/index.ts"], + "@deepseek-ai/dsh-api-gateway/invariant": ["./packages/api/gateway/src/invariant.ts"], + "@deepseek-ai/dsh-api-gateway/types": ["./packages/api/gateway/src/types.ts"], "@deepseek-ai/dsh-type-meta": ["./packages/typert/type-meta/src/index.ts"], "@deepseek-ai/dsh-type-meta/types": ["./packages/typert/type-meta/src/types.ts"], "@deepseek-ai/dsh-typert-loader": ["./packages/typert/loader/src/index.ts"], @@ -151,8 +151,8 @@ "@deepseek-ai/dsh-client-schema-form/invariant": ["./packages/client/schema-form/src/invariant.ts"], "@deepseek-ai/dsh-client-web-react": ["./packages/client/web-react/src"], "@deepseek-ai/dsh-client-connection": ["./packages/client/connection/src"], - "@deepseek-ai/dsh-client-remotes": ["./packages/client/remotes/src"], - "@deepseek-ai/dsh-client-remotes/client": ["./packages/client/remotes/src/client/index.ts"], + "@deepseek-ai/dsh-api-remotes": ["./packages/api/remotes/src"], + "@deepseek-ai/dsh-api-remotes/client": ["./packages/api/remotes/src/client/index.ts"], "@deepseek-ai/dsh-client-hmr": ["./packages/client/hmr/src"], "@deepseek-ai/dsh-client-modules": ["./packages/client/modules/src"], "@deepseek-ai/dsh-client-runtime": ["./packages/client/runtime/src"], diff --git a/tsconfig.client.json b/tsconfig.client.json index 327b337963..9821c0e41b 100644 --- a/tsconfig.client.json +++ b/tsconfig.client.json @@ -52,8 +52,8 @@ { "path": "./packages/client/hmr" }, { "path": "./packages/client/connection" }, { "path": "./packages/typert/registry" }, - { "path": "./packages/host/api-gateway" }, - { "path": "./packages/client/remotes" }, + { "path": "./packages/api/gateway" }, + { "path": "./packages/api/remotes" }, { "path": "./packages/client/runtime" }, { "path": "./packages/client/test-runtime" }, { "path": "./packages/client/ui-layout" }, diff --git a/tsconfig.host.json b/tsconfig.host.json index 37c20c0d5c..6884839536 100644 --- a/tsconfig.host.json +++ b/tsconfig.host.json @@ -102,7 +102,7 @@ { "path": "./packages/core/scope" }, { "path": "./packages/typert/type-meta" }, { "path": "./packages/typert/registry" }, - { "path": "./packages/host/api-gateway" }, + { "path": "./packages/api/gateway" }, { "path": "./packages/typert/loader" }, { "path": "./packages/session-persistence/session-persistence" }, { "path": "./packages/session-persistence/session-checkpoint-policy" }, diff --git a/vitest.config.ts b/vitest.config.ts index 56a5a1575b..f5a86ac7a9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -181,8 +181,8 @@ export default defineConfig({ 'packages/client/connection/src/http-bridge.ts', // This assembly imports generated Host-for-Client code that exists // only in lib; the post-build built-bin smoke executes both entries. - 'packages/client/remotes/src/index.ts', - 'packages/client/remotes/src/client/index.ts', + 'packages/api/remotes/src/index.ts', + 'packages/api/remotes/src/client/index.ts', // Slash/command/input round: per-file gaps deferred with the same // client-lane debt. TODO(gui): cover and remove with the lane above. 'packages/client/connection/src/client/fixture.ts',