@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.
Files changed (98) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +10 -0
  3. package/README.md +203 -144
  4. package/THIRD_PARTY_NOTICES.txt +1232 -128
  5. package/bin/rebon.js +22 -22
  6. package/lib/windows-managed.js +390 -390
  7. package/package.json +9 -2
  8. package/payload/compose-runtime/package.json +17 -17
  9. package/payload/compose-runtime/payload/compose/shims/dsh-anonymous-user-id.js +10 -10
  10. package/payload/compose-runtime/payload/compose/shims/dsh-credentials.js +13 -13
  11. package/payload/compose-runtime/payload/compose/shims/dsh-launch-environment.js +21 -21
  12. package/payload/compose-runtime/payload/compose/shims/dsh-llm.js +313 -313
  13. package/payload/compose-runtime/payload/compose/shims/dsh-settings.js +43 -43
  14. package/payload/compose-runtime/payload/compose/shims/dsh-tools.js +20 -20
  15. package/payload/compose-runtime/payload/compose/shims/dsh-web.js +10 -10
  16. package/payload/compose-runtime/payload/compose/shims/llm-adapter.js +32 -32
  17. package/payload/compose-runtime/payload/compose/shims/zod-lite.js +49 -49
  18. package/payload/compose-runtime/payload/vendor/cordis/LICENSE +21 -0
  19. package/payload/compose-runtime/payload/vendor/cordis/index.js +1530 -1530
  20. package/payload/compose-runtime/payload/vendor/cosmokit/LICENSE +21 -21
  21. package/payload/compose-runtime/payload/vendor/cosmokit/index.mjs +357 -357
  22. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent +21 -21
  23. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent-loop +21 -21
  24. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-core +21 -21
  25. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-deepseek +21 -21
  26. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-logger-console +21 -21
  27. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-scope +21 -21
  28. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-session +21 -21
  29. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-system-prompt +21 -21
  30. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timeout +21 -21
  31. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timer +21 -21
  32. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-todo +21 -21
  33. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-web +21 -21
  34. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tools-schema +21 -21
  35. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown +21 -0
  36. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown-plugin-gfm +21 -0
  37. package/payload/compose-runtime/payload/vendor/dsh/LICENSE-web-search-exa +21 -21
  38. package/payload/compose-runtime/payload/vendor/dsh/agent-loop.js +1193 -1193
  39. package/payload/compose-runtime/payload/vendor/dsh/agent.js +714 -714
  40. package/payload/compose-runtime/payload/vendor/dsh/llm-core.js +269 -269
  41. package/payload/compose-runtime/payload/vendor/dsh/llm-deepseek.js +672 -672
  42. package/payload/compose-runtime/payload/vendor/dsh/logger-console.js +83 -83
  43. package/payload/compose-runtime/payload/vendor/dsh/scope.js +287 -287
  44. package/payload/compose-runtime/payload/vendor/dsh/session.js +1668 -1668
  45. package/payload/compose-runtime/payload/vendor/dsh/system-prompt.js +309 -309
  46. package/payload/compose-runtime/payload/vendor/dsh/timeout.js +100 -100
  47. package/payload/compose-runtime/payload/vendor/dsh/timer.js +128 -128
  48. package/payload/compose-runtime/payload/vendor/dsh/tool-todo.js +143 -143
  49. package/payload/compose-runtime/payload/vendor/dsh/tool-web.js +1527 -1527
  50. package/payload/compose-runtime/payload/vendor/dsh/tools-schema.js +849 -849
  51. package/payload/compose-runtime/payload/vendor/dsh/web-search-exa.js +122 -122
  52. package/payload/compose-runtime/payload/vendor/eventsource-parser/LICENSE +21 -21
  53. package/payload/compose-runtime/payload/vendor/eventsource-parser/index.js +177 -177
  54. package/payload/compose-runtime/payload/vendor/eventsource-parser/stream.js +48 -48
  55. package/payload/compose-runtime/payload/vendor/schemastery/LICENSE +21 -0
  56. package/payload/compose-runtime/payload/vendor/schemastery/index.mjs +656 -656
  57. package/payload/compose-runtime/payload-manifests.json +48 -48
  58. package/payload/compose-runtime/src/bridge.mjs +193 -195
  59. package/payload/compose-runtime/src/credentials-runtime.mjs +50 -50
  60. package/payload/compose-runtime/src/eventsource-stream.mjs +48 -48
  61. package/payload/compose-runtime/src/index.mjs +7 -7
  62. package/payload/compose-runtime/src/llm-runtime.mjs +326 -326
  63. package/payload/compose-runtime/src/loader.mjs +78 -78
  64. package/payload/compose-runtime/src/loop-assembly.mjs +237 -237
  65. package/payload/compose-runtime/src/payload.mjs +49 -49
  66. package/payload/compose-runtime/src/plugin.mjs +59 -59
  67. package/payload/compose-runtime/src/realm.mjs +223 -223
  68. package/payload/compose-runtime/src/registry.mjs +191 -191
  69. package/payload/compose-runtime/src/resolve.mjs +115 -115
  70. package/payload/compose-runtime/src/systemprompt-runtime.mjs +67 -67
  71. package/payload/compose-runtime/src/tools-runtime.mjs +239 -239
  72. package/payload/compose-runtime/src/web-runtime.mjs +209 -209
  73. package/payload/plugin-host/package.json +11 -11
  74. package/payload/plugin-host/src/bridge.mjs +64 -64
  75. package/payload/plugin-host/src/cli.mjs +147 -147
  76. package/payload/plugin-host/src/framing.mjs +113 -113
  77. package/payload/plugin-host/src/host.mjs +691 -691
  78. package/payload/plugin-host/src/json.mjs +208 -208
  79. package/payload/plugin-host/src/lifecycle.mjs +114 -114
  80. package/payload/plugin-host/src/loader.mjs +183 -183
  81. package/payload/plugin-host/src/methods.mjs +377 -377
  82. package/payload/plugin-host/src/ownership.mjs +57 -57
  83. package/payload/plugin-host/src/protocol.mjs +129 -129
  84. package/payload/plugin-host/src/sdk.mjs +7 -7
  85. package/payload/rebon-boa-helper.exe +0 -0
  86. package/payload/rebon-browser-mcp.exe +0 -0
  87. package/payload/rebon-computer-use.exe +0 -0
  88. package/payload/rebon-lsp-mcp.exe +0 -0
  89. package/payload/rebon.exe +2 -2
  90. package/payload/sandbox-win.exe +0 -0
  91. package/scripts/postinstall.js +112 -112
  92. package/payload/compose-runtime/payload/compose/credentials-runtime.js +0 -38
  93. package/payload/compose-runtime/payload/compose/llm-runtime.js +0 -330
  94. package/payload/compose-runtime/payload/compose/loop-assembly.js +0 -149
  95. package/payload/compose-runtime/payload/compose/serve-dispatch.js +0 -82
  96. package/payload/compose-runtime/payload/compose/systemprompt-runtime.js +0 -74
  97. package/payload/compose-runtime/payload/compose/tools-runtime.js +0 -228
  98. 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": "UNLICENSED",
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
+ }