dsh-loop-engine-allinone 0.4.1 → 0.7.2

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/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
- * Empty for the shared `external` route: an engine with no declared catalog
163
- * has nothing to add to "the engine decides".
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 models;
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.1",
4
+ "version": "0.7.2",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
7
7
  },