refactor(api): colocate gateway and remote assembly

This commit is contained in:
imccyu
2026-08-07 15:48:29 +08:00
parent d941350227
commit bb61dc13f2
82 changed files with 645 additions and 432 deletions

View File

@@ -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

View File

@@ -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`.

View File

@@ -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 设置。

View File

@@ -0,0 +1,64 @@
{
"name": "@deepseek-ai/dsh-api-remotes",
"description": "Remote BFF assembly and Host Agent/Session lookup policy",
"version": "0.0.1",
"private": true,
"type": "module",
"main": "lib/index.js",
"types": "lib/types/index.d.ts",
"exports": {
".": {
"types": "./lib/types/index.d.ts",
"default": "./lib/index.js"
},
"./invariant": {
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./client": {
"types": "./lib/types/client/index.d.ts",
"default": "./lib/client.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"dshClient": {
"inject": [
"@deepseek-ai/dsh-api-gateway"
],
"platform": "web",
"immediately": true
},
"scripts": {
"bundle": "tsdown",
"watch": "tsdown --watch"
},
"license": "BSD-3-Clause",
"files": [
"lib/index.js",
"lib/invariant.js",
"lib/client.js",
"lib/types/**/*.d.ts"
],
"dependencies": {
"@deepseek-ai/dsh-type-meta": "workspace:^"
},
"peerDependencies": {
"@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-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"
}
}

View File

@@ -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<never, never> }
/** 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<Session, 'header'>,
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<ApiRemoteAgentResult> {
const resumes = new Map<SessionId, Promise<Agent>>()
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<ApiRemoteAgentResult> => {
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<Agent> => {
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
}

View File

@@ -0,0 +1,26 @@
/** Platform-neutral assembly of generated Host Remote contributions. */
import type { Context } from 'cordis'
import goalsRemote from '@deepseek-ai/dsh-goal/remote'
import type { TypeRTClientApi } from '@deepseek-ai/dsh-type-meta'
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']
/**
* Mount the Host capabilities explicitly selected for this Client assembly.
* @param ctx - Client Cordis root carrying the typed API service.
*/
export function apply(ctx: Context): void {
ctx.api.mount(goalsRemote)
}

View File

@@ -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 {}

View File

@@ -0,0 +1,24 @@
/** 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-api-remotes'
/** Cordis companion plugin name. */
export const name = 'api-remotes-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/** No runtime invariant: TypeRT and the Agent/Session registries own the observed relationships. */
const install: InvariantInstaller = () => {}
/**
* Register this package's invariant companion.
* @param ctx - Cordis context carrying the invariant service.
* @returns the installed registration's disposer after setup succeeds.
*/
export const apply = (ctx: Context): Promise<() => void> =>
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
/* jscpd:ignore-end */

View File

@@ -0,0 +1,224 @@
import { execFile } from 'node:child_process'
import { existsSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { describe, expect, it } from 'vitest'
/**
* Built-artifact smoke for the first generated Remote: plain Node boots the
* Host and Browser bundle handoffs, then crosses the shared `/api` HTTP route.
*/
const packageDir = fileURLToPath(new URL('..', import.meta.url))
const root = resolve(packageDir, '../../..')
const artifact = (path: string): string => join(root, path)
const artifactUrl = (path: string): string => pathToFileURL(artifact(path)).href
const requiredArtifacts = [
'packages/client/connection/lib/client.js',
'packages/client/connection/lib/index.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/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)))
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/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/api/remotes/lib/client.js',
session: 'packages/core/session/lib/index.js',
}).map(([key, path]) => [key, artifactUrl(path)]))
const script = `
import { createServer } from 'node:http'
import * as cordis from 'cordis'
const urls = ${JSON.stringify(urls)}
const { Context } = cordis
const { default: AgentRegistry } = await import(urls.agent)
const connectionHost = await import(urls.connectionHost)
const { default: TypertGatewayService } = await import(urls.apiGatewayHost)
const { default: GoalService } = await import(urls.goal)
const { TYPERT } = await import(urls.goalTypert)
const { default: TypertRegistry } = await import(urls.registryHost)
const { Session, SessionId } = await import(urls.session)
const routes = []
const host = new Context()
host.provide('httpServer', {
register(route) {
routes.push(route)
return () => { routes.splice(routes.indexOf(route), 1) }
},
tapIndex() { return () => {} },
port: 0,
})
await host.plugin({ inject: connectionHost.inject, apply: connectionHost.apply })
await host.plugin(TypertRegistry)
await host.plugin(AgentRegistry)
await host.plugin(TypertGatewayService)
await host.plugin(GoalService)
host.typert.register(TYPERT)
const makeAgent = rawId => {
const session = new Session(SessionId(rawId))
return {
id: session.id,
options: {},
session,
ctx: host.extend(),
status: 'idle',
acceptsNextStep: false,
send() {},
updateInbox() { return 'not-found' },
followup() {},
steer() { return { outcome: Promise.resolve({ status: 'rejected' }) } },
inject(input) { session.append('user/message', input, { surfaceOp: 'append' }) },
reserveTurnAdmission() {},
cancel() {},
whenIdle() { return Promise.resolve() },
}
}
const rootAgent = makeAgent('built-root-agent')
const scopedAgent = makeAgent('built-scoped-agent')
host.agents.register(rootAgent)
host.agents.register(scopedAgent)
if (routes.length !== 1 || routes[0].path !== '/api') {
throw new Error('Connection did not register exactly one /api route')
}
const server = createServer((request, response) => { void routes[0].handler(request, response) })
await new Promise(resolveListen => server.listen(0, '127.0.0.1', resolveListen))
const address = server.address()
if (address === null || typeof address === 'string') throw new Error('HTTP server has no TCP address')
const origin = 'http://127.0.0.1:' + String(address.port)
const handoffs = new Map()
globalThis.window = {
__ModuleLoader__: {
load(handoff) { handoffs.set(handoff.id, handoff) },
},
}
globalThis.location = { hostname: '127.0.0.1', origin, search: '' }
await import(urls.registryClient)
await import(urls.connectionClient)
await import(urls.apiGatewayClient)
await import(urls.remotesClient)
const instantiate = id => {
const handoff = handoffs.get(id)
if (handoff === undefined) throw new Error('missing Client bundle handoff ' + id)
return handoff.factory(specifier => {
if (specifier === 'cordis') return cordis
throw new Error('unexpected Client external ' + specifier)
})
}
const client = new Context()
for (const id of [
'@deepseek-ai/dsh-typert-registry',
'@deepseek-ai/dsh-client-connection',
'@deepseek-ai/dsh-api-gateway',
'@deepseek-ai/dsh-api-remotes',
]) {
const plugin = instantiate(id)
await client.plugin({ inject: plugin.inject, apply: plugin.apply })
}
client.typert.contexts.registerClient('agent', {
identity: candidate => candidate.builtAgentId,
})
let invalidRejected = false
try {
await client.api.goals.create(rootAgent.id, { objective: 1 })
} catch {
invalidRejected = true
}
const rootResult = await client.api.goals.create(rootAgent.id, { objective: 'root goal' })
const rootEdit = await client.api.goals.edit(
rootAgent.id,
rootResult.ref,
{ objective: 'edited root goal' },
)
const agentContext = client.extend({ builtAgentId: scopedAgent.id })
const scopedResult = await agentContext.goals.create({ objective: 'scoped goal', maxGoalRounds: 3 })
const result = {
invalidRejected,
rootResult,
rootEdit,
scopedResult,
rootGoal: host.goals.get(rootAgent)?.objective,
scopedGoal: host.goals.get(scopedAgent)?.objective,
rootEvents: rootAgent.session.events.length,
scopedEvents: scopedAgent.session.events.length,
}
await client.fiber.dispose()
await new Promise((resolveClose, rejectClose) => server.close(error => {
if (error === undefined) resolveClose()
else rejectClose(error)
}))
await host.fiber.dispose()
console.log(JSON.stringify(result))
`
const result = await runPlainNode(script)
expect(result.exitCode, `stderr:\n${result.stderr}`).toBe(0)
const output = JSON.parse(result.stdout.trim().split('\n').at(-1) ?? '{}') as {
invalidRejected: boolean
rootResult: { ref: { id: string; revision: number } }
rootEdit: { objective: string; revision: number }
scopedResult: { ref: { id: string; revision: number } }
rootGoal: string
scopedGoal: string
rootEvents: number
scopedEvents: number
}
expect(output).toMatchObject({
invalidRejected: true,
rootResult: { ref: { revision: 1 } },
rootEdit: { objective: 'edited root goal', revision: 2 },
scopedResult: { ref: { revision: 1 } },
rootGoal: 'edited root goal',
scopedGoal: 'scoped goal',
rootEvents: 2,
scopedEvents: 1,
})
expect(output.rootResult.ref.id).toMatch(/^goal-/)
expect(output.scopedResult.ref.id).toMatch(/^goal-/)
}, 60_000)
})
/** Execute one ESM script without tsx or a TypeScript loader. */
function runPlainNode(script: string): Promise<{
readonly exitCode: number | null
readonly stdout: string
readonly stderr: string
}> {
return new Promise((resolveRun) => {
execFile(process.execPath, ['--input-type=module', '-e', script], {
cwd: packageDir,
encoding: 'utf8',
timeout: 55_000,
}, (error, stdout, stderr) => {
resolveRun({
exitCode: error === null ? 0 : typeof error.code === 'number' ? error.code : null,
stdout,
stderr,
})
})
})
}

View File

@@ -0,0 +1,42 @@
{
"extends": "../../../tsconfig.base.client.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib/types"
},
"include": [
"src"
],
"references": [
{
"path": "../../../vendor/cordis"
},
{
"path": "../../core/agent"
},
{
"path": "../../core/session"
},
{
"path": "../../session-persistence/session-persistence"
},
{
"path": "../../typert/type-meta"
},
{
"path": "../../typert/registry"
},
{
"path": "../../ui/commands"
},
{
"path": "../../goal/goal"
},
{
"path": "../../session-title/session-title"
},
{
"path": "../../support/invariants"
}
]
}

View File

@@ -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'])