@rebon/cli-linux-arm64 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 +63 -4
- package/THIRD_PARTY_NOTICES.txt +1122 -18
- package/bin/compose-runtime/payload/compose/shims/zod-lite.js +2 -2
- package/bin/compose-runtime/payload/vendor/cordis/LICENSE +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-turndown +21 -0
- package/bin/compose-runtime/payload/vendor/dsh/LICENSE-turndown-plugin-gfm +21 -0
- package/bin/compose-runtime/payload/vendor/schemastery/LICENSE +21 -0
- package/bin/compose-runtime/src/bridge.mjs +11 -13
- package/bin/compose-runtime/src/loop-assembly.mjs +6 -6
- package/bin/plugin-host/package.json +1 -1
- package/bin/plugin-host/src/cli.mjs +9 -9
- package/bin/plugin-host/src/ownership.mjs +5 -5
- package/bin/rebon +2 -2
- package/bin/rebon-boa-helper +0 -0
- package/bin/rebon-browser-mcp +0 -0
- package/bin/rebon-computer-use +0 -0
- package/bin/rebon-lsp-mcp +0 -0
- package/bin/sandbox-win +0 -0
- package/package.json +9 -2
- package/bin/compose-runtime/payload/compose/credentials-runtime.js +0 -38
- package/bin/compose-runtime/payload/compose/llm-runtime.js +0 -330
- package/bin/compose-runtime/payload/compose/loop-assembly.js +0 -149
- package/bin/compose-runtime/payload/compose/serve-dispatch.js +0 -82
- package/bin/compose-runtime/payload/compose/systemprompt-runtime.js +0 -74
- package/bin/compose-runtime/payload/compose/tools-runtime.js +0 -228
- package/bin/compose-runtime/payload/compose/web-runtime.js +0 -204
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
//
|
|
9
9
|
// Honesty boundary: NO validation semantics are provided. Any attempt to
|
|
10
10
|
// actually validate through a shimmed schema (`parse`/`safeParse`) throws
|
|
11
|
-
// loudly instead of silently accepting — if a projection consumer ever lands
|
|
12
|
-
//
|
|
11
|
+
// loudly instead of silently accepting — if a projection consumer ever lands,
|
|
12
|
+
// replace this shim with a real vendored zod.
|
|
13
13
|
|
|
14
14
|
function inert(kind) {
|
|
15
15
|
return {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021-present Shigma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 Dom Christie
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 Dom Christie
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021-present Shigma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// The composition's doorway to rebon, on the plugin plane.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// module's:
|
|
3
|
+
// The plane is across a process boundary, so there are no synchronous ops
|
|
4
|
+
// into an in-process kernel here. Three consequences follow, and they are the
|
|
5
|
+
// protocol's rather than this module's:
|
|
7
6
|
//
|
|
8
7
|
// * **Everything is async.** `callService` returned a value from an op; a
|
|
9
8
|
// `seat/call` is a request that has to be answered. The one place this
|
|
@@ -14,11 +13,11 @@
|
|
|
14
13
|
// handler. `AsyncLocalStorage` is what carries the handler's context down
|
|
15
14
|
// to code that has no way to be handed it — a dsh credential provider is
|
|
16
15
|
// called by an adapter that knows nothing about rebon.
|
|
17
|
-
// * **Registration is not a call.**
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
16
|
+
// * **Registration is not a call.** What a composition provides is
|
|
17
|
+
// reported once, in the `plugin/load` ready report, and revoked by
|
|
18
|
+
// `plugin/unload` draining. It has to be that shape: a compensating
|
|
19
|
+
// `unregister` would have to run from a cordis disposer, and a disposer
|
|
20
|
+
// cannot await the answer.
|
|
22
21
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
23
22
|
import { sessionOf } from './registry.mjs';
|
|
24
23
|
|
|
@@ -172,10 +171,9 @@ export const logger = Object.freeze(Object.fromEntries(LEVELS.map((level) => [
|
|
|
172
171
|
// session it is attached to is the handle that can still reach the seat.
|
|
173
172
|
//
|
|
174
173
|
// This is the same ladder `require()` climbs, with one difference that
|
|
175
|
-
// matters: it does not throw. A log line is not worth failing a plugin
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
// ever read when the host died.
|
|
174
|
+
// matters: it does not throw. A log line is not worth failing a plugin over,
|
|
175
|
+
// and a line that goes to stderr is only ever read when the host dies — the
|
|
176
|
+
// very silence this is fixing.
|
|
179
177
|
const ctx = calls.getStore() ?? sessionOf(options.via);
|
|
180
178
|
if (ctx === undefined) {
|
|
181
179
|
process.stderr.write(`[compose:${level}] ${line}\n`);
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
// 3. **Drive** — `loop:control` is the embedder's inbound face
|
|
15
15
|
// (followup / steer / cancel / status).
|
|
16
16
|
//
|
|
17
|
-
// Two
|
|
17
|
+
// Two consequences of the transport:
|
|
18
18
|
//
|
|
19
|
-
// * **The catalog is configuration, not a call.**
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
19
|
+
// * **The catalog is configuration, not a call.** There is no `describeTools`
|
|
20
|
+
// method on the plane, and there should not be: what rebon offers a model
|
|
21
|
+
// is settled when the composition is built, so rebon hands it over at
|
|
22
|
+
// load. It is a snapshot either way; asking would only make the moment it
|
|
23
|
+
// was taken harder to see.
|
|
24
24
|
// * **Events publish through a scope handle.** A loop produces turn events on
|
|
25
25
|
// its own schedule, with no inbound call to hang them on, so the assembly
|
|
26
26
|
// takes a handle on each open session and fans its events to whichever are
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": "0.0.0-private",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
7
|
"engines": { "node": ">=24.19.0 <25" },
|
|
8
8
|
"scripts": { "test": "node --test test/*.test.mjs" },
|
|
9
9
|
"bin": { "rebon-plugin-host": "./src/cli.mjs" },
|
|
@@ -88,16 +88,16 @@ export async function runHost({ input, output, error, loader }) {
|
|
|
88
88
|
/// A rejection nobody handled, said out loud instead of swallowed, and
|
|
89
89
|
/// attributed where the async chain reaches back to an entry.
|
|
90
90
|
///
|
|
91
|
-
/// Node does not stop this process for one, and
|
|
92
|
-
///
|
|
93
|
-
///
|
|
91
|
+
/// Node does not stop this process for one, and nothing else looks either: a
|
|
92
|
+
/// plugin could start an async task, have it fail, and leave no trace anywhere
|
|
93
|
+
/// rebon reads.
|
|
94
94
|
///
|
|
95
|
-
///
|
|
96
|
-
///
|
|
97
|
-
///
|
|
98
|
-
///
|
|
99
|
-
///
|
|
100
|
-
///
|
|
95
|
+
/// A failed promise does not carry who created it, but `plugin/load` runs
|
|
96
|
+
/// inside a store that survives timers, microtasks and awaits, so this handler
|
|
97
|
+
/// reads the owner straight out of it. What escapes the chain — through an
|
|
98
|
+
/// `EventEmitter`, a native callback or a third-party library — is still
|
|
99
|
+
/// `unattributed`, which is the truthful answer and is kept distinct from a
|
|
100
|
+
/// wrong one.
|
|
101
101
|
///
|
|
102
102
|
/// It never exits. A stray rejection in one plugin must not take down a host
|
|
103
103
|
/// other plugins are being served from; a load still in flight is failed by its
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Which entry a stray promise belonged to.
|
|
2
2
|
//
|
|
3
|
-
// Node does not stop this process for an unhandled rejection, and
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// Node does not stop this process for an unhandled rejection, and nothing else
|
|
4
|
+
// looks either: a plugin could start an async task, have it fail, and leave no
|
|
5
|
+
// trace anywhere rebon reads. Making it visible is not the same as attributing
|
|
6
|
+
// it — a failed promise does not carry who created it.
|
|
7
7
|
//
|
|
8
|
-
// Measured on Node 24.19.0,
|
|
8
|
+
// Measured on Node 24.19.0, an `AsyncLocalStorage` store survives
|
|
9
9
|
// `setTimeout`, `queueMicrotask` and `await`, and the `unhandledRejection`
|
|
10
10
|
// handler reads it directly — so wrapping each `plugin/load` is enough, with no
|
|
11
11
|
// per-promise `async_hooks` and therefore no standing cost.
|
package/bin/rebon
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/bin/rebon
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 51172672 bytes
|
|
4
|
+
sha256: d4f68d58548ab511df02f7300ddbfb84cf0e3686ca60235f35274c8bb1d260e2
|
package/bin/rebon-boa-helper
CHANGED
|
Binary file
|
package/bin/rebon-browser-mcp
CHANGED
|
Binary file
|
package/bin/rebon-computer-use
CHANGED
|
Binary file
|
package/bin/rebon-lsp-mcp
CHANGED
|
Binary file
|
package/bin/sandbox-win
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebon/cli-linux-arm64",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Agent cli for coding and more.",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/RebonAI/Rebon-Code.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://reboncode.ai",
|
|
6
11
|
"engines": {
|
|
7
12
|
"node": ">=20"
|
|
8
13
|
},
|
|
@@ -12,6 +17,8 @@
|
|
|
12
17
|
"files": [
|
|
13
18
|
"bin",
|
|
14
19
|
"README.md",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"NOTICE",
|
|
15
22
|
"THIRD_PARTY_NOTICES.txt",
|
|
16
23
|
"scripts"
|
|
17
24
|
],
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// dsh-compatible `ctx.credentials` seat for the composition host.
|
|
2
|
-
//
|
|
3
|
-
// dsh configuration carries credential *references* (environment-variable
|
|
4
|
-
// names); the provider owns the values. This provider resolves a reference
|
|
5
|
-
// through the kernel's fail-closed `credentials` JSON service (`resolveEnv`
|
|
6
|
-
// runs the `credentials/authorize` waterfall before touching the
|
|
7
|
-
// environment), so an unauthorized or unset reference is simply "absent" —
|
|
8
|
-
// the consumer's own missing-credential diagnosis then fires (dsh-llm's
|
|
9
|
-
// `MISSING_CREDENTIAL`), instead of this seat inventing one.
|
|
10
|
-
import { Service } from 'cordis';
|
|
11
|
-
import { callService } from 'rebon';
|
|
12
|
-
|
|
13
|
-
export default class RebonCredentials extends Service {
|
|
14
|
-
constructor(ctx) {
|
|
15
|
-
super(ctx, 'credentials');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** dsh CredentialProvider.resolve: `{value, source}` or undefined. */
|
|
19
|
-
async resolve(ref) {
|
|
20
|
-
try {
|
|
21
|
-
const out = callService('credentials', 'resolveEnv', { ref: String(ref) });
|
|
22
|
-
const value = out?.value;
|
|
23
|
-
if (typeof value === 'string' && value.length > 0) {
|
|
24
|
-
return { value, source: 'env' };
|
|
25
|
-
}
|
|
26
|
-
} catch {
|
|
27
|
-
// Fail-closed kernel refusal (no authorizer, unset variable, bad ref)
|
|
28
|
-
// all read as "not configured here".
|
|
29
|
-
}
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** dsh CredentialProvider.describe: facts only, never the value. */
|
|
34
|
-
async describe(ref) {
|
|
35
|
-
const hit = await this.resolve(ref);
|
|
36
|
-
return { configured: hit !== undefined, ...(hit ? { source: hit.source } : {}), writable: false };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
// Minimal dsh-compatible `ctx.llm` seat for the composition host.
|
|
2
|
-
//
|
|
3
|
-
// API shape follows dsh packages/llm: adapters extend `LlmAdapter` (only
|
|
4
|
-
// `stream(options)` is required) and register with
|
|
5
|
-
// `ctx.llm.registerAdapter(providers, adapter)`. Each registered route is
|
|
6
|
-
// mirrored into the kernel's model-router table (catalog fetched via the
|
|
7
|
-
// adapter's advisory `listModels`), and `llm/adapter-registered` /
|
|
8
|
-
// `llm/adapter-unregistered` kernel events let the Rust side bind this
|
|
9
|
-
// composition as the route's stream host. Model requests arrive over the
|
|
10
|
-
// host's llm ops (`op_llm_next`) and are dispatched to the owning adapter;
|
|
11
|
-
// cancellation aborts the per-request AbortController.
|
|
12
|
-
import { Service } from 'cordis';
|
|
13
|
-
import { callService, emit } from 'rebon';
|
|
14
|
-
import {
|
|
15
|
-
LlmError,
|
|
16
|
-
deepFreeze,
|
|
17
|
-
callConfigEquals,
|
|
18
|
-
errorChain,
|
|
19
|
-
resolveRetryPolicy,
|
|
20
|
-
} from './shims/dsh-llm.js';
|
|
21
|
-
// The base class every provider plugin extends. Re-exported here because
|
|
22
|
-
// that is where dsh consumers have always imported it from; it is defined
|
|
23
|
-
// apart from this module so importing it does not drag a host's transport in.
|
|
24
|
-
export { LlmAdapter } from './shims/llm-adapter.js';
|
|
25
|
-
|
|
26
|
-
const core = globalThis.Deno.core;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Normalize an adapter throw into dsh's terminal finish chunk
|
|
30
|
-
* (`adapterFailureChunk`, transcribed lean: LlmError keeps its facts,
|
|
31
|
-
* anything else flattens under UNKNOWN — the same rule the loop itself
|
|
32
|
-
* applies at its turn boundary).
|
|
33
|
-
*/
|
|
34
|
-
function failureChunk(error, signal) {
|
|
35
|
-
const failure = error instanceof LlmError
|
|
36
|
-
? error.failure
|
|
37
|
-
: { message: errorChain(error), code: 'UNKNOWN' };
|
|
38
|
-
return {
|
|
39
|
-
type: 'finish',
|
|
40
|
-
reason: signal?.aborted || failure.code === 'ABORTED'
|
|
41
|
-
? { kind: 'aborted', failure }
|
|
42
|
-
: { kind: 'error', failure },
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
export default class RebonLlmRuntime extends Service {
|
|
48
|
-
constructor(ctx, config) {
|
|
49
|
-
super(ctx, 'llm');
|
|
50
|
-
this.routes = new Map(); // provider -> adapter
|
|
51
|
-
this.aborts = new Map(); // stream id -> AbortController
|
|
52
|
-
// Per-loop hosts serve only in-composition consumers: no process-plane
|
|
53
|
-
// mirror, no adapter events (a process binding acting on them would
|
|
54
|
-
// tear down routes the shared composition still serves).
|
|
55
|
-
this.announce = config?.announce !== false;
|
|
56
|
-
void this._pump();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
registerAdapter(providers, adapter) {
|
|
60
|
-
if (!providers?.length) {
|
|
61
|
-
throw new Error('an adapter must register at least one provider');
|
|
62
|
-
}
|
|
63
|
-
for (const provider of providers) {
|
|
64
|
-
if (this.routes.has(provider)) {
|
|
65
|
-
throw new Error(`an adapter for provider "${provider}" is already registered`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const runtime = this;
|
|
69
|
-
// Owner captured in the synchronous half: _announce runs async, and
|
|
70
|
-
// by then the registering context must already be pinned down.
|
|
71
|
-
const owner = this.ctx[Symbol.for('rebon.composeOwner')] ?? '';
|
|
72
|
-
this.ctx.effect(() => {
|
|
73
|
-
for (const provider of providers) {
|
|
74
|
-
runtime.routes.set(provider, adapter);
|
|
75
|
-
if (runtime.announce) void runtime._announce(provider, adapter, owner);
|
|
76
|
-
}
|
|
77
|
-
return () => {
|
|
78
|
-
for (const provider of providers) {
|
|
79
|
-
runtime.routes.delete(provider);
|
|
80
|
-
if (!runtime.announce) continue;
|
|
81
|
-
try {
|
|
82
|
-
callService('model-router', 'unregister', { provider });
|
|
83
|
-
} catch {}
|
|
84
|
-
emit('llm/adapter-unregistered', { provider });
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
});
|
|
88
|
-
// dsh handle shape: route replacement exists on it, but a static
|
|
89
|
-
// composition never re-reads registration facts, so a call here is a
|
|
90
|
-
// consumer bug — refuse loudly instead of silently diverging from dsh.
|
|
91
|
-
return {
|
|
92
|
-
providers: [...providers],
|
|
93
|
-
replace() {
|
|
94
|
-
throw new Error('adapter route replacement is not supported in a static composition');
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* dsh surfaces these entries to its settings UI. The embedded runtime has
|
|
101
|
-
* no such surface; record them so diagnostics can list what a composition
|
|
102
|
-
* declared, and accept the call so unmodified dsh plugins load.
|
|
103
|
-
*/
|
|
104
|
-
registerConfigurableProviders(entries) {
|
|
105
|
-
this.configurableProviders ??= [];
|
|
106
|
-
for (const entry of entries ?? []) {
|
|
107
|
-
if (entry?.provider) {
|
|
108
|
-
this.configurableProviders.push({
|
|
109
|
-
provider: String(entry.provider),
|
|
110
|
-
displayName: entry.displayName === undefined ? undefined : String(entry.displayName),
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ---- JS consumer face (loop round) ----
|
|
117
|
-
//
|
|
118
|
-
// The pump below serves REQUESTS FROM RUST; the two methods here serve
|
|
119
|
-
// consumers INSIDE the composition (the agent loop). Routing is the same
|
|
120
|
-
// in-JS adapter table. v1 honesty boundary: no `llm/stream` middleware
|
|
121
|
-
// waterfall — dispatch goes straight to the owning adapter.
|
|
122
|
-
|
|
123
|
-
/** Resolve the owning adapter or refuse with dsh's NO_ADAPTER code. */
|
|
124
|
-
_registration(provider) {
|
|
125
|
-
const adapter = this.routes.get(provider);
|
|
126
|
-
if (!adapter) {
|
|
127
|
-
throw new LlmError(`no adapter registered for provider "${provider}"`, 'NO_ADAPTER');
|
|
128
|
-
}
|
|
129
|
-
return adapter;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* dsh `LlmRuntime.prepareCall`, transcribed: resolve the route's exact
|
|
134
|
-
* model, materialize adapter defaults (recorded in `adapterDefaults` so
|
|
135
|
-
* the loop's request header can re-resolve them later), and hand back a
|
|
136
|
-
* one-shot stream bound to this registration.
|
|
137
|
-
*/
|
|
138
|
-
async prepareCall(config, signal) {
|
|
139
|
-
const adapter = this._registration(config.provider);
|
|
140
|
-
const info = (await adapter.resolveModel(config.provider, config.model)) ?? {};
|
|
141
|
-
signal?.throwIfAborted?.();
|
|
142
|
-
const defaulted = config.maxTokens === undefined && info.defaultMaxTokens !== undefined
|
|
143
|
-
? { ...config, maxTokens: info.defaultMaxTokens }
|
|
144
|
-
: config;
|
|
145
|
-
const reasoning = info.reasoning;
|
|
146
|
-
const requested = defaulted.reasoningEffort;
|
|
147
|
-
let resolvedConfig = defaulted;
|
|
148
|
-
if (reasoning === undefined) {
|
|
149
|
-
if (requested !== undefined) {
|
|
150
|
-
throw new LlmError(
|
|
151
|
-
`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${requested}"`,
|
|
152
|
-
'UNSUPPORTED_REASONING_EFFORT',
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
} else {
|
|
156
|
-
const effective = requested ?? reasoning.defaultEffort;
|
|
157
|
-
if (effective !== undefined) {
|
|
158
|
-
if (!(reasoning.efforts ?? []).some((effort) => effort.id === effective)) {
|
|
159
|
-
throw new LlmError(
|
|
160
|
-
`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${effective}"`,
|
|
161
|
-
'UNSUPPORTED_REASONING_EFFORT',
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
if (requested !== effective) resolvedConfig = { ...defaulted, reasoningEffort: effective };
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
const frozen = deepFreeze(structuredClone(resolvedConfig));
|
|
168
|
-
const context = info.context === undefined
|
|
169
|
-
? undefined
|
|
170
|
-
: deepFreeze(structuredClone(info.context));
|
|
171
|
-
const adapterDefaults = deepFreeze({
|
|
172
|
-
...(config.reasoningEffort === undefined && frozen.reasoningEffort !== undefined
|
|
173
|
-
? { reasoningEffort: true }
|
|
174
|
-
: {}),
|
|
175
|
-
...(config.maxTokens === undefined && frozen.maxTokens !== undefined
|
|
176
|
-
? { maxTokens: true }
|
|
177
|
-
: {}),
|
|
178
|
-
});
|
|
179
|
-
const runtime = this;
|
|
180
|
-
let dispatched = false;
|
|
181
|
-
return Object.freeze({
|
|
182
|
-
config: frozen,
|
|
183
|
-
// dsh contract: providerRetryPolicy returns an ALREADY-RESOLVED
|
|
184
|
-
// policy (or undefined for the normal defaults) — never re-resolve.
|
|
185
|
-
retryPolicy: adapter.providerRetryPolicy(config.provider)
|
|
186
|
-
?? resolveRetryPolicy(undefined, `llm: provider "${config.provider}" retryPolicy`),
|
|
187
|
-
adapterDefaults,
|
|
188
|
-
...(context === undefined ? {} : { context }),
|
|
189
|
-
stream(options) {
|
|
190
|
-
if (dispatched) {
|
|
191
|
-
throw new LlmError('a prepared LLM call can only be dispatched once', 'INVALID_PREPARED_CALL');
|
|
192
|
-
}
|
|
193
|
-
if (!callConfigEquals(options, frozen)) {
|
|
194
|
-
throw new LlmError('prepared LLM call config changed before adapter dispatch', 'INVALID_PREPARED_CALL');
|
|
195
|
-
}
|
|
196
|
-
dispatched = true;
|
|
197
|
-
return runtime._consumerStream(options);
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/** Consumer fallback entry (the loop's NO_ADAPTER path): direct dispatch. */
|
|
203
|
-
stream(options) {
|
|
204
|
-
return this._consumerStream(options);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Adapter boundary for in-composition consumers: selection, dispatch, and
|
|
209
|
-
* iteration failures become one terminal failure chunk (dsh
|
|
210
|
-
* `adapterStream` semantics) so the loop's assembler/request-error path
|
|
211
|
-
* sees them instead of a raw throw.
|
|
212
|
-
*/
|
|
213
|
-
async *_consumerStream(options) {
|
|
214
|
-
let iterator;
|
|
215
|
-
try {
|
|
216
|
-
const adapter = this._registration(options.provider);
|
|
217
|
-
iterator = adapter.stream(options)[Symbol.asyncIterator]();
|
|
218
|
-
} catch (error) {
|
|
219
|
-
yield failureChunk(error, options.signal);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
while (true) {
|
|
223
|
-
let item;
|
|
224
|
-
try {
|
|
225
|
-
item = await iterator.next();
|
|
226
|
-
} catch (error) {
|
|
227
|
-
yield failureChunk(error, options.signal);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
if (item.done) return;
|
|
231
|
-
yield item.value;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Mirror one route into the kernel table, catalog included (advisory).
|
|
237
|
-
*
|
|
238
|
-
* `listModels` is an adapter call that can take as long as a network
|
|
239
|
-
* round-trip, and an unload may complete inside that await — the effect's
|
|
240
|
-
* disposer having already issued the compensating `unregister`. Announcing
|
|
241
|
-
* afterwards would resurrect the route of a plugin that is gone, with
|
|
242
|
-
* nothing left to tear it down. The route table is the liveness witness:
|
|
243
|
-
* the disposer deletes this provider's entry, so anything but our own
|
|
244
|
-
* adapter still sitting there means the registration we are announcing is
|
|
245
|
-
* no longer the live one.
|
|
246
|
-
*/
|
|
247
|
-
async _announce(provider, adapter, owner = '') {
|
|
248
|
-
let models = [];
|
|
249
|
-
try {
|
|
250
|
-
models = (await adapter.listModels(provider)) ?? [];
|
|
251
|
-
} catch {}
|
|
252
|
-
if (this.routes.get(provider) !== adapter) return;
|
|
253
|
-
const entries = models
|
|
254
|
-
.filter((m) => m?.id)
|
|
255
|
-
.map((m) => ({ id: m.id, contextWindow: m.context?.window ?? m.contextWindow }));
|
|
256
|
-
try {
|
|
257
|
-
callService('model-router', 'register', {
|
|
258
|
-
provider,
|
|
259
|
-
models: entries,
|
|
260
|
-
// The catalog is advisory (dsh semantics): with an empty one the
|
|
261
|
-
// route still exists and callers name models explicitly; "default"
|
|
262
|
-
// is only the resolve fallback for bare `{provider}` lookups.
|
|
263
|
-
defaultModel: entries[0]?.id ?? 'default',
|
|
264
|
-
}, owner);
|
|
265
|
-
} catch (e) {
|
|
266
|
-
emit('llm/adapter-error', { provider, error: String(e?.message ?? e) });
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
emit('llm/adapter-registered', { provider });
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/** Host instruction loop: dispatch requests, abort on cancel. */
|
|
273
|
-
async _pump() {
|
|
274
|
-
while (true) {
|
|
275
|
-
const instr = JSON.parse(await core.ops.op_llm_next());
|
|
276
|
-
if (instr.closed) break;
|
|
277
|
-
if (instr.kind === 'cancel') {
|
|
278
|
-
this.aborts.get(instr.id)?.abort();
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
void this._dispatch(instr.id, instr.request);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
async _dispatch(id, request) {
|
|
286
|
-
const send = (obj) => core.ops.op_llm_emit(id, JSON.stringify(obj));
|
|
287
|
-
const adapter = this.routes.get(request.provider);
|
|
288
|
-
if (!adapter) {
|
|
289
|
-
send({ err: `no adapter registered for provider route "${request.provider}"` });
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
const controller = new AbortController();
|
|
293
|
-
this.aborts.set(id, controller);
|
|
294
|
-
try {
|
|
295
|
-
const options = {
|
|
296
|
-
provider: request.provider,
|
|
297
|
-
model: request.model,
|
|
298
|
-
messages: request.messages ?? [],
|
|
299
|
-
system: request.system ?? undefined,
|
|
300
|
-
tools: request.tools ?? undefined,
|
|
301
|
-
maxTokens: request.maxTokens ?? undefined,
|
|
302
|
-
temperature: request.temperature ?? undefined,
|
|
303
|
-
stop: request.stop ?? undefined,
|
|
304
|
-
reasoningEffort: request.reasoningEffort ?? undefined,
|
|
305
|
-
sessionId: request.sessionId ?? undefined,
|
|
306
|
-
purpose: request.purpose ?? undefined,
|
|
307
|
-
signal: controller.signal,
|
|
308
|
-
};
|
|
309
|
-
for await (const chunk of adapter.stream(options)) {
|
|
310
|
-
send({ chunk });
|
|
311
|
-
}
|
|
312
|
-
send({ done: true });
|
|
313
|
-
} catch (e) {
|
|
314
|
-
// dsh contract: an adapter may throw on abort; normalize either way.
|
|
315
|
-
if (controller.signal.aborted) {
|
|
316
|
-
send({ chunk: { type: 'finish', reason: 'aborted' } });
|
|
317
|
-
send({ done: true });
|
|
318
|
-
} else {
|
|
319
|
-
// LlmError carries a stable machine code beside the message —
|
|
320
|
-
// surface it so the Rust side can route/report on it.
|
|
321
|
-
send({
|
|
322
|
-
err: String(e?.message ?? e),
|
|
323
|
-
...(typeof e?.code === 'string' && e.code.length > 0 ? { code: e.code } : {}),
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
} finally {
|
|
327
|
-
this.aborts.delete(id);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|