Merge commit 'refs/codex/pr885/master-20260730' into worktree/retarget-pr885-20260729

# Conflicts:
#	eslint.config.mjs
This commit is contained in:
Tianyi Cui
2026-07-30 01:18:28 +08:00
122 changed files with 16714 additions and 2564 deletions
@@ -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/typert/registry/README.md
README.md: 83c03ab284abf2b7cab4dd1ee70d7e855184a1e0
README.zh.md: 6ef8b22805e21a379b4c2fac4bf9fbae447c41a1
+31
View File
@@ -0,0 +1,31 @@
# @deepseek-ai/dsh-typert-registry
English | [中文](README.zh.md)
Runtime registry for generated Typert artifacts. A contribution carries one package face's business reflection and optional live Zod schemas; `ctx.typert` registers both atomically and withdraws them with the calling Cordis fiber. TypeScript analysis and code generation live in [`dsh-typert-generator`](../generator/README.md).
Package reflection is keyed by `<package>#<face>`. Schemas are keyed by `<package>#<name>` and retain the producer's Zod instance. JSON Schema is computed on demand at the consumer edge.
## Public API
- `TypertRegistry` is the default plugin and provides `ctx.typert`.
- `register(contribution)` rejects malformed identities and duplicate package-face or schema keys before committing anything, then returns the exact Cordis effect disposer.
- `get(key)`, `resolve(key)`, and `list(filter?)` query live schemas. `resolve()` distinguishes a malformed key, an absent package, and a package that contributes no schema under that name.
- `getPackage(packageName, face?)` and `listPackages(filter?)` query generated service, event, and object reflection; the default face is `host`.
- `toJSONSchema(key, params?)` projects a live schema with `z.toJSONSchema()` without caching the result.
- `typertKey()` and `typertPackageKey()` compose the two stable identity forms.
The `@deepseek-ai/dsh-typert-registry/types` subpath contains the pure contribution and record contracts. [`dsh-typert-loader`](../loader/README.md) discovers and registers generated host artifacts in Loader compositions; direct `ctx.typert.register()` supports other composition owners.
## Model Experience
None, as the registry contributes no prompt, tool, or session event; consumers such as `cordis_inspect` own any model-visible projection.
#### KV Cache effect
No direct effect. A consumer that places reflection in a request owns the resulting prefix change.
## Known Limitations and Deferred Work
- The registry stores generated reflection but does not merge host and client graphs or resolve TypeScript references. Those are analyzer and emitter concerns.
- Schema keys omit the face because host and client run in separate contexts. Registering same-named schemas from both faces into one context is rejected as a duplicate.
+31
View File
@@ -0,0 +1,31 @@
# @deepseek-ai/dsh-typert-registry
[English](README.md) | 中文
生成的 Typert 产物所用的运行时注册表。每个注册项包含某个包(package)在一个 face 上的业务反射信息,以及可选的运行时 Zod schema;`ctx.typert` 会以原子方式同时注册两者,并在发起调用的 Cordis fiber 释放时一并移除它们。TypeScript 分析和代码生成由 [`dsh-typert-generator`](../generator/README.md) 负责。
包反射信息以 `<package>#<face>` 为键。schema 以 `<package>#<name>` 为键,并保留生成方的 Zod 实例。系统按需在消费方边界计算 JSON Schema。
## 公开 API
- `TypertRegistry` 是默认插件,并提供 `ctx.typert`。
- `register(contribution)` 会在提交任何内容之前拒绝格式错误的标识,以及重复的包与 face 组合键或 schema 键,随后返回 Cordis effect 提供的同一资源释放函数。
- `get(key)`、`resolve(key)` 和 `list(filter?)` 查询当前有效的 schema。`resolve()` 能区分格式错误的键、未注册的包,以及已注册但未以该名称提供 schema 的包。
- `getPackage(packageName, face?)` 和 `listPackages(filter?)` 查询生成的服务、事件和对象反射信息;默认 face 为 `host`。
- `toJSONSchema(key, params?)` 使用 `z.toJSONSchema()` 投影当前有效的 schema,且不缓存结果。
- `typertKey()` 和 `typertPackageKey()` 构造两种稳定的标识形式。
`@deepseek-ai/dsh-typert-registry/types` 子路径包含注册项和记录的纯类型契约。[`dsh-typert-loader`](../loader/README.md) 会在 Loader 组合中发现并注册生成的宿主侧产物;其他组合所有者可以直接调用 `ctx.typert.register()`。
## 模型体验
无。注册表不会提供提示词、工具或会话事件;所有模型可见投影均由 `cordis_inspect` 等消费方负责。
#### KV Cache 影响
无直接影响。将反射信息放入请求的消费方负责由此产生的前缀变化。
## 已知限制与暂缓工作
- 注册表存储生成的反射信息,但不会合并宿主侧与客户端侧的图,也不会解析 TypeScript 引用;这些由分析器和产物输出器负责。
- schema 键不包含 face,因为宿主侧和客户端侧在不同的上下文中运行。若在同一上下文中注册来自两个 face 的同名 schema,系统会将其作为重复项拒绝。
+45
View File
@@ -0,0 +1,45 @@
{
"name": "@deepseek-ai/dsh-typert-registry",
"description": "Runtime registry for generated package reflection and Zod schemas",
"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"
},
"./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",
"lib/types/**/*.d.ts.map",
"src"
],
"license": "BSD-3-Clause",
"dependencies": {
"zod": "^4.4.3"
},
"peerDependencies": {
"@deepseek-ai/dsh-invariants": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"devDependencies": {
"@deepseek-ai/dsh-invariants": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
}
+219
View File
@@ -0,0 +1,219 @@
/**
* Runtime registry for generated Typert contributions. It owns live Zod
* instances and generated package reflection, but performs no TypeScript
* analysis or schema generation.
* @module @deepseek-ai/dsh-typert-registry
*/
import { Context, Service } from 'cordis'
import { z } from 'zod'
import type {
TypertContribution,
TypertFace,
TypertPackageFilter,
TypertPackageRecord,
TypertSchemaFilter,
TypertSchemaRecord,
} from './types.ts'
export type {
TypertContribution,
TypertDocTag,
TypertDocumentation,
TypertEventModel,
TypertFace,
TypertMemberModel,
TypertObjectModel,
TypertPackageFilter,
TypertPackageModel,
TypertPackageRecord,
TypertSchema,
TypertSchemaFilter,
TypertSchemaRecord,
TypertServiceModel,
TypertTypeModel,
} from './types.ts'
declare module 'cordis' {
interface Context {
typert: TypertRegistry
}
}
/**
* Compose the global key of one generated schema.
* @param packageName - contributing npm package.
* @param name - schema export name.
* @returns `<package>#<name>`.
*/
export function typertKey(packageName: string, name: string): string {
return `${packageName}#${name}`
}
/**
* Compose the identity of one package-face model.
* @param packageName - contributing npm package.
* @param face - independently compiled face.
* @returns `<package>#<face>`.
*/
export function typertPackageKey(packageName: string, face: TypertFace): string {
return `${packageName}#${face}`
}
/**
* Registry of generated schemas and package reflection.
* @typert service
*/
export class TypertRegistry extends Service {
private readonly schemas = new Map<string, TypertSchemaRecord>()
private readonly packages = new Map<string, TypertPackageRecord>()
constructor(ctx: Context) {
super(ctx, 'typert')
}
/**
* Register one generated contribution atomically for the calling fiber.
* Duplicate package-face identities or schema keys reject the whole batch.
* @param contribution - generated schemas and package metadata.
* @returns the exact effect disposer that removes this contribution.
*/
register(contribution: TypertContribution): () => void {
const packageRecord = this.validatePackage(contribution)
const schemaRecords = this.validateSchemas(contribution)
const { schemas, packages } = this
const dispose = this.ctx.effect(function* () {
packages.set(packageRecord.key, packageRecord)
for (const record of schemaRecords) schemas.set(record.key, record)
yield () => {
packages.delete(packageRecord.key)
for (const record of schemaRecords) schemas.delete(record.key)
}
}, 'typert.register()')
// oxlint-disable-next-line typescript/no-misused-promises -- synchronous cleanup; preserve Cordis disposer identity
return dispose
}
/**
* Look up one schema by `<package>#<name>`.
* @param key - global schema key.
* @returns the live schema record, or `undefined` when absent.
*/
get(key: string): TypertSchemaRecord | undefined {
return this.schemas.get(key)
}
/**
* Resolve one required schema.
* @param key - global schema key.
* @returns the live schema record.
* @throws when the key is malformed, the package face is absent, or the schema is not contributed.
*/
resolve(key: string): TypertSchemaRecord {
const record = this.schemas.get(key)
if (record !== undefined) return record
const hash = key.indexOf('#')
if (hash <= 0 || hash === key.length - 1) {
throw new Error(`typert: invalid schema key "${key}" — expected "<package>#<name>"`)
}
const packageName = key.slice(0, hash)
if ([...this.packages.values()].some(candidate => candidate.package === packageName)) {
throw new Error(
`typert: cannot resolve "${key}" — package "${packageName}" is registered but contributes no schema named "${key.slice(hash + 1)}"`,
)
}
throw new Error(`typert: cannot resolve "${key}" — package "${packageName}" has no registered contribution`)
}
/**
* Enumerate live schemas in registration order.
* @param filter - optional package and face restriction.
* @returns matching schema records.
*/
list(filter: TypertSchemaFilter = {}): TypertSchemaRecord[] {
return [...this.schemas.values()].filter(record => matches(record, filter))
}
/**
* Look up generated reflection for one package face.
* @param packageName - exact npm package name.
* @param face - face to query; defaults to the host runtime.
* @returns the live package record, or `undefined` when absent.
*/
getPackage(packageName: string, face: TypertFace = 'host'): TypertPackageRecord | undefined {
return this.packages.get(typertPackageKey(packageName, face))
}
/**
* Enumerate generated package reflection in registration order.
* @param filter - optional package and face restriction.
* @returns matching package records.
*/
listPackages(filter: TypertPackageFilter = {}): TypertPackageRecord[] {
return [...this.packages.values()].filter(record => matches(record, filter))
}
/**
* Project a live Zod schema to JSON Schema without caching the result.
* @param key - global schema key.
* @param params - Zod projection parameters.
* @returns a fresh JSON Schema document.
*/
toJSONSchema(key: string, params?: z.core.ToJSONSchemaParams): z.core.JSONSchema.BaseSchema {
return z.toJSONSchema(this.resolve(key).schema, params)
}
private validatePackage(contribution: TypertContribution): TypertPackageRecord {
validateSegment('package name', contribution.package)
const face: unknown = contribution.face
if (face !== 'host' && face !== 'client') {
throw new Error(`typert: invalid face ${JSON.stringify(face)} — expected "host" or "client"`)
}
const key = typertPackageKey(contribution.package, contribution.face)
if (this.packages.has(key)) {
throw new Error(`typert: package face "${key}" is already registered`)
}
return {
package: contribution.package,
face,
key,
model: contribution.model,
}
}
private validateSchemas(contribution: TypertContribution): TypertSchemaRecord[] {
const records: TypertSchemaRecord[] = []
const batch = new Set<string>()
for (const schema of contribution.schemas) {
validateSegment('schema name', schema.name)
const key = typertKey(contribution.package, schema.name)
if (batch.has(key) || this.schemas.has(key)) {
throw new Error(`typert: schema "${key}" is already registered`)
}
batch.add(key)
records.push({
...schema,
package: contribution.package,
face: contribution.face,
key,
})
}
return records
}
}
function matches(
record: { readonly package: string; readonly face: TypertFace },
filter: { readonly package?: string; readonly face?: TypertFace },
): boolean {
return (filter.package === undefined || record.package === filter.package)
&& (filter.face === undefined || record.face === filter.face)
}
function validateSegment(subject: string, value: string): void {
if (value.length === 0 || value.includes('#')) {
throw new Error(`typert: invalid ${subject} "${value}" — must be nonempty and must not contain "#"`)
}
}
export default TypertRegistry
+31
View File
@@ -0,0 +1,31 @@
/**
* Package-owned invariant companion for `@deepseek-ai/dsh-typert-registry`.
* @module @deepseek-ai/dsh-typert-registry/invariant
*/
/* jscpd:ignore-start */
import type { Context } from 'cordis'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
const PACKAGE_NAME = '@deepseek-ai/dsh-typert-registry'
/** Cordis companion plugin name. */
export const name = 'typert-registry-invariant'
/** Service required before the companion can reserve package ownership. */
export const inject = ['invariants']
/**
* No runtime invariant: schema and package-reflection records mutate together
* inside register/dispose, with no independent event or second data source to
* cross-check; duplicate identities fail at the owning operation boundary.
*/
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 */
+112
View File
@@ -0,0 +1,112 @@
/**
* Pure generated-artifact and runtime-registry types. The registry stores Zod
* schemas separately from generated package reflection metadata.
* @module @deepseek-ai/dsh-typert-registry/types
*/
import type { z } from 'zod'
/** Independently compiled side that produced a contribution. */
export type TypertFace = 'host' | 'client'
/** Structured JSDoc tag retained by generated runtime metadata. */
export interface TypertDocTag {
readonly name: string
readonly argument?: string
readonly comment?: string
readonly text: string
}
/** Source documentation retained on reflected package elements. */
export interface TypertDocumentation {
readonly description?: string
readonly summary?: string
readonly tags: readonly TypertDocTag[]
readonly jsDoc?: string
}
/** One generated public member signature. */
export interface TypertMemberModel {
readonly kind: 'property' | 'method' | 'getter' | 'setter' | 'call' | 'construct' | 'index'
readonly name: string
readonly signature: string
readonly summary?: string
readonly jsDoc?: string
}
/** One named type declaration referenced by a reflected business surface. */
export interface TypertTypeModel {
readonly name: string
readonly declaration: string
}
/** Runtime reflection metadata for one Cordis service. */
export interface TypertServiceModel extends TypertDocumentation {
readonly key: string
readonly exportName: string
readonly members: readonly TypertMemberModel[]
readonly types: readonly TypertTypeModel[]
}
/** Runtime reflection metadata for one Cordis event. */
export interface TypertEventModel extends TypertDocumentation {
readonly name: string
readonly mode?: string
readonly signature: string
}
/** Runtime reflection metadata for one explicitly exported reference object. */
export interface TypertObjectModel extends TypertDocumentation {
readonly name: string
readonly exportName: string
readonly members: readonly TypertMemberModel[]
readonly types: readonly TypertTypeModel[]
}
/** Generated business reflection for one package on one face. */
export interface TypertPackageModel {
readonly services: readonly TypertServiceModel[]
readonly events: readonly TypertEventModel[]
readonly objects: readonly TypertObjectModel[]
}
/** One generated live Zod schema. */
export interface TypertSchema {
readonly name: string
readonly schema: z.ZodType
}
/** One generated package contribution registered and withdrawn atomically. */
export interface TypertContribution {
readonly package: string
readonly face: TypertFace
readonly schemas: readonly TypertSchema[]
readonly model: TypertPackageModel
}
/** A live schema plus its contribution identity. */
export interface TypertSchemaRecord extends TypertSchema {
readonly package: string
readonly face: TypertFace
readonly key: string
}
/** A live generated package model plus its stable identity. */
export interface TypertPackageRecord {
readonly package: string
readonly face: TypertFace
readonly key: string
readonly model: TypertPackageModel
}
/** Filter for schema enumeration. */
export interface TypertSchemaFilter {
readonly package?: string
readonly face?: TypertFace
}
/** Filter for package-model enumeration. */
export interface TypertPackageFilter {
readonly package?: string
readonly face?: TypertFace
}
@@ -0,0 +1,148 @@
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { z } from 'zod'
import TypertRegistry, {
typertKey,
typertPackageKey,
type TypertContribution,
} from '@deepseek-ai/dsh-typert-registry'
async function makeCtx(): Promise<Context> {
const ctx = new Context()
await ctx.plugin(TypertRegistry)
return ctx
}
function toolsContribution(schema: z.ZodType = z.object({ name: z.string() })): TypertContribution {
return {
package: '@deepseek-ai/dsh-tools',
face: 'host',
schemas: [{ name: 'ToolInput', schema }],
model: {
services: [{
key: 'tools',
exportName: 'ToolRegistry',
summary: 'Tool registry and execution pipeline.',
tags: [],
members: [{
kind: 'method',
name: 'register',
signature: 'register(definition: ToolDefinition): () => void',
}],
types: [{ name: 'ToolDefinition', declaration: 'export interface ToolDefinition {}' }],
}],
events: [{
name: 'tools/change',
mode: 'emit',
signature: "'tools/change'(): void",
tags: [],
}],
objects: [],
},
}
}
describe('TypertRegistry', () => {
it('registers and queries generated schemas separately from package reflection', async () => {
const ctx = await makeCtx()
const contribution = toolsContribution()
ctx.typert.register(contribution)
expect(typertKey('@deepseek-ai/dsh-tools', 'ToolInput')).toBe('@deepseek-ai/dsh-tools#ToolInput')
expect(typertPackageKey('@deepseek-ai/dsh-tools', 'host')).toBe('@deepseek-ai/dsh-tools#host')
expect(ctx.typert.get('@deepseek-ai/dsh-tools#ToolInput')).toMatchObject({
package: '@deepseek-ai/dsh-tools',
face: 'host',
name: 'ToolInput',
})
expect(ctx.typert.get('@deepseek-ai/dsh-tools#ToolInput')?.schema).toBe(contribution.schemas[0]?.schema)
expect(ctx.typert.getPackage('@deepseek-ai/dsh-tools', 'host')).toMatchObject({
key: '@deepseek-ai/dsh-tools#host',
model: { services: [{ key: 'tools' }] },
})
expect(ctx.typert.list()).toHaveLength(1)
expect(ctx.typert.listPackages({ face: 'host' })).toHaveLength(1)
})
it('withdraws schemas and package metadata through the exact contribution disposer', async () => {
const ctx = await makeCtx()
const dispose = ctx.typert.register(toolsContribution())
expect(ctx.typert.getPackage('@deepseek-ai/dsh-tools')).toBeDefined()
dispose()
expect(ctx.typert.get('@deepseek-ai/dsh-tools#ToolInput')).toBeUndefined()
expect(ctx.typert.getPackage('@deepseek-ai/dsh-tools')).toBeUndefined()
expect(ctx.typert.listPackages()).toEqual([])
})
it('follows the registering plugin fiber lifecycle', async () => {
const ctx = await makeCtx()
const fiber = ctx.plugin(Object.assign(
(child: Context) => { child.typert.register(toolsContribution()) },
{ inject: ['typert'] },
))
await fiber
expect(ctx.typert.getPackage('@deepseek-ai/dsh-tools')).toBeDefined()
await fiber.dispose()
expect(ctx.typert.getPackage('@deepseek-ai/dsh-tools')).toBeUndefined()
expect(ctx.typert.list()).toEqual([])
})
it('rejects duplicate package faces and schema keys before committing', async () => {
const ctx = await makeCtx()
const original = toolsContribution()
ctx.typert.register(original)
expect(() => ctx.typert.register(toolsContribution(z.never()))).toThrow('package face')
expect(ctx.typert.get('@deepseek-ai/dsh-tools#ToolInput')?.schema).toBe(original.schemas[0]?.schema)
const duplicateBatch: TypertContribution = {
...toolsContribution(),
package: '@fixture/duplicate',
schemas: [
{ name: 'Same', schema: z.string() },
{ name: 'Same', schema: z.number() },
],
}
expect(() => ctx.typert.register(duplicateBatch)).toThrow('schema "@fixture/duplicate#Same" is already registered')
expect(ctx.typert.getPackage('@fixture/duplicate')).toBeUndefined()
})
it('rejects malformed contribution identities and filters both registry views', async () => {
const ctx = await makeCtx()
ctx.typert.register(toolsContribution())
expect(() => ctx.typert.register({ ...toolsContribution(), package: '' }))
.toThrow('invalid package name')
expect(() => ctx.typert.register({ ...toolsContribution(), package: 'bad#package' }))
.toThrow('invalid package name')
expect(() => ctx.typert.register({ ...toolsContribution(), face: 'worker' as 'host' }))
.toThrow('invalid face')
expect(() => ctx.typert.register({
...toolsContribution(),
package: '@fixture/schema-name',
schemas: [{ name: 'bad#name', schema: z.string() }],
})).toThrow('invalid schema name')
expect(ctx.typert.list({ package: '@fixture/absent' })).toEqual([])
expect(ctx.typert.list({ face: 'client' })).toEqual([])
expect(ctx.typert.listPackages({ package: '@fixture/absent' })).toEqual([])
expect(ctx.typert.listPackages({ face: 'client' })).toEqual([])
})
it('resolves required schemas and projects fresh JSON Schema documents', async () => {
const ctx = await makeCtx()
ctx.typert.register(toolsContribution())
expect(ctx.typert.resolve('@deepseek-ai/dsh-tools#ToolInput').name).toBe('ToolInput')
expect(() => ctx.typert.resolve('@deepseek-ai/dsh-tools#Missing')).toThrow('contributes no schema named "Missing"')
expect(() => ctx.typert.resolve('@fixture/absent#Value')).toThrow('has no registered contribution')
expect(() => ctx.typert.resolve('invalid')).toThrow('expected "<package>#<name>"')
const projected = ctx.typert.toJSONSchema('@deepseek-ai/dsh-tools#ToolInput')
expect(projected).toMatchObject({ type: 'object', properties: { name: { type: 'string' } } })
expect(ctx.typert.toJSONSchema('@deepseek-ai/dsh-tools#ToolInput')).not.toBe(projected)
})
})
+21
View File
@@ -0,0 +1,21 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib/types"
},
"include": [
"src"
],
"references": [
{
"path": "../../../vendor/cosmokit"
},
{
"path": "../../../vendor/cordis"
},
{
"path": "../../support/invariants"
}
]
}
+25
View File
@@ -0,0 +1,25 @@
import { defineConfig } from 'tsdown'
/** Build the registry and its invariant companion as independent bundles. */
export default defineConfig([
{
entry: ['lib/types/index.js'],
outDir: 'lib',
format: ['esm'],
platform: 'node',
target: 'es2024',
fixedExtension: false,
dts: false,
clean: false,
},
{
entry: ['lib/types/invariant.js'],
outDir: 'lib',
format: ['esm'],
platform: 'node',
target: 'es2024',
fixedExtension: false,
dts: false,
clean: false,
},
])