dsh-loop-engine-allinone 0.4.1 → 0.7.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/CHANGELOG.md +88 -0
- package/README.md +171 -197
- package/README.zh.md +175 -157
- package/lib/client.js +28 -0
- package/lib/index.js +251 -108
- package/lib/invariant.js +28 -0
- package/lib/types/driver-acp/client.d.ts +20 -0
- package/lib/types/engine-registry.d.ts +35 -0
- package/lib/types/model-discovery.d.ts +98 -0
- package/lib/types/model-registry.d.ts +78 -0
- package/lib/types/provider-route.d.ts +8 -4
- package/package.json +1 -1
package/lib/invariant.js
CHANGED
|
@@ -49,6 +49,20 @@ var ENGINE_REGISTRY = {
|
|
|
49
49
|
loginHint: "run `codex login`, or set CODEX_API_KEY, or point `codexBin` at a signed-in install",
|
|
50
50
|
authPaths: [".codex/auth.json"],
|
|
51
51
|
localeKey: "engineCodex",
|
|
52
|
+
// Read out of the bundled CLI binary (0.149.1), which carries the ids its
|
|
53
|
+
// `thread/start` accepts; the driver passes the picked id straight through as
|
|
54
|
+
// `model`. Only the codex-family ids are listed: the binary also mentions
|
|
55
|
+
// `gpt-4.1`/`gpt-5.6` in contexts this build did not verify, and a wrong id
|
|
56
|
+
// fails loudly at the engine rather than silently falling back.
|
|
57
|
+
models: [
|
|
58
|
+
{ id: "gpt-5.3-codex", label: "GPT-5.3 Codex" },
|
|
59
|
+
{ id: "gpt-5.2-codex", label: "GPT-5.2 Codex" },
|
|
60
|
+
{ id: "gpt-5.1-codex-max", label: "GPT-5.1 Codex Max" },
|
|
61
|
+
{ id: "gpt-5.1-codex", label: "GPT-5.1 Codex" },
|
|
62
|
+
{ id: "gpt-5.4", label: "GPT-5.4" },
|
|
63
|
+
{ id: "gpt-5.2", label: "GPT-5.2" },
|
|
64
|
+
{ id: "gpt-5.1", label: "GPT-5.1" }
|
|
65
|
+
],
|
|
52
66
|
capabilities: {
|
|
53
67
|
streaming: true,
|
|
54
68
|
protocol: "jsonrpc",
|
|
@@ -92,6 +106,20 @@ var ENGINE_REGISTRY = {
|
|
|
92
106
|
loginHint: "run `kimi` once and sign in (or `kimi login`)",
|
|
93
107
|
authPaths: [".kimi-code/credentials.json", ".kimi-code/auth.json"],
|
|
94
108
|
localeKey: "engineKimi",
|
|
109
|
+
// Read out of `~/.kimi-code/config.toml`, which the Kimi Code app writes for
|
|
110
|
+
// the signed-in account: one `[models."kimi-code/<id>"]` block per model the
|
|
111
|
+
// account is provisioned for, with the vendor's own display name. The ids are
|
|
112
|
+
// what the app passes as `model`, so they are the right vocabulary for this
|
|
113
|
+
// driver's `session/set_model`. The catalog was NOT read off the ACP surface
|
|
114
|
+
// itself, because the `kimi` CLI is not installed on the machine this was
|
|
115
|
+
// measured on (only the desktop app is) — a stale id fails loudly at the
|
|
116
|
+
// engine rather than silently falling back.
|
|
117
|
+
models: [
|
|
118
|
+
{ id: "k3", label: "K3" },
|
|
119
|
+
{ id: "k3-256k", label: "K3-256k" },
|
|
120
|
+
{ id: "kimi-for-coding", label: "K2.8 Preview" },
|
|
121
|
+
{ id: "kimi-for-coding-highspeed", label: "K2.7 Code Highspeed" }
|
|
122
|
+
],
|
|
95
123
|
capabilities: {
|
|
96
124
|
streaming: true,
|
|
97
125
|
protocol: "acp",
|
|
@@ -112,6 +112,15 @@ export declare class AcpClient {
|
|
|
112
112
|
private readonly handlers;
|
|
113
113
|
/** Every terminal this client owns, so a disposed client cannot leak a process. */
|
|
114
114
|
private readonly terminals;
|
|
115
|
+
/**
|
|
116
|
+
* The model catalog the agent published on its last `session/new`.
|
|
117
|
+
*
|
|
118
|
+
* Free discovery: the frame was already paid for by a session the user opened, so
|
|
119
|
+
* keeping it costs nothing and spares the maintainer from re-declaring the list
|
|
120
|
+
* every time the vendor ships a model. `undefined` means the agent published no
|
|
121
|
+
* model option (every engine except the ACP ones that do).
|
|
122
|
+
*/
|
|
123
|
+
private lastCatalog;
|
|
115
124
|
/** Monotonic id source for the terminals the agent creates. */
|
|
116
125
|
private nextTerminalId;
|
|
117
126
|
/** Whether this client was sealed or its process exited. */
|
|
@@ -171,6 +180,17 @@ export declare class AcpClient {
|
|
|
171
180
|
* @returns the new session id.
|
|
172
181
|
*/
|
|
173
182
|
newSession(cwd: string, extra?: Record<string, unknown>): Promise<string>;
|
|
183
|
+
/**
|
|
184
|
+
* The model catalog this agent published on its last `session/new`, if any.
|
|
185
|
+
*
|
|
186
|
+
* Empty until the first session is opened, which is the honest state: the plugin
|
|
187
|
+
* does not know the catalog before an engine has been asked.
|
|
188
|
+
* @returns the discovered models, or undefined when the agent published none.
|
|
189
|
+
*/
|
|
190
|
+
discoveredModels(): readonly {
|
|
191
|
+
readonly id: string;
|
|
192
|
+
readonly label: string;
|
|
193
|
+
}[] | undefined;
|
|
174
194
|
/**
|
|
175
195
|
* Load (resume) an existing ACP session by id.
|
|
176
196
|
*
|
|
@@ -290,6 +290,28 @@ export declare const ENGINE_REGISTRY: {
|
|
|
290
290
|
readonly loginHint: "run `codex login`, or set CODEX_API_KEY, or point `codexBin` at a signed-in install";
|
|
291
291
|
readonly authPaths: readonly [".codex/auth.json"];
|
|
292
292
|
readonly localeKey: "engineCodex";
|
|
293
|
+
readonly models: readonly [{
|
|
294
|
+
readonly id: "gpt-5.3-codex";
|
|
295
|
+
readonly label: "GPT-5.3 Codex";
|
|
296
|
+
}, {
|
|
297
|
+
readonly id: "gpt-5.2-codex";
|
|
298
|
+
readonly label: "GPT-5.2 Codex";
|
|
299
|
+
}, {
|
|
300
|
+
readonly id: "gpt-5.1-codex-max";
|
|
301
|
+
readonly label: "GPT-5.1 Codex Max";
|
|
302
|
+
}, {
|
|
303
|
+
readonly id: "gpt-5.1-codex";
|
|
304
|
+
readonly label: "GPT-5.1 Codex";
|
|
305
|
+
}, {
|
|
306
|
+
readonly id: "gpt-5.4";
|
|
307
|
+
readonly label: "GPT-5.4";
|
|
308
|
+
}, {
|
|
309
|
+
readonly id: "gpt-5.2";
|
|
310
|
+
readonly label: "GPT-5.2";
|
|
311
|
+
}, {
|
|
312
|
+
readonly id: "gpt-5.1";
|
|
313
|
+
readonly label: "GPT-5.1";
|
|
314
|
+
}];
|
|
293
315
|
readonly capabilities: {
|
|
294
316
|
readonly streaming: true;
|
|
295
317
|
readonly protocol: "jsonrpc";
|
|
@@ -329,6 +351,19 @@ export declare const ENGINE_REGISTRY: {
|
|
|
329
351
|
readonly loginHint: "run `kimi` once and sign in (or `kimi login`)";
|
|
330
352
|
readonly authPaths: readonly [".kimi-code/credentials.json", ".kimi-code/auth.json"];
|
|
331
353
|
readonly localeKey: "engineKimi";
|
|
354
|
+
readonly models: readonly [{
|
|
355
|
+
readonly id: "k3";
|
|
356
|
+
readonly label: "K3";
|
|
357
|
+
}, {
|
|
358
|
+
readonly id: "k3-256k";
|
|
359
|
+
readonly label: "K3-256k";
|
|
360
|
+
}, {
|
|
361
|
+
readonly id: "kimi-for-coding";
|
|
362
|
+
readonly label: "K2.8 Preview";
|
|
363
|
+
}, {
|
|
364
|
+
readonly id: "kimi-for-coding-highspeed";
|
|
365
|
+
readonly label: "K2.7 Code Highspeed";
|
|
366
|
+
}];
|
|
332
367
|
readonly capabilities: {
|
|
333
368
|
readonly streaming: true;
|
|
334
369
|
readonly protocol: "acp";
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime model discovery: asking an engine what its subscription serves, without
|
|
3
|
+
* breaking the plugin's "booting the harness starts no external process" contract.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
*
|
|
7
|
+
* The registry DECLARES each engine's model catalog, because probing one means
|
|
8
|
+
* spawning the engine and a plugin that spawns ten CLIs at boot is a plugin nobody
|
|
9
|
+
* can start. The cost of that decision was real: when a vendor shipped a new model,
|
|
10
|
+
* the list in `engine-registry.ts` went stale, and a user saw the new model only
|
|
11
|
+
* after a plugin release. That is a maintenance burden on the maintainer and a
|
|
12
|
+
* stale menu for everyone else.
|
|
13
|
+
*
|
|
14
|
+
* This module removes the burden WITHOUT reintroducing the boot cost, by splitting
|
|
15
|
+
* discovery into two kinds:
|
|
16
|
+
*
|
|
17
|
+
* - **FREE discovery** — the answer is already on disk or already in a frame the
|
|
18
|
+
* driver received. Reading `~/.kimi-code/config.toml` spawns nothing; the
|
|
19
|
+
* model list inside a `session/new` response was paid for by a session the user
|
|
20
|
+
* opened anyway. These run whenever a catalog is asked for.
|
|
21
|
+
* - **COSTLY discovery** — it needs a new child process (`grok models`,
|
|
22
|
+
* `agy models`). Those are NOT run here: they belong behind an explicit refresh,
|
|
23
|
+
* which is the caller's decision rather than something a menu render triggers.
|
|
24
|
+
*
|
|
25
|
+
* ## Precedence, and why declared stays
|
|
26
|
+
*
|
|
27
|
+
* A discovered catalog wins over the declared one when it is non-empty, because it
|
|
28
|
+
* describes the account RIGHT NOW. The declared catalog remains as the fallback for
|
|
29
|
+
* when discovery is impossible (CLI absent, not signed in, subscription lapsed) —
|
|
30
|
+
* so the menu is never empty and a deployment with no config file still works.
|
|
31
|
+
*
|
|
32
|
+
* A stale DECLARED id fails loudly at the engine; a stale DISCOVERED one cannot,
|
|
33
|
+
* because it came from the engine itself.
|
|
34
|
+
*
|
|
35
|
+
* @module dsh-loop-engine-allinone/model-discovery
|
|
36
|
+
*/
|
|
37
|
+
import type { EngineModel } from './engine-registry.ts';
|
|
38
|
+
/**
|
|
39
|
+
* One discovered catalog, with where it came from.
|
|
40
|
+
*
|
|
41
|
+
* The provenance travels because the settings page shows it: "these came from the
|
|
42
|
+
* engine's own config" and "these are the plugin's declared fallback" are different
|
|
43
|
+
* claims, and a user debugging a missing model needs to know which one they are
|
|
44
|
+
* looking at.
|
|
45
|
+
*/
|
|
46
|
+
export interface DiscoveredCatalog {
|
|
47
|
+
/** The models the engine's own state says this account can use. */
|
|
48
|
+
readonly models: readonly EngineModel[];
|
|
49
|
+
/** Where they were read from, for the settings page and for diagnostics. */
|
|
50
|
+
readonly source: 'kimi-config' | 'acp-session' | 'codex-binary';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read a TOML `[models."<provider>/<id>"]` block set into model entries.
|
|
54
|
+
*
|
|
55
|
+
* The Kimi Code app writes one block per provisioned model, each carrying the
|
|
56
|
+
* vendor's own `display_name` and its `model` id:
|
|
57
|
+
*
|
|
58
|
+
* ```toml
|
|
59
|
+
* [models."kimi-code/k3"]
|
|
60
|
+
* model = "k3"
|
|
61
|
+
* display_name = "K3"
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* A minimal parser rather than a TOML dependency: this reads exactly two keys out of
|
|
65
|
+
* a file this project does not own, and a dependency for that would be a larger
|
|
66
|
+
* liability than the twenty lines below. Anything it does not recognize is skipped,
|
|
67
|
+
* so a config format change degrades to "no discovered models" (and the declared
|
|
68
|
+
* fallback applies) rather than throwing.
|
|
69
|
+
* @param text - the config file's contents.
|
|
70
|
+
* @returns the models, in file order, deduplicated by id.
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseKimiConfigModels(text: string): readonly EngineModel[];
|
|
73
|
+
/**
|
|
74
|
+
* Read a model catalog out of an ACP `session/new` result's `configOptions`.
|
|
75
|
+
*
|
|
76
|
+
* ACP agents publish their session options at `session/new`; a `model` option
|
|
77
|
+
* carries the engine's own composite ids plus the labels it wants rendered
|
|
78
|
+
* (measured on MiniMax Code: `m:minimax:MiniMax-M3:v:thinking` -> `M3 · thinking`).
|
|
79
|
+
* That list cost the user nothing extra - it arrived with a session they opened.
|
|
80
|
+
* @param configOptions - the `configOptions` array from a `session/new` result.
|
|
81
|
+
* @returns the models, or undefined when the frame carried no model option.
|
|
82
|
+
*/
|
|
83
|
+
export declare function modelsFromAcpConfigOptions(configOptions: unknown): readonly EngineModel[] | undefined;
|
|
84
|
+
/**
|
|
85
|
+
* Pick the catalog a surface should render for one engine.
|
|
86
|
+
*
|
|
87
|
+
* Discovered wins when non-empty; the declared list is the fallback. Both being
|
|
88
|
+
* absent is the engine's own "I have no catalog" answer, which the model menu
|
|
89
|
+
* renders as the shared `external` group.
|
|
90
|
+
* @param discovered - what runtime discovery found, if anything.
|
|
91
|
+
* @param declared - the registry's declaration, if any.
|
|
92
|
+
* @returns the models to render, and which of the two produced them.
|
|
93
|
+
*/
|
|
94
|
+
export declare function effectiveCatalog(discovered: DiscoveredCatalog | undefined, declared: readonly EngineModel[] | undefined): {
|
|
95
|
+
models: readonly EngineModel[];
|
|
96
|
+
origin: 'discovered' | 'declared' | 'none';
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=model-discovery.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime model discovery, wired to the provider routes.
|
|
3
|
+
*
|
|
4
|
+
* ## The timing problem this solves
|
|
5
|
+
*
|
|
6
|
+
* Routes are registered at APPLY (plugin boot), but discovery only produces an
|
|
7
|
+
* answer once an engine has run: an ACP catalog arrives in `session/new`, and a
|
|
8
|
+
* config file exists only after the app has been used. So boot cannot wait for it.
|
|
9
|
+
*
|
|
10
|
+
* The resolution is: boot registers each engine's route from what is knowable then
|
|
11
|
+
* (the declared catalog, or a FREE disk read), and a later discovery REPLACES that
|
|
12
|
+
* route's model list in place. `registerAdapter` is not re-called - the adapter
|
|
13
|
+
* object is asked for its models on every catalog build, so swapping the list it
|
|
14
|
+
* holds is what makes a newly discovered model appear without a restart.
|
|
15
|
+
*
|
|
16
|
+
* ## What is free, and what is not
|
|
17
|
+
*
|
|
18
|
+
* - `kimi`: read `~/.kimi-code/config.toml`. No process, safe at boot.
|
|
19
|
+
* - any ACP engine: the catalog in `session/new`'s `configOptions`, already paid
|
|
20
|
+
* for by the session the user opened.
|
|
21
|
+
* - `codex`: the ids are static in the bundled binary; declared, not probed.
|
|
22
|
+
* - `grok` / `agy`: would need `grok models` / `agy models`, i.e. a NEW child
|
|
23
|
+
* process. Not run here on purpose - the plugin still starts no external process
|
|
24
|
+
* to build a menu. Those stay declared.
|
|
25
|
+
*
|
|
26
|
+
* @module dsh-loop-engine-allinone/model-registry
|
|
27
|
+
*/
|
|
28
|
+
import { type EngineModel, type HostedEngineId } from './engine-registry.ts';
|
|
29
|
+
import { type DiscoveredCatalog } from './model-discovery.ts';
|
|
30
|
+
/**
|
|
31
|
+
* Read Kimi's catalog from the app's own config, if the file is there.
|
|
32
|
+
*
|
|
33
|
+
* Free: one `readFileSync`, no child process. A missing or unreadable file is not
|
|
34
|
+
* an error - it means "this host has not used Kimi Code", and the declared catalog
|
|
35
|
+
* applies.
|
|
36
|
+
* @returns the catalog, or undefined when there is nothing to read.
|
|
37
|
+
*/
|
|
38
|
+
export declare function discoverKimiCatalog(): DiscoveredCatalog | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Record a catalog an engine published at runtime (an ACP `session/new`).
|
|
41
|
+
* @param engine - the engine that published it.
|
|
42
|
+
* @param models - the models it published.
|
|
43
|
+
*/
|
|
44
|
+
export declare function recordDiscoveredCatalog(engine: HostedEngineId, models: readonly EngineModel[]): void;
|
|
45
|
+
/**
|
|
46
|
+
* The catalog to register a route with at boot: discovered-if-free, else declared.
|
|
47
|
+
*
|
|
48
|
+
* Only the FREE sources are consulted here, so apply never spawns anything.
|
|
49
|
+
* @param engine - the engine whose route is being registered.
|
|
50
|
+
* @returns the models to advertise, and where they came from.
|
|
51
|
+
*/
|
|
52
|
+
export declare function bootCatalog(engine: HostedEngineId): {
|
|
53
|
+
models: readonly EngineModel[];
|
|
54
|
+
origin: 'discovered' | 'declared' | 'none';
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* The catalog to render RIGHT NOW for one engine.
|
|
58
|
+
*
|
|
59
|
+
* A route asks this on every catalog build, so a discovery made after boot shows up
|
|
60
|
+
* without a restart.
|
|
61
|
+
* @param engine - the engine whose catalog is wanted.
|
|
62
|
+
* @returns the live models, discovered taking precedence over declared.
|
|
63
|
+
*/
|
|
64
|
+
export declare function liveCatalog(engine: HostedEngineId): readonly EngineModel[];
|
|
65
|
+
/** What one engine's catalog currently is, and where it came from. */
|
|
66
|
+
export interface CatalogReport {
|
|
67
|
+
readonly engine: HostedEngineId;
|
|
68
|
+
readonly models: readonly EngineModel[];
|
|
69
|
+
readonly origin: 'discovered' | 'declared' | 'none';
|
|
70
|
+
/** Where a discovered catalog was read from; absent for a declared one. */
|
|
71
|
+
readonly source?: DiscoveredCatalog['source'];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Every engine's catalog with its provenance, for the settings page and diagnostics.
|
|
75
|
+
* @returns one report per engine that has any catalog.
|
|
76
|
+
*/
|
|
77
|
+
export declare function catalogReport(): readonly CatalogReport[];
|
|
78
|
+
//# sourceMappingURL=model-registry.d.ts.map
|
|
@@ -159,10 +159,14 @@ export declare class HostedEngineRouteAdapter extends LlmAdapter {
|
|
|
159
159
|
/**
|
|
160
160
|
* The engine-native models this route advertises, beyond `default`.
|
|
161
161
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
162
|
+
* A THUNK rather than a fixed list, because the catalog can change after the
|
|
163
|
+
* route is registered: an ACP engine publishes its models in `session/new`, and a
|
|
164
|
+
* config file can gain a model when the vendor ships one. Reading it per catalog
|
|
165
|
+
* build is what makes a discovered model appear without a plugin restart (see
|
|
166
|
+
* `model-registry.ts`). A route with nothing to add returns an empty list, which
|
|
167
|
+
* is the shared `external` route's state.
|
|
164
168
|
*/
|
|
165
|
-
private readonly
|
|
169
|
+
private readonly modelsOf;
|
|
166
170
|
/**
|
|
167
171
|
* The name this route's group shows in the model menu.
|
|
168
172
|
*
|
|
@@ -178,7 +182,7 @@ export declare class HostedEngineRouteAdapter extends LlmAdapter {
|
|
|
178
182
|
* @param models - the engine-native models to advertise beside `default`.
|
|
179
183
|
* @param displayName - the group name shown in the model menu.
|
|
180
184
|
*/
|
|
181
|
-
constructor(contextWindow?: number, label?: string, models?: readonly EngineModel[], displayName?: string);
|
|
185
|
+
constructor(contextWindow?: number, label?: string, models?: readonly EngineModel[] | (() => readonly EngineModel[]), displayName?: string);
|
|
182
186
|
/**
|
|
183
187
|
* Name the route's one entry.
|
|
184
188
|
*
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-loop-engine-allinone",
|
|
3
3
|
"description": "Web-switchable agent loop engine selection for the DeepSeek Harness - out-of-tree plugin with a data-driven engine registry and a pluggable driver layer. Ten engines: Claude Code / Codex / Pi / Kimi Code / Antigravity / ZCode / Qoder CN / Hermes / Grok Build / MiniMax Code. All-in-one fork of kuun993/dsh-loop-engine.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
7
7
|
},
|