@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,228 @@
|
|
|
1
|
+
// Minimal dsh-compatible `ctx.tools` seat for the composition host (P1.6,
|
|
2
|
+
// docs/dsh-tools-assembly.md).
|
|
3
|
+
//
|
|
4
|
+
// API shape follows dsh packages/core/tools: plugins call
|
|
5
|
+
// `ctx.tools.register(defineTool({...}))` and get back the exact disposer.
|
|
6
|
+
// Definitions arrive with `parameters` already compiled to JSON Schema and
|
|
7
|
+
// argument validation baked into `execute` (the vendored dsh-tools schema
|
|
8
|
+
// layer does both). Each registration is mirrored into the kernel's
|
|
9
|
+
// process-level `tool-registry` service — that call rides the bridge effect
|
|
10
|
+
// ledger, so a hard-killed composition can never leave the kernel table
|
|
11
|
+
// dirty — and execution requests come back over the shared serve pump
|
|
12
|
+
// (serve-dispatch.js, target = the tool name), running the registered body
|
|
13
|
+
// in this isolate.
|
|
14
|
+
//
|
|
15
|
+
// Deliberately NOT provided (v1 honesty boundary, see the assembly doc):
|
|
16
|
+
// `restrict`/`guard` (dsh agent-scope concepts), code mode, output-schema
|
|
17
|
+
// enforcement of the canonical value, presenter projections. `exec` carries
|
|
18
|
+
// the identity fields plus an `agent.session.append` bridge that lands as
|
|
19
|
+
// the kernel event `compose:session/append` — observable, but not a claim
|
|
20
|
+
// of rebon transcript persistence (that is the P2.9 session seat).
|
|
21
|
+
import { Service } from 'cordis';
|
|
22
|
+
import { callService, emit, invokeTool } from 'rebon';
|
|
23
|
+
import { TOOL_RUNTIME_SCHEDULER } from '@deepseek-ai/dsh-tools';
|
|
24
|
+
import { ensureServePump, registerServeTarget } from './serve-dispatch.js';
|
|
25
|
+
|
|
26
|
+
/** Extract a `[BRACKET_CODE]` prefix from a seat error message, if any. */
|
|
27
|
+
function bracketCode(message) {
|
|
28
|
+
const match = /^\[([A-Z_]+)\]/.exec(message);
|
|
29
|
+
return match ? match[1] : undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Per-run context/conclusion collection installed by scheduler.prepare. */
|
|
33
|
+
const runStates = new WeakMap();
|
|
34
|
+
|
|
35
|
+
export default class RebonToolsRuntime extends Service {
|
|
36
|
+
constructor(ctx) {
|
|
37
|
+
super(ctx, 'tools');
|
|
38
|
+
this.names = new Set();
|
|
39
|
+
// Local definition table for the scheduler's in-composition dispatch
|
|
40
|
+
// (register() below keeps it in step with the kernel mirror).
|
|
41
|
+
this.defs = new Map();
|
|
42
|
+
ensureServePump();
|
|
43
|
+
|
|
44
|
+
// ---- dsh scheduler contract (loop round) ----
|
|
45
|
+
//
|
|
46
|
+
// agent-loop's tool-calls scheduler drives tools through this
|
|
47
|
+
// symbol-keyed view (dsh's private loop↔tools seam, contract
|
|
48
|
+
// transcribed from packages/core/tools/src/index.ts:451). Dispatch
|
|
49
|
+
// resolution: a composition-registered definition runs in-isolate;
|
|
50
|
+
// anything else goes to the R5 core seat, whose refusals (stable
|
|
51
|
+
// bracketed codes) come back as error RESULTS for the model, not
|
|
52
|
+
// crashes. v1 honesty boundary: no pre/post-execute pipeline —
|
|
53
|
+
// prepare always dispatches, finalize/finish pass through.
|
|
54
|
+
const runtime = this;
|
|
55
|
+
this[TOOL_RUNTIME_SCHEDULER] = {
|
|
56
|
+
async prepare(exec) {
|
|
57
|
+
const state = { contexts: [], concluded: false };
|
|
58
|
+
const run = {
|
|
59
|
+
...exec,
|
|
60
|
+
rootCallId: exec.rootCallId ?? exec.callId,
|
|
61
|
+
deferContext(message) {
|
|
62
|
+
state.contexts.push(message);
|
|
63
|
+
},
|
|
64
|
+
concludeTurn() {
|
|
65
|
+
state.concluded = true;
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
runStates.set(run, state);
|
|
69
|
+
return { kind: 'dispatch', exec: run };
|
|
70
|
+
},
|
|
71
|
+
async dispatch(exec) {
|
|
72
|
+
const state = runStates.get(exec) ?? { contexts: [], concluded: false };
|
|
73
|
+
try {
|
|
74
|
+
const def = runtime.defs.get(exec.name);
|
|
75
|
+
let content;
|
|
76
|
+
if (def !== undefined) {
|
|
77
|
+
const value = await def.execute(exec.arguments, exec);
|
|
78
|
+
content = def.output.render(exec.arguments, value);
|
|
79
|
+
} else {
|
|
80
|
+
const value = await invokeTool(exec.name, exec.arguments ?? {});
|
|
81
|
+
content = [{
|
|
82
|
+
type: 'text',
|
|
83
|
+
text: typeof value === 'string' ? value : JSON.stringify(value),
|
|
84
|
+
}];
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
kind: 'final-result',
|
|
88
|
+
result: {
|
|
89
|
+
content,
|
|
90
|
+
isError: false,
|
|
91
|
+
...(state.contexts.length > 0 ? { additionalContexts: state.contexts } : {}),
|
|
92
|
+
...(state.concluded ? { concludesTurn: true } : {}),
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
} catch (error) {
|
|
96
|
+
const message = String(error?.message ?? error);
|
|
97
|
+
return {
|
|
98
|
+
kind: 'final-result',
|
|
99
|
+
result: {
|
|
100
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
101
|
+
isError: true,
|
|
102
|
+
error: {
|
|
103
|
+
message,
|
|
104
|
+
info: {
|
|
105
|
+
name: error?.name ?? 'Error',
|
|
106
|
+
code: bracketCode(message) ?? 'TOOL_FAILED',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
async finalize(_exec, result) {
|
|
114
|
+
return result;
|
|
115
|
+
},
|
|
116
|
+
finish(_exec, result) {
|
|
117
|
+
return result;
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* dsh concurrency classification. v1: everything is exclusive — strictly
|
|
124
|
+
* serial scheduling is always semantically correct, and it sidesteps the
|
|
125
|
+
* parallel pool until `isConcurrencySafe` mapping is wired.
|
|
126
|
+
*/
|
|
127
|
+
executionMode(_exec) {
|
|
128
|
+
return { kind: 'exclusive' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Register one dsh-shaped tool definition. Mirrors dsh
|
|
133
|
+
* `ToolRuntime.register` validation; returns the exact disposer.
|
|
134
|
+
*/
|
|
135
|
+
register(definition) {
|
|
136
|
+
const name = definition?.name;
|
|
137
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
138
|
+
throw new TypeError('tools.register: definition.name must be a non-empty string');
|
|
139
|
+
}
|
|
140
|
+
if (name.startsWith('web:')) {
|
|
141
|
+
throw new Error(`tool name "${name}" uses the reserved web-provider serve namespace`);
|
|
142
|
+
}
|
|
143
|
+
const output = definition.output;
|
|
144
|
+
if (
|
|
145
|
+
output === undefined || typeof output !== 'object'
|
|
146
|
+
|| typeof output.render !== 'function'
|
|
147
|
+
) {
|
|
148
|
+
throw new TypeError(`tool "${name}" must declare output { schema, render }`);
|
|
149
|
+
}
|
|
150
|
+
if (typeof definition.execute !== 'function') {
|
|
151
|
+
throw new TypeError(`tool "${name}" must declare execute()`);
|
|
152
|
+
}
|
|
153
|
+
const timeoutMs = definition.timeoutMs;
|
|
154
|
+
if (timeoutMs !== undefined && (!Number.isFinite(timeoutMs) || timeoutMs <= 0)) {
|
|
155
|
+
throw new TypeError(`tool "${name}" timeoutMs must be a positive finite number`);
|
|
156
|
+
}
|
|
157
|
+
if (this.names.has(name)) {
|
|
158
|
+
throw new Error(`tool "${name}" is already registered in this scope`);
|
|
159
|
+
}
|
|
160
|
+
const runtime = this;
|
|
161
|
+
// `this.ctx` is the CALLER's context through the Service proxy, so the
|
|
162
|
+
// effect (and with it the kernel mirror) unwinds when the registering
|
|
163
|
+
// plugin disposes — cordis RAII, llm-runtime same pattern. ctx.effect
|
|
164
|
+
// returns the disposer, which dsh's register contract hands back.
|
|
165
|
+
// Ledger attribution: the composition-entry id, captured synchronously
|
|
166
|
+
// (never ambient — async interleavings must not mis-attribute).
|
|
167
|
+
const owner = this.ctx[Symbol.for('rebon.composeOwner')] ?? '';
|
|
168
|
+
return this.ctx.effect(() => {
|
|
169
|
+
const registered = callService('tool-registry', 'register', {
|
|
170
|
+
name,
|
|
171
|
+
description: String(definition.description ?? ''),
|
|
172
|
+
inputSchema: definition.parameters ?? { type: 'object' },
|
|
173
|
+
}, owner);
|
|
174
|
+
if (registered?.shadowed) {
|
|
175
|
+
// Same rule as R3: builtin always wins at dispatch; loud, not
|
|
176
|
+
// fatal — and best-effort, because the Rust registry already
|
|
177
|
+
// logged it and a missing logger seat must not fail registration.
|
|
178
|
+
const message = `composition tool "${name}" shares a builtin tool's name; the builtin wins`;
|
|
179
|
+
try {
|
|
180
|
+
callService('logger', 'warn', { message });
|
|
181
|
+
} catch {
|
|
182
|
+
console.warn(message);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
runtime.names.add(name);
|
|
186
|
+
runtime.defs.set(name, definition);
|
|
187
|
+
const disposeServe = registerServeTarget(name, (input, signal, id) =>
|
|
188
|
+
runtime._execute(definition, input, signal, id),
|
|
189
|
+
);
|
|
190
|
+
const token = registered?.token;
|
|
191
|
+
return () => {
|
|
192
|
+
disposeServe();
|
|
193
|
+
runtime.names.delete(name);
|
|
194
|
+
runtime.defs.delete(name);
|
|
195
|
+
try {
|
|
196
|
+
callService('tool-registry', 'unregister', { name, token });
|
|
197
|
+
} catch {}
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async _execute(definition, input, signal, id) {
|
|
203
|
+
const callId = `compose:${id}`;
|
|
204
|
+
const tool = definition.name;
|
|
205
|
+
const exec = {
|
|
206
|
+
callId,
|
|
207
|
+
rootCallId: callId,
|
|
208
|
+
name: tool,
|
|
209
|
+
arguments: input,
|
|
210
|
+
signal,
|
|
211
|
+
// v1 bridge: dsh tools write per-session facts through
|
|
212
|
+
// `exec.agent.session.append`; here that lands as a kernel event
|
|
213
|
+
// the embedder can observe. No persistence claim.
|
|
214
|
+
agent: {
|
|
215
|
+
session: {
|
|
216
|
+
append(type, data) {
|
|
217
|
+
emit('compose:session/append', { type, data, tool, callId });
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
// defineTool's execute validates arguments first (ToolArgsError with
|
|
223
|
+
// INVALID_ARGS semantics flows into the err envelope's message).
|
|
224
|
+
const value = await definition.execute(input, exec);
|
|
225
|
+
const content = definition.output.render(input, value);
|
|
226
|
+
return { content, isError: false };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Minimal dsh-compatible `ctx.web` seat for the composition host (P2.7,
|
|
2
|
+
// docs/dsh-web-assembly.md).
|
|
3
|
+
//
|
|
4
|
+
// API shape follows dsh packages/web/web verbatim: providers register with
|
|
5
|
+
// `ctx.web.registerSearchProvider/registerFetchProvider` (duplicate ids
|
|
6
|
+
// throw WEB_DUPLICATE_PROVIDER; the exact disposer comes back), and
|
|
7
|
+
// `search`/`fetch` resolve their provider at EXECUTION time with dsh's
|
|
8
|
+
// five-state rules — a configured id wins or fails loudly (never falls
|
|
9
|
+
// back), otherwise exactly one usable provider auto-selects. The seat
|
|
10
|
+
// enforces `maxResults` truncation on search results (capSources).
|
|
11
|
+
//
|
|
12
|
+
// Deployment default: the `rebon` builtin provider (backed by the R5
|
|
13
|
+
// invokeTool bridge onto rebon's own WebSearch/WebFetch) is always
|
|
14
|
+
// registered, and the configured id DEFAULTS to `"rebon"` — feeding the
|
|
15
|
+
// same config field dsh uses (`bootConfig().web.searchProvider` /
|
|
16
|
+
// `.fetchProvider`, i.e. config.json's `web` section), never a hidden
|
|
17
|
+
// priority chain. So plugin providers take over only when configured, and
|
|
18
|
+
// the multi-provider AMBIGUOUS state is unreachable unless the default is
|
|
19
|
+
// explicitly cleared.
|
|
20
|
+
//
|
|
21
|
+
// Registrations are mirrored to the Rust seat via
|
|
22
|
+
// `web/provider-registered` / `web/provider-unregistered` kernel events
|
|
23
|
+
// (`available` is a registration-time snapshot — a static composition's
|
|
24
|
+
// config does not change under it; documented v1 semantics), and each
|
|
25
|
+
// plugin provider is served back to Rust dispatch through the shared serve
|
|
26
|
+
// pump under the reserved target `web:<kind>:<id>`.
|
|
27
|
+
import { Service } from 'cordis';
|
|
28
|
+
import { bootConfig, emit, invokeTool } from 'rebon';
|
|
29
|
+
import { WebError } from './shims/dsh-web.js';
|
|
30
|
+
import { ensureServePump, registerServeTarget } from './serve-dispatch.js';
|
|
31
|
+
|
|
32
|
+
/** dsh WebRuntime.resolveProvider, transcribed. */
|
|
33
|
+
function resolveProvider(configuredId, providers) {
|
|
34
|
+
if (configuredId !== undefined) {
|
|
35
|
+
const provider = providers.get(configuredId);
|
|
36
|
+
if (!provider) {
|
|
37
|
+
throw new WebError(
|
|
38
|
+
`configured web provider "${configuredId}" is not registered`,
|
|
39
|
+
'WEB_PROVIDER_CONFIGURED_MISSING',
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (!provider.available()) {
|
|
43
|
+
throw new WebError(
|
|
44
|
+
`configured web provider "${configuredId}" is registered but unavailable`,
|
|
45
|
+
'WEB_PROVIDER_CONFIGURED_UNAVAILABLE',
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return provider;
|
|
49
|
+
}
|
|
50
|
+
const usable = [...providers.values()].filter((provider) => {
|
|
51
|
+
try {
|
|
52
|
+
return !!provider.available();
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
if (usable.length === 0) {
|
|
58
|
+
throw new WebError('no usable web provider is registered', 'WEB_PROVIDER_UNAVAILABLE');
|
|
59
|
+
}
|
|
60
|
+
if (usable.length > 1) {
|
|
61
|
+
const ids = usable.map((provider) => provider.id).join(', ');
|
|
62
|
+
throw new WebError(
|
|
63
|
+
`multiple usable web providers are registered (${ids}); configure one explicitly`,
|
|
64
|
+
'WEB_PROVIDER_AMBIGUOUS',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return usable[0];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** dsh capSources: enforce `maxResults` on the way back. */
|
|
71
|
+
function capSources(result, maxResults) {
|
|
72
|
+
if (maxResults === undefined || (result.sources?.length ?? 0) <= maxResults) return result;
|
|
73
|
+
return { ...result, sources: result.sources.slice(0, maxResults), truncated: true };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The rebon builtin search provider: rides the R5 invokeTool bridge onto
|
|
78
|
+
* rebon's WebSearch tool (read-only, so no grant is needed) and maps the
|
|
79
|
+
* `{query, answer, results}` contract into dsh's result vocabulary.
|
|
80
|
+
*/
|
|
81
|
+
const rebonBuiltinSearch = {
|
|
82
|
+
id: 'rebon',
|
|
83
|
+
available: () => true,
|
|
84
|
+
async search(request, _signal) {
|
|
85
|
+
const out = await invokeTool('WebSearch', { query: String(request?.query ?? '') });
|
|
86
|
+
const sources = (out?.results ?? [])
|
|
87
|
+
.map((entry) => ({
|
|
88
|
+
url: String(entry?.url ?? ''),
|
|
89
|
+
...(entry?.title ? { title: String(entry.title) } : {}),
|
|
90
|
+
...(entry?.snippet ? { snippet: String(entry.snippet) } : {}),
|
|
91
|
+
}))
|
|
92
|
+
.filter((source) => source.url.length > 0);
|
|
93
|
+
return {
|
|
94
|
+
...(out?.answer ? { content: String(out.answer) } : {}),
|
|
95
|
+
sources,
|
|
96
|
+
truncated: false,
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The rebon builtin fetch provider over rebon's WebFetch tool. The tool
|
|
103
|
+
* returns extracted text, not raw transport facts, so `statusCode` is the
|
|
104
|
+
* 200 the extraction implies and the body is `text` — a documented v1
|
|
105
|
+
* approximation, not a transport claim.
|
|
106
|
+
*/
|
|
107
|
+
const rebonBuiltinFetch = {
|
|
108
|
+
id: 'rebon',
|
|
109
|
+
available: () => true,
|
|
110
|
+
async fetch(request, _signal) {
|
|
111
|
+
const out = await invokeTool('WebFetch', { url: String(request?.url ?? '') });
|
|
112
|
+
const content =
|
|
113
|
+
typeof out === 'string' ? out : String(out?.content ?? out?.text ?? JSON.stringify(out));
|
|
114
|
+
return {
|
|
115
|
+
url: String(request?.url ?? ''),
|
|
116
|
+
statusCode: 200,
|
|
117
|
+
body: { kind: 'text', content },
|
|
118
|
+
truncated: false,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export default class RebonWebRuntime extends Service {
|
|
124
|
+
constructor(ctx, options) {
|
|
125
|
+
super(ctx, 'web');
|
|
126
|
+
// Per-loop hosts keep provider registrations off the process event
|
|
127
|
+
// plane (llm-runtime same rule).
|
|
128
|
+
this.announce = options?.announce !== false;
|
|
129
|
+
const config = bootConfig()?.web ?? {};
|
|
130
|
+
const configured = (value) =>
|
|
131
|
+
typeof value === 'string' && value.trim().length > 0 ? value.trim() : 'rebon';
|
|
132
|
+
this.searchProviderId = configured(config.searchProvider);
|
|
133
|
+
this.fetchProviderId = configured(config.fetchProvider);
|
|
134
|
+
this.searchProviders = new Map([['rebon', rebonBuiltinSearch]]);
|
|
135
|
+
this.fetchProviders = new Map([['rebon', rebonBuiltinFetch]]);
|
|
136
|
+
ensureServePump();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
registerSearchProvider(provider) {
|
|
140
|
+
return this._register(this.searchProviders, 'search', provider);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
registerFetchProvider(provider) {
|
|
144
|
+
return this._register(this.fetchProviders, 'fetch', provider);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
_register(store, kind, provider) {
|
|
148
|
+
if (typeof provider?.id !== 'string' || provider.id.length === 0) {
|
|
149
|
+
throw new TypeError('a web provider must declare a non-empty string `id`');
|
|
150
|
+
}
|
|
151
|
+
if (store.has(provider.id)) {
|
|
152
|
+
throw new WebError(
|
|
153
|
+
`a web provider with id "${provider.id}" is already registered`,
|
|
154
|
+
'WEB_DUPLICATE_PROVIDER',
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const runtime = this;
|
|
158
|
+
// Caller-fork RAII (Service proxy binds this.ctx to the registrant):
|
|
159
|
+
// plugin disposal removes the provider, the kernel mirror, and the
|
|
160
|
+
// serve target together.
|
|
161
|
+
return this.ctx.effect(() => {
|
|
162
|
+
store.set(provider.id, provider);
|
|
163
|
+
let available = false;
|
|
164
|
+
try {
|
|
165
|
+
available = !!provider.available();
|
|
166
|
+
} catch {}
|
|
167
|
+
const disposeServe = registerServeTarget(
|
|
168
|
+
`web:${kind}:${provider.id}`,
|
|
169
|
+
(input, signal) => runtime._serve(kind, provider, input, signal),
|
|
170
|
+
);
|
|
171
|
+
if (runtime.announce) emit('web/provider-registered', { kind, id: provider.id, available });
|
|
172
|
+
return () => {
|
|
173
|
+
disposeServe();
|
|
174
|
+
store.delete(provider.id);
|
|
175
|
+
if (runtime.announce) emit('web/provider-unregistered', { kind, id: provider.id });
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Rust-dispatched execution of one plugin provider (seat rules applied). */
|
|
181
|
+
async _serve(kind, provider, input, signal) {
|
|
182
|
+
if (kind === 'search') {
|
|
183
|
+
const request = {
|
|
184
|
+
query: String(input?.query ?? ''),
|
|
185
|
+
...(input?.maxResults !== undefined ? { maxResults: input.maxResults } : {}),
|
|
186
|
+
};
|
|
187
|
+
return capSources(await provider.search(request, signal), request.maxResults);
|
|
188
|
+
}
|
|
189
|
+
return provider.fetch({ url: String(input?.url ?? '') }, signal);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** dsh consumer API: run one search through the selected provider. */
|
|
193
|
+
async search(request, signal) {
|
|
194
|
+
const provider = resolveProvider(this.searchProviderId, this.searchProviders);
|
|
195
|
+
const result = await provider.search(request, signal);
|
|
196
|
+
return capSources(result, request?.maxResults);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** dsh consumer API: retrieve one URL through the selected provider. */
|
|
200
|
+
async fetch(request, signal) {
|
|
201
|
+
const provider = resolveProvider(this.fetchProviderId, this.fetchProviders);
|
|
202
|
+
return provider.fetch(request, signal);
|
|
203
|
+
}
|
|
204
|
+
}
|