@fonlan/dsh-model-parameters 0.1.4 → 0.1.6

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/index.js CHANGED
@@ -6,13 +6,16 @@ import { join } from "node:path";
6
6
  /**
7
7
  * Plugin configuration and shared constants for @fonlan/dsh-model-parameters.
8
8
  *
9
- * The plugin owns the `model-parameters` settings namespace (stored in
10
- * settings.yaml) and OBSERVES the `llm-pi-ai` namespace: whenever provider
11
- * models are written there with missing capability fields (which is the norm
12
- * for a freshly synced gateway), it patches the missing fields from the cached
13
- * models.dev catalog.
9
+ * The plugin's own settings ARE its loader entry config (DSH >= 0.1.7): the
10
+ * profile entry whose id is `model-parameters` (see cordis.patch.yml) is
11
+ * validated with {@link Config}, the resolved document is handed to
12
+ * `apply(ctx, config)`, and the settings page is derived from that schema. The
13
+ * plugin OBSERVES the `llm-pi-ai` namespace: whenever provider models are
14
+ * written there with missing capability fields (which is the norm for a freshly
15
+ * synced gateway), it patches the missing fields from the cached models.dev
16
+ * catalog.
14
17
  */
15
- /** This plugin's own settings namespace. */
18
+ /** This plugin's own settings namespace: its profile entry id. */
16
19
  const PLUGIN_NS = "model-parameters";
17
20
  /** The namespace whose models this plugin completes: pi-ai provider profiles. */
18
21
  const TARGET_NS = "llm-pi-ai";
@@ -56,16 +59,17 @@ const REASONING_LEVELS = [
56
59
  "xhigh",
57
60
  "max"
58
61
  ];
62
+ /** The plugin's `Config` schema, exported for the loader and the settings page. */
59
63
  const Config = z.object({
60
- enabled: z.boolean().default(true),
61
- ttlDays: z.number().min(1).max(90).default(7),
62
- fillName: z.boolean().default(true),
63
- fillContext: z.boolean().default(true),
64
- fillMaxTokens: z.boolean().default(true),
65
- fillReasoning: z.boolean().default(true),
66
- fillInput: z.boolean().default(true),
67
- providerMap: z.dict(z.string()).default({}),
68
- officialProviders: z.array(z.string()).default([...DEFAULT_OFFICIAL_PROVIDERS])
64
+ enabled: z.boolean().default(true).volatile(),
65
+ ttlDays: z.number().min(1).max(90).default(7).volatile(),
66
+ fillName: z.boolean().default(true).volatile(),
67
+ fillContext: z.boolean().default(true).volatile(),
68
+ fillMaxTokens: z.boolean().default(true).volatile(),
69
+ fillReasoning: z.boolean().default(true).volatile(),
70
+ fillInput: z.boolean().default(true).volatile(),
71
+ providerMap: z.dict(z.string()).default({}).volatile(),
72
+ officialProviders: z.array(z.string()).default([...DEFAULT_OFFICIAL_PROVIDERS]).volatile()
69
73
  });
70
74
  const DEFAULT_CONFIG = {
71
75
  enabled: true,
@@ -78,6 +82,44 @@ const DEFAULT_CONFIG = {
78
82
  providerMap: {},
79
83
  officialProviders: [...DEFAULT_OFFICIAL_PROVIDERS]
80
84
  };
85
+ /**
86
+ * Read a config node that may be a plain value or a live (`.volatile()`) node.
87
+ *
88
+ * A plugin whose Config declares volatile fields receives a reactive config
89
+ * whose fields expose `get()`, while hand-written config objects and tests pass
90
+ * plain values. Both shapes are supported here so the same code path serves the
91
+ * settings page, `cordis.patch.yml`, and the unit tests.
92
+ */
93
+ function liveValue(node, fallback) {
94
+ if (node === void 0 || node === null) return fallback;
95
+ const getter = node.get;
96
+ if (typeof getter === "function") {
97
+ const value = getter.call(node);
98
+ return value === void 0 ? fallback : value;
99
+ }
100
+ return node;
101
+ }
102
+ /**
103
+ * Project the loader entry config into the plain document shape, per field
104
+ * falling back to {@link DEFAULT_CONFIG}. The config is re-read on EVERY call,
105
+ * so settings-page edits (which the loader swaps into the live cells in place)
106
+ * take effect immediately. Container fields are copied so callers can never
107
+ * mutate the live document through a returned reference.
108
+ */
109
+ function readConfig(config) {
110
+ const section = config ?? {};
111
+ return {
112
+ enabled: liveValue(section.enabled, DEFAULT_CONFIG.enabled),
113
+ ttlDays: liveValue(section.ttlDays, DEFAULT_CONFIG.ttlDays),
114
+ fillName: liveValue(section.fillName, DEFAULT_CONFIG.fillName),
115
+ fillContext: liveValue(section.fillContext, DEFAULT_CONFIG.fillContext),
116
+ fillMaxTokens: liveValue(section.fillMaxTokens, DEFAULT_CONFIG.fillMaxTokens),
117
+ fillReasoning: liveValue(section.fillReasoning, DEFAULT_CONFIG.fillReasoning),
118
+ fillInput: liveValue(section.fillInput, DEFAULT_CONFIG.fillInput),
119
+ providerMap: { ...liveValue(section.providerMap, DEFAULT_CONFIG.providerMap) },
120
+ officialProviders: [...liveValue(section.officialProviders, DEFAULT_CONFIG.officialProviders)]
121
+ };
122
+ }
81
123
  //#endregion
82
124
  //#region src/server/catalog.ts
83
125
  /**
@@ -309,6 +351,7 @@ const TARGET = TARGET_NS;
309
351
  const OWN = PLUGIN_NS;
310
352
  var ModelParametersService = class {
311
353
  ctx;
354
+ entryConfig;
312
355
  /**
313
356
  * The settings service value captured from the inject callback. The plugin
314
357
  * itself declares `inject: []` (settings is optional — headless profiles
@@ -317,42 +360,45 @@ var ModelParametersService = class {
317
360
  * a sibling fiber. The injected child ctx itself is only valid while its
318
361
  * fiber is active — the provider can be reloaded (and every dependent
319
362
  * fiber torn down) while an async reconcile is in flight — so all reads
320
- * and writes go through the captured service value, which is a plain
321
- * object and never walks the fiber chain.
363
+ * and writes of the `llm-pi-ai` namespace go through the captured service
364
+ * value, which is a plain object and never walks the fiber chain.
322
365
  */
323
366
  settings;
324
- scope;
325
367
  lastReport = null;
326
368
  catalog;
327
369
  reconciling = null;
328
- constructor(ctx) {
370
+ /**
371
+ * @param ctx the plugin context.
372
+ * @param entryConfig this plugin's own live entry config (the profile entry
373
+ * `model-parameters`): the resolved `Config` document, whose `.volatile()`
374
+ * fields are live cells that the loader swaps in place on every settings
375
+ * write. Read through {@link readConfig} on each access, never cached.
376
+ */
377
+ constructor(ctx, entryConfig) {
329
378
  this.ctx = ctx;
379
+ this.entryConfig = entryConfig;
330
380
  this.catalog = new CatalogManager(ctx.logger);
331
381
  }
332
382
  start() {
333
383
  this.ctx.inject(["settings"], (sctx) => {
334
384
  this.settings = sctx.settings;
335
- this.scope = this.settings.register(OWN, Config);
336
385
  this.reconcile();
337
386
  });
338
- this.ctx.on("settings/updated", (ns) => {
387
+ this.ctx.on("settings/document-updated", (ns) => {
339
388
  if (ns !== TARGET) return;
340
389
  this.reconcile();
341
390
  });
342
391
  }
343
392
  stop() {
344
- this.scope = void 0;
345
393
  this.settings = void 0;
346
394
  }
395
+ /**
396
+ * The plugin's current settings, projected from the live entry config.
397
+ * Works with no settings service at all (headless), which is why the plain
398
+ * {@link DEFAULT_CONFIG} fallback lives in {@link readConfig}.
399
+ */
347
400
  config() {
348
- if (this.scope !== void 0) try {
349
- return this.scope.get();
350
- } catch {}
351
- return {
352
- ...DEFAULT_CONFIG,
353
- providerMap: {},
354
- officialProviders: [...DEFAULT_CONFIG.officialProviders]
355
- };
401
+ return readConfig(this.entryConfig);
356
402
  }
357
403
  catalogSnapshot() {
358
404
  return this.catalog.snapshotOf();
@@ -365,14 +411,21 @@ var ModelParametersService = class {
365
411
  await this.catalog.refresh(this.config().ttlDays);
366
412
  await this.reconcile();
367
413
  }
368
- /** Update plugin config toggles from the settings page. */
414
+ /**
415
+ * Update the plugin's own settings from the settings page. `dsh-settings`
416
+ * addresses the plugin's loader entry by its id and refuses any path the
417
+ * `Config` schema did not mark volatile — every field here is volatile.
418
+ */
369
419
  async updateConfig(patch) {
370
- if (this.scope === void 0) throw new Error("model-parameters: settings service is not available in this profile");
371
- const current = this.config();
372
- await this.scope.update({
373
- ...current,
374
- ...patch
375
- });
420
+ const settings = this.settings;
421
+ if (settings === void 0) throw new Error("model-parameters: settings service is not available in this profile");
422
+ const { providerMap, ...rest } = patch;
423
+ if (Object.keys(rest).length > 0) await settings.update(OWN, rest);
424
+ if (providerMap !== void 0) await settings.mutate(OWN, [{
425
+ op: "set",
426
+ path: ["providerMap"],
427
+ value: providerMap
428
+ }]);
376
429
  }
377
430
  /**
378
431
  * Fill missing fields on every provider model from the catalog. Serialized:
@@ -622,8 +675,8 @@ function stateOf(service) {
622
675
  //#region src/index.ts
623
676
  const name = "@fonlan/dsh-model-parameters";
624
677
  const inject = [];
625
- function apply(ctx) {
626
- const service = new ModelParametersService(ctx);
678
+ function apply(ctx, config = {}) {
679
+ const service = new ModelParametersService(ctx, config);
627
680
  ctx.effect(() => {
628
681
  service.start();
629
682
  return () => service.stop();
@@ -6,15 +6,15 @@
6
6
  * tokens / reasoning efforts / input modalities (the norm for a freshly
7
7
  * synced gateway), it fills the missing fields from a cached models.dev
8
8
  * catalog (TTL-refreshed), and serves the fenced JSON API the web settings
9
- * page calls. The plugin's own toggles live in the `model-parameters`
10
- * settings namespace (settings.yaml).
9
+ * page calls. The plugin's own toggles are its loader entry config (the
10
+ * `model-parameters` profile entry, schema {@link Config}).
11
11
  */
12
12
  import type { Context } from '@deepseek-ai/cordis';
13
- import { Config } from './shared/config.js';
13
+ import { Config, type ModelParametersLiveConfig } from './shared/config.js';
14
14
  import { CatalogManager, acceptedInputModalities, identityReasoningEfforts, type CatalogEntry, type CatalogSnapshot } from './server/catalog.js';
15
15
  export declare const name = "@fonlan/dsh-model-parameters";
16
16
  export declare const inject: string[];
17
17
  export { Config };
18
18
  export { CatalogManager, acceptedInputModalities, identityReasoningEfforts };
19
19
  export type { CatalogEntry, CatalogSnapshot };
20
- export declare function apply(ctx: Context): void;
20
+ export declare function apply(ctx: Context, config?: ModelParametersLiveConfig): void;
@@ -1,19 +1,19 @@
1
1
  /**
2
- * The model-parameters service: owns the plugin settings namespace, watches
3
- * the `llm-pi-ai` namespace for provider model writes, reconciles missing
4
- * fields from the cached models.dev catalog, and tracks a fill report.
2
+ * The model-parameters service: completes missing model fields in the
3
+ * `llm-pi-ai` settings namespace from the cached models.dev catalog and tracks
4
+ * a fill report. Its own settings ARE this plugin's loader entry config (see
5
+ * shared/config.ts), so there is no per-plugin settings scope to own here.
5
6
  *
6
7
  * Trigger model:
7
8
  * - any committed `llm-pi-ai` settings change (settings UI apply, manual
8
- * settings.yaml edit published by the file provider, model-router-style
9
- * external writes) runs a reconcile;
9
+ * profile-patch edit, model-router-style external writes) runs a reconcile;
10
10
  * - plugin start runs a backfill reconcile over the existing configuration.
11
11
  * Reconcile is fill-only: it never overwrites a present field, and it writes
12
12
  * nothing when nothing is missing — so its own writes cannot re-trigger a
13
13
  * second patch (the next event sees the fields present and produces no ops).
14
14
  */
15
15
  import type { Context } from '@deepseek-ai/cordis';
16
- import { type ModelParametersConfig } from '../shared/config.js';
16
+ import { type ModelParametersConfig, type ModelParametersLiveConfig } from '../shared/config.js';
17
17
  import { type CatalogSnapshot } from './catalog.js';
18
18
  /** One reconcile run's outcome, surfaced to the settings page. */
19
19
  export interface FillReport {
@@ -27,6 +27,7 @@ export interface FillReport {
27
27
  }
28
28
  export declare class ModelParametersService {
29
29
  private readonly ctx;
30
+ private readonly entryConfig;
30
31
  /**
31
32
  * The settings service value captured from the inject callback. The plugin
32
33
  * itself declares `inject: []` (settings is optional — headless profiles
@@ -35,23 +36,38 @@ export declare class ModelParametersService {
35
36
  * a sibling fiber. The injected child ctx itself is only valid while its
36
37
  * fiber is active — the provider can be reloaded (and every dependent
37
38
  * fiber torn down) while an async reconcile is in flight — so all reads
38
- * and writes go through the captured service value, which is a plain
39
- * object and never walks the fiber chain.
39
+ * and writes of the `llm-pi-ai` namespace go through the captured service
40
+ * value, which is a plain object and never walks the fiber chain.
40
41
  */
41
42
  private settings;
42
- private scope;
43
43
  private lastReport;
44
44
  private readonly catalog;
45
45
  private reconciling;
46
- constructor(ctx: Context);
46
+ /**
47
+ * @param ctx the plugin context.
48
+ * @param entryConfig this plugin's own live entry config (the profile entry
49
+ * `model-parameters`): the resolved `Config` document, whose `.volatile()`
50
+ * fields are live cells that the loader swaps in place on every settings
51
+ * write. Read through {@link readConfig} on each access, never cached.
52
+ */
53
+ constructor(ctx: Context, entryConfig: ModelParametersLiveConfig | undefined);
47
54
  start(): void;
48
55
  stop(): void;
56
+ /**
57
+ * The plugin's current settings, projected from the live entry config.
58
+ * Works with no settings service at all (headless), which is why the plain
59
+ * {@link DEFAULT_CONFIG} fallback lives in {@link readConfig}.
60
+ */
49
61
  config(): ModelParametersConfig;
50
62
  catalogSnapshot(): CatalogSnapshot;
51
63
  report(): FillReport | null;
52
64
  /** Force a catalog refresh, then reconcile and report. */
53
65
  refreshAndReconcile(): Promise<void>;
54
- /** Update plugin config toggles from the settings page. */
66
+ /**
67
+ * Update the plugin's own settings from the settings page. `dsh-settings`
68
+ * addresses the plugin's loader entry by its id and refuses any path the
69
+ * `Config` schema did not mark volatile — every field here is volatile.
70
+ */
55
71
  updateConfig(patch: Partial<ModelParametersConfig>): Promise<void>;
56
72
  /**
57
73
  * Fill missing fields on every provider model from the catalog. Serialized:
@@ -1,14 +1,17 @@
1
1
  /**
2
2
  * Plugin configuration and shared constants for @fonlan/dsh-model-parameters.
3
3
  *
4
- * The plugin owns the `model-parameters` settings namespace (stored in
5
- * settings.yaml) and OBSERVES the `llm-pi-ai` namespace: whenever provider
6
- * models are written there with missing capability fields (which is the norm
7
- * for a freshly synced gateway), it patches the missing fields from the cached
8
- * models.dev catalog.
4
+ * The plugin's own settings ARE its loader entry config (DSH >= 0.1.7): the
5
+ * profile entry whose id is `model-parameters` (see cordis.patch.yml) is
6
+ * validated with {@link Config}, the resolved document is handed to
7
+ * `apply(ctx, config)`, and the settings page is derived from that schema. The
8
+ * plugin OBSERVES the `llm-pi-ai` namespace: whenever provider models are
9
+ * written there with missing capability fields (which is the norm for a freshly
10
+ * synced gateway), it patches the missing fields from the cached models.dev
11
+ * catalog.
9
12
  */
10
13
  import z from '@deepseek-ai/schemastery';
11
- /** This plugin's own settings namespace. */
14
+ /** This plugin's own settings namespace: its profile entry id. */
12
15
  export declare const PLUGIN_NS = "model-parameters";
13
16
  /** The namespace whose models this plugin completes: pi-ai provider profiles. */
14
17
  export declare const TARGET_NS = "llm-pi-ai";
@@ -28,7 +31,7 @@ export declare const ACCEPTED_INPUT_MODALITIES: readonly string[];
28
31
  * this set are written.
29
32
  */
30
33
  export declare const REASONING_LEVELS: readonly string[];
31
- /** Plugin configuration shape. */
34
+ /** Plugin configuration shape: the PLAIN document, with no live handles in it. */
32
35
  export interface ModelParametersConfig {
33
36
  /** Master switch: when off, no fills happen (listener is inert). */
34
37
  enabled: boolean;
@@ -49,5 +52,84 @@ export interface ModelParametersConfig {
49
52
  /** models.dev provider ids treated as first-party vendors for tie-breaking. */
50
53
  officialProviders: string[];
51
54
  }
52
- export declare const Config: z<ModelParametersConfig>;
55
+ /**
56
+ * Recursively readonly value a live handle returns — cosmokit's
57
+ * `VolatileSnapshot<T>`, spelled locally for the same reason as
58
+ * {@link LiveSettingsField}.
59
+ */
60
+ export type LiveSettingsSnapshot<T> = T extends object ? {
61
+ readonly [K in keyof T]: LiveSettingsSnapshot<T[K]>;
62
+ } : T;
63
+ /**
64
+ * One `volatile()` config field as the loader hands it to `apply()`: a live
65
+ * handle, not the value it currently holds. Spelled structurally rather than as
66
+ * cosmokit's `Volatile<T>` on purpose — cosmokit is a transitive dependency
67
+ * here, and importing it would put a second schemastery/cosmokit pair into the
68
+ * emitted declaration.
69
+ */
70
+ export interface LiveSettingsField<T> {
71
+ get(): LiveSettingsSnapshot<T>;
72
+ }
73
+ /** A hand-written entry config (profile patch / test fixture): every field is optional. */
74
+ export type ModelParametersConfigInput = {
75
+ enabled?: boolean | null;
76
+ ttlDays?: number | null;
77
+ fillName?: boolean | null;
78
+ fillContext?: boolean | null;
79
+ fillMaxTokens?: boolean | null;
80
+ fillReasoning?: boolean | null;
81
+ fillInput?: boolean | null;
82
+ providerMap?: Record<string, string> | null;
83
+ officialProviders?: string[] | null;
84
+ };
85
+ /** The resolved entry config `apply()` receives: every field is a live handle. */
86
+ export type ResolvedModelParametersConfig = {
87
+ enabled: LiveSettingsField<boolean>;
88
+ ttlDays: LiveSettingsField<number>;
89
+ fillName: LiveSettingsField<boolean>;
90
+ fillContext: LiveSettingsField<boolean>;
91
+ fillMaxTokens: LiveSettingsField<boolean>;
92
+ fillReasoning: LiveSettingsField<boolean>;
93
+ fillInput: LiveSettingsField<boolean>;
94
+ providerMap: LiveSettingsField<Record<string, string>>;
95
+ officialProviders: LiveSettingsField<string[]>;
96
+ };
97
+ /**
98
+ * Entry config schema. Deliberately NOT annotated `z<ModelParametersConfig>`:
99
+ * schemastery >= 3.18.3 carries a third `Mode` type parameter, so a
100
+ * `.volatile()` field's OUTPUT type is `Volatile<T>` (a live `{ get() }` cell),
101
+ * and that annotation no longer compiles. The plain document shape stays
102
+ * {@link ModelParametersConfig} and is what {@link readConfig} projects to.
103
+ *
104
+ * The annotation below names the type through the package's own default export
105
+ * (`z<...>`), with locally declared type arguments: the bare inferred type of a
106
+ * volatile schema resolves through a schemastery copy this package does not
107
+ * import, so `tsc -p tsconfig.build.json` (`declaration: true`) could not name
108
+ * it (TS2742) — the emitted declaration stays portable this way.
109
+ *
110
+ * `.volatile()` is REQUIRED on every field the settings page may write, not
111
+ * cosmetic: `dsh-settings` derives `volatileForm(schema)` from this schema, and
112
+ * with no volatile field at all it SKIPS this entry in `describe()` (the
113
+ * settings page disappears) and REJECTS every write as a non-volatile path.
114
+ * The profile entry id (`model-parameters`, see cordis.patch.yml) is the
115
+ * namespace those writes address.
116
+ */
117
+ export type ModelParametersConfigSchema = z<ModelParametersConfigInput, ResolvedModelParametersConfig>;
118
+ /** The plugin's `Config` schema, exported for the loader and the settings page. */
119
+ export declare const Config: ModelParametersConfigSchema;
53
120
  export declare const DEFAULT_CONFIG: ModelParametersConfig;
121
+ /**
122
+ * The entry config as `apply()` receives it. Every field may arrive either as a
123
+ * live `Volatile` cell or as a plain value (a hand-built config object, a
124
+ * profile patch written before the volatile marker existed, or a host that
125
+ * resolves the document without the wrapper).
126
+ */
127
+ export type ModelParametersLiveConfig = Partial<Record<keyof ModelParametersConfig, unknown>>;
128
+ /**
129
+ * Project the loader entry config into the plain document shape, per field
130
+ * falling back to {@link DEFAULT_CONFIG}. The config is re-read on EVERY call,
131
+ * so settings-page edits (which the loader swaps into the live cells in place)
132
+ * take effect immediately. Container fields are copied so callers can never
133
+ * mutate the live document through a returned reference.
134
+ */
135
+ export declare function readConfig(config: ModelParametersLiveConfig | undefined): ModelParametersConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonlan/dsh-model-parameters",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "DSH plugin: when a provider's model sync returns only ids, automatically fill display name, context window, max output tokens, reasoning efforts, and input modalities from the models.dev catalog (cached, TTL-refreshed), and surface a settings page with toggles, refresh, and a fill report.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -69,13 +69,13 @@
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@deepseek-ai/cordis": "^4.0.1",
72
- "@deepseek-ai/schemastery": "^3.18.2",
73
- "@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
74
- "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2",
75
- "@deepseek-ai/dsh-llm-pi-ai": "^0.1.2-alpha.2",
76
- "@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2",
77
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.2",
78
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2",
72
+ "@deepseek-ai/schemastery": "^3.18.4",
73
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.2 || ^0.2.0-rc.1",
74
+ "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2 || ^0.2.0-rc.1",
75
+ "@deepseek-ai/dsh-llm-pi-ai": "^0.1.2-alpha.2 || ^0.2.0-rc.1",
76
+ "@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2 || ^0.2.0-rc.1",
77
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.2 || ^0.2.0-rc.1",
78
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2 || ^0.2.0-rc.1",
79
79
  "react": "^18.2.0",
80
80
  "react-dom": "^18.2.0"
81
81
  },
@@ -86,13 +86,13 @@
86
86
  "tsdown": "^0.22.14",
87
87
  "typescript": "^5.9.0",
88
88
  "@deepseek-ai/cordis": "^4.0.1",
89
- "@deepseek-ai/schemastery": "^3.18.2",
90
- "@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
91
- "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2",
92
- "@deepseek-ai/dsh-llm-pi-ai": "^0.1.2-alpha.2",
93
- "@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2",
94
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.2",
95
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2"
89
+ "@deepseek-ai/schemastery": "^3.18.4",
90
+ "@deepseek-ai/dsh-settings": "0.2.0-rc.1",
91
+ "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.1",
92
+ "@deepseek-ai/dsh-llm-pi-ai": "0.2.0-rc.1",
93
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.1",
94
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.1",
95
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.1"
96
96
  },
97
97
  "packageManager": "pnpm@11.21.0"
98
98
  }
@@ -1,12 +1,9 @@
1
1
  /**
2
- * @fonlan/dsh-model-parameters client half: the plugin's own Settings Card.
2
+ * @fonlan/dsh-model-parameters client half: the plugin's own Settings section.
3
3
  *
4
- * Registers into the `settings.plugin.item` slot keyed by the `model-parameters`
5
- * settings namespace (the same string the host half registers), so the card
6
- * appears inside 设置 → 插件 → 插件配置, paired with the namespace by the tab —
7
- * exactly like @fonlan/dsh-web-auth. The card draws its own expandable chrome
8
- * (external plugins cannot import the built-in PluginCard) with styles aligned
9
- * to the built-in plugin cards.
4
+ * Registers into the settings.section slot, so the page owns one entry in the
5
+ * settings sidebar and renders in the panel's content column. The page itself
6
+ * lives in ./settings-section.
10
7
  *
11
8
  * Deps resolved from the browser module table: react only. The slots service
12
9
  * is reached through the cordis context; types come from devDependencies and
@@ -17,16 +14,18 @@ type ClientContext = Context
17
14
  import type {} from '@deepseek-ai/dsh-client-locale/client'
18
15
  import type {} from '@deepseek-ai/dsh-client-ui-slots'
19
16
  import { LOCALE_NS, zh, en } from './locales'
20
- import { makeSettingsCard } from './settings-card'
17
+ import { makeSettingsSection } from './settings-section'
21
18
 
22
- /** The settings namespace this card edits (must match the host half). */
23
- const PLUGIN_NS = 'model-parameters'
19
+ /** The settings sidebar entry this page owns (must stay stable). */
20
+ const SECTION_ID = 'model-parameters'
24
21
 
25
22
  /** The slots service face (subset of @deepseek-ai/dsh-client-ui-slots). */
26
23
  interface SlotEntry {
27
24
  name: string
28
- key?: string
29
- inject?: () => unknown
25
+ id?: string
26
+ order?: number
27
+ label?: () => string
28
+ locale?: string
30
29
  }
31
30
 
32
31
  interface Slots {
@@ -44,12 +43,16 @@ export function apply(ctx: ClientContext): void {
44
43
  return () => off()
45
44
  }, 'model-parameters: dictionaries')
46
45
 
47
- const Card = makeSettingsCard(ctx)
46
+ const t = ctx.locale.bind(LOCALE_NS) as unknown as (key: string) => string
47
+ const Section = makeSettingsSection(ctx)
48
48
  const slots = (ctx as unknown as { slots: Slots }).slots
49
- slots.inject('settings.plugin.item', () =>
49
+ slots.inject('settings.section', () =>
50
50
  slots.register({
51
- name: 'settings.plugin.item',
52
- key: PLUGIN_NS,
53
- }, Card),
51
+ name: 'settings.section',
52
+ id: SECTION_ID,
53
+ order: 320,
54
+ label: () => t('settingsTitle'),
55
+ locale: LOCALE_NS,
56
+ }, Section),
54
57
  )
55
58
  }
@@ -6,9 +6,7 @@ export const LOCALE_NS = 'model-parameters'
6
6
 
7
7
  export const zh = {
8
8
  settingsTitle: '模型参数补全',
9
- cardSub: '补全字段、目录刷新、Provider 映射与补全报告。',
10
- collapse: '收起',
11
- expand: '展开',
9
+ sectionSub: '补全字段、目录刷新、Provider 映射与补全报告。',
12
10
  master: '启用自动补全',
13
11
  masterDesc: '关闭后不再自动补全任何字段(已补全的值保留)。',
14
12
  fields: '补全字段',
@@ -53,9 +51,7 @@ export const zh = {
53
51
 
54
52
  export const en: Record<keyof typeof zh, string> = {
55
53
  settingsTitle: 'Model Parameters',
56
- cardSub: 'Field toggles, catalog refresh, provider mapping and the fill report.',
57
- collapse: 'Collapse',
58
- expand: 'Expand',
54
+ sectionSub: 'Field toggles, catalog refresh, provider mapping and the fill report.',
59
55
  master: 'Enable auto-fill',
60
56
  masterDesc: 'When off, no fields are filled (already-filled values are kept).',
61
57
  fields: 'Fields to fill',