fix(tools): collapse code-mode executor to run_code for model-direct calls

wireSchemas() already advertised only run_code under mode: 'code', but the
executor resolved every call through get(), which returns the full visible
map plus the reserved transport. A model could name a native tool directly
and bypass run_code entirely. Route the execution-path lookups through a
new private resolveExecution() that applies the mode collapse at the
operation boundary: model-direct calls under 'code' may only name run_code
(UNKNOWN_TOOL otherwise), while SDK sub-dispatches (parent token set) keep
every visible tool. get()/schemas() public semantics are unchanged.

The denial happens at createExecution, before the extensible policy
pipeline — pre-execute listeners, approval ask, and guards never observe
a call that is deterministically denied. A collapsed call honors the
pre-dispatch cancellation contract, routes aborted results through the
visible tool's finalizeContent, and captures the finalizer before
argument materialization.

Under code mode, a system-prompt/assemble listener filters out tool:*
guidance sections that told the model to call native tools directly.
The tools:sdk section and SDK types remain so programs can still use
all tools through run_code.

Regenerated docs, catalogs, graphs, scoped events, and re-recorded
translation pairs.

Fixes #1815
This commit is contained in:
Chinesezjc
2026-08-11 00:58:45 +08:00
parent 75a09efd51
commit 6f6defd64a
8 changed files with 29 additions and 23 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/config-catalog.md
config-catalog.md: 08c1c63050aefd9e0fcc7c603ad40ec6405bf6c1
config-catalog.md: f84fdb17f29f9352cda77fc1901beba79a416c2f
config-catalog.zh.md: e7cfcff8d69c3a1efbe8426e25415783bc41f864

View File

@@ -2507,7 +2507,7 @@ export interface Config {
export type ToolPresentationMode = 'native' | 'code' | 'both'
```
Source: [`packages/core/tools/src/index.ts:625`](../packages/core/tools/src/index.ts)
Source: [`packages/core/tools/src/index.ts:629`](../packages/core/tools/src/index.ts)
## `@deepseek-ai/dsh-typert-loader`

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/tools.md
tools.md: 54d20de3b4d1f2ff6d03a8e89e9356eef403382a
tools.zh.md: f8a35ffa7121438b0623f2a0972f90df2923ac89
tools.md: 4f0360f45e7ed4f3537bedeb0f370f8eb3472ab9
tools.zh.md: aa6c67c6379e4a9b9ab7bdc473f6294d063e3649

View File

@@ -156,14 +156,13 @@ Registration is a trusted same-process contract. The registry borrows the typed
```ts type-equiv
/**
* Per-scope filter over the tools a scope INHERITS — the global layer and
* every ancestor layer on its chain. Restrictions intersect, and do not affect
* the scope's own registrations or the reserved Code Mode transport.
* Per-scope filter over global tools. Restrictions intersect and do not affect
* scoped registrations or the reserved Code Mode transport.
*/
interface ToolRestriction {
/** Inherited tool names that stay visible; every other inherited one is removed. */
/** Global tool names that stay visible; everything else is removed. */
readonly allow?: readonly string[]
/** Inherited tool names removed from visibility. */
/** Global tool names removed from visibility. */
readonly deny?: readonly string[]
}
```
@@ -198,8 +197,12 @@ interface ToolExecutionInput {
/**
* Opaque token of the enclosing transport execution, when one exists. Code
* 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 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: 'code'`, 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. See {@link ToolRegistry.execute}.
*/
readonly parent?: ToolExecutionToken
/** Required caller-owned cancellation for this invocation. */
readonly signal: AbortSignal
@@ -568,7 +571,7 @@ async execute(exec: ToolExecutionInput): Promise<ToolExecutionResult>
Types: [ScopeKey](scope.md)
Source: [`packages/core/tools/src/index.ts:761`](../../packages/core/tools/src/index.ts)
Source: [`packages/core/tools/src/index.ts:764`](../../packages/core/tools/src/index.ts)
<a id="tools-events"></a>

View File

@@ -156,14 +156,13 @@ type InferArgs<S> = InferProperties<S, []>
```ts type-equiv
/**
* Per-scope filter over the tools a scope INHERITS — the global layer and
* every ancestor layer on its chain. Restrictions intersect, and do not affect
* the scope's own registrations or the reserved Code Mode transport.
* Per-scope filter over global tools. Restrictions intersect and do not affect
* scoped registrations or the reserved Code Mode transport.
*/
interface ToolRestriction {
/** Inherited tool names that stay visible; every other inherited one is removed. */
/** Global tool names that stay visible; everything else is removed. */
readonly allow?: readonly string[]
/** Inherited tool names removed from visibility. */
/** Global tool names removed from visibility. */
readonly deny?: readonly string[]
}
```
@@ -198,8 +197,12 @@ interface ToolExecutionInput {
/**
* Opaque token of the enclosing transport execution, when one exists. Code
* 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 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: 'code'`, 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. See {@link ToolRegistry.execute}.
*/
readonly parent?: ToolExecutionToken
/** Required caller-owned cancellation for this invocation. */
readonly signal: AbortSignal
@@ -568,7 +571,7 @@ async execute(exec: ToolExecutionInput): Promise<ToolExecutionResult>
Types: [ScopeKey](scope.md)
Source: [`packages/core/tools/src/index.ts:761`](../../packages/core/tools/src/index.ts)
Source: [`packages/core/tools/src/index.ts:764`](../../packages/core/tools/src/index.ts)
<a id="tools-events"></a>