anolisa-tokenless 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +770 -0
- package/adapters/tokenless/claude-code/.claude-plugin/marketplace.json +15 -0
- package/adapters/tokenless/claude-code/.claude-plugin/plugin.json +9 -0
- package/adapters/tokenless/claude-code/hooks/hooks.json +38 -0
- package/adapters/tokenless/claude-code/scripts/detect.sh +193 -0
- package/adapters/tokenless/claude-code/scripts/install.sh +92 -0
- package/adapters/tokenless/claude-code/scripts/uninstall.sh +50 -0
- package/adapters/tokenless/codex/.codex-plugin/plugin.json +20 -0
- package/adapters/tokenless/codex/README.md +160 -0
- package/adapters/tokenless/codex/hooks/hooks.json +52 -0
- package/adapters/tokenless/codex/scripts/_common.sh +25 -0
- package/adapters/tokenless/codex/scripts/check-tokenless +94 -0
- package/adapters/tokenless/codex/scripts/compress-response +441 -0
- package/adapters/tokenless/codex/scripts/detect.sh +61 -0
- package/adapters/tokenless/codex/scripts/install.sh +151 -0
- package/adapters/tokenless/codex/scripts/rewrite-hook +282 -0
- package/adapters/tokenless/codex/scripts/tool-ready +303 -0
- package/adapters/tokenless/codex/scripts/uninstall.sh +78 -0
- package/adapters/tokenless/common/commands/tokenless-stats.toml +2 -0
- package/adapters/tokenless/common/cosh-extension.json +65 -0
- package/adapters/tokenless/common/hooks/compress_response_hook.py +487 -0
- package/adapters/tokenless/common/hooks/compress_schema_hook.py +144 -0
- package/adapters/tokenless/common/hooks/compress_toon_hook.py +160 -0
- package/adapters/tokenless/common/hooks/hook_utils.py +584 -0
- package/adapters/tokenless/common/hooks/rewrite_hook.py +223 -0
- package/adapters/tokenless/common/hooks/run-hook.sh +62 -0
- package/adapters/tokenless/common/hooks/tool_categories.json +99 -0
- package/adapters/tokenless/common/hooks/tool_ready_hook.sh +571 -0
- package/adapters/tokenless/common/tokenless-env-fix.sh +730 -0
- package/adapters/tokenless/common/tool-ready-spec.json +113 -0
- package/adapters/tokenless/dsh/cordis.patch.yml +8 -0
- package/adapters/tokenless/dsh/dist/index.js +399 -0
- package/adapters/tokenless/dsh/package.json +26 -0
- package/adapters/tokenless/hermes/__init__.py +572 -0
- package/adapters/tokenless/hermes/plugin.yaml +9 -0
- package/adapters/tokenless/hermes/scripts/detect.sh +80 -0
- package/adapters/tokenless/hermes/scripts/install.sh +60 -0
- package/adapters/tokenless/hermes/scripts/uninstall.sh +45 -0
- package/adapters/tokenless/manifest.json +147 -0
- package/adapters/tokenless/openclaw/dist/index.d.ts +27 -0
- package/adapters/tokenless/openclaw/dist/index.js +598 -0
- package/adapters/tokenless/openclaw/dist/tool_categories.json +99 -0
- package/adapters/tokenless/openclaw/index.ts +720 -0
- package/adapters/tokenless/openclaw/openclaw.plugin.json +40 -0
- package/adapters/tokenless/openclaw/package.json +24 -0
- package/adapters/tokenless/openclaw/scripts/detect.sh +102 -0
- package/adapters/tokenless/openclaw/scripts/install.sh +75 -0
- package/adapters/tokenless/openclaw/scripts/uninstall.sh +46 -0
- package/adapters/tokenless/openclaw/tsconfig.json +13 -0
- package/adapters/tokenless/opencode/plugin.js +246 -0
- package/adapters/tokenless/opencode/scripts/detect.sh +38 -0
- package/adapters/tokenless/opencode/scripts/install.sh +56 -0
- package/adapters/tokenless/opencode/scripts/uninstall.sh +29 -0
- package/adapters/tokenless/qoder/.qoder-plugin/plugin.json +11 -0
- package/adapters/tokenless/qoder/commands/tokenless-stats.md +8 -0
- package/adapters/tokenless/qoder/hooks/hooks.json +36 -0
- package/adapters/tokenless/qoder/hooks/run-hook.sh +51 -0
- package/adapters/tokenless/qoder/scripts/detect.sh +35 -0
- package/adapters/tokenless/qoder/scripts/install.sh +136 -0
- package/adapters/tokenless/qoder/scripts/uninstall.sh +106 -0
- package/adapters/tokenless/qwencode/qwen-extension.json +69 -0
- package/adapters/tokenless/qwencode/scripts/detect.sh +125 -0
- package/adapters/tokenless/qwencode/scripts/install.sh +128 -0
- package/adapters/tokenless/qwencode/scripts/uninstall.sh +62 -0
- package/bin/rtk +6 -0
- package/bin/tokenless +6 -0
- package/bin/toon +6 -0
- package/package.json +57 -0
- package/scripts/postinstall.js +208 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"version": "2.0",
|
|
4
|
+
"description": "Dormant legacy Tool Ready dependency spec. Release builds do not read it while the hard bypass is active.",
|
|
5
|
+
"related_files": {
|
|
6
|
+
"tool_categories.json": "Unified tool categorization for compression strategies. Tool names in 'aliases' must match those in tool_categories.json to ensure consistent behavior across tool-ready and compression."
|
|
7
|
+
},
|
|
8
|
+
"schema": {
|
|
9
|
+
"tool_entry": {
|
|
10
|
+
"aliases": "Cross-framework tool name mapping. Cosh/openclaw/hermes use different tool names; aliases collect all variants for reverse lookup.",
|
|
11
|
+
"required": "Hard dependencies. Any missing → NOT_READY, triggers auto-fix.",
|
|
12
|
+
"recommended": "Soft dependencies. Missing → PARTIAL. System tools (git, docker, cargo, uv) are shell-invoked, grouped under Shell.",
|
|
13
|
+
"config_files": "Required config file paths (~ expansion supported). Missing → PARTIAL.",
|
|
14
|
+
"permissions": "Required system permissions: file_read, file_write, exec_shell. Missing → NOT_READY. Install readiness only, not runtime state.",
|
|
15
|
+
"network": "Removed. Network connectivity is runtime state, not install readiness."
|
|
16
|
+
},
|
|
17
|
+
"dep_entry": {
|
|
18
|
+
"binary": "CLI tool name for command -v detection",
|
|
19
|
+
"version": "Version constraint, e.g. >=0.35",
|
|
20
|
+
"package": "Package name in the manager",
|
|
21
|
+
"apt_package": "Override package name when detected manager is apt (Debian/Ubuntu). Optional; defaults to package.",
|
|
22
|
+
"apk_package": "Override package name when detected manager is apk (Alpine). Optional; defaults to package.",
|
|
23
|
+
"manager": "Package manager type. Default rpm (auto-detects yum/dnf/apt/apk at runtime)",
|
|
24
|
+
"pip_name": "pip package name (default = package)",
|
|
25
|
+
"uv_name": "uv package name (default = package)",
|
|
26
|
+
"npm_name": "npm package name (default = package)",
|
|
27
|
+
"use_npx": "Whether to use npx for execution",
|
|
28
|
+
"url": "Download URL for curl_pipe_sh install",
|
|
29
|
+
"args": "Extra args for curl_pipe_sh install",
|
|
30
|
+
"fallback": "Fallback install strategies, tried sequentially on primary failure"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"supported_managers": {
|
|
34
|
+
"rpm": "System package manager (auto-detects yum/dnf/apt-get/apk by runtime environment)",
|
|
35
|
+
"apt": "apt-get — Debian/Ubuntu (only when explicitly specified)",
|
|
36
|
+
"pip": "pip / pip3 — Python packages",
|
|
37
|
+
"uv": "uv tool install / uv pip install — modern Python package manager",
|
|
38
|
+
"npm": "npm install -g — Node.js global packages",
|
|
39
|
+
"npx": "npx -y — Node.js ephemeral execution",
|
|
40
|
+
"cargo": "cargo install --locked — Rust crates.io",
|
|
41
|
+
"cargo_build": "cargo build --manifest-path — local source build",
|
|
42
|
+
"symlink": "ln -sf — pre-built binary symlink",
|
|
43
|
+
"path": "export PATH — directory injection",
|
|
44
|
+
"dir": "mkdir -p — directory creation",
|
|
45
|
+
"curl_pipe_sh": "curl/wget | sh — official install script (fallback only)"
|
|
46
|
+
},
|
|
47
|
+
"categories": "4 agent tool categories: Shell/WebFetch/Read/Write. System tools (git, docker, cargo, uv) invoked via shell → grouped under Shell.recommended."
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
"Shell": {
|
|
51
|
+
"_description": "Shell — base command execution environment for AI agents",
|
|
52
|
+
"_compression_layer": "layer_2_shell",
|
|
53
|
+
"aliases": ["Bash", "bash", "Shell", "shell", "run_shell_command", "run_in_terminal", "get_terminal_output", "terminal", "exec", "execute_command", "process"],
|
|
54
|
+
"required": [
|
|
55
|
+
{ "binary": "bash", "package": "bash", "manager": "rpm" },
|
|
56
|
+
{ "binary": "jq", "package": "jq", "manager": "rpm" }
|
|
57
|
+
],
|
|
58
|
+
"recommended": [
|
|
59
|
+
{ "binary": "git", "package": "git", "manager": "rpm" },
|
|
60
|
+
{ "binary": "ssh", "package": "openssh-clients", "apt_package": "openssh-client", "manager": "rpm" },
|
|
61
|
+
{ "binary": "docker", "package": "docker-ce", "apt_package": "docker.io", "manager": "rpm",
|
|
62
|
+
"fallback": [
|
|
63
|
+
{ "method": "rpm", "binary": "docker", "package": "docker" }
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{ "binary": "uv", "package": "uv", "manager": "pip", "pip_name": "uv",
|
|
67
|
+
"fallback": [
|
|
68
|
+
{ "method": "curl_pipe_sh", "binary": "uv", "url": "https://astral.sh/uv/install.sh" },
|
|
69
|
+
{ "method": "cargo", "binary": "uv", "package": "uv" }
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{ "binary": "python3", "package": "python3", "manager": "rpm" },
|
|
73
|
+
{ "binary": "cargo", "package": "cargo", "manager": "rpm" },
|
|
74
|
+
{ "binary": "rustc", "package": "rustc", "manager": "rpm" }
|
|
75
|
+
],
|
|
76
|
+
"permissions": ["exec_shell"],
|
|
77
|
+
"network": []
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
"WebFetch": {
|
|
81
|
+
"_description": "WebFetch — web request tool for AI agents",
|
|
82
|
+
"_compression_layer": "layer_3_api",
|
|
83
|
+
"aliases": ["WebFetch", "web_fetch", "fetch_content", "WebSearch", "web_search", "search_web", "web_extract", "x_search"],
|
|
84
|
+
"required": [
|
|
85
|
+
{ "binary": "curl", "package": "curl", "manager": "rpm" }
|
|
86
|
+
],
|
|
87
|
+
"recommended": [],
|
|
88
|
+
"permissions": [],
|
|
89
|
+
"network": []
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
"Read": {
|
|
93
|
+
"_description": "Read — file read and search tool for AI agents",
|
|
94
|
+
"_compression_layer": "layer_1_skip",
|
|
95
|
+
"aliases": ["Read", "read", "read_file", "read_many_files", "Glob", "glob", "search_file", "list_directory", "list_dir", "Grep", "grep", "grep_code", "grep_search", "search_files", "Lsp", "lsp", "NotebookRead", "notebook_read", "notebookread"],
|
|
96
|
+
"required": [],
|
|
97
|
+
"recommended": [],
|
|
98
|
+
"config_files": [],
|
|
99
|
+
"permissions": ["file_read"],
|
|
100
|
+
"network": []
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
"Write": {
|
|
104
|
+
"_description": "Write — file write and edit tool for AI agents",
|
|
105
|
+
"_compression_layer": "layer_3_api",
|
|
106
|
+
"aliases": ["Write", "write", "write_file", "create_file", "Edit", "edit", "edit_file", "search_replace", "delete_file", "apply_patch", "patch"],
|
|
107
|
+
"required": [],
|
|
108
|
+
"recommended": [],
|
|
109
|
+
"config_files": [],
|
|
110
|
+
"permissions": ["file_write"],
|
|
111
|
+
"network": []
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Tokenless native bundle layer for DeepSeek Harness.
|
|
2
|
+
#
|
|
3
|
+
# The dsh adapter installs this bundle through the framework's own plugin
|
|
4
|
+
# lifecycle. Resolve the installed package by its published name so dsh can
|
|
5
|
+
# load the bundle through its normal npm package lookup.
|
|
6
|
+
- insert:
|
|
7
|
+
- id: anolisa-tokenless
|
|
8
|
+
name: '@anolisa/dsh-tokenless'
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native Tokenless plugin for DeepSeek Harness (dsh).
|
|
3
|
+
*
|
|
4
|
+
* This entry intentionally has no dsh runtime imports. dsh supplies the
|
|
5
|
+
* Cordis event types at runtime, while the only process boundary is the
|
|
6
|
+
* installed Tokenless CLI. Keeping the entry dependency-free lets ANOLISA
|
|
7
|
+
* install one self-contained bundle without running npm in $DSH_HOME.
|
|
8
|
+
*/
|
|
9
|
+
import { execFile } from 'node:child_process'
|
|
10
|
+
import { randomUUID } from 'node:crypto'
|
|
11
|
+
|
|
12
|
+
const PLUGIN_NAME = 'anolisa-tokenless'
|
|
13
|
+
const DEFAULT_AGENT_ID = 'dsh'
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 3000
|
|
15
|
+
const DEFAULT_MAX_BUFFER = 2 * 1024 * 1024
|
|
16
|
+
|
|
17
|
+
// Keep content-retrieval tools lossless. These names mirror the shared
|
|
18
|
+
// Tokenless adapter taxonomy; callers may extend or replace the list through
|
|
19
|
+
// the dsh plugin config without changing this safety default.
|
|
20
|
+
const DEFAULT_SKIP_TOOLS = new Set([
|
|
21
|
+
'Read',
|
|
22
|
+
'read',
|
|
23
|
+
'read_file',
|
|
24
|
+
'read_many_files',
|
|
25
|
+
'Glob',
|
|
26
|
+
'glob',
|
|
27
|
+
'search_file',
|
|
28
|
+
'list_directory',
|
|
29
|
+
'list_dir',
|
|
30
|
+
'Grep',
|
|
31
|
+
'grep',
|
|
32
|
+
'grep_code',
|
|
33
|
+
'grep_search',
|
|
34
|
+
'search_files',
|
|
35
|
+
'Lsp',
|
|
36
|
+
'lsp',
|
|
37
|
+
'NotebookRead',
|
|
38
|
+
'notebook_read',
|
|
39
|
+
'notebookread',
|
|
40
|
+
])
|
|
41
|
+
|
|
42
|
+
// These values are the same thresholds used by the shared hook adapter. A
|
|
43
|
+
// dsh profile can override them in its plugin config; the Tokenless CLI still
|
|
44
|
+
// owns the actual compression algorithm and stats recording.
|
|
45
|
+
const DEFAULT_SHELL_TOOLS = new Set([
|
|
46
|
+
'Bash',
|
|
47
|
+
'bash',
|
|
48
|
+
'Shell',
|
|
49
|
+
'shell',
|
|
50
|
+
'exec',
|
|
51
|
+
'terminal',
|
|
52
|
+
'run_shell_command',
|
|
53
|
+
'run_in_terminal',
|
|
54
|
+
'get_terminal_output',
|
|
55
|
+
'execute_command',
|
|
56
|
+
'process',
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
const DEFAULT_THRESHOLDS = {
|
|
60
|
+
shell: { strings: 65536, arrays: 128, depth: 8 },
|
|
61
|
+
api: { strings: 1048576, arrays: 65536, depth: 32 },
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Mirror common/hooks/hook_utils.py ENV_PATTERNS exactly. The dsh bundle is
|
|
65
|
+
// independently publishable, so changes to the canonical table must update
|
|
66
|
+
// this dependency-free runtime copy and its tests together.
|
|
67
|
+
const ENV_PATTERNS = [
|
|
68
|
+
[
|
|
69
|
+
[
|
|
70
|
+
/command not found/i,
|
|
71
|
+
/not installed/i,
|
|
72
|
+
/which:\s+no/i,
|
|
73
|
+
/no command\s/i,
|
|
74
|
+
/cannot execute/i,
|
|
75
|
+
/is not recognized/i,
|
|
76
|
+
/could not find/i,
|
|
77
|
+
/unable to locate/i,
|
|
78
|
+
/package not found/i,
|
|
79
|
+
/\/bin\/sh:.*: not found/i,
|
|
80
|
+
/command not found:/i,
|
|
81
|
+
],
|
|
82
|
+
'ENV_DEPENDENCY_MISSING',
|
|
83
|
+
'Missing dependency detected. Install it or ask the user for guidance.',
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
[
|
|
87
|
+
/permission denied/i,
|
|
88
|
+
/operation not permitted/i,
|
|
89
|
+
/eacces/i,
|
|
90
|
+
/access denied/i,
|
|
91
|
+
/cannot open .* for writing/i,
|
|
92
|
+
],
|
|
93
|
+
'ENV_PERMISSION',
|
|
94
|
+
'Permission denied. Check file/directory permissions or run with appropriate access.',
|
|
95
|
+
],
|
|
96
|
+
[
|
|
97
|
+
[
|
|
98
|
+
/no such file or directory/i,
|
|
99
|
+
/enoent/i,
|
|
100
|
+
/cannot find/i,
|
|
101
|
+
/file not found/i,
|
|
102
|
+
/does not exist/i,
|
|
103
|
+
],
|
|
104
|
+
'ENV_FILE_MISSING',
|
|
105
|
+
'Required file or directory not found. Verify the path or create it.',
|
|
106
|
+
],
|
|
107
|
+
[
|
|
108
|
+
[
|
|
109
|
+
/connection refused/i,
|
|
110
|
+
/could not resolve host/i,
|
|
111
|
+
/network is unreachable/i,
|
|
112
|
+
/curl: \(7\)/i,
|
|
113
|
+
/curl: \(6\)/i,
|
|
114
|
+
/failed to connect/i,
|
|
115
|
+
/name or service not known/i,
|
|
116
|
+
/couldn't resolve host/i,
|
|
117
|
+
/temporary failure in name resolution/i,
|
|
118
|
+
/econnrefused/i,
|
|
119
|
+
/etimedout/i,
|
|
120
|
+
/connection timed out/i,
|
|
121
|
+
],
|
|
122
|
+
'ENV_NETWORK',
|
|
123
|
+
'Network connectivity issue. Check DNS, proxy, and firewall settings.',
|
|
124
|
+
],
|
|
125
|
+
[
|
|
126
|
+
[
|
|
127
|
+
/modulenotfounderror/i,
|
|
128
|
+
/importerror/i,
|
|
129
|
+
/no module named/i,
|
|
130
|
+
/cannot import name/i,
|
|
131
|
+
/npm err! 404/i,
|
|
132
|
+
],
|
|
133
|
+
'ENV_PACKAGE_MISSING',
|
|
134
|
+
'Required package or module is missing. Install the needed dependency.',
|
|
135
|
+
],
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
/** Return a plain config value or the supplied fallback. */
|
|
139
|
+
function valueOr(config, key, fallback) {
|
|
140
|
+
return config && Object.prototype.hasOwnProperty.call(config, key)
|
|
141
|
+
? config[key]
|
|
142
|
+
: fallback
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Normalize a config tool list without allowing malformed values to widen it. */
|
|
146
|
+
function toolSet(value, fallback) {
|
|
147
|
+
if (!Array.isArray(value)) return fallback
|
|
148
|
+
return new Set(value.filter((name) => typeof name === 'string' && name.length > 0))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Resolve the executable without ever installing or mutating a dsh profile. */
|
|
152
|
+
function tokenlessBinary(config) {
|
|
153
|
+
const configured = valueOr(config, 'tokenlessBin', undefined)
|
|
154
|
+
if (typeof configured === 'string' && configured.length > 0) return configured
|
|
155
|
+
return process.env.TOKENLESS_BIN || 'tokenless'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Extract text used only for error attribution; never stringify image blocks. */
|
|
159
|
+
function errorText(result) {
|
|
160
|
+
if (!result || typeof result !== 'object') return ''
|
|
161
|
+
const error = result.error
|
|
162
|
+
if (error && typeof error.message === 'string') return error.message
|
|
163
|
+
return result.content
|
|
164
|
+
?.filter((block) => block && block.type === 'text' && typeof block.text === 'string')
|
|
165
|
+
.map((block) => block.text)
|
|
166
|
+
.join('\n') || ''
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Return an environment category and remediation hint for known failure text. */
|
|
170
|
+
function classifyEnvironmentError(text) {
|
|
171
|
+
if (typeof text !== 'string') return undefined
|
|
172
|
+
if (!text) return undefined
|
|
173
|
+
for (const [patterns, category, hint] of ENV_PATTERNS) {
|
|
174
|
+
if (patterns.some((pattern) => pattern.test(text))) return { category, hint }
|
|
175
|
+
}
|
|
176
|
+
return undefined
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Extract attribution only when structured output explicitly reports failure. */
|
|
180
|
+
function classifyStructuredEnvironmentError(value) {
|
|
181
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
|
|
182
|
+
const exitCode = value.exit_code ?? value.exitCode
|
|
183
|
+
const stringExitCode = typeof exitCode === 'string' ? exitCode.trim() : ''
|
|
184
|
+
const nonzeroExit = (typeof exitCode === 'number' && exitCode !== 0)
|
|
185
|
+
|| (/^-?\d+$/.test(stringExitCode) && Number(stringExitCode) !== 0)
|
|
186
|
+
const timedOut = value.timed_out === true || value.timedOut === true
|
|
187
|
+
const failed = nonzeroExit
|
|
188
|
+
|| timedOut
|
|
189
|
+
|| value.isError === true
|
|
190
|
+
|| value.success === false
|
|
191
|
+
|| value.ok === false
|
|
192
|
+
if (!failed) return undefined
|
|
193
|
+
const error = value.error
|
|
194
|
+
let errorValue = error
|
|
195
|
+
if (error && typeof error === 'object') {
|
|
196
|
+
if (typeof error.message === 'string') {
|
|
197
|
+
errorValue = error.message
|
|
198
|
+
} else {
|
|
199
|
+
try {
|
|
200
|
+
errorValue = JSON.stringify(error)
|
|
201
|
+
} catch {
|
|
202
|
+
errorValue = String(error)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const streamText = (stream) => {
|
|
207
|
+
if (typeof stream === 'string') return stream
|
|
208
|
+
if (stream && typeof stream === 'object' && typeof stream.text === 'string') return stream.text
|
|
209
|
+
return undefined
|
|
210
|
+
}
|
|
211
|
+
const text = [streamText(value.stderr), errorValue]
|
|
212
|
+
.filter((part) => typeof part === 'string')
|
|
213
|
+
.join('\n')
|
|
214
|
+
return classifyEnvironmentError(text)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Construct a valid plugin-owned user message without importing dsh modules. */
|
|
218
|
+
function attributionContext(text) {
|
|
219
|
+
return {
|
|
220
|
+
id: randomUUID(),
|
|
221
|
+
role: 'user',
|
|
222
|
+
content: [{ type: 'text', text }],
|
|
223
|
+
source: {
|
|
224
|
+
kind: 'plugin',
|
|
225
|
+
plugin: PLUGIN_NAME,
|
|
226
|
+
form: 'notice',
|
|
227
|
+
summary: text.slice(0, 120),
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Add an attribution context to a decision while preserving its shape. */
|
|
233
|
+
function withAttribution(decision, attribution) {
|
|
234
|
+
if (!attribution) return decision
|
|
235
|
+
return {
|
|
236
|
+
...decision,
|
|
237
|
+
additionalContexts: [
|
|
238
|
+
...(Array.isArray(decision.additionalContexts) ? decision.additionalContexts : []),
|
|
239
|
+
attributionContext(attribution),
|
|
240
|
+
],
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Safely read one text-only result projection. */
|
|
245
|
+
function singleTextContent(result) {
|
|
246
|
+
if (!result || result.isError || !Array.isArray(result.content)) return undefined
|
|
247
|
+
if (result.content.length !== 1) return undefined
|
|
248
|
+
const [block] = result.content
|
|
249
|
+
if (!block || block.type !== 'text' || typeof block.text !== 'string') return undefined
|
|
250
|
+
return block.text
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Convert one config threshold to a positive finite integer. */
|
|
254
|
+
function positiveInteger(value, fallback) {
|
|
255
|
+
return Number.isInteger(value) && value > 0 ? value : fallback
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Build the Tokenless CLI argv for one dsh execution. */
|
|
259
|
+
function compressionArgs(exec, config, shellTools) {
|
|
260
|
+
const selected = shellTools.has(exec.name) ? DEFAULT_THRESHOLDS.shell : DEFAULT_THRESHOLDS.api
|
|
261
|
+
const strings = positiveInteger(valueOr(config, 'truncateStringsAt', undefined), selected.strings)
|
|
262
|
+
const arrays = positiveInteger(valueOr(config, 'truncateArraysAt', undefined), selected.arrays)
|
|
263
|
+
const depth = positiveInteger(valueOr(config, 'maxDepth', undefined), selected.depth)
|
|
264
|
+
const args = [
|
|
265
|
+
'compress-response',
|
|
266
|
+
'--agent-id',
|
|
267
|
+
String(valueOr(config, 'agentId', DEFAULT_AGENT_ID)),
|
|
268
|
+
'--truncate-strings-at',
|
|
269
|
+
String(strings),
|
|
270
|
+
'--truncate-arrays-at',
|
|
271
|
+
String(arrays),
|
|
272
|
+
'--max-depth',
|
|
273
|
+
String(depth),
|
|
274
|
+
]
|
|
275
|
+
const sessionId = exec.agent?.id
|
|
276
|
+
if (typeof sessionId === 'string' && sessionId.length > 0) {
|
|
277
|
+
args.push('--session-id', sessionId)
|
|
278
|
+
}
|
|
279
|
+
if (typeof exec.callId === 'string' && exec.callId.length > 0) {
|
|
280
|
+
args.push('--tool-use-id', exec.callId)
|
|
281
|
+
}
|
|
282
|
+
if (valueOr(config, 'noStash', false) === true) args.push('--no-stash')
|
|
283
|
+
return args
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Execute a child process with bounded output and explicit stdin. */
|
|
287
|
+
function runTokenless(binary, args, options, input) {
|
|
288
|
+
return new Promise((resolve, reject) => {
|
|
289
|
+
let child
|
|
290
|
+
try {
|
|
291
|
+
child = execFile(binary, args, options, (error, stdout, stderr) => {
|
|
292
|
+
if (error) {
|
|
293
|
+
error.stderr = stderr
|
|
294
|
+
reject(error)
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
resolve({ stdout, stderr })
|
|
298
|
+
})
|
|
299
|
+
} catch (error) {
|
|
300
|
+
reject(error)
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
child.stdin?.on('error', () => {})
|
|
304
|
+
try {
|
|
305
|
+
child.stdin?.end(input)
|
|
306
|
+
} catch (error) {
|
|
307
|
+
reject(error)
|
|
308
|
+
}
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Run Tokenless and return a strictly smaller JSON candidate, or undefined. */
|
|
313
|
+
async function compressText(text, exec, config, shellTools) {
|
|
314
|
+
if (exec.signal?.aborted) return undefined
|
|
315
|
+
let parsed
|
|
316
|
+
try {
|
|
317
|
+
parsed = JSON.parse(text)
|
|
318
|
+
} catch {
|
|
319
|
+
return undefined
|
|
320
|
+
}
|
|
321
|
+
if (parsed === null || (typeof parsed !== 'object' && !Array.isArray(parsed))) return undefined
|
|
322
|
+
const binary = tokenlessBinary(config)
|
|
323
|
+
try {
|
|
324
|
+
const { stdout } = await runTokenless(binary, compressionArgs(exec, config, shellTools), {
|
|
325
|
+
timeout: positiveInteger(valueOr(config, 'timeoutMs', undefined), DEFAULT_TIMEOUT_MS),
|
|
326
|
+
maxBuffer: positiveInteger(valueOr(config, 'maxBuffer', undefined), DEFAULT_MAX_BUFFER),
|
|
327
|
+
encoding: 'utf8',
|
|
328
|
+
windowsHide: true,
|
|
329
|
+
signal: exec.signal,
|
|
330
|
+
}, text)
|
|
331
|
+
const candidate = typeof stdout === 'string' ? stdout.trim() : ''
|
|
332
|
+
// The host's original content is authoritative unless compression proves
|
|
333
|
+
// a real reduction. This avoids duplicate payloads and preserves fail-open
|
|
334
|
+
// behavior when the CLI is unavailable, malformed, or no-op.
|
|
335
|
+
if (!candidate || candidate.length >= text.length) return undefined
|
|
336
|
+
JSON.parse(candidate)
|
|
337
|
+
return candidate
|
|
338
|
+
} catch {
|
|
339
|
+
return undefined
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Register native response compression on dsh's typed post-execute seam. */
|
|
344
|
+
export function apply(ctx, config = {}) {
|
|
345
|
+
const skipTools = toolSet(valueOr(config, 'skipTools', undefined), DEFAULT_SKIP_TOOLS)
|
|
346
|
+
const shellTools = toolSet(valueOr(config, 'shellTools', undefined), DEFAULT_SHELL_TOOLS)
|
|
347
|
+
const enabled = valueOr(config, 'responseCompressionEnabled', true) !== false
|
|
348
|
+
ctx.on('tools/post-execute', async (exec, result, next) => {
|
|
349
|
+
const envError = result?.isError === true
|
|
350
|
+
? classifyEnvironmentError(errorText(result))
|
|
351
|
+
: undefined
|
|
352
|
+
const structuredError = result?.isError === false && shellTools.has(exec.name)
|
|
353
|
+
? classifyStructuredEnvironmentError(result.value)
|
|
354
|
+
: undefined
|
|
355
|
+
// Attribution is independent of compression so disabled, skipped, and
|
|
356
|
+
// parented failures still tell the agent why blind retries are unsafe.
|
|
357
|
+
const originalAttribution = structuredError || envError
|
|
358
|
+
|
|
359
|
+
// DSH treats this seam as a waterfall. Let downstream policies settle
|
|
360
|
+
// their decision before replacing only its accepted display content.
|
|
361
|
+
const decision = await next()
|
|
362
|
+
const replacesValue = Object.prototype.hasOwnProperty.call(decision, 'value')
|
|
363
|
+
// A downstream canonical value replaces the original result entirely, so
|
|
364
|
+
// any attribution must describe that value rather than the stale result.
|
|
365
|
+
const attribution = replacesValue
|
|
366
|
+
? (shellTools.has(exec.name)
|
|
367
|
+
? classifyStructuredEnvironmentError(decision.value)
|
|
368
|
+
: undefined)
|
|
369
|
+
: originalAttribution
|
|
370
|
+
const responseContext = attribution
|
|
371
|
+
? `[tokenless:env] ${exec.name} failed: ${attribution.category} (${attribution.hint}). Skip retry.`
|
|
372
|
+
: undefined
|
|
373
|
+
const canCompress = enabled
|
|
374
|
+
&& !skipTools.has(exec.name)
|
|
375
|
+
&& exec.parent === undefined
|
|
376
|
+
&& decision.kind === 'accept'
|
|
377
|
+
&& !replacesValue
|
|
378
|
+
if (!canCompress) return withAttribution(decision, responseContext)
|
|
379
|
+
|
|
380
|
+
// Only a single text block is safe to replace. Images, tool-call blocks,
|
|
381
|
+
// nested tool results, and mixed content remain untouched by design.
|
|
382
|
+
const contentResult = decision.content === undefined
|
|
383
|
+
? result
|
|
384
|
+
: { ...result, content: decision.content }
|
|
385
|
+
const original = singleTextContent(contentResult)
|
|
386
|
+
if (original === undefined) return withAttribution(decision, responseContext)
|
|
387
|
+
|
|
388
|
+
const candidate = await compressText(original, exec, config, shellTools)
|
|
389
|
+
if (!candidate) return withAttribution(decision, responseContext)
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
...withAttribution(decision, responseContext),
|
|
393
|
+
content: [{ type: 'text', text: candidate }],
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export const name = PLUGIN_NAME
|
|
399
|
+
export const inject = ['tools']
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@anolisa/dsh-tokenless",
|
|
3
|
+
"version": "0.7.7",
|
|
4
|
+
"description": "Native DeepSeek Harness response compression for Tokenless",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js",
|
|
9
|
+
"./package.json": "./package.json"
|
|
10
|
+
},
|
|
11
|
+
"files": ["dist/", "cordis.patch.yml", "package.json"],
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"dsh": {
|
|
14
|
+
"bundle": {
|
|
15
|
+
"patch": "./cordis.patch.yml"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
20
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.2 <0.2.0",
|
|
21
|
+
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.2 <0.2.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=22.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|