Test/extract/safe-replace/statically explain regexes without executing code.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:omdsh-dev/dsh-tool-regex
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
DSH regex tool plugin — test matches, extract capture groups, safe replacement, and statically explain the meaning of a regex (without executing any code). Zero dependencies, pure functions.
Motivation
Models often need to validate a user-provided pattern, extract fields from logs/text, and perform text replacement. "Mental" regex evaluation has an extremely high error rate and cannot show the user a verifiable process. The existing alternative is to spawn a bash process to run node -e or python — process overhead plus the correctness risk of scripts the model writes on the fly. The built-in grep only supports file-scoped search; it cannot test/extract/replace/explain on arbitrary text.
This plugin provides deterministic regex tools, of which explain is the differentiating capability: it statically parses the pattern structure and returns a human-readable explanation, without executing the match, making it inherently immune to ReDoS.
Security Model (Multi-layered ReDoS Defense)
Catastrophic backtracking in JS regex is a real threat (e.g. (a+)+$ combined with a very long input). Defenses:
- Worker hard timeout: test/find/replace run synchronously inside a terminable worker thread; when the 1,000ms budget expires,
worker.terminate()is called andregex: execution timed outis returned — catastrophic backtracking can no longer block the host process (the tool pipeline'stimeoutMsis cooperative for a synchronous blocking body and is insufficient on its own; all limit checks are re-executed inside the worker) - Input length cap: 64,000 bytes (UTF-8) — over-limit inputs are rejected at the entry point and never enter backtracking
- Resource caps: pattern ≤ 16KB, replacement ≤ 16KB, output ≤ 1MB, match count ≤ 1,000 (clamped by limit)
- explain executes nothing: only a static tokenizer; no
RegExpinstance is constructed, so any pattern returns immediately
⚠️ Both the tool description and this README explicitly warn the model: do not use unanchored nested-quantifier patterns (such as
(a+)+,(.*)*) on untrusted large inputs.
Other boundaries: invalid patterns are caught and reported as SyntaxError (including position info); invalid/duplicate flags are validated character by character; replace uses the String.replace string replacement path (native JS $ semantics, no new Function, no eval).
Tool Declaration
Registers the regex tool (@deepseek-ai/dsh-tool-regex, row id tool-regex), uniformly outputting a JSON text string.
| Parameter | Type | Required | Description |
|---|---|---|---|
action |
string | ✅ | test / find / replace / explain |
pattern |
string | ✅ | Regex (JavaScript syntax, without surrounding /); ≤ 16KB |
input |
string | Text to match (required for test/find/replace); ≤ 64KB | |
flags |
string | e.g. "gi"; supports g i m s u y d v, must be unique and valid |
|
replacement |
string | Replacement text for replace, supports $1/$2/$<name>/$$; ≤ 16KB |
|
limit |
integer | Max number of matches reported by find, default 50, cap 1,000 |
Actions
| action | Function | Output example |
|---|---|---|
test |
Determine whether it matches (whole-string semantics are expressed by the model itself via ^...$) |
{"matched":true} |
find |
All matches: index / full match / numbered capture groups captures / named groups groups (auto-adds g when absent) |
[{"index":0,"match":"a@b","captures":["a","b"],"groups":{"name":"a"}}] |
replace |
Global safe replacement ($1/$<name>/$$), returns the result and the replacement count |
{"result":"world hello","replaced":1} |
explain |
Statically parse the pattern → human-readable node sequence (no match executed; node count ≤ 4,096) | [{"kind":"escape","text":"\\d","meaning":"A digit [0-9]"}] |
Examples
regex { action: "find", pattern: "(\\w+)@(\\w+)", input: "a@b x c@d" }
→ [{"index":0,"match":"a@b","captures":["a","b"],"groups":null},{"index":6,"match":"c@d","captures":["c","d"],"groups":null}]
regex { action: "replace", pattern: "(\\w+) (\\w+)", input: "hello world", replacement: "$2 $1" }
→ {"result":"world hello","replaced":1}
regex { action: "explain", pattern: "\\d{4}-\\d{2}" }
→ [{"kind":"escape","text":"\\d","meaning":"A digit [0-9]"},{"kind":"quantifier","text":"{4}",...},...]
Edge Cases
| Case | Handling |
|---|---|
| Invalid pattern | regex: invalid pattern: <SyntaxError message (with position)>, no crash |
| Invalid / duplicate flag | regex: invalid flag "q" / regex: duplicate flag "g" |
| Empty pattern | Valid (matches the empty string); under u/v, empty matches advance by code point (surrogate pairs are not matched twice) |
| ReDoS (pathological pattern) | Worker hard timeout: regex: execution timed out (1000ms), host not blocked |
| Named / numbered groups | find outputs groups: {name: value} and captures: [...]; replace supports $<name>/$n |
| Zero matches | find returns []; replace returns the original text + replaced: 0 |
| Input over 64KB / pattern over 16KB / replacement over 16KB | Rejected at the entry point (not truncated) |
Output over 1MB (replacement expansion such as $`` / $'`) |
regex: result/output exceeds 1000000 bytes, rejected rather than truncated |
| find limit | Default 50, clamped to 1,000 (prevents output bloat) |
| explain nodes over 4,096 | regex: explain: pattern too complex |
$ references |
Go through the native JS string replacement path: $$→$, $n→group (empty string if not matched), $<name>→named group, unknown references kept literally ($0/$<foo> consistent with V8) |
DSH 0.1.5-rc.1 Compatibility (Verified)
This plugin has been migrated to the DSH 0.1.5-rc.1 harness and fully verified end-to-end in an isolated consumer of local harness 0.1.5-rc.1 (npm private package):
- Types/runtime: peers are
@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; no longer depends on the unscopedcordis - Standalone build:
npm install(devDependencies are self-contained: typescript/vitest/@types/node) →npm run typecheck→npm test→npm run build→npm pack - Consumption verification: tarball installed into a DSH 0.1.5-rc.1 consumer →
dsh --profile compat --dump-configshows this plugin's row → tool registration and execution actually pass - Launch method:
npx -p @deepseek-ai/dsh@next dsh web(lib production mode; do notinstall -gglobally)
Installation
Profile Bundle (Recommended)
Install this plugin into a profile as a standalone bundle (DSH 0.1.5-rc.1). This repository lives under the omdsh-dev organization and is publicly accessible:
# Interactive (web) profile —— install from the GitHub repository
dsh plugin --profile web add github:omdsh-dev/dsh-tool-regex
# One-off task (headless) profile —— dsh run uses headless by default
dsh plugin --profile headless add github:omdsh-dev/dsh-tool-regex
Or install from the tarball produced by npm pack:
npm pack # produces dsh-tool-regex-<version>.tgz
# Interactive (web) profile
dsh plugin --profile web add ./dsh-tool-regex-<version>.tgz
# One-off task (headless) profile
dsh plugin --profile headless add ./dsh-tool-regex-<version>.tgz
The bundled dsh.bundle.patch automatically adds the plugin to the profile's layer stack after installation (row id: tool-regex). The plugin's missing peer dependencies (@deepseek-ai/cordis, @deepseek-ai/dsh-tools) are provided by the profile's healed profiles/node_modules fallback installation.
⚠️ web and headless are different profiles: installing into web does not 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-regex
Runtime Verification
dsh run "使用 regex 工具测试 d+ 是否匹配 abc123"
Manual Installation and Legacy Compatibility (legacy monorepo scenario)
The monorepo way is only for legacy scenarios: old snapshots that do not support Profile Bundle, or plugin development/debugging environments (local junction/symlink, manually editing profile layers).
Tests
node <monorepo>/node_modules/vitest/vitest.mjs run tests
engine.spec.ts: all branches of test/find/replace + flags/pattern errors + 64KB cap + ReDoS worker cases (pathological patterns are cancelled within the 3s budget without hanging the test process)explain.spec.ts: literals/character classes/groups/quantifiers/escapes/anchors/alternation + unterminated errorsregister.spec.ts: registration contract (AUDIT-CROSS-02 style)
License
MIT
Links
More in this category
Tencent/WeKnora#dsh-weknora★ 30675
Four read-only tools over a WeKnora knowledge base: list knowledge bases, hybrid passage search, reassemble one document's chunks in order, and WeKnora's own cited RAG or ReAct-agent answer with a resumable session id.
superdesigndev/treg★ 3596
Tool catalog for agents: search ~2,600 external endpoints (SEO and SERP, backlinks, social, people and company enrichment, ad libraries, scraping) by the task you want done, read each one's parameters and per-call price, then call it with the credential injected server-side. Ships the skill plus an MCP row that stays disabled until TREG_TOKEN is set.
TencentCloudBase/CloudBase-AI-Toolkit#dsh-plugin★ 1126
Tencent CloudBase backend for DeepSeek Harness — scaffold and deploy full-stack apps from chat, render query results as table cards with paging, sorting and CSV export, preview a deployment on its domain, and call the CloudBase MCP toolset (`mcp__cloudbase__*`) with device-code login.
gitroomhq/postiz-agent#dsh-postiz★ 496
Connects DeepSeek Harness to Postiz over MCP: list connected social media channels, fetch per-platform posting rules, and schedule, draft, or publish posts to X, LinkedIn, Instagram, Facebook, Threads, TikTok, YouTube, Reddit, Bluesky, Mastodon, Discord, Slack, Telegram and more; adds a postiz workflow skill.
EthanYoQ/Invoice-Downloader#dsh-invoice-downloader★ 446
Local IMAP invoice download, OCR, archive, and Excel reimbursement summaries for DeepSeek Harness.
anysearch-team/anysearch-dsh★ 430
AnySearch-powered real-time web and vertical search provider for DeepSeek Harness.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.