@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,209 +1,209 @@
|
|
|
1
|
-
// The composition's `ctx.web` seat, on the plugin plane.
|
|
2
|
-
//
|
|
3
|
-
// Ported from `js/compose/web-runtime.js`. The dsh-facing surface is
|
|
4
|
-
// unchanged and 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 back;
|
|
9
|
-
// otherwise exactly one usable provider auto-selects. `maxResults` truncation
|
|
10
|
-
// (capSources) is enforced on the way out.
|
|
11
|
-
//
|
|
12
|
-
// Deployment default: the `rebon` builtin provider, backed by rebon's own
|
|
13
|
-
// WebSearch/WebFetch tools through `tool/invoke`, is always registered, and the
|
|
14
|
-
// configured id DEFAULTS to `"rebon"`. So a plugin provider takes over only
|
|
15
|
-
// when configured, and dsh's AMBIGUOUS state is unreachable unless the default
|
|
16
|
-
// is explicitly cleared. **A plugin whose tool reaches `ctx.web` therefore
|
|
17
|
-
// causes a `tool/invoke` of rebon's WebSearch, attributed to that plugin** —
|
|
18
|
-
// which is why such a plugin declares `WebSearch`/`WebFetch` in
|
|
19
|
-
// `invokableTools`. The attribution is the point: rebon can always answer who
|
|
20
|
-
// caused a tool to run.
|
|
21
|
-
//
|
|
22
|
-
// What changed: `emit('web/provider-registered', …)` and the shared serve pump
|
|
23
|
-
// are gone. A registered provider is *reported* — it appears in the plugin's
|
|
24
|
-
// load report as a service named `web:<kind>:<id>`, which is also how rebon
|
|
25
|
-
// dispatches into it.
|
|
26
|
-
import { Service } from 'cordis';
|
|
27
|
-
import { WebError } from '@deepseek-ai/dsh-web';
|
|
28
|
-
import { invokeTool, withCall } from './bridge.mjs';
|
|
29
|
-
import { sinkOf } from './registry.mjs';
|
|
30
|
-
|
|
31
|
-
/** The service-name namespace a plugin web provider is served under. */
|
|
32
|
-
export const webTarget = (kind, id) => `web:${kind}:${id}`;
|
|
33
|
-
|
|
34
|
-
/** dsh WebRuntime.resolveProvider, transcribed. */
|
|
35
|
-
function resolveProvider(configuredId, providers) {
|
|
36
|
-
if (configuredId !== undefined) {
|
|
37
|
-
const provider = providers.get(configuredId);
|
|
38
|
-
if (!provider) {
|
|
39
|
-
throw new WebError(
|
|
40
|
-
`configured web provider "${configuredId}" is not registered`,
|
|
41
|
-
'WEB_PROVIDER_CONFIGURED_MISSING',
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
if (!provider.available()) {
|
|
45
|
-
throw new WebError(
|
|
46
|
-
`configured web provider "${configuredId}" is registered but unavailable`,
|
|
47
|
-
'WEB_PROVIDER_CONFIGURED_UNAVAILABLE',
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
return provider;
|
|
51
|
-
}
|
|
52
|
-
const usable = [...providers.values()].filter((provider) => {
|
|
53
|
-
try {
|
|
54
|
-
return !!provider.available();
|
|
55
|
-
} catch {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
if (usable.length === 0) {
|
|
60
|
-
throw new WebError('no usable web provider is registered', 'WEB_PROVIDER_UNAVAILABLE');
|
|
61
|
-
}
|
|
62
|
-
if (usable.length > 1) {
|
|
63
|
-
const ids = usable.map((provider) => provider.id).join(', ');
|
|
64
|
-
throw new WebError(
|
|
65
|
-
`multiple usable web providers are registered (${ids}); configure one explicitly`,
|
|
66
|
-
'WEB_PROVIDER_AMBIGUOUS',
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
return usable[0];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** dsh capSources: enforce `maxResults` on the way back. */
|
|
73
|
-
function capSources(result, maxResults) {
|
|
74
|
-
if (maxResults === undefined || (result.sources?.length ?? 0) <= maxResults) return result;
|
|
75
|
-
return { ...result, sources: result.sources.slice(0, maxResults), truncated: true };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* The rebon builtin search provider: reaches rebon's WebSearch tool and maps
|
|
80
|
-
* the `{query, answer, results}` contract into dsh's result vocabulary.
|
|
81
|
-
*/
|
|
82
|
-
const rebonBuiltinSearch = {
|
|
83
|
-
id: 'rebon',
|
|
84
|
-
available: () => true,
|
|
85
|
-
async search(request, _signal) {
|
|
86
|
-
const out = await invokeTool('WebSearch', { query: String(request?.query ?? '') });
|
|
87
|
-
const sources = (out?.results ?? [])
|
|
88
|
-
.map((entry) => ({
|
|
89
|
-
url: String(entry?.url ?? ''),
|
|
90
|
-
...(entry?.title ? { title: String(entry.title) } : {}),
|
|
91
|
-
...(entry?.snippet ? { snippet: String(entry.snippet) } : {}),
|
|
92
|
-
}))
|
|
93
|
-
.filter((source) => source.url.length > 0);
|
|
94
|
-
return {
|
|
95
|
-
...(out?.answer ? { content: String(out.answer) } : {}),
|
|
96
|
-
sources,
|
|
97
|
-
truncated: false,
|
|
98
|
-
};
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* The rebon builtin fetch provider over rebon's WebFetch tool. The tool
|
|
104
|
-
* returns extracted text, not raw transport facts, so `statusCode` is the
|
|
105
|
-
* 200 the extraction implies and the body is `text` — a documented v1
|
|
106
|
-
* approximation, not a transport claim.
|
|
107
|
-
*/
|
|
108
|
-
const rebonBuiltinFetch = {
|
|
109
|
-
id: 'rebon',
|
|
110
|
-
available: () => true,
|
|
111
|
-
async fetch(request, _signal) {
|
|
112
|
-
const out = await invokeTool('WebFetch', { url: String(request?.url ?? '') });
|
|
113
|
-
const content =
|
|
114
|
-
typeof out === 'string' ? out : String(out?.content ?? out?.text ?? JSON.stringify(out));
|
|
115
|
-
return {
|
|
116
|
-
url: String(request?.url ?? ''),
|
|
117
|
-
statusCode: 200,
|
|
118
|
-
body: { kind: 'text', content },
|
|
119
|
-
truncated: false,
|
|
120
|
-
};
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export default class RebonWebRuntime extends Service {
|
|
125
|
-
constructor(ctx, options) {
|
|
126
|
-
super(ctx, 'web');
|
|
127
|
-
const config = options?.web ?? {};
|
|
128
|
-
const configured = (value) =>
|
|
129
|
-
typeof value === 'string' && value.trim().length > 0 ? value.trim() : 'rebon';
|
|
130
|
-
this.searchProviderId = configured(config?.searchProvider);
|
|
131
|
-
this.fetchProviderId = configured(config?.fetchProvider);
|
|
132
|
-
this.searchProviders = new Map([['rebon', rebonBuiltinSearch]]);
|
|
133
|
-
this.fetchProviders = new Map([['rebon', rebonBuiltinFetch]]);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
registerSearchProvider(provider) {
|
|
137
|
-
return this._register(this.searchProviders, 'search', provider);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
registerFetchProvider(provider) {
|
|
141
|
-
return this._register(this.fetchProviders, 'fetch', provider);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
_register(store, kind, provider) {
|
|
145
|
-
if (typeof provider?.id !== 'string' || provider.id.length === 0) {
|
|
146
|
-
throw new TypeError('a web provider must declare a non-empty string `id`');
|
|
147
|
-
}
|
|
148
|
-
if (store.has(provider.id)) {
|
|
149
|
-
throw new WebError(
|
|
150
|
-
`a web provider with id "${provider.id}" is already registered`,
|
|
151
|
-
'WEB_DUPLICATE_PROVIDER',
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
const runtime = this;
|
|
155
|
-
let available = false;
|
|
156
|
-
try {
|
|
157
|
-
available = !!provider.available();
|
|
158
|
-
} catch {}
|
|
159
|
-
// `available` is a registration-time snapshot: a static composition's
|
|
160
|
-
// configuration does not change under it. Documented v1 semantics,
|
|
161
|
-
// carried over unchanged.
|
|
162
|
-
const sink = sinkOf(this.ctx);
|
|
163
|
-
sink?.service(webTarget(kind, provider.id), (input, callCtx) =>
|
|
164
|
-
withCall(callCtx, () => runtime._serve(kind, provider.id, input, callCtx.signal)));
|
|
165
|
-
sink?.webProvider(kind, provider.id, available);
|
|
166
|
-
// Caller-fork RAII (the Service proxy binds this.ctx to the registrant):
|
|
167
|
-
// plugin disposal removes the provider, and a call arriving afterwards is
|
|
168
|
-
// refused rather than answered by a provider nobody can revoke.
|
|
169
|
-
return this.ctx.effect(() => {
|
|
170
|
-
store.set(provider.id, provider);
|
|
171
|
-
return () => {
|
|
172
|
-
store.delete(provider.id);
|
|
173
|
-
};
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** rebon-dispatched execution of one plugin provider (seat rules applied). */
|
|
178
|
-
async _serve(kind, id, input, signal) {
|
|
179
|
-
const store = kind === 'search' ? this.searchProviders : this.fetchProviders;
|
|
180
|
-
const provider = store.get(id);
|
|
181
|
-
if (provider === undefined) {
|
|
182
|
-
throw new WebError(
|
|
183
|
-
`web provider "${id}" is no longer registered in this composition`,
|
|
184
|
-
'WEB_PROVIDER_UNAVAILABLE',
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
if (kind === 'search') {
|
|
188
|
-
const request = {
|
|
189
|
-
query: String(input?.query ?? ''),
|
|
190
|
-
...(input?.maxResults !== undefined ? { maxResults: input.maxResults } : {}),
|
|
191
|
-
};
|
|
192
|
-
return capSources(await provider.search(request, signal), request.maxResults);
|
|
193
|
-
}
|
|
194
|
-
return provider.fetch({ url: String(input?.url ?? '') }, signal);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** dsh consumer API: run one search through the selected provider. */
|
|
198
|
-
async search(request, signal) {
|
|
199
|
-
const provider = resolveProvider(this.searchProviderId, this.searchProviders);
|
|
200
|
-
const result = await provider.search(request, signal);
|
|
201
|
-
return capSources(result, request?.maxResults);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** dsh consumer API: retrieve one URL through the selected provider. */
|
|
205
|
-
async fetch(request, signal) {
|
|
206
|
-
const provider = resolveProvider(this.fetchProviderId, this.fetchProviders);
|
|
207
|
-
return provider.fetch(request, signal);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
1
|
+
// The composition's `ctx.web` seat, on the plugin plane.
|
|
2
|
+
//
|
|
3
|
+
// Ported from `js/compose/web-runtime.js`. The dsh-facing surface is
|
|
4
|
+
// unchanged and 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 back;
|
|
9
|
+
// otherwise exactly one usable provider auto-selects. `maxResults` truncation
|
|
10
|
+
// (capSources) is enforced on the way out.
|
|
11
|
+
//
|
|
12
|
+
// Deployment default: the `rebon` builtin provider, backed by rebon's own
|
|
13
|
+
// WebSearch/WebFetch tools through `tool/invoke`, is always registered, and the
|
|
14
|
+
// configured id DEFAULTS to `"rebon"`. So a plugin provider takes over only
|
|
15
|
+
// when configured, and dsh's AMBIGUOUS state is unreachable unless the default
|
|
16
|
+
// is explicitly cleared. **A plugin whose tool reaches `ctx.web` therefore
|
|
17
|
+
// causes a `tool/invoke` of rebon's WebSearch, attributed to that plugin** —
|
|
18
|
+
// which is why such a plugin declares `WebSearch`/`WebFetch` in
|
|
19
|
+
// `invokableTools`. The attribution is the point: rebon can always answer who
|
|
20
|
+
// caused a tool to run.
|
|
21
|
+
//
|
|
22
|
+
// What changed: `emit('web/provider-registered', …)` and the shared serve pump
|
|
23
|
+
// are gone. A registered provider is *reported* — it appears in the plugin's
|
|
24
|
+
// load report as a service named `web:<kind>:<id>`, which is also how rebon
|
|
25
|
+
// dispatches into it.
|
|
26
|
+
import { Service } from 'cordis';
|
|
27
|
+
import { WebError } from '@deepseek-ai/dsh-web';
|
|
28
|
+
import { invokeTool, withCall } from './bridge.mjs';
|
|
29
|
+
import { sinkOf } from './registry.mjs';
|
|
30
|
+
|
|
31
|
+
/** The service-name namespace a plugin web provider is served under. */
|
|
32
|
+
export const webTarget = (kind, id) => `web:${kind}:${id}`;
|
|
33
|
+
|
|
34
|
+
/** dsh WebRuntime.resolveProvider, transcribed. */
|
|
35
|
+
function resolveProvider(configuredId, providers) {
|
|
36
|
+
if (configuredId !== undefined) {
|
|
37
|
+
const provider = providers.get(configuredId);
|
|
38
|
+
if (!provider) {
|
|
39
|
+
throw new WebError(
|
|
40
|
+
`configured web provider "${configuredId}" is not registered`,
|
|
41
|
+
'WEB_PROVIDER_CONFIGURED_MISSING',
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
if (!provider.available()) {
|
|
45
|
+
throw new WebError(
|
|
46
|
+
`configured web provider "${configuredId}" is registered but unavailable`,
|
|
47
|
+
'WEB_PROVIDER_CONFIGURED_UNAVAILABLE',
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return provider;
|
|
51
|
+
}
|
|
52
|
+
const usable = [...providers.values()].filter((provider) => {
|
|
53
|
+
try {
|
|
54
|
+
return !!provider.available();
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
if (usable.length === 0) {
|
|
60
|
+
throw new WebError('no usable web provider is registered', 'WEB_PROVIDER_UNAVAILABLE');
|
|
61
|
+
}
|
|
62
|
+
if (usable.length > 1) {
|
|
63
|
+
const ids = usable.map((provider) => provider.id).join(', ');
|
|
64
|
+
throw new WebError(
|
|
65
|
+
`multiple usable web providers are registered (${ids}); configure one explicitly`,
|
|
66
|
+
'WEB_PROVIDER_AMBIGUOUS',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return usable[0];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** dsh capSources: enforce `maxResults` on the way back. */
|
|
73
|
+
function capSources(result, maxResults) {
|
|
74
|
+
if (maxResults === undefined || (result.sources?.length ?? 0) <= maxResults) return result;
|
|
75
|
+
return { ...result, sources: result.sources.slice(0, maxResults), truncated: true };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The rebon builtin search provider: reaches rebon's WebSearch tool and maps
|
|
80
|
+
* the `{query, answer, results}` contract into dsh's result vocabulary.
|
|
81
|
+
*/
|
|
82
|
+
const rebonBuiltinSearch = {
|
|
83
|
+
id: 'rebon',
|
|
84
|
+
available: () => true,
|
|
85
|
+
async search(request, _signal) {
|
|
86
|
+
const out = await invokeTool('WebSearch', { query: String(request?.query ?? '') });
|
|
87
|
+
const sources = (out?.results ?? [])
|
|
88
|
+
.map((entry) => ({
|
|
89
|
+
url: String(entry?.url ?? ''),
|
|
90
|
+
...(entry?.title ? { title: String(entry.title) } : {}),
|
|
91
|
+
...(entry?.snippet ? { snippet: String(entry.snippet) } : {}),
|
|
92
|
+
}))
|
|
93
|
+
.filter((source) => source.url.length > 0);
|
|
94
|
+
return {
|
|
95
|
+
...(out?.answer ? { content: String(out.answer) } : {}),
|
|
96
|
+
sources,
|
|
97
|
+
truncated: false,
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The rebon builtin fetch provider over rebon's WebFetch tool. The tool
|
|
104
|
+
* returns extracted text, not raw transport facts, so `statusCode` is the
|
|
105
|
+
* 200 the extraction implies and the body is `text` — a documented v1
|
|
106
|
+
* approximation, not a transport claim.
|
|
107
|
+
*/
|
|
108
|
+
const rebonBuiltinFetch = {
|
|
109
|
+
id: 'rebon',
|
|
110
|
+
available: () => true,
|
|
111
|
+
async fetch(request, _signal) {
|
|
112
|
+
const out = await invokeTool('WebFetch', { url: String(request?.url ?? '') });
|
|
113
|
+
const content =
|
|
114
|
+
typeof out === 'string' ? out : String(out?.content ?? out?.text ?? JSON.stringify(out));
|
|
115
|
+
return {
|
|
116
|
+
url: String(request?.url ?? ''),
|
|
117
|
+
statusCode: 200,
|
|
118
|
+
body: { kind: 'text', content },
|
|
119
|
+
truncated: false,
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export default class RebonWebRuntime extends Service {
|
|
125
|
+
constructor(ctx, options) {
|
|
126
|
+
super(ctx, 'web');
|
|
127
|
+
const config = options?.web ?? {};
|
|
128
|
+
const configured = (value) =>
|
|
129
|
+
typeof value === 'string' && value.trim().length > 0 ? value.trim() : 'rebon';
|
|
130
|
+
this.searchProviderId = configured(config?.searchProvider);
|
|
131
|
+
this.fetchProviderId = configured(config?.fetchProvider);
|
|
132
|
+
this.searchProviders = new Map([['rebon', rebonBuiltinSearch]]);
|
|
133
|
+
this.fetchProviders = new Map([['rebon', rebonBuiltinFetch]]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
registerSearchProvider(provider) {
|
|
137
|
+
return this._register(this.searchProviders, 'search', provider);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
registerFetchProvider(provider) {
|
|
141
|
+
return this._register(this.fetchProviders, 'fetch', provider);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_register(store, kind, provider) {
|
|
145
|
+
if (typeof provider?.id !== 'string' || provider.id.length === 0) {
|
|
146
|
+
throw new TypeError('a web provider must declare a non-empty string `id`');
|
|
147
|
+
}
|
|
148
|
+
if (store.has(provider.id)) {
|
|
149
|
+
throw new WebError(
|
|
150
|
+
`a web provider with id "${provider.id}" is already registered`,
|
|
151
|
+
'WEB_DUPLICATE_PROVIDER',
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const runtime = this;
|
|
155
|
+
let available = false;
|
|
156
|
+
try {
|
|
157
|
+
available = !!provider.available();
|
|
158
|
+
} catch {}
|
|
159
|
+
// `available` is a registration-time snapshot: a static composition's
|
|
160
|
+
// configuration does not change under it. Documented v1 semantics,
|
|
161
|
+
// carried over unchanged.
|
|
162
|
+
const sink = sinkOf(this.ctx);
|
|
163
|
+
sink?.service(webTarget(kind, provider.id), (input, callCtx) =>
|
|
164
|
+
withCall(callCtx, () => runtime._serve(kind, provider.id, input, callCtx.signal)));
|
|
165
|
+
sink?.webProvider(kind, provider.id, available);
|
|
166
|
+
// Caller-fork RAII (the Service proxy binds this.ctx to the registrant):
|
|
167
|
+
// plugin disposal removes the provider, and a call arriving afterwards is
|
|
168
|
+
// refused rather than answered by a provider nobody can revoke.
|
|
169
|
+
return this.ctx.effect(() => {
|
|
170
|
+
store.set(provider.id, provider);
|
|
171
|
+
return () => {
|
|
172
|
+
store.delete(provider.id);
|
|
173
|
+
};
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** rebon-dispatched execution of one plugin provider (seat rules applied). */
|
|
178
|
+
async _serve(kind, id, input, signal) {
|
|
179
|
+
const store = kind === 'search' ? this.searchProviders : this.fetchProviders;
|
|
180
|
+
const provider = store.get(id);
|
|
181
|
+
if (provider === undefined) {
|
|
182
|
+
throw new WebError(
|
|
183
|
+
`web provider "${id}" is no longer registered in this composition`,
|
|
184
|
+
'WEB_PROVIDER_UNAVAILABLE',
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (kind === 'search') {
|
|
188
|
+
const request = {
|
|
189
|
+
query: String(input?.query ?? ''),
|
|
190
|
+
...(input?.maxResults !== undefined ? { maxResults: input.maxResults } : {}),
|
|
191
|
+
};
|
|
192
|
+
return capSources(await provider.search(request, signal), request.maxResults);
|
|
193
|
+
}
|
|
194
|
+
return provider.fetch({ url: String(input?.url ?? '') }, signal);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** dsh consumer API: run one search through the selected provider. */
|
|
198
|
+
async search(request, signal) {
|
|
199
|
+
const provider = resolveProvider(this.searchProviderId, this.searchProviders);
|
|
200
|
+
const result = await provider.search(request, signal);
|
|
201
|
+
return capSources(result, request?.maxResults);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** dsh consumer API: retrieve one URL through the selected provider. */
|
|
205
|
+
async fetch(request, signal) {
|
|
206
|
+
const provider = resolveProvider(this.fetchProviderId, this.fetchProviders);
|
|
207
|
+
return provider.fetch(request, signal);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@rebon/plugin-host",
|
|
3
|
-
"version": "0.0.0-private",
|
|
4
|
-
"private": true,
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "
|
|
7
|
-
"engines": { "node": ">=24.19.0 <25" },
|
|
8
|
-
"scripts": { "test": "node --test test/*.test.mjs" },
|
|
9
|
-
"bin": { "rebon-plugin-host": "./src/cli.mjs" },
|
|
10
|
-
"exports": { ".": "./src/sdk.mjs" }
|
|
11
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@rebon/plugin-host",
|
|
3
|
+
"version": "0.0.0-private",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"engines": { "node": ">=24.19.0 <25" },
|
|
8
|
+
"scripts": { "test": "node --test test/*.test.mjs" },
|
|
9
|
+
"bin": { "rebon-plugin-host": "./src/cli.mjs" },
|
|
10
|
+
"exports": { ".": "./src/sdk.mjs" }
|
|
11
|
+
}
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { cancel, notification, request, ProtocolError } from './protocol.mjs';
|
|
3
|
-
|
|
4
|
-
export function createPluginBridge({ trustedPluginId, scopeBinding, transport, ledger, idFactory = randomUUID }) {
|
|
5
|
-
if (typeof trustedPluginId !== 'string') throw new TypeError('trustedPluginId must be a string');
|
|
6
|
-
if (typeof idFactory !== 'function') throw new TypeError('idFactory must be a function');
|
|
7
|
-
const pending = new Map();
|
|
8
|
-
const makeIdentity = () => Object.freeze({ host_epoch: scopeBinding.hostEpoch, plugin_id: trustedPluginId,
|
|
9
|
-
scope_id: scopeBinding.scopeId, scope_generation: scopeBinding.scopeGeneration, call_id: idFactory() });
|
|
10
|
-
const call = (method, payload, { signal } = {}) => {
|
|
11
|
-
if (signal !== undefined && !(signal instanceof AbortSignal)) throw new TypeError('signal must be an AbortSignal');
|
|
12
|
-
const identity = makeIdentity();
|
|
13
|
-
ledger.register(identity, 'outbound', trustedPluginId);
|
|
14
|
-
let resolvePromise, rejectPromise;
|
|
15
|
-
const promise = new Promise((resolve, reject) => { resolvePromise = resolve; rejectPromise = reject; });
|
|
16
|
-
let settled = false; let cancelSent = false; let requestSent = false; let abortWanted = signal?.aborted ?? false; let listenerAdded = false;
|
|
17
|
-
const cleanup = () => {
|
|
18
|
-
if (listenerAdded) { signal.removeEventListener('abort', onAbort); listenerAdded = false; }
|
|
19
|
-
pending.delete(identity.call_id);
|
|
20
|
-
};
|
|
21
|
-
const settle = (action, value) => {
|
|
22
|
-
if (settled) return;
|
|
23
|
-
settled = true; cleanup(); action(value);
|
|
24
|
-
};
|
|
25
|
-
const sendCancel = () => {
|
|
26
|
-
if (settled || cancelSent || !requestSent) return Promise.resolve();
|
|
27
|
-
cancelSent = true;
|
|
28
|
-
return Promise.resolve().then(() => transport.send(cancel(identity))).catch((error) => settle(rejectPromise, error));
|
|
29
|
-
};
|
|
30
|
-
const onAbort = () => {
|
|
31
|
-
if (settled) return;
|
|
32
|
-
abortWanted = true;
|
|
33
|
-
if (requestSent) void sendCancel();
|
|
34
|
-
};
|
|
35
|
-
pending.set(identity.call_id, { identity, resolve: (value) => settle(resolvePromise, value), reject: (error) => settle(rejectPromise, error) });
|
|
36
|
-
if (signal && !signal.aborted) {
|
|
37
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
38
|
-
listenerAdded = true;
|
|
39
|
-
}
|
|
40
|
-
Promise.resolve().then(() => transport.send(request(identity, method, payload))).then(() => {
|
|
41
|
-
if (settled) return;
|
|
42
|
-
requestSent = true;
|
|
43
|
-
if (abortWanted) return sendCancel();
|
|
44
|
-
}).catch((error) => {
|
|
45
|
-
if (!settled) ledger.retire(identity);
|
|
46
|
-
settle(rejectPromise, error);
|
|
47
|
-
});
|
|
48
|
-
return promise;
|
|
49
|
-
};
|
|
50
|
-
const notify = (method, payload) => Promise.resolve().then(() => transport.send(notification(makeIdentity(), method, payload)));
|
|
51
|
-
const receive = (envelope) => {
|
|
52
|
-
const disposition = ledger.terminal(envelope, 'outbound');
|
|
53
|
-
if (disposition.stale) return;
|
|
54
|
-
const item = pending.get(envelope.call_id);
|
|
55
|
-
if (!item) throw new ProtocolError('unknown_call', 'no pending bridge call');
|
|
56
|
-
if (envelope.message.status === 'success') item.resolve(envelope.message.payload);
|
|
57
|
-
else {
|
|
58
|
-
const error = new ProtocolError(envelope.message.status, `call ended with ${envelope.message.status}`);
|
|
59
|
-
error.payload = envelope.message.payload;
|
|
60
|
-
item.reject(error);
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
return Object.freeze({ call, notify, receive, pendingCount: () => pending.size });
|
|
64
|
-
}
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { cancel, notification, request, ProtocolError } from './protocol.mjs';
|
|
3
|
+
|
|
4
|
+
export function createPluginBridge({ trustedPluginId, scopeBinding, transport, ledger, idFactory = randomUUID }) {
|
|
5
|
+
if (typeof trustedPluginId !== 'string') throw new TypeError('trustedPluginId must be a string');
|
|
6
|
+
if (typeof idFactory !== 'function') throw new TypeError('idFactory must be a function');
|
|
7
|
+
const pending = new Map();
|
|
8
|
+
const makeIdentity = () => Object.freeze({ host_epoch: scopeBinding.hostEpoch, plugin_id: trustedPluginId,
|
|
9
|
+
scope_id: scopeBinding.scopeId, scope_generation: scopeBinding.scopeGeneration, call_id: idFactory() });
|
|
10
|
+
const call = (method, payload, { signal } = {}) => {
|
|
11
|
+
if (signal !== undefined && !(signal instanceof AbortSignal)) throw new TypeError('signal must be an AbortSignal');
|
|
12
|
+
const identity = makeIdentity();
|
|
13
|
+
ledger.register(identity, 'outbound', trustedPluginId);
|
|
14
|
+
let resolvePromise, rejectPromise;
|
|
15
|
+
const promise = new Promise((resolve, reject) => { resolvePromise = resolve; rejectPromise = reject; });
|
|
16
|
+
let settled = false; let cancelSent = false; let requestSent = false; let abortWanted = signal?.aborted ?? false; let listenerAdded = false;
|
|
17
|
+
const cleanup = () => {
|
|
18
|
+
if (listenerAdded) { signal.removeEventListener('abort', onAbort); listenerAdded = false; }
|
|
19
|
+
pending.delete(identity.call_id);
|
|
20
|
+
};
|
|
21
|
+
const settle = (action, value) => {
|
|
22
|
+
if (settled) return;
|
|
23
|
+
settled = true; cleanup(); action(value);
|
|
24
|
+
};
|
|
25
|
+
const sendCancel = () => {
|
|
26
|
+
if (settled || cancelSent || !requestSent) return Promise.resolve();
|
|
27
|
+
cancelSent = true;
|
|
28
|
+
return Promise.resolve().then(() => transport.send(cancel(identity))).catch((error) => settle(rejectPromise, error));
|
|
29
|
+
};
|
|
30
|
+
const onAbort = () => {
|
|
31
|
+
if (settled) return;
|
|
32
|
+
abortWanted = true;
|
|
33
|
+
if (requestSent) void sendCancel();
|
|
34
|
+
};
|
|
35
|
+
pending.set(identity.call_id, { identity, resolve: (value) => settle(resolvePromise, value), reject: (error) => settle(rejectPromise, error) });
|
|
36
|
+
if (signal && !signal.aborted) {
|
|
37
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
38
|
+
listenerAdded = true;
|
|
39
|
+
}
|
|
40
|
+
Promise.resolve().then(() => transport.send(request(identity, method, payload))).then(() => {
|
|
41
|
+
if (settled) return;
|
|
42
|
+
requestSent = true;
|
|
43
|
+
if (abortWanted) return sendCancel();
|
|
44
|
+
}).catch((error) => {
|
|
45
|
+
if (!settled) ledger.retire(identity);
|
|
46
|
+
settle(rejectPromise, error);
|
|
47
|
+
});
|
|
48
|
+
return promise;
|
|
49
|
+
};
|
|
50
|
+
const notify = (method, payload) => Promise.resolve().then(() => transport.send(notification(makeIdentity(), method, payload)));
|
|
51
|
+
const receive = (envelope) => {
|
|
52
|
+
const disposition = ledger.terminal(envelope, 'outbound');
|
|
53
|
+
if (disposition.stale) return;
|
|
54
|
+
const item = pending.get(envelope.call_id);
|
|
55
|
+
if (!item) throw new ProtocolError('unknown_call', 'no pending bridge call');
|
|
56
|
+
if (envelope.message.status === 'success') item.resolve(envelope.message.payload);
|
|
57
|
+
else {
|
|
58
|
+
const error = new ProtocolError(envelope.message.status, `call ended with ${envelope.message.status}`);
|
|
59
|
+
error.payload = envelope.message.payload;
|
|
60
|
+
item.reject(error);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
return Object.freeze({ call, notify, receive, pendingCount: () => pending.size });
|
|
64
|
+
}
|