From d35459e3c19dfbc31fd2b0499f726b60d56ca16b Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 24 Aug 2026 23:36:47 +0800 Subject: [PATCH] refactor(sdk): remove unused root tool filter --- ...3-python-sdk-dsh-profile-runtime.i18n.yaml | 4 +- ...26-08-23-python-sdk-dsh-profile-runtime.md | 2 +- ...08-23-python-sdk-dsh-profile-runtime.zh.md | 2 +- docs/config-catalog.i18n.yaml | 4 +- docs/config-catalog.md | 7 --- docs/config-catalog.zh.md | 7 --- packages/sdk/server/README.i18n.yaml | 4 +- packages/sdk/server/README.md | 4 +- packages/sdk/server/README.zh.md | 4 +- packages/sdk/server/src/index.ts | 13 ----- packages/sdk/server/src/server.ts | 7 --- .../sdk/server/tests/plugin-apply.spec.ts | 48 ------------------- packages/sdk/server/tests/server.spec.ts | 41 ---------------- 13 files changed, 12 insertions(+), 135 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.i18n.yaml b/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.i18n.yaml index add950844a..e02c19ea8c 100644 --- a/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.i18n.yaml +++ b/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.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-23-python-sdk-dsh-profile-runtime.md -2026-08-23-python-sdk-dsh-profile-runtime.md: dcb4f77048d516f0e187b611dc09c224fba47b58 -2026-08-23-python-sdk-dsh-profile-runtime.zh.md: 505b49506ae4c4be4809d588448b2c593d3ebaf5 +2026-08-23-python-sdk-dsh-profile-runtime.md: 4af7812db6818b65c754a43ec1a7f973d1cbcbf9 +2026-08-23-python-sdk-dsh-profile-runtime.zh.md: 155e7d2ae0b0ba3e4163dd85a31de90bef9d588a diff --git a/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.md b/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.md index dcb4f77048..4af7812db6 100644 --- a/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.md +++ b/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.md @@ -24,7 +24,7 @@ Every Python launch requires either explicit `dsh_home` or a non-empty `DSH_HOME Persistent SDK customization uses the same profile interfaces as direct CLI use. `dsh plugin --profile ...` manages external dependencies and bundle order, `$DSH_HOME/profiles//cordis.patch.yml` owns persistent row changes, the home patch applies machine-local changes across profiles, and Python `patches` supplies invocation-specific overlays. A selected profile is valid only when it retains an SDK server row. Missing profiles, bundles, server rows, and invalid patches fail without a complete-config fallback; a profile that remains alive without serving JSON-RPC fails the independently bounded initialization handshake with a diagnostic naming that profile. -The [standalone sdk-minimal profile](2026-08-24-standalone-sdk-minimal-profile.md) lists one repository-owned bundle that inserts its complete explicit tree without `dsh-base`. Its persistent Bash and string-replace editor are present by composition rather than a server filter; dynamic runtime context, workspace instructions, settings, managed credentials, telemetry, compaction, and every other base row are absent. The same runtime still packages the full `sdk` and `web` profiles as separate choices. +The [standalone sdk-minimal profile](2026-08-24-standalone-sdk-minimal-profile.md) lists one repository-owned bundle that inserts its complete explicit tree without `dsh-base`. Its persistent Bash and string-replace editor are present by composition; the shared JSON-RPC server exposes no root-agent tool filter. Dynamic runtime context, workspace instructions, settings, managed credentials, telemetry, compaction, and every other base row are absent. The same runtime still packages the full `sdk` and `web` profiles as separate choices. The runtime wheel installs a `dsh` console command. Ordinary profile and SDK execution remains Node-free; external package management requires a caller-installed `pnpm`. diff --git a/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.zh.md b/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.zh.md index 505b49506a..155e7d2ae0 100644 --- a/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.zh.md +++ b/.agents/notes/implemented/architecture/2026-08-23-python-sdk-dsh-profile-runtime.zh.md @@ -24,7 +24,7 @@ Python SDK 分发一个私有 Node 应用,直接启动完整外部 `cordis.yml 持久 SDK 自定义使用与直接 CLI 相同的 profile 接口。`dsh plugin --profile ...` 管理外部依赖与 bundle 顺序,`$DSH_HOME/profiles//cordis.patch.yml` 负责持久配置项变更,home patch 对所有 profile 应用机器本地变更,Python `patches` 则提供单次启动 overlay。所选 profile 只有保留 SDK server 配置项时才有效。缺失 profile、bundle、server 配置项或非法 patch 都会直接失败,不存在完整配置回退;保持运行却不提供 JSON-RPC 服务的 profile 会在独立有界的初始化握手中失败,诊断会指明该 profile。 -[独立 sdk-minimal profile](2026-08-24-standalone-sdk-minimal-profile.zh.md)只列出一个仓库自有组合包,该组合包会插入不含 `dsh-base` 的完整显式配置树。持久 Bash 与字符串替换 editor 通过组合存在,而不是通过 server 筛选;动态运行时上下文、workspace 指令、settings、托管凭据、遥测、compaction 与其他所有 base 配置项均不存在。同一运行时仍会把完整 `sdk` 与 `web` profile 作为独立选择打包。 +[独立 sdk-minimal profile](2026-08-24-standalone-sdk-minimal-profile.zh.md)只列出一个仓库自有组合包,该组合包会插入不含 `dsh-base` 的完整显式配置树。持久 Bash 与字符串替换 editor 通过组合存在;共享 JSON-RPC server 不暴露根 agent 工具筛选器。动态运行时上下文、workspace 指令、settings、托管凭据、遥测、compaction 与其他所有 base 配置项均不存在。同一运行时仍会把完整 `sdk` 与 `web` profile 作为独立选择打包。 运行时 wheel 安装 `dsh` 控制台命令。普通 profile 与 SDK 运行仍不需要 Node;外部包管理要求调用方自行安装 `pnpm`。 diff --git a/docs/config-catalog.i18n.yaml b/docs/config-catalog.i18n.yaml index cec89ac365..906ce63c6e 100644 --- a/docs/config-catalog.i18n.yaml +++ b/docs/config-catalog.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/config-catalog.md -config-catalog.md: 19f292ee40861ffa6f8e7fdef4d8717189a2149c -config-catalog.zh.md: 96f9721cae20a1a0ed172349c0189bc47576c75d +config-catalog.md: 54cf64bdf6ff5c8ee51b6ecedaf218267778d19f +config-catalog.zh.md: e887edf989d279435ae04beb40172341f5125b1e diff --git a/docs/config-catalog.md b/docs/config-catalog.md index 19f292ee40..54cf64bdf6 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -1714,13 +1714,6 @@ Requires: `agents` export interface JsonRpcConfig { /** Report max-token turn/subagent termination as a successful SDK result. */ maxTokensAsSuccess?: boolean - /** Per-root-agent model-facing tool filter; an allow list excludes later unnamed global tools. */ - toolFilter?: { - /** Global tool names that remain visible. */ - allow?: string[] - /** Global tool names removed from visibility. */ - deny?: string[] - } /** Transport input override; production uses `process.stdin`. */ input?: Readable /** Transport output override; production uses `process.stdout`. */ diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 96f9721cae..e887edf989 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -1716,13 +1716,6 @@ export interface Config { export interface JsonRpcConfig { /** Report max-token turn/subagent termination as a successful SDK result. */ maxTokensAsSuccess?: boolean - /** Per-root-agent model-facing tool filter; an allow list excludes later unnamed global tools. */ - toolFilter?: { - /** Global tool names that remain visible. */ - allow?: string[] - /** Global tool names removed from visibility. */ - deny?: string[] - } /** Transport input override; production uses `process.stdin`. */ input?: Readable /** Transport output override; production uses `process.stdout`. */ diff --git a/packages/sdk/server/README.i18n.yaml b/packages/sdk/server/README.i18n.yaml index 957bd99306..b44e84412e 100644 --- a/packages/sdk/server/README.i18n.yaml +++ b/packages/sdk/server/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/sdk/server/README.md -README.md: e6d36df7e387e895941c082226ac955c69f16ca4 -README.zh.md: 13c026edc491ebdbec298b8c9cd330b03d675db7 +README.md: f10c45e2df06383d6726f249bb6db2015a7fb418 +README.zh.md: 7bf3bcdab2a318fbfcba7b5a6c3a60ff883f47b6 diff --git a/packages/sdk/server/README.md b/packages/sdk/server/README.md index e6d36df7e3..f10c45e2df 100644 --- a/packages/sdk/server/README.md +++ b/packages/sdk/server/README.md @@ -10,7 +10,7 @@ The `jsonrpc` plugin serves newline-delimited JSON-RPC over stdio so out-of-proc ## Config -`maxTokensAsSuccess` defaults to `false` and affects only the deployment-mapped status on `subagent.finished`; root-session prompts have no prompt-level status. Optional `toolFilter.allow` and `toolFilter.deny` restrict each SDK-created root agent through `ctx.tools.restrict()`. An allow list excludes later global tool registrations that it does not name, so a fixed SDK deployment cannot silently gain model-facing tools when its base bundle expands. Unknown names and an empty filter fail when the first session is created. `JsonRpcConfig.input`, `output`, and `exit` are runtime-only transport hooks; production uses process stdio and `process.exit`. +`maxTokensAsSuccess` defaults to `false` and affects only the deployment-mapped status on `subagent.finished`; root-session prompts have no prompt-level status. The profile composition owns each root agent's tools. `JsonRpcConfig.input`, `output`, and `exit` are runtime-only transport hooks; production uses process stdio and `process.exit`. ## stdout is the protocol @@ -30,7 +30,7 @@ The plugin answers `shutdown`, flushes the response, disposes the root context s #### What the model sees -For each accepted `session/prompt`, text and durable content references enter one user message verbatim. Inline `SdkEncodedImageBlock` values are validated and committed through the composition's attachment store first, so the session log retains content-addressed image references rather than base64 bytes. This package adds no system-prompt prose or tool schema; those come from the other plugins in the composition. A configured `toolFilter` projects that composition's global tool registry before the request is assembled and executed. +For each accepted `session/prompt`, text and durable content references enter one user message verbatim. Inline `SdkEncodedImageBlock` values are validated and committed through the composition's attachment store first, so the session log retains content-addressed image references rather than base64 bytes. This package adds no system-prompt prose or tool schema; those come from the other plugins in the composition. #### Token effect diff --git a/packages/sdk/server/README.zh.md b/packages/sdk/server/README.zh.md index 13c026edc4..7bf3bcdab2 100644 --- a/packages/sdk/server/README.zh.md +++ b/packages/sdk/server/README.zh.md @@ -10,7 +10,7 @@ ## 配置 -`maxTokensAsSuccess` 默认为 `false`,且只影响 `subagent.finished` 上由部署映射的状态;根会话提示词没有提示词级状态。可选的 `toolFilter.allow` 与 `toolFilter.deny` 通过 `ctx.tools.restrict()` 限制每个由 SDK 创建的根 agent。Allow 列表会排除之后出现但未指名的全局工具,因此固定的 SDK 部署不会在基础 bundle 扩展时静默获得面向模型的新工具。未知名称与空筛选器会在创建首个会话时明确失败。`JsonRpcConfig.input`、`output` 和 `exit` 是仅供运行时使用的传输钩子;生产环境使用进程 stdio 和 `process.exit`。 +`maxTokensAsSuccess` 默认为 `false`,且只影响 `subagent.finished` 上由部署映射的状态;根会话提示词没有提示词级状态。Profile 组合负责每个根 agent 的工具。`JsonRpcConfig.input`、`output` 和 `exit` 是仅供运行时使用的传输钩子;生产环境使用进程 stdio 和 `process.exit`。 ## stdout 即协议 @@ -30,7 +30,7 @@ Stdout 只承载 JSON-RPC 帧。部署不得组合 stdout logger;诊断应写 #### 模型看到的内容 -对于每个已接受的 `session/prompt`,文本和持久内容引用会原样进入一条用户消息。内联 `SdkEncodedImageBlock` 会先通过组合中的附件存储完成校验与提交,因此会话日志保留内容寻址的图片引用而不是 base64 字节。此包不会添加系统提示词文本或工具 schema;这些内容来自组合中的其他插件。配置的 `toolFilter` 会在请求组装与执行前投影该组合的全局工具注册表。 +对于每个已接受的 `session/prompt`,文本和持久内容引用会原样进入一条用户消息。内联 `SdkEncodedImageBlock` 会先通过组合中的附件存储完成校验与提交,因此会话日志保留内容寻址的图片引用而不是 base64 字节。此包不会添加系统提示词文本或工具 schema;这些内容来自组合中的其他插件。 #### Token 影响 diff --git a/packages/sdk/server/src/index.ts b/packages/sdk/server/src/index.ts index 17e9d3a89f..963b4fb3bd 100644 --- a/packages/sdk/server/src/index.ts +++ b/packages/sdk/server/src/index.ts @@ -25,13 +25,6 @@ export const inject = ['agents'] export interface JsonRpcConfig { /** Report max-token turn/subagent termination as a successful SDK result. */ maxTokensAsSuccess?: boolean - /** Per-root-agent model-facing tool filter; an allow list excludes later unnamed global tools. */ - toolFilter?: { - /** Global tool names that remain visible. */ - allow?: string[] - /** Global tool names removed from visibility. */ - deny?: string[] - } /** Transport input override; production uses `process.stdin`. */ input?: Readable /** Transport output override; production uses `process.stdout`. */ @@ -42,11 +35,6 @@ export interface JsonRpcConfig { export const Config: Schema = Schema.object({ maxTokensAsSuccess: Schema.boolean().default(false), - // Preserve omission; Schemastery's materialized empty object is not a valid restriction. - toolFilter: Schema.object({ - allow: Schema.array(Schema.string()).default(undefined as unknown as string[]), - deny: Schema.array(Schema.string()).default(undefined as unknown as string[]), - }).default(undefined as unknown as { allow: string[]; deny: string[] }), }) /** @@ -71,7 +59,6 @@ export function apply(ctx: Context, config: JsonRpcConfig): void { const transport = new JsonRpcLineTransport(input, output) const server = new HarnessSdkJsonRpcServer(ctx, transport, { maxTokensAsSuccess: resolvedConfig.maxTokensAsSuccess, - ...resolvedConfig.toolFilter === undefined ? {} : { toolFilter: resolvedConfig.toolFilter }, }) // Share one exit task so racing shutdown requests cannot dispose the root or diff --git a/packages/sdk/server/src/server.ts b/packages/sdk/server/src/server.ts index 14b996f217..f07b238755 100644 --- a/packages/sdk/server/src/server.ts +++ b/packages/sdk/server/src/server.ts @@ -15,7 +15,6 @@ import { carrierKeyOf, type Scoped } from '@deepseek-ai/dsh-scope' import { SessionId } from '@deepseek-ai/dsh-session' import type SubagentRuntime from '@deepseek-ai/dsh-subagent' import type { SubagentRunEndInfo } from '@deepseek-ai/dsh-subagent' -import type { ToolRestriction } from '@deepseek-ai/dsh-tools' import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek' import type { InitializeParams, @@ -61,8 +60,6 @@ function subagentParentOf(carrier: Scoped): Agent { export interface HarnessSdkJsonRpcServerOptions { /** Report max-token termination as an accepted result instead of an infrastructure error. */ maxTokensAsSuccess?: boolean - /** Restrict each SDK-created root agent to an explicit subset of global tools. */ - toolFilter?: ToolRestriction } function successStatus(reason: string, options: HarnessSdkJsonRpcServerOptions): 'ok' | 'error' { @@ -256,7 +253,6 @@ export class HarnessSdkJsonRpcServer { // rows in the host plane, so this agent reads them from the global layer. A // deployment that configures a roster has to join one here first // (@deepseek-ai/dsh-agent-presets README, "Composing a child agent"). - const toolFilter = this.options.toolFilter const handle = await this.ctx.agents.create({ sessionId: SessionId(sessionId), meta: { cwd: this.cwd }, @@ -265,9 +261,6 @@ export class HarnessSdkJsonRpcServer { model: this.model, ...this.maxTokens === undefined ? {} : { maxTokens: this.maxTokens }, }, - ...toolFilter === undefined - ? {} - : { setup: (agentCtx: Context) => { agentCtx.tools.restrict(toolFilter) } }, }) const rec: SessionRecord = { handle } this.sessions.set(sessionId, rec) diff --git a/packages/sdk/server/tests/plugin-apply.spec.ts b/packages/sdk/server/tests/plugin-apply.spec.ts index 41954007eb..c5954e8dac 100644 --- a/packages/sdk/server/tests/plugin-apply.spec.ts +++ b/packages/sdk/server/tests/plugin-apply.spec.ts @@ -11,7 +11,6 @@ import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo' import { LlmAdapter } from '@deepseek-ai/dsh-llm' import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm' import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl' -import { defineTool } from '@deepseek-ai/dsh-tools' import * as jsonrpc from '../src/index.ts' /** @@ -72,7 +71,6 @@ async function mountPlugin( writeDelayMs?: number failFlush?: boolean beforeServer?: (ctx: Context) => Promise | void - toolFilter?: jsonrpc.JsonRpcConfig['toolFilter'] } = {}, ): Promise { const ctx = new Context() @@ -122,7 +120,6 @@ async function mountPlugin( input, output, exit, - ...options.toolFilter === undefined ? {} : { toolFilter: options.toolFilter }, }) const frames = (): Record[] => @@ -289,51 +286,6 @@ describe('dsh-sdk-jsonrpc-server plugin apply', () => { } }) - it('applies the configured root-agent tool filter through the Loader plugin', async () => { - const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-apply-tool-filter-')) - const llmServer = await mockCompletionServer() - vi.stubEnv('DEEPSEEK_API_KEY', 'test-key') - vi.stubEnv('DEEPSEEK_BASE_URL', llmServer.url) - const harness = await mountPlugin(storageDir, { - toolFilter: { allow: ['kept'] }, - beforeServer: (ctx) => { - for (const name of ['kept', 'excluded']) { - ctx.tools.register(defineTool({ - name, - description: name, - parameters: {}, - output: { - schema: { type: 'string' }, - render: (_args, value) => [{ type: 'text', text: value }], - }, - execute: async () => name, - })) - } - }, - }) - try { - harness.send({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { cwd: storageDir, provider: 'deepseek-official', model: 'filtered-model' } }) - await harness.waitForFrame(frame => frame.id === 1, 'initialize response') - harness.send({ - jsonrpc: '2.0', - id: 2, - method: 'session/prompt', - params: { sessionId: 'filtered', contentBlocks: [{ type: 'text', text: 'inspect tools' }] }, - }) - await harness.waitForFrame( - frame => frame.method === 'session.status' - && (frame.params as { status?: string } | undefined)?.status === 'idle', - 'filtered session idle status', - ) - - const request = llmServer.requests[0] as { tools?: Array<{ function?: { name?: string } }> } - expect(request.tools?.map(entry => entry.function?.name)).toEqual(['kept']) - } finally { - await harness.dispose() - await rm(storageDir, { recursive: true, force: true }) - } - }) - it('answers shutdown before exiting 0 exactly once, even against a racing second shutdown', async () => { const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-apply-shutdown-')) const harness = await mountPlugin(storageDir, { writeDelayMs: 10 }) diff --git a/packages/sdk/server/tests/server.spec.ts b/packages/sdk/server/tests/server.spec.ts index 471f794f27..9707417e32 100644 --- a/packages/sdk/server/tests/server.spec.ts +++ b/packages/sdk/server/tests/server.spec.ts @@ -14,7 +14,6 @@ import JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl' import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek' import SubagentRuntime, { type SubagentResult, type SubagentRunEndInfo } from '@deepseek-ai/dsh-subagent' import type { JsonRpcTransportPeer } from '@deepseek-ai/dsh-sdk-protocol' -import { defineTool } from '@deepseek-ai/dsh-tools' import { HarnessSdkJsonRpcServer } from '../src/index.ts' class FakeTransport implements JsonRpcTransportPeer { @@ -172,46 +171,6 @@ describe('HarnessSdkJsonRpcServer', () => { } }) - it('allowlists each root session against current and later global tools', { timeout: 15_000 }, async () => { - const storageDir = await mkdtemp(join(tmpdir(), 'dsh-jsonrpc-tool-filter-')) - const llmServer = await mockCompletionServer() - vi.stubEnv('DEEPSEEK_API_KEY', 'test-key') - vi.stubEnv('DEEPSEEK_BASE_URL', llmServer.url) - const ctx = await makeHarness(storageDir) - const tool = (name: string) => defineTool({ - name, - description: name, - parameters: {}, - output: { - schema: { type: 'string' as const }, - render: (_args, value) => [{ type: 'text' as const, text: value }], - }, - execute: async () => name, - }) - ctx.tools.register(tool('kept')) - ctx.tools.register(tool('excluded')) - const server = new HarnessSdkJsonRpcServer(ctx, new FakeTransport(), { - toolFilter: { allow: ['kept'] }, - }) - try { - await server.initialize({ cwd: storageDir, provider: 'deepseek-official', model: 'filtered-model' }) - await server.prompt({ sessionId: 'first', contentBlocks: [{ type: 'text', text: 'first' }] }) - await vi.waitFor(() => { expect(llmServer.requests).toHaveLength(1) }) - ctx.tools.register(tool('future')) - await server.prompt({ sessionId: 'second', contentBlocks: [{ type: 'text', text: 'second' }] }) - await vi.waitFor(() => { expect(llmServer.requests).toHaveLength(2) }) - - expect(llmServer.requests.map((request) => { - const tools = (request as { tools?: Array<{ function?: { name?: string } }> }).tools ?? [] - return tools.map(entry => entry.function?.name) - })).toEqual([['kept'], ['kept']]) - await server.shutdown() - } finally { - await ctx.fiber.dispose() - await rm(storageDir, { recursive: true, force: true }) - } - }) - it('queues overlapping prompts for one session without blocking other sessions', async () => { const mainFollowup = vi.fn() const mainAgent = ({