@rebon/cli-darwin-arm64 0.17.2 → 0.18.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/bin/compose-runtime/package.json +17 -0
- package/bin/compose-runtime/payload/compose/credentials-runtime.js +38 -0
- package/bin/compose-runtime/payload/compose/llm-runtime.js +330 -0
- package/bin/compose-runtime/payload/compose/loop-assembly.js +149 -0
- package/bin/compose-runtime/payload/compose/serve-dispatch.js +82 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-anonymous-user-id.js +10 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-credentials.js +13 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-launch-environment.js +21 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-llm.js +313 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-settings.js +43 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-tools.js +20 -0
- package/bin/compose-runtime/payload/compose/shims/dsh-web.js +10 -0
- package/bin/compose-runtime/payload/compose/shims/llm-adapter.js +32 -0
- package/bin/compose-runtime/payload/compose/shims/zod-lite.js +49 -0
- package/bin/compose-runtime/payload/compose/systemprompt-runtime.js +74 -0
- package/bin/compose-runtime/payload/compose/tools-runtime.js +228 -0
- package/bin/compose-runtime/payload/compose/web-runtime.js +204 -0
- package/bin/compose-runtime/payload/vendor/cordis/index.js +1530 -0
- package/bin/compose-runtime/payload/vendor/cosmokit/LICENSE +21 -0
- package/bin/compose-runtime/payload/vendor/cosmokit/index.mjs +357 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-agent +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-agent-loop +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-llm-core +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-llm-deepseek +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-logger-console +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-scope +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-session +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-system-prompt +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-timeout +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-timer +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-tool-todo +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-tool-web +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-tools-schema +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-web-search-exa +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/agent-loop.js +1193 -0
- package/bin/compose-runtime/payload/vendor/dsh/agent.js +714 -0
- package/bin/compose-runtime/payload/vendor/dsh/llm-core.js +269 -0
- package/bin/compose-runtime/payload/vendor/dsh/llm-deepseek.js +672 -0
- package/bin/compose-runtime/payload/vendor/dsh/logger-console.js +83 -0
- package/bin/compose-runtime/payload/vendor/dsh/scope.js +287 -0
- package/bin/compose-runtime/payload/vendor/dsh/session.js +1668 -0
- package/bin/compose-runtime/payload/vendor/dsh/system-prompt.js +309 -0
- package/bin/compose-runtime/payload/vendor/dsh/timeout.js +100 -0
- package/bin/compose-runtime/payload/vendor/dsh/timer.js +128 -0
- package/bin/compose-runtime/payload/vendor/dsh/tool-todo.js +143 -0
- package/bin/compose-runtime/payload/vendor/dsh/tool-web.js +1527 -0
- package/bin/compose-runtime/payload/vendor/dsh/tools-schema.js +849 -0
- package/bin/compose-runtime/payload/vendor/dsh/web-search-exa.js +122 -0
- package/bin/compose-runtime/payload/vendor/eventsource-parser/LICENSE +21 -0
- package/bin/compose-runtime/payload/vendor/eventsource-parser/index.js +177 -0
- package/bin/compose-runtime/payload/vendor/eventsource-parser/stream.js +48 -0
- package/bin/compose-runtime/payload/vendor/schemastery/index.mjs +656 -0
- package/bin/compose-runtime/payload-manifests.json +48 -0
- package/bin/compose-runtime/src/bridge.mjs +142 -0
- package/bin/compose-runtime/src/credentials-runtime.mjs +50 -0
- package/bin/compose-runtime/src/eventsource-stream.mjs +48 -0
- package/bin/compose-runtime/src/index.mjs +7 -0
- package/bin/compose-runtime/src/llm-runtime.mjs +326 -0
- package/bin/compose-runtime/src/loader.mjs +78 -0
- package/bin/compose-runtime/src/loop-assembly.mjs +237 -0
- package/bin/compose-runtime/src/payload.mjs +49 -0
- package/bin/compose-runtime/src/plugin.mjs +59 -0
- package/bin/compose-runtime/src/realm.mjs +223 -0
- package/bin/compose-runtime/src/registry.mjs +191 -0
- package/bin/compose-runtime/src/resolve.mjs +115 -0
- package/bin/compose-runtime/src/systemprompt-runtime.mjs +67 -0
- package/bin/compose-runtime/src/tools-runtime.mjs +239 -0
- package/bin/compose-runtime/src/web-runtime.mjs +209 -0
- package/bin/plugin-host/package.json +11 -0
- package/bin/plugin-host/src/bridge.mjs +64 -0
- package/bin/plugin-host/src/cli.mjs +105 -0
- package/bin/plugin-host/src/framing.mjs +113 -0
- package/bin/plugin-host/src/host.mjs +624 -0
- package/bin/plugin-host/src/json.mjs +196 -0
- package/bin/plugin-host/src/lifecycle.mjs +114 -0
- package/bin/plugin-host/src/loader.mjs +142 -0
- package/bin/plugin-host/src/methods.mjs +256 -0
- package/bin/plugin-host/src/protocol.mjs +129 -0
- package/bin/plugin-host/src/sdk.mjs +7 -0
- package/bin/rebon +0 -0
- package/bin/rebon-boa-helper +0 -0
- package/package.json +4 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// What one Cordis entry registered, captured while it mounts.
|
|
2
|
+
//
|
|
3
|
+
// A Cordis plugin does not know the plugin plane exists: it calls
|
|
4
|
+
// `ctx.tools.register(...)`, `ctx.llm.registerAdapter(...)`,
|
|
5
|
+
// `ctx.systemPrompt.section(...)`, and the seats it calls are the ones that
|
|
6
|
+
// have to turn those into something `plugin/load` can answer with. This is
|
|
7
|
+
// where they put it.
|
|
8
|
+
//
|
|
9
|
+
// The sink reaches a seat through the registering context — `ctx[SINK]`, set
|
|
10
|
+
// when the entry's context is derived — for the same reason the old ledger's
|
|
11
|
+
// owner stamp did: it is read synchronously at registration time, so an async
|
|
12
|
+
// interleaving cannot attribute one plugin's registration to another.
|
|
13
|
+
//
|
|
14
|
+
// Two rules it enforces, both borrowed from the host's own registrar because a
|
|
15
|
+
// composition entry is a plugin and should not get a weaker contract:
|
|
16
|
+
//
|
|
17
|
+
// * **Declared first.** A registration the load request did not declare is
|
|
18
|
+
// refused where the plugin made it, rather than arriving later as a ready
|
|
19
|
+
// report rebon has to reject.
|
|
20
|
+
// * **Registration closes.** Once the entry's fiber has resolved, the host
|
|
21
|
+
// has already been told what this plugin provides; a later registration
|
|
22
|
+
// would be a capability nothing downstream knows about.
|
|
23
|
+
|
|
24
|
+
/** The key an entry's context carries its sink under. */
|
|
25
|
+
export const SINK = Symbol.for('rebon.composeSink');
|
|
26
|
+
/** The key an entry's context carries its plugin id under. */
|
|
27
|
+
export const OWNER = Symbol.for('rebon.composeOwner');
|
|
28
|
+
|
|
29
|
+
class RegistrationError extends Error {
|
|
30
|
+
constructor(code, message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.code = code;
|
|
33
|
+
this.name = 'RegistrationError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class RegistrationSink {
|
|
38
|
+
#sealed = false;
|
|
39
|
+
|
|
40
|
+
constructor(pluginId, declared = {}) {
|
|
41
|
+
this.pluginId = pluginId;
|
|
42
|
+
this.declared = {
|
|
43
|
+
services: new Set(declared.services ?? []),
|
|
44
|
+
eventTopics: new Set(declared.eventTopics ?? []),
|
|
45
|
+
llmProviders: new Set(declared.llmProviders ?? []),
|
|
46
|
+
tools: new Set(declared.tools ?? []),
|
|
47
|
+
};
|
|
48
|
+
/** Protocol-shaped registrations, answered by `plugin/load`. */
|
|
49
|
+
this.services = new Map();
|
|
50
|
+
this.topics = new Map();
|
|
51
|
+
this.llmProviders = new Map();
|
|
52
|
+
this.tools = new Map();
|
|
53
|
+
/** Handles on a session, handed out when a scope opens. */
|
|
54
|
+
this.scopeHandlers = [];
|
|
55
|
+
/** The sessions this plugin is currently attached to.
|
|
56
|
+
*
|
|
57
|
+
* Every entry gets one of these, not just the ones that ask: it is how a
|
|
58
|
+
* seat reaches rebon on behalf of code running outside any inbound call.
|
|
59
|
+
* An agent loop drives its own turn, and the adapter it reaches for a
|
|
60
|
+
* credential is running on the loop's schedule rather than answering
|
|
61
|
+
* anything — but it is still that plugin's code, in that session, and the
|
|
62
|
+
* call it makes should say so. */
|
|
63
|
+
this.sessions = new Set();
|
|
64
|
+
this.scopeHandlers.push((scopeCtx) => {
|
|
65
|
+
this.sessions.add(scopeCtx);
|
|
66
|
+
return () => this.sessions.delete(scopeCtx);
|
|
67
|
+
});
|
|
68
|
+
/** Facts with no protocol counterpart, fetched from the `compose` service. */
|
|
69
|
+
this.sections = [];
|
|
70
|
+
this.catalogs = new Map();
|
|
71
|
+
this.webProviders = [];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#admit(kind, name, declared) {
|
|
75
|
+
if (this.#sealed) {
|
|
76
|
+
throw new RegistrationError(
|
|
77
|
+
'[REGISTRATION_CLOSED]',
|
|
78
|
+
`${kind} ${JSON.stringify(name)} was registered after ${this.pluginId} finished loading`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
82
|
+
throw new RegistrationError('[WRONG_SHAPE]', `a ${kind} needs a non-empty name`);
|
|
83
|
+
}
|
|
84
|
+
if (!declared.has(name)) {
|
|
85
|
+
throw new RegistrationError(
|
|
86
|
+
'[UNAUTHORIZED_REGISTER]',
|
|
87
|
+
`${kind} ${JSON.stringify(name)} is not declared by ${this.pluginId}'s manifest`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
service(name, handler) {
|
|
93
|
+
this.#admit('service', name, this.declared.services);
|
|
94
|
+
this.services.set(name, handler);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
topic(name, handler) {
|
|
98
|
+
this.#admit('topic', name, this.declared.eventTopics);
|
|
99
|
+
this.topics.set(name, handler);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
llm(provider, handler) {
|
|
103
|
+
this.#admit('provider', provider, this.declared.llmProviders);
|
|
104
|
+
this.llmProviders.set(provider, handler);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
tool(definition, handler) {
|
|
108
|
+
this.#admit('tool', definition?.name, this.declared.tools);
|
|
109
|
+
this.tools.set(definition.name, { definition, handler });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// A handle on the session, for a plugin that acts rather than only answers.
|
|
113
|
+
///
|
|
114
|
+
/// Not declared, because it is not a capability: every power the handle
|
|
115
|
+
/// carries is one of the declarations above, checked when it is used.
|
|
116
|
+
scope(handler) {
|
|
117
|
+
if (typeof handler !== 'function') {
|
|
118
|
+
throw new RegistrationError('[WRONG_SHAPE]', 'a scope handler must be a function');
|
|
119
|
+
}
|
|
120
|
+
this.scopeHandlers.push(handler);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---- extras ----------------------------------------------------------
|
|
124
|
+
//
|
|
125
|
+
// A model catalog, a prompt section and a web provider are things rebon
|
|
126
|
+
// needs and the protocol has no field for. They are reported by the
|
|
127
|
+
// composition's own `compose` service rather than by widening the ready
|
|
128
|
+
// report, which every plugin shares and only this one would use.
|
|
129
|
+
|
|
130
|
+
section(entry) {
|
|
131
|
+
this.sections.push(entry);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
catalog(provider, catalog) {
|
|
135
|
+
this.catalogs.set(provider, catalog);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
webProvider(kind, id, available) {
|
|
139
|
+
this.webProviders.push({ kind, id, available });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Closes registration and hands back what the host asked for.
|
|
143
|
+
seal() {
|
|
144
|
+
this.#sealed = true;
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// The `plugin/load` answer, in the shape the host's own loader produces.
|
|
149
|
+
loaded() {
|
|
150
|
+
return {
|
|
151
|
+
services: [...this.services.keys()],
|
|
152
|
+
eventTopics: [...this.topics.keys()],
|
|
153
|
+
llmProviders: [...this.llmProviders.keys()],
|
|
154
|
+
tools: [...this.tools.values()].map((entry) => entry.definition),
|
|
155
|
+
serviceHandlers: this.services,
|
|
156
|
+
topicHandlers: this.topics,
|
|
157
|
+
llmAdapters: this.llmProviders,
|
|
158
|
+
toolHandlers: new Map([...this.tools].map(([name, entry]) => [name, entry.handler])),
|
|
159
|
+
scopeHandlers: [...this.scopeHandlers],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// What the `compose` service reports for this plugin.
|
|
164
|
+
report() {
|
|
165
|
+
return {
|
|
166
|
+
sections: this.sections.map((section) => ({ ...section })),
|
|
167
|
+
providers: [...this.catalogs].map(([provider, catalog]) => ({ provider, ...catalog })),
|
|
168
|
+
webProviders: this.webProviders.map((entry) => ({ ...entry })),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// The session this plugin is attached to, if it is attached to one.
|
|
174
|
+
///
|
|
175
|
+
/// With more than one open, the first is used: a plugin acting on its own
|
|
176
|
+
/// schedule is acting in one session, and which one is a fact about how rebon
|
|
177
|
+
/// opened them rather than something guessable here.
|
|
178
|
+
export function sessionOf(ctx) {
|
|
179
|
+
const sink = ctx?.[SINK];
|
|
180
|
+
if (sink === undefined) return undefined;
|
|
181
|
+
for (const session of sink.sessions) return session;
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// The sink the code currently registering belongs to, or undefined.
|
|
186
|
+
///
|
|
187
|
+
/// Undefined is normal: the built-in seats mount into the realm itself, and a
|
|
188
|
+
/// seat calling another seat is not a registration by any plugin.
|
|
189
|
+
export function sinkOf(ctx) {
|
|
190
|
+
return ctx?.[SINK];
|
|
191
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Module resolution for the composition, on real Node.
|
|
2
|
+
//
|
|
3
|
+
// Node has no import map, but it has `module.registerHooks()` — synchronous,
|
|
4
|
+
// in-thread resolve/load hooks — and this is the composition's map written in
|
|
5
|
+
// that vocabulary. There used to be a second map, in the deno_core host's
|
|
6
|
+
// `build_import_map`, and a test here holding the two to the same specifier
|
|
7
|
+
// set; that host is gone, so this is the only map there is.
|
|
8
|
+
//
|
|
9
|
+
// Two entries it does not carry, both from the deleted one and both in the
|
|
10
|
+
// direction of deleting code:
|
|
11
|
+
//
|
|
12
|
+
// * the four `node:` entries. `node:async_hooks`, `node:util/types`,
|
|
13
|
+
// `node:path` and `node:crypto` resolved to hand-written shims because bare
|
|
14
|
+
// deno_core has no standard library; real Node has all four.
|
|
15
|
+
// * `eventsource-parser/stream` resolves to this package's module rather than
|
|
16
|
+
// the vendored one. The vendored build is not the published package: it was
|
|
17
|
+
// rewritten against the "Mini streams" protocol the old host installed,
|
|
18
|
+
// because the published `EventSourceParserStream` extends the WHATWG
|
|
19
|
+
// `TransformStream` and bare deno_core has none. On real Node that
|
|
20
|
+
// rewrite breaks — `body.pipeThrough(new EventSourceParserStream())` throws
|
|
21
|
+
// `ERR_INVALID_ARG_TYPE` — so the upstream shape comes back. See
|
|
22
|
+
// `eventsource-stream.mjs`.
|
|
23
|
+
import { registerHooks } from 'node:module';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { pathToFileURL } from 'node:url';
|
|
26
|
+
import { payloadDir } from './payload.mjs';
|
|
27
|
+
|
|
28
|
+
/// Payload-relative module map, keyed by bare specifier.
|
|
29
|
+
///
|
|
30
|
+
/// Mirrors `build_import_map` entry for entry, minus the `node:` shims.
|
|
31
|
+
export function payloadModules(dir) {
|
|
32
|
+
const at = (relative) => pathToFileURL(path.join(dir, relative)).href;
|
|
33
|
+
return new Map(Object.entries({
|
|
34
|
+
cordis: at('vendor/cordis/index.js'),
|
|
35
|
+
cosmokit: at('vendor/cosmokit/index.mjs'),
|
|
36
|
+
schemastery: at('vendor/schemastery/index.mjs'),
|
|
37
|
+
'@deepseek-ai/cordis': at('vendor/cordis/index.js'),
|
|
38
|
+
'@deepseek-ai/cosmokit': at('vendor/cosmokit/index.mjs'),
|
|
39
|
+
'@deepseek-ai/schemastery': at('vendor/schemastery/index.mjs'),
|
|
40
|
+
'@deepseek-ai/cordis-plugin-timer': at('vendor/dsh/timer.js'),
|
|
41
|
+
'@deepseek-ai/cordis-plugin-logger-console': at('vendor/dsh/logger-console.js'),
|
|
42
|
+
'@deepseek-ai/dsh-llm-deepseek': at('vendor/dsh/llm-deepseek.js'),
|
|
43
|
+
'@deepseek-ai/dsh-timeout': at('vendor/dsh/timeout.js'),
|
|
44
|
+
'@deepseek-ai/dsh-llm': at('compose/shims/dsh-llm.js'),
|
|
45
|
+
'@deepseek-ai/dsh-credentials': at('compose/shims/dsh-credentials.js'),
|
|
46
|
+
'@deepseek-ai/dsh-settings': at('compose/shims/dsh-settings.js'),
|
|
47
|
+
'@deepseek-ai/dsh-launch-environment': at('compose/shims/dsh-launch-environment.js'),
|
|
48
|
+
'@deepseek-ai/dsh-anonymous-user-id': at('compose/shims/dsh-anonymous-user-id.js'),
|
|
49
|
+
'eventsource-parser': at('vendor/eventsource-parser/index.js'),
|
|
50
|
+
'@deepseek-ai/dsh-tools': at('compose/shims/dsh-tools.js'),
|
|
51
|
+
'@deepseek-ai/dsh-tool-todo': at('vendor/dsh/tool-todo.js'),
|
|
52
|
+
zod: at('compose/shims/zod-lite.js'),
|
|
53
|
+
'@deepseek-ai/dsh-web': at('compose/shims/dsh-web.js'),
|
|
54
|
+
'@deepseek-ai/dsh-web-search-exa': at('vendor/dsh/web-search-exa.js'),
|
|
55
|
+
'@deepseek-ai/dsh-tool-web': at('vendor/dsh/tool-web.js'),
|
|
56
|
+
'@deepseek-ai/dsh-scope': at('vendor/dsh/scope.js'),
|
|
57
|
+
'@deepseek-ai/dsh-session': at('vendor/dsh/session.js'),
|
|
58
|
+
'@deepseek-ai/dsh-agent': at('vendor/dsh/agent.js'),
|
|
59
|
+
'@deepseek-ai/dsh-system-prompt': at('vendor/dsh/system-prompt.js'),
|
|
60
|
+
'@deepseek-ai/dsh-agent-loop': at('vendor/dsh/agent-loop.js'),
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Specifiers this package answers itself rather than from the payload.
|
|
65
|
+
export function localModules() {
|
|
66
|
+
const at = (relative) => new URL(relative, import.meta.url).href;
|
|
67
|
+
return new Map(Object.entries({
|
|
68
|
+
rebon: at('./bridge.mjs'),
|
|
69
|
+
'eventsource-parser/stream': at('./eventsource-stream.mjs'),
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let installed;
|
|
74
|
+
|
|
75
|
+
/// Installs the composition's module resolution, once per process.
|
|
76
|
+
///
|
|
77
|
+
/// `extraModules` is the embedder's own map (the `modules` half of a
|
|
78
|
+
/// composition config: plugin name → absolute module file), which is how a
|
|
79
|
+
/// plugin outside the vendored payload gets a name.
|
|
80
|
+
///
|
|
81
|
+
/// Returns the resolved map so callers can report what a specifier became
|
|
82
|
+
/// without repeating the lookup rules.
|
|
83
|
+
export function installModuleResolution({ dir = payloadDir(), extraModules = {} } = {}) {
|
|
84
|
+
const map = new Map([...payloadModules(dir), ...localModules()]);
|
|
85
|
+
for (const [name, file] of Object.entries(extraModules)) {
|
|
86
|
+
map.set(name, pathToFileURL(path.resolve(file)).href);
|
|
87
|
+
}
|
|
88
|
+
if (installed) {
|
|
89
|
+
// Hooks are process-wide and cannot be unregistered per call; re-running
|
|
90
|
+
// with a different map would leave two resolvers disagreeing about the
|
|
91
|
+
// same specifier, which is a debugging trap rather than a feature.
|
|
92
|
+
for (const [name, url] of map) installed.map.set(name, url);
|
|
93
|
+
return installed.map;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const payloadPrefix = pathToFileURL(dir.endsWith(path.sep) ? dir : dir + path.sep).href;
|
|
97
|
+
const state = { map };
|
|
98
|
+
registerHooks({
|
|
99
|
+
resolve(specifier, context, next) {
|
|
100
|
+
const mapped = state.map.get(specifier);
|
|
101
|
+
if (mapped !== undefined) return { url: mapped, format: 'module', shortCircuit: true };
|
|
102
|
+
return next(specifier, context);
|
|
103
|
+
},
|
|
104
|
+
// The payload's ESM is `.js` under no `package.json`, so Node's own rules
|
|
105
|
+
// read it as CommonJS and every `import` in it is a syntax error. The
|
|
106
|
+
// deno_core loader had no such ambiguity — everything it loaded was a
|
|
107
|
+
// module — so this is a fact about Node, not about the payload.
|
|
108
|
+
load(url, context, next) {
|
|
109
|
+
if (url.startsWith(payloadPrefix)) return next(url, { ...context, format: 'module' });
|
|
110
|
+
return next(url, context);
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
installed = state;
|
|
114
|
+
return state.map;
|
|
115
|
+
}
|
|
@@ -0,0 +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
|
+
}
|
|
@@ -0,0 +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
|
+
}
|