Files
deepseek-harness/packages/core/tools

description, kind
description kind
The tool registry and execution pipeline for tool authors and maintainers registering, restricting, presenting, or debugging model-facing tools. package-reference

@deepseek-ai/dsh-tools

English | 中文

Summary

With dsh-tools, tool plugins register schemas and executors, and every model tool call runs through a guarded pipeline — allow/deny/ask policy, monotonic guards, around-dispatch wrappers, result inspection, definition-owned content finalization, and a final observe-only notification. The package also controls how tools are presented to the model: its mode config selects native function calling, Code Mode, or both, and one agent shadows that default for itself with presentAs. Tool authors use defineTool for typed parameter and output schemas, an optional cooperative timeout, parallel-safety classification, and optional UI presentation intents. Choose it as the registry for any capability you want the model to reach — schemas flow into prompt assembly automatically.

Table of Contents


Use this package

Mount dsh-tools wherever agents call tools: it provides ctx.tools, the registry every tool plugin registers into and the loop dispatches through. Registering a tool is enough to make it visible — the registry feeds its schemas into the system-prompt assembly automatically.

Register a tool

defineTool builds a typed tool definition: a model-facing name, description, and parameter schema, a canonical output declaration, and an execute body that returns only the declared JSON value. Model arguments are validated before execution; invalid input becomes a normal error result.

import { readFile } from 'node:fs/promises'
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

declare const ctx: Context

ctx.tools.register(defineTool({
  name: 'read_file',
  description: 'Read a file from disk.',
  parameters: {
    path: { type: 'string', required: true, description: 'Absolute file path' },
    offset: { type: 'number' },
    limit: { type: 'number' },
  },
  output: {
    schema: { type: 'string' },
    render: (_args, value) => [{ type: 'text', text: value }],
  },
  async execute(args, exec) {
    // args is typed: { path: string; offset?: number; limit?: number }
    return readFile(args.path, { encoding: 'utf8', signal: exec.signal })
  },
}))

The unified schema DSL supports string, number, integer, boolean, null, array, object, author-only json, and exact-one oneOf; InferValue preserves exact types through 16 container levels before widening to JsonValue. A raw JSON Schema (JsonSchemaNode) is the wire-level counterpart shared with subagents, workflows, and MCP.

Configure the presentation mode

The mode config decides what the model sees: native (every visible schema), code (only run_code plus a generated SDK), or both.

- name: '@deepseek-ai/dsh-tools'
  config:
    mode: native
Field Default Meaning
mode native How visible tools are presented to the model: native, code, or both
maxParallelSubCalls 10 Concurrency cap for a run_code program's overlapping sub-calls; 1 restores strictly serial dispatch

The generated configuration catalog is the exhaustive source for every accepted field. Non-native modes require a composed ctx.codeRuntime whose language has a registered SDK renderer; an agent preset selects its own presentation with dsh-agent-tool-presentation, and one agent can shadow the default with presentAs(mode).

Restrict tools per agent

ctx.tools.restrict(filter) applies an allow or deny mask to the global tools one agent inherits; masks intersect, scoped registrations stay visible, and the restriction lifts when disposed. ctx.tools.get(name, scope) resolves a tool as one scope sees it. A Host-local presenter consumer passes the calling agent when it must match the definition that executed. ctx.tools.schemas(scope) returns the visible schemas without the execute functions.

Enforce policy on calls

ctx.tools.guard(guard) registers a monotonic synchronous guard after the extensible tools/pre-execute waterfall: a returned reason denies the call, and no later listener can turn that denial back into permission. The pipeline's events give plugins more control — tools/pre-execute decides allow/deny/ask, tools/execute wraps dispatch for timeout or retry, tools/post-execute inspects or replaces the result, and tools/result observes the frozen final outcome.

Host presentation descriptors

A tool can retain pure presentCall() and presentResult() methods for Host-local consumers. The built-in Web Client does not consume those values. It selects a renderer through tool.call.toolview and derives card props from raw call arguments, result content, failure state, and persisted metadata. The Client-derived presentation decision owns this transport split.


Understand the implementation

Implementation internals — click to expand

This section explains how the package realizes the behavior above; the observable contract is covered in Use this package.

Design concept

The registry holds typed ToolDefinitions in scoped layers and projects them onto the model-facing ToolSchema set at request time — output, execute, finalizeContent, timeoutMs, and presentation callbacks never leak onto the wire. Every call runs a fixed pipeline: tools/pre-execute (extensible allow/deny/ask) → registered monotonic guards → tools/execute (around-dispatch wrappers) → tools/post-execute (inspect/replace, attach context) → definition-owned finalizeContent → the observe-only tools/result event. Only the tools/execute view may replace the required signal, and the registry re-fuses the caller signal before the body.

Source map

File Role
src/index.ts Plugin entry: ToolRuntime service, config, registry, execution pipeline
src/types.ts ToolDefinition, ToolExecution, ToolExecutionResult, guard and decision types
src/schema.ts The defineTool DSL: ValueSchemaSpec, ParameterSchemaSpec, InferValue, InferArgs
src/json-schema.ts The enforced raw JSON Schema subset and validation
src/presentation.ts The card-tagged UI render intents
src/code-mode.ts Code Mode: SDK generation, run_code dispatch bridge, settlement
src/ts-types.ts TypeScript SDK type rendering
src/py-types.ts Python SDK type rendering
src/invariant.ts Invariant companion

