@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,326 +1,326 @@
|
|
|
1
|
-
// The composition's `ctx.llm` seat, on the plugin plane.
|
|
2
|
-
//
|
|
3
|
-
// Ported from `js/compose/llm-runtime.js`. The dsh-facing surface is
|
|
4
|
-
// unchanged — adapters extend `LlmAdapter`, register with
|
|
5
|
-
// `ctx.llm.registerAdapter(providers, adapter)`, and in-composition consumers
|
|
6
|
-
// (the agent loop) still go through `prepareCall` / `stream` — because that
|
|
7
|
-
// surface is dsh's contract and the transport underneath it is not dsh's
|
|
8
|
-
// business.
|
|
9
|
-
//
|
|
10
|
-
// What changed is everything that was transport:
|
|
11
|
-
//
|
|
12
|
-
// * **The pump is gone.** `_pump` parked on `op_llm_next` and `_dispatch`
|
|
13
|
-
// wrote chunks back with `op_llm_emit`. On the plugin plane a model turn
|
|
14
|
-
// arrives as an `llm/stream` request routed by provider, and its chunks go
|
|
15
|
-
// out on that call. What `registerAdapter` hands the registration sink is
|
|
16
|
-
// the handler for that method; there is nothing left to pump.
|
|
17
|
-
// * **Announcing is not a call.** `_announce` used to push each route into
|
|
18
|
-
// the kernel's `model-router` with `callService('model-router',
|
|
19
|
-
// 'register', …)`. Registration is a protocol declaration now: the route
|
|
20
|
-
// is reported by `plugin/load`, and its model catalog by the composition's
|
|
21
|
-
// own `compose` service.
|
|
22
|
-
// * **The disposer only forgets locally.** It used to issue the
|
|
23
|
-
// compensating `callService('model-router', 'unregister', …)`. A Cordis
|
|
24
|
-
// disposer is synchronous and a cross-process call is not, so that call
|
|
25
|
-
// could not survive the move — and does not need to. Revocation is what
|
|
26
|
-
// `plugin/unload` draining is for.
|
|
27
|
-
import { Service } from 'cordis';
|
|
28
|
-
import {
|
|
29
|
-
LlmError,
|
|
30
|
-
deepFreeze,
|
|
31
|
-
callConfigEquals,
|
|
32
|
-
errorChain,
|
|
33
|
-
resolveRetryPolicy,
|
|
34
|
-
} from '@deepseek-ai/dsh-llm';
|
|
35
|
-
import { withCall } from './bridge.mjs';
|
|
36
|
-
import { sinkOf } from './registry.mjs';
|
|
37
|
-
|
|
38
|
-
// Re-exported from the payload so a plugin extending `LlmAdapter` from
|
|
39
|
-
// `@deepseek-ai/dsh-llm` and the runtime checking one share a single class.
|
|
40
|
-
export { LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Normalize an adapter throw into dsh's terminal finish chunk
|
|
44
|
-
* (`adapterFailureChunk`, transcribed lean: LlmError keeps its facts,
|
|
45
|
-
* anything else flattens under UNKNOWN — the same rule the loop itself
|
|
46
|
-
* applies at its turn boundary).
|
|
47
|
-
*/
|
|
48
|
-
function failureChunk(error, signal) {
|
|
49
|
-
const failure = error instanceof LlmError
|
|
50
|
-
? error.failure
|
|
51
|
-
: { message: errorChain(error), code: 'UNKNOWN' };
|
|
52
|
-
return {
|
|
53
|
-
type: 'finish',
|
|
54
|
-
reason: signal?.aborted || failure.code === 'ABORTED'
|
|
55
|
-
? { kind: 'aborted', failure }
|
|
56
|
-
: { kind: 'error', failure },
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export default class RebonLlmRuntime extends Service {
|
|
61
|
-
constructor(ctx) {
|
|
62
|
-
super(ctx, 'llm');
|
|
63
|
-
this.routes = new Map(); // provider -> adapter
|
|
64
|
-
/** Catalog reads still in flight; `settle()` is what waits for them. */
|
|
65
|
-
this.pending = new Set();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
registerAdapter(providers, adapter) {
|
|
69
|
-
if (!providers?.length) {
|
|
70
|
-
throw new Error('an adapter must register at least one provider');
|
|
71
|
-
}
|
|
72
|
-
for (const provider of providers) {
|
|
73
|
-
if (this.routes.has(provider)) {
|
|
74
|
-
throw new Error(`an adapter for provider "${provider}" is already registered`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
const runtime = this;
|
|
78
|
-
// Read synchronously, before any await can interleave: which plugin is
|
|
79
|
-
// registering is a fact about *now*, not about whenever this resolves.
|
|
80
|
-
const sink = sinkOf(this.ctx);
|
|
81
|
-
for (const provider of providers) {
|
|
82
|
-
sink?.llm(provider, (request, callCtx) =>
|
|
83
|
-
withCall(callCtx, () => runtime.serve(provider, request, callCtx)));
|
|
84
|
-
}
|
|
85
|
-
this.ctx.effect(() => {
|
|
86
|
-
for (const provider of providers) {
|
|
87
|
-
runtime.routes.set(provider, adapter);
|
|
88
|
-
runtime._read(provider, adapter, sink);
|
|
89
|
-
}
|
|
90
|
-
return () => {
|
|
91
|
-
// Local only. What rebon knows about these routes was reported at
|
|
92
|
-
// load and is withdrawn by unload; a disposer cannot tell it anything
|
|
93
|
-
// because a disposer cannot await an answer.
|
|
94
|
-
for (const provider of providers) runtime.routes.delete(provider);
|
|
95
|
-
};
|
|
96
|
-
});
|
|
97
|
-
// dsh handle shape: route replacement exists on it, but a static
|
|
98
|
-
// composition never re-reads registration facts, so a call here is a
|
|
99
|
-
// consumer bug — refuse loudly instead of silently diverging from dsh.
|
|
100
|
-
return {
|
|
101
|
-
providers: [...providers],
|
|
102
|
-
replace() {
|
|
103
|
-
throw new Error('adapter route replacement is not supported in a static composition');
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* dsh surfaces these entries to its settings UI. The composition has no
|
|
110
|
-
* such surface; record them so diagnostics can list what a composition
|
|
111
|
-
* declared, and accept the call so unmodified dsh plugins load.
|
|
112
|
-
*/
|
|
113
|
-
registerConfigurableProviders(entries) {
|
|
114
|
-
this.configurableProviders ??= [];
|
|
115
|
-
for (const entry of entries ?? []) {
|
|
116
|
-
if (entry?.provider) {
|
|
117
|
-
this.configurableProviders.push({
|
|
118
|
-
provider: String(entry.provider),
|
|
119
|
-
displayName: entry.displayName === undefined ? undefined : String(entry.displayName),
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ---- JS consumer face ----
|
|
126
|
-
//
|
|
127
|
-
// These serve consumers INSIDE the composition (the agent loop); `serve()`
|
|
128
|
-
// serves requests from rebon. Routing is the same in-JS adapter table.
|
|
129
|
-
// v1 honesty boundary: no `llm/stream` middleware waterfall — dispatch goes
|
|
130
|
-
// straight to the owning adapter.
|
|
131
|
-
|
|
132
|
-
/** Resolve the owning adapter or refuse with dsh's NO_ADAPTER code. */
|
|
133
|
-
_registration(provider) {
|
|
134
|
-
const adapter = this.routes.get(provider);
|
|
135
|
-
if (!adapter) {
|
|
136
|
-
throw new LlmError(`no adapter registered for provider "${provider}"`, 'NO_ADAPTER');
|
|
137
|
-
}
|
|
138
|
-
return adapter;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* dsh `LlmRuntime.prepareCall`, transcribed: resolve the route's exact
|
|
143
|
-
* model, materialize adapter defaults (recorded in `adapterDefaults` so
|
|
144
|
-
* the loop's request header can re-resolve them later), and hand back a
|
|
145
|
-
* one-shot stream bound to this registration.
|
|
146
|
-
*/
|
|
147
|
-
async prepareCall(config, signal) {
|
|
148
|
-
const adapter = this._registration(config.provider);
|
|
149
|
-
const info = (await adapter.resolveModel(config.provider, config.model)) ?? {};
|
|
150
|
-
signal?.throwIfAborted?.();
|
|
151
|
-
const defaulted = config.maxTokens === undefined && info.defaultMaxTokens !== undefined
|
|
152
|
-
? { ...config, maxTokens: info.defaultMaxTokens }
|
|
153
|
-
: config;
|
|
154
|
-
const reasoning = info.reasoning;
|
|
155
|
-
const requested = defaulted.reasoningEffort;
|
|
156
|
-
let resolvedConfig = defaulted;
|
|
157
|
-
if (reasoning === undefined) {
|
|
158
|
-
if (requested !== undefined) {
|
|
159
|
-
throw new LlmError(
|
|
160
|
-
`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${requested}"`,
|
|
161
|
-
'UNSUPPORTED_REASONING_EFFORT',
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
} else {
|
|
165
|
-
const effective = requested ?? reasoning.defaultEffort;
|
|
166
|
-
if (effective !== undefined) {
|
|
167
|
-
if (!(reasoning.efforts ?? []).some((effort) => effort.id === effective)) {
|
|
168
|
-
throw new LlmError(
|
|
169
|
-
`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${effective}"`,
|
|
170
|
-
'UNSUPPORTED_REASONING_EFFORT',
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
if (requested !== effective) resolvedConfig = { ...defaulted, reasoningEffort: effective };
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
const frozen = deepFreeze(structuredClone(resolvedConfig));
|
|
177
|
-
const context = info.context === undefined
|
|
178
|
-
? undefined
|
|
179
|
-
: deepFreeze(structuredClone(info.context));
|
|
180
|
-
const adapterDefaults = deepFreeze({
|
|
181
|
-
...(config.reasoningEffort === undefined && frozen.reasoningEffort !== undefined
|
|
182
|
-
? { reasoningEffort: true }
|
|
183
|
-
: {}),
|
|
184
|
-
...(config.maxTokens === undefined && frozen.maxTokens !== undefined
|
|
185
|
-
? { maxTokens: true }
|
|
186
|
-
: {}),
|
|
187
|
-
});
|
|
188
|
-
const runtime = this;
|
|
189
|
-
let dispatched = false;
|
|
190
|
-
return Object.freeze({
|
|
191
|
-
config: frozen,
|
|
192
|
-
// dsh contract: providerRetryPolicy returns an ALREADY-RESOLVED
|
|
193
|
-
// policy (or undefined for the normal defaults) — never re-resolve.
|
|
194
|
-
retryPolicy: adapter.providerRetryPolicy(config.provider)
|
|
195
|
-
?? resolveRetryPolicy(undefined, `llm: provider "${config.provider}" retryPolicy`),
|
|
196
|
-
adapterDefaults,
|
|
197
|
-
...(context === undefined ? {} : { context }),
|
|
198
|
-
stream(options) {
|
|
199
|
-
if (dispatched) {
|
|
200
|
-
throw new LlmError('a prepared LLM call can only be dispatched once', 'INVALID_PREPARED_CALL');
|
|
201
|
-
}
|
|
202
|
-
if (!callConfigEquals(options, frozen)) {
|
|
203
|
-
throw new LlmError('prepared LLM call config changed before adapter dispatch', 'INVALID_PREPARED_CALL');
|
|
204
|
-
}
|
|
205
|
-
dispatched = true;
|
|
206
|
-
return runtime._consumerStream(options);
|
|
207
|
-
},
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/** Consumer fallback entry (the loop's NO_ADAPTER path): direct dispatch. */
|
|
212
|
-
stream(options) {
|
|
213
|
-
return this._consumerStream(options);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Adapter boundary for in-composition consumers: selection, dispatch, and
|
|
218
|
-
* iteration failures become one terminal failure chunk (dsh
|
|
219
|
-
* `adapterStream` semantics) so the loop's assembler/request-error path
|
|
220
|
-
* sees them instead of a raw throw.
|
|
221
|
-
*/
|
|
222
|
-
async *_consumerStream(options) {
|
|
223
|
-
let iterator;
|
|
224
|
-
try {
|
|
225
|
-
const adapter = this._registration(options.provider);
|
|
226
|
-
iterator = adapter.stream(options)[Symbol.asyncIterator]();
|
|
227
|
-
} catch (error) {
|
|
228
|
-
yield failureChunk(error, options.signal);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
while (true) {
|
|
232
|
-
let item;
|
|
233
|
-
try {
|
|
234
|
-
item = await iterator.next();
|
|
235
|
-
} catch (error) {
|
|
236
|
-
yield failureChunk(error, options.signal);
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
if (item.done) return;
|
|
240
|
-
yield item.value;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// ---- what rebon is told, and what rebon asks for ----
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Read one route's catalog for the load report.
|
|
248
|
-
*
|
|
249
|
-
* `listModels` is advisory in dsh and can take a network round trip, so a
|
|
250
|
-
* failure is recorded rather than raised: a provider with no catalog is
|
|
251
|
-
* still a working route whose callers name models explicitly. The route
|
|
252
|
-
* table is the liveness witness — an adapter disposed while its catalog was
|
|
253
|
-
* being read is no longer the live one, and its answer is dropped.
|
|
254
|
-
*/
|
|
255
|
-
_read(provider, adapter, sink) {
|
|
256
|
-
if (sink === undefined) return;
|
|
257
|
-
const task = (async () => {
|
|
258
|
-
let models = [];
|
|
259
|
-
try {
|
|
260
|
-
models = (await adapter.listModels(provider)) ?? [];
|
|
261
|
-
} catch (error) {
|
|
262
|
-
if (this.routes.get(provider) !== adapter) return;
|
|
263
|
-
sink.catalog(provider, { models: [], defaultModel: 'default', catalogError: String(error?.message ?? error) });
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
if (this.routes.get(provider) !== adapter) return;
|
|
267
|
-
// A key whose value is `undefined` is not JSON, and this report crosses
|
|
268
|
-
// the wire: an unknown context window is an absent field, not a present
|
|
269
|
-
// one holding nothing.
|
|
270
|
-
const entries = models
|
|
271
|
-
.filter((m) => m?.id)
|
|
272
|
-
.map((m) => {
|
|
273
|
-
const window = m.context?.window ?? m.contextWindow;
|
|
274
|
-
return { id: m.id, ...(window === undefined ? {} : { contextWindow: window }) };
|
|
275
|
-
});
|
|
276
|
-
sink.catalog(provider, {
|
|
277
|
-
models: entries,
|
|
278
|
-
// The catalog is advisory (dsh semantics): with an empty one the route
|
|
279
|
-
// still exists and callers name models explicitly; "default" is only
|
|
280
|
-
// the resolve fallback for bare `{provider}` lookups.
|
|
281
|
-
defaultModel: entries[0]?.id ?? 'default',
|
|
282
|
-
});
|
|
283
|
-
})();
|
|
284
|
-
this.pending.add(task);
|
|
285
|
-
void task.finally(() => this.pending.delete(task));
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/** Waits for every catalog read in flight, so the load report is complete. */
|
|
289
|
-
async settle() {
|
|
290
|
-
while (this.pending.size > 0) await Promise.all([...this.pending]);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Serves one `llm/stream` call: chunks out on the call, adapter throws left
|
|
295
|
-
* to become its terminal.
|
|
296
|
-
*
|
|
297
|
-
* Deliberately the raw adapter rather than `_consumerStream`: an in-JS
|
|
298
|
-
* consumer wants a failure flattened into a finish chunk it can assemble,
|
|
299
|
-
* whereas rebon wants the call to end as an error carrying the adapter's own
|
|
300
|
-
* machine code. Flattening here would report every failed turn as a
|
|
301
|
-
* successful one whose last chunk happens to say otherwise.
|
|
302
|
-
*/
|
|
303
|
-
async serve(provider, request, ctx) {
|
|
304
|
-
const adapter = this._registration(provider);
|
|
305
|
-
const options = {
|
|
306
|
-
provider,
|
|
307
|
-
model: request.model,
|
|
308
|
-
messages: request.messages ?? [],
|
|
309
|
-
system: request.system ?? undefined,
|
|
310
|
-
tools: request.tools ?? undefined,
|
|
311
|
-
maxTokens: request.maxTokens ?? undefined,
|
|
312
|
-
temperature: request.temperature ?? undefined,
|
|
313
|
-
stop: request.stop ?? undefined,
|
|
314
|
-
reasoningEffort: request.reasoningEffort ?? undefined,
|
|
315
|
-
sessionId: request.sessionId ?? undefined,
|
|
316
|
-
purpose: request.purpose ?? undefined,
|
|
317
|
-
signal: ctx.signal,
|
|
318
|
-
};
|
|
319
|
-
let count = 0;
|
|
320
|
-
for await (const chunk of adapter.stream(options)) {
|
|
321
|
-
await ctx.emit(chunk);
|
|
322
|
-
count += 1;
|
|
323
|
-
}
|
|
324
|
-
return { chunks: count };
|
|
325
|
-
}
|
|
326
|
-
}
|
|
1
|
+
// The composition's `ctx.llm` seat, on the plugin plane.
|
|
2
|
+
//
|
|
3
|
+
// Ported from `js/compose/llm-runtime.js`. The dsh-facing surface is
|
|
4
|
+
// unchanged — adapters extend `LlmAdapter`, register with
|
|
5
|
+
// `ctx.llm.registerAdapter(providers, adapter)`, and in-composition consumers
|
|
6
|
+
// (the agent loop) still go through `prepareCall` / `stream` — because that
|
|
7
|
+
// surface is dsh's contract and the transport underneath it is not dsh's
|
|
8
|
+
// business.
|
|
9
|
+
//
|
|
10
|
+
// What changed is everything that was transport:
|
|
11
|
+
//
|
|
12
|
+
// * **The pump is gone.** `_pump` parked on `op_llm_next` and `_dispatch`
|
|
13
|
+
// wrote chunks back with `op_llm_emit`. On the plugin plane a model turn
|
|
14
|
+
// arrives as an `llm/stream` request routed by provider, and its chunks go
|
|
15
|
+
// out on that call. What `registerAdapter` hands the registration sink is
|
|
16
|
+
// the handler for that method; there is nothing left to pump.
|
|
17
|
+
// * **Announcing is not a call.** `_announce` used to push each route into
|
|
18
|
+
// the kernel's `model-router` with `callService('model-router',
|
|
19
|
+
// 'register', …)`. Registration is a protocol declaration now: the route
|
|
20
|
+
// is reported by `plugin/load`, and its model catalog by the composition's
|
|
21
|
+
// own `compose` service.
|
|
22
|
+
// * **The disposer only forgets locally.** It used to issue the
|
|
23
|
+
// compensating `callService('model-router', 'unregister', …)`. A Cordis
|
|
24
|
+
// disposer is synchronous and a cross-process call is not, so that call
|
|
25
|
+
// could not survive the move — and does not need to. Revocation is what
|
|
26
|
+
// `plugin/unload` draining is for.
|
|
27
|
+
import { Service } from 'cordis';
|
|
28
|
+
import {
|
|
29
|
+
LlmError,
|
|
30
|
+
deepFreeze,
|
|
31
|
+
callConfigEquals,
|
|
32
|
+
errorChain,
|
|
33
|
+
resolveRetryPolicy,
|
|
34
|
+
} from '@deepseek-ai/dsh-llm';
|
|
35
|
+
import { withCall } from './bridge.mjs';
|
|
36
|
+
import { sinkOf } from './registry.mjs';
|
|
37
|
+
|
|
38
|
+
// Re-exported from the payload so a plugin extending `LlmAdapter` from
|
|
39
|
+
// `@deepseek-ai/dsh-llm` and the runtime checking one share a single class.
|
|
40
|
+
export { LlmAdapter } from '@deepseek-ai/dsh-llm';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Normalize an adapter throw into dsh's terminal finish chunk
|
|
44
|
+
* (`adapterFailureChunk`, transcribed lean: LlmError keeps its facts,
|
|
45
|
+
* anything else flattens under UNKNOWN — the same rule the loop itself
|
|
46
|
+
* applies at its turn boundary).
|
|
47
|
+
*/
|
|
48
|
+
function failureChunk(error, signal) {
|
|
49
|
+
const failure = error instanceof LlmError
|
|
50
|
+
? error.failure
|
|
51
|
+
: { message: errorChain(error), code: 'UNKNOWN' };
|
|
52
|
+
return {
|
|
53
|
+
type: 'finish',
|
|
54
|
+
reason: signal?.aborted || failure.code === 'ABORTED'
|
|
55
|
+
? { kind: 'aborted', failure }
|
|
56
|
+
: { kind: 'error', failure },
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default class RebonLlmRuntime extends Service {
|
|
61
|
+
constructor(ctx) {
|
|
62
|
+
super(ctx, 'llm');
|
|
63
|
+
this.routes = new Map(); // provider -> adapter
|
|
64
|
+
/** Catalog reads still in flight; `settle()` is what waits for them. */
|
|
65
|
+
this.pending = new Set();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
registerAdapter(providers, adapter) {
|
|
69
|
+
if (!providers?.length) {
|
|
70
|
+
throw new Error('an adapter must register at least one provider');
|
|
71
|
+
}
|
|
72
|
+
for (const provider of providers) {
|
|
73
|
+
if (this.routes.has(provider)) {
|
|
74
|
+
throw new Error(`an adapter for provider "${provider}" is already registered`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const runtime = this;
|
|
78
|
+
// Read synchronously, before any await can interleave: which plugin is
|
|
79
|
+
// registering is a fact about *now*, not about whenever this resolves.
|
|
80
|
+
const sink = sinkOf(this.ctx);
|
|
81
|
+
for (const provider of providers) {
|
|
82
|
+
sink?.llm(provider, (request, callCtx) =>
|
|
83
|
+
withCall(callCtx, () => runtime.serve(provider, request, callCtx)));
|
|
84
|
+
}
|
|
85
|
+
this.ctx.effect(() => {
|
|
86
|
+
for (const provider of providers) {
|
|
87
|
+
runtime.routes.set(provider, adapter);
|
|
88
|
+
runtime._read(provider, adapter, sink);
|
|
89
|
+
}
|
|
90
|
+
return () => {
|
|
91
|
+
// Local only. What rebon knows about these routes was reported at
|
|
92
|
+
// load and is withdrawn by unload; a disposer cannot tell it anything
|
|
93
|
+
// because a disposer cannot await an answer.
|
|
94
|
+
for (const provider of providers) runtime.routes.delete(provider);
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
// dsh handle shape: route replacement exists on it, but a static
|
|
98
|
+
// composition never re-reads registration facts, so a call here is a
|
|
99
|
+
// consumer bug — refuse loudly instead of silently diverging from dsh.
|
|
100
|
+
return {
|
|
101
|
+
providers: [...providers],
|
|
102
|
+
replace() {
|
|
103
|
+
throw new Error('adapter route replacement is not supported in a static composition');
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* dsh surfaces these entries to its settings UI. The composition has no
|
|
110
|
+
* such surface; record them so diagnostics can list what a composition
|
|
111
|
+
* declared, and accept the call so unmodified dsh plugins load.
|
|
112
|
+
*/
|
|
113
|
+
registerConfigurableProviders(entries) {
|
|
114
|
+
this.configurableProviders ??= [];
|
|
115
|
+
for (const entry of entries ?? []) {
|
|
116
|
+
if (entry?.provider) {
|
|
117
|
+
this.configurableProviders.push({
|
|
118
|
+
provider: String(entry.provider),
|
|
119
|
+
displayName: entry.displayName === undefined ? undefined : String(entry.displayName),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---- JS consumer face ----
|
|
126
|
+
//
|
|
127
|
+
// These serve consumers INSIDE the composition (the agent loop); `serve()`
|
|
128
|
+
// serves requests from rebon. Routing is the same in-JS adapter table.
|
|
129
|
+
// v1 honesty boundary: no `llm/stream` middleware waterfall — dispatch goes
|
|
130
|
+
// straight to the owning adapter.
|
|
131
|
+
|
|
132
|
+
/** Resolve the owning adapter or refuse with dsh's NO_ADAPTER code. */
|
|
133
|
+
_registration(provider) {
|
|
134
|
+
const adapter = this.routes.get(provider);
|
|
135
|
+
if (!adapter) {
|
|
136
|
+
throw new LlmError(`no adapter registered for provider "${provider}"`, 'NO_ADAPTER');
|
|
137
|
+
}
|
|
138
|
+
return adapter;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* dsh `LlmRuntime.prepareCall`, transcribed: resolve the route's exact
|
|
143
|
+
* model, materialize adapter defaults (recorded in `adapterDefaults` so
|
|
144
|
+
* the loop's request header can re-resolve them later), and hand back a
|
|
145
|
+
* one-shot stream bound to this registration.
|
|
146
|
+
*/
|
|
147
|
+
async prepareCall(config, signal) {
|
|
148
|
+
const adapter = this._registration(config.provider);
|
|
149
|
+
const info = (await adapter.resolveModel(config.provider, config.model)) ?? {};
|
|
150
|
+
signal?.throwIfAborted?.();
|
|
151
|
+
const defaulted = config.maxTokens === undefined && info.defaultMaxTokens !== undefined
|
|
152
|
+
? { ...config, maxTokens: info.defaultMaxTokens }
|
|
153
|
+
: config;
|
|
154
|
+
const reasoning = info.reasoning;
|
|
155
|
+
const requested = defaulted.reasoningEffort;
|
|
156
|
+
let resolvedConfig = defaulted;
|
|
157
|
+
if (reasoning === undefined) {
|
|
158
|
+
if (requested !== undefined) {
|
|
159
|
+
throw new LlmError(
|
|
160
|
+
`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${requested}"`,
|
|
161
|
+
'UNSUPPORTED_REASONING_EFFORT',
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
const effective = requested ?? reasoning.defaultEffort;
|
|
166
|
+
if (effective !== undefined) {
|
|
167
|
+
if (!(reasoning.efforts ?? []).some((effort) => effort.id === effective)) {
|
|
168
|
+
throw new LlmError(
|
|
169
|
+
`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${effective}"`,
|
|
170
|
+
'UNSUPPORTED_REASONING_EFFORT',
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (requested !== effective) resolvedConfig = { ...defaulted, reasoningEffort: effective };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const frozen = deepFreeze(structuredClone(resolvedConfig));
|
|
177
|
+
const context = info.context === undefined
|
|
178
|
+
? undefined
|
|
179
|
+
: deepFreeze(structuredClone(info.context));
|
|
180
|
+
const adapterDefaults = deepFreeze({
|
|
181
|
+
...(config.reasoningEffort === undefined && frozen.reasoningEffort !== undefined
|
|
182
|
+
? { reasoningEffort: true }
|
|
183
|
+
: {}),
|
|
184
|
+
...(config.maxTokens === undefined && frozen.maxTokens !== undefined
|
|
185
|
+
? { maxTokens: true }
|
|
186
|
+
: {}),
|
|
187
|
+
});
|
|
188
|
+
const runtime = this;
|
|
189
|
+
let dispatched = false;
|
|
190
|
+
return Object.freeze({
|
|
191
|
+
config: frozen,
|
|
192
|
+
// dsh contract: providerRetryPolicy returns an ALREADY-RESOLVED
|
|
193
|
+
// policy (or undefined for the normal defaults) — never re-resolve.
|
|
194
|
+
retryPolicy: adapter.providerRetryPolicy(config.provider)
|
|
195
|
+
?? resolveRetryPolicy(undefined, `llm: provider "${config.provider}" retryPolicy`),
|
|
196
|
+
adapterDefaults,
|
|
197
|
+
...(context === undefined ? {} : { context }),
|
|
198
|
+
stream(options) {
|
|
199
|
+
if (dispatched) {
|
|
200
|
+
throw new LlmError('a prepared LLM call can only be dispatched once', 'INVALID_PREPARED_CALL');
|
|
201
|
+
}
|
|
202
|
+
if (!callConfigEquals(options, frozen)) {
|
|
203
|
+
throw new LlmError('prepared LLM call config changed before adapter dispatch', 'INVALID_PREPARED_CALL');
|
|
204
|
+
}
|
|
205
|
+
dispatched = true;
|
|
206
|
+
return runtime._consumerStream(options);
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Consumer fallback entry (the loop's NO_ADAPTER path): direct dispatch. */
|
|
212
|
+
stream(options) {
|
|
213
|
+
return this._consumerStream(options);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Adapter boundary for in-composition consumers: selection, dispatch, and
|
|
218
|
+
* iteration failures become one terminal failure chunk (dsh
|
|
219
|
+
* `adapterStream` semantics) so the loop's assembler/request-error path
|
|
220
|
+
* sees them instead of a raw throw.
|
|
221
|
+
*/
|
|
222
|
+
async *_consumerStream(options) {
|
|
223
|
+
let iterator;
|
|
224
|
+
try {
|
|
225
|
+
const adapter = this._registration(options.provider);
|
|
226
|
+
iterator = adapter.stream(options)[Symbol.asyncIterator]();
|
|
227
|
+
} catch (error) {
|
|
228
|
+
yield failureChunk(error, options.signal);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
while (true) {
|
|
232
|
+
let item;
|
|
233
|
+
try {
|
|
234
|
+
item = await iterator.next();
|
|
235
|
+
} catch (error) {
|
|
236
|
+
yield failureChunk(error, options.signal);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (item.done) return;
|
|
240
|
+
yield item.value;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ---- what rebon is told, and what rebon asks for ----
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Read one route's catalog for the load report.
|
|
248
|
+
*
|
|
249
|
+
* `listModels` is advisory in dsh and can take a network round trip, so a
|
|
250
|
+
* failure is recorded rather than raised: a provider with no catalog is
|
|
251
|
+
* still a working route whose callers name models explicitly. The route
|
|
252
|
+
* table is the liveness witness — an adapter disposed while its catalog was
|
|
253
|
+
* being read is no longer the live one, and its answer is dropped.
|
|
254
|
+
*/
|
|
255
|
+
_read(provider, adapter, sink) {
|
|
256
|
+
if (sink === undefined) return;
|
|
257
|
+
const task = (async () => {
|
|
258
|
+
let models = [];
|
|
259
|
+
try {
|
|
260
|
+
models = (await adapter.listModels(provider)) ?? [];
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (this.routes.get(provider) !== adapter) return;
|
|
263
|
+
sink.catalog(provider, { models: [], defaultModel: 'default', catalogError: String(error?.message ?? error) });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (this.routes.get(provider) !== adapter) return;
|
|
267
|
+
// A key whose value is `undefined` is not JSON, and this report crosses
|
|
268
|
+
// the wire: an unknown context window is an absent field, not a present
|
|
269
|
+
// one holding nothing.
|
|
270
|
+
const entries = models
|
|
271
|
+
.filter((m) => m?.id)
|
|
272
|
+
.map((m) => {
|
|
273
|
+
const window = m.context?.window ?? m.contextWindow;
|
|
274
|
+
return { id: m.id, ...(window === undefined ? {} : { contextWindow: window }) };
|
|
275
|
+
});
|
|
276
|
+
sink.catalog(provider, {
|
|
277
|
+
models: entries,
|
|
278
|
+
// The catalog is advisory (dsh semantics): with an empty one the route
|
|
279
|
+
// still exists and callers name models explicitly; "default" is only
|
|
280
|
+
// the resolve fallback for bare `{provider}` lookups.
|
|
281
|
+
defaultModel: entries[0]?.id ?? 'default',
|
|
282
|
+
});
|
|
283
|
+
})();
|
|
284
|
+
this.pending.add(task);
|
|
285
|
+
void task.finally(() => this.pending.delete(task));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Waits for every catalog read in flight, so the load report is complete. */
|
|
289
|
+
async settle() {
|
|
290
|
+
while (this.pending.size > 0) await Promise.all([...this.pending]);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Serves one `llm/stream` call: chunks out on the call, adapter throws left
|
|
295
|
+
* to become its terminal.
|
|
296
|
+
*
|
|
297
|
+
* Deliberately the raw adapter rather than `_consumerStream`: an in-JS
|
|
298
|
+
* consumer wants a failure flattened into a finish chunk it can assemble,
|
|
299
|
+
* whereas rebon wants the call to end as an error carrying the adapter's own
|
|
300
|
+
* machine code. Flattening here would report every failed turn as a
|
|
301
|
+
* successful one whose last chunk happens to say otherwise.
|
|
302
|
+
*/
|
|
303
|
+
async serve(provider, request, ctx) {
|
|
304
|
+
const adapter = this._registration(provider);
|
|
305
|
+
const options = {
|
|
306
|
+
provider,
|
|
307
|
+
model: request.model,
|
|
308
|
+
messages: request.messages ?? [],
|
|
309
|
+
system: request.system ?? undefined,
|
|
310
|
+
tools: request.tools ?? undefined,
|
|
311
|
+
maxTokens: request.maxTokens ?? undefined,
|
|
312
|
+
temperature: request.temperature ?? undefined,
|
|
313
|
+
stop: request.stop ?? undefined,
|
|
314
|
+
reasoningEffort: request.reasoningEffort ?? undefined,
|
|
315
|
+
sessionId: request.sessionId ?? undefined,
|
|
316
|
+
purpose: request.purpose ?? undefined,
|
|
317
|
+
signal: ctx.signal,
|
|
318
|
+
};
|
|
319
|
+
let count = 0;
|
|
320
|
+
for await (const chunk of adapter.stream(options)) {
|
|
321
|
+
await ctx.emit(chunk);
|
|
322
|
+
count += 1;
|
|
323
|
+
}
|
|
324
|
+
return { chunks: count };
|
|
325
|
+
}
|
|
326
|
+
}
|