@prettier-ai/dsh-client-ui-settings-models 0.1.2-alpha.1 → 0.1.2-alpha.3

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/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/client/ui-settings-models/README.md
5
- README.md: 75efb18daf3e4a96fea637f36633f216046ebccd
6
- README.zh.md: f3c7099e242e379be5143352d4045208a8209dc6
5
+ README.md: 920780b2193d6165a10967cc49afcb5b7e193105
6
+ README.zh.md: 4ec9ae43eef9a13ffde22e2a3de0e44d3b6cb51d
package/README.md CHANGED
@@ -63,7 +63,7 @@ A typed API key is judged on its own field: after trimming, it must be non-empty
63
63
 
64
64
  ### Concurrency and credentials
65
65
 
66
- Each settings write carries the card's current `revision`, so a concurrent write from another tab or an external `settings.yaml` edit is refused as `settings-conflict`. After settings commit, the card adopts the returned redacted user subtree and revision before storing the credential, so a failed credential stage retries only that stage. Deletion removes a configured, writable credential only when the profile names the page's derived `<ROUTE>_API_KEY` target, then unsets the profile; both operations are idempotent. Once loaded, the page subscribes to forwarded `settings/document-updated`, `credentials/reference-updated`, and `llm/adapters-updated` owner events, plus local `connection/reset`, so external edits converge without polling.
66
+ Each settings write carries the card's current `revision`, so a concurrent write from another tab or an external `settings.yaml` edit is refused as `settings/conflict`. After settings commit, the card adopts the returned redacted user subtree and revision before storing the credential, so a failed credential stage retries only that stage. Deletion removes a configured, writable credential only when the profile names the page's derived `<ROUTE>_API_KEY` target, then unsets the profile; both operations are idempotent. Once loaded, the page subscribes to forwarded `settings/document-updated`, `credentials/reference-updated`, and `llm/adapters-updated` owner events, plus local `connection/reset`, so external edits converge without polling.
67
67
 
68
68
  ### Onboarding coordinator
69
69
 
package/README.zh.md CHANGED
@@ -63,7 +63,7 @@ kind: "package-reference"
63
63
 
64
64
  ### 并发与凭据
65
65
 
66
- 每次 settings 写入都携带卡片当前的 `revision`,因此来自另一个标签页或外部 `settings.yaml` 编辑的并发写入会以 `settings-conflict` 被拒绝。settings 提交后,卡片会在存储凭据前采纳返回的脱敏用户子树与 revision,因此失败的凭据阶段只重试该阶段。删除只会在 profile 指名本页派生的 `<ROUTE>_API_KEY` 目标时移除已配置且可写的凭据,然后 unset 该 profile;两个操作都幂等。加载完成后,页面订阅转发的 `settings/document-updated`、`credentials/reference-updated` 与 `llm/adapters-updated` 属主事件,以及本地 `connection/reset`,因此外部编辑无需轮询即可收敛。
66
+ 每次 settings 写入都携带卡片当前的 `revision`,因此来自另一个标签页或外部 `settings.yaml` 编辑的并发写入会以 `settings/conflict` 被拒绝。settings 提交后,卡片会在存储凭据前采纳返回的脱敏用户子树与 revision,因此失败的凭据阶段只重试该阶段。删除只会在 profile 指名本页派生的 `<ROUTE>_API_KEY` 目标时移除已配置且可写的凭据,然后 unset 该 profile;两个操作都幂等。加载完成后,页面订阅转发的 `settings/document-updated`、`credentials/reference-updated` 与 `llm/adapters-updated` 属主事件,以及本地 `connection/reset`,因此外部编辑无需轮询即可收敛。
67
67
 
68
68
  ### 引导协调器
69
69
 
package/lib/client.js CHANGED
@@ -443,252 +443,6 @@ window.__ModuleLoader__.load({
443
443
  });
444
444
  }
445
445
  //#endregion
