refactor: apply repository naming contract
Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
6
packages/core/agent-tool-presentation/README.i18n.yaml
Normal file
6
packages/core/agent-tool-presentation/README.i18n.yaml
Normal 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/core/agent-tool-presentation/README.md
|
||||
README.md: a4747d4d95a732f4eccb81ed44b68c961895d773
|
||||
README.zh.md: 33b33c63cd61893ea68bd2ab5d7242f8cf7d7c27
|
||||
31
packages/core/agent-tool-presentation/README.md
Normal file
31
packages/core/agent-tool-presentation/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# dsh-agent-tool-presentation
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
The row an [agent preset](../../preset/agent-presets/README.md) carries to say which form of its tools the model sees: `native` (every schema), `code` (only `run_code` plus a generated TypeScript SDK), or `both`.
|
||||
|
||||
## Why a row rather than a registry
|
||||
|
||||
The tool registry cannot move into a preset. Its consumers are all host-plane — [`dsh-agent-loop`](../agent-loop/README.md) reads its scheduler, [`dsh-apiproxy`](../../host/apiproxy/README.md) reads its presenters to render tool cards, and every tool plugin registers into it — and a service only moves down when all of its consumers move with it.
|
||||
|
||||
What a preset can own is the **presentation** of that registry. `ctx.tools.presentAs()` declares it for the mounting agent alone, so a Code Mode session runs beside native ones in one process, each seeing its own catalog. The deployment's `mode` on the [`dsh-tools`](../tools/README.md) row remains the default that agents declaring nothing get.
|
||||
|
||||
## What it does
|
||||
|
||||
`native` applies immediately. A code mode instead waits for `ctx.codeRuntime`, which is a host-plane service ([`dsh-code-runtime-worker-thread`](../../code-runtime/code-runtime-worker-thread/README.md)): a preset selecting Code Mode against a deployment composing no runtime then holds this row pending, and `dsh-agent-presets` refuses the mount naming this id. The alternative — applying optimistically — moves the failure to the session's first request, where the operator can act on neither the preset nor the composition.
|
||||
|
||||
`mode` is required rather than defaulted, because a preset without this row already gets the deployment default; an omitted value would mean the row was composed for nothing.
|
||||
|
||||
One agent declares one presentation. A second declaration in the same composition is refused rather than merged: two answers to "which form does the model see" is a contradiction, not an override.
|
||||
|
||||
## Model Experience
|
||||
|
||||
Indirectly, through the projection it selects in `dsh-tools`: `code` presents `run_code` plus a generated SDK section and the rule that only `run_code` may be called directly, `native` presents every tool schema. The selection also decides what may EXECUTE: under `code` the registry resolves a model-direct call naming any other tool to `UNKNOWN_TOOL`, so this row is what keeps the announced surface and the callable surface the same for every agent it covers ([executor-collapse note](../../../.agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.md)).
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
No direct invalidation; the presentation is fixed when the agent is composed, so its request prefix is stable for the session's life.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **The runtime stays host-plane** — a preset can select Code Mode but cannot supply the TypeScript runtime it needs; a deployment that composes none can compose no code-mode preset.
|
||||
31
packages/core/agent-tool-presentation/README.zh.md
Normal file
31
packages/core/agent-tool-presentation/README.zh.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# dsh-agent-tool-presentation
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
[agent preset](../../preset/agent-presets/README.md) 用来声明「模型看到的工具是哪一种形态」的那一行:`native`(全部 schema)、`code`(只有 `run_code` 加一份生成的 TypeScript SDK)或 `both`。
|
||||
|
||||
## 为什么是一行插件,而不是把注册表搬下来
|
||||
|
||||
工具注册表搬不进 preset。它的消费者全在宿主平面——[`dsh-agent-loop`](../agent-loop/README.md) 读它的调度器,[`dsh-apiproxy`](../../host/apiproxy/README.md) 读它的 presenter 来渲染工具卡,每个工具插件都往里注册——而一个服务只有在**所有**消费者一起下沉时才能下沉。
|
||||
|
||||
preset 能拥有的是这份注册表的**呈现方式**。`ctx.tools.presentAs()` 只为正在挂载的那个 agent 声明,于是一个 Code Mode 会话可以和多个 native 会话同进程并存,各自看到各自的清单。[`dsh-tools`](../tools/README.md) 那一行上的 `mode` 仍然是默认值,供未作声明的 agent 使用。
|
||||
|
||||
## 它做什么
|
||||
|
||||
`native` 立即生效。code 类模式则等待 `ctx.codeRuntime`——这是一个宿主平面服务([`dsh-code-runtime-worker-thread`](../../code-runtime/code-runtime-worker-thread/README.md)):若某个 preset 在未组装运行时的部署上选择 Code Mode,本行就停在 pending,`dsh-agent-presets` 会指名此 id 拒绝挂载。另一种做法——先乐观应用——会把失败推迟到该会话的第一次请求,那时操作者对 preset 和组装都已无从下手。
|
||||
|
||||
`mode` 是必填而非有默认值:不带这一行的 preset 本来就会拿到部署默认值,省略它等于这一行白组装了。
|
||||
|
||||
一个 agent 只声明一次呈现方式。同一份组装里的第二次声明会被拒绝而不是合并:对「模型看到哪种形态」给出两个答案是矛盾,不是覆盖。
|
||||
|
||||
## 模型体验
|
||||
|
||||
间接生效,取决于它在 `dsh-tools` 中选择的投影:`code` 呈现 `run_code`、一份生成的 SDK 段,以及「只有 `run_code` 可被直接调用」这条规则,`native` 呈现每个工具的 schema。该选择同时决定了**什么可以执行**:在 `code` 下,注册表会把模型直呼其他任何工具名解析为 `UNKNOWN_TOOL`,因此这一行正是让「通告面」与「可调用面」对每个被它覆盖的 agent 保持一致的东西([执行器塌缩 note](../../../.agents/notes/implemented/bug-fix/2026-08-07-code-mode-executor-collapse.md))。
|
||||
|
||||
#### KV Cache effect
|
||||
|
||||
没有直接的失效影响;呈现方式在 agent 组装时即固定,因此其请求前缀在该会话的整个生命周期内保持稳定。
|
||||
|
||||
## 已知限制与暂缓事项
|
||||
|
||||
- **运行时仍在宿主平面** —— preset 可以选择 Code Mode,却无法自带它所需的 TypeScript 运行时;未组装运行时的部署也就无法组装任何 code 模式的 preset。
|
||||
52
packages/core/agent-tool-presentation/package.json
Normal file
52
packages/core/agent-tool-presentation/package.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@deepseek-ai/dsh-agent-tool-presentation",
|
||||
"description": "Agent-plane presentation selector: composes one agent's tools as Code Mode, native, or both",
|
||||
"version": "0.0.1-rc.2",
|
||||
"publishConfig": {
|
||||
"access": "restricted"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
||||
"directory": "packages/core/agent-tool-presentation"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"./src/*": "./src/*",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"lib/index.js",
|
||||
"lib/invariant.js",
|
||||
"lib/types/**/*.d.ts"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@deepseek-ai/schemastery": "workspace:^"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-tools": "workspace:^",
|
||||
"@deepseek-ai/cordis": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-agent": "workspace:^",
|
||||
"@deepseek-ai/dsh-code-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-scope": "workspace:^",
|
||||
"@deepseek-ai/dsh-session": "workspace:^",
|
||||
"@deepseek-ai/dsh-system-prompt": "workspace:^",
|
||||
"@deepseek-ai/dsh-tools": "workspace:^",
|
||||
"@deepseek-ai/cordis": "workspace:^"
|
||||
}
|
||||
}
|
||||
72
packages/core/agent-tool-presentation/src/index.ts
Normal file
72
packages/core/agent-tool-presentation/src/index.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Agent-plane presentation selector: the row an agent preset carries to say
|
||||
* which form of its tools the model sees.
|
||||
*
|
||||
* The tool registry itself stays on the host plane — the agent loop's
|
||||
* scheduler, the API proxy's presenters, and every tool plugin are all its
|
||||
* consumers, so it cannot move into a preset. What a preset CAN own is the
|
||||
* presentation: `ctx.tools.presentAs()` declares it for the mounting SCOPE,
|
||||
* which is the preset's standing mount, so the declaration covers every agent
|
||||
* joined to that preset and a Code Mode preset runs beside native ones in one
|
||||
* process. One row per composition, not one per session.
|
||||
*
|
||||
* A code mode needs a TypeScript code runtime, which is a host-plane service
|
||||
* ([`dsh-code-runtime-worker-thread`](../../code-runtime/code-runtime-worker/README.md)).
|
||||
* This row therefore waits for it rather than assuming it: a preset selecting
|
||||
* Code Mode against a deployment that composes no runtime fails at mount, named
|
||||
* in the preset's own activation audit, instead of at the first prompt.
|
||||
* @module @deepseek-ai/dsh-agent-tool-presentation
|
||||
*/
|
||||
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
import type { ToolPresentationMode } from '@deepseek-ai/dsh-tools'
|
||||
// Type-only: brings the `ctx.tools` Context merge into this program.
|
||||
import type {} from '@deepseek-ai/dsh-tools'
|
||||
|
||||
/** Cordis plugin name. */
|
||||
export const name = 'tool-presentation'
|
||||
|
||||
/**
|
||||
* Required services. `codeRuntime` is NOT listed: a `native` row must mount in
|
||||
* a deployment that composes no runtime, and the mode-dependent wait is
|
||||
* declared inside {@link apply} instead.
|
||||
*/
|
||||
export const inject = ['tools']
|
||||
|
||||
/** Plugin config. */
|
||||
export interface Config {
|
||||
/**
|
||||
* The form this agent's model sees. `native` sends every visible schema,
|
||||
* `code` sends only `run_code` plus a generated SDK, `both` sends both.
|
||||
* Required rather than defaulted: the deployment default is what a preset
|
||||
* without this row already gets, so an omitted value would mean the row was
|
||||
* composed for nothing.
|
||||
*/
|
||||
mode: ToolPresentationMode
|
||||
}
|
||||
|
||||
/** Runtime schema. */
|
||||
export const Config: z<Config> = z.object({
|
||||
mode: z.union(['native', 'code', 'both'] as const).required(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Declare the tool presentation for every agent this composition covers.
|
||||
* @param ctx - the mounting composition's scope context (a preset's standing scope).
|
||||
* @param config - the selected presentation.
|
||||
*/
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
// `presentAs` is itself the effect — it registers through the calling
|
||||
// context and hands back that exact disposer — so the declaration unwinds
|
||||
// with this row without a second wrapper owning it.
|
||||
if (config.mode === 'native') {
|
||||
ctx.tools.presentAs('native')
|
||||
return
|
||||
}
|
||||
// The wait is the loud failure: an entry still pending on `codeRuntime` is
|
||||
// what `dsh-agent-presets` reports as an unusable row, naming this id.
|
||||
ctx.inject(['codeRuntime'], (runtimeCtx: Context) => {
|
||||
runtimeCtx.tools.presentAs(config.mode)
|
||||
})
|
||||
}
|
||||
32
packages/core/agent-tool-presentation/src/invariant.ts
Normal file
32
packages/core/agent-tool-presentation/src/invariant.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Package-owned invariant companion for `@deepseek-ai/dsh-agent-tool-presentation`.
|
||||
* @module @deepseek-ai/dsh-agent-tool-presentation/invariant
|
||||
*/
|
||||
|
||||
/* jscpd:ignore-start */
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
|
||||
const PACKAGE_NAME = '@deepseek-ai/dsh-agent-tool-presentation'
|
||||
|
||||
/** Cordis companion plugin name. */
|
||||
export const name = 'tool-presentation-invariant'
|
||||
/** Service required before the companion can reserve package ownership. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/**
|
||||
* No runtime invariant: this package makes exactly one scoped call into
|
||||
* `ctx.tools` and owns no event or snapshot of its own; the relation it
|
||||
* establishes — which presentation one agent's assembly uses — is the tool
|
||||
* registry's to hold, and `dsh-tools` observes it there.
|
||||
*/
|
||||
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 */
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* The row an agent preset carries to pick its tool presentation. What it owes
|
||||
* its caller: the choice reaches THIS agent and no other, it unwinds with the
|
||||
* agent, and a code mode composed against a deployment with no code runtime
|
||||
* stops at mount — where a preset's activation audit can name it — rather
|
||||
* than at the first prompt assembly.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { Context } from '@deepseek-ai/cordis'
|
||||
import { createScope } from '@deepseek-ai/dsh-scope'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
|
||||
import type { CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
|
||||
import ToolRuntime, { RUN_CODE_NAME, defineTool } from '@deepseek-ai/dsh-tools'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { apply, Config, inject, name } from '@deepseek-ai/dsh-agent-tool-presentation'
|
||||
|
||||
/** A runtime that never runs anything: presentation never dispatches. */
|
||||
class StubRuntime extends CodeRuntime {
|
||||
readonly language = 'typescript'
|
||||
readonly isolation = 'stub'
|
||||
|
||||
run(_request: CodeRunRequest): Promise<CodeRunResult> {
|
||||
return Promise.resolve({ logs: [] })
|
||||
}
|
||||
}
|
||||
|
||||
/** A host plane with one tool, optionally carrying a code runtime. */
|
||||
async function host(options: { runtime?: boolean } = {}) {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt, {})
|
||||
await ctx.plugin(ToolRuntime, {})
|
||||
if (options.runtime !== false) await ctx.plugin(StubRuntime)
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'echo',
|
||||
description: 'Echo tool.',
|
||||
parameters: { value: { type: 'string', required: true } },
|
||||
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }] },
|
||||
execute: args => Promise.resolve(args.value),
|
||||
}))
|
||||
return ctx
|
||||
}
|
||||
|
||||
/** Mount the row under one agent's scope, as a preset subtree does. */
|
||||
async function mount(ctx: Context, config: Config, id = 'agent') {
|
||||
const agent = { id: SessionId(id) } as Agent
|
||||
let inner!: Context
|
||||
const fiber = ctx.plugin(Object.assign((host: Context) => {
|
||||
inner = createScope(host, agent).ctx
|
||||
}, { inject: ['tools', 'systemPrompt'] }))
|
||||
await fiber.await()
|
||||
const row = inner.plugin({ name, inject: [...inject], Config, apply }, config)
|
||||
await row.await()
|
||||
return { agent, fiber, row }
|
||||
}
|
||||
|
||||
describe('the tool-presentation row', () => {
|
||||
it('declares the services it uses without holding a code runtime hostage', () => {
|
||||
// A `native` row must mount where no runtime is composed, so the wait is
|
||||
// conditional inside apply rather than static metadata.
|
||||
expect(inject).toEqual(['tools'])
|
||||
})
|
||||
|
||||
it('gives its own agent Code Mode and leaves the rest native', async () => {
|
||||
const ctx = await host()
|
||||
const coded = await mount(ctx, { mode: 'code' }, 'coded')
|
||||
const plain = await mount(ctx, { mode: 'native' }, 'plain')
|
||||
|
||||
const codedAssembly = await ctx.systemPrompt.assemble({ scope: coded.agent })
|
||||
const plainAssembly = await ctx.systemPrompt.assemble({ scope: plain.agent })
|
||||
|
||||
expect(codedAssembly.tools.map(tool => tool.name)).toEqual([RUN_CODE_NAME])
|
||||
expect(codedAssembly.sections.find(section => section.name === 'tools:sdk')?.text).toContain('echo')
|
||||
expect(plainAssembly.tools.map(tool => tool.name)).toEqual(['echo'])
|
||||
})
|
||||
|
||||
it('presents both forms when asked for both', async () => {
|
||||
const ctx = await host()
|
||||
const { agent } = await mount(ctx, { mode: 'both' })
|
||||
|
||||
const assembly = await ctx.systemPrompt.assemble({ scope: agent })
|
||||
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual(['echo', RUN_CODE_NAME])
|
||||
})
|
||||
|
||||
it('restores the deployment default when the agent unloads', async () => {
|
||||
const ctx = await host()
|
||||
const { agent, row } = await mount(ctx, { mode: 'code' })
|
||||
|
||||
await row.dispose()
|
||||
|
||||
// HMR safety: the preset subtree is torn down with its agent, and the
|
||||
// presentation must go with it rather than outliving the composition.
|
||||
const assembly = await ctx.systemPrompt.assemble({ scope: agent })
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual(['echo'])
|
||||
expect(assembly.sections.some(section => section.name === 'tools:sdk')).toBe(false)
|
||||
})
|
||||
|
||||
it('waits for a code runtime the deployment does not compose', async () => {
|
||||
const ctx = await host({ runtime: false })
|
||||
|
||||
const { agent, row } = await mount(ctx, { mode: 'code' })
|
||||
|
||||
// Pending, not applied: `dsh-agent-presets` rejects a mount holding a row
|
||||
// that never reached a usable state, naming this id — so the preset fails
|
||||
// where the operator can act, instead of at the first request.
|
||||
expect(row.ctx.get('codeRuntime')).toBeUndefined()
|
||||
const assembly = await ctx.systemPrompt.assemble({ scope: agent })
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual(['echo'])
|
||||
})
|
||||
|
||||
it('applies once the runtime arrives', async () => {
|
||||
const ctx = await host({ runtime: false })
|
||||
const { agent } = await mount(ctx, { mode: 'code' })
|
||||
|
||||
await ctx.plugin(StubRuntime)
|
||||
|
||||
const assembly = await ctx.systemPrompt.assemble({ scope: agent })
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual([RUN_CODE_NAME])
|
||||
})
|
||||
|
||||
it('requires a mode rather than defaulting one', () => {
|
||||
// An omitted value would mean the row was composed for nothing: a preset
|
||||
// without this row already gets the deployment default.
|
||||
expect(() => Config({} as never)).toThrow()
|
||||
})
|
||||
})
|
||||
27
packages/core/agent-tool-presentation/tsconfig.json
Normal file
27
packages/core/agent-tool-presentation/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../vendor/cosmokit"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/cordis"
|
||||
},
|
||||
{
|
||||
"path": "../../../vendor/schemastery"
|
||||
},
|
||||
{
|
||||
"path": "../../core/tools"
|
||||
},
|
||||
{
|
||||
"path": "../../runtime-diagnostics/invariants"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user