feat: add TypeRT remote gateway infrastructure

This commit is contained in:
imccyu
2026-08-05 11:17:47 +08:00
parent effd8e1ebd
commit 64a963da0b
98 changed files with 7812 additions and 444 deletions

View File

@@ -15,12 +15,17 @@
"types": "./lib/types/invariant.d.ts",
"default": "./lib/invariant.js"
},
"./types": {
"types": "./lib/types/types.d.ts",
"default": "./lib/types/types.js"
},
"./src/*": "./src/*",
"./package.json": "./package.json"
},
"files": [
"lib/index.js",
"lib/invariant.js",
"lib/types/**/*.js",
"lib/types/**/*.d.ts"
],
"license": "BSD-3-Clause",
@@ -30,6 +35,7 @@
"@deepseek-ai/dsh-scope": "^0.0.1",
"@deepseek-ai/dsh-session": "^0.0.1",
"@deepseek-ai/dsh-system-prompt": "^0.0.1",
"@deepseek-ai/dsh-type-meta": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"devDependencies": {
@@ -38,6 +44,8 @@
"@deepseek-ai/dsh-scope": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-system-prompt": "workspace:^",
"@deepseek-ai/dsh-type-meta": "workspace:^",
"@deepseek-ai/dsh-typert-registry": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
}

View File

