rlmh-dsh 0.0.0-stage → 1.1.0
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 +21 -0
- package/README.md +112 -2
- package/cordis.patch.yml +119 -0
- package/lib/blobs.js +45 -0
- package/lib/client.js +115 -0
- package/lib/index.js +5 -0
- package/lib/rlm-runtime.js +429 -0
- package/lib/rlm-tool.js +100 -0
- package/lib/settings.js +52 -0
- package/lib/subcall-tool.js +76 -0
- package/lib/subcalls.js +102 -0
- package/lib/tool-names.js +4 -0
- package/package.json +78 -4
- package/python/rlm_repl.py +443 -0
- package/python/rlmapi.py +484 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rishi Jain
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,113 @@
|
|
|
1
|
-
#
|
|
1
|
+
# rlmh-dsh: RLM mode for DeepSeek Harness
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`rlmh-dsh` adds **RLM mode** to [DeepSeek Harness](https://www.npmjs.com/package/@deepseek-ai/dsh) (dsh). RLM mode is the Recursive Language Model method from [rlm-harness](https://github.com/RishiJain905/rlm-harness), running inside dsh.
|
|
4
|
+
|
|
5
|
+
The model doesn't read long inputs. It works through a persistent Python REPL, the `run_code` tool:
|
|
6
|
+
- loading files into variables;
|
|
7
|
+
- measuring and slicing them with code;
|
|
8
|
+
- handing the reading to **sub-calls** (`llm`, `llm_batch`, `scan`, `ask_files`), which are separate model calls that each read one piece and answer briefly;
|
|
9
|
+
- doing exact counting, filtering and arithmetic in Python.
|
|
10
|
+
|
|
11
|
+
The model's own context stays small and its prompt cache stays warm, even on inputs far larger than its context window.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
**dsh desktop:**
|
|
16
|
+
1. Open **Plugins** → **Add plugin** → **Install a third-party plugin**.
|
|
17
|
+
2. Enter `rlmh-dsh`, then click **Install** and **Enable now**.
|
|
18
|
+
|
|
19
|
+
There is no restart and no terminal. RLM mode needs Python, and the desktop app ships one.
|
|
20
|
+
|
|
21
|
+
**dsh CLI or web:**
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
dsh plugin --profile web add rlmh-dsh
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use your own profile's name in place of `web`.
|
|
28
|
+
|
|
29
|
+
Needs dsh 0.2.2-rc.1 or later, below 0.3.0.
|
|
30
|
+
|
|
31
|
+
## Use it
|
|
32
|
+
|
|
33
|
+
Start a session with the **RLM mode** agent preset. The model gets `run_code` and the REPL, plus these helpers:
|
|
34
|
+
|
|
35
|
+
| In the REPL | What it does |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `llm(prompt)` | One sub-call; returns the answer as text |
|
|
38
|
+
| `llm_batch(prompts, shared=…, over=…)` | Several sub-calls in parallel. `shared=` and `over=` put a fixed prefix first, so sub-calls hit the provider's cache |
|
|
39
|
+
| `segments(text, size)` | Cuts text at line breaks into pieces sized for one sub-call |
|
|
40
|
+
| `scan(text, question)` | Asks a question of every segment of a text |
|
|
41
|
+
| `ask_files(paths, question)` | Asks a question of files, without loading them into the model's prompt |
|
|
42
|
+
| `rlm(task, context=None)` | A child agent in RLM mode, with its own REPL, run as a dsh subagent |
|
|
43
|
+
|
|
44
|
+
Each sub-call shows up as an `llm` row in the Trajectory view, with a `[sub-call tokens: …]` line.
|
|
45
|
+
|
|
46
|
+
## Settings
|
|
47
|
+
|
|
48
|
+
Go to **Plugins** → **Built-in plugin settings** → **RLM mode**.
|
|
49
|
+
|
|
50
|
+
| Setting | Default |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Sub-call provider and model | The session's own model |
|
|
53
|
+
| Sub-call reasoning effort | The session's own effort |
|
|
54
|
+
| Characters one `run_code` result shows | 8,000 |
|
|
55
|
+
| Sub-calls per `run_code` call | 200 |
|
|
56
|
+
| Parallel sub-calls | 8 |
|
|
57
|
+
|
|
58
|
+
Sub-calls cost tokens, mostly output tokens. A cheaper sub-call model or a lower effort cuts that cost.
|
|
59
|
+
|
|
60
|
+
## Python
|
|
61
|
+
|
|
62
|
+
RLM mode looks for Python 3.10 or later, in this order:
|
|
63
|
+
1. the runtime's `pythonBin` option, if you set one in the profile;
|
|
64
|
+
2. the Python bundled with dsh desktop;
|
|
65
|
+
3. an installed rlm-harness's Python;
|
|
66
|
+
4. a Python 3.12 that uv already has;
|
|
67
|
+
5. `python` or `python3` on PATH.
|
|
68
|
+
|
|
69
|
+
If none is found, the agent says so. Install the desktop app, or install Python 3.12 from python.org or with `uv python install 3.12`, then start a new session. RLM mode never downloads anything itself.
|
|
70
|
+
|
|
71
|
+
## Caching
|
|
72
|
+
|
|
73
|
+
RLM mode follows dsh's caching rules:
|
|
74
|
+
- its instructions and tool list are fixed for the session;
|
|
75
|
+
- it never rewrites requests;
|
|
76
|
+
- compaction is dsh's own;
|
|
77
|
+
- sub-calls put their fixed prefix first and use their own session affinity key, `<session>:sub`.
|
|
78
|
+
|
|
79
|
+
In tests on DeepSeek Flash through OpenRouter, long RLM sessions read 94% to 98% of their input from cache, about the same as Standard mode. Two settings outside the plugin matter:
|
|
80
|
+
|
|
81
|
+
- **OpenRouter host switching.** OpenRouter serves many open models from several hosts and may move a request to a host without your cache. Each move is one full miss. Two ways to keep requests on one host:
|
|
82
|
+
- use dsh's own DeepSeek provider with a DeepSeek key;
|
|
83
|
+
- on OpenRouter, create a preset with a provider **order** and use a model id ending in `@preset/<slug>`.
|
|
84
|
+
- **A huge output cap starts compaction early.** dsh compacts at about `context window − output cap − 65,536` tokens. Some imported OpenRouter models declare an output cap close to their context window, for example DeepSeek Flash Latest's 943,718. On those, every session compacts at about 39k tokens, and each compaction misses the cache. Set a smaller output cap, such as 65,536, on that model in dsh's model settings.
|
|
85
|
+
|
|
86
|
+
## Permissions
|
|
87
|
+
|
|
88
|
+
The REPL runs under dsh's file policy, through dsh's sandbox service, in the same way as dsh's own code runtime:
|
|
89
|
+
- **read-only** and **plan** mode: the REPL and everything it starts can't write;
|
|
90
|
+
- **workspace-write**: writes outside the workspace are denied;
|
|
91
|
+
- if the sandbox is unavailable, a confined mode fails closed;
|
|
92
|
+
- dsh's one-time escalation prompt still works.
|
|
93
|
+
|
|
94
|
+
How strongly writes are blocked depends on dsh's backend for your OS. On Windows, dsh reports enforcement as `partial`. Treat it as protection against accidents, not against a hostile program.
|
|
95
|
+
|
|
96
|
+
## Privacy
|
|
97
|
+
|
|
98
|
+
- **The plugin holds no keys.** Sub-calls go through dsh's model service, so dsh holds the key.
|
|
99
|
+
- **No key-like variables reach the REPL.** Environment variables whose names look like keys (`KEY`, `TOKEN`, `SECRET`, `PASSWORD`, `CREDENTIAL`, `AUTH`, `HEADERS`) are removed before it starts.
|
|
100
|
+
- **Nothing contacts the network except your model provider.** It is contacted only through dsh.
|
|
101
|
+
|
|
102
|
+
## Development
|
|
103
|
+
|
|
104
|
+
The source is on the `dsh-plugin` branch of [rlm-harness](https://github.com/RishiJain905/rlm-harness/tree/dsh-plugin/dsh-plugin):
|
|
105
|
+
- Node tests: `npm test`.
|
|
106
|
+
- Python tests: `python -m unittest discover -s test -p "test_*.py"`.
|
|
107
|
+
- Cache guard, which runs a real dsh against a fake model server: `node test/e2e/cache-guard.mjs`.
|
|
108
|
+
|
|
109
|
+
The design and its test results are in `documentation/v1.1/dsh-plugin-plan.md`.
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# KV cache: every text here (the persona, the preset) is fixed, so dsh's cached prompt prefix stays byte-identical.
|
|
2
|
+
# rlmh for DeepSeek Harness (D-264): the user's settings (`rlmh-settings`) and the "RLM mode" agent preset. The
|
|
3
|
+
# top-level `rlm-runtime`, `rlm-subcall-tool` and `rlm-rlm-tool` rows are off by default and exist for headless tests (an overlay
|
|
4
|
+
# turns them on for the whole deployment); the preset mounts its own copies.
|
|
5
|
+
- insert:
|
|
6
|
+
# The package root is the settings service (lib/index.js). It must be the only active top-level entry from this
|
|
7
|
+
# package, because the package has a browser half (the settings page); the rows below are off by default.
|
|
8
|
+
- id: rlmh-settings
|
|
9
|
+
name: 'rlmh-dsh'
|
|
10
|
+
- id: rlm-runtime
|
|
11
|
+
name: './lib/rlm-runtime.js'
|
|
12
|
+
disabled: true
|
|
13
|
+
- id: rlm-subcall-tool
|
|
14
|
+
name: './lib/subcall-tool.js'
|
|
15
|
+
disabled: true
|
|
16
|
+
- id: rlm-rlm-tool
|
|
17
|
+
name: './lib/rlm-tool.js'
|
|
18
|
+
disabled: true
|
|
19
|
+
- id: preset-rlm
|
|
20
|
+
name: '@deepseek-ai/dsh-agent-preset'
|
|
21
|
+
config:
|
|
22
|
+
id: rlm
|
|
23
|
+
name: RLM mode
|
|
24
|
+
description: Recursive Language Model mode. The agent works in a persistent Python REPL and hands reading to sub-calls (llm, llm_batch, scan, ask_files).
|
|
25
|
+
order: 5
|
|
26
|
+
plugins:
|
|
27
|
+
- id: persona
|
|
28
|
+
name: '@deepseek-ai/dsh-persona'
|
|
29
|
+
config:
|
|
30
|
+
# rlmh's `default` prompt (engine/rlm_engine/profiles/default/system.md and guidance.md), adapted to
|
|
31
|
+
# run_code. The function list itself is in run_code's description (lib/rlm-runtime.js INSTRUCTIONS).
|
|
32
|
+
prefix: |-
|
|
33
|
+
You are a coding agent powered by the {{model}} model, working as a Recursive Language Model.
|
|
34
|
+
|
|
35
|
+
Your only tool is run_code: a persistent Python REPL. Variables, functions and imports persist between calls, and you see what a call prints, cut at a few thousand characters. The harness's tools are functions inside the REPL (`await tools.read({...})`, `await tools.grep({...})`, `await tools.edit({...})` and the rest), and so are the RLM functions: llm, llm_batch, segments, scan, ask_files and rlm.
|
|
36
|
+
|
|
37
|
+
How to work: your own view is small, and sub-calls are where the reading happens. A sub-call is another model call that reads a large piece of text (up to about 60,000 characters) and answers in a few thousand tokens. You are the orchestrator: look at the input's structure first with code (sizes, headings, line counts), decide how to split the work (which pieces, which question for each, how you will combine the answers), send independent pieces out together, ask each for a short answer you can parse, combine the answers with code, and check anything surprising with another sub-call. Use code for exact filtering, counting and arithmetic, and sub-calls for reading and judgment. Your sub-call budget is there to be spent.
|
|
38
|
+
|
|
39
|
+
Reading a large text (a long file, a log, a document):
|
|
40
|
+
- Load it into a variable with Python and never print it whole.
|
|
41
|
+
- Searching it with re or str.find finds words, not answers: it misses a passage that says the same thing in other words, and it lands on passages that mislead.
|
|
42
|
+
- So start with a scan: `found = scan(text, "<the question, with what it depends on>")`. Print the passages, then read around the offsets that matter (`text[at - 500:at + 500]`).
|
|
43
|
+
- When the answer takes several steps (one fact leads to the next), scan for the first step, then scan for each next step with what a passage named. Put nothing you have not read into a scan's question.
|
|
44
|
+
- For lists, counts and totals, ask with schema= (`scan(text, q, schema=list[str])`, or `llm_batch(prompts, over=segments(text), schema=...)`) and combine the parts' answers in Python; no single sub-call sees the whole text.
|
|
45
|
+
- Use thinking="off" for lookups and extraction; keep thinking for judgment.
|
|
46
|
+
|
|
47
|
+
Coding tasks:
|
|
48
|
+
- Delegate reading and checking to sub-calls; keep your own turns for deciding and changing. When a question covers more than three files, or a file over 300 lines, use ask_files instead of reading it yourself. For a job of several steps of its own (investigate one part, draft one module), start a child run with rlm(task, context=None), with a clear task and what its answer should contain.
|
|
49
|
+
- Change files with the harness's edit and write tools (`await tools.edit({...})`), so the user's approvals apply, and run commands and tests with its shell tool.
|
|
50
|
+
|
|
51
|
+
Rules:
|
|
52
|
+
- Explore with code and print summaries, never whole files or pages.
|
|
53
|
+
- Answer the user in a plain reply once what you have read supports each part of it; where passages disagree, read both in place first.
|
|
54
|
+
- id: agent-instructions
|
|
55
|
+
name: '@deepseek-ai/dsh-agent-instructions'
|
|
56
|
+
config:
|
|
57
|
+
maxBytes: 65536
|
|
58
|
+
- id: time-context
|
|
59
|
+
name: '@deepseek-ai/dsh-time-context'
|
|
60
|
+
- id: tool-bash
|
|
61
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
62
|
+
disabled: !!js process.platform === 'win32'
|
|
63
|
+
- id: tool-pwsh
|
|
64
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
65
|
+
disabled: !!js process.platform !== 'win32'
|
|
66
|
+
- id: tool-fs
|
|
67
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
68
|
+
config:
|
|
69
|
+
applyPatch: false
|
|
70
|
+
- id: tool-fs-search
|
|
71
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
72
|
+
config:
|
|
73
|
+
sampleOverCapGlobResults: false
|
|
74
|
+
- id: compaction
|
|
75
|
+
name: cordis:group
|
|
76
|
+
group: true
|
|
77
|
+
isolate:
|
|
78
|
+
compaction: true
|
|
79
|
+
toolResultPruner: true
|
|
80
|
+
config:
|
|
81
|
+
- id: compaction-basic
|
|
82
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
83
|
+
- id: command-compact
|
|
84
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
85
|
+
- id: tool-result-pruner
|
|
86
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
87
|
+
config:
|
|
88
|
+
# KV cache: pruning rewrites an earlier result, which misses the cache from there on. run_code
|
|
89
|
+
# results are capped at 8,000 characters (outputMaxChars) plus a note, so 12,000 never prunes them.
|
|
90
|
+
thresholdChars: 12000
|
|
91
|
+
headChars: 4096
|
|
92
|
+
tailChars: 1024
|
|
93
|
+
- id: tool-ask-user
|
|
94
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
95
|
+
- id: tool-todo
|
|
96
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
97
|
+
config:
|
|
98
|
+
allowParallelInProgress: true
|
|
99
|
+
- id: tool-web
|
|
100
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
101
|
+
config:
|
|
102
|
+
fetch: true
|
|
103
|
+
searchTimeoutMs: 60000
|
|
104
|
+
- id: rlm-execution
|
|
105
|
+
name: cordis:group
|
|
106
|
+
group: true
|
|
107
|
+
isolate:
|
|
108
|
+
ptcRuntime: true
|
|
109
|
+
config:
|
|
110
|
+
- id: rlm-runtime
|
|
111
|
+
name: 'rlmh-dsh/lib/rlm-runtime.js'
|
|
112
|
+
- id: rlm-subcall-tool
|
|
113
|
+
name: 'rlmh-dsh/lib/subcall-tool.js'
|
|
114
|
+
- id: rlm-rlm-tool
|
|
115
|
+
name: 'rlmh-dsh/lib/rlm-tool.js'
|
|
116
|
+
- id: tool-executor-ptc
|
|
117
|
+
name: '@deepseek-ai/dsh-tool-executor-ptc'
|
|
118
|
+
- id: present
|
|
119
|
+
name: '@deepseek-ai/dsh-tool-present'
|
package/lib/blobs.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// KV cache: none (this holds sub-call prefixes by content; the bytes sent are the stored text, unchanged)
|
|
2
|
+
// Shared sub-call prefixes (a segment for over=, a batch's shared= text), stored once per process by the SHA-256 of
|
|
3
|
+
// their text. The REPL sends each distinct prefix once; the `llm` tool's arguments carry its id, so dsh's session log
|
|
4
|
+
// records a short reference instead of up to 60,000 characters per sub-call. Ids are checked against the text, so one
|
|
5
|
+
// session's code can't plant different text under an id another session uses. Least recently used goes first past
|
|
6
|
+
// the size cap; a miss tells the REPL to send the prefix again.
|
|
7
|
+
import { createHash } from 'node:crypto'
|
|
8
|
+
|
|
9
|
+
export const MAX_CHARS = 256 * 1024 * 1024
|
|
10
|
+
const store = new Map() // id -> text, oldest first
|
|
11
|
+
let held = 0
|
|
12
|
+
|
|
13
|
+
export function blobId(text) {
|
|
14
|
+
return createHash('sha256').update(text, 'utf8').digest('hex')
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Store `text` under `id`; false (and nothing stored) when the id is not the text's SHA-256. */
|
|
18
|
+
export function putBlob(id, text) {
|
|
19
|
+
if (typeof id !== 'string' || typeof text !== 'string' || blobId(text) !== id) return false
|
|
20
|
+
if (store.has(id)) {
|
|
21
|
+
store.delete(id)
|
|
22
|
+
store.set(id, text)
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
25
|
+
store.set(id, text)
|
|
26
|
+
held += text.length
|
|
27
|
+
for (const [old, oldText] of store) {
|
|
28
|
+
if (held <= MAX_CHARS || old === id) break
|
|
29
|
+
store.delete(old)
|
|
30
|
+
held -= oldText.length
|
|
31
|
+
}
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The text stored under `id`, refreshed as recently used; undefined when unknown. */
|
|
36
|
+
export function getBlob(id) {
|
|
37
|
+
const text = store.get(id)
|
|
38
|
+
if (text !== undefined) {
|
|
39
|
+
store.delete(id)
|
|
40
|
+
store.set(id, text)
|
|
41
|
+
}
|
|
42
|
+
return text
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const MISSING_PREFIX = 'unknown prefix_id'
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// KV cache: none (a settings page in the browser; it changes settings, never prompt or tool text)
|
|
2
|
+
// RLM mode's settings page, the browser half of rlmh-dsh (step 4c). It edits the volatile fields of the
|
|
3
|
+
// `rlmh-settings` profile entry (lib/settings.js) through dsh's shared settings form: values are staged until Save,
|
|
4
|
+
// an overridden field carries a badge and a reset, and an empty field saves as a reset. The page sits in the Plugins
|
|
5
|
+
// page (`plugins.item` slot) while the Host serves that entry's form. dsh supplies React and the UI primitives, so
|
|
6
|
+
// this file is loaded as-is (no build step), in the module format dsh's client loader expects.
|
|
7
|
+
window.__ModuleLoader__.load({
|
|
8
|
+
id: 'rlmh-dsh',
|
|
9
|
+
factory: (require) => {
|
|
10
|
+
const { jsx, jsxs } = require('react/jsx-runtime')
|
|
11
|
+
const P = require('@deepseek-ai/dsh-client-ui-primitives')
|
|
12
|
+
|
|
13
|
+
/** The profile entry whose volatile fields this page edits (cordis.patch.yml `rlmh-settings`). */
|
|
14
|
+
const ENTRY = 'rlmh-settings'
|
|
15
|
+
const NS = 'settings.rlmh'
|
|
16
|
+
|
|
17
|
+
const en = {
|
|
18
|
+
title: 'RLM mode',
|
|
19
|
+
description: 'Choose the model and effort for RLM mode\'s sub-calls, and how much one run_code result shows.',
|
|
20
|
+
subProvider: 'Sub-call provider',
|
|
21
|
+
subProviderHint: 'The provider route for sub-calls, as in the model picker (for example openrouter). Leave blank to use the agent\'s own.',
|
|
22
|
+
subModel: 'Sub-call model',
|
|
23
|
+
subModelHint: 'The model id for sub-calls (for example ~deepseek/deepseek-flash-latest). Leave blank to use the agent\'s own model.',
|
|
24
|
+
subReasoningEffort: 'Sub-call reasoning effort',
|
|
25
|
+
subReasoningEffortHint: 'An effort the sub-call model offers, such as low, medium or high. Leave blank to use the agent\'s effort. Lower effort costs less output.',
|
|
26
|
+
outputMaxChars: 'Result size (characters)',
|
|
27
|
+
outputMaxCharsHint: 'How much of one run_code result the agent sees; the rest is cut so it reads through sub-calls. Default 8000.',
|
|
28
|
+
maxSubcallsPerRun: 'Sub-calls per run_code call',
|
|
29
|
+
maxSubcallsPerRunHint: 'The most sub-calls one run_code call may make. Default 200.',
|
|
30
|
+
maxParallelSubcalls: 'Sub-calls at once (fallback)',
|
|
31
|
+
maxParallelSubcallsHint: 'Only used when the session has no sub-call tool. Default 8.',
|
|
32
|
+
overridden: 'Overridden',
|
|
33
|
+
reset: 'Reset to default',
|
|
34
|
+
readOnly: 'This deployment stores settings read-only.',
|
|
35
|
+
unavailable: 'RLM mode\'s settings plugin is not loaded, so it cannot be configured right now.',
|
|
36
|
+
save: 'Save',
|
|
37
|
+
saving: 'Saving…',
|
|
38
|
+
saveFailed: 'The deployment did not accept these values; they were left for you to correct.',
|
|
39
|
+
invalidNumber: 'Enter a number, or leave blank to use the default.',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const TEXT = ['subProvider', 'subModel', 'subReasoningEffort']
|
|
43
|
+
const NUMBERS = ['outputMaxChars', 'maxSubcallsPerRun', 'maxParallelSubcalls']
|
|
44
|
+
|
|
45
|
+
/** Bridges the `rlmh-settings` entry's form onto the page's staged form. */
|
|
46
|
+
class RlmhCardController {
|
|
47
|
+
constructor(scope) {
|
|
48
|
+
this.form = new P.SettingsFormModel(scope, [...TEXT.map((f) => P.settingsTextField(f)), ...NUMBERS.map((f) => P.settingsNumberField(f))])
|
|
49
|
+
this.store = this.form.bind(() => this.projection())
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
projection() {
|
|
53
|
+
const out = { ...this.form.shell() }
|
|
54
|
+
for (const f of [...TEXT, ...NUMBERS]) out[f] = this.form.field(f)
|
|
55
|
+
return out
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
inject() {
|
|
59
|
+
return { hooks: { rlmhCard: this.store }, ...this.form.actions() }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
dispose() {
|
|
63
|
+
this.form.dispose()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The one-liner on the Plugins page, or the settings form when the page is open. */
|
|
68
|
+
function RlmhCard(props) {
|
|
69
|
+
const { t } = props
|
|
70
|
+
const state = props.useRlmhCard((snapshot) => snapshot)
|
|
71
|
+
if (props.view === 'summary') return t('description')
|
|
72
|
+
const disabled = !state.writable
|
|
73
|
+
const field = (name, numeric) => jsx(P.SettingsValueField, {
|
|
74
|
+
id: `plugin-config-rlmh-${name}`,
|
|
75
|
+
label: t(name),
|
|
76
|
+
hint: t(`${name}Hint`),
|
|
77
|
+
overriddenLabel: t('overridden'),
|
|
78
|
+
resetLabel: t('reset'),
|
|
79
|
+
invalidLabel: t('invalidNumber'),
|
|
80
|
+
numeric,
|
|
81
|
+
disabled,
|
|
82
|
+
...state[name],
|
|
83
|
+
onEdit: (text) => { props.edit(name, text) },
|
|
84
|
+
onReset: () => { props.resetField(name) },
|
|
85
|
+
}, name)
|
|
86
|
+
return jsxs(P.SettingsForm, {
|
|
87
|
+
labels: { unavailable: t('unavailable'), readOnly: t('readOnly'), saveFailed: t('saveFailed'), save: t('save'), saving: t('saving') },
|
|
88
|
+
state,
|
|
89
|
+
onSave: props.save,
|
|
90
|
+
onDiscard: props.discard,
|
|
91
|
+
children: [...TEXT.map((f) => field(f, false)), ...NUMBERS.map((f) => field(f, true))],
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const inject = ['slots', 'locale', 'configForms']
|
|
96
|
+
|
|
97
|
+
function apply(ctx) {
|
|
98
|
+
const t = ctx.locale.bind(NS)
|
|
99
|
+
// English only for now; every language dsh ships reads the same copy.
|
|
100
|
+
ctx.effect(() => ctx.locale.register(NS, { en, zh: en, ja: en, es: en, fr: en, de: en, ru: en, pt: en }), 'rlmh-dsh: dictionaries')
|
|
101
|
+
const card = new RlmhCardController(ctx.configForms.get(ENTRY))
|
|
102
|
+
ctx.effect(() => () => { card.dispose() }, 'rlmh-dsh: form subscription')
|
|
103
|
+
ctx.effect(() => ctx.configForms.whileServed([ENTRY], () => ctx.slots.inject('plugins.item', () => ctx.slots.register({
|
|
104
|
+
name: 'plugins.item',
|
|
105
|
+
id: 'rlmh',
|
|
106
|
+
order: 60,
|
|
107
|
+
label: () => t('title'),
|
|
108
|
+
locale: NS,
|
|
109
|
+
inject: () => card.inject(),
|
|
110
|
+
}, RlmhCard))), 'rlmh-dsh: page')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { NS, inject, apply }
|
|
114
|
+
},
|
|
115
|
+
})
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// KV cache: none
|
|
2
|
+
// rlmh-dsh's one top-level entry (cordis.patch.yml `rlmh-settings`, named by the package): the user's settings
|
|
3
|
+
// service (lib/settings.js). Because package.json declares a browser half under `dsh.client` (lib/client.js, the
|
|
4
|
+
// settings page), dsh allows only one active deployment entry from this package, so the settings service is it.
|
|
5
|
+
export { default, name, SETTING_DEFAULTS, effective } from './settings.js'
|