fix(tools): settle executor calls after unload and validate configuration
This commit is contained in:
1 parent
3b978adcec
commit
e9784c073b
63 files changed
+327
-310
No files matched your search
@@ -399,7 +399,7 @@ describe('Python SDK dsh profile keyless smoke', () => {
|
||||
const patch = join(root, 'failure.yml')
|
||||
await mkdir(home)
|
||||
if (blocked) await writeFile(join(home, 'logs'), 'blocked')
|
||||
await writeFile(patch, '- id: tool-executor-native\n config:\n maxParallelToolCalls: 0\n')
|
||||
await writeFile(patch, '- id: agent-loop\n config:\n maxParallelToolCalls: 0\n')
|
||||
const child = execa(launch.command, [
|
||||
...launch.args, '--profile', 'sdk', '--patch', patch,
|
||||
], {
|
||||
|
||||
@@ -816,7 +816,7 @@ describe('a user preset declared from the shipped cordis rows', () => {
|
||||
expect(queriedText).toContain(`"name": "${toolName}"`)
|
||||
}
|
||||
|
||||
// The `Config` provider reads the booted profile tree: the executor selector declares a Config,
|
||||
// The `Config` provider reads the booted profile tree: the Agent loop declares a Config,
|
||||
// and the bootstrap include row is a carrier. The name filter keeps each page small.
|
||||
type ConfigRow = { id: string; patchId: string; name: string; status: string }
|
||||
type ConfigPage = { data: { entries: ConfigRow[]; total: number; nextOffset: number | null } }
|
||||
@@ -831,9 +831,9 @@ describe('a user preset declared from the shipped cordis rows', () => {
|
||||
expect(page.isError, resultText(page)).toBe(false)
|
||||
return (JSON.parse(resultText(page)) as ConfigPage).data
|
||||
}
|
||||
const toolsRows = await listConfigs({ name: '@deepseek-ai/dsh-tool-executor-native' }, 'copied-preset-inspect-configs')
|
||||
const toolsRows = await listConfigs({ name: '@deepseek-ai/dsh-agent-loop' }, 'copied-preset-inspect-configs')
|
||||
const toolsRow = toolsRows.entries[0]
|
||||
expect(toolsRow).toMatchObject({ patchId: 'tool-executor-native', status: 'schema' })
|
||||
expect(toolsRow).toMatchObject({ patchId: 'agent-loop', status: 'schema' })
|
||||
expect(toolsRows).toMatchObject({ total: 1, nextOffset: null })
|
||||
const includes = await listConfigs({ name: 'cordis:include' }, 'copied-preset-inspect-includes')
|
||||
expect(includes.entries.find(entry => entry.patchId === 'include')).toMatchObject({ status: 'tree' })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { executeToolFixture } from '@deepseek-ai/dsh-tool-testkit'
|
||||
import { executeToolFixture, executeModelToolFixture } from '@deepseek-ai/dsh-tool-testkit'
|
||||
// Boots the shipped Web composition over the built dist this lane already uses
|
||||
// and asserts its catalog, defaults, Loader lifecycle, and one complete Auto
|
||||
// producer-to-tool path. Browser scenarios in this lane own visual behavior.
|
||||
@@ -680,7 +680,7 @@ it('lets a preset producer reach the background-job registry', async () => {
|
||||
// `tool-bash` is a preset row and `tasks` is a host registry; the producer
|
||||
// resolves it with `ctx.get`, so a registry hidden behind a preset realm
|
||||
// fails here — with every task control still listed in the catalog above.
|
||||
const started = await executeToolFixture(ctx, {
|
||||
const started = await executeModelToolFixture(ctx, {
|
||||
signal,
|
||||
callId: ToolCallId('shipped-bash-background'),
|
||||
name: 'bash',
|
||||
|
||||
+5
-25
@@ -162,7 +162,7 @@ export interface Config {
|
||||
## `@deepseek-ai/dsh-agent-tool-presentation`
|
||||
|
||||
- `inject`: `tools`
|
||||
- `source`: [`packages/core/agent-tool-presentation/src/index.ts:26`](../packages/core/agent-tool-presentation/src/index.ts)
|
||||
- `source`: [`packages/core/agent-tool-presentation/src/index.ts:25`](../packages/core/agent-tool-presentation/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/**
|
||||
@@ -3822,40 +3822,19 @@ export interface Config {
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-tool-bash-persistent -->
|
||||
|
||||
<!-- BEGIN GENERATED config-catalog:@deepseek-ai/dsh-tool-executor-native -->
|
||||
<a id="deepseek-aidsh-tool-executor-native"></a>
|
||||
|
||||
## `@deepseek-ai/dsh-tool-executor-native`
|
||||
|
||||
- `inject`: `tools`
|
||||
- `refs`: `Volatile` (`@deepseek-ai/cosmokit`)
|
||||
- `source`: [`packages/core/tool-executor-native/src/index.ts:14`](../packages/core/tool-executor-native/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Native batch concurrency configuration. */
|
||||
export interface Config {
|
||||
/** Maximum overlapping concurrency-safe calls in one batch. */
|
||||
maxParallelToolCalls: Volatile<number>
|
||||
}
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-tool-executor-native -->
|
||||
|
||||
<!-- BEGIN GENERATED config-catalog:@deepseek-ai/dsh-tool-executor-ptc -->
|
||||
<a id="deepseek-aidsh-tool-executor-ptc"></a>
|
||||
|
||||
## `@deepseek-ai/dsh-tool-executor-ptc`
|
||||
|
||||
- `inject`: `tools` · `systemPrompt` · `ptcRuntime`
|
||||
- `refs`: `Volatile` (`@deepseek-ai/cosmokit`)
|
||||
- `source`: [`packages/core/tool-executor-ptc/src/index.ts:19`](../packages/core/tool-executor-ptc/src/index.ts)
|
||||
- `inject`: `tools` · `systemPrompt`
|
||||
- `source`: [`packages/core/tool-executor-ptc/src/config.ts:5`](../packages/core/tool-executor-ptc/src/config.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Program execution limits. */
|
||||
export interface Config {
|
||||
/** Maximum overlapping concurrency-safe calls in one model batch. */
|
||||
maxParallelToolCalls: Volatile<number>
|
||||
/** Maximum overlapping concurrency-safe program subcalls. */
|
||||
maxParallelSubCalls?: number
|
||||
maxParallelSubCalls: number
|
||||
}
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-tool-executor-ptc -->
|
||||
@@ -4769,6 +4748,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co
|
||||
| `@deepseek-ai/dsh-terminal` | — | [`packages/terminal/terminal/src/index.ts`](../packages/terminal/terminal/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-call-timeout-policy` | `tools` | [`packages/guard/timeout-policy/src/index.ts`](../packages/guard/timeout-policy/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-cordis` | `tools` · `cordisInspect` | [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-executor-native` | `tools` | [`packages/core/tool-executor-native/src/index.ts`](../packages/core/tool-executor-native/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-schedule` | `tools` | [`packages/schedule/tool-schedule/src/index.ts`](../packages/schedule/tool-schedule/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-subagent-control` | `tools` · `subagents` · `agents` | [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-working-directory` | `tools` · `workingDirectory` | [`packages/session/tool-working-directory/src/index.ts`](../packages/session/tool-working-directory/src/index.ts) |
|
||||
|
||||
@@ -164,7 +164,7 @@ export interface Config {
|
||||
## `@deepseek-ai/dsh-agent-tool-presentation`
|
||||
|
||||
- `inject`: `tools`
|
||||
- `source`: [`packages/core/agent-tool-presentation/src/index.ts:26`](../packages/core/agent-tool-presentation/src/index.ts)
|
||||
- `source`: [`packages/core/agent-tool-presentation/src/index.ts:25`](../packages/core/agent-tool-presentation/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/**
|
||||
@@ -3824,40 +3824,19 @@ export interface Config {
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-tool-bash-persistent -->
|
||||
|
||||
<!-- BEGIN GENERATED config-catalog:@deepseek-ai/dsh-tool-executor-native -->
|
||||
<a id="deepseek-aidsh-tool-executor-native"></a>
|
||||
|
||||
## `@deepseek-ai/dsh-tool-executor-native`
|
||||
|
||||
- `inject`: `tools`
|
||||
- `refs`: `Volatile` (`@deepseek-ai/cosmokit`)
|
||||
- `source`: [`packages/core/tool-executor-native/src/index.ts:14`](../packages/core/tool-executor-native/src/index.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Native batch concurrency configuration. */
|
||||
export interface Config {
|
||||
/** Maximum overlapping concurrency-safe calls in one batch. */
|
||||
maxParallelToolCalls: Volatile<number>
|
||||
}
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-tool-executor-native -->
|
||||
|
||||
<!-- BEGIN GENERATED config-catalog:@deepseek-ai/dsh-tool-executor-ptc -->
|
||||
<a id="deepseek-aidsh-tool-executor-ptc"></a>
|
||||
|
||||
## `@deepseek-ai/dsh-tool-executor-ptc`
|
||||
|
||||
- `inject`: `tools` · `systemPrompt` · `ptcRuntime`
|
||||
- `refs`: `Volatile` (`@deepseek-ai/cosmokit`)
|
||||
- `source`: [`packages/core/tool-executor-ptc/src/index.ts:19`](../packages/core/tool-executor-ptc/src/index.ts)
|
||||
- `inject`: `tools` · `systemPrompt`
|
||||
- `source`: [`packages/core/tool-executor-ptc/src/config.ts:5`](../packages/core/tool-executor-ptc/src/config.ts)
|
||||
|
||||
```ts config-catalog
|
||||
/** Program execution limits. */
|
||||
export interface Config {
|
||||
/** Maximum overlapping concurrency-safe calls in one model batch. */
|
||||
maxParallelToolCalls: Volatile<number>
|
||||
/** Maximum overlapping concurrency-safe program subcalls. */
|
||||
maxParallelSubCalls?: number
|
||||
maxParallelSubCalls: number
|
||||
}
|
||||
```
|
||||
<!-- END GENERATED config-catalog:@deepseek-ai/dsh-tool-executor-ptc -->
|
||||
@@ -4771,6 +4750,7 @@ export interface Config {
|
||||
| `@deepseek-ai/dsh-terminal` | — | [`packages/terminal/terminal/src/index.ts`](../packages/terminal/terminal/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-call-timeout-policy` | `tools` | [`packages/guard/timeout-policy/src/index.ts`](../packages/guard/timeout-policy/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-cordis` | `tools` · `cordisInspect` | [`packages/extensions/tool-cordis/src/index.ts`](../packages/extensions/tool-cordis/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-executor-native` | `tools` | [`packages/core/tool-executor-native/src/index.ts`](../packages/core/tool-executor-native/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-schedule` | `tools` | [`packages/schedule/tool-schedule/src/index.ts`](../packages/schedule/tool-schedule/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-subagent-control` | `tools` · `subagents` · `agents` | [`packages/subagent/tool-subagent-control/src/index.ts`](../packages/subagent/tool-subagent-control/src/index.ts) |
|
||||
| `@deepseek-ai/dsh-tool-working-directory` | `tools` · `workingDirectory` | [`packages/session/tool-working-directory/src/index.ts`](../packages/session/tool-working-directory/src/index.ts) |
|
||||
|
||||
@@ -1194,7 +1194,6 @@ flowchart TD
|
||||
pkg_spill_policy --> pkg_session
|
||||
pkg_spill_policy --> pkg_spill
|
||||
pkg_spill_policy --> pkg_token_meter
|
||||
pkg_spill_policy --> pkg_tool_executor_ptc
|
||||
pkg_spill_policy --> pkg_tools
|
||||
pkg_session_query_sqlite --> pkg_session
|
||||
pkg_session_query_sqlite --> pkg_session_persistence
|
||||
@@ -1769,7 +1768,7 @@ flowchart TD
|
||||
| [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | `ssh` | [`ssh`](../packages/ssh/ssh), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) |
|
||||
| [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`fs-local`](../packages/fs/fs-local), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) |
|
||||
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`working-directory`](../packages/session/working-directory), [`workspace`](../packages/workspace/workspace) |
|
||||
| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tool-executor-ptc`](../packages/core/tool-executor-ptc), [`tools`](../packages/core/tools) |
|
||||
| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) |
|
||||
| [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) |
|
||||
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) |
|
||||
|
||||
@@ -1196,7 +1196,6 @@ flowchart TD
|
||||
pkg_spill_policy --> pkg_session
|
||||
pkg_spill_policy --> pkg_spill
|
||||
pkg_spill_policy --> pkg_token_meter
|
||||
pkg_spill_policy --> pkg_tool_executor_ptc
|
||||
pkg_spill_policy --> pkg_tools
|
||||
pkg_session_query_sqlite --> pkg_session
|
||||
pkg_session_query_sqlite --> pkg_session_persistence
|
||||
@@ -1771,7 +1770,7 @@ flowchart TD
|
||||
| [`subprocess-ssh`](../packages/ssh/subprocess-ssh) | `ssh` | [`ssh`](../packages/ssh/ssh), [`subprocess`](../packages/subprocess/subprocess), [`subprocess-local`](../packages/subprocess/subprocess-local) |
|
||||
| [`agent-loop-testkit`](../packages/test-support/agent-loop-testkit) | `test-support` | [`agent`](../packages/core/agent), [`agent-loop`](../packages/core/agent-loop), [`fs-local`](../packages/fs/fs-local), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) |
|
||||
| [`subagent`](../packages/subagent/subagent) | `subagent` | [`agent`](../packages/core/agent), [`agent-preset-registry`](../packages/preset/agent-preset-registry), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`scope`](../packages/core/scope), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`session-query`](../packages/session-query/session-query), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval), [`util-time`](../packages/util/time), [`working-directory`](../packages/session/working-directory), [`workspace`](../packages/workspace/workspace) |
|
||||
| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tool-executor-ptc`](../packages/core/tool-executor-ptc), [`tools`](../packages/core/tools) |
|
||||
| [`spill-policy`](../packages/spill/spill-policy) | `spill` | [`attachment`](../packages/attachment/attachment), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`spill`](../packages/spill/spill), [`token-meter`](../packages/llm/token-meter), [`tools`](../packages/core/tools) |
|
||||
| [`session-query-sqlite`](../packages/session-query/session-query-sqlite) | `session-query` | [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-query`](../packages/session-query/session-query) |
|
||||
| [`acp`](../packages/acp/acp) | `acp` | [`agent`](../packages/core/agent), [`attachment`](../packages/attachment/attachment), [`llm`](../packages/llm/llm), [`mcp-client`](../packages/mcp/mcp-client), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`token-meter`](../packages/llm/token-meter), [`user-approval`](../packages/interaction/user-approval) |
|
||||
| [`plugin-manager`](../packages/boot/plugin-manager) | `boot` | [`brand`](../packages/util/brand), [`hmr`](../packages/boot/hmr), [`host-plugin-inventory`](../packages/host/plugin-inventory), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`subprocess`](../packages/subprocess/subprocess), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`user-approval`](../packages/interaction/user-approval) |
|
||||
|
||||
@@ -1830,8 +1830,8 @@
|
||||
en: 08be4349548406a9
|
||||
zh: 8e7cf9966198ef78
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-tool-registry:
|
||||
en: 4fde95c569339141
|
||||
zh: 6fa54e19c24ab7f3
|
||||
en: c59d020828d8211f
|
||||
zh: 154a30cb69e79489
|
||||
/session-persistence-event-catalog/resolved-persistence-types/kind-user:
|
||||
en: a58f339332877b15
|
||||
zh: b7406af7d8197b64
|
||||
|
||||
@@ -7542,7 +7542,7 @@ SHA-256: `c5228b7a55498d84f529591744a0ab26e0b93ade18a70a26db1377a1ecfa6b49`
|
||||
|
||||
SHA-256: `8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543`
|
||||
|
||||
Sources: [`packages/core/tools/src/index.ts:17`](../packages/core/tools/src/index.ts)
|
||||
Sources: [`packages/core/tools/src/index.ts:18`](../packages/core/tools/src/index.ts)
|
||||
|
||||
| Property | Presence | Type |
|
||||
|---|---|---|
|
||||
|
||||
@@ -7544,7 +7544,7 @@ SHA-256: `c5228b7a55498d84f529591744a0ab26e0b93ade18a70a26db1377a1ecfa6b49`
|
||||
|
||||
SHA-256: `8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543`
|
||||
|
||||
来源:[`packages/core/tools/src/index.ts:17`](../packages/core/tools/src/index.ts)
|
||||
来源:[`packages/core/tools/src/index.ts:18`](../packages/core/tools/src/index.ts)
|
||||
|
||||
| 属性 | 存在性 | 类型 |
|
||||
|---|---|---|
|
||||
|
||||
Generated
+1
-1
@@ -24599,7 +24599,7 @@
|
||||
"digest": "8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"packages/core/tools/src/index.ts:17"
|
||||
"packages/core/tools/src/index.ts:18"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -293,7 +293,7 @@ interface ToolExecutionInput {
|
||||
* mode sets this on SDK sub-dispatches so commit-style observers can wait for
|
||||
* the outer `run_code` outcome without receiving its live mutable execution.
|
||||
* The token also marks the call as a transport sub-dispatch rather than a
|
||||
* model-direct call: under `mode: 'ptc'`, only calls WITH a parent may
|
||||
* model-direct call: with the PTC executor selected, only calls with a parent may
|
||||
* execute a native tool name — a model-direct call (no parent) is denied as
|
||||
* `UNKNOWN_TOOL` before the policy pipeline.
|
||||
*/
|
||||
@@ -635,6 +635,7 @@ schemas(scope?: ScopeKey): ToolSchema[]
|
||||
|
||||
/**
|
||||
* Provide the executor for the calling scope; an unscoped contribution is the deployment default.
|
||||
* Rendering any executor-owned section requires the viewing scope's selection to remain available.
|
||||
* @param executor - initialized provider owned by the contributing plugin.
|
||||
* @returns the effect disposer. Unloading leaves the selection unavailable until it is replaced.
|
||||
*/
|
||||
|
||||
@@ -293,7 +293,7 @@ interface ToolExecutionInput {
|
||||
* mode sets this on SDK sub-dispatches so commit-style observers can wait for
|
||||
* the outer `run_code` outcome without receiving its live mutable execution.
|
||||
* The token also marks the call as a transport sub-dispatch rather than a
|
||||
* model-direct call: under `mode: 'ptc'`, only calls WITH a parent may
|
||||
* model-direct call: with the PTC executor selected, only calls with a parent may
|
||||
* execute a native tool name — a model-direct call (no parent) is denied as
|
||||
* `UNKNOWN_TOOL` before the policy pipeline.
|
||||
*/
|
||||
@@ -635,6 +635,7 @@ schemas(scope?: ScopeKey): ToolSchema[]
|
||||
|
||||
/**
|
||||
* Provide the executor for the calling scope; an unscoped contribution is the deployment default.
|
||||
* Rendering any executor-owned section requires the viewing scope's selection to remain available.
|
||||
* @param executor - initialized provider owned by the contributing plugin.
|
||||
* @returns the effect disposer. Unloading leaves the selection unavailable until it is replaced.
|
||||
*/
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 73002a2963b9ea27
|
||||
zh: a67ea382d7c70c61
|
||||
/mount-a-tool-executor/change:
|
||||
en: b47c04b407b204a9
|
||||
zh: f3f54c9b25eb1951
|
||||
en: f732d85027bf0236
|
||||
zh: d6597cde7ba31e47
|
||||
/mount-a-tool-executor/migration:
|
||||
en: 411860d06f1013ea
|
||||
zh: 615e044545d89b55
|
||||
@@ -9,7 +9,7 @@ English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
`@deepseek-ai/dsh-tools` keeps tool registration and executor selection, but no longer accepts `mode` or `maxParallelSubCalls`. Native and PTC execution require `@deepseek-ai/dsh-tool-executor-native` or `@deepseek-ai/dsh-tool-executor-ptc`. Shipped profiles mount an executor; custom compositions must add one. The existing `dsh-agent-tool-presentation` selector remains supported but is deprecated. Enabled `tool-executor-native` and `tool-executor-ptc` rows are required at startup; invalid executor configuration now stops startup.
|
||||
`@deepseek-ai/dsh-tools` keeps tool registration and executor selection, but rejects the removed `mode` and `maxParallelSubCalls` configuration fields at load time. Native and PTC execution require `@deepseek-ai/dsh-tool-executor-native` or `@deepseek-ai/dsh-tool-executor-ptc`. Shipped profiles mount an executor; custom compositions must add one. The existing `dsh-agent-tool-presentation` selector remains supported but is deprecated. Enabled `tool-executor-native` and `tool-executor-ptc` rows are required at startup; invalid executor configuration now stops startup.
|
||||
|
||||
## Migration
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ description: "工具执行模式从 dsh-tools 配置移至显式执行器插件
|
||||
|
||||
## 变更
|
||||
|
||||
`@deepseek-ai/dsh-tools` 保留工具注册和执行器选择,但不再接受 `mode` 或 `maxParallelSubCalls`。原生和 PTC 执行分别要求 `@deepseek-ai/dsh-tool-executor-native` 或 `@deepseek-ai/dsh-tool-executor-ptc`。内置 profile 已挂载执行器,自定义组合必须添加一个。现有 `dsh-agent-tool-presentation` 选择器仍受支持,但已弃用。 启用的 `tool-executor-native` 和 `tool-executor-ptc` 条目属于启动必需项;无效的执行器配置会终止启动。
|
||||
`@deepseek-ai/dsh-tools` 保留工具注册和执行器选择,但加载时会拒绝已移除的 `mode` 和 `maxParallelSubCalls` 配置字段。原生和 PTC 执行分别要求 `@deepseek-ai/dsh-tool-executor-native` 或 `@deepseek-ai/dsh-tool-executor-ptc`。内置 profile 已挂载执行器,自定义组合必须添加一个。现有 `dsh-agent-tool-presentation` 选择器仍受支持,但已弃用。启用的 `tool-executor-native` 和 `tool-executor-ptc` 条目属于启动必需项;无效的执行器配置会终止启动。
|
||||
|
||||
## 迁移
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@deepseek-ai/dsh-brand": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-executor-native": "workspace:*",
|
||||
"@deepseek-ai/dsh-util-values": "workspace:*",
|
||||
"@deepseek-ai/schemastery": "workspace:~",
|
||||
"zod": "4.4.3"
|
||||
@@ -95,6 +94,7 @@
|
||||
"@deepseek-ai/dsh-subagent": "workspace:*",
|
||||
"@deepseek-ai/dsh-spill-local": "workspace:*",
|
||||
"@deepseek-ai/dsh-system-prompt": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-executor-native": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-fs": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-testkit": "workspace:*",
|
||||
"@deepseek-ai/dsh-tools": "workspace:*",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
/**
|
||||
* Exercises scheduler ordering and cancellation with deterministic gated tools.
|
||||
* ACP expected outputs own transcript-facing coverage.
|
||||
*/
|
||||
import type { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
|
||||
import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native'
|
||||
import * as PtcToolExecutor from '@deepseek-ai/dsh-tool-executor-ptc'
|
||||
@@ -37,13 +37,13 @@ async function harness(adapter: MockAdapter, maxParallelToolCalls?: number, exec
|
||||
await ctx.plugin(ToolRuntime)
|
||||
let toolPipeline!: ToolExecutionPipeline
|
||||
await ctx.plugin({
|
||||
inject: ['tools'], Config: NativeToolExecutor.Config,
|
||||
apply(owner: Context, config: NativeToolExecutor.Config) {
|
||||
const executor = new NativeToolExecutor.NativeToolExecutor(owner, () => config.maxParallelToolCalls.get())
|
||||
inject: ['tools'],
|
||||
apply(owner: Context) {
|
||||
const executor = new NativeToolExecutor.NativeToolExecutor(owner, () => executorLimit ?? 10)
|
||||
toolPipeline = executor['pipeline']
|
||||
owner.tools.setExecutor(executor)
|
||||
},
|
||||
}, { ...executorLimit === undefined ? {} : { maxParallelToolCalls: executorLimit } })
|
||||
})
|
||||
await ctx.plugin(AgentRegistry)
|
||||
await ctx.plugin(AgentLoop, {
|
||||
agents: [],
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
en: 1550e3dcc5a3aa1e
|
||||
zh: 47b2d1e0e3e3cbf2
|
||||
/deepseek-ai-dsh-agent-tool-presentation/use-this-package/what-ptc-mode-requires:
|
||||
en: d5004b2caa31b245
|
||||
zh: 32619d7e03c7c2ac
|
||||
en: 3ec33f18b3ac0859
|
||||
zh: 92bf371bfbbc511d
|
||||
/deepseek-ai-dsh-agent-tool-presentation/use-this-package/one-presentation-per-agent:
|
||||
en: 46bf80f051175567
|
||||
zh: 8f5e1623da5d1520
|
||||
@@ -36,8 +36,8 @@
|
||||
en: 89ecdb3d5d032e7f
|
||||
zh: 64ae2feb4344c679
|
||||
/deepseek-ai-dsh-agent-tool-presentation/understand-the-implementation/behavior-notes:
|
||||
en: 7996925328f0e771
|
||||
zh: 40146455aac119d2
|
||||
en: 4a44ac4057732829
|
||||
zh: 813d3ec7627146fd
|
||||
/deepseek-ai-dsh-agent-tool-presentation/further-exploration:
|
||||
en: 6a1d0096e4da25e4
|
||||
zh: 9d5de770addb69d2
|
||||
|
||||
@@ -45,7 +45,7 @@ The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-a
|
||||
|
||||
### What PTC mode requires
|
||||
|
||||
Selecting `ptc` needs a composed PTC runtime (`ctx.ptcRuntime`) whose language has a registered SDK renderer — the TypeScript runtime ships via [`dsh-ptc-runtime-node`](../../ptc-runtime/ptc-runtime-node/README.md), and both the TypeScript and Python SDK renderers are built into `dsh-tool-executor-ptc`. A preset that selects a PTC mode against a deployment composing no such runtime refuses to mount, naming this row, so the failure lands where the operator can act instead of at the session's first request.
|
||||
Selecting `ptc` needs a composed PTC runtime (`ctx.ptcRuntime`) whose language has a registered SDK renderer — the TypeScript runtime ships via [`dsh-ptc-runtime-node`](../../ptc-runtime/ptc-runtime-node/README.md), and both the TypeScript and Python SDK renderers are built into `dsh-tool-executor-ptc`. Selection takes effect even when the runtime is missing; prompt assembly or program execution then reports the missing runtime instead of inheriting native execution.
|
||||
|
||||
### One presentation per agent
|
||||
|
||||
@@ -73,7 +73,7 @@ The tool registry cannot move into a preset: its consumers are all host-plane
|
||||
|
||||
### Behavior notes
|
||||
|
||||
`native` applies immediately. A PTC mode instead waits for `ctx.ptcRuntime`, a host-plane service: a preset selecting PTC mode against a deployment composing no runtime holds this row pending, and `dsh-agent-preset-registry` refuses the mount naming this id. `setExecutor` is itself the effect, so the declaration unwinds with this row without a second wrapper owning it.
|
||||
Both modes mount their executor immediately. PTC checks runtime availability when describing or executing a program. The mounted executor owns its scoped registration, which is withdrawn with this selector's plugin lifetime.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ kind: "package-reference"
|
||||
|
||||
### PTC 模式需要什么
|
||||
|
||||
选择 `ptc` 需要已组合的 PTC 运行时(`ctx.ptcRuntime`),且其语言有已注册的 SDK 渲染器——TypeScript 运行时经 [`dsh-ptc-runtime-node`](../../ptc-runtime/ptc-runtime-node/README.zh.md) 交付,TypeScript 与 Python 的 SDK 渲染器都内置在 `dsh-tools` 中。针对未组装此类运行时的部署选择 PTC 模式的 preset 会拒绝挂载并点名这一行,使失败落在操作者可以行动的地方,而不是落在会话的第一次请求上。
|
||||
选择 `ptc` 需要已组合的 PTC 运行时(`ctx.ptcRuntime`),且其语言有已注册的 SDK 渲染器——TypeScript 运行时经 [`dsh-ptc-runtime-node`](../../ptc-runtime/ptc-runtime-node/README.zh.md) 交付,TypeScript 与 Python 的 SDK 渲染器都内置在 `dsh-tool-executor-ptc` 中。即使缺少运行时,选择也会生效;随后提示词组装或程序执行会报告缺少运行时,而不会继承原生执行方式。
|
||||
|
||||
### 每个 agent 只声明一次呈现方式
|
||||
|
||||
@@ -73,7 +73,7 @@ kind: "package-reference"
|
||||
|
||||
### 行为说明
|
||||
|
||||
`native` 立即生效。PTC 模式则等待 `ctx.ptcRuntime`——这是一个宿主平面服务:针对未组装运行时的部署选择 PTC mode 的 preset 会让这一行停在 pending,`dsh-agent-preset-registry` 会指名此 id 拒绝挂载。`setExecutor` 本身就是 effect,因此该声明随这一行撤销,无需第二个包装层拥有它。
|
||||
两种模式都会立即挂载各自的执行器。PTC 在描述或执行程序时检查运行时是否可用。挂载的执行器拥有作用域注册,并随此选择器的插件生命周期一同撤销。
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -13,9 +13,8 @@ import * as PtcToolExecutor from '@deepseek-ai/dsh-tool-executor-ptc'
|
||||
export const name = 'tool-presentation'
|
||||
|
||||
/**
|
||||
* Required services. `ptcRuntime` 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.
|
||||
* Tool selection does not require a program runtime; the PTC executor
|
||||
* reports a missing runtime when its description or execution is requested.
|
||||
*/
|
||||
export const inject = ['tools']
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/**
|
||||
* 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 PTC runtime
|
||||
* stops at mount — where a preset's activation audit can name it — rather
|
||||
* than at the first prompt assembly.
|
||||
* agent, and a missing PTC runtime rejects the selected scope's prompt
|
||||
* instead of inheriting the native executor.
|
||||
*/
|
||||
|
||||
import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native'
|
||||
@@ -68,8 +67,7 @@ describe('the tool-presentation row', () => {
|
||||
})
|
||||
|
||||
it('declares the services it uses without holding a PTC runtime hostage', () => {
|
||||
// A `native` row must mount where no runtime is composed, so the wait is
|
||||
// conditional inside apply rather than static metadata.
|
||||
// Native execution does not require a program runtime.
|
||||
expect(inject).toEqual(['tools'])
|
||||
})
|
||||
|
||||
@@ -95,17 +93,13 @@ describe('the tool-presentation row', () => {
|
||||
await expect(ctx.systemPrompt.assemble({ scope: agent })).rejects.toThrow('selected tool executor is unavailable')
|
||||
})
|
||||
|
||||
it('waits for a PTC runtime the deployment does not compose', async () => {
|
||||
it('rejects PTC assembly when the deployment does not compose its runtime', async () => {
|
||||
const ctx = await host({ runtime: false })
|
||||
|
||||
const { agent, row } = await mount(ctx, { mode: 'ptc' })
|
||||
|
||||
// Pending, not applied: `dsh-agent-preset-registry` 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('ptcRuntime')).toBeUndefined()
|
||||
const assembly = await ctx.systemPrompt.assemble({ scope: agent })
|
||||
expect(assembly.tools.map(tool => tool.name)).toEqual(['echo'])
|
||||
await expect(ctx.systemPrompt.assemble({ scope: agent })).rejects.toThrow('requires a PTC runtime')
|
||||
})
|
||||
|
||||
it('applies once the runtime arrives', async () => {
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
en: d152484eb41ac6b4
|
||||
zh: 09388d293f9be9cb
|
||||
/deepseek-ai-dsh-tool-executor-native/use-this-package:
|
||||
en: 176f69450bacc28e
|
||||
zh: fc3c2d876195a4a4
|
||||
en: 0e73b1a49308a0f6
|
||||
zh: 968b3ee68284d849
|
||||
/deepseek-ai-dsh-tool-executor-native/understand-the-implementation:
|
||||
en: d005036097c11788
|
||||
zh: 65fcaec551dc1697
|
||||
en: 6a06c6b318f58436
|
||||
zh: 4f572feca095e3a6
|
||||
/deepseek-ai-dsh-tool-executor-native/further-exploration:
|
||||
en: e8ae407b0ecdc5d3
|
||||
zh: 022a8a714d24da4b
|
||||
|
||||
@@ -29,13 +29,9 @@ Add this row to a composition with `dsh-tools` mounted. The nearest scoped execu
|
||||
|
||||
```yaml
|
||||
- name: '@deepseek-ai/dsh-tool-executor-native'
|
||||
config:
|
||||
maxParallelToolCalls: 10
|
||||
```
|
||||
|
||||
For Agent steps, configure [agent-loop concurrency](../agent-loop/README.md); the loop always forwards its resolved limit to `execute()`. The executor's `maxParallelToolCalls` supplies the limit only when the caller omits it. This fallback is volatile, defaults to `10`, and is sampled for the next tool group. It must be a positive integer; `1` selects serial execution.
|
||||
|
||||
[Configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tool-executor-native)
|
||||
Configure [agent-loop concurrency](../agent-loop/README.md) for Agent steps; the loop reads the limit once per batch. This plugin has no configuration fields. Direct `NativeToolExecutor` callers may pass a constructor callback, defaulting to `10`, which is sampled for each tool group when the batch omits `maxParallelToolCalls`. Limits must be positive integers; `1` selects serial execution.
|
||||
|
||||
-----
|
||||
|
||||
@@ -47,7 +43,7 @@ For Agent steps, configure [agent-loop concurrency](../agent-loop/README.md); th
|
||||
|
||||
The executor receives the entire raw call batch and owns parsing, scheduling, cancellation, and optional model-step logging. It reuses the shared invocation pipeline for policy, tool bodies, and result notifications. After abort, it waits for started work and returns ordered error results for skipped calls.
|
||||
|
||||
- [`src/index.ts`](src/index.ts) — Validated configuration and scoped registration; tool descriptions and batch execution.
|
||||
- [`src/index.ts`](src/index.ts) — Scoped executor registration; tool descriptions and batch execution.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -29,13 +29,9 @@ kind: "package-reference"
|
||||
|
||||
```yaml
|
||||
- name: '@deepseek-ai/dsh-tool-executor-native'
|
||||
config:
|
||||
maxParallelToolCalls: 10
|
||||
```
|
||||
|
||||
Agent 步骤使用 [agent-loop 并发配置](../agent-loop/README.zh.md),循环始终向 `execute()` 传入解析后的上限。只有调用方未传入上限时,才使用执行器的 `maxParallelToolCalls`。这个后备配置是 volatile 字段,默认值为 `10`,在下一个工具组开始时读取。值必须是正整数;`1` 表示串行执行。
|
||||
|
||||
[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tool-executor-native)
|
||||
Agent 步骤使用 [agent-loop 并发配置](../agent-loop/README.zh.md),循环在每个批次开始时读取一次上限。本插件没有配置字段。直接构造 `NativeToolExecutor` 的调用方可以传入回调,默认返回 `10`;批次未指定 `maxParallelToolCalls` 时,每个工具组读取该回调。上限必须是正整数;`1` 表示串行执行。
|
||||
|
||||
-----
|
||||
|
||||
@@ -47,7 +43,7 @@ Agent 步骤使用 [agent-loop 并发配置](../agent-loop/README.zh.md),循
|
||||
|
||||
执行器接收完整的原始调用批次,负责解析、调度、取消和可选的模型步骤日志。它复用共享调用流水线来执行策略、工具和结果通知。中止后,它等待已开始的工作完成,并为跳过的调用返回有序错误结果。
|
||||
|
||||
- [`src/index.ts`](src/index.ts) — 配置校验和作用域注册;工具描述和批次执行。
|
||||
- [`src/index.ts`](src/index.ts) — 作用域执行器注册;工具描述和批次执行。
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -28,17 +28,12 @@
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"@deepseek-ai/cosmokit": "workspace:~",
|
||||
"@deepseek-ai/dsh-llm": "workspace:*",
|
||||
"@deepseek-ai/dsh-scope": "workspace:*",
|
||||
"@deepseek-ai/dsh-tools": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@deepseek-ai/schemastery": "workspace:~"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"@deepseek-ai/cosmokit": "workspace:~",
|
||||
"@deepseek-ai/dsh-llm": "workspace:*",
|
||||
"@deepseek-ai/dsh-scope": "workspace:*",
|
||||
"@deepseek-ai/dsh-tools": "workspace:*"
|
||||
|
||||
@@ -3,15 +3,13 @@ import type { Context } from '@deepseek-ai/cordis'
|
||||
import { projectToolSchema } from '@deepseek-ai/dsh-tools'
|
||||
import type { ToolDefinition, ToolExecutor, ToolModelInput, ToolBatchInput, ToolBatchResult } from '@deepseek-ai/dsh-tools'
|
||||
import { ToolExecutionPipeline, executeToolBatch } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
import type { Volatile } from '@deepseek-ai/cosmokit'
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
|
||||
/** Executes visible capabilities directly and owns their complete batch settlement. */
|
||||
export class NativeToolExecutor implements ToolExecutor {
|
||||
private readonly pipeline: ToolExecutionPipeline
|
||||
|
||||
constructor(ctx: Context, private readonly maxParallel: () => number = () => 10) {
|
||||
this.pipeline = new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) })
|
||||
this.pipeline = new ToolExecutionPipeline(ctx)
|
||||
}
|
||||
|
||||
describe(tools: readonly ToolDefinition[]): ToolModelInput {
|
||||
@@ -28,22 +26,10 @@ export const name = 'native-tool-executor'
|
||||
/** Tool registration and scoped dispatch. */
|
||||
export const inject = ['tools']
|
||||
|
||||
/** Native batch concurrency configuration. */
|
||||
export interface Config {
|
||||
/** Maximum overlapping concurrency-safe calls in one batch. */
|
||||
maxParallelToolCalls: Volatile<number>
|
||||
}
|
||||
|
||||
/** Validated live concurrency limit; one makes each batch serial. */
|
||||
export const Config: z<{ maxParallelToolCalls?: number }, Config> = z.object({
|
||||
maxParallelToolCalls: z.number().step(1).min(1).default(10).volatile(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Select native execution in the mounting scope.
|
||||
* @param ctx - context owning this executor's selection and lifetime.
|
||||
* @param config - live batch concurrency configuration.
|
||||
*/
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
ctx.tools.setExecutor(new NativeToolExecutor(ctx, () => config.maxParallelToolCalls.get()))
|
||||
export function apply(ctx: Context): void {
|
||||
ctx.tools.setExecutor(new NativeToolExecutor(ctx))
|
||||
}
|
||||
@@ -9,17 +9,17 @@
|
||||
en: e253ed9cec03a396
|
||||
zh: 922fde282e2b0fd2
|
||||
/deepseek-ai-dsh-tool-executor-ptc/summary:
|
||||
en: 60fb16b4d443d265
|
||||
zh: 6b0df6471c370c6b
|
||||
en: 66e159c308afb6c1
|
||||
zh: 29b1a646ae7f918a
|
||||
/deepseek-ai-dsh-tool-executor-ptc/table-of-contents:
|
||||
en: d152484eb41ac6b4
|
||||
zh: 09388d293f9be9cb
|
||||
/deepseek-ai-dsh-tool-executor-ptc/use-this-package:
|
||||
en: 5cfc28690358a419
|
||||
zh: d066d078ca649022
|
||||
en: 488f394426b3aaa0
|
||||
zh: a15185f91a1c342c
|
||||
/deepseek-ai-dsh-tool-executor-ptc/understand-the-implementation:
|
||||
en: f054dc0faabb468f
|
||||
zh: 10f13508bfa9ef84
|
||||
en: 9d4f61c351945d30
|
||||
zh: 1c575526230b0e02
|
||||
/deepseek-ai-dsh-tool-executor-ptc/understand-the-implementation/ptc-mode:
|
||||
en: 11d32e05ba397f86
|
||||
zh: 4e071ec4e6222d9d
|
||||
|
||||
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
||||
|
||||
## Summary
|
||||
|
||||
Use this executor when models should call tools through `run_code` programs. Mount a compatible PTC runtime and tool registry first. Scoped restrictions determine the generated SDK; only `run_code` is directly callable. Configure root-call and program-subcall concurrency independently.
|
||||
Use this executor when models should call tools through `run_code` programs. Mount a compatible PTC runtime and tool registry first. Scoped restrictions determine the generated SDK; only `run_code` is directly callable. Configure concurrency for parallel-safe program subcalls.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -30,13 +30,12 @@ Add this row to a composition with `dsh-tools` mounted. The nearest scoped execu
|
||||
```yaml
|
||||
- name: '@deepseek-ai/dsh-tool-executor-ptc'
|
||||
config:
|
||||
maxParallelToolCalls: 10
|
||||
maxParallelSubCalls: 10
|
||||
```
|
||||
|
||||
For Agent steps, configure [agent-loop concurrency](../agent-loop/README.md); the loop always forwards its resolved limit to `execute()`. The executor's `maxParallelToolCalls` supplies the limit only when the caller omits it. This fallback is volatile, defaults to `10`, and is sampled for the next tool group. It must be a positive integer; `1` selects serial execution.
|
||||
Root `run_code` calls are exclusive and execute serially. `maxParallelSubCalls` limits parallel-safe nested calls within each program and defaults to `10`; it must be a positive integer, with `1` selecting serial execution. Exclusive calls form barriers.
|
||||
|
||||
This package also requires `ctx.ptcRuntime` and `ctx.systemPrompt`; Agent-owned programs need `ctx.workingDirectory`. `maxParallelSubCalls` limits overlapping parallel-safe calls within each program and defaults to `10`.
|
||||
This package also requires `ctx.ptcRuntime` and `ctx.systemPrompt`; Agent-owned programs need `ctx.workingDirectory`. A missing runtime rejects prompt assembly or execution; the selected PTC executor never falls back to native execution.
|
||||
|
||||
Import durable event payloads from `@deepseek-ai/dsh-tool-executor-ptc/types`; this entry is safe for detached readers and client compilation. Host policy extensions import `PtcDispatchLog`, `tools/ptc-dispatch-log`, and the `ptc-mode` message-source declaration through a type-only import of the package entry, without mounting an executor.
|
||||
|
||||
@@ -50,7 +49,7 @@ Import durable event payloads from `@deepseek-ai/dsh-tool-executor-ptc/types`; t
|
||||
<details>
|
||||
<summary>Implementation internals — click to expand</summary>
|
||||
|
||||
The executor receives the entire raw call batch and owns parsing, scheduling, cancellation, and optional model-step logging. It reuses the shared invocation pipeline for policy, tool bodies, and result notifications. After abort, it waits for started work and returns ordered error results for skipped calls.
|
||||
The executor receives the entire raw call batch and owns parsing, scheduling, cancellation, and optional model-step logging. It reuses the shared invocation pipeline for policy, tool bodies, and result notifications. After abort, it waits for started work and returns ordered error results for skipped calls. Unloading the executor aborts active sandboxed programs and waits for their nested calls to settle. Accepted batches still settle through that executor, potentially with cancellation errors.
|
||||
|
||||
### PTC mode
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ kind: "package-reference"
|
||||
|
||||
## 概述
|
||||
|
||||
当模型应通过 `run_code` 程序调用工具时,使用此执行器。先挂载兼容的 PTC 运行时和工具注册表。作用域限制决定生成的 SDK;只有 `run_code` 能被直接调用。根调用和程序子调用的并发限制分别配置。
|
||||
当模型应通过 `run_code` 程序调用工具时,使用此执行器。先挂载兼容的 PTC 运行时和工具注册表。作用域限制决定生成的 SDK;只有 `run_code` 能被直接调用。可配置并行安全的程序子调用并发上限。
|
||||
|
||||
## 目录
|
||||
|
||||
@@ -30,13 +30,12 @@ kind: "package-reference"
|
||||
```yaml
|
||||
- name: '@deepseek-ai/dsh-tool-executor-ptc'
|
||||
config:
|
||||
maxParallelToolCalls: 10
|
||||
maxParallelSubCalls: 10
|
||||
```
|
||||
|
||||
Agent 步骤使用 [agent-loop 并发配置](../agent-loop/README.zh.md),循环始终向 `execute()` 传入解析后的上限。只有调用方未传入上限时,才使用执行器的 `maxParallelToolCalls`。这个后备配置是 volatile 字段,默认值为 `10`,在下一个工具组开始时读取。值必须是正整数;`1` 表示串行执行。
|
||||
根 `run_code` 调用是独占调用,按串行执行。`maxParallelSubCalls` 限制每个程序中并行安全的嵌套调用数,默认值为 `10`;值必须是正整数,`1` 表示串行执行。独占调用构成屏障。
|
||||
|
||||
本包还要求 `ctx.ptcRuntime` 和 `ctx.systemPrompt`;带 Agent 的程序要求 `ctx.workingDirectory`。`maxParallelSubCalls` 限制每个程序内重叠的并行安全调用,默认值为 `10`。
|
||||
本包还要求 `ctx.ptcRuntime` 和 `ctx.systemPrompt`;带 Agent 的程序要求 `ctx.workingDirectory`。缺少运行时时,提示词组装或执行会失败;选中的 PTC 执行器不会回退到原生执行。
|
||||
|
||||
持久化事件载荷从 `@deepseek-ai/dsh-tool-executor-ptc/types` 导入;该入口可用于独立读取者与 Client 编译。Host 策略扩展通过包主入口的仅类型导入获取 `PtcDispatchLog`、`tools/ptc-dispatch-log` 与 `ptc-mode` 消息来源声明,无需挂载执行器。
|
||||
|
||||
@@ -50,7 +49,7 @@ Agent 步骤使用 [agent-loop 并发配置](../agent-loop/README.zh.md),循
|
||||
<details>
|
||||
<summary>实现细节——点击展开</summary>
|
||||
|
||||
执行器接收完整的原始调用批次,负责解析、调度、取消和可选的模型步骤日志。它复用共享调用流水线来执行策略、工具和结果通知。中止后,它等待已开始的工作完成,并为跳过的调用返回有序错误结果。
|
||||
执行器接收完整的原始调用批次,负责解析、调度、取消和可选的模型步骤日志。它复用共享调用流水线来执行策略、工具和结果通知。中止后,它等待已开始的工作完成,并为跳过的调用返回有序错误结果。卸载执行器会中止运行中的沙箱程序,并等待其嵌套调用结束。已接纳批次仍由该执行器结算,但可能返回取消错误。
|
||||
|
||||
### PTC mode
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"@deepseek-ai/cosmokit": "workspace:~",
|
||||
"@deepseek-ai/dsh-agent": "workspace:*",
|
||||
"@deepseek-ai/dsh-llm": "workspace:*",
|
||||
"@deepseek-ai/dsh-ptc-runtime": "workspace:*",
|
||||
@@ -52,7 +51,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"@deepseek-ai/cosmokit": "workspace:~",
|
||||
"@deepseek-ai/dsh-agent": "workspace:*",
|
||||
"@deepseek-ai/dsh-agent-loop-testkit": "workspace:*",
|
||||
"@deepseek-ai/dsh-llm": "workspace:*",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/** Configuration shared by plugin mounting and direct executor construction. @module */
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
|
||||
/** Program execution limits. */
|
||||
export interface Config {
|
||||
/** Maximum overlapping concurrency-safe program subcalls. */
|
||||
maxParallelSubCalls: number
|
||||
}
|
||||
|
||||
/** Validated program subcall concurrency; one makes subcalls serial. */
|
||||
export const Config: z<Partial<Config>, Config> = z.object({
|
||||
maxParallelSubCalls: z.natural().min(1).default(10),
|
||||
})
|
||||
@@ -1,14 +1,12 @@
|
||||
/** PTC execution plugin for the mounting Agent, preset, or deployment scope. @module */
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type { ScopeKey } from '@deepseek-ai/dsh-scope'
|
||||
import { RUN_CODE_NAME, ToolNotFoundError, projectToolSchema } from '@deepseek-ai/dsh-tools'
|
||||
import type { ToolDefinition, ToolExecutor, ToolModelInput, ToolPromptSection, ToolBatchInput, ToolBatchResult } from '@deepseek-ai/dsh-tools'
|
||||
import { ToolExecutionPipeline, executeToolBatch } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
import { createRunCodeTool, requirePtcRuntime, type PtcSdkLanguage } from './ptc.ts'
|
||||
import { renderToolsSdk, type ToolSdkSchema } from './ts-types.ts'
|
||||
import { renderToolsSdkPy } from './py-types.ts'
|
||||
import type { Volatile } from '@deepseek-ai/cosmokit'
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
import { Config } from './config.ts'
|
||||
|
||||
const PTC_ONLY_INSTRUCTION = `\`${RUN_CODE_NAME}\` is the only tool you can call directly — a tool call naming any other tool fails. Reach every tool the SDK declares below from inside the program.`
|
||||
|
||||
@@ -24,17 +22,15 @@ export class PtcExecutor implements ToolExecutor {
|
||||
private readonly maxParallelSubCalls: number
|
||||
private ptcTransport: ToolDefinition | undefined
|
||||
|
||||
constructor(private readonly ctx: Context, config: Pick<Config, 'maxParallelSubCalls'> = {}, private readonly maxParallel: () => number = () => 10) {
|
||||
constructor(private readonly ctx: Context, config: Partial<Config> = {}, private readonly maxParallel: () => number = () => 10) {
|
||||
this.maxParallelSubCalls = Config(config).maxParallelSubCalls
|
||||
const tools = ctx.tools
|
||||
this.sections = [
|
||||
{ name: 'tools:ptc-only', order: ctx.systemPrompt.getSectionOrder('PTC_ONLY'), text: PTC_ONLY_INSTRUCTION, interpolate: false },
|
||||
{ name: 'tools:sdk', order: ctx.systemPrompt.getSectionOrder('TOOLS_SDK'), text: tools => this.renderSdk(tools), interpolate: false },
|
||||
]
|
||||
this.maxParallelSubCalls = config.maxParallelSubCalls ?? 10
|
||||
if (!Number.isInteger(this.maxParallelSubCalls) || this.maxParallelSubCalls < 1) {
|
||||
throw new Error('maxParallelSubCalls must be a positive integer')
|
||||
}
|
||||
this.pipeline = new ToolExecutionPipeline(ctx, {
|
||||
get: (name, scope) => this.get(name, scope),
|
||||
get: (name, scope) => name === RUN_CODE_NAME ? this.requirePtcTransport() : tools.get(name, scope),
|
||||
reject: (name, nested) => !nested && name !== RUN_CODE_NAME
|
||||
? new ToolNotFoundError(name, `only \`${RUN_CODE_NAME}\` is callable directly — call \`${name}\` from inside a \`${RUN_CODE_NAME}\` program instead`)
|
||||
: undefined,
|
||||
@@ -54,10 +50,6 @@ export class PtcExecutor implements ToolExecutor {
|
||||
})), runtime.programFormat)
|
||||
}
|
||||
|
||||
private get(name: string, scope?: ScopeKey): ToolDefinition | undefined {
|
||||
return name === RUN_CODE_NAME ? this.requirePtcTransport() : this.ctx.tools.get(name, scope)
|
||||
}
|
||||
|
||||
execute(input: ToolBatchInput): Promise<ToolBatchResult> {
|
||||
return executeToolBatch(input, this.pipeline, this.maxParallel)
|
||||
}
|
||||
@@ -69,6 +61,7 @@ export class PtcExecutor implements ToolExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
export { Config } from './config.ts'
|
||||
export { CodeRunFailedError } from './errors.ts'
|
||||
export type { PtcDispatchLog } from './types.ts'
|
||||
export type { PtcDispatchStartEventData, PtcDispatchEventData } from './session-types.ts'
|
||||
@@ -77,22 +70,8 @@ export { jsonSchemaToPy, renderToolsSdkPy } from './py-types.ts'
|
||||
|
||||
/** Cordis plugin name. */
|
||||
export const name = 'ptc-tool-executor'
|
||||
/** The program provider activates with its runtime. */
|
||||
export const inject = ['tools', 'systemPrompt', 'ptcRuntime']
|
||||
|
||||
/** Program execution limits. */
|
||||
export interface Config {
|
||||
/** Maximum overlapping concurrency-safe calls in one model batch. */
|
||||
maxParallelToolCalls: Volatile<number>
|
||||
/** Maximum overlapping concurrency-safe program subcalls. */
|
||||
maxParallelSubCalls?: number
|
||||
}
|
||||
|
||||
/** Validated program execution configuration. */
|
||||
export const Config: z<{ maxParallelToolCalls?: number; maxParallelSubCalls?: number }, Config> = z.object({
|
||||
maxParallelToolCalls: z.number().step(1).min(1).default(10).volatile(),
|
||||
maxParallelSubCalls: z.natural().min(1).default(10),
|
||||
})
|
||||
/** Register the selected executor even when its runtime is unavailable. */
|
||||
export const inject = ['tools', 'systemPrompt']
|
||||
|
||||
/**
|
||||
* Select PTC execution in the mounting scope.
|
||||
@@ -100,5 +79,5 @@ export const Config: z<{ maxParallelToolCalls?: number; maxParallelSubCalls?: nu
|
||||
* @param config - program subcall concurrency limits.
|
||||
*/
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
ctx.tools.setExecutor(new PtcExecutor(ctx, config, () => config.maxParallelToolCalls.get()))
|
||||
ctx.tools.setExecutor(new PtcExecutor(ctx, config))
|
||||
}
|
||||
@@ -15,8 +15,8 @@ export class PtcCalls {
|
||||
private disposed = false
|
||||
|
||||
/**
|
||||
* Observe policy changes for the owning tool registry.
|
||||
* @param ctx - Registry lifecycle and Session events.
|
||||
* Observe policy changes for the owning executor.
|
||||
* @param ctx - Executor lifecycle and Session events.
|
||||
*/
|
||||
constructor(ctx: Context) {
|
||||
ctx.on('session/event', (session, event) => {
|
||||
@@ -28,7 +28,7 @@ export class PtcCalls {
|
||||
ctx.effect(() => async () => {
|
||||
this.disposed = true
|
||||
const calls = [...this.calls.values()].flatMap(calls => [...calls])
|
||||
for (const call of calls) call.controller.abort(new Error('PTC tool registry disposed'))
|
||||
for (const call of calls) call.controller.abort(new Error('PTC tool executor disposed'))
|
||||
await Promise.all(calls.map(call => call.done))
|
||||
}, 'PTC calls')
|
||||
}
|
||||
@@ -48,7 +48,7 @@ export class PtcCalls {
|
||||
await Promise.all(pending.map(call => call.done))
|
||||
}
|
||||
signal.throwIfAborted()
|
||||
if (this.disposed) throw new Error('PTC tool registry disposed')
|
||||
if (this.disposed) throw new Error('PTC tool executor disposed')
|
||||
const controller = new AbortController()
|
||||
const done = Promise.withResolvers<undefined>()
|
||||
const call = { mode: mode(), controller, done: done.promise }
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {} from '@deepseek-ai/dsh-user-approval'
|
||||
import type {} from '@deepseek-ai/dsh-working-directory'
|
||||
import type {} from '@deepseek-ai/dsh-sandbox-policy'
|
||||
import { deepFreeze, snapshotJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values'
|
||||
import { defineTool, parameterSchemaSpecToJsonSchema } from '@deepseek-ai/dsh-tools'
|
||||
import { defineTool, parameterSchemaSpecToJsonSchema, RUN_CODE_NAME } from '@deepseek-ai/dsh-tools'
|
||||
import type { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
import { toolErrorMessage } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
import { scopeTarget } from '@deepseek-ai/dsh-scope'
|
||||
@@ -29,8 +29,7 @@ import type { ToolDefinition, ToolExecutionResult, ToolRunContext } from '@deeps
|
||||
import type {} from './session-types.ts'
|
||||
|
||||
/** The model-facing name of the PTC mode tool. */
|
||||
export { RUN_CODE_NAME } from '@deepseek-ai/dsh-tools'
|
||||
import { RUN_CODE_NAME } from '@deepseek-ai/dsh-tools'
|
||||
export { RUN_CODE_NAME }
|
||||
|
||||
/** Language-specific tool and code-parameter descriptions. */
|
||||
interface RunCodeFlavor {
|
||||
@@ -264,19 +263,6 @@ function resolveSandboxPolicy(ctx: Context, exec: ToolRunContext): SandboxExecut
|
||||
return policy.resolve(exec.agent === undefined ? {} : { session: exec.agent.session })
|
||||
}
|
||||
|
||||
/** A failed log-content listener preserves the settled content and cannot fail the dispatch. */
|
||||
async function shapeDispatchLog(ctx: Context, dispatch: PtcDispatchLog): Promise<ContentBlock[]> {
|
||||
try {
|
||||
return await ctx.waterfall(
|
||||
scopeTarget(ctx.tools, dispatch.agent), 'tools/ptc-dispatch-log', dispatch,
|
||||
() => Promise.resolve(dispatch.content),
|
||||
)
|
||||
} catch (error: unknown) {
|
||||
ctx.logger.warn(`tools: ptc-dispatch-log listener failed for ${dispatch.name}: ${toolErrorMessage(error)}; logging the original settled content`)
|
||||
return dispatch.content
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the reserved `run_code` transport outside registered capabilities.
|
||||
* @param ctx - executor context owning policy listeners and active program cleanup.
|
||||
@@ -289,6 +275,23 @@ export function createRunCodeTool(
|
||||
executor: ToolExecutionPipeline,
|
||||
maxParallel: number,
|
||||
): ToolDefinition {
|
||||
// Retain services for log settlement after the executor context becomes inactive.
|
||||
const tools = ctx.tools
|
||||
const logger = ctx.logger
|
||||
|
||||
/** A failed log-content listener preserves the settled content and cannot fail the dispatch. */
|
||||
async function shapeDispatchLog(dispatch: PtcDispatchLog): Promise<ContentBlock[]> {
|
||||
try {
|
||||
return await ctx.waterfall(
|
||||
scopeTarget(tools, dispatch.agent), 'tools/ptc-dispatch-log', dispatch,
|
||||
() => Promise.resolve(dispatch.content),
|
||||
)
|
||||
} catch (error: unknown) {
|
||||
logger.warn(`tools: ptc-dispatch-log listener failed for ${dispatch.name}: ${toolErrorMessage(error)}; logging the original settled content`)
|
||||
return dispatch.content
|
||||
}
|
||||
}
|
||||
|
||||
const stateOwners = new WeakMap<object, WeakSet<PtcRuntime>>()
|
||||
const calls = new PtcCalls(ctx)
|
||||
const definition = defineTool({
|
||||
@@ -536,7 +539,7 @@ export function createRunCodeTool(
|
||||
// The listener may replace the durable copy with a preview and
|
||||
// locator; the program's value and model-visible result are
|
||||
// untouched.
|
||||
const logged = await shapeDispatchLog(ctx, {
|
||||
const logged = await shapeDispatchLog({
|
||||
exec, agent, subCallId, name, isError: result.isError,
|
||||
// The registry deep-froze this projection at result
|
||||
// finalization; append snapshots the final copy again, so
|
||||
@@ -645,7 +648,7 @@ export function createRunCodeTool(
|
||||
// restricted globals vanish) — the same view the SDK section declared,
|
||||
// so a program can bind exactly what its prompt promised; sub-dispatch
|
||||
// re-resolves per call through the same view (exec.agent threads down).
|
||||
for (const schema of ctx.tools.schemas(exec.agent)) {
|
||||
for (const schema of tools.schemas(exec.agent)) {
|
||||
Object.defineProperty(functions, schema.name, { enumerable: true, value: binding(deepFreeze(schema)) })
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
* store, keyed by the loaded {@link @deepseek-ai/dsh-ptc-runtime#PtcRuntime.language | PTC
|
||||
* runtime's language}.
|
||||
*
|
||||
* Under `mode: 'ptc'` the native tool schemas are omitted from the request, so this generated
|
||||
* SDK is the model's ONLY source for each tool's argument names, required fields, types,
|
||||
* With the PTC executor selected, native tool schemas are omitted from the request. This generated
|
||||
* SDK is the model's only source for each tool's argument names, required fields, types,
|
||||
* descriptions, and canonical output types. Object-shaped arguments and outputs render as one
|
||||
* named `TypedDict` per tool (and per nested object), not an opaque `dict[str, Any]`, so the
|
||||
* shape survives into the program under the mode that has nothing else to carry it.
|
||||
* @module @deepseek-ai/dsh-tools/src/py-types
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { assertSupportedJsonSchema } from '@deepseek-ai/dsh-tools'
|
||||
@@ -31,7 +31,7 @@ const IDENTIFIER = /^[\p{XID_Start}_]\p{XID_Continue}*$/u
|
||||
* Python identifiers are not ASCII: `路径` is as legal a field name as `path`,
|
||||
* and rejecting it would degrade the whole enclosing object, dropping every
|
||||
* field's name, requiredness, and type — information whose only source under
|
||||
* `mode: 'ptc'` is this generated text.
|
||||
* PTC execution is this generated text.
|
||||
*
|
||||
* NFKC stability is a second and separate condition, because CPython
|
||||
* normalizes identifiers at compile time while JSON keys are compared as
|
||||
@@ -162,7 +162,7 @@ interface RenderState {
|
||||
* (`SyntaxError: source code string cannot contain null bytes`), whether it
|
||||
* sits in a docstring or in a comment, so one such byte anywhere in a schema
|
||||
* description would make the whole generated SDK unparseable — under
|
||||
* `mode: 'ptc'`, the model's only declaration of the tools. The rest are
|
||||
* PTC execution, the model's only declaration of the tools. The rest are
|
||||
* legal but invisible; escaping them with the same rule keeps the emitted text
|
||||
* readable and the treatment uniform.
|
||||
*
|
||||
@@ -578,7 +578,7 @@ function renderType(schema: unknown, className: string, state: RenderState): str
|
||||
}
|
||||
}
|
||||
// TypedDict syntax cannot express openness, so an open object states it
|
||||
// in-band: the annotation is advisory either way, and `mode: 'ptc'`
|
||||
// in-band: the annotation is advisory either way, and PTC execution
|
||||
// omits the native schemas, making this line the model's only signal
|
||||
// that extra keys are accepted.
|
||||
if (node.additionalProperties !== false) {
|
||||
@@ -777,8 +777,8 @@ export function renderToolsSdkPy(schemas: readonly ToolSdkSchema[]): string {
|
||||
// of that method's body. Emitted before the `async def` it would instead
|
||||
// become the `Tools` class docstring (for the first tool) or a dead
|
||||
// expression (for every later one), leaving every method undocumented —
|
||||
// and under `mode: 'ptc'` this SDK is the model's only description of
|
||||
// what a tool does. A docstring is a complete body, so the `...` stub is
|
||||
// and with PTC selected, this SDK is the model's only description of what
|
||||
// a tool does. A docstring is a complete body, so the `...` stub is
|
||||
// only for the description-less case.
|
||||
const doc = docLines(schema.description, 2)
|
||||
members.push(doc.length > 0
|
||||
|
||||
@@ -67,7 +67,7 @@ it('honors cancellation while waiting for a canceled predecessor', async () => {
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('unloads the registry only after calls stop and refuses late admission', async () => {
|
||||
it('unloads the executor only after calls stop and refuses late admission', async () => {
|
||||
const { ctx, session, calls, mode, signal } = await setup()
|
||||
const cleanup = Promise.withResolvers<undefined>()
|
||||
const stopped = Promise.withResolvers<undefined>()
|
||||
@@ -79,11 +79,11 @@ it('unloads the registry only after calls stop and refuses late admission', asyn
|
||||
try {
|
||||
await stopped.promise
|
||||
const next = calls.run(session.id, signal, mode, async () => 'unexpected')
|
||||
const rejected = expect(next).rejects.toThrow('PTC tool registry disposed')
|
||||
const rejected = expect(next).rejects.toThrow('PTC tool executor disposed')
|
||||
cleanup.resolve(undefined)
|
||||
await first
|
||||
await rejected
|
||||
await disposed
|
||||
await expect(calls.run(session.id, signal, mode, async () => 'unexpected')).rejects.toThrow('PTC tool registry disposed')
|
||||
await expect(calls.run(session.id, signal, mode, async () => 'unexpected')).rejects.toThrow('PTC tool executor disposed')
|
||||
} finally { cleanup.resolve(undefined); await first; await disposed }
|
||||
})
|
||||
@@ -83,7 +83,7 @@ async function setup(options: SetupOptions = {}) {
|
||||
runtime = ctx.ptcRuntime as FakeRuntime
|
||||
}
|
||||
return { ctx, tools: {
|
||||
get: (name: string, scope?: ScopeKey) => executor instanceof PtcExecutor ? executor['get'](name, scope) : ctx.tools.get(name, scope),
|
||||
get: (name: string, scope?: ScopeKey) => executor instanceof PtcExecutor && name === RUN_CODE_NAME ? executor['requirePtcTransport']() : ctx.tools.get(name, scope),
|
||||
execute: (input: ToolExecutionInput) => executeCall(ctx, input),
|
||||
}, systemPrompt: ctx.systemPrompt, runtime: runtime! }
|
||||
}
|
||||
@@ -490,7 +490,7 @@ describe('mode-aware wire contribution', () => {
|
||||
it.each(['typescript', 'python'])('lists code before description for a %s runtime', async (language) => {
|
||||
const { ctx, systemPrompt } = await setup({ mode: 'ptc', runtime: { language } })
|
||||
try {
|
||||
const parameters = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)!.parameters
|
||||
const parameters = new PtcExecutor(ctx)['requirePtcTransport']().parameters
|
||||
expect(Object.keys(parameters.properties as Record<string, unknown>).slice(0, 2))
|
||||
.toEqual(['code', 'description'])
|
||||
expect(parameters).toHaveProperty('required', ['code', 'description'])
|
||||
@@ -558,17 +558,17 @@ describe('mode-aware wire contribution', () => {
|
||||
|
||||
it.each(['ruby', 'constructor'])('rejects transport schema reads for unsupported runtime language %s', async (language) => {
|
||||
const { ctx } = await setup({ mode: 'ptc', runtime: { language } })
|
||||
const definition = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)
|
||||
const definition = new PtcExecutor(ctx)['requirePtcTransport']()
|
||||
const message = `no SDK renderer registered for runtime language "${language}" (known: "typescript", "python")`
|
||||
expect(() => definition?.description).toThrow(message)
|
||||
expect(() => definition?.parameters).toThrow(message)
|
||||
expect(() => definition.description).toThrow(message)
|
||||
expect(() => definition.parameters).toThrow(message)
|
||||
})
|
||||
|
||||
it('rejects transport schema reads when no runtime is mounted', async () => {
|
||||
const { ctx } = await setup({ mode: 'ptc', runtime: false })
|
||||
const definition = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)
|
||||
expect(() => definition?.description).toThrow('requires a PTC runtime')
|
||||
expect(() => definition?.parameters).toThrow('requires a PTC runtime')
|
||||
const definition = new PtcExecutor(ctx)['requirePtcTransport']()
|
||||
expect(() => definition.description).toThrow('requires a PTC runtime')
|
||||
expect(() => definition.parameters).toThrow('requires a PTC runtime')
|
||||
})
|
||||
|
||||
it("rejects the assembly when toolOrder names a native tool that mode 'ptc' no longer contributes", async () => {
|
||||
@@ -584,7 +584,7 @@ describe('mode-aware wire contribution', () => {
|
||||
await ctx.plugin(FakeRuntime, {})
|
||||
const fiber = await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(PtcToolExecutor)
|
||||
expect(new PtcExecutor(ctx)['get'](RUN_CODE_NAME)).toBeDefined()
|
||||
expect(new PtcExecutor(ctx)['requirePtcTransport']()).toBeDefined()
|
||||
await fiber.dispose()
|
||||
const assembly = await ctx.systemPrompt.assemble()
|
||||
expect(assembly.tools).toEqual([])
|
||||
@@ -718,14 +718,8 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => {
|
||||
const gated = registerGated(ctx, 'safe_read', true)
|
||||
runtime.behavior = async (request) => {
|
||||
const tools = request.bindings[0]!.functions
|
||||
const all = Promise.all([
|
||||
tools.safe_read!({ id: 'a' }),
|
||||
tools.safe_read!({ id: 'b' }),
|
||||
tools.safe_read!({ id: 'c' }),
|
||||
])
|
||||
const all = Promise.all(Array.from({ length: 3 }, (_, i) => tools.safe_read!({ id: String(i) })))
|
||||
await expect.poll(() => gated.pending()).toBe(2)
|
||||
// The third call waits for a slot.
|
||||
expect(gated.pending()).toBe(2)
|
||||
gated.release()
|
||||
await expect.poll(() => gated.pending()).toBe(2)
|
||||
gated.releaseAll()
|
||||
@@ -733,7 +727,6 @@ describe('the sub-dispatch scheduler (native concurrency contract)', () => {
|
||||
return { logs: [], value: 'capped' }
|
||||
}
|
||||
const result = await runCode(ctx, 'program')
|
||||
if (result.isError) console.error('CAP-FAIL:', (result.content[0] as { text: string }).text)
|
||||
expect(result.isError).toBe(false)
|
||||
expect(gated.peakLive()).toBe(2)
|
||||
})
|
||||
@@ -1659,7 +1652,7 @@ describe('the run_code dispatch bridge', () => {
|
||||
|
||||
it('presents the model-authored description as the execute-card title over the program input', async () => {
|
||||
const { ctx } = await setup({ mode: 'ptc' })
|
||||
const tool = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)!
|
||||
const tool = new PtcExecutor(ctx)['requirePtcTransport']()
|
||||
// The description labels the card (the bash description precedent); the
|
||||
// program itself remains the expanded raw input.
|
||||
expect(tool.presentCall?.({ code: 'return 1', description: 'Return the constant one' })).toEqual({
|
||||
@@ -1687,7 +1680,7 @@ describe('the run_code dispatch bridge', () => {
|
||||
runtime.behavior = () => Promise.resolve(output)
|
||||
|
||||
const result = await runCode(ctx, 'return 1')
|
||||
const tool = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)!
|
||||
const tool = new PtcExecutor(ctx)['requirePtcTransport']()
|
||||
|
||||
expect(result.content).toEqual([{ type: 'text', text }])
|
||||
// Presenters keep the pending program title and render this durable content
|
||||
@@ -1706,7 +1699,7 @@ describe('the run_code dispatch bridge', () => {
|
||||
})
|
||||
|
||||
const result = await runCode(ctx, 'return 1')
|
||||
const tool = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)!
|
||||
const tool = new PtcExecutor(ctx)['requirePtcTransport']()
|
||||
|
||||
expect(result.content).toEqual([{ type: 'text', text: preview }])
|
||||
expect('presentResult' in tool).toBe(false)
|
||||
@@ -1720,7 +1713,7 @@ describe('the run_code dispatch bridge', () => {
|
||||
})
|
||||
|
||||
const result = await runCode(ctx, 'return 1')
|
||||
const tool = new PtcExecutor(ctx)['get'](RUN_CODE_NAME)!
|
||||
const tool = new PtcExecutor(ctx)['requirePtcTransport']()
|
||||
|
||||
expect(result.isError).toBe(true)
|
||||
expect(result.content).toEqual([{
|
||||
@@ -2000,21 +1993,21 @@ describe('the run_code dispatch bridge', () => {
|
||||
expect(derived[0]?.role).toBe('user')
|
||||
})
|
||||
|
||||
it('direct construction rejects a non-positive parallel sub-call cap at load', async () => {
|
||||
it('direct construction rejects a non-positive parallel subcall limit', async () => {
|
||||
const ctx = new Context()
|
||||
provideWorkingDirectoryFixture(ctx)
|
||||
await ctx.plugin(SystemPrompt, {})
|
||||
expect(() => new PtcExecutor(ctx, { maxParallelSubCalls: 0 }))
|
||||
.toThrow('maxParallelSubCalls must be a positive integer')
|
||||
try {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
expect(() => new PtcExecutor(ctx, { maxParallelSubCalls: 0 })).toThrow('maxParallelSubCalls')
|
||||
} finally { await ctx.fiber.dispose() }
|
||||
})
|
||||
|
||||
it('direct construction in PTC mode defaults the parallel sub-call cap', async () => {
|
||||
it('direct construction exposes the PTC transport without configuration', async () => {
|
||||
const ctx = new Context()
|
||||
provideWorkingDirectoryFixture(ctx)
|
||||
await ctx.plugin(SystemPrompt, {})
|
||||
await ctx.plugin(ToolRuntime)
|
||||
const registry = new PtcExecutor(ctx)
|
||||
expect(registry['get'](RUN_CODE_NAME)).toBeDefined()
|
||||
expect(registry['requirePtcTransport']()).toBeDefined()
|
||||
})
|
||||
|
||||
it('defaults to native mode under direct construction with no config', async () => {
|
||||
@@ -2542,3 +2535,47 @@ describe('PTC state ownership', () => {
|
||||
} finally { format.mockRestore(); instructions.mockRestore(); await ctx.fiber.dispose() }
|
||||
})
|
||||
})
|
||||
|
||||
it.each([false, true])('settles nested calls after the PTC executor fiber unloads (Agent: %s)', async (owned) => {
|
||||
const ctx = new Context()
|
||||
provideWorkingDirectoryFixture(ctx)
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin(FakeRuntime)
|
||||
const provider = ctx.plugin(PtcToolExecutor)
|
||||
await provider
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'echo', description: 'Retained capability.', parameters: {},
|
||||
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }] },
|
||||
execute: async () => 'settled',
|
||||
}))
|
||||
const entered = Promise.withResolvers<undefined>()
|
||||
const release = Promise.withResolvers<undefined>()
|
||||
const runtime = ctx.ptcRuntime as FakeRuntime
|
||||
runtime.behavior = async (request) => {
|
||||
entered.resolve(undefined)
|
||||
await release.promise
|
||||
return { logs: [], value: await request.bindings[0]!.functions.echo!({}) }
|
||||
}
|
||||
const agent = { ctx, id: SessionId('unloading-ptc'), session: Session.create(SessionId('unloading-ptc')) } as Agent
|
||||
const input = {
|
||||
calls: [{ type: 'tool-call' as const, id: ToolCallId('unloading-program'), name: RUN_CODE_NAME,
|
||||
arguments: JSON.stringify({ code: 'return await tools.echo({})', description: 'Invoke retained tool after unload' }) }],
|
||||
signal: new AbortController().signal,
|
||||
...owned ? { agent } : {},
|
||||
}
|
||||
const pending = ctx.tools.execute(input)
|
||||
try {
|
||||
await entered.promise
|
||||
await provider.dispose()
|
||||
await expect(ctx.tools.execute(input)).rejects.toThrow('selected tool executor is unavailable')
|
||||
release.resolve(undefined)
|
||||
expect((await pending).results[0]?.result).toMatchObject({ isError: false, value: { logs: [], result: 'settled' } })
|
||||
if (owned) expect(agent.session.snapshotEvents().filter(event => event.type === 'tool/ptc-dispatch'))
|
||||
.toMatchObject([{ data: { name: 'echo', isError: false, content: [{ type: 'text', text: 'settled' }] } }])
|
||||
} finally {
|
||||
release.resolve(undefined)
|
||||
await Promise.allSettled([pending])
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
@@ -21,8 +21,8 @@
|
||||
en: 9d2e6caa39c53d62
|
||||
zh: 096a777f828c7565
|
||||
/deepseek-ai-dsh-tools/use-this-package/select-an-executor:
|
||||
en: f3efeb3d996cb9c5
|
||||
zh: 442cf4ca6d68fea0
|
||||
en: 1ddfa584f9280cb7
|
||||
zh: b0899fcb8163569e
|
||||
/deepseek-ai-dsh-tools/use-this-package/restrict-tools-per-agent:
|
||||
en: c6f98b2283cc2e47
|
||||
zh: c2cdce5d13473f15
|
||||
|
||||
@@ -72,7 +72,7 @@ Mount one executor in the deployment, preset, or Agent scope. The nearest scope
|
||||
- name: '@deepseek-ai/dsh-tool-executor-native'
|
||||
```
|
||||
|
||||
Agent-step concurrency uses the [agent-loop configuration](../agent-loop/README.md). See the [native](../tool-executor-native/README.md) or [PTC](../tool-executor-ptc/README.md) package for executor settings. Custom providers implement only `describe(definitions)` and `execute(batch)`; the shared pipeline is optional. The registry selects the current scoped executor separately for prompt descriptions and each `ctx.tools.execute()` batch. An accepted batch finishes through its selected provider even if that provider unloads; new batches reject until a replacement is registered.
|
||||
Agent-step concurrency uses the [agent-loop configuration](../agent-loop/README.md). See the [native](../tool-executor-native/README.md) or [PTC](../tool-executor-ptc/README.md) package for executor settings. Custom providers implement only `describe(definitions)` and `execute(batch)`; the shared pipeline is optional. The registry selects the current scoped executor separately for prompt descriptions and each `ctx.tools.execute()` batch. An accepted batch settles through its selected provider even if that provider unloads; settlement may include cancellation errors according to the provider’s disposal policy. New batches reject until a replacement is registered.
|
||||
|
||||
### Restrict tools per agent
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ ctx.tools.register(defineTool({
|
||||
- name: '@deepseek-ai/dsh-tool-executor-native'
|
||||
```
|
||||
|
||||
Agent 步骤的并发上限使用 [agent-loop 配置](../agent-loop/README.zh.md)。执行器设置参见[原生](../tool-executor-native/README.zh.md)或 [PTC](../tool-executor-ptc/README.zh.md) 包。自定义提供者只需实现 `describe(definitions)` 和 `execute(batch)`,共享流水线为可选项。注册表分别为提示词描述和每次 `ctx.tools.execute()` 批次选择当前作用域的执行器。已接纳的批次由所选提供者完成,即使该提供者已卸载;注册替代提供者之前,新批次会被拒绝。
|
||||
Agent 步骤的并发上限使用 [agent-loop 配置](../agent-loop/README.zh.md)。执行器设置参见[原生](../tool-executor-native/README.zh.md)或 [PTC](../tool-executor-ptc/README.zh.md) 包。自定义提供者只需实现 `describe(definitions)` 和 `execute(batch)`,共享流水线为可选项。注册表分别为提示词描述和每次 `ctx.tools.execute()` 批次选择当前作用域的执行器。已接纳批次仍由所选提供者结算,即使该提供者已卸载;根据提供者的卸载策略,结果可能包含取消错误。注册替代提供者之前,新批次会被拒绝。
|
||||
|
||||
### 按 agent 限制工具
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ export interface ToolExecutionInput {
|
||||
* mode sets this on SDK sub-dispatches so commit-style observers can wait for
|
||||
* the outer `run_code` outcome without receiving its live mutable execution.
|
||||
* The token also marks the call as a transport sub-dispatch rather than a
|
||||
* model-direct call: under `mode: 'ptc'`, only calls WITH a parent may
|
||||
* model-direct call: with the PTC executor selected, only calls with a parent may
|
||||
* execute a native tool name — a model-direct call (no parent) is denied as
|
||||
* `UNKNOWN_TOOL` before the policy pipeline.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/** Tool definitions and scoped dispatch to plugin-owned executors. @module */
|
||||
import { Context, Service } from '@deepseek-ai/cordis'
|
||||
import z from '@deepseek-ai/schemastery'
|
||||
import { AnonymousEntries, NamedEntries, ScopedLayers, scopeOf, scopeChainOf } from '@deepseek-ai/dsh-scope'
|
||||
import type { ScopeKey, ScopeLayer } from '@deepseek-ai/dsh-scope'
|
||||
import { ToolExecutorUnavailableError } from './executor.ts'
|
||||
@@ -79,6 +80,10 @@ class GuardLayer {
|
||||
/** Concrete tool registry and scoped executor dispatcher. */
|
||||
export class ToolsService extends Service {
|
||||
static inject = ['systemPrompt']
|
||||
static Config = z.object({
|
||||
mode: z.never().description('Removed. Mount a native or PTC tool executor instead.'),
|
||||
maxParallelSubCalls: z.never().description('Moved to the PTC tool executor configuration.'),
|
||||
})
|
||||
|
||||
private readonly layers = new ScopedLayers(scope => new ToolLayer(scope), () => { this.ctx.emit('tools/change') })
|
||||
private readonly scoped = new WeakMap<ScopeKey, Registration>()
|
||||
@@ -231,6 +236,7 @@ export class ToolsService extends Service {
|
||||
}
|
||||
/**
|
||||
* Provide the executor for the calling scope; an unscoped contribution is the deployment default.
|
||||
* Rendering any executor-owned section requires the viewing scope's selection to remain available.
|
||||
* @param executor - initialized provider owned by the contributing plugin.
|
||||
* @returns the effect disposer. Unloading leaves the selection unavailable until it is replaced.
|
||||
*/
|
||||
|
||||
@@ -9,12 +9,13 @@ import type {} from '@deepseek-ai/dsh-user-approval'
|
||||
import { assertNever, deepFreeze, snapshotJsonValue, type JsonValue } from '@deepseek-ai/dsh-util-values'
|
||||
import { TOOL_ABORTED, TOOL_ABORTED_BEFORE_DISPATCH, ToolNotFoundError, ToolOutputError } from './contracts.ts'
|
||||
import { validateJsonSchemaValue } from './json-schema.ts'
|
||||
import type { ToolsService } from './index.ts'
|
||||
import type { ToolDefinition, ToolExecutionInput, ToolExecutionMode, ToolExecution, ToolRunContext, ToolExecutionResult, ToolExecutionSuccess, ToolErrorInfo, ToolExecutionToken, PreToolDecision, PostToolDecision } from './contracts.ts'
|
||||
|
||||
/** Executor-owned lookup and restrictions applied before extensible execution policy. */
|
||||
export interface ToolExecutionSource {
|
||||
/** Read the live definition visible to the calling scope. */
|
||||
get(name: string, scope?: ScopeKey): ToolDefinition | undefined
|
||||
/** Override registry lookup for executor-owned capabilities; omitted uses the retained registry. */
|
||||
get?(name: string, scope?: ScopeKey): ToolDefinition | undefined
|
||||
/** Reject an otherwise visible invocation before policy; omitted permits it. */
|
||||
reject?(name: string, nested: boolean): Error | undefined
|
||||
}
|
||||
@@ -56,15 +57,19 @@ interface FusedToolSignal {
|
||||
* for complete calls, including a single-call batch.
|
||||
*/
|
||||
export class ToolExecutionPipeline {
|
||||
private readonly tools: Context['tools']
|
||||
private readonly tools: ToolsService
|
||||
|
||||
constructor(private readonly ctx: Context, private readonly source: ToolExecutionSource) {
|
||||
constructor(private readonly ctx: Context, private readonly source: ToolExecutionSource = {}) {
|
||||
// Accepted calls retain their event subject while the service is withdrawn during shutdown.
|
||||
this.tools = ctx.tools
|
||||
}
|
||||
|
||||
private get(name: string, scope?: ScopeKey): ToolDefinition | undefined {
|
||||
return this.source.get === undefined ? this.tools.get(name, scope) : this.source.get(name, scope)
|
||||
}
|
||||
|
||||
private resolveExecution(name: string, scope: ScopeKey | undefined, nested: boolean): ToolDefinition | undefined {
|
||||
const tool = this.source.get(name, scope)
|
||||
const tool = this.get(name, scope)
|
||||
return tool !== undefined && this.source.reject?.(name, nested) === undefined ? tool : undefined
|
||||
}
|
||||
|
||||
@@ -111,7 +116,7 @@ export class ToolExecutionPipeline {
|
||||
const parent = exec.parent
|
||||
const signal = exec.signal
|
||||
// Executor refusals precede extensible policy; unknown tools retain dispatch-stage handling.
|
||||
const visible = this.source.get(name, agent)
|
||||
const visible = this.get(name, agent)
|
||||
const rejection = visible === undefined ? undefined : this.source.reject?.(name, parent !== undefined)
|
||||
const rejected = rejection !== undefined
|
||||
const concludingExecutions = this.concludingExecutions
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
/** Covers fail-closed per-call classification and model-schema isolation. */
|
||||
import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
|
||||
import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native'
|
||||
import { describe, expect, expectTypeOf, it } from 'vitest'
|
||||
@@ -33,7 +33,7 @@ describe('ToolExecutionPipeline.executionMode', () => {
|
||||
isConcurrencySafe: () => true,
|
||||
async execute() { return [] },
|
||||
}))
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('safe', {}))).toEqual({ kind: 'parallel' })
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode(exec('safe', {}))).toEqual({ kind: 'parallel' })
|
||||
})
|
||||
|
||||
it('defaults to exclusive for a tool with no isConcurrencySafe declaration', async () => {
|
||||
@@ -44,12 +44,12 @@ describe('ToolExecutionPipeline.executionMode', () => {
|
||||
parameters: {},
|
||||
async execute() { return [] },
|
||||
}))
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('plain', {}))).toEqual({ kind: 'exclusive' })
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode(exec('plain', {}))).toEqual({ kind: 'exclusive' })
|
||||
})
|
||||
|
||||
it('returns exclusive for an unknown tool', async () => {
|
||||
const ctx = await setup()
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('nonexistent', {}))).toEqual({ kind: 'exclusive' })
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode(exec('nonexistent', {}))).toEqual({ kind: 'exclusive' })
|
||||
})
|
||||
|
||||
it('returns exclusive when the classifier returns false for these args', async () => {
|
||||
@@ -61,8 +61,8 @@ describe('ToolExecutionPipeline.executionMode', () => {
|
||||
isConcurrencySafe: args => args.mode === 'read',
|
||||
async execute() { return [] },
|
||||
}))
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('rw', { mode: 'read' }))).toEqual({ kind: 'parallel' })
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('rw', { mode: 'write' }))).toEqual({ kind: 'exclusive' })
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode(exec('rw', { mode: 'read' }))).toEqual({ kind: 'parallel' })
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode(exec('rw', { mode: 'write' }))).toEqual({ kind: 'exclusive' })
|
||||
})
|
||||
|
||||
it('classifies invalid defineContentToolFixture arguments as exclusive without throwing', async () => {
|
||||
@@ -74,7 +74,7 @@ describe('ToolExecutionPipeline.executionMode', () => {
|
||||
isConcurrencySafe: () => true,
|
||||
async execute() { return [] },
|
||||
}))
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('needs-mode', {}))).toEqual({ kind: 'exclusive' })
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode(exec('needs-mode', {}))).toEqual({ kind: 'exclusive' })
|
||||
})
|
||||
|
||||
it('treats a throwing raw classifier as exclusive', async () => {
|
||||
@@ -88,7 +88,7 @@ describe('ToolExecutionPipeline.executionMode', () => {
|
||||
async execute() { return null },
|
||||
}
|
||||
ctx.tools.register(raw)
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('thrower', {}))).toEqual({ kind: 'exclusive' })
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode(exec('thrower', {}))).toEqual({ kind: 'exclusive' })
|
||||
})
|
||||
|
||||
it('treats a truthy non-boolean raw result as exclusive', async () => {
|
||||
@@ -102,7 +102,7 @@ describe('ToolExecutionPipeline.executionMode', () => {
|
||||
async execute() { return null },
|
||||
} as unknown as ToolDefinition
|
||||
ctx.tools.register(raw)
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('truthy', {}))).toEqual({ kind: 'exclusive' })
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode(exec('truthy', {}))).toEqual({ kind: 'exclusive' })
|
||||
})
|
||||
|
||||
it('passes parsed arguments directly to a raw definition', async () => {
|
||||
@@ -116,7 +116,7 @@ describe('ToolExecutionPipeline.executionMode', () => {
|
||||
isConcurrencySafe(args) { seen = args; return true },
|
||||
async execute() { return null },
|
||||
})
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode(exec('raw-safe', { anything: 1 }))).toEqual({ kind: 'parallel' })
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode(exec('raw-safe', { anything: 1 }))).toEqual({ kind: 'parallel' })
|
||||
expect(seen).toEqual({ anything: 1 })
|
||||
})
|
||||
|
||||
|
||||
@@ -52,6 +52,48 @@ const echoTool = defineTool({
|
||||
})
|
||||
|
||||
describe('ToolRuntime', () => {
|
||||
it.each([{ mode: 'ptc' }, { maxParallelSubCalls: 4 }])('rejects removed execution configuration %j during plugin mounting', async (config) => {
|
||||
const ctx = new Context()
|
||||
try {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
// @ts-expect-error Removed fields can still arrive from untyped configuration files.
|
||||
await expect(ctx.plugin(ToolRuntime, config).await()).rejects.toThrow('invalid config')
|
||||
} finally { await ctx.fiber.dispose() }
|
||||
})
|
||||
|
||||
it('settles accepted calls after the actual native executor fiber unloads', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
const provider = ctx.plugin(NativeToolExecutor)
|
||||
await provider
|
||||
ctx.tools.register(echoTool)
|
||||
const entered = Promise.withResolvers<undefined>()
|
||||
const release = Promise.withResolvers<undefined>()
|
||||
ctx.on('tools/pre-execute', async (exec, next) => {
|
||||
if (exec.callId === 'first') { entered.resolve(undefined); await release.promise }
|
||||
return next()
|
||||
})
|
||||
const input = {
|
||||
calls: ['first', 'second'].map(id => ({ type: 'tool-call' as const, id: ToolCallId(id), name: 'echo', arguments: JSON.stringify({ text: id }) })),
|
||||
signal: new AbortController().signal,
|
||||
}
|
||||
const pending = ctx.tools.execute(input)
|
||||
try {
|
||||
await entered.promise
|
||||
await provider.dispose()
|
||||
await expect(ctx.tools.execute(input)).rejects.toThrow('selected tool executor is unavailable')
|
||||
release.resolve(undefined)
|
||||
expect((await pending).results.map(({ result }) => result)).toMatchObject([
|
||||
{ isError: false, value: 'first' }, { isError: false, value: 'second' },
|
||||
])
|
||||
} finally {
|
||||
release.resolve(undefined)
|
||||
await Promise.allSettled([pending])
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('preserves deferred loading through registry and prompt schema projection', async () => {
|
||||
const ctx = await setup()
|
||||
try {
|
||||
|
||||
@@ -2,4 +2,6 @@
|
||||
config:
|
||||
executionMode: persistent-in-memory
|
||||
- id: tool-executor-native
|
||||
name: '@deepseek-ai/dsh-tool-executor-ptc'
|
||||
disabled: true
|
||||
- id: tool-executor-ptc
|
||||
disabled: false
|
||||
@@ -3489,7 +3489,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
},
|
||||
{
|
||||
signature: 'setExecutor(executor: ToolExecutor): () => void',
|
||||
description: 'Provide the executor for the calling scope; an unscoped contribution is the deployment default.',
|
||||
description: 'Provide the executor for the calling scope; an unscoped contribution is the deployment default. Rendering any executor-owned section requires the viewing scope\'s selection to remain available.',
|
||||
parameters: [{ name: 'executor', description: 'initialized provider owned by the contributing plugin.' }],
|
||||
returns: 'the effect disposer. Unloading leaves the selection unavailable until it is replaced.',
|
||||
},
|
||||
@@ -8394,7 +8394,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'ToolsService',
|
||||
declaration: 'export class ToolsService extends Service {\n static inject;\n constructor(ctx: Context);\n register(definition: ToolDefinition): () => void;\n restrict(filter: ToolRestriction): () => void;\n get(name: string, scope?: ScopeKey): ToolDefinition | undefined;\n definitions(scope?: ScopeKey): readonly ToolDefinition[];\n schemas(scope?: ScopeKey): ToolSchema[];\n setExecutor(executor: ToolExecutor): () => void;\n async execute(input: ToolBatchInput): Promise<ToolBatchResult>;\n guard(guard: ToolGuard): () => void;\n checkGuards(exec: ToolExecution): string | undefined;\n}',
|
||||
declaration: 'export class ToolsService extends Service {\n static inject;\n static Config;\n constructor(ctx: Context);\n register(definition: ToolDefinition): () => void;\n restrict(filter: ToolRestriction): () => void;\n get(name: string, scope?: ScopeKey): ToolDefinition | undefined;\n definitions(scope?: ScopeKey): readonly ToolDefinition[];\n schemas(scope?: ScopeKey): ToolSchema[];\n setExecutor(executor: ToolExecutor): () => void;\n async execute(input: ToolBatchInput): Promise<ToolBatchResult>;\n guard(guard: ToolGuard): () => void;\n checkGuards(exec: ToolExecution): string | undefined;\n}',
|
||||
},
|
||||
{
|
||||
name: 'ToolUpdate',
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
import { executeToolFixture, executeModelToolFixture } from '@deepseek-ai/dsh-tool-testkit'
|
||||
/**
|
||||
* The `read_image` tool over the REAL local filesystem and attachment store:
|
||||
* extension routing, extension-less content sniffing (attachment object paths
|
||||
@@ -7,6 +5,8 @@ import { executeToolFixture, executeModelToolFixture } from '@deepseek-ai/dsh-to
|
||||
* commit + image-block rendering, attachment admission failures, and the
|
||||
* regression that `read` keeps its text-only contract.
|
||||
*/
|
||||
import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
import { executeToolFixture, executeModelToolFixture } from '@deepseek-ai/dsh-tool-testkit'
|
||||
|
||||
import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native'
|
||||
import * as PtcToolExecutor from '@deepseek-ai/dsh-tool-executor-ptc'
|
||||
@@ -840,7 +840,7 @@ describe('registration surface', () => {
|
||||
|
||||
it('declares read_image parallel-safe and presents a read-family card', async () => {
|
||||
const ctx = await setup()
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode({
|
||||
signal: testToolSignal, callId: ToolCallId('img-parallel'), name: 'read_image', arguments: { file_path: 'a.png' },
|
||||
})).toEqual({ kind: 'parallel' })
|
||||
expect(ctx.tools.get('read_image')?.presentCall?.({ file_path: 'shot.png' })).toEqual({
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
import { executeToolFixture } from '@deepseek-ai/dsh-tool-testkit'
|
||||
/**
|
||||
* Consumer API tests over a fake provider and the real policy collaborator: schemas,
|
||||
* validation, formatting, typed errors, intent dispatch, and observation-driven authorization.
|
||||
*/
|
||||
import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
|
||||
import { executeToolFixture } from '@deepseek-ai/dsh-tool-testkit'
|
||||
|
||||
import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native'
|
||||
import * as PtcToolExecutor from '@deepseek-ai/dsh-tool-executor-ptc'
|
||||
@@ -198,11 +198,11 @@ describe('registration', () => {
|
||||
|
||||
it('declares read parallel-safe while write/edit remain exclusive', async () => {
|
||||
const { ctx } = await setup()
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ signal: testToolSignal, callId: ToolCallId('read-safe'), name: 'read', arguments: { file_path: 'a.txt' } }))
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('read-safe'), name: 'read', arguments: { file_path: 'a.txt' } }))
|
||||
.toEqual({ kind: 'parallel' })
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ signal: testToolSignal, callId: ToolCallId('write-exclusive'), name: 'write', arguments: { file_path: 'a.txt', content: 'x' } }))
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('write-exclusive'), name: 'write', arguments: { file_path: 'a.txt', content: 'x' } }))
|
||||
.toEqual({ kind: 'exclusive' })
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ signal: testToolSignal, callId: ToolCallId('edit-exclusive'), name: 'edit', arguments: { file_path: 'a.txt', old_string: 'x', new_string: 'y' } }))
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('edit-exclusive'), name: 'edit', arguments: { file_path: 'a.txt', old_string: 'x', new_string: 'y' } }))
|
||||
.toEqual({ kind: 'exclusive' })
|
||||
})
|
||||
|
||||
@@ -1149,7 +1149,7 @@ describe('scope-aware PTC guidance', () => {
|
||||
await ctx.plugin(SystemPrompt)
|
||||
await ctx.plugin(GuidancePtcRuntime)
|
||||
await ctx.plugin(ToolRuntime)
|
||||
await ctx.plugin((mode) === 'ptc' ? PtcToolExecutor : NativeToolExecutor)
|
||||
await ctx.plugin(mode === 'ptc' ? PtcToolExecutor : NativeToolExecutor)
|
||||
await ctx.plugin(FakeFs)
|
||||
await ctx.plugin(ToolFs)
|
||||
const { key, scope } = await guidanceScope(ctx)
|
||||
|
||||
@@ -157,7 +157,7 @@ describe('goal tool registration and presentation', () => {
|
||||
const { ctx, fiber } = await harness({ blockedAfterConsecutiveRounds: 5 })
|
||||
expect(['create_goal', 'get_goal', 'update_goal'].map(name => ctx.tools.get(name)?.name))
|
||||
.toEqual(['create_goal', 'get_goal', 'update_goal'])
|
||||
const pipeline = new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) })
|
||||
const pipeline = new ToolExecutionPipeline(ctx)
|
||||
for (const name of ['create_goal', 'get_goal', 'update_goal']) {
|
||||
expect(pipeline.executionMode({ signal: testToolSignal, callId: ToolCallId(name), name, arguments: {} }))
|
||||
.toEqual({ kind: 'exclusive' })
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
"dependencies": {
|
||||
"@deepseek-ai/cosmokit": "workspace:~",
|
||||
"@deepseek-ai/dsh-brand": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-executor-native": "workspace:*",
|
||||
"@deepseek-ai/dsh-util-values": "workspace:*",
|
||||
"@deepseek-ai/schemastery": "workspace:~",
|
||||
"eventsource-parser": "^3.1.0"
|
||||
@@ -72,6 +71,7 @@
|
||||
"@deepseek-ai/dsh-agent-loop": "workspace:*",
|
||||
"@deepseek-ai/dsh-session-projection": "workspace:*",
|
||||
"@deepseek-ai/dsh-system-prompt": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-executor-native": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-testkit": "workspace:*",
|
||||
"@deepseek-ai/dsh-tools": "workspace:*",
|
||||
"@deepseek-ai/dsh-credentials-local": "workspace:*",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { executeModelToolFixture } from '@deepseek-ai/dsh-tool-testkit'
|
||||
/** Executor plugins follow the Loader-owned preset revision lifetime. */
|
||||
import { executeModelToolFixture } from '@deepseek-ai/dsh-tool-testkit'
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
@@ -91,7 +91,7 @@ class ProgramFixture extends PtcRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
it('loads the separate executor packages through cordis.yml presets and logs PTC subcalls', async () => {
|
||||
it.each([true, false])('loads cordis.yml executor presets with runtime availability %s', async (hasRuntime) => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-executor-packages-'))
|
||||
const ctx = await harness()
|
||||
onTestFinished(async () => {
|
||||
@@ -99,7 +99,7 @@ it('loads the separate executor packages through cordis.yml presets and logs PTC
|
||||
})
|
||||
await ctx.plugin(FsLocal)
|
||||
await ctx.plugin(WorkingDirectory, { defaultDirectory: root })
|
||||
await ctx.plugin(ProgramFixture)
|
||||
if (hasRuntime) await ctx.plugin(ProgramFixture)
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'echo', description: 'Shared capability.', parameters: { value: { type: 'string', required: true } },
|
||||
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }] },
|
||||
@@ -127,6 +127,15 @@ it('loads the separate executor packages through cordis.yml presets and logs PTC
|
||||
const native = await agentOn(ctx, 'native-package', 'standard')
|
||||
const ptc = await agentOn(ctx, 'ptc-package', 'ptc')
|
||||
expect((await ctx.systemPrompt.assemble({ scope: native })).tools.map(tool => tool.name)).toEqual(['echo'])
|
||||
if (!hasRuntime) {
|
||||
await expect(ctx.systemPrompt.assemble({ scope: ptc })).rejects.toThrow('requires a PTC runtime')
|
||||
const batch = await ctx.tools.execute({
|
||||
agent: ptc, calls: [{ type: 'tool-call', id: ToolCallId('missing-runtime'), name: 'echo', arguments: '{}' }],
|
||||
signal: new AbortController().signal,
|
||||
})
|
||||
expect(batch.results[0]?.result).toMatchObject({ isError: true, error: { info: { code: 'UNKNOWN_TOOL' } } })
|
||||
return
|
||||
}
|
||||
expect((await ctx.systemPrompt.assemble({ scope: ptc })).tools.map(tool => tool.name)).toEqual(['run_code'])
|
||||
const signal = new AbortController().signal
|
||||
expect(await executeModelToolFixture(ctx, {
|
||||
|
||||
+1
-1
@@ -157,7 +157,7 @@ Every package below exports a Cordis plugin that a bundle patch can name in a Lo
|
||||
| `@deepseek-ai/dsh-agent-tool-presentation` | yes | Deprecated tool presentation selector; use dsh-tool-executor-native or dsh-tool-executor-ptc directly |
|
||||
| `@deepseek-ai/dsh-session` | no | Event-sourced session store for the DeepSeek Harness |
|
||||
| `@deepseek-ai/dsh-system-prompt` | yes | System prompt assembly registry for the DeepSeek Harness |
|
||||
| `@deepseek-ai/dsh-tool-executor-native` | yes | Native tool execution plugin |
|
||||
| `@deepseek-ai/dsh-tool-executor-native` | no | Native tool execution plugin |
|
||||
| `@deepseek-ai/dsh-tool-executor-ptc` | yes | PTC program execution, tool bindings, and generated SDK prompts |
|
||||
| `@deepseek-ai/dsh-tools` | no | Tool registry and execution pipeline for the DeepSeek Harness |
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
"@deepseek-ai/dsh-output-retention": "workspace:*",
|
||||
"@deepseek-ai/dsh-session": "workspace:*",
|
||||
"@deepseek-ai/dsh-spill": "workspace:*",
|
||||
"@deepseek-ai/dsh-tool-executor-ptc": "workspace:*",
|
||||
"@deepseek-ai/dsh-tools": "workspace:*",
|
||||
"@deepseek-ai/cordis": "workspace:~",
|
||||
"@deepseek-ai/dsh-attachment": "workspace:*",
|
||||
|
||||
@@ -111,13 +111,13 @@ describe('dsh-tool-subagent', () => {
|
||||
|
||||
it('classifies delegations as concurrency-safe', async () => {
|
||||
const ctx = await setup({ provider: 'mock' })
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode({
|
||||
signal: testToolSignal,
|
||||
callId: ToolCallId('subagent-first'),
|
||||
name: 'subagent',
|
||||
arguments: { description: 'do work', prompt: 'Reply OK' },
|
||||
})).toEqual({ kind: 'parallel' })
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode({
|
||||
signal: testToolSignal,
|
||||
callId: ToolCallId('subagent-second'),
|
||||
name: 'subagent',
|
||||
@@ -696,7 +696,7 @@ describe('dsh-tool-subagent local activation', () => {
|
||||
|
||||
it('classifies local activations concurrency-safe', async () => {
|
||||
const { ctx } = await continuableSetup()
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode({
|
||||
signal: testToolSignal,
|
||||
callId: ToolCallId('subagent-continuable'),
|
||||
name: 'subagent',
|
||||
|
||||
@@ -43,6 +43,17 @@ describe('dsh-agent-loop-testkit', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('mounts the PTC executor with its runtime', async () => {
|
||||
const ctx = new Context()
|
||||
try {
|
||||
await ctx.plugin(FixtureRuntime)
|
||||
await mountAgentLoopTestDependencies(ctx, { tools: { mode: 'ptc' } })
|
||||
expect((await ctx.systemPrompt.assemble()).tools.map(tool => tool.name)).toEqual(['run_code'])
|
||||
} finally {
|
||||
await ctx.fiber.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects mutations through an unsupported Agent stub Inbox', () => {
|
||||
const inbox = unsupportedInbox()
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export function defineContentToolFixture<const S extends ParameterSchemaSpec>(
|
||||
* @internal
|
||||
*/
|
||||
export async function executeToolFixture(ctx: Context, input: ToolExecutionInput): Promise<ToolExecutionResult> {
|
||||
const pipeline = new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) })
|
||||
const pipeline = new ToolExecutionPipeline(ctx)
|
||||
const prepared = await pipeline.prepare(input)
|
||||
const outcome = prepared.kind === 'dispatch' ? await pipeline.dispatch(prepared.exec) : prepared
|
||||
return pipeline.complete(prepared.exec, outcome)
|
||||
|
||||
@@ -457,9 +457,9 @@ describe('tool-web registration', () => {
|
||||
const names = ctx.tools.schemas().map(s => s.name)
|
||||
expect(names).toContain('web_search')
|
||||
expect(names).toContain('web_fetch')
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ signal: testToolSignal, callId: ToolCallId('search-safe'), name: 'web_search', arguments: { queries: ['q'] } }))
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('search-safe'), name: 'web_search', arguments: { queries: ['q'] } }))
|
||||
.toEqual({ kind: 'parallel' })
|
||||
expect(new ToolExecutionPipeline(ctx, { get: (name, scope) => ctx.tools.get(name, scope) }).executionMode({ signal: testToolSignal, callId: ToolCallId('fetch-safe'), name: 'web_fetch', arguments: { url: 'https://a.test' } }))
|
||||
expect(new ToolExecutionPipeline(ctx).executionMode({ signal: testToolSignal, callId: ToolCallId('fetch-safe'), name: 'web_fetch', arguments: { url: 'https://a.test' } }))
|
||||
.toEqual({ kind: 'parallel' })
|
||||
await fiber.dispose()
|
||||
expect(ctx.tools.schemas().map(s => s.name)).not.toContain('web_search')
|
||||
|
||||
Generated
+6
-16
@@ -7035,9 +7035,6 @@ importers:
|
||||
'@deepseek-ai/dsh-brand':
|
||||
specifier: workspace:*
|
||||
version: link:../../util/brand
|
||||
'@deepseek-ai/dsh-tool-executor-native':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/tool-executor-native
|
||||
'@deepseek-ai/dsh-util-values':
|
||||
specifier: workspace:*
|
||||
version: link:../../util/values
|
||||
@@ -7102,6 +7099,9 @@ importers:
|
||||
'@deepseek-ai/dsh-system-prompt':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/system-prompt
|
||||
'@deepseek-ai/dsh-tool-executor-native':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/tool-executor-native
|
||||
'@deepseek-ai/dsh-tool-fs':
|
||||
specifier: workspace:*
|
||||
version: link:../../fs/tool-fs
|
||||
@@ -7411,13 +7411,6 @@ importers:
|
||||
version: link:../scope
|
||||
|
||||
packages/core/tool-executor-native:
|
||||
dependencies:
|
||||
'@deepseek-ai/cosmokit':
|
||||
specifier: link:../../../vendor/cosmokit
|
||||
version: link:../../../vendor/cosmokit
|
||||
'@deepseek-ai/schemastery':
|
||||
specifier: link:../../../vendor/schemastery
|
||||
version: link:../../../vendor/schemastery
|
||||
devDependencies:
|
||||
'@deepseek-ai/cordis':
|
||||
specifier: workspace:~
|
||||
@@ -7434,9 +7427,6 @@ importers:
|
||||
|
||||
packages/core/tool-executor-ptc:
|
||||
dependencies:
|
||||
'@deepseek-ai/cosmokit':
|
||||
specifier: link:../../../vendor/cosmokit
|
||||
version: link:../../../vendor/cosmokit
|
||||
'@deepseek-ai/dsh-brand':
|
||||
specifier: workspace:*
|
||||
version: link:../../util/brand
|
||||
@@ -11468,9 +11458,6 @@ importers:
|
||||
'@deepseek-ai/dsh-brand':
|
||||
specifier: workspace:*
|
||||
version: link:../../util/brand
|
||||
'@deepseek-ai/dsh-tool-executor-native':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/tool-executor-native
|
||||
'@deepseek-ai/dsh-util-values':
|
||||
specifier: workspace:*
|
||||
version: link:../../util/values
|
||||
@@ -11562,6 +11549,9 @@ importers:
|
||||
'@deepseek-ai/dsh-timeout':
|
||||
specifier: workspace:*
|
||||
version: link:../../util/timeout
|
||||
'@deepseek-ai/dsh-tool-executor-native':
|
||||
specifier: workspace:*
|
||||
version: link:../../core/tool-executor-native
|
||||
'@deepseek-ai/dsh-tool-testkit':
|
||||
specifier: workspace:*
|
||||
version: link:../../test-support/tool-testkit
|
||||
|
||||
@@ -728,7 +728,6 @@ export const LINK_MAP: Readonly<Record<string, string>> = {
|
||||
ToolExecutionResult: 'tools.md',
|
||||
ToolExecutionToken: 'tools.md',
|
||||
ToolGuard: 'tools.md',
|
||||
ToolPresentationMode: 'tools.md',
|
||||
ToolRuntime: 'tools.md',
|
||||
ToolsService: 'tools.md',
|
||||
ToolExecutor: 'tools.md',
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
{"type":"session/title","data":{"title":"Inspect the exact tools service","messageSeqs":[8],"source":{"kind":"fallback"}}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1788778145981,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788778145981,"index":0,"dt":[],"id":"inspect-tools-api","name":"cordis_inspect_query","args":["{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"]},{"type":"chunk","time":1788778145981,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}}},{"type":"chunk","time":1788778145981,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1788778145981,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"inspect-tools-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"tools\"}}"}}
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"inspect-tools-api"},"toolCallId":"inspect-tools-api","content":[{"type":"text","text":"{\n \"platform\": \"host\",\n \"provider\": \"Service\",\n \"method\": \"listService\",\n \"data\": {\n \"mode\": \"service\",\n \"service\": {\n \"key\": \"tools\",\n \"description\": \"Concrete tool registry and scoped executor dispatcher.\",\n \"access\": {\n \"optional\": {\n \"expression\": \"ctx.get(\\\"tools\\\")\",\n \"requiresUndefinedCheck\": true\n },\n \"hardDependency\": {\n \"inject\": [\n \"tools\"\n ],\n \"expression\": \"ctx.tools\"\n }\n },\n \"methods\": [\n {\n \"signature\": \"register(definition: ToolDefinition): () => void\",\n \"description\": \"Register globally or in the calling agent scope. Scoped tools shadow globals; duplicates within one layer and the reserved `run_code` name fail.\",\n \"parameters\": [\n {\n \"name\": \"definition\",\n \"description\": \"tool schema, execution, and optional finalization/presentation callbacks.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the tool.\"\n },\n {\n \"signature\": \"restrict(filter: ToolRestriction): () => void\",\n \"description\": \"Restrict global tools for the calling agent scope. Empty filters, unknown names, scope-local names, and reserved transport names fail. Restrictions intersect; scoped registrations remain visible.\",\n \"parameters\": [\n {\n \"name\": \"filter\",\n \"description\": \"global-tool mask: `allow` (keep only) and/or `deny` (remove).\"\n }\n ],\n \"returns\": \"the exact disposer that lifts this restriction.\"\n },\n {\n \"signature\": \"get(name: string, scope?: ScopeKey): ToolDefinition | undefined\",\n \"description\": \"Look up a tool as one scope sees it (scoped shadows global; a restricted-away global reads as absent). Presenters pass the calling agent so the rendered card matches the definition that actually executed.\",\n \"parameters\": [\n {\n \"name\": \"name\",\n \"description\": \"the tool name as registered.\"\n },\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"the definition the scope resolves, or undefined when none is visible.\"\n },\n {\n \"signature\": \"definitions(scope?: ScopeKey): readonly ToolDefinition[]\",\n \"description\": \"Read the visible capability definitions for descriptions and program bindings.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"viewing Agent or preset; omitted selects the global view.\"\n }\n ],\n \"returns\": \"definitions after scoped shadowing and restrictions.\"\n },\n {\n \"signature\": \"schemas(scope?: ScopeKey): ToolSchema[]\",\n \"description\": \"Project visible definitions onto the allowlisted model-facing schema fields, excluding execution and presentation callbacks.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"one deep-cloned schema per visible tool.\"\n },\n {\n \"signature\": \"setExecutor(executor: ToolExecutor): () => void\",\n \"description\": \"Provide the executor for the calling scope; an unscoped contribution is the deployment default.\",\n \"parameters\": [\n {\n \"name\": \"executor\",\n \"description\": \"initialized provider owned by the contributing plugin.\"\n }\n ],\n \"returns\": \"the effect disposer. Unloading leaves the selection unavailable until it is replaced.\"\n },\n {\n \"signature\": \"async execute(input: ToolBatchInput): Promise<ToolBatchResult>\",\n \"description\": \"Delegate a raw call batch to its Agent's provider or the deployment default. Select at dispatch; accepted batches settle through that provider even if it unloads.\",\n \"parameters\": [\n {\n \"name\": \"input\",\n \"description\": \"original calls, optional owning Agent, and optional logging step.\"\n }\n ],\n \"returns\": \"the selected executor's settled batch; unavailable selections reject.\"\n },\n {\n \"sLine truncated
|
||||
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"inspect-tools-api"},"toolCallId":"inspect-tools-api","content":[{"type":"text","text":"{\n \"platform\": \"host\",\n \"provider\": \"Service\",\n \"method\": \"listService\",\n \"data\": {\n \"mode\": \"service\",\n \"service\": {\n \"key\": \"tools\",\n \"description\": \"Concrete tool registry and scoped executor dispatcher.\",\n \"access\": {\n \"optional\": {\n \"expression\": \"ctx.get(\\\"tools\\\")\",\n \"requiresUndefinedCheck\": true\n },\n \"hardDependency\": {\n \"inject\": [\n \"tools\"\n ],\n \"expression\": \"ctx.tools\"\n }\n },\n \"methods\": [\n {\n \"signature\": \"register(definition: ToolDefinition): () => void\",\n \"description\": \"Register globally or in the calling agent scope. Scoped tools shadow globals; duplicates within one layer and the reserved `run_code` name fail.\",\n \"parameters\": [\n {\n \"name\": \"definition\",\n \"description\": \"tool schema, execution, and optional finalization/presentation callbacks.\"\n }\n ],\n \"returns\": \"the exact disposer that unregisters the tool.\"\n },\n {\n \"signature\": \"restrict(filter: ToolRestriction): () => void\",\n \"description\": \"Restrict global tools for the calling agent scope. Empty filters, unknown names, scope-local names, and reserved transport names fail. Restrictions intersect; scoped registrations remain visible.\",\n \"parameters\": [\n {\n \"name\": \"filter\",\n \"description\": \"global-tool mask: `allow` (keep only) and/or `deny` (remove).\"\n }\n ],\n \"returns\": \"the exact disposer that lifts this restriction.\"\n },\n {\n \"signature\": \"get(name: string, scope?: ScopeKey): ToolDefinition | undefined\",\n \"description\": \"Look up a tool as one scope sees it (scoped shadows global; a restricted-away global reads as absent). Presenters pass the calling agent so the rendered card matches the definition that actually executed.\",\n \"parameters\": [\n {\n \"name\": \"name\",\n \"description\": \"the tool name as registered.\"\n },\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"the definition the scope resolves, or undefined when none is visible.\"\n },\n {\n \"signature\": \"definitions(scope?: ScopeKey): readonly ToolDefinition[]\",\n \"description\": \"Read the visible capability definitions for descriptions and program bindings.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"viewing Agent or preset; omitted selects the global view.\"\n }\n ],\n \"returns\": \"definitions after scoped shadowing and restrictions.\"\n },\n {\n \"signature\": \"schemas(scope?: ScopeKey): ToolSchema[]\",\n \"description\": \"Project visible definitions onto the allowlisted model-facing schema fields, excluding execution and presentation callbacks.\",\n \"parameters\": [\n {\n \"name\": \"scope\",\n \"description\": \"the viewing scope (the agent); omitted = the global view.\"\n }\n ],\n \"returns\": \"one deep-cloned schema per visible tool.\"\n },\n {\n \"signature\": \"setExecutor(executor: ToolExecutor): () => void\",\n \"description\": \"Provide the executor for the calling scope; an unscoped contribution is the deployment default. Rendering any executor-owned section requires the viewing scope's selection to remain available.\",\n \"parameters\": [\n {\n \"name\": \"executor\",\n \"description\": \"initialized provider owned by the contributing plugin.\"\n }\n ],\n \"returns\": \"the effect disposer. Unloading leaves the selection unavailable until it is replaced.\"\n },\n {\n \"signature\": \"async execute(input: ToolBatchInput): Promise<ToolBatchResult>\",\n \"description\": \"Delegate a raw call batch to its Agent's provider or the deployment default. Select at dispatch; accepted batches settle through that provider even if it unloads.\",\n \"parameters\": [\n {\n \"name\": \"input\",\n \"description\": \"original calls, optional owning Agent, and optional logging step.\"\n }\n ],\n \"returns\": \"the selectedLine truncated
|
||||
{"type":"step/end","data":{"turn":1,"step":1}}
|
||||
{"type":"step/start","data":{"turn":1,"step":2}}
|
||||
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"inspect-subprocess-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1788778146017,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788778146017,"index":0,"dt":[],"id":"inspect-subprocess-api","name":"cordis_inspect_query","args":["{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"]},{"type":"chunk","time":1788778146017,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"inspect-subprocess-api","name":"cordis_inspect_query","arguments":"{\"platform\":\"host\",\"provider\":\"Service\",\"method\":\"listService\",\"input\":{\"service\":\"subprocess\"}}"}}},{"type":"chunk","time":1788778146017,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1788778146017,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
|
||||
|
||||
Reference in new issue
Block a user