Execution and cancellation

Each typed invocation materializes and freezes parsed arguments, assigns an opaque correlation token, and runs policy and dispatch. Cancellation is cooperative and quiescent: every tool body receives the caller-owned exec.signal and must observe it; cancellation before body invocation is ABORTED_BEFORE_DISPATCH, after invocation it replaces only a successful outcome with ABORTED. Denials, wrapper failures, tool failures, post-policy failures, and timeout-owned TOOL_TIMEOUT remain more specific. Unknown and throwing tools become structured errors (UNKNOWN_TOOL), so a call fails without ending the turn.

Code Mode

Under code or both, the registry exposes the reserved run_code transport plus a deterministic SDK generated in the loaded runtime's language. Each SDK binding call re-enters the complete tool pipeline with logged correlation to the outer call, scheduled through a per-run pool that reuses the native concurrency contract. Under code alone, a model-direct call naming any other visible tool resolves to UNKNOWN_TOOL before policy — the announced surface and the callable surface stay the same. Intermediate binding values are execution-local; only the outer run_code result has a hard size cap. The executor-collapse note owns the collapse contract.

Extension points

Tool plugins call ctx.tools.register() and their schemas flow into prompt assembly automatically. tools/pre-execute is the reorderable allow/deny/ask gate; ctx.tools.guard() adds monotonic owner policy after it; tools/execute wraps normalized canonical dispatch for timeout, retry, or metrics; tools/post-execute may replace content or value, block with feedback, or attach ordered contexts; tools/result observes the immutable final outcome. MCP servers discover tools and register them with the server's schemas.


Further Exploration

The package-level contract is enough for most consumers; read these when you need the surrounding domain.


Model Experience

Normal tool schemas

What the model sees

In normal mode the model sees each visible definition's exact name, description, and JSON schema; the shipped definitions are recorded in the generated tool catalog. Agent-scoped restrictions, shadows, and extension registrations change that agent's end-tool set.

Token effect

Fixed per-request cost proportional to the visible definitions. Restrictions that hide tools remove their entire schema cost for that agent.

KV Cache effect

Prefix-stable while visible definitions and their order are unchanged. Registration, disposal, or scoped restriction may invalidate reuse from the first changed schema token.

Code Mode schema and system prompt

What the model sees

Code Mode exposes the generated run_code schema, the SDK instructions below, and the generated exact SDK block for the loaded runtime's language. The tools:sdk section uses first-party order 5000. both exposes normal schemas and this Code Mode API; under code the prompt also carries the tools:code-only rule earlier in the first-party order, so the model reads which tools it may call before it reads what each one is for.

Code Mode SDK instructions
## Writing code for run_code

`run_code` takes two required arguments: `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped) — and `description`, a short summary of what the program does. Inside the program:

- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.
- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.
- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.
- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.

The available tools:

Token effect

Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction.

KV Cache effect

Prefix-stable while the Code Mode selection, generated SDK, transport schema, and visible tool set are unchanged. Mode or filter changes may invalidate reuse from the first changed prompt or schema token.

Tool-call history and results

What the model sees

The loop retains model-emitted arguments and the registry's final content. Any thrown or denied call becomes exactly Error: <message>. Code Mode renders the outer program's printed lines and return value, (run_code completed with no output) when both are empty, or Error: code run failed (<kind>): <message> followed conditionally by Captured output: and the captured lines. Inner dispatch events stay log-only, while a successful image-bearing sub-result is appended after the outer result as source-attributed context.

Token effect

Arguments, results, and additional context are data-dependent and resent until compaction. Restrictions that hide tools also remove their schemas before the model can call them.

KV Cache effect

Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.

Known Limitations and Deferred Work

These limits define when the registry needs special care. They are current package constraints, not a task backlog.

  • Concurrency policy is not an event gateexecutionMode() reads the resolved tool definition directly; plugins can only declare a classifier on definitions they own.
  • tools/pre-execute deliberately cannot rewrite exec.arguments — logged and rendered args would desync from what ran; the rewrite design is a proposed Agent Note.
  • Caller-defined subagent and workflow structured outputs remain object-rooted — this is a consumer-level guard; the shared schema vocabulary and tool outputs support every JSON root.
  • timeoutMs on a definition is declarative only — the registry never enforces deadlines; enforcement requires the @deepseek-ai/dsh-tool-call-timeout-policy wrapper.
  • Code Mode's SDK language follows the one loaded runtime, and a presentation is per agent rather than per toolmode: code/both rejects prompt assembly unless ctx.codeRuntime.language has a registered SDK renderer; within one agent no tool can be native-only while another is code-only.
  • Code Mode intermediate values are execution-local and unbounded by bytes — they cannot be reconstructed from session replay and may exhaust process or worker memory; only the outer run_code output has the worker's configurable hard cap.
  • run_code state is fresh per run — a persistent REPL-style kernel is rejected for the MVP, because cross-call state would be invisible to the log.

Dev Note

Working context for maintainers — click to expand

None.