Safe math expression evaluator, zero-dependency recursive-descent parser.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:omdsh-dev/dsh-tool-calculator
GitHub-sourced plugins run build scripts on your machine at install time. Only install sources you trust, and pin a commit (github:owner/repo#sha).
README
DSH Calculator tool plugin — a safe mathematical expression evaluator. Zero dependencies, zero processes, pure functions.
Motivation
Agents being unreliable at arithmetic is a common weakness of LLMs. DSH's built-in bash tool can call echo $((15 + 27 * 3)) to do calculations, but it has two problems:
- Every arithmetic operation spawns a bash process — especially expensive on Windows (process creation, shell loading, execution, output collection), with noticeable cumulative latency under high-frequency calls
- bash arithmetic syntax is limited — it doesn't support math functions such as
sqrt,sin,cos,log,pow; in these scenarios the agent can only guess the answer or write a script
This plugin provides a zero-dependency, zero-process, pure-function calculator — a single function call returns the result in milliseconds, covering common elementary math functions.
Security Model
No eval, no new Function. It uses a hand-written recursive descent parser (lexer layer + parser layer), evaluating only whitelisted nodes:
- The lexer layer only recognizes numeric literals, whitelisted identifiers, and operators; quotes, semicolons, backticks,
{}[]fail directly with an error - Identifiers are looked up by name in the whitelist table (15 functions + 2 constants); anything not found throws
Unknown identifier - Evaluation results must be finite numbers;
NaN/Infinity(division by zero, square root of a negative number, etc.) are uniformly rejected
new Function + regex whitelisting is unsafe — constructor.constructor(...) can reach the Function constructor directly and execute arbitrary code, and process.exit(0) can kill the host process outright (both reproduced in actual testing). This implementation uses no code-evaluation shortcuts.
Architecture
┌──────────────────────────────┐
│ DSH Agent │
│ tool call: calculator { ... }│
└──────────┬───────────────────┘
│ ctx.tools.register()
┌──────────▼───────────────────┐
│ src/index.ts │
│ Cordis 插件入口 │
└──────────┬───────────────────┘
│
┌──────────▼───────────────────┐
│ src/evaluate.ts │
│ tokenize() → parse() │
│ 递归下降解析器 │
└──────────────────────────────┘
src/index.ts: Cordis plugin entry (name/inject/apply), registers thecalculatortoolsrc/evaluate.ts:evaluate(expression: unknown): number— the entry point independently validates the type (throwscalculator: expression must be a stringfor non-strings), returns finite numbers, and throws on all invalid input
Tool Declaration
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'
import { evaluate } from './evaluate.ts'
export const name = '@deepseek-ai/dsh-tool-calculator'
export const inject = ['tools']
export function apply(ctx: Context): void {
ctx.tools.register(defineTool({
name: 'calculator',
description:
'Evaluate a mathematical expression safely. ' +
'Supports +, -, *, /, %, **, parentheses, and functions: ' +
'abs, ceil, floor, round, max, min, sqrt, pow, log, log2, log10, exp, sin, cos, tan, PI, E.',
parameters: {
expression: {
type: 'string',
required: true,
description: 'Mathematical expression, e.g. "15 + 27 * sqrt(9)"',
},
},
output: {
schema: { type: 'number' },
render: (_args, value) => [{ type: 'text', text: String(value) }],
},
execute: async (args) => evaluate(args.expression),
timeoutMs: 1000,
}))
}
Supported Operations
| Category | Items |
|---|---|
| Arithmetic | + - * / % ** (power, right-associative: 2 ** 3 ** 2 = 512) |
| Functions (single arg) | abs ceil floor round sqrt log log2 log10 exp sin cos tan |
| Functions (multiple args) | pow(x, y) max(a, b, ...) min(a, b, ...) |
| Constants | PI E |
| Grouping | ( ), unary plus/minus +5 -5 |
Precedence: ** (right-associative) > unary ± > * / % > + -.
npm 0.1.0-rc.6 Compatibility (Verified)
This plugin has been migrated to the npm 0.1.0-rc.6 dependency line and fully verified end-to-end in an isolated consumer of @deepseek-ai/dsh@0.1.0-rc.6:
- Types/runtime:
@deepseek-ai/cordis: ^4.0.1+@deepseek-ai/dsh-tools: >=0.0.1-rc.1 <0.2.0+@deepseek-ai/dsh-invariants: >=0.0.1-rc.1 <0.2.0(peer); no longer depends on unscopedcordis - Standalone build:
npm install(devDependencies self-contained: typescript/vitest/@types/node) →npm run typecheck→npm test→npm run build→npm pack - Consumption verification: tarball loaded into a 0.1.0-rc.6 consumer →
dsh --profile compat --dump-configshows this plugin's row → the tool genuinely registers and executes - Launch method:
npx -p @deepseek-ai/dsh@0.1.0-rc.6 dsh web(lib production mode; don'tinstall -gglobally)
Version Adaptation
- DSH adapted: DSH 0.1.0-rc.6 (npm) (migration: profile/bundle plugin system)
- bundle declaration:
dsh.bundleinpackage.json(patch points tocordis.patch.yml) +exportsexport - patch format:
cordis.patch.ymluses the- insert:list (the DSH 0.1.0-rc.6 (npm) patch is id-targeted semantics; a bare- id:entry reportsentry not found) - files: the published tarball contains
lib/,src/,cordis.patch.yml
Installation
Profile Bundle (Recommended)
As of DSH 0.1.0-rc.6 (npm), this plugin can be installed into any profile as a standalone bundle in one step (repositories live at https://github.com/omdsh-dev, public):
# 交互式(web)profile
dsh plugin --profile web add github:omdsh-dev/dsh-tool-calculator
# 一次性任务(headless)profile —— dsh run 默认使用 headless
dsh plugin --profile headless add github:omdsh-dev/dsh-tool-calculator
You can also pack a tarball with npm pack first and install that:
git clone https://github.com/omdsh-dev/dsh-tool-calculator
cd dsh-tool-calculator
npm install && npm pack
dsh plugin --profile web add ./deepseek-ai-dsh-tool-calculator-*.tgz
dsh plugin --profile headless add ./deepseek-ai-dsh-tool-calculator-*.tgz
The in-package dsh.bundle.patch (pointing to cordis.patch.yml) automatically adds the plugin to the profile's layer stack after installation; the plugin's cordis.patch.yml inserts the tool-calculator entry via - insert:. The plugin's missing peer dependencies (@deepseek-ai/cordis, @deepseek-ai/dsh-tools, @deepseek-ai/dsh-invariants) are provided by the profile's healed profiles/node_modules fallback installation.
⚠️ web and headless are different profiles: installing for web doesn't automatically cover headless;
dsh runuses the headless profile by default. Use forward slashes for Windows paths (C:/...).
Verify Installation
dsh --profile web --dump-config | grep tool-calculator
Runtime Verification
dsh run "使用 calculator 工具计算 1+2*3"
Manual Installation (Source Contribution / Legacy Snapshot Scenarios)
For source contribution (developing/debugging this plugin in the monorepo) or for scenarios still using old snapshots:
- Place it in the monorepo:
cp -r calculator ~/.dsh/source/master/packages/tools/calculator(dev debugging) - Add
"@deepseek-ai/dsh-tool-calculator": "workspace:^"toapps/cli/package.json; add{ "path": "./packages/tools/calculator" }to thetsconfig.host.jsonreferences pnpm install && pnpm run build- Insert the plugin in the profile user-layer patch (
~/.dsh/profiles/<name>/cordis.patch.yml):
- insert:
- id: tool-calculator
name: '@deepseek-ai/dsh-tool-calculator'
- Verify:
dsh --profile <name> --dump-config | grep tool-calculator
DSH 0.1.0-rc.6 (npm) note: the patch is id-targeted semantics — a bare
- id:entry reportsentry "xxx" not found; it must be wrapped in a- insert:list.
Usage
After installation, the agent automatically gets the calculator tool:
calculator { expression: "15 + 27 * sqrt(9)" } → 96
The tool name satisfies DeepSeek's function-name constraints (≤64 characters, [A-Za-z0-9_-]). After registration it automatically enters the Code Mode SDK (await tools.calculator(...)), and the canonical return value is a number.
Known Limitations
- Distribution chain:
@deepseek-ai/dsh-toolsis now published as a private npm package with DSH 0.1.0-rc.6 (npm); the plugin installs directly viadsh plugin add github:omdsh-dev/...or the tarball, with no need to place it in the monorepo for workspace resolution - Trigonometric functions use radians: consistent with
Math.sin/Math.cos; writesin(30 * PI / 180)when you need degrees - No big integers: JS
numberis an IEEE 754 double; the safe integer range is ±9e15, beyond which precision is lost - No scientific notation:
1e5is rejected by the lexer layer
Testing
pnpm test
Includes functional test cases and attack-payload cases (constructor.constructor chain, process.exit(0), globalThis, quote injection, semicolon statements, etc.). The complete case list is in the locally maintained design document.
License
MIT
Links
More in this category
liustack/modlens★ 1199
Vision bridge for text-only models: paste an image, get structured JSON evidence (OCR, layout, semantics).
Anionex/dsh-vision-toolkit★ 308
Vision tasks for text-only models: intent-aware image Q&A, long-screenshot OCR, UI reproduction, grounding, and pixel diff.
zhaoolee/notes★ 138
Export DSH conversations as Smartisan Notes-style PNGs, or create and update Markdown notes in a configured account-scoped workspace.
liustack/modsearch★ 85
Web search bridge for text-only agents: ask the web or X, get structured JSON evidence (search, fetch, citations).
Lum1104/dsh-browser★ 80
Chrome sidebar extension that lets DSH operate your browser directly, no vision capabilities required.
taxueseek/argo★ 69
Search built for agents: multilingual coverage across web, academic, code, shopping, finance, news, and encyclopedias.