@@ -12,6 +12,7 @@ import { isPromise } from 'node:util/types'
import { scopeTarget } from '@deepseek-ai/dsh-scope'
import type { Scoped } from '@deepseek-ai/dsh-scope'
import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session'
import type { TypeRTContext, TypeRTLookup } from '@deepseek-ai/dsh-type-meta'
import type { Agent, AgentOptions } from './types.ts'
export * from './types.ts'
@@ -20,6 +21,16 @@ export * from './llm-target.ts'
export { agentCarrier, agentEvents, assembleContextFor, emitAgentEvent } from './dispatch.ts'
export type { AgentEventDispatch, AgentSubjectEvent } from './dispatch.ts'
declare module '@deepseek-ai/dsh-type-meta' {
interface TypeRTLookupMap {
agent: TypeRTLookup<Agent, SessionId>
}
interface TypeRTContextMap {
agent: TypeRTContext<SessionId>
}
}
declare module 'cordis' {
interface Context {
agents: AgentRegistry
@@ -251,6 +262,20 @@ export class AgentRegistry extends Service {
constructor(ctx: Context) {
super(ctx, 'agents')
ctx.inject(['typert'], (typeCtx) => {
typeCtx.typert.lookups.register('agent', {
parameter: 'agent',
wire: 'agentId',
hostTypeSymbol: '@deepseek-ai/dsh-agent#Agent',
wireTypeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
resolve: sessionId => this.get(sessionId),
})
typeCtx.typert.contexts.registerHost('agent', {
wire: 'agentId',
wireTypeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
resolve: sessionId => this.get(sessionId)?.ctx,
})
})
// The `ctx.agent` DX accessor: default `undefined` on every context, so a
// plain plugin context reads cleanly instead of hitting the Cordis
// unknown-property throw. Each Agent.ctx shadows it with an own property

View File

@@ -6,6 +6,7 @@ import AgentRegistry, {
agentEvents,
Inbox,
} from '@deepseek-ai/dsh-agent'
import TypertRegistry from '@deepseek-ai/dsh-typert-registry'
import type {
Agent,
@@ -142,6 +143,31 @@ describe('Inbox', () => {
})
describe('AgentRegistry', () => {
it('contributes Agent lookup and scoped Context providers while TypeRT is live', async () => {
const ctx = new Context()
const agentFiber = ctx.plugin(AgentRegistry)
await agentFiber
await ctx.plugin(TypertRegistry)
const agent = stubAgent('remote-agent')
const disposeAgent = ctx.agents.register(agent)
const lookup = ctx.typert.lookups.get('agent')
expect(lookup).toMatchObject({
parameter: 'agent',
wire: 'agentId',
hostTypeSymbol: '@deepseek-ai/dsh-agent#Agent',
wireTypeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
})
expect(lookup?.resolve(agent.id)).toBe(agent)
expect(ctx.typert.contexts.getHost('agent')?.resolve(agent.id)).toBe(agent.ctx)
disposeAgent()
expect(lookup?.resolve(agent.id)).toBeUndefined()
await agentFiber.dispose()
expect(ctx.typert.lookups.get('agent')).toBeUndefined()
expect(ctx.typert.contexts.getHost('agent')).toBeUndefined()
})
it('registers exact entries, emits lifecycle events, and unregisters on owner disposal', async () => {
const ctx = new Context()
await ctx.plugin(AgentRegistry)

View File

@@ -28,6 +28,9 @@
},
{
"path": "../../support/invariants"
},
{
"path": "../../typert/type-meta"
}
]
}

View File

@@ -38,6 +38,7 @@
"@deepseek-ai/dsh-invariants": "^0.0.1",
"@deepseek-ai/dsh-llm": "^0.0.1",
"@deepseek-ai/dsh-scope": "^0.0.1",
"@deepseek-ai/dsh-type-meta": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"devDependencies": {
@@ -45,6 +46,8 @@
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-scope": "workspace:^",
"@deepseek-ai/dsh-type-meta": "workspace:^",
"@deepseek-ai/dsh-typert-registry": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
}

View File

@@ -13,6 +13,7 @@ import { scopeOf, scopeTarget } from '@deepseek-ai/dsh-scope'
import type { Scoped } from '@deepseek-ai/dsh-scope'
import type { Message } from '@deepseek-ai/dsh-llm'
import { SESSION_FORMAT_VERSION, SessionId } from './types.ts'
import type { TypeRTLookup } from '@deepseek-ai/dsh-type-meta'
import type { CreateSessionOptions, EpochHeader, PrepareSessionOptions, RequestContext, SessionEvent, SessionEventMap, SessionEventType, SessionHeader, SurfaceIntent, SurfaceEventType } from './types.ts'
import { snapshotJsonValue } from './json.ts'
import { deriveEventMessage, SurfaceManager } from './surface.ts'
@@ -105,6 +106,12 @@ declare module 'cordis' {
}
}
declare module '@deepseek-ai/dsh-type-meta' {
interface TypeRTLookupMap {
session: TypeRTLookup<Session, SessionId>
}
}
/** Validate and freeze one detached creation header in place. */
function validateSessionHeader(id: SessionId, input: unknown): SessionHeader {
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
@@ -803,6 +810,15 @@ export class SessionStore extends Service {
constructor(ctx: Context) {
super(ctx, 'sessions')
ctx.inject(['typert'], (typeCtx) => {
typeCtx.typert.lookups.register('session', {
parameter: 'session',
wire: 'sessionId',
hostTypeSymbol: '@deepseek-ai/dsh-session#Session',
wireTypeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
resolve: sessionId => this.get(sessionId),
})
})
}
/**

View File

@@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
import TypertRegistry from '@deepseek-ai/dsh-typert-registry'
describe('Session TypeRT provider', () => {
it('contributes live Session lookup in either service load order', async () => {
const ctx = new Context()
const sessionFiber = ctx.plugin(SessionStore)
await sessionFiber
await ctx.plugin(TypertRegistry)
const session = ctx.sessions.create(SessionId('remote-session'))
const lookup = ctx.typert.lookups.get('session')
expect(lookup).toMatchObject({
parameter: 'session',
wire: 'sessionId',
hostTypeSymbol: '@deepseek-ai/dsh-session#Session',
wireTypeSymbol: '@deepseek-ai/dsh-session/types#SessionId',
})
expect(lookup?.resolve(session.id)).toBe(session)
await sessionFiber.dispose()
expect(ctx.typert.lookups.get('session')).toBeUndefined()
})
})

View File

@@ -25,6 +25,9 @@
},
{
"path": "../../support/invariants"
},
{
"path": "../../typert/type-meta"
}
]
}