446
- //#region lib/types/client/store.js
447
- /**
448
- * Models settings page store: one snapshot joining the configurable-provider
449
- * directory (`llm/listProviders` joined with `llm/listConfigurableProviders`),
450
- * the settings namespaces (shared settings mirror),
451
- * and the referenced credentials (`credentials/describe`). The host stays the
452
- * single fact source — every mutation writes through the wire and the page
453
- * re-renders from the next describe, pushed or refetched.
454
- */
455
- /**
456
- * Any route key walks a dict schema to the same profile node, so the lookup
457
- * names one that cannot collide with a configured route.
458
- */
459
- const PROBE_ROUTE = "\0probe";
460
- /**
461
- * Join declared configurable providers with the currently registered routes.
462
- * @param registered - live provider routes in registration order.
463
- * @param directory - declared configurable providers in declaration order.
464
- * @returns declared rows followed by live routes with no declaration.
465
- */
466
- function joinProviderDirectory(registered, directory) {
467
- const active = new Set(registered.map((provider) => provider.id));
468
- const declared = new Set(directory.map((entry) => entry.provider));
469
- const rows = directory.map((entry) => ({
470
- provider: entry.provider,
471
- displayName: entry.displayName,
472
- settingsNs: entry.settingsNs,
473
- settingsPath: [...entry.settingsPath],
474
- active: active.has(entry.provider),
475
- ...entry.declared === void 0 ? {} : { declared: entry.declared }
476
- }));
477
- for (const provider of registered) {
478
- if (declared.has(provider.id)) continue;
479
- rows.push({
480
- provider: provider.id,
481
- displayName: provider.name,
482
- settingsNs: "",
483
- settingsPath: [],
484
- active: true
485
- });
486
- }
487
- return rows;
488
- }
489
- /**
490
- * Human text for a rejected wire call. A transport failure rejects with an
491
- * Error; a host or a runtime can reject with anything, and the page still has
492
- * to say something.
493
- * @param error - the rejection value.
494
- * @returns the message to show.
495
- */
496
- function messageOf(error) {
497
- return error instanceof Error ? error.message : String(error);
498
- }
499
- /**
500
- * Derive the conventional credential reference for a provider route: the v1
501
- * page never asks for an environment-variable name, so a typed key stores
502
- * under this derived reference and the profile records it as `apiKeyEnv`.
503
- * @param provider - provider route id (e.g. `anthropic`, `minimax-cn`).
504
- * @returns the derived reference name (e.g. `MINIMAX_CN_API_KEY`).
505
- */
506
- function deriveKeyRef(provider) {
507
- return `${provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`;
508
- }
509
- /**
510
- * The wire protocols a hand-declared route may name, read out of the owning
511
- * namespace's own schema. This stays a schema read rather than a wire field so
512
- * the choices the page offers cannot drift from the ones the adapter accepts:
513
- * both come from the same `Config`.
514
- * @param namespace - the namespace view whose schema declares the profile shape.
515
- * @param schema - settings schema operations.
516
- * @returns the protocol identifiers, or an empty list when the schema has none.
517
- */
518
- function protocolChoices(namespace, schema) {
519
- if (namespace === void 0) return [];
520
- const list = schema.nodeAtPath(schema.rehydrate(namespace.schema), [
521
- "providers",
522
- PROBE_ROUTE,
523
- "api"
524
- ]);
525
- if (list?.type !== "union" || list.list === void 0) return [];
526
- return list.list.map((entry) => entry.value).filter((value) => typeof value === "string");
527
- }
528
- /** The credential reference a resolved profile names (its `apiKeyEnv` field). */
529
- function apiKeyEnvOf(namespace, path, schema) {
530
- if (namespace === void 0) return void 0;
531
- const profile = schema.getPath(namespace.value, path);
532
- if (typeof profile !== "object" || profile === null) return void 0;
533
- const ref = profile.apiKeyEnv;
534
- return typeof ref === "string" && ref.length > 0 ? ref : void 0;
535
- }
536
- /** The models settings page controller (one per settings surface). */
537
- var ModelsSettingsStore = class {
538
- api;
539
- schema;
540
- describeFace;
541
- /** The snapshot the section renders from (uSES-safe store). */
542
- store = (0, _prettier_ai_dsh_client_store.createSnapshotStore)({
543
- status: "idle",
544
- error: null,
545
- credentialError: null,
546
- writable: false,
547
- rows: [],
548
- namespaces: /* @__PURE__ */ new Map()
549
- });
550
- /** Latest load wins; an older response never overwrites a newer one. */
551
- generation = 0;
552
- /**
553
- * @param api - the page's credentials Remote and LLM wire faces.
554
- * @param describeFace - the shared mirror's describe face (namespace views and writability).
555
- */
556
- constructor(api, schema, describeFace) {
557
- this.api = api;
558
- this.schema = schema;
559
- this.describeFace = describeFace;
560
- }
561
- /**
562
- * Refresh the whole page snapshot: the provider directory and the mirror's
563
- * settings answer in parallel, then one batched credential describe over
564
- * every referenced ref. Provider failure or absence of an initial settings
565
- * answer keeps the last good rows and surfaces an error; a failed settings
566
- * refresh reuses the mirror's held view.
567
- * @returns nothing; the snapshot carries the outcome.
568
- */
569
- async load() {
570
- const generation = ++this.generation;
571
- this.store.update((s) => {
572
- s.status = "loading";
573
- s.error = null;
574
- });
575
- let providers;
576
- let writable;
577
- let views;
578
- try {
579
- const [registered, declared] = await Promise.all([
580
- this.api.llm.listProviders(),
581
- this.api.llm.listConfigurableProviders(),
582
- this.describeFace.ensure()
583
- ]);
584
- if (!registered.ok) throw new Error(registered.error.message);
585
- if (!declared.ok) throw new Error(declared.error.message);
586
- const mirrored = this.describeFace.getSnapshot();
587
- if (mirrored.view === void 0) throw new Error(mirrored.error ?? "settings are unavailable in this browser");
588
- providers = joinProviderDirectory(registered.value, declared.value);
589
- writable = mirrored.view.writable;
590
- views = mirrored.view.namespaces;
591
- } catch (error) {
592
- if (generation !== this.generation) return;
593
- this.store.update((s) => {
594
- s.status = "error";
595
- s.error = error instanceof Error ? error.message : String(error);
596
- });
597
- return;
598
- }
599
- const namespaces = new Map(views.map((view) => [view.ns, view]));
600
- const rows = providers.map((entry) => {
601
- const namespace = namespaces.get(entry.settingsNs);
602
- return {
603
- entry,
604
- configured: namespace !== void 0 && (entry.settingsPath.length === 0 || this.schema.getPath(namespace.value, entry.settingsPath) !== void 0),
605
- removable: namespace !== void 0 && entry.settingsPath.length > 0 && this.schema.hasPath(namespace.user, entry.settingsPath) && !this.schema.hasPath(namespace.base, entry.settingsPath),
606
- apiKeyEnv: apiKeyEnvOf(namespace, entry.settingsPath, this.schema),
607
- credential: void 0
608
- };
609
- });
610
- const refs = [...new Set(rows.map((row) => row.apiKeyEnv ?? deriveKeyRef(row.entry.provider)))];
611
- let credentials = {};
612
- let credentialError = null;
613
- if (refs.length > 0) try {
614
- const response = await this.api.credentials.describe(refs);
615
- if (response.ok) credentials = response.value;
616
- else credentialError = response.error.message;
617
- } catch (error) {
618
- credentialError = messageOf(error);
619
- }
620
- if (generation !== this.generation) return;
621
- this.store.update((s) => {
622
- s.status = "ready";
623
- s.error = null;
624
- s.credentialError = credentialError;
625
- s.writable = writable;
626
- s.rows = rows.map((row) => {
627
- const named = row.apiKeyEnv === void 0 ? void 0 : credentials[row.apiKeyEnv];
628
- const derived = row.apiKeyEnv !== void 0 ? void 0 : credentials[deriveKeyRef(row.entry.provider)];
629
- return {
630
- ...row,
631
- ...named === void 0 ? {} : { credential: named },
632
- ...derived === void 0 ? {} : { derivedCredential: derived }
633
- };
634
- });
635
- s.namespaces = namespaces;
636
- });
637
- }
638
- };
639
- /**
640
- * Whether a joined row can serve model requests as it stands: the route is
641
- * registered with the adapter registry, and whatever credential its resolved
642
- * profile names is stored. A profile naming no reference authenticates through
643
- * the provider's own path (the Bedrock chain, Vertex ADC, a gateway that needs
644
- * nothing), as does a live route with no settings address at all, so neither
645
- * owes this page a key.
646
- * @param row - one joined provider row.
647
- * @returns whether the user already has this provider to talk to.
648
- */
649
- function providerUsable(row) {
650
- if (!row.entry.active) return false;
651
- if (row.apiKeyEnv === void 0) return true;
652
- return row.credential?.configured === true;
653
- }
654
- /**
655
- * Project first-run readiness from the provider/settings/credential join used
656
- * by the Models page. The step exists to leave the user with a model to talk
657
- * to, so ANY usable provider ends it; only when none exists does the official
658
- * DeepSeek route — the one route the prompt can offer a key field for — decide
659
- * whether prompting can help. A missing official configurable-provider
660
- * declaration means the adapter is not repairable by navigating to Models.
661
- * @param state - current shared Models join snapshot.
662
- * @returns the onboarding state without reading a parallel fact source.
663
- */
664
- function onboardingReadiness(state) {
665
- if ((state.status === "idle" || state.status === "loading") && state.rows.length === 0) return { kind: "loading" };
666
- if (state.status === "error") return {
667
- kind: "unavailable",
668
- reason: "load-failed"
669
- };
670
- if (state.rows.some(providerUsable)) return { kind: "provider-ready" };
671
- const row = state.rows.find((candidate) => candidate.entry.provider === "deepseek-official" && candidate.entry.settingsNs === "llm-deepseek" && candidate.entry.settingsPath.length === 0);
672
- if (row === void 0) return { kind: "adapter-absent" };
673
- if (!row.entry.active) return {
674
- kind: "unavailable",
675
- reason: "provider-inactive"
676
- };
677
- if (state.credentialError !== null || row.credential === void 0) return {
678
- kind: "unavailable",
679
- reason: "credentials-unavailable"
680
- };
681
- if (!state.writable) return {
682
- kind: "unavailable",
683
- reason: "settings-read-only"
684
- };
685
- if (!row.credential.writable) return {
686
- kind: "unavailable",
687
- reason: "credential-read-only"
688
- };
689
- return { kind: "credential-missing" };
690
- }
691
- //#endregion
692
446
  //#region lib/types/client/ModelListEditor.js
693
447
  /**
694
448
  * The model list of one pi-ai provider profile, plus the action that asks the
@@ -793,7 +547,7 @@ window.__ModuleLoader__.load({
793
547
  * @returns the model-list editor.
794
548
  */
795
549
  function ModelListEditor(props) {
796
- const { models, onChange, probe, api, t, disabled } = props;
550
+ const { models, onChange, probe, operations, t, disabled } = props;
797
551
  const [busy, setBusy] = (0, react.useState)(false);
798
552
  const [failure, setFailure] = (0, react.useState)(void 0);
799
553
  const [candidates, setCandidates] = (0, react.useState)(void 0);
@@ -839,17 +593,17 @@ window.__ModuleLoader__.load({
839
593
  setBusy(true);
840
594
  setFailure(void 0);
841
595
  try {
842
- const response = await api.llm.discoverModels(probe.settingsNs, {
596
+ const answer = await operations.discoverModels(probe.settingsNs, {
843
597
  ...probe.provider === void 0 ? {} : { provider: probe.provider },
844
598
  ...probe.baseURL === void 0 || probe.baseURL.length === 0 ? {} : { baseURL: probe.baseURL },
845
599
  ...probe.api === void 0 ? {} : { api: probe.api },
846
600
  ...probe.apiKey === void 0 ? {} : { apiKey: probe.apiKey }
847
601
  });
848
- if (!response.ok) {
849
- setFailure(response.error.message);
602
+ if (answer.kind === "refused") {
603
+ setFailure(answer.message);
850
604
  return;
851
605
  }
852
- const found = response.value;
606
+ const found = answer.models;
853
607
  if (found.length === 0) {
854
608
  setFailure(t("fetchEmpty"));
855
609
  return;
@@ -857,8 +611,6 @@ window.__ModuleLoader__.load({
857
611
  const known = new Set(models.map((model) => textOf(model, "id")));
858
612
  setCandidates(found);
859
613
  setPicked(new Set(found.filter((model) => !known.has(model.id)).map((model) => model.id)));
860
- } catch (error) {
861
- setFailure(messageOf(error));
862
614
  } finally {
863
615
  setBusy(false);
864
616
  }
@@ -1090,6 +842,247 @@ window.__ModuleLoader__.load({
1090
842
  });
1091
843
  }
1092
844
  //#endregion
845
+ //#region lib/types/client/store.js
846
+ /**
847
+ * Models settings page store: one snapshot joining the configurable-provider
848
+ * directory (`llm/listProviders` joined with `llm/listConfigurableProviders`),
849
+ * the settings namespaces (shared settings mirror),
850
+ * and the referenced credentials (`credentials/describe`). The host stays the
851
+ * single fact source — every mutation writes through the wire and the page
852
+ * re-renders from the next describe, pushed or refetched.
853
+ */
854
+ /**
855
+ * Any route key walks a dict schema to the same profile node, so the lookup
856
+ * names one that cannot collide with a configured route.
857
+ */
858
+ const PROBE_ROUTE = "\0probe";
859
+ /**
860
+ * Join declared configurable providers with the currently registered routes.
861
+ * @param registered - live provider routes in registration order.
862
+ * @param directory - declared configurable providers in declaration order.
863
+ * @returns declared rows followed by live routes with no declaration.
864
+ */
865
+ function joinProviderDirectory(registered, directory) {
866
+ const active = new Set(registered.map((provider) => provider.id));
867
+ const declared = new Set(directory.map((entry) => entry.provider));
868
+ const rows = directory.map((entry) => ({
869
+ provider: entry.provider,
870
+ displayName: entry.displayName,
871
+ settingsNs: entry.settingsNs,
872
+ settingsPath: [...entry.settingsPath],
873
+ active: active.has(entry.provider),
874
+ ...entry.declared === void 0 ? {} : { declared: entry.declared }
875
+ }));
876
+ for (const provider of registered) {
877
+ if (declared.has(provider.id)) continue;
878
+ rows.push({
879
+ provider: provider.id,
880
+ displayName: provider.name,
881
+ settingsNs: "",
882
+ settingsPath: [],
883
+ active: true
884
+ });
885
+ }
886
+ return rows;
887
+ }
888
+ /**
889
+ * Derive the conventional credential reference for a provider route: the v1
890
+ * page never asks for an environment-variable name, so a typed key stores
891
+ * under this derived reference and the profile records it as `apiKeyEnv`.
892
+ * @param provider - provider route id (e.g. `anthropic`, `minimax-cn`).
893
+ * @returns the derived reference name (e.g. `MINIMAX_CN_API_KEY`).
894
+ */
895
+ function deriveKeyRef(provider) {
896
+ return `${provider.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_API_KEY`;
897
+ }
898
+ /**
899
+ * The wire protocols a hand-declared route may name, read out of the owning
900
+ * namespace's own schema. This stays a schema read rather than a wire field so
901
+ * the choices the page offers cannot drift from the ones the adapter accepts:
902
+ * both come from the same `Config`.
903
+ * @param namespace - the namespace view whose schema declares the profile shape.
904
+ * @param schema - settings schema operations.
905
+ * @returns the protocol identifiers, or an empty list when the schema has none.
906
+ */
907
+ function protocolChoices(namespace, schema) {
908
+ if (namespace === void 0) return [];
909
+ const list = schema.nodeAtPath(schema.rehydrate(namespace.schema), [
910
+ "providers",
911
+ PROBE_ROUTE,
912
+ "api"
913
+ ]);
914
+ if (list?.type !== "union" || list.list === void 0) return [];
915
+ return list.list.map((entry) => entry.value).filter((value) => typeof value === "string");
916
+ }
917
+ /** The credential reference a resolved profile names (its `apiKeyEnv` field). */
918
+ function apiKeyEnvOf(namespace, path, schema) {
919
+ if (namespace === void 0) return void 0;
920
+ const profile = schema.getPath(namespace.value, path);
921
+ if (typeof profile !== "object" || profile === null) return void 0;
922
+ const ref = profile.apiKeyEnv;
923
+ return typeof ref === "string" && ref.length > 0 ? ref : void 0;
924
+ }
925
+ /** The models settings page controller (one per settings surface). */
926
+ var ModelsSettingsStore = class {
927
+ ctx;
928
+ schema;
929
+ describeFace;
930
+ /** The snapshot the section renders from (uSES-safe store). */
931
+ store = (0, _prettier_ai_dsh_client_store.createSnapshotStore)({
932
+ status: "idle",
933
+ error: null,
934
+ credentialError: null,
935
+ writable: false,
936
+ rows: [],
937
+ namespaces: /* @__PURE__ */ new Map()
938
+ });
939
+ /** Latest load wins; an older response never overwrites a newer one. */
940
+ generation = 0;
941
+ /**
942
+ * @param ctx - the page plugin's context, whose `remote.llm` and
943
+ * `remote.credentials` namespaces carry the directory and credential reads.
944
+ * @param schema - settings-owned schema and immutable path operations.
945
+ * @param describeFace - the shared mirror's describe face (namespace views and writability).
946
+ */
947
+ constructor(ctx, schema, describeFace) {
948
+ this.ctx = ctx;
949
+ this.schema = schema;
950
+ this.describeFace = describeFace;
951
+ }
952
+ /**
953
+ * Refresh the whole page snapshot: the provider directory and the mirror's
954
+ * settings answer in parallel, then one batched credential describe over
955
+ * every referenced ref. Provider failure or absence of an initial settings
956
+ * answer keeps the last good rows and surfaces an error; a failed settings
957
+ * refresh reuses the mirror's held view.
958
+ * @returns nothing; the snapshot carries the outcome.
959
+ */
960
+ async load() {
961
+ const generation = ++this.generation;
962
+ this.store.update((s) => {
963
+ s.status = "loading";
964
+ s.error = null;
965
+ });
966
+ const [registered, declared] = await Promise.all([
967
+ this.ctx.remote.llm.listProviders(),
968
+ this.ctx.remote.llm.listConfigurableProviders(),
969
+ this.describeFace.ensure()
970
+ ]);
971
+ if (!registered.ok) {
972
+ this.failLoad(generation, registered.error.message);
973
+ return;
974
+ }
975
+ if (!declared.ok) {
976
+ this.failLoad(generation, declared.error.message);
977
+ return;
978
+ }
979
+ const mirrored = this.describeFace.getSnapshot();
980
+ if (mirrored.view === void 0) {
981
+ this.failLoad(generation, mirrored.error ?? "settings are unavailable in this browser");
982
+ return;
983
+ }
984
+ const providers = joinProviderDirectory(registered.value, declared.value);
985
+ const writable = mirrored.view.writable;
986
+ const views = mirrored.view.namespaces;
987
+ const namespaces = new Map(views.map((view) => [view.ns, view]));
988
+ const rows = providers.map((entry) => {
989
+ const namespace = namespaces.get(entry.settingsNs);
990
+ return {
991
+ entry,
992
+ configured: namespace !== void 0 && (entry.settingsPath.length === 0 || this.schema.getPath(namespace.value, entry.settingsPath) !== void 0),
993
+ removable: namespace !== void 0 && entry.settingsPath.length > 0 && this.schema.hasPath(namespace.user, entry.settingsPath) && !this.schema.hasPath(namespace.base, entry.settingsPath),
994
+ apiKeyEnv: apiKeyEnvOf(namespace, entry.settingsPath, this.schema),
995
+ credential: void 0
996
+ };
997
+ });
998
+ const refs = [...new Set(rows.map((row) => row.apiKeyEnv ?? deriveKeyRef(row.entry.provider)))];
999
+ let credentials = {};
1000
+ let credentialError = null;
1001
+ if (refs.length > 0) {
1002
+ const response = await this.ctx.remote.credentials.describe(refs);
1003
+ if (response.ok) credentials = response.value;
1004
+ else credentialError = response.error.message;
1005
+ }
1006
+ if (generation !== this.generation) return;
1007
+ this.store.update((s) => {
1008
+ s.status = "ready";
1009
+ s.error = null;
1010
+ s.credentialError = credentialError;
1011
+ s.writable = writable;
1012
+ s.rows = rows.map((row) => {
1013
+ const named = row.apiKeyEnv === void 0 ? void 0 : credentials[row.apiKeyEnv];
1014
+ const derived = row.apiKeyEnv !== void 0 ? void 0 : credentials[deriveKeyRef(row.entry.provider)];
1015
+ return {
1016
+ ...row,
1017
+ ...named === void 0 ? {} : { credential: named },
1018
+ ...derived === void 0 ? {} : { derivedCredential: derived }
1019
+ };
1020
+ });
1021
+ s.namespaces = namespaces;
1022
+ });
1023
+ }
1024
+ /** Publish one load's failure text, unless a newer load already took over. */
1025
+ failLoad(generation, message) {
1026
+ if (generation !== this.generation) return;
1027
+ this.store.update((s) => {
1028
+ s.status = "error";
1029
+ s.error = message;
1030
+ });
1031
+ }
1032
+ };
1033
+ /**
1034
+ * Whether a joined row can serve model requests as it stands: the route is
1035
+ * registered with the adapter registry, and whatever credential its resolved
1036
+ * profile names is stored. A profile naming no reference authenticates through
1037
+ * the provider's own path (the Bedrock chain, Vertex ADC, a gateway that needs
1038
+ * nothing), as does a live route with no settings address at all, so neither
1039
+ * owes this page a key.
1040
+ * @param row - one joined provider row.
1041
+ * @returns whether the user already has this provider to talk to.
1042
+ */
1043
+ function providerUsable(row) {
1044
+ if (!row.entry.active) return false;
1045
+ if (row.apiKeyEnv === void 0) return true;
1046
+ return row.credential?.configured === true;
1047
+ }
1048
+ /**
1049
+ * Project first-run readiness from the provider/settings/credential join used
1050
+ * by the Models page. The step exists to leave the user with a model to talk
1051
+ * to, so ANY usable provider ends it; only when none exists does the official
1052
+ * DeepSeek route — the one route the prompt can offer a key field for — decide
1053
+ * whether prompting can help. A missing official configurable-provider
1054
+ * declaration means the adapter is not repairable by navigating to Models.
1055
+ * @param state - current shared Models join snapshot.
1056
+ * @returns the onboarding state without reading a parallel fact source.
1057
+ */
1058
+ function onboardingReadiness(state) {
1059
+ if ((state.status === "idle" || state.status === "loading") && state.rows.length === 0) return { kind: "loading" };
1060
+ if (state.status === "error") return {
1061
+ kind: "unavailable",
1062
+ reason: "load-failed"
1063
+ };
1064
+ if (state.rows.some(providerUsable)) return { kind: "provider-ready" };
1065
+ const row = state.rows.find((candidate) => candidate.entry.provider === "deepseek-official" && candidate.entry.settingsNs === "llm-deepseek" && candidate.entry.settingsPath.length === 0);
1066
+ if (row === void 0) return { kind: "adapter-absent" };
1067
+ if (!row.entry.active) return {
1068
+ kind: "unavailable",
1069
+ reason: "provider-inactive"
1070
+ };
1071
+ if (state.credentialError !== null || row.credential === void 0) return {
1072
+ kind: "unavailable",
1073
+ reason: "credentials-unavailable"
1074
+ };
1075
+ if (!state.writable) return {
1076
+ kind: "unavailable",
1077
+ reason: "settings-read-only"
1078
+ };
1079
+ if (!row.credential.writable) return {
1080
+ kind: "unavailable",
1081
+ reason: "credential-read-only"
1082
+ };
1083
+ return { kind: "credential-missing" };
1084
+ }
1085
+ //#endregion
1093
1086
  //#region lib/types/client/CustomProviderCard.js
1094
1087
  /**
1095
1088
  * The card that declares a provider pi-ai does not ship — an OpenAI-compatible
@@ -1130,7 +1123,7 @@ window.__ModuleLoader__.load({
1130
1123
  * @returns the creation card.
1131
1124
  */
1132
1125
  function CustomProviderCard(props) {
1133
- const { taken, protocols, api, t } = props;
1126
+ const { taken, protocols, operations, t } = props;
1134
1127
  const [openedAt] = (0, react.useState)(() => props.revision);
1135
1128
  const [route, setRoute] = (0, react.useState)("");
1136
1129
  const [displayName, setDisplayName] = (0, react.useState)("");
@@ -1168,17 +1161,17 @@ window.__ModuleLoader__.load({
1168
1161
  baseURL,
1169
1162
  models: models.map((model) => ({ ...model }))
1170
1163
  };
1171
- const response = await api.settings.mutate(NS$1, [{
1164
+ const written = await operations.writeSettings(NS$1, [{
1172
1165
  op: "set",
1173
1166
  path: ["providers", route],
1174
1167
  value: profile
1175
1168
  }], openedAt);
1176
- if (!response.ok) return response.error.message;
1169
+ if (written.kind !== "written") return written.kind === "conflict" ? t("conflict") : written.message;
1177
1170
  setCommitted(true);
1178
1171
  }
1179
1172
  if (storesKey) {
1180
- const stored = await api.credentials.set(keyRef, keyValue);
1181
- if (!stored.ok) return stored.error.message;
1173
+ const stored = await operations.storeCredential(keyRef, keyValue);
1174
+ if (stored !== void 0) return stored;
1182
1175
  }
1183
1176
  };
1184
1177
  const create = async () => {
@@ -1191,8 +1184,6 @@ window.__ModuleLoader__.load({
1191
1184
  return;
1192
1185
  }
1193
1186
  props.onClose(true);
1194
- } catch (error) {
1195
- setFailure(messageOf(error));
1196
1187
  } finally {
1197
1188
  setBusy(false);
1198
1189
  }
@@ -1319,7 +1310,7 @@ window.__ModuleLoader__.load({
1319
1310
  ...keyValue.length === 0 ? {} : { apiKey: keyValue }
1320
1311
  },
1321
1312
  probeBlocked: keyFailure === "keyBlank" ? "keyBlankNew" : keyFailure,
1322
- api,
1313
+ operations,
1323
1314
  t,
1324
1315
  disabled: profileDisabled
1325
1316
  }),
@@ -1424,7 +1415,7 @@ window.__ModuleLoader__.load({
1424
1415
  * @returns the editor card.
1425
1416
  */
1426
1417
  function ProviderEditor(props) {
1427
- const { namespace, schema, settingsPath, api, t } = props;
1418
+ const { namespace, schema, settingsPath, operations, t } = props;
1428
1419
  const [draft, setDraft] = (0, react.useState)(() => draftAt(schema, namespace, settingsPath));
1429
1420
  const [keyDraft, setKeyDraft] = (0, react.useState)("");
1430
1421
  const [keyState, setKeyState] = (0, react.useState)(void 0);
@@ -1450,14 +1441,14 @@ window.__ModuleLoader__.load({
1450
1441
  (0, react.useEffect)(() => {
1451
1442
  let stale = false;
1452
1443
  setKeyState(void 0);
1453
- api.credentials.describe([keyRef]).then((response) => {
1454
- if (stale || !response.ok) return;
1455
- setKeyState(response.value[keyRef]);
1456
- }, () => void 0);
1444
+ operations.describeCredential(keyRef).then((described) => {
1445
+ if (stale) return;
1446
+ setKeyState(described);
1447
+ });
1457
1448
  return () => {
1458
1449
  stale = true;
1459
1450
  };
1460
- }, [api.credentials, keyRef]);
1451
+ }, [operations, keyRef]);
1461
1452
  const stringAt = (source, key) => {
1462
1453
  const value = schema.getPath(source, [key]);
1463
1454
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
@@ -1505,15 +1496,15 @@ window.__ModuleLoader__.load({
1505
1496
  value: {}
1506
1497
  }] : pathOps(settingsPath, committedOriginal, next);
1507
1498
  if (ops.length > 0) {
1508
- const response = await api.settings.mutate(ns, ops, expectedRevision);
1509
- if (!response.ok) return response.error.code === "settings-conflict" ? t("conflict") : response.error.message;
1510
- setCommittedOriginal(schema.getPath(response.value.user, settingsPath));
1511
- setExpectedRevision(response.value.revision);
1499
+ const written = await operations.writeSettings(ns, ops, expectedRevision);
1500
+ if (written.kind !== "written") return written.kind === "conflict" ? t("conflict") : written.message;
1501
+ setCommittedOriginal(schema.getPath(written.view.user, settingsPath));
1502
+ setExpectedRevision(written.view.revision);
1512
1503
  setDraft(next);
1513
1504
  }
1514
1505
  if (keyValue.length > 0) {
1515
- const stored = await api.credentials.set(keyRef, keyValue);
1516
- if (!stored.ok) return stored.error.message;
1506
+ const stored = await operations.storeCredential(keyRef, keyValue);
1507
+ if (stored !== void 0) return stored;
1517
1508
  }
1518
1509
  setKeyDraft("");
1519
1510
  };
@@ -1527,8 +1518,6 @@ window.__ModuleLoader__.load({
1527
1518
  return;
1528
1519
  }
1529
1520
  props.onClose(true);
1530
- } catch (error) {
1531
- setFailure(messageOf(error));
1532
1521
  } finally {
1533
1522
  setBusy(false);
1534
1523
  }
@@ -1677,7 +1666,7 @@ window.__ModuleLoader__.load({
1677
1666
  ...catalogProps,
1678
1667
  probe,
1679
1668
  probeBlocked: keyFailure,
1680
- api
1669
+ operations
1681
1670
  })
1682
1671
  ]
1683
1672
  })]
@@ -1756,25 +1745,21 @@ window.__ModuleLoader__.load({
1756
1745
  * and the whole operation safely retryable; both unsets are idempotent.
1757
1746
  * The settings removal names the profile rather than rebuilding its whole
1758
1747
  * namespace from a partial view.
1759
- * @param api - settings and credential wire faces.
1748
+ * @param operations - the page's Host operations.
1760
1749
  * @param controller - the page store to refresh.
1761
1750
  * @param target - the provider's settings address and optional managed credential.
1762
1751
  * @returns the failure message, or undefined once the write and reload landed.
1763
1752
  */
1764
- async function removeProviderProfile(api, controller, target) {
1765
- try {
1766
- if (target.credentialRef !== void 0) {
1767
- const credential = await api.credentials.unset(target.credentialRef);
1768
- if (!credential.ok) return credential.error.message;
1769
- }
1770
- const response = await api.settings.mutate(target.settingsNs, [{
1771
- op: "unset",
1772
- path: [...target.settingsPath]
1773
- }], void 0);
1774
- if (!response.ok) return response.error.message;
1775
- } catch (error) {
1776
- return messageOf(error);
1753
+ async function removeProviderProfile(operations, controller, target) {
1754
+ if (target.credentialRef !== void 0) {
1755
+ const credential = await operations.removeCredential(target.credentialRef);
1756
+ if (credential !== void 0) return credential;
1777
1757
  }
1758
+ const written = await operations.writeSettings(target.settingsNs, [{
1759
+ op: "unset",
1760
+ path: [...target.settingsPath]
1761
+ }], void 0);
1762
+ if (written.kind !== "written") return written.message;
1778
1763
  await controller.load();
1779
1764
  }
1780
1765
  /**
@@ -1827,13 +1812,13 @@ window.__ModuleLoader__.load({
1827
1812
  * @returns the section, or null while the shell has not injected yet.
1828
1813
  */
1829
1814
  function ModelsSection(props) {
1830
- const { controller, useSnapshot, api, schema, t, renderSlot } = props;
1831
- if (controller === void 0 || useSnapshot === void 0 || api === void 0 || schema === void 0 || t === void 0) return null;
1815
+ const { controller, useSnapshot, operations, schema, t, renderSlot } = props;
1816
+ if (controller === void 0 || useSnapshot === void 0 || operations === void 0 || schema === void 0 || t === void 0) return null;
1832
1817
  return (0, react_jsx_runtime.jsx)(Loaded, {
1833
1818
  injected: {
1834
1819
  controller,
1835
1820
  useSnapshot,
1836
- api,
1821
+ operations,
1837
1822
  schema,
1838
1823
  t
1839
1824
  },
@@ -1841,7 +1826,7 @@ window.__ModuleLoader__.load({
1841
1826
  });
1842
1827
  }
1843
1828
  function Loaded({ injected, renderSlot }) {
1844
- const { controller, api, schema, t } = injected;
1829
+ const { controller, operations, schema, t } = injected;
1845
1830
  const state = injected.useSnapshot((snapshot) => snapshot);
1846
1831
  const [editing, setEditing] = (0, react.useState)(void 0);
1847
1832
  const [adding, setAdding] = (0, react.useState)(false);
@@ -1883,7 +1868,7 @@ window.__ModuleLoader__.load({
1883
1868
  if (deleteTarget === void 0 || deleting) return;
1884
1869
  setDeleting(true);
1885
1870
  setDeleteFailure(void 0);
1886
- removeProviderProfile(api, controller, deleteTarget).then((failure) => {
1871
+ removeProviderProfile(operations, controller, deleteTarget).then((failure) => {
1887
1872
  if (failure !== void 0) {
1888
1873
  setDeleteFailure(failure);
1889
1874
  return;
@@ -1958,7 +1943,7 @@ window.__ModuleLoader__.load({
1958
1943
  target,
1959
1944
  namespace,
1960
1945
  schema,
1961
- api,
1946
+ operations,
1962
1947
  t,
1963
1948
  readOnly: !state.writable,
1964
1949
  onClose: (changed) => {
@@ -2037,7 +2022,7 @@ window.__ModuleLoader__.load({
2037
2022
  target,
2038
2023
  namespace,
2039
2024
  schema,
2040
- api,
2025
+ operations,
2041
2026
  t,
2042
2027
  readOnly: !state.writable,
2043
2028
  onClose: (changed) => {
@@ -2081,7 +2066,7 @@ window.__ModuleLoader__.load({
2081
2066
  namespace: addNamespace,
2082
2067
  schema,
2083
2068
  settingsPath: addTarget.settingsPath,
2084
- api,
2069
+ operations,
2085
2070
  t,
2086
2071
  readOnly: !state.writable,
2087
2072
  onClose: (changed) => {
@@ -2101,7 +2086,7 @@ window.__ModuleLoader__.load({
2101
2086
  protocols,
2102
2087
  /* v8 ignore next -- the card only opens from a button disabled without this namespace */
2103
2088
  revision: state.namespaces.get("llm-pi-ai")?.revision ?? 0,
2104
- api,
2089
+ operations,
2105
2090
  t,
2106
2091
  readOnly: !state.writable,
2107
2092
  onClose: (changed) => {
@@ -2265,7 +2250,7 @@ window.__ModuleLoader__.load({
2265
2250
  * @returns the onboarding modal or null when onboarding needs no intervention.
2266
2251
  */
2267
2252
  function DeepSeekOnboardingDialog(props) {
2268
- const { complete, controller, useModels, api, schema, t } = props;
2253
+ const { complete, controller, useModels, operations, schema, t } = props;
2269
2254
  const state = useModels((snapshot) => snapshot);
2270
2255
  const readiness = onboardingReadiness(state);
2271
2256
  (0, react.useEffect)(() => {
@@ -2307,7 +2292,7 @@ window.__ModuleLoader__.load({
2307
2292
  namespace,
2308
2293
  schema,
2309
2294
  settingsPath: row.entry.settingsPath,
2310
- api,
2295
+ operations,
2311
2296
  t,
2312
2297
  readOnly: false,
2313
2298
  hideTitle: true,
@@ -2533,6 +2518,61 @@ window.__ModuleLoader__.load({
2533
2518
  }
2534
2519
  };
2535
2520
  //#endregion
2521
+ //#region lib/types/client/operations.js
2522
+ /**
2523
+ * The Host reads and writes the Models cards perform, as callbacks built in the
2524
+ * plugin body. Cards receive these instead of a context: the outcomes name what
2525
+ * a card renders — a stored view, a stale revision, a refusal message — so the
2526
+ * failure codes and Remote namespaces stay in the apply world.
2527
+ */
2528
+ /**
2529
+ * Bind the page's Host operations to the plugin's own Remote namespaces.
2530
+ * @param ctx - the page plugin's context, which declares `remote.credentials`,
2531
+ * `remote.llm`, and `remote.settings` in its own `inject`.
2532
+ * @returns the callbacks the section and its cards are injected with.
2533
+ */
2534
+ function createModelsOperations(ctx) {
2535
+ return {
2536
+ describeCredential: async (ref) => {
2537
+ const response = await ctx.remote.credentials.describe([ref]);
2538
+ return response.ok ? response.value[ref] : void 0;
2539
+ },
2540
+ storeCredential: async (ref, value) => {
2541
+ const response = await ctx.remote.credentials.set(ref, value);
2542
+ return response.ok ? void 0 : response.error.message;
2543
+ },
2544
+ removeCredential: async (ref) => {
2545
+ const response = await ctx.remote.credentials.unset(ref);
2546
+ return response.ok ? void 0 : response.error.message;
2547
+ },
2548
+ writeSettings: async (ns, ops, expectedRevision) => {
2549
+ const response = await ctx.remote.settings.mutate(ns, ops, expectedRevision);
2550
+ if (response.ok) return {
2551
+ kind: "written",
2552
+ view: response.value
2553
+ };
2554
+ const { code, message } = response.error;
2555
+ return code === "settings/conflict" ? {
2556
+ kind: "conflict",
2557
+ message
2558
+ } : {
2559
+ kind: "refused",
2560
+ message
2561
+ };
2562
+ },
2563
+ discoverModels: async (settingsNs, request) => {
2564
+ const response = await ctx.remote.llm.discoverModels(settingsNs, request);
2565
+ return response.ok ? {
2566
+ kind: "found",
2567
+ models: response.value
2568
+ } : {
2569
+ kind: "refused",
2570
+ message: response.error.message
2571
+ };
2572
+ }
2573
+ };
2574
+ }
2575
+ //#endregion
2536
2576
  //#region lib/types/client/schema-operations.js
2537
2577
  /**
2538
2578
  * Hide the Cordis service identity behind bound schema callbacks.
@@ -2793,24 +2833,20 @@ window.__ModuleLoader__.load({
2793
2833
  en
2794
2834
  }), "ui-settings-models: copy dictionaries");
2795
2835
  const schema = createSettingsSchemaOperations(ctx.settingsSchema);
2796
- const wire = {
2797
- credentials: ctx.remote.credentials,
2798
- llm: ctx.remote.llm,
2799
- settings: ctx.remote.settings
2800
- };
2801
- const controller = new ModelsSettingsStore(wire, schema, ctx.settingsScope.describe());
2836
+ const operations = createModelsOperations(ctx);
2837
+ const controller = new ModelsSettingsStore(ctx, schema, ctx.settingsScope.describe());
2802
2838
  const t = ctx.locale.bind(NS);
2803
2839
  const injected = () => ({
2804
2840
  controller,
2805
2841
  hooks: { snapshot: controller.store },
2806
- api: wire,
2842
+ operations,
2807
2843
  schema,
2808
2844
  t
2809
2845
  });
2810
2846
  const deepSeekOnboardingInjected = () => ({
2811
2847
  controller,
2812
2848
  hooks: { models: controller.store },
2813
- api: wire,
2849
+ operations,
2814
2850
  schema,
2815
2851
  t
2816
2852
  });
@@ -21,7 +21,7 @@
21
21
  * levels instead.
22
22
  */
23
23
  import type { ReactNode } from 'react';
24
- import type { ModelsWire } from './store.ts';
24
+ import type { ModelsOperations } from './operations.ts';
25
25
  import type { en } from './locales.ts';
26
26
  /** Props of {@link CustomProviderCard}. */
27
27
  export interface CustomProviderCardProps {
@@ -35,8 +35,8 @@ export interface CustomProviderCardProps {
35
35
  * than a silent overwrite of its whole profile.
36
36
  */
37
37
  revision: number;
38
- /** Wire faces for the write and for interrogating the endpoint. */
39
- api: ModelsWire;
38
+ /** The Host operations this card writes and interrogates through. */
39
+ operations: ModelsOperations;
40
40
  /** Section copy. */
41
41
  t: (key: keyof typeof en) => string;
42
42
  /** Disable writes (read-only settings provider). */
@@ -8,7 +8,8 @@
8
8
  import type { ReactNode } from 'react';
9
9
  import type { SnapshotStore } from '@prettier-ai/dsh-client-store';
10
10
  import type { InjectFace, PropsRuntime } from '@prettier-ai/dsh-client-ui-slots';
11
- import type { ModelsSettingsState, ModelsSettingsStore, ModelsWire } from './store.ts';
11
+ import type { ModelsSettingsState, ModelsSettingsStore } from './store.ts';
12
+ import type { ModelsOperations } from './operations.ts';
12
13
  import type { SettingsSchemaOperations } from './schema-operations.ts';
13
14
  import type { en } from './locales.ts';
14
15
  /** Registration-side dependencies of {@link DeepSeekOnboardingDialog}. */
@@ -19,8 +20,8 @@ export interface DeepSeekOnboardingInjected {
19
20
  };
20
21
  /** Shared Models-page join controller. */
21
22
  controller: ModelsSettingsStore;
22
- /** Existing wire face reused by the Models credential editor. */
23
- api: ModelsWire;
23
+ /** The Host operations the reused Models credential editor writes through. */
24
+ operations: ModelsOperations;
24
25
  /** Settings schema and immutable path callbacks. */
25
26
  schema: SettingsSchemaOperations;
26
27
  /** Feature copy. */
@@ -14,8 +14,8 @@
14
14
  * rows the user can still fill in by hand.
15
15
  */
16
16
  import type { ReactNode } from 'react';
17
+ import type { ModelsOperations } from './operations.ts';
17
18
  import type { DeepSeekModelDraft } from './DeepSeekModelsEditor.tsx';
18
- import { type ModelsWire } from './store.ts';
19
19
  import type { en } from './locales.ts';
20
20
  /**
21
21
  * One configured model row. Fields this card does not edit must survive an
@@ -58,8 +58,8 @@ export interface ModelListEditorProps {
58
58
  * told what the field already says.
59
59
  */
60
60
  probeBlocked?: keyof typeof en | undefined;
61
- /** Wire face the fetch action calls. */
62
- api: Pick<ModelsWire, 'llm'>;
61
+ /** The Host operations whose interrogation answers the fetch action. */
62
+ operations: ModelsOperations;
63
63
  /** Section copy. */
64
64
  t: (key: keyof typeof en) => string;
65
65
  /** Disable every control (read-only deployment or a pending write). */
@@ -13,7 +13,8 @@
13
13
  */
14
14
  import type { ReactNode } from 'react';
15
15
  import type { InjectFace, PropsRenderSlots } from '@prettier-ai/dsh-client-ui-slots';
16
- import type { ModelsSettingsStore, ModelsWire, ProviderRow } from './store.ts';
16
+ import type { ModelsSettingsStore, ProviderRow } from './store.ts';
17
+ import type { ModelsOperations } from './operations.ts';
17
18
  import type { SettingsSchemaOperations } from './schema-operations.ts';
18
19
  import type { en } from './locales.ts';
19
20
  /** Injected dependencies of {@link ModelsSection} (slot `inject`). */
@@ -24,8 +25,8 @@ export interface ModelsSectionInjected {
24
25
  /** Page snapshot bound by the UI renderer as useSnapshot. */
25
26
  snapshot: ModelsSettingsStore['store'];
26
27
  };
27
- /** Wire faces the editor writes through. */
28
- api: ModelsWire;
28
+ /** The Host operations the section and its cards invoke. */
29
+ operations: ModelsOperations;
29
30
  /** Settings schema and immutable path callbacks. */
30
31
  schema: SettingsSchemaOperations;
31
32
  /** Section copy. */
@@ -54,12 +55,12 @@ export interface ProviderIdentity {
54
55
  * and the whole operation safely retryable; both unsets are idempotent.
55
56
  * The settings removal names the profile rather than rebuilding its whole
56
57
  * namespace from a partial view.
57
- * @param api - settings and credential wire faces.
58
+ * @param operations - the page's Host operations.
58
59
  * @param controller - the page store to refresh.
59
60
  * @param target - the provider's settings address and optional managed credential.
60
61
  * @returns the failure message, or undefined once the write and reload landed.
61
62
  */
62
- export declare function removeProviderProfile(api: Pick<ModelsWire, 'settings' | 'credentials'>, controller: ModelsSettingsStore, target: {
63
+ export declare function removeProviderProfile(operations: ModelsOperations, controller: ModelsSettingsStore, target: {
63
64
  settingsNs: string;
64
65
  settingsPath: readonly string[];
65
66
  credentialRef?: string;
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import type { ReactNode } from 'react';
24
24
  import type { SettingsNamespaceView, SettingsPathOpView } from '@prettier-ai/dsh-api-remotes/client';
25
- import type { ModelsWire } from './store.ts';
25
+ import type { ModelsOperations } from './operations.ts';
26
26
  import type { SettingsSchemaOperations } from './schema-operations.ts';
27
27
  import type { en } from './locales.ts';
28
28
  /** Props of {@link ProviderEditor}. */
@@ -47,8 +47,8 @@ export interface ProviderEditorProps {
47
47
  schema: SettingsSchemaOperations;
48
48
  /** Path from the section root to this provider's profile. */
49
49
  settingsPath: readonly string[];
50
- /** Wire faces for writes and for interrogating a provider endpoint. */
51
- api: ModelsWire;
50
+ /** The Host operations this card writes and interrogates through. */
51
+ operations: ModelsOperations;
52
52
  /** Section copy. */
53
53
  t: (key: keyof typeof en) => string;
54
54
  /** Disable writes (read-only settings provider). */
@@ -18,7 +18,8 @@ declare module '@prettier-ai/dsh-client-ui-slots' {
18
18
  'settings.models': ModelsKey;
19
19
  }
20
20
  }
21
- export type { ModelsCredentials, ModelsLlm, ModelsSettingsState, ModelsWire, ProviderDirectoryEntry, ProviderRow, } from './store.ts';
21
+ export type { ModelsSettingsState, ProviderDirectoryEntry, ProviderRow, } from './store.ts';
22
+ export type { ModelDiscoveryOutcome, ModelsOperations, SettingsWriteOutcome } from './operations.ts';
22
23
  /**
23
24
  * Refetch the page snapshot only after its first load: an unopened Models
24
25
  * page must not fetch on background invalidations.
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The Host reads and writes the Models cards perform, as callbacks built in the
3
+ * plugin body. Cards receive these instead of a context: the outcomes name what
4
+ * a card renders — a stored view, a stale revision, a refusal message — so the
5
+ * failure codes and Remote namespaces stay in the apply world.
6
+ */
7
+ import type { Context as ClientContext } from '@prettier-ai/cordis';
8
+ import type { CredentialInfo, LlmDiscoveredModel, LlmModelDiscoveryRequest, SettingsNamespaceView, SettingsPathOpView } from '@prettier-ai/dsh-api-remotes/client';
9
+ /** What one namespace write answered. */
10
+ export type SettingsWriteOutcome =
11
+ /** Committed; the view carries the stored user subtree and the new revision. */
12
+ {
13
+ readonly kind: 'written';
14
+ readonly view: SettingsNamespaceView;
15
+ }
16
+ /**
17
+ * The stored revision moved after the card read it, so the draft is stale.
18
+ * The message stays for callers that report the Host diagnostic as it is.
19
+ */
20
+ | {
21
+ readonly kind: 'conflict';
22
+ readonly message: string;
23
+ }
24
+ /** Any other refusal, with the Host's own diagnostic. */
25
+ | {
26
+ readonly kind: 'refused';
27
+ readonly message: string;
28
+ };
29
+ /** What one endpoint interrogation answered. */
30
+ export type ModelDiscoveryOutcome =
31
+ /** The candidates the provider disclosed, in its own order. */
32
+ {
33
+ readonly kind: 'found';
34
+ readonly models: readonly LlmDiscoveredModel[];
35
+ }
36
+ /** The interrogation was refused, with the Host's own diagnostic. */
37
+ | {
38
+ readonly kind: 'refused';
39
+ readonly message: string;
40
+ };
41
+ /** The Host operations the Models page and its cards invoke. */
42
+ export interface ModelsOperations {
43
+ /**
44
+ * Read one credential reference's state.
45
+ * @param ref - credential reference name.
46
+ * @returns the state, or undefined when the reference is unknown or the read was refused.
47
+ */
48
+ describeCredential(ref: string): Promise<CredentialInfo | undefined>;
49
+ /**
50
+ * Store one credential literal under its reference.
51
+ * @param ref - credential reference name.
52
+ * @param value - the literal to store.
53
+ * @returns the refusal message, or undefined once stored.
54
+ */
55
+ storeCredential(ref: string, value: string): Promise<string | undefined>;
56
+ /**
57
+ * Remove one credential reference (idempotent).
58
+ * @param ref - credential reference name.
59
+ * @returns the refusal message, or undefined once removed.
60
+ */
61
+ removeCredential(ref: string): Promise<string | undefined>;
62
+ /**
63
+ * Apply path operations to one settings namespace.
64
+ * @param ns - settings namespace identity.
65
+ * @param ops - ordered path operations against the stored section, as the
66
+ * wire takes them (the Remote signature owns the array).
67
+ * @param expectedRevision - revision the draft was opened at, or undefined to write unfenced.
68
+ * @returns the write outcome the card renders from.
69
+ */
70
+ writeSettings(ns: string, ops: SettingsPathOpView[], expectedRevision: number | undefined): Promise<SettingsWriteOutcome>;
71
+ /**
72
+ * Ask a provider endpoint what models it serves.
73
+ * @param settingsNs - namespace whose adapter family answers.
74
+ * @param request - endpoint facts as the form currently shows them.
75
+ * @returns the candidates, or the refusal.
76
+ */
77
+ discoverModels(settingsNs: string, request: LlmModelDiscoveryRequest): Promise<ModelDiscoveryOutcome>;
78
+ }
79
+ /**
80
+ * Bind the page's Host operations to the plugin's own Remote namespaces.
81
+ * @param ctx - the page plugin's context, which declares `remote.credentials`,
82
+ * `remote.llm`, and `remote.settings` in its own `inject`.
83
+ * @returns the callbacks the section and its cards are injected with.
84
+ */
85
+ export declare function createModelsOperations(ctx: ClientContext): ModelsOperations;
86
+ //# sourceMappingURL=operations.d.ts.map
@@ -6,14 +6,11 @@
6
6
  * single fact source — every mutation writes through the wire and the page
7
7
  * re-renders from the next describe, pushed or refetched.
8
8
  */
9
- import type { ClientRemote, CredentialInfo, LlmConfigurableProvider, LlmProviderInfo, SettingsNamespaceView } from '@prettier-ai/dsh-api-remotes/client';
9
+ import type { Context as ClientContext } from '@prettier-ai/cordis';
10
+ import type { CredentialInfo, LlmConfigurableProvider, LlmProviderInfo, SettingsNamespaceView } from '@prettier-ai/dsh-api-remotes/client';
10
11
  import type { SnapshotStore } from '@prettier-ai/dsh-client-store';
11
- import type { SettingsDescribeFace, SettingsRemote } from '@prettier-ai/dsh-client-ui-settings/client';
12
+ import type { SettingsDescribeFace } from '@prettier-ai/dsh-client-ui-settings/client';
12
13
  import type { SettingsSchemaOperations } from './schema-operations.ts';
13
- /** The credentials Remote methods the Models page reads and writes through. */
14
- export type ModelsCredentials = Pick<ClientRemote['credentials'], 'describe' | 'set' | 'unset'>;
15
- /** LLM Remote methods used by the Models page. */
16
- export type ModelsLlm = Pick<ClientRemote['llm'], 'discoverModels' | 'listConfigurableProviders' | 'listProviders'>;
17
14
  /** One provider row after joining the configurable directory with live routes. */
18
15
  export interface ProviderDirectoryEntry {
19
16
  readonly provider: string;
@@ -30,17 +27,6 @@ export interface ProviderDirectoryEntry {
30
27
  * @returns declared rows followed by live routes with no declaration.
31
28
  */
32
29
  export declare function joinProviderDirectory(registered: readonly LlmProviderInfo[], directory: readonly LlmConfigurableProvider[]): ProviderDirectoryEntry[];
33
- /**
34
- * Every Remote wire face the Models page reaches.
35
- */
36
- export interface ModelsWire {
37
- /** The settings Remote namespace: the redacted read and the profile writes. */
38
- settings: SettingsRemote;
39
- /** Credential state and writes for the references provider profiles name. */
40
- credentials: ModelsCredentials;
41
- /** Provider directory reads and draft endpoint discovery. */
42
- llm: ModelsLlm;
43
- }
44
30
  /** One provider row the page renders. */
45
31
  export interface ProviderRow {
46
32
  /** The directory entry (route id, display name, settings address, live state). */
@@ -75,14 +61,6 @@ export interface ModelsSettingsState {
75
61
  /** Namespace views by ns, for the editor's schema/layers/secrets. */
76
62
  namespaces: ReadonlyMap<string, SettingsNamespaceView>;
77
63
  }
78
- /**
79
- * Human text for a rejected wire call. A transport failure rejects with an
80
- * Error; a host or a runtime can reject with anything, and the page still has
81
- * to say something.
82
- * @param error - the rejection value.
83
- * @returns the message to show.
84
- */
85
- export declare function messageOf(error: unknown): string;
86
64
  /**
87
65
  * Derive the conventional credential reference for a provider route: the v1
88
66
  * page never asks for an environment-variable name, so a typed key stores
@@ -103,7 +81,7 @@ export declare function deriveKeyRef(provider: string): string;
103
81
  export declare function protocolChoices(namespace: SettingsNamespaceView | undefined, schema: SettingsSchemaOperations): string[];
104
82
  /** The models settings page controller (one per settings surface). */
105
83
  export declare class ModelsSettingsStore {
106
- private readonly api;
84
+ private readonly ctx;
107
85
  private readonly schema;
108
86
  private readonly describeFace;
109
87
  /** The snapshot the section renders from (uSES-safe store). */
@@ -111,10 +89,12 @@ export declare class ModelsSettingsStore {
111
89
  /** Latest load wins; an older response never overwrites a newer one. */
112
90
  private generation;
113
91
  /**
114
- * @param api - the page's credentials Remote and LLM wire faces.
92
+ * @param ctx - the page plugin's context, whose `remote.llm` and
93
+ * `remote.credentials` namespaces carry the directory and credential reads.
94
+ * @param schema - settings-owned schema and immutable path operations.
115
95
  * @param describeFace - the shared mirror's describe face (namespace views and writability).
116
96
  */
117
- constructor(api: Pick<ModelsWire, 'credentials' | 'llm'>, schema: SettingsSchemaOperations, describeFace: SettingsDescribeFace);
97
+ constructor(ctx: ClientContext, schema: SettingsSchemaOperations, describeFace: SettingsDescribeFace);
118
98
  /**
119
99
  * Refresh the whole page snapshot: the provider directory and the mirror's
120
100
  * settings answer in parallel, then one batched credential describe over
@@ -124,6 +104,8 @@ export declare class ModelsSettingsStore {
124
104
  * @returns nothing; the snapshot carries the outcome.
125
105
  */
126
106
  load(): Promise<void>;
107
+ /** Publish one load's failure text, unless a newer load already took over. */
108
+ private failLoad;
127
109
  }
128
110
  /**
129
111
  * Whether a joined row can serve model requests as it stands: the route is
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prettier-ai/dsh-client-ui-settings-models",
3
3
  "description": "Models settings and shared product-onboarding dialogs over existing settings and credential joins",
4
- "version": "0.1.2-alpha.1",
4
+ "version": "0.1.2-alpha.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -41,26 +41,22 @@
41
41
  },
42
42
  "license": "MIT",
43
43
  "peerDependencies": {
44
- "@prettier-ai/dsh-api-remotes": "^0.1.2-alpha.1",
45
- "@prettier-ai/cordis": "^4.0.1",
46
- "@prettier-ai/dsh-client-locale": "^0.1.2-alpha.1",
47
- "@prettier-ai/dsh-client-ui-settings": "^0.1.2-alpha.1",
48
- "@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.1",
49
- "@prettier-ai/dsh-invariants": "^0.1.2-alpha.1"
44
+ "@prettier-ai/cordis": "^4.0.2"
50
45
  },
51
46
  "devDependencies": {
52
47
  "@types/react": "~18.3.1",
53
48
  "react": "^18.2.0",
54
- "@prettier-ai/dsh-api-remotes": "^0.1.2-alpha.1",
55
- "@prettier-ai/dsh-client-store": "^0.1.2-alpha.1",
56
- "@prettier-ai/dsh-client-locale": "^0.1.2-alpha.1",
57
- "@prettier-ai/dsh-client-test-runtime": "^0.1.2-alpha.1",
58
- "@prettier-ai/dsh-client-ui-settings": "^0.1.2-alpha.1",
59
- "@prettier-ai/dsh-client-ui-primitives": "^0.1.2-alpha.1",
60
- "@prettier-ai/dsh-client-ui-slots": "^0.1.2-alpha.1",
61
- "@prettier-ai/dsh-invariants": "^0.1.2-alpha.1",
62
- "@prettier-ai/cordis": "^4.0.1",
63
- "@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.1"
49
+ "@prettier-ai/dsh-api-remotes": "^0.1.2-alpha.3",
50
+ "@prettier-ai/dsh-client-locale": "^0.1.2-alpha.3",
51
+ "@prettier-ai/dsh-client-ui-settings": "^0.1.2-alpha.3",
52
+ "@prettier-ai/dsh-client-test-runtime": "^0.1.2-alpha.3",
53
+ "@prettier-ai/dsh-client-ui-slots": "^0.1.2-alpha.3",
54
+ "@prettier-ai/dsh-client-store": "^0.1.2-alpha.3",
55
+ "@prettier-ai/dsh-client-ui-primitives": "^0.1.2-alpha.3",
56
+ "@prettier-ai/dsh-invariants": "^0.1.2-alpha.3",
57
+ "@prettier-ai/cordis": "^4.0.2",
58
+ "@prettier-ai/dsh-client-ui-renderer": "^0.1.2-alpha.3",
59
+ "@prettier-ai/dsh-util-values": "^0.1.2-alpha.3"
64
60
  },
65
61
  "files": [
66
62
  "lib/index.js",