@rebon/cli-win32-x64 1.2.0 → 1.4.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 +202 -0
- package/NOTICE +10 -0
- package/README.md +203 -144
- package/THIRD_PARTY_NOTICES.txt +1232 -128
- package/bin/rebon.js +22 -22
- package/lib/windows-managed.js +390 -390
- package/package.json +9 -2
- package/payload/compose-runtime/package.json +17 -17
- package/payload/compose-runtime/payload/compose/shims/dsh-anonymous-user-id.js +10 -10
- package/payload/compose-runtime/payload/compose/shims/dsh-credentials.js +13 -13
- package/payload/compose-runtime/payload/compose/shims/dsh-launch-environment.js +21 -21
- package/payload/compose-runtime/payload/compose/shims/dsh-llm.js +313 -313
- package/payload/compose-runtime/payload/compose/shims/dsh-settings.js +43 -43
- package/payload/compose-runtime/payload/compose/shims/dsh-tools.js +20 -20
- package/payload/compose-runtime/payload/compose/shims/dsh-web.js +10 -10
- package/payload/compose-runtime/payload/compose/shims/llm-adapter.js +32 -32
- package/payload/compose-runtime/payload/compose/shims/zod-lite.js +49 -49
- package/payload/compose-runtime/payload/vendor/cordis/LICENSE +21 -0
- package/payload/compose-runtime/payload/vendor/cordis/index.js +1530 -1530
- package/payload/compose-runtime/payload/vendor/cosmokit/LICENSE +21 -21
- package/payload/compose-runtime/payload/vendor/cosmokit/index.mjs +357 -357
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent-loop +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-core +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-deepseek +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-logger-console +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-scope +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-session +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-system-prompt +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timeout +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timer +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-todo +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-web +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tools-schema +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown +21 -0
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown-plugin-gfm +21 -0
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-web-search-exa +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/agent-loop.js +1193 -1193
- package/payload/compose-runtime/payload/vendor/dsh/agent.js +714 -714
- package/payload/compose-runtime/payload/vendor/dsh/llm-core.js +269 -269
- package/payload/compose-runtime/payload/vendor/dsh/llm-deepseek.js +672 -672
- package/payload/compose-runtime/payload/vendor/dsh/logger-console.js +83 -83
- package/payload/compose-runtime/payload/vendor/dsh/scope.js +287 -287
- package/payload/compose-runtime/payload/vendor/dsh/session.js +1668 -1668
- package/payload/compose-runtime/payload/vendor/dsh/system-prompt.js +309 -309
- package/payload/compose-runtime/payload/vendor/dsh/timeout.js +100 -100
- package/payload/compose-runtime/payload/vendor/dsh/timer.js +128 -128
- package/payload/compose-runtime/payload/vendor/dsh/tool-todo.js +143 -143
- package/payload/compose-runtime/payload/vendor/dsh/tool-web.js +1527 -1527
- package/payload/compose-runtime/payload/vendor/dsh/tools-schema.js +849 -849
- package/payload/compose-runtime/payload/vendor/dsh/web-search-exa.js +122 -122
- package/payload/compose-runtime/payload/vendor/eventsource-parser/LICENSE +21 -21
- package/payload/compose-runtime/payload/vendor/eventsource-parser/index.js +177 -177
- package/payload/compose-runtime/payload/vendor/eventsource-parser/stream.js +48 -48
- package/payload/compose-runtime/payload/vendor/schemastery/LICENSE +21 -0
- package/payload/compose-runtime/payload/vendor/schemastery/index.mjs +656 -656
- package/payload/compose-runtime/payload-manifests.json +48 -48
- package/payload/compose-runtime/src/bridge.mjs +193 -195
- package/payload/compose-runtime/src/credentials-runtime.mjs +50 -50
- package/payload/compose-runtime/src/eventsource-stream.mjs +48 -48
- package/payload/compose-runtime/src/index.mjs +7 -7
- package/payload/compose-runtime/src/llm-runtime.mjs +326 -326
- package/payload/compose-runtime/src/loader.mjs +78 -78
- package/payload/compose-runtime/src/loop-assembly.mjs +237 -237
- package/payload/compose-runtime/src/payload.mjs +49 -49
- package/payload/compose-runtime/src/plugin.mjs +59 -59
- package/payload/compose-runtime/src/realm.mjs +223 -223
- package/payload/compose-runtime/src/registry.mjs +191 -191
- package/payload/compose-runtime/src/resolve.mjs +115 -115
- package/payload/compose-runtime/src/systemprompt-runtime.mjs +67 -67
- package/payload/compose-runtime/src/tools-runtime.mjs +239 -239
- package/payload/compose-runtime/src/web-runtime.mjs +209 -209
- package/payload/plugin-host/package.json +11 -11
- package/payload/plugin-host/src/bridge.mjs +64 -64
- package/payload/plugin-host/src/cli.mjs +147 -147
- package/payload/plugin-host/src/framing.mjs +113 -113
- package/payload/plugin-host/src/host.mjs +691 -691
- package/payload/plugin-host/src/json.mjs +208 -208
- package/payload/plugin-host/src/lifecycle.mjs +114 -114
- package/payload/plugin-host/src/loader.mjs +183 -183
- package/payload/plugin-host/src/methods.mjs +377 -377
- package/payload/plugin-host/src/ownership.mjs +57 -57
- package/payload/plugin-host/src/protocol.mjs +129 -129
- package/payload/plugin-host/src/sdk.mjs +7 -7
- package/payload/rebon-boa-helper.exe +0 -0
- package/payload/rebon-browser-mcp.exe +0 -0
- package/payload/rebon-computer-use.exe +0 -0
- package/payload/rebon-lsp-mcp.exe +0 -0
- package/payload/rebon.exe +2 -2
- package/payload/sandbox-win.exe +0 -0
- package/scripts/postinstall.js +112 -112
- package/payload/compose-runtime/payload/compose/credentials-runtime.js +0 -38
- package/payload/compose-runtime/payload/compose/llm-runtime.js +0 -330
- package/payload/compose-runtime/payload/compose/loop-assembly.js +0 -149
- package/payload/compose-runtime/payload/compose/serve-dispatch.js +0 -82
- package/payload/compose-runtime/payload/compose/systemprompt-runtime.js +0 -74
- package/payload/compose-runtime/payload/compose/tools-runtime.js +0 -228
- package/payload/compose-runtime/payload/compose/web-runtime.js +0 -204
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
// The composition's `ctx.systemPrompt` seat, on the plugin plane.
|
|
2
|
-
//
|
|
3
|
-
// Ported from `js/compose/systemprompt-runtime.js`. The dsh-facing surface is
|
|
4
|
-
// unchanged: plugins call `ctx.systemPrompt.section({name, order, text})` and
|
|
5
|
-
// get the exact disposer back, duplicate names throw dsh's message, non-finite
|
|
6
|
-
// orders throw dsh's TypeError.
|
|
7
|
-
//
|
|
8
|
-
// What changed is where the section goes. It used to be
|
|
9
|
-
// `callService('system-prompt', 'register', …)` — a synchronous call into a
|
|
10
|
-
// kernel living in the same process, compensated by an `unregister` in the
|
|
11
|
-
// disposer. Neither survives a process boundary: the call has to be awaited and
|
|
12
|
-
// the disposer cannot await. So a section is *reported*: collected here, handed
|
|
13
|
-
// to rebon with the rest of the load report, and withdrawn when the plugin
|
|
14
|
-
// unloads.
|
|
15
|
-
//
|
|
16
|
-
// Deliberately NOT provided (v1 honesty boundary, unchanged): provider-function
|
|
17
|
-
// `text` (dsh resolves it per assembly with an AssembleContext this side does
|
|
18
|
-
// not have — a static composition registers static text), `complete` sections
|
|
19
|
-
// (replacing rebon's own prompt belongs to the loop seat, not a side door),
|
|
20
|
-
// `context()` / variables / tool-order surfaces. All fail loudly rather than
|
|
21
|
-
// silently degrade.
|
|
22
|
-
import { Service } from 'cordis';
|
|
23
|
-
import { sinkOf } from './registry.mjs';
|
|
24
|
-
|
|
25
|
-
export default class RebonSystemPromptRuntime extends Service {
|
|
26
|
-
constructor(ctx) {
|
|
27
|
-
super(ctx, 'systemPrompt');
|
|
28
|
-
this.names = new Set();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** Register one ordered prompt section; returns the exact disposer. */
|
|
32
|
-
section(entry) {
|
|
33
|
-
const name = entry?.name;
|
|
34
|
-
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
35
|
-
throw new TypeError('systemPrompt.section: section.name must be a non-empty string');
|
|
36
|
-
}
|
|
37
|
-
if (!Number.isFinite(entry.order)) {
|
|
38
|
-
throw new TypeError(`prompt section "${name}" order must be a finite number`);
|
|
39
|
-
}
|
|
40
|
-
if (typeof entry.text === 'function') {
|
|
41
|
-
throw new Error(
|
|
42
|
-
`prompt section "${name}": provider-function text is not supported in the composition (register static text)`,
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
if (typeof entry.text !== 'string') {
|
|
46
|
-
throw new TypeError(`prompt section "${name}" text must be a string`);
|
|
47
|
-
}
|
|
48
|
-
if (entry.complete) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
`prompt section "${name}": complete-section replacement is not supported in the composition`,
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
if (this.names.has(name)) {
|
|
54
|
-
throw new Error(`prompt section "${name}" is already registered in this scope`);
|
|
55
|
-
}
|
|
56
|
-
const runtime = this;
|
|
57
|
-
// Caller-fork RAII through the Service proxy (llm/tools/web same pattern):
|
|
58
|
-
// the registering plugin's disposal takes the section with it.
|
|
59
|
-
sinkOf(this.ctx)?.section({ name, order: entry.order, text: entry.text });
|
|
60
|
-
return this.ctx.effect(() => {
|
|
61
|
-
runtime.names.add(name);
|
|
62
|
-
return () => {
|
|
63
|
-
runtime.names.delete(name);
|
|
64
|
-
};
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
1
|
+
// The composition's `ctx.systemPrompt` seat, on the plugin plane.
|
|
2
|
+
//
|
|
3
|
+
// Ported from `js/compose/systemprompt-runtime.js`. The dsh-facing surface is
|
|
4
|
+
// unchanged: plugins call `ctx.systemPrompt.section({name, order, text})` and
|
|
5
|
+
// get the exact disposer back, duplicate names throw dsh's message, non-finite
|
|
6
|
+
// orders throw dsh's TypeError.
|
|
7
|
+
//
|
|
8
|
+
// What changed is where the section goes. It used to be
|
|
9
|
+
// `callService('system-prompt', 'register', …)` — a synchronous call into a
|
|
10
|
+
// kernel living in the same process, compensated by an `unregister` in the
|
|
11
|
+
// disposer. Neither survives a process boundary: the call has to be awaited and
|
|
12
|
+
// the disposer cannot await. So a section is *reported*: collected here, handed
|
|
13
|
+
// to rebon with the rest of the load report, and withdrawn when the plugin
|
|
14
|
+
// unloads.
|
|
15
|
+
//
|
|
16
|
+
// Deliberately NOT provided (v1 honesty boundary, unchanged): provider-function
|
|
17
|
+
// `text` (dsh resolves it per assembly with an AssembleContext this side does
|
|
18
|
+
// not have — a static composition registers static text), `complete` sections
|
|
19
|
+
// (replacing rebon's own prompt belongs to the loop seat, not a side door),
|
|
20
|
+
// `context()` / variables / tool-order surfaces. All fail loudly rather than
|
|
21
|
+
// silently degrade.
|
|
22
|
+
import { Service } from 'cordis';
|
|
23
|
+
import { sinkOf } from './registry.mjs';
|
|
24
|
+
|
|
25
|
+
export default class RebonSystemPromptRuntime extends Service {
|
|
26
|
+
constructor(ctx) {
|
|
27
|
+
super(ctx, 'systemPrompt');
|
|
28
|
+
this.names = new Set();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Register one ordered prompt section; returns the exact disposer. */
|
|
32
|
+
section(entry) {
|
|
33
|
+
const name = entry?.name;
|
|
34
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
35
|
+
throw new TypeError('systemPrompt.section: section.name must be a non-empty string');
|
|
36
|
+
}
|
|
37
|
+
if (!Number.isFinite(entry.order)) {
|
|
38
|
+
throw new TypeError(`prompt section "${name}" order must be a finite number`);
|
|
39
|
+
}
|
|
40
|
+
if (typeof entry.text === 'function') {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`prompt section "${name}": provider-function text is not supported in the composition (register static text)`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (typeof entry.text !== 'string') {
|
|
46
|
+
throw new TypeError(`prompt section "${name}" text must be a string`);
|
|
47
|
+
}
|
|
48
|
+
if (entry.complete) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
`prompt section "${name}": complete-section replacement is not supported in the composition`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
if (this.names.has(name)) {
|
|
54
|
+
throw new Error(`prompt section "${name}" is already registered in this scope`);
|
|
55
|
+
}
|
|
56
|
+
const runtime = this;
|
|
57
|
+
// Caller-fork RAII through the Service proxy (llm/tools/web same pattern):
|
|
58
|
+
// the registering plugin's disposal takes the section with it.
|
|
59
|
+
sinkOf(this.ctx)?.section({ name, order: entry.order, text: entry.text });
|
|
60
|
+
return this.ctx.effect(() => {
|
|
61
|
+
runtime.names.add(name);
|
|
62
|
+
return () => {
|
|
63
|
+
runtime.names.delete(name);
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -1,239 +1,239 @@
|
|
|
1
|
-
// The composition's `ctx.tools` seat, on the plugin plane.
|
|
2
|
-
//
|
|
3
|
-
// Ported from `js/compose/tools-runtime.js`. The dsh-facing surface is
|
|
4
|
-
// unchanged: plugins call `ctx.tools.register(defineTool({...}))` and get the
|
|
5
|
-
// exact disposer back, definitions arrive with `parameters` already compiled to
|
|
6
|
-
// JSON Schema and argument validation baked into `execute`, and the loop drives
|
|
7
|
-
// tools through the symbol-keyed scheduler contract.
|
|
8
|
-
//
|
|
9
|
-
// What changed:
|
|
10
|
-
//
|
|
11
|
-
// * **The serve pump is gone.** `registerServeTarget(name, …)` and
|
|
12
|
-
// `op_tool_serve_next` are replaced by the tool being a *declared* tool of
|
|
13
|
-
// the loading plugin, called back through `tool/call`. A tool is offered to
|
|
14
|
-
// a model, so the registration carries the description and input schema
|
|
15
|
-
// rather than a name alone — which is what the plane needs anyway.
|
|
16
|
-
// * **`callService('tool-registry', 'register', …)` is gone.** The tool is
|
|
17
|
-
// reported by `plugin/load` and withdrawn by `plugin/unload` draining, so
|
|
18
|
-
// the compensating `unregister` a disposer used to make — and could not
|
|
19
|
-
// have made across a process boundary — has nothing left to compensate.
|
|
20
|
-
// Shadowing a builtin name is rebon's judgement to make and rebon's to log;
|
|
21
|
-
// the composition no longer hears about it, which is why the warning that
|
|
22
|
-
// used to be printed here is not.
|
|
23
|
-
// * **`exec.agent.session.append` publishes.** It was a kernel event emitted
|
|
24
|
-
// from inside the isolate; it is `event/emit` now, declared as
|
|
25
|
-
// `publishedTopics`, and it is the reason a tool-providing plugin declares
|
|
26
|
-
// `compose:session/append`.
|
|
27
|
-
import { Service } from 'cordis';
|
|
28
|
-
import { TOOL_RUNTIME_SCHEDULER } from '@deepseek-ai/dsh-tools';
|
|
29
|
-
import { invokeTool, publish, withCall } from './bridge.mjs';
|
|
30
|
-
import { sinkOf } from './registry.mjs';
|
|
31
|
-
|
|
32
|
-
/** The topic a tool body's session writes land on. */
|
|
33
|
-
export const SESSION_APPEND_TOPIC = 'compose:session/append';
|
|
34
|
-
|
|
35
|
-
/** Extract a `[BRACKET_CODE]` prefix from a seat error message, if any. */
|
|
36
|
-
function bracketCode(message) {
|
|
37
|
-
const match = /^\[([A-Z_]+)\]/.exec(message);
|
|
38
|
-
return match ? match[1] : undefined;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Per-run context/conclusion collection installed by scheduler.prepare. */
|
|
42
|
-
const runStates = new WeakMap();
|
|
43
|
-
|
|
44
|
-
export default class RebonToolsRuntime extends Service {
|
|
45
|
-
constructor(ctx) {
|
|
46
|
-
super(ctx, 'tools');
|
|
47
|
-
this.names = new Set();
|
|
48
|
-
// Local definition table for the scheduler's in-composition dispatch
|
|
49
|
-
// (register() below keeps it in step with what rebon was told).
|
|
50
|
-
this.defs = new Map();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* dsh scheduler contract.
|
|
55
|
-
*
|
|
56
|
-
* agent-loop's tool-calls scheduler drives tools through this symbol-keyed
|
|
57
|
-
* view (dsh's private loop↔tools seam, contract transcribed from
|
|
58
|
-
* packages/core/tools/src/index.ts:451). Dispatch resolution: a
|
|
59
|
-
* composition-registered definition runs here; anything else goes to rebon's
|
|
60
|
-
* own tools through `tool/invoke`, whose refusals (stable bracketed codes)
|
|
61
|
-
* come back as error RESULTS for the model, not crashes. v1 honesty
|
|
62
|
-
* boundary: no pre/post-execute pipeline — prepare always dispatches,
|
|
63
|
-
* finalize/finish pass through.
|
|
64
|
-
*
|
|
65
|
-
* A getter rather than a field on purpose: read through the Cordis Service
|
|
66
|
-
* proxy, `this.ctx` is the *caller's* context — the loop plugin driving the
|
|
67
|
-
* turn. That is the identity a `tool/invoke` made on the loop's own schedule
|
|
68
|
-
* has to carry, and capturing it here is the only place it is still known.
|
|
69
|
-
*/
|
|
70
|
-
get [TOOL_RUNTIME_SCHEDULER]() {
|
|
71
|
-
const runtime = this;
|
|
72
|
-
const via = this.ctx;
|
|
73
|
-
return {
|
|
74
|
-
async prepare(exec) {
|
|
75
|
-
const state = { contexts: [], concluded: false };
|
|
76
|
-
const run = {
|
|
77
|
-
...exec,
|
|
78
|
-
rootCallId: exec.rootCallId ?? exec.callId,
|
|
79
|
-
deferContext(message) {
|
|
80
|
-
state.contexts.push(message);
|
|
81
|
-
},
|
|
82
|
-
concludeTurn() {
|
|
83
|
-
state.concluded = true;
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
runStates.set(run, state);
|
|
87
|
-
return { kind: 'dispatch', exec: run };
|
|
88
|
-
},
|
|
89
|
-
dispatch: (exec) => runtime._dispatch(exec, via),
|
|
90
|
-
async finalize(_exec, result) {
|
|
91
|
-
return result;
|
|
92
|
-
},
|
|
93
|
-
finish(_exec, result) {
|
|
94
|
-
return result;
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async _dispatch(exec, via) {
|
|
100
|
-
const state = runStates.get(exec) ?? { contexts: [], concluded: false };
|
|
101
|
-
try {
|
|
102
|
-
const def = this.defs.get(exec.name);
|
|
103
|
-
let content;
|
|
104
|
-
if (def !== undefined) {
|
|
105
|
-
const value = await def.execute(exec.arguments, exec);
|
|
106
|
-
content = def.output.render(exec.arguments, value);
|
|
107
|
-
} else {
|
|
108
|
-
const value = await invokeTool(exec.name, exec.arguments ?? {}, { via });
|
|
109
|
-
content = [{
|
|
110
|
-
type: 'text',
|
|
111
|
-
text: typeof value === 'string' ? value : JSON.stringify(value),
|
|
112
|
-
}];
|
|
113
|
-
}
|
|
114
|
-
return {
|
|
115
|
-
kind: 'final-result',
|
|
116
|
-
result: {
|
|
117
|
-
content,
|
|
118
|
-
isError: false,
|
|
119
|
-
...(state.contexts.length > 0 ? { additionalContexts: state.contexts } : {}),
|
|
120
|
-
...(state.concluded ? { concludesTurn: true } : {}),
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
} catch (error) {
|
|
124
|
-
const message = String(error?.message ?? error);
|
|
125
|
-
return {
|
|
126
|
-
kind: 'final-result',
|
|
127
|
-
result: {
|
|
128
|
-
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
129
|
-
isError: true,
|
|
130
|
-
error: {
|
|
131
|
-
message,
|
|
132
|
-
info: {
|
|
133
|
-
name: error?.name ?? 'Error',
|
|
134
|
-
code: error?.code ?? bracketCode(message) ?? 'TOOL_FAILED',
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* dsh concurrency classification. v1: everything is exclusive — strictly
|
|
144
|
-
* serial scheduling is always semantically correct, and it sidesteps the
|
|
145
|
-
* parallel pool until `isConcurrencySafe` mapping is wired.
|
|
146
|
-
*/
|
|
147
|
-
executionMode(_exec) {
|
|
148
|
-
return { kind: 'exclusive' };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Register one dsh-shaped tool definition. Mirrors dsh
|
|
153
|
-
* `ToolRuntime.register` validation; returns the exact disposer.
|
|
154
|
-
*/
|
|
155
|
-
register(definition) {
|
|
156
|
-
const name = definition?.name;
|
|
157
|
-
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
158
|
-
throw new TypeError('tools.register: definition.name must be a non-empty string');
|
|
159
|
-
}
|
|
160
|
-
const output = definition.output;
|
|
161
|
-
if (
|
|
162
|
-
output === undefined || typeof output !== 'object'
|
|
163
|
-
|| typeof output.render !== 'function'
|
|
164
|
-
) {
|
|
165
|
-
throw new TypeError(`tool "${name}" must declare output { schema, render }`);
|
|
166
|
-
}
|
|
167
|
-
if (typeof definition.execute !== 'function') {
|
|
168
|
-
throw new TypeError(`tool "${name}" must declare execute()`);
|
|
169
|
-
}
|
|
170
|
-
const timeoutMs = definition.timeoutMs;
|
|
171
|
-
if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
|
|
172
|
-
throw new TypeError(`tool "${name}" timeoutMs must be a positive finite number`);
|
|
173
|
-
}
|
|
174
|
-
if (this.names.has(name)) {
|
|
175
|
-
throw new Error(`tool "${name}" is already registered in this scope`);
|
|
176
|
-
}
|
|
177
|
-
const runtime = this;
|
|
178
|
-
// `this.ctx` is the CALLER's context through the Service proxy, so the
|
|
179
|
-
// registration is attributed to the plugin that made it and the effect
|
|
180
|
-
// unwinds when that plugin disposes — Cordis RAII, llm-runtime same
|
|
181
|
-
// pattern. Read synchronously: which plugin is registering is a fact
|
|
182
|
-
// about now, and an await would let another one interleave.
|
|
183
|
-
const sink = sinkOf(this.ctx);
|
|
184
|
-
sink?.tool(
|
|
185
|
-
{
|
|
186
|
-
name,
|
|
187
|
-
description: String(definition.description ?? ''),
|
|
188
|
-
inputSchema: definition.parameters ?? { type: 'object' },
|
|
189
|
-
},
|
|
190
|
-
(input, callCtx) => withCall(callCtx, () => runtime._execute(name, input, callCtx)),
|
|
191
|
-
);
|
|
192
|
-
return this.ctx.effect(() => {
|
|
193
|
-
runtime.names.add(name);
|
|
194
|
-
runtime.defs.set(name, definition);
|
|
195
|
-
return () => {
|
|
196
|
-
runtime.names.delete(name);
|
|
197
|
-
runtime.defs.delete(name);
|
|
198
|
-
};
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/// Runs one registered tool for a `tool/call`.
|
|
203
|
-
///
|
|
204
|
-
/// Resolved through the live table rather than closing over the definition:
|
|
205
|
-
/// a tool whose plugin disposed it is gone, and answering with a body that
|
|
206
|
-
/// is no longer registered would make disposal a lie.
|
|
207
|
-
async _execute(name, input, callCtx) {
|
|
208
|
-
const definition = this.defs.get(name);
|
|
209
|
-
if (definition === undefined) {
|
|
210
|
-
const error = new Error(`tool "${name}" is no longer registered in this composition`);
|
|
211
|
-
error.code = '[STALE_PROVIDER]';
|
|
212
|
-
throw error;
|
|
213
|
-
}
|
|
214
|
-
const callId = `compose:${callCtx.scopeId ?? 'scope'}:${name}`;
|
|
215
|
-
const exec = {
|
|
216
|
-
callId,
|
|
217
|
-
rootCallId: callId,
|
|
218
|
-
name,
|
|
219
|
-
arguments: input,
|
|
220
|
-
signal: callCtx.signal,
|
|
221
|
-
// dsh tools write per-session facts through `exec.agent.session.append`.
|
|
222
|
-
// On the plane that is a published event: the tool is telling rebon
|
|
223
|
-
// something happened, and rebon decides who hears it.
|
|
224
|
-
agent: {
|
|
225
|
-
session: {
|
|
226
|
-
append(type, data) {
|
|
227
|
-
void publish(SESSION_APPEND_TOPIC, { type, data, tool: name, callId })
|
|
228
|
-
.catch(() => {});
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
};
|
|
233
|
-
// defineTool's execute validates arguments first (ToolArgsError with
|
|
234
|
-
// INVALID_ARGS semantics flows out as this call's terminal).
|
|
235
|
-
const value = await definition.execute(input, exec);
|
|
236
|
-
const content = definition.output.render(input, value);
|
|
237
|
-
return { content, isError: false };
|
|
238
|
-
}
|
|
239
|
-
}
|
|
1
|
+
// The composition's `ctx.tools` seat, on the plugin plane.
|
|
2
|
+
//
|
|
3
|
+
// Ported from `js/compose/tools-runtime.js`. The dsh-facing surface is
|
|
4
|
+
// unchanged: plugins call `ctx.tools.register(defineTool({...}))` and get the
|
|
5
|
+
// exact disposer back, definitions arrive with `parameters` already compiled to
|
|
6
|
+
// JSON Schema and argument validation baked into `execute`, and the loop drives
|
|
7
|
+
// tools through the symbol-keyed scheduler contract.
|
|
8
|
+
//
|
|
9
|
+
// What changed:
|
|
10
|
+
//
|
|
11
|
+
// * **The serve pump is gone.** `registerServeTarget(name, …)` and
|
|
12
|
+
// `op_tool_serve_next` are replaced by the tool being a *declared* tool of
|
|
13
|
+
// the loading plugin, called back through `tool/call`. A tool is offered to
|
|
14
|
+
// a model, so the registration carries the description and input schema
|
|
15
|
+
// rather than a name alone — which is what the plane needs anyway.
|
|
16
|
+
// * **`callService('tool-registry', 'register', …)` is gone.** The tool is
|
|
17
|
+
// reported by `plugin/load` and withdrawn by `plugin/unload` draining, so
|
|
18
|
+
// the compensating `unregister` a disposer used to make — and could not
|
|
19
|
+
// have made across a process boundary — has nothing left to compensate.
|
|
20
|
+
// Shadowing a builtin name is rebon's judgement to make and rebon's to log;
|
|
21
|
+
// the composition no longer hears about it, which is why the warning that
|
|
22
|
+
// used to be printed here is not.
|
|
23
|
+
// * **`exec.agent.session.append` publishes.** It was a kernel event emitted
|
|
24
|
+
// from inside the isolate; it is `event/emit` now, declared as
|
|
25
|
+
// `publishedTopics`, and it is the reason a tool-providing plugin declares
|
|
26
|
+
// `compose:session/append`.
|
|
27
|
+
import { Service } from 'cordis';
|
|
28
|
+
import { TOOL_RUNTIME_SCHEDULER } from '@deepseek-ai/dsh-tools';
|
|
29
|
+
import { invokeTool, publish, withCall } from './bridge.mjs';
|
|
30
|
+
import { sinkOf } from './registry.mjs';
|
|
31
|
+
|
|
32
|
+
/** The topic a tool body's session writes land on. */
|
|
33
|
+
export const SESSION_APPEND_TOPIC = 'compose:session/append';
|
|
34
|
+
|
|
35
|
+
/** Extract a `[BRACKET_CODE]` prefix from a seat error message, if any. */
|
|
36
|
+
function bracketCode(message) {
|
|
37
|
+
const match = /^\[([A-Z_]+)\]/.exec(message);
|
|
38
|
+
return match ? match[1] : undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Per-run context/conclusion collection installed by scheduler.prepare. */
|
|
42
|
+
const runStates = new WeakMap();
|
|
43
|
+
|
|
44
|
+
export default class RebonToolsRuntime extends Service {
|
|
45
|
+
constructor(ctx) {
|
|
46
|
+
super(ctx, 'tools');
|
|
47
|
+
this.names = new Set();
|
|
48
|
+
// Local definition table for the scheduler's in-composition dispatch
|
|
49
|
+
// (register() below keeps it in step with what rebon was told).
|
|
50
|
+
this.defs = new Map();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* dsh scheduler contract.
|
|
55
|
+
*
|
|
56
|
+
* agent-loop's tool-calls scheduler drives tools through this symbol-keyed
|
|
57
|
+
* view (dsh's private loop↔tools seam, contract transcribed from
|
|
58
|
+
* packages/core/tools/src/index.ts:451). Dispatch resolution: a
|
|
59
|
+
* composition-registered definition runs here; anything else goes to rebon's
|
|
60
|
+
* own tools through `tool/invoke`, whose refusals (stable bracketed codes)
|
|
61
|
+
* come back as error RESULTS for the model, not crashes. v1 honesty
|
|
62
|
+
* boundary: no pre/post-execute pipeline — prepare always dispatches,
|
|
63
|
+
* finalize/finish pass through.
|
|
64
|
+
*
|
|
65
|
+
* A getter rather than a field on purpose: read through the Cordis Service
|
|
66
|
+
* proxy, `this.ctx` is the *caller's* context — the loop plugin driving the
|
|
67
|
+
* turn. That is the identity a `tool/invoke` made on the loop's own schedule
|
|
68
|
+
* has to carry, and capturing it here is the only place it is still known.
|
|
69
|
+
*/
|
|
70
|
+
get [TOOL_RUNTIME_SCHEDULER]() {
|
|
71
|
+
const runtime = this;
|
|
72
|
+
const via = this.ctx;
|
|
73
|
+
return {
|
|
74
|
+
async prepare(exec) {
|
|
75
|
+
const state = { contexts: [], concluded: false };
|
|
76
|
+
const run = {
|
|
77
|
+
...exec,
|
|
78
|
+
rootCallId: exec.rootCallId ?? exec.callId,
|
|
79
|
+
deferContext(message) {
|
|
80
|
+
state.contexts.push(message);
|
|
81
|
+
},
|
|
82
|
+
concludeTurn() {
|
|
83
|
+
state.concluded = true;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
runStates.set(run, state);
|
|
87
|
+
return { kind: 'dispatch', exec: run };
|
|
88
|
+
},
|
|
89
|
+
dispatch: (exec) => runtime._dispatch(exec, via),
|
|
90
|
+
async finalize(_exec, result) {
|
|
91
|
+
return result;
|
|
92
|
+
},
|
|
93
|
+
finish(_exec, result) {
|
|
94
|
+
return result;
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async _dispatch(exec, via) {
|
|
100
|
+
const state = runStates.get(exec) ?? { contexts: [], concluded: false };
|
|
101
|
+
try {
|
|
102
|
+
const def = this.defs.get(exec.name);
|
|
103
|
+
let content;
|
|
104
|
+
if (def !== undefined) {
|
|
105
|
+
const value = await def.execute(exec.arguments, exec);
|
|
106
|
+
content = def.output.render(exec.arguments, value);
|
|
107
|
+
} else {
|
|
108
|
+
const value = await invokeTool(exec.name, exec.arguments ?? {}, { via });
|
|
109
|
+
content = [{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: typeof value === 'string' ? value : JSON.stringify(value),
|
|
112
|
+
}];
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
kind: 'final-result',
|
|
116
|
+
result: {
|
|
117
|
+
content,
|
|
118
|
+
isError: false,
|
|
119
|
+
...(state.contexts.length > 0 ? { additionalContexts: state.contexts } : {}),
|
|
120
|
+
...(state.concluded ? { concludesTurn: true } : {}),
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
} catch (error) {
|
|
124
|
+
const message = String(error?.message ?? error);
|
|
125
|
+
return {
|
|
126
|
+
kind: 'final-result',
|
|
127
|
+
result: {
|
|
128
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
129
|
+
isError: true,
|
|
130
|
+
error: {
|
|
131
|
+
message,
|
|
132
|
+
info: {
|
|
133
|
+
name: error?.name ?? 'Error',
|
|
134
|
+
code: error?.code ?? bracketCode(message) ?? 'TOOL_FAILED',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* dsh concurrency classification. v1: everything is exclusive — strictly
|
|
144
|
+
* serial scheduling is always semantically correct, and it sidesteps the
|
|
145
|
+
* parallel pool until `isConcurrencySafe` mapping is wired.
|
|
146
|
+
*/
|
|
147
|
+
executionMode(_exec) {
|
|
148
|
+
return { kind: 'exclusive' };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Register one dsh-shaped tool definition. Mirrors dsh
|
|
153
|
+
* `ToolRuntime.register` validation; returns the exact disposer.
|
|
154
|
+
*/
|
|
155
|
+
register(definition) {
|
|
156
|
+
const name = definition?.name;
|
|
157
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
158
|
+
throw new TypeError('tools.register: definition.name must be a non-empty string');
|
|
159
|
+
}
|
|
160
|
+
const output = definition.output;
|
|
161
|
+
if (
|
|
162
|
+
output === undefined || typeof output !== 'object'
|
|
163
|
+
|| typeof output.render !== 'function'
|
|
164
|
+
) {
|
|
165
|
+
throw new TypeError(`tool "${name}" must declare output { schema, render }`);
|
|
166
|
+
}
|
|
167
|
+
if (typeof definition.execute !== 'function') {
|
|
168
|
+
throw new TypeError(`tool "${name}" must declare execute()`);
|
|
169
|
+
}
|
|
170
|
+
const timeoutMs = definition.timeoutMs;
|
|
171
|
+
if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
|
|
172
|
+
throw new TypeError(`tool "${name}" timeoutMs must be a positive finite number`);
|
|
173
|
+
}
|
|
174
|
+
if (this.names.has(name)) {
|
|
175
|
+
throw new Error(`tool "${name}" is already registered in this scope`);
|
|
176
|
+
}
|
|
177
|
+
const runtime = this;
|
|
178
|
+
// `this.ctx` is the CALLER's context through the Service proxy, so the
|
|
179
|
+
// registration is attributed to the plugin that made it and the effect
|
|
180
|
+
// unwinds when that plugin disposes — Cordis RAII, llm-runtime same
|
|
181
|
+
// pattern. Read synchronously: which plugin is registering is a fact
|
|
182
|
+
// about now, and an await would let another one interleave.
|
|
183
|
+
const sink = sinkOf(this.ctx);
|
|
184
|
+
sink?.tool(
|
|
185
|
+
{
|
|
186
|
+
name,
|
|
187
|
+
description: String(definition.description ?? ''),
|
|
188
|
+
inputSchema: definition.parameters ?? { type: 'object' },
|
|
189
|
+
},
|
|
190
|
+
(input, callCtx) => withCall(callCtx, () => runtime._execute(name, input, callCtx)),
|
|
191
|
+
);
|
|
192
|
+
return this.ctx.effect(() => {
|
|
193
|
+
runtime.names.add(name);
|
|
194
|
+
runtime.defs.set(name, definition);
|
|
195
|
+
return () => {
|
|
196
|
+
runtime.names.delete(name);
|
|
197
|
+
runtime.defs.delete(name);
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/// Runs one registered tool for a `tool/call`.
|
|
203
|
+
///
|
|
204
|
+
/// Resolved through the live table rather than closing over the definition:
|
|
205
|
+
/// a tool whose plugin disposed it is gone, and answering with a body that
|
|
206
|
+
/// is no longer registered would make disposal a lie.
|
|
207
|
+
async _execute(name, input, callCtx) {
|
|
208
|
+
const definition = this.defs.get(name);
|
|
209
|
+
if (definition === undefined) {
|
|
210
|
+
const error = new Error(`tool "${name}" is no longer registered in this composition`);
|
|
211
|
+
error.code = '[STALE_PROVIDER]';
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
const callId = `compose:${callCtx.scopeId ?? 'scope'}:${name}`;
|
|
215
|
+
const exec = {
|
|
216
|
+
callId,
|
|
217
|
+
rootCallId: callId,
|
|
218
|
+
name,
|
|
219
|
+
arguments: input,
|
|
220
|
+
signal: callCtx.signal,
|
|
221
|
+
// dsh tools write per-session facts through `exec.agent.session.append`.
|
|
222
|
+
// On the plane that is a published event: the tool is telling rebon
|
|
223
|
+
// something happened, and rebon decides who hears it.
|
|
224
|
+
agent: {
|
|
225
|
+
session: {
|
|
226
|
+
append(type, data) {
|
|
227
|
+
void publish(SESSION_APPEND_TOPIC, { type, data, tool: name, callId })
|
|
228
|
+
.catch(() => {});
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
// defineTool's execute validates arguments first (ToolArgsError with
|
|
234
|
+
// INVALID_ARGS semantics flows out as this call's terminal).
|
|
235
|
+
const value = await definition.execute(input, exec);
|
|
236
|
+
const content = definition.output.render(input, value);
|
|
237
|
+
return { content, isError: false };
|
|
238
|
+
}
|
|
239
|
+
}
|