@ai-matrx/agents 0.59.225 → 0.59.227

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,10 +1,20 @@
1
1
  # Changelog
2
2
 
3
- ## 0.59.225
3
+ ## 0.59.227
4
4
 
5
5
  UNVERIFIED: this release has no Unreleased entry and the source comparison could not be made (the caller supplied no source comparison). Whether source changed is unknown; consumer action is unknown.
6
6
  Check the package's gitHead on npm against this version's gitHead: `git diff <previous gitHead> <this gitHead> -- apps/shared/matrx-agents/src`.
7
7
 
8
+ ## 0.59.225
9
+
10
+ Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff fa2e9e0515 2859805452 -- apps/shared/matrx-agents/src`
11
+ has no shipped-source changes outside tests, fixtures and docs). No consumer action required.
12
+
13
+ ## 0.59.224
14
+
15
+ Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 54d2f38f9e fa2e9e0515 -- apps/shared/matrx-agents/src`
16
+ has no shipped-source changes outside tests, fixtures and docs). No consumer action required.
17
+
8
18
  ## 0.59.223
9
19
 
10
20
  Automatic changed-only republish (docs/metadata drift since the last published version; verified: `git diff 02639b3a61 54d2f38f9e -- apps/shared/matrx-agents/src`
@@ -47,9 +47,9 @@ module.exports = __toCommonJS(mandates_exports);
47
47
 
48
48
  // mandates/keys.generated.ts
49
49
  var MANDATE_KEYS_META = {
50
- aidreamRevision: "285980545216bea41c11ff6e8af8705328508ddc",
50
+ aidreamRevision: "351d20c8350b3bbc8010d4bd5d426f4c4742fb30",
51
51
  generatorVersion: "1.0.0",
52
- packageVersion: "0.59.225",
52
+ packageVersion: "0.59.227",
53
53
  retireWindow: 3
54
54
  };
55
55
  var MANDATE_KEYS = {
@@ -6,9 +6,9 @@
6
6
  * from the IN-PROCESS mandate declarations — the same `declared_mandates()` the
7
7
  * boot sync writes to the database from. There is no hand list anywhere.
8
8
  *
9
- * aidream revision: 285980545216bea41c11ff6e8af8705328508ddc
9
+ * aidream revision: 351d20c8350b3bbc8010d4bd5d426f4c4742fb30
10
10
  * generator version: 1.0.0
11
- * package version: 0.59.225
11
+ * package version: 0.59.227
12
12
  * active keys: 615
13
13
  * retiring keys: 5
14
14
  *
@@ -19,9 +19,9 @@
19
19
  */
20
20
  /** Where this module came from — surfaced so a report can name the exact build. */
21
21
  declare const MANDATE_KEYS_META: {
22
- readonly aidreamRevision: "285980545216bea41c11ff6e8af8705328508ddc";
22
+ readonly aidreamRevision: "351d20c8350b3bbc8010d4bd5d426f4c4742fb30";
23
23
  readonly generatorVersion: "1.0.0";
24
- readonly packageVersion: "0.59.225";
24
+ readonly packageVersion: "0.59.227";
25
25
  readonly retireWindow: 3;
26
26
  };
27
27
  /** Every declared mandate key, by THE IDENTIFIER RULE. */
@@ -6,9 +6,9 @@
6
6
  * from the IN-PROCESS mandate declarations — the same `declared_mandates()` the
7
7
  * boot sync writes to the database from. There is no hand list anywhere.
8
8
  *
9
- * aidream revision: 285980545216bea41c11ff6e8af8705328508ddc
9
+ * aidream revision: 351d20c8350b3bbc8010d4bd5d426f4c4742fb30
10
10
  * generator version: 1.0.0
11
- * package version: 0.59.225
11
+ * package version: 0.59.227
12
12
  * active keys: 615
13
13
  * retiring keys: 5
14
14
  *
@@ -19,9 +19,9 @@
19
19
  */
20
20
  /** Where this module came from — surfaced so a report can name the exact build. */
21
21
  declare const MANDATE_KEYS_META: {
22
- readonly aidreamRevision: "285980545216bea41c11ff6e8af8705328508ddc";
22
+ readonly aidreamRevision: "351d20c8350b3bbc8010d4bd5d426f4c4742fb30";
23
23
  readonly generatorVersion: "1.0.0";
24
- readonly packageVersion: "0.59.225";
24
+ readonly packageVersion: "0.59.227";
25
25
  readonly retireWindow: 3;
26
26
  };
27
27
  /** Every declared mandate key, by THE IDENTIFIER RULE. */
@@ -1,8 +1,8 @@
1
1
  // mandates/keys.generated.ts
2
2
  var MANDATE_KEYS_META = {
3
- aidreamRevision: "285980545216bea41c11ff6e8af8705328508ddc",
3
+ aidreamRevision: "351d20c8350b3bbc8010d4bd5d426f4c4742fb30",
4
4
  generatorVersion: "1.0.0",
5
- packageVersion: "0.59.225",
5
+ packageVersion: "0.59.227",
6
6
  retireWindow: 3
7
7
  };
8
8
  var MANDATE_KEYS = {
@@ -25,7 +25,9 @@ __export(react_exports, {
25
25
  ModelCatalogProvider: () => ModelCatalogProvider,
26
26
  ModelDiscovery: () => ModelDiscovery,
27
27
  ModelListDropdown: () => ModelListDropdown,
28
+ YOUR_KEY_LABEL: () => YOUR_KEY_LABEL,
28
29
  useLocalModels: () => useLocalModels,
30
+ useModelAccess: () => useModelAccess,
29
31
  useModelCatalog: () => useModelCatalog,
30
32
  useModelCatalogStore: () => useModelCatalogStore,
31
33
  useModelCatalogUiPorts: () => useModelCatalogUiPorts,
@@ -39,7 +41,7 @@ var import_react = require("react");
39
41
  var import_jsx_runtime = require("react/jsx-runtime");
40
42
  var ModelCatalogContext = (0, import_react.createContext)(null);
41
43
  var DefaultLink = ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { ...props, children });
42
- function ModelCatalogProvider({ catalog, favorites, children, localModels, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction }) {
44
+ function ModelCatalogProvider({ catalog, favorites, children, localModels, access, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction }) {
43
45
  const value = (0, import_react.useMemo)(
44
46
  () => ({
45
47
  catalog,
@@ -51,10 +53,11 @@ function ModelCatalogProvider({ catalog, favorites, children, localModels, isSup
51
53
  LinkComponent: LinkComponent ?? DefaultLink,
52
54
  settingsHref: settingsHref ?? null,
53
55
  ...localModels ? { localModels } : {},
56
+ ...access ? { access } : {},
54
57
  ...renderErrorAction ? { renderErrorAction } : {}
55
58
  }
56
59
  }),
57
- [catalog, favorites, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction, localModels]
60
+ [catalog, favorites, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction, localModels, access]
58
61
  );
59
62
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModelCatalogContext.Provider, { value, children });
60
63
  }
@@ -106,6 +109,12 @@ function useLocalModels(override) {
106
109
  );
107
110
  return { models, groupLabel: port?.groupLabel ?? "On this computer", open: port?.open ? (id) => port.open?.(id) : void 0 };
108
111
  }
112
+ var NO_ACCESS = {};
113
+ function useModelAccess(override) {
114
+ const contextPort = useBinding("useModelAccess").ports.access;
115
+ return override ?? contextPort ?? NO_ACCESS;
116
+ }
117
+ var YOUR_KEY_LABEL = "Your key";
109
118
 
110
119
  // models/react/ModelListDropdown.tsx
111
120
  var import_format = require("@ai-matrx/kit/format");
@@ -741,7 +750,8 @@ function ModelDetailCard({
741
750
  isCurrentModel,
742
751
  pinnedOfferingId,
743
752
  onPinOffering,
744
- includeAdminSection
753
+ includeAdminSection,
754
+ yourKey = false
745
755
  }) {
746
756
  const pinnedHere = (t) => !!isCurrentModel && !!pinnedOfferingId && t.offeringId === pinnedOfferingId;
747
757
  const anyPinnedHere = model.tiers.some(pinnedHere);
@@ -806,7 +816,10 @@ function ModelDetailCard({
806
816
  )) })
807
817
  ] }),
808
818
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
809
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "col-span-2", children: [
819
+ yourKey ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "col-span-2", children: [
820
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dt", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "Points" }),
821
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dd", { "data-your-key": true, className: "text-sm font-semibold text-foreground", title: "Runs on your own API key", children: YOUR_KEY_LABEL })
822
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "col-span-2", children: [
810
823
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dt", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "Points / 1M tokens" }),
811
824
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
812
825
  "dd",
@@ -1257,14 +1270,16 @@ function ModelRow({
1257
1270
  selected,
1258
1271
  isFavorite,
1259
1272
  locked = false,
1273
+ blockedReason = null,
1274
+ yourKey = false,
1260
1275
  onSelect,
1261
1276
  onHover,
1262
1277
  onToggleFavorite
1263
1278
  }) {
1264
1279
  const unavailable = variant === "admin" && !(model.admin?.offerings ?? []).some((o) => o.isAvailable);
1265
1280
  const retired = model.retiredAt != null;
1266
- const retiredTitle = retired ? `Retired ${model.retiredAt?.slice(0, 10)} \u2014 the provider no longer serves this model, so it cannot be selected.` : locked ? TOP_TIER_LOCKED_TITLE : void 0;
1267
- const blocked = retired || locked;
1281
+ const retiredTitle = retired ? `Retired ${model.retiredAt?.slice(0, 10)} \u2014 the provider no longer serves this model, so it cannot be selected.` : blockedReason ? blockedReason : locked ? TOP_TIER_LOCKED_TITLE : void 0;
1282
+ const blocked = retired || locked || blockedReason != null;
1268
1283
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1269
1284
  "div",
1270
1285
  {
@@ -1312,55 +1327,67 @@ function ModelRow({
1312
1327
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_brand_glyphs.MakerBrandGlyph, { maker: model.maker, colored: true, className: "h-3.5 w-3.5" })
1313
1328
  }
1314
1329
  ),
1315
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "flex min-w-0 items-center gap-1.5", children: [
1316
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DelayedHint, { label: model.name, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "truncate text-xs font-medium text-foreground", children: model.name }) }),
1317
- isDecisionModelCapability(model) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1318
- "span",
1319
- {
1320
- className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1321
- title: "Decision model \u2014 it answers a Questions part with probabilities and generates no text.",
1322
- children: "decision"
1323
- }
1324
- ),
1325
- model.isDeprecated && !retired && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1326
- "span",
1327
- {
1328
- className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1329
- title: "Deprecated \u2014 still runs; hidden from default selection",
1330
- children: "dep"
1331
- }
1332
- ),
1333
- retired && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1334
- "span",
1335
- {
1336
- className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1337
- title: retiredTitle,
1338
- children: "retired"
1339
- }
1340
- ),
1341
- unavailable && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1342
- "span",
1343
- {
1344
- className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1345
- title: "No available offerings \u2014 not callable",
1346
- children: "off"
1347
- }
1348
- ),
1349
- classTier ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1350
- "span",
1351
- {
1352
- className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1353
- title: `Runs on ${classTier.servedVia}`,
1354
- children: classTier.servedVia
1355
- }
1356
- ) : model.tiers.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1357
- "span",
1358
- {
1359
- className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1360
- title: model.tiers.map((t) => t.servedVia).join(" \xB7 "),
1361
- children: serviceCountLabel(model.tiers.length)
1362
- }
1363
- )
1330
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "flex min-w-0 flex-col", children: [
1331
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "flex min-w-0 items-center gap-1.5", children: [
1332
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DelayedHint, { label: model.name, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "truncate text-xs font-medium text-foreground", children: model.name }) }),
1333
+ yourKey && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1334
+ "span",
1335
+ {
1336
+ "data-your-key": true,
1337
+ className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1338
+ title: "Runs on your own API key",
1339
+ children: YOUR_KEY_LABEL
1340
+ }
1341
+ ),
1342
+ isDecisionModelCapability(model) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1343
+ "span",
1344
+ {
1345
+ className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1346
+ title: "Decision model \u2014 it answers a Questions part with probabilities and generates no text.",
1347
+ children: "decision"
1348
+ }
1349
+ ),
1350
+ model.isDeprecated && !retired && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1351
+ "span",
1352
+ {
1353
+ className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1354
+ title: "Deprecated \u2014 still runs; hidden from default selection",
1355
+ children: "dep"
1356
+ }
1357
+ ),
1358
+ retired && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1359
+ "span",
1360
+ {
1361
+ className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1362
+ title: retiredTitle,
1363
+ children: "retired"
1364
+ }
1365
+ ),
1366
+ unavailable && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1367
+ "span",
1368
+ {
1369
+ className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1370
+ title: "No available offerings \u2014 not callable",
1371
+ children: "off"
1372
+ }
1373
+ ),
1374
+ classTier ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1375
+ "span",
1376
+ {
1377
+ className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1378
+ title: `Runs on ${classTier.servedVia}`,
1379
+ children: classTier.servedVia
1380
+ }
1381
+ ) : model.tiers.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1382
+ "span",
1383
+ {
1384
+ className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1385
+ title: model.tiers.map((t) => t.servedVia).join(" \xB7 "),
1386
+ children: serviceCountLabel(model.tiers.length)
1387
+ }
1388
+ )
1389
+ ] }),
1390
+ blockedReason && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "data-cloud-blocked-reason": true, className: "truncate text-[11px] text-muted-foreground", children: blockedReason })
1364
1391
  ] }),
1365
1392
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpeedCell, { value: model.speedRating }),
1366
1393
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -1435,6 +1462,7 @@ var GROUP_HEADING = "px-2 pb-0.5 pt-1.5 text-[10px] uppercase tracking-wide text
1435
1462
  function ModelListDropdown({
1436
1463
  value: savedValue,
1437
1464
  localModels: localModelsProp,
1465
+ access: accessProp,
1438
1466
  onValueChange,
1439
1467
  inputModalities,
1440
1468
  outputModalities,
@@ -1465,6 +1493,9 @@ function ModelListDropdown({
1465
1493
  const { favoriteSet, toggleFavorite } = useModelFavorites(open);
1466
1494
  const ports = useModelCatalogUiPorts();
1467
1495
  const canUseTopTier = ports.canUseTopTier ?? false;
1496
+ const access = useModelAccess(accessProp);
1497
+ const cloudBlockedReason = access.cloudBlockedReason ?? null;
1498
+ const usesOwnKey = (modelId) => !!modelId && access.usesOwnKey?.(modelId) === true;
1468
1499
  const isSuperAdmin = ports.isSuperAdmin ?? false;
1469
1500
  const LinkComponent = ports.LinkComponent;
1470
1501
  const hiddenModelIds = ports.hiddenModelIds;
@@ -1718,6 +1749,10 @@ function ModelListDropdown({
1718
1749
  };
1719
1750
  const refuseIfRetired = (id2) => {
1720
1751
  const target = models.find((m) => m.id === id2);
1752
+ if (target && cloudBlockedReason) {
1753
+ console.warn(`[ModelListDropdown] Refused to select ${target.name} (${id2}): ${cloudBlockedReason}.`);
1754
+ return true;
1755
+ }
1721
1756
  if (target && !canUseTopTier && isTopTierRating(target.costRating)) {
1722
1757
  console.warn(`[ModelListDropdown] Refused to select ${target.name} (${id2}): ${TOP_TIER_LOCKED_TITLE}.`);
1723
1758
  return true;
@@ -1796,6 +1831,10 @@ function ModelListDropdown({
1796
1831
  selectedClassLabel && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "text-muted-foreground", children: [
1797
1832
  " \xB7 ",
1798
1833
  selectedClassLabel
1834
+ ] }),
1835
+ usesOwnKey(value) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { "data-your-key": true, className: "text-muted-foreground", children: [
1836
+ " \xB7 ",
1837
+ YOUR_KEY_LABEL
1799
1838
  ] })
1800
1839
  ] })
1801
1840
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "min-w-0 whitespace-normal text-left leading-tight text-muted-foreground", children: isLoading ? "Loading models\u2026" : emptyOptionLabel && !value ? emptyOptionLabel : placeholder }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: shown ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
@@ -1812,6 +1851,10 @@ function ModelListDropdown({
1812
1851
  selectedClassLabel && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "text-muted-foreground", children: [
1813
1852
  " \xB7 ",
1814
1853
  selectedClassLabel
1854
+ ] }),
1855
+ usesOwnKey(value) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { "data-your-key": true, className: "text-muted-foreground", children: [
1856
+ " \xB7 ",
1857
+ YOUR_KEY_LABEL
1815
1858
  ] })
1816
1859
  ] })
1817
1860
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "min-w-0 truncate text-muted-foreground", children: isLoading ? "Loading models\u2026" : emptyOptionLabel && !value ? emptyOptionLabel : placeholder }) }),
@@ -1931,6 +1974,8 @@ function ModelListDropdown({
1931
1974
  selected: m.id === value && (classTier == null || classTier.offeringId === selectedClass?.offeringId),
1932
1975
  isFavorite: favoriteSet.has(m.id),
1933
1976
  locked: !canUseTopTier && isTopTierRating(m.costRating),
1977
+ blockedReason: cloudBlockedReason,
1978
+ yourKey: usesOwnKey(m.id),
1934
1979
  onToggleFavorite: () => toggleFavorite(m.id),
1935
1980
  onSelect: () => isMobile ? (setMobileDetail(m), setMobileDetailClass(classTier)) : handleSelectClass(m, classTier),
1936
1981
  onHover: isMobile ? void 0 : () => {
@@ -1998,6 +2043,7 @@ function ModelListDropdown({
1998
2043
  ModelDetailCard,
1999
2044
  {
2000
2045
  model: mobileDetail,
2046
+ yourKey: usesOwnKey(mobileDetail.id),
2001
2047
  tier: costRatingTier(mobileDetail.costRating),
2002
2048
  variant,
2003
2049
  onSelect: () => handleSelect(mobileDetail.id, mobileDetailClass),
@@ -2096,6 +2142,7 @@ function ModelListDropdown({
2096
2142
  ModelDetailCard,
2097
2143
  {
2098
2144
  model: hovered,
2145
+ yourKey: usesOwnKey(hovered.id),
2099
2146
  tier: costRatingTier(hovered.costRating),
2100
2147
  variant,
2101
2148
  onSelect: () => handleSelect(hovered.id, hoveredClass),
@@ -456,8 +456,25 @@ interface LocalModelsPort {
456
456
  */
457
457
  open?(id: string): void;
458
458
  }
459
+ /**
460
+ * How the person may reach a cloud model in THIS place (a chat composer passes it to its own picker,
461
+ * like `localModels`). Both fields are optional; a host that supplies neither changes nothing.
462
+ */
463
+ interface ModelAccessPort {
464
+ /**
465
+ * Set = every cloud model is listed disabled with this one-line reason (never hidden), e.g. a
466
+ * confidential chat in an organization that requires its own hardware: "Blocked by your organization".
467
+ */
468
+ cloudBlockedReason?: string | null;
469
+ /**
470
+ * True when the person's own API key serves this model: the picker and the chat's model chip say
471
+ * "Your key" and show no model points.
472
+ */
473
+ usesOwnKey?(modelId: string): boolean;
474
+ }
459
475
  interface ModelCatalogUiPorts {
460
476
  localModels?: LocalModelsPort;
477
+ access?: ModelAccessPort;
461
478
  isSuperAdmin?: boolean;
462
479
  canUseTopTier?: boolean;
463
480
  hiddenModelIds?: readonly string[] | undefined;
@@ -470,7 +487,7 @@ interface ModelCatalogProviderProps extends ModelCatalogUiPorts {
470
487
  favorites: ModelFavorites;
471
488
  children: ReactNode;
472
489
  }
473
- declare function ModelCatalogProvider({ catalog, favorites, children, localModels, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction }: ModelCatalogProviderProps): react.JSX.Element;
490
+ declare function ModelCatalogProvider({ catalog, favorites, children, localModels, access, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction }: ModelCatalogProviderProps): react.JSX.Element;
474
491
  declare const useModelCatalogStore: () => ModelCatalog;
475
492
  declare const useModelCatalogUiPorts: () => ModelCatalogUiPorts;
476
493
  /**
@@ -498,6 +515,10 @@ declare function useLocalModels(override?: LocalModelsPort): {
498
515
  groupLabel: string;
499
516
  open: ((id: string) => void) | undefined;
500
517
  };
518
+ /** The cloud-access rules for this picker; a port passed straight to the picker beats the provider's. */
519
+ declare function useModelAccess(override?: ModelAccessPort): ModelAccessPort;
520
+ /** The label a model reached through the person's own key carries, in the picker and on the chat's chip. */
521
+ declare const YOUR_KEY_LABEL = "Your key";
501
522
 
502
523
  /**
503
524
  * A model offered in several CLASSES (Matrx Fast, Matrx Lightning, ...) is
@@ -542,6 +563,12 @@ interface ModelListDropdownBaseProps {
542
563
  * supplies one.
543
564
  */
544
565
  localModels?: LocalModelsPort;
566
+ /**
567
+ * Cloud-access rules for THIS picker (a chat composer passes its host's): a blocked reason that
568
+ * lists every cloud model disabled with why, and which models run on the person's own key.
569
+ * Beats the provider's port.
570
+ */
571
+ access?: ModelAccessPort;
545
572
  onValueChange: (modelId: string) => void;
546
573
  /** REQUIRED — input modalities the model must accept (seeds the filter). */
547
574
  inputModalities: Modality[];
@@ -600,7 +627,7 @@ interface ModelListDropdownBaseProps {
600
627
  "aria-label"?: string;
601
628
  "aria-labelledby"?: string;
602
629
  }
603
- declare function ModelListDropdown({ value: savedValue, localModels: localModelsProp, onValueChange, inputModalities, outputModalities, selectionPurpose, allowedModelIds, catalogVariant, emptyOptionLabel, onClear, placeholder, priorityModelIds, pinnedOfferingId, onOfferingPinChange, modelOnly, className, triggerVariant, presentation, id, disabled, disabledTitle, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: ModelListDropdownProps): react.JSX.Element;
630
+ declare function ModelListDropdown({ value: savedValue, localModels: localModelsProp, access: accessProp, onValueChange, inputModalities, outputModalities, selectionPurpose, allowedModelIds, catalogVariant, emptyOptionLabel, onClear, placeholder, priorityModelIds, pinnedOfferingId, onOfferingPinChange, modelOnly, className, triggerVariant, presentation, id, disabled, disabledTitle, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: ModelListDropdownProps): react.JSX.Element;
604
631
 
605
632
  /** Subscribe to a slice of a model catalog's records. `select` must return a stable value for unchanged state. */
606
633
  declare function useModelRecordsSelector<T>(records: ModelRecords, select: (state: ModelRecordsState) => T): T;
@@ -962,4 +989,4 @@ interface DiscoveryResultViewProps {
962
989
  /** A resolved model: its header, the source's reason when the originals will not run, and every version judged. */
963
990
  declare function DiscoveryResultView({ result, device, runtimes, want, chooseLabel, mode, onChoose, renderVariantActions, renderActions }: DiscoveryResultViewProps): react.JSX.Element;
964
991
 
965
- export { type DiscoveryKind, type DiscoveryMode, type DiscoveryPort, DiscoveryResultView, type DiscoveryResultViewProps, type DiscoverySearchHit, type DiscoverySearchPage, type DiscoverySearchQuery, type DiscoverySort, type LocalModelEntry, type LocalModelsPort, type ModelCatalogLinkComponent, ModelCatalogProvider, type ModelCatalogProviderProps, type ModelCatalogUiPorts, ModelDiscovery, type ModelDiscoveryProps, ModelListDropdown, type ModelListDropdownProps, useLocalModels, useModelCatalog, useModelCatalogStore, useModelCatalogUiPorts, useModelFavorites, useModelRecordsSelector };
992
+ export { type DiscoveryKind, type DiscoveryMode, type DiscoveryPort, DiscoveryResultView, type DiscoveryResultViewProps, type DiscoverySearchHit, type DiscoverySearchPage, type DiscoverySearchQuery, type DiscoverySort, type LocalModelEntry, type LocalModelsPort, type ModelAccessPort, type ModelCatalogLinkComponent, ModelCatalogProvider, type ModelCatalogProviderProps, type ModelCatalogUiPorts, ModelDiscovery, type ModelDiscoveryProps, ModelListDropdown, type ModelListDropdownProps, YOUR_KEY_LABEL, useLocalModels, useModelAccess, useModelCatalog, useModelCatalogStore, useModelCatalogUiPorts, useModelFavorites, useModelRecordsSelector };
@@ -456,8 +456,25 @@ interface LocalModelsPort {
456
456
  */
457
457
  open?(id: string): void;
458
458
  }
459
+ /**
460
+ * How the person may reach a cloud model in THIS place (a chat composer passes it to its own picker,
461
+ * like `localModels`). Both fields are optional; a host that supplies neither changes nothing.
462
+ */
463
+ interface ModelAccessPort {
464
+ /**
465
+ * Set = every cloud model is listed disabled with this one-line reason (never hidden), e.g. a
466
+ * confidential chat in an organization that requires its own hardware: "Blocked by your organization".
467
+ */
468
+ cloudBlockedReason?: string | null;
469
+ /**
470
+ * True when the person's own API key serves this model: the picker and the chat's model chip say
471
+ * "Your key" and show no model points.
472
+ */
473
+ usesOwnKey?(modelId: string): boolean;
474
+ }
459
475
  interface ModelCatalogUiPorts {
460
476
  localModels?: LocalModelsPort;
477
+ access?: ModelAccessPort;
461
478
  isSuperAdmin?: boolean;
462
479
  canUseTopTier?: boolean;
463
480
  hiddenModelIds?: readonly string[] | undefined;
@@ -470,7 +487,7 @@ interface ModelCatalogProviderProps extends ModelCatalogUiPorts {
470
487
  favorites: ModelFavorites;
471
488
  children: ReactNode;
472
489
  }
473
- declare function ModelCatalogProvider({ catalog, favorites, children, localModels, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction }: ModelCatalogProviderProps): react.JSX.Element;
490
+ declare function ModelCatalogProvider({ catalog, favorites, children, localModels, access, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction }: ModelCatalogProviderProps): react.JSX.Element;
474
491
  declare const useModelCatalogStore: () => ModelCatalog;
475
492
  declare const useModelCatalogUiPorts: () => ModelCatalogUiPorts;
476
493
  /**
@@ -498,6 +515,10 @@ declare function useLocalModels(override?: LocalModelsPort): {
498
515
  groupLabel: string;
499
516
  open: ((id: string) => void) | undefined;
500
517
  };
518
+ /** The cloud-access rules for this picker; a port passed straight to the picker beats the provider's. */
519
+ declare function useModelAccess(override?: ModelAccessPort): ModelAccessPort;
520
+ /** The label a model reached through the person's own key carries, in the picker and on the chat's chip. */
521
+ declare const YOUR_KEY_LABEL = "Your key";
501
522
 
502
523
  /**
503
524
  * A model offered in several CLASSES (Matrx Fast, Matrx Lightning, ...) is
@@ -542,6 +563,12 @@ interface ModelListDropdownBaseProps {
542
563
  * supplies one.
543
564
  */
544
565
  localModels?: LocalModelsPort;
566
+ /**
567
+ * Cloud-access rules for THIS picker (a chat composer passes its host's): a blocked reason that
568
+ * lists every cloud model disabled with why, and which models run on the person's own key.
569
+ * Beats the provider's port.
570
+ */
571
+ access?: ModelAccessPort;
545
572
  onValueChange: (modelId: string) => void;
546
573
  /** REQUIRED — input modalities the model must accept (seeds the filter). */
547
574
  inputModalities: Modality[];
@@ -600,7 +627,7 @@ interface ModelListDropdownBaseProps {
600
627
  "aria-label"?: string;
601
628
  "aria-labelledby"?: string;
602
629
  }
603
- declare function ModelListDropdown({ value: savedValue, localModels: localModelsProp, onValueChange, inputModalities, outputModalities, selectionPurpose, allowedModelIds, catalogVariant, emptyOptionLabel, onClear, placeholder, priorityModelIds, pinnedOfferingId, onOfferingPinChange, modelOnly, className, triggerVariant, presentation, id, disabled, disabledTitle, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: ModelListDropdownProps): react.JSX.Element;
630
+ declare function ModelListDropdown({ value: savedValue, localModels: localModelsProp, access: accessProp, onValueChange, inputModalities, outputModalities, selectionPurpose, allowedModelIds, catalogVariant, emptyOptionLabel, onClear, placeholder, priorityModelIds, pinnedOfferingId, onOfferingPinChange, modelOnly, className, triggerVariant, presentation, id, disabled, disabledTitle, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: ModelListDropdownProps): react.JSX.Element;
604
631
 
605
632
  /** Subscribe to a slice of a model catalog's records. `select` must return a stable value for unchanged state. */
606
633
  declare function useModelRecordsSelector<T>(records: ModelRecords, select: (state: ModelRecordsState) => T): T;
@@ -962,4 +989,4 @@ interface DiscoveryResultViewProps {
962
989
  /** A resolved model: its header, the source's reason when the originals will not run, and every version judged. */
963
990
  declare function DiscoveryResultView({ result, device, runtimes, want, chooseLabel, mode, onChoose, renderVariantActions, renderActions }: DiscoveryResultViewProps): react.JSX.Element;
964
991
 
965
- export { type DiscoveryKind, type DiscoveryMode, type DiscoveryPort, DiscoveryResultView, type DiscoveryResultViewProps, type DiscoverySearchHit, type DiscoverySearchPage, type DiscoverySearchQuery, type DiscoverySort, type LocalModelEntry, type LocalModelsPort, type ModelCatalogLinkComponent, ModelCatalogProvider, type ModelCatalogProviderProps, type ModelCatalogUiPorts, ModelDiscovery, type ModelDiscoveryProps, ModelListDropdown, type ModelListDropdownProps, useLocalModels, useModelCatalog, useModelCatalogStore, useModelCatalogUiPorts, useModelFavorites, useModelRecordsSelector };
992
+ export { type DiscoveryKind, type DiscoveryMode, type DiscoveryPort, DiscoveryResultView, type DiscoveryResultViewProps, type DiscoverySearchHit, type DiscoverySearchPage, type DiscoverySearchQuery, type DiscoverySort, type LocalModelEntry, type LocalModelsPort, type ModelAccessPort, type ModelCatalogLinkComponent, ModelCatalogProvider, type ModelCatalogProviderProps, type ModelCatalogUiPorts, ModelDiscovery, type ModelDiscoveryProps, ModelListDropdown, type ModelListDropdownProps, YOUR_KEY_LABEL, useLocalModels, useModelAccess, useModelCatalog, useModelCatalogStore, useModelCatalogUiPorts, useModelFavorites, useModelRecordsSelector };
@@ -5,7 +5,7 @@ import { createContext, useContext, useEffect, useMemo, useSyncExternalStore } f
5
5
  import { jsx } from "react/jsx-runtime";
6
6
  var ModelCatalogContext = createContext(null);
7
7
  var DefaultLink = ({ children, ...props }) => /* @__PURE__ */ jsx("a", { ...props, children });
8
- function ModelCatalogProvider({ catalog, favorites, children, localModels, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction }) {
8
+ function ModelCatalogProvider({ catalog, favorites, children, localModels, access, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction }) {
9
9
  const value = useMemo(
10
10
  () => ({
11
11
  catalog,
@@ -17,10 +17,11 @@ function ModelCatalogProvider({ catalog, favorites, children, localModels, isSup
17
17
  LinkComponent: LinkComponent ?? DefaultLink,
18
18
  settingsHref: settingsHref ?? null,
19
19
  ...localModels ? { localModels } : {},
20
+ ...access ? { access } : {},
20
21
  ...renderErrorAction ? { renderErrorAction } : {}
21
22
  }
22
23
  }),
23
- [catalog, favorites, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction, localModels]
24
+ [catalog, favorites, isSuperAdmin, canUseTopTier, hiddenModelIds, LinkComponent, settingsHref, renderErrorAction, localModels, access]
24
25
  );
25
26
  return /* @__PURE__ */ jsx(ModelCatalogContext.Provider, { value, children });
26
27
  }
@@ -72,6 +73,12 @@ function useLocalModels(override) {
72
73
  );
73
74
  return { models, groupLabel: port?.groupLabel ?? "On this computer", open: port?.open ? (id) => port.open?.(id) : void 0 };
74
75
  }
76
+ var NO_ACCESS = {};
77
+ function useModelAccess(override) {
78
+ const contextPort = useBinding("useModelAccess").ports.access;
79
+ return override ?? contextPort ?? NO_ACCESS;
80
+ }
81
+ var YOUR_KEY_LABEL = "Your key";
75
82
 
76
83
  // models/react/ModelListDropdown.tsx
77
84
  import { formatCount } from "@ai-matrx/kit/format";
@@ -734,7 +741,8 @@ function ModelDetailCard({
734
741
  isCurrentModel,
735
742
  pinnedOfferingId,
736
743
  onPinOffering,
737
- includeAdminSection
744
+ includeAdminSection,
745
+ yourKey = false
738
746
  }) {
739
747
  const pinnedHere = (t) => !!isCurrentModel && !!pinnedOfferingId && t.offeringId === pinnedOfferingId;
740
748
  const anyPinnedHere = model.tiers.some(pinnedHere);
@@ -799,7 +807,10 @@ function ModelDetailCard({
799
807
  )) })
800
808
  ] }),
801
809
  /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-2 gap-2", children: [
802
- /* @__PURE__ */ jsxs2("div", { className: "col-span-2", children: [
810
+ yourKey ? /* @__PURE__ */ jsxs2("div", { className: "col-span-2", children: [
811
+ /* @__PURE__ */ jsx3("dt", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "Points" }),
812
+ /* @__PURE__ */ jsx3("dd", { "data-your-key": true, className: "text-sm font-semibold text-foreground", title: "Runs on your own API key", children: YOUR_KEY_LABEL })
813
+ ] }) : /* @__PURE__ */ jsxs2("div", { className: "col-span-2", children: [
803
814
  /* @__PURE__ */ jsx3("dt", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "Points / 1M tokens" }),
804
815
  /* @__PURE__ */ jsx3(
805
816
  "dd",
@@ -1250,14 +1261,16 @@ function ModelRow({
1250
1261
  selected,
1251
1262
  isFavorite,
1252
1263
  locked = false,
1264
+ blockedReason = null,
1265
+ yourKey = false,
1253
1266
  onSelect,
1254
1267
  onHover,
1255
1268
  onToggleFavorite
1256
1269
  }) {
1257
1270
  const unavailable = variant === "admin" && !(model.admin?.offerings ?? []).some((o) => o.isAvailable);
1258
1271
  const retired = model.retiredAt != null;
1259
- const retiredTitle = retired ? `Retired ${model.retiredAt?.slice(0, 10)} \u2014 the provider no longer serves this model, so it cannot be selected.` : locked ? TOP_TIER_LOCKED_TITLE : void 0;
1260
- const blocked = retired || locked;
1272
+ const retiredTitle = retired ? `Retired ${model.retiredAt?.slice(0, 10)} \u2014 the provider no longer serves this model, so it cannot be selected.` : blockedReason ? blockedReason : locked ? TOP_TIER_LOCKED_TITLE : void 0;
1273
+ const blocked = retired || locked || blockedReason != null;
1261
1274
  return /* @__PURE__ */ jsxs2(
1262
1275
  "div",
1263
1276
  {
@@ -1305,55 +1318,67 @@ function ModelRow({
1305
1318
  children: /* @__PURE__ */ jsx3(MakerBrandGlyph, { maker: model.maker, colored: true, className: "h-3.5 w-3.5" })
1306
1319
  }
1307
1320
  ),
1308
- /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-1.5", children: [
1309
- /* @__PURE__ */ jsx3(DelayedHint, { label: model.name, children: /* @__PURE__ */ jsx3("span", { className: "truncate text-xs font-medium text-foreground", children: model.name }) }),
1310
- isDecisionModelCapability(model) && /* @__PURE__ */ jsx3(
1311
- "span",
1312
- {
1313
- className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1314
- title: "Decision model \u2014 it answers a Questions part with probabilities and generates no text.",
1315
- children: "decision"
1316
- }
1317
- ),
1318
- model.isDeprecated && !retired && /* @__PURE__ */ jsx3(
1319
- "span",
1320
- {
1321
- className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1322
- title: "Deprecated \u2014 still runs; hidden from default selection",
1323
- children: "dep"
1324
- }
1325
- ),
1326
- retired && /* @__PURE__ */ jsx3(
1327
- "span",
1328
- {
1329
- className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1330
- title: retiredTitle,
1331
- children: "retired"
1332
- }
1333
- ),
1334
- unavailable && /* @__PURE__ */ jsx3(
1335
- "span",
1336
- {
1337
- className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1338
- title: "No available offerings \u2014 not callable",
1339
- children: "off"
1340
- }
1341
- ),
1342
- classTier ? /* @__PURE__ */ jsx3(
1343
- "span",
1344
- {
1345
- className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1346
- title: `Runs on ${classTier.servedVia}`,
1347
- children: classTier.servedVia
1348
- }
1349
- ) : model.tiers.length > 1 && /* @__PURE__ */ jsx3(
1350
- "span",
1351
- {
1352
- className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1353
- title: model.tiers.map((t) => t.servedVia).join(" \xB7 "),
1354
- children: serviceCountLabel(model.tiers.length)
1355
- }
1356
- )
1321
+ /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 flex-col", children: [
1322
+ /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 items-center gap-1.5", children: [
1323
+ /* @__PURE__ */ jsx3(DelayedHint, { label: model.name, children: /* @__PURE__ */ jsx3("span", { className: "truncate text-xs font-medium text-foreground", children: model.name }) }),
1324
+ yourKey && /* @__PURE__ */ jsx3(
1325
+ "span",
1326
+ {
1327
+ "data-your-key": true,
1328
+ className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1329
+ title: "Runs on your own API key",
1330
+ children: YOUR_KEY_LABEL
1331
+ }
1332
+ ),
1333
+ isDecisionModelCapability(model) && /* @__PURE__ */ jsx3(
1334
+ "span",
1335
+ {
1336
+ className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1337
+ title: "Decision model \u2014 it answers a Questions part with probabilities and generates no text.",
1338
+ children: "decision"
1339
+ }
1340
+ ),
1341
+ model.isDeprecated && !retired && /* @__PURE__ */ jsx3(
1342
+ "span",
1343
+ {
1344
+ className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1345
+ title: "Deprecated \u2014 still runs; hidden from default selection",
1346
+ children: "dep"
1347
+ }
1348
+ ),
1349
+ retired && /* @__PURE__ */ jsx3(
1350
+ "span",
1351
+ {
1352
+ className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1353
+ title: retiredTitle,
1354
+ children: "retired"
1355
+ }
1356
+ ),
1357
+ unavailable && /* @__PURE__ */ jsx3(
1358
+ "span",
1359
+ {
1360
+ className: "shrink-0 rounded bg-destructive/15 px-1 text-[9px] text-destructive-ink",
1361
+ title: "No available offerings \u2014 not callable",
1362
+ children: "off"
1363
+ }
1364
+ ),
1365
+ classTier ? /* @__PURE__ */ jsx3(
1366
+ "span",
1367
+ {
1368
+ className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1369
+ title: `Runs on ${classTier.servedVia}`,
1370
+ children: classTier.servedVia
1371
+ }
1372
+ ) : model.tiers.length > 1 && /* @__PURE__ */ jsx3(
1373
+ "span",
1374
+ {
1375
+ className: "shrink-0 rounded border border-primary/40 bg-primary/10 px-1 text-[9px] font-medium text-foreground/80",
1376
+ title: model.tiers.map((t) => t.servedVia).join(" \xB7 "),
1377
+ children: serviceCountLabel(model.tiers.length)
1378
+ }
1379
+ )
1380
+ ] }),
1381
+ blockedReason && /* @__PURE__ */ jsx3("span", { "data-cloud-blocked-reason": true, className: "truncate text-[11px] text-muted-foreground", children: blockedReason })
1357
1382
  ] }),
1358
1383
  /* @__PURE__ */ jsx3(SpeedCell, { value: model.speedRating }),
1359
1384
  /* @__PURE__ */ jsx3(
@@ -1428,6 +1453,7 @@ var GROUP_HEADING = "px-2 pb-0.5 pt-1.5 text-[10px] uppercase tracking-wide text
1428
1453
  function ModelListDropdown({
1429
1454
  value: savedValue,
1430
1455
  localModels: localModelsProp,
1456
+ access: accessProp,
1431
1457
  onValueChange,
1432
1458
  inputModalities,
1433
1459
  outputModalities,
@@ -1458,6 +1484,9 @@ function ModelListDropdown({
1458
1484
  const { favoriteSet, toggleFavorite } = useModelFavorites(open);
1459
1485
  const ports = useModelCatalogUiPorts();
1460
1486
  const canUseTopTier = ports.canUseTopTier ?? false;
1487
+ const access = useModelAccess(accessProp);
1488
+ const cloudBlockedReason = access.cloudBlockedReason ?? null;
1489
+ const usesOwnKey = (modelId) => !!modelId && access.usesOwnKey?.(modelId) === true;
1461
1490
  const isSuperAdmin = ports.isSuperAdmin ?? false;
1462
1491
  const LinkComponent = ports.LinkComponent;
1463
1492
  const hiddenModelIds = ports.hiddenModelIds;
@@ -1711,6 +1740,10 @@ function ModelListDropdown({
1711
1740
  };
1712
1741
  const refuseIfRetired = (id2) => {
1713
1742
  const target = models.find((m) => m.id === id2);
1743
+ if (target && cloudBlockedReason) {
1744
+ console.warn(`[ModelListDropdown] Refused to select ${target.name} (${id2}): ${cloudBlockedReason}.`);
1745
+ return true;
1746
+ }
1714
1747
  if (target && !canUseTopTier && isTopTierRating(target.costRating)) {
1715
1748
  console.warn(`[ModelListDropdown] Refused to select ${target.name} (${id2}): ${TOP_TIER_LOCKED_TITLE}.`);
1716
1749
  return true;
@@ -1789,6 +1822,10 @@ function ModelListDropdown({
1789
1822
  selectedClassLabel && /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground", children: [
1790
1823
  " \xB7 ",
1791
1824
  selectedClassLabel
1825
+ ] }),
1826
+ usesOwnKey(value) && /* @__PURE__ */ jsxs2("span", { "data-your-key": true, className: "text-muted-foreground", children: [
1827
+ " \xB7 ",
1828
+ YOUR_KEY_LABEL
1792
1829
  ] })
1793
1830
  ] })
1794
1831
  ] }) : /* @__PURE__ */ jsx3("span", { className: "min-w-0 whitespace-normal text-left leading-tight text-muted-foreground", children: isLoading ? "Loading models\u2026" : emptyOptionLabel && !value ? emptyOptionLabel : placeholder }) }) : /* @__PURE__ */ jsx3("span", { className: "flex min-w-0 flex-1 items-center gap-1.5", children: shown ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
@@ -1805,6 +1842,10 @@ function ModelListDropdown({
1805
1842
  selectedClassLabel && /* @__PURE__ */ jsxs2("span", { className: "text-muted-foreground", children: [
1806
1843
  " \xB7 ",
1807
1844
  selectedClassLabel
1845
+ ] }),
1846
+ usesOwnKey(value) && /* @__PURE__ */ jsxs2("span", { "data-your-key": true, className: "text-muted-foreground", children: [
1847
+ " \xB7 ",
1848
+ YOUR_KEY_LABEL
1808
1849
  ] })
1809
1850
  ] })
1810
1851
  ] }) : /* @__PURE__ */ jsx3("span", { className: "min-w-0 truncate text-muted-foreground", children: isLoading ? "Loading models\u2026" : emptyOptionLabel && !value ? emptyOptionLabel : placeholder }) }),
@@ -1924,6 +1965,8 @@ function ModelListDropdown({
1924
1965
  selected: m.id === value && (classTier == null || classTier.offeringId === selectedClass?.offeringId),
1925
1966
  isFavorite: favoriteSet.has(m.id),
1926
1967
  locked: !canUseTopTier && isTopTierRating(m.costRating),
1968
+ blockedReason: cloudBlockedReason,
1969
+ yourKey: usesOwnKey(m.id),
1927
1970
  onToggleFavorite: () => toggleFavorite(m.id),
1928
1971
  onSelect: () => isMobile ? (setMobileDetail(m), setMobileDetailClass(classTier)) : handleSelectClass(m, classTier),
1929
1972
  onHover: isMobile ? void 0 : () => {
@@ -1991,6 +2034,7 @@ function ModelListDropdown({
1991
2034
  ModelDetailCard,
1992
2035
  {
1993
2036
  model: mobileDetail,
2037
+ yourKey: usesOwnKey(mobileDetail.id),
1994
2038
  tier: costRatingTier(mobileDetail.costRating),
1995
2039
  variant,
1996
2040
  onSelect: () => handleSelect(mobileDetail.id, mobileDetailClass),
@@ -2089,6 +2133,7 @@ function ModelListDropdown({
2089
2133
  ModelDetailCard,
2090
2134
  {
2091
2135
  model: hovered,
2136
+ yourKey: usesOwnKey(hovered.id),
2092
2137
  tier: costRatingTier(hovered.costRating),
2093
2138
  variant,
2094
2139
  onSelect: () => handleSelect(hovered.id, hoveredClass),
@@ -2683,7 +2728,9 @@ export {
2683
2728
  ModelCatalogProvider,
2684
2729
  ModelDiscovery,
2685
2730
  ModelListDropdown,
2731
+ YOUR_KEY_LABEL,
2686
2732
  useLocalModels,
2733
+ useModelAccess,
2687
2734
  useModelCatalog,
2688
2735
  useModelCatalogStore,
2689
2736
  useModelCatalogUiPorts,
@@ -586,6 +586,22 @@ interface LocalModelsPort {
586
586
  */
587
587
  open?(id: string): void;
588
588
  }
589
+ /**
590
+ * How the person may reach a cloud model in THIS place (a chat composer passes it to its own picker,
591
+ * like `localModels`). Both fields are optional; a host that supplies neither changes nothing.
592
+ */
593
+ interface ModelAccessPort {
594
+ /**
595
+ * Set = every cloud model is listed disabled with this one-line reason (never hidden), e.g. a
596
+ * confidential chat in an organization that requires its own hardware: "Blocked by your organization".
597
+ */
598
+ cloudBlockedReason?: string | null;
599
+ /**
600
+ * True when the person's own API key serves this model: the picker and the chat's model chip say
601
+ * "Your key" and show no model points.
602
+ */
603
+ usesOwnKey?(modelId: string): boolean;
604
+ }
589
605
 
590
606
  /**
591
607
  * THE SETTINGS WRITE TARGET — the one seam between THE model-settings panel
@@ -683,6 +699,8 @@ interface ModelRowTarget {
683
699
  };
684
700
  /** The person's local models (a chat host's port). */
685
701
  localModels?: LocalModelsPort | undefined;
702
+ /** The picker's cloud-access rules for this conversation (blocked cloud models, own-key labels). */
703
+ access?: ModelAccessPort | undefined;
686
704
  disabled?: boolean;
687
705
  /** Said on the read-only picker when `disabled`. */
688
706
  disabledTitle?: string;
@@ -586,6 +586,22 @@ interface LocalModelsPort {
586
586
  */
587
587
  open?(id: string): void;
588
588
  }
589
+ /**
590
+ * How the person may reach a cloud model in THIS place (a chat composer passes it to its own picker,
591
+ * like `localModels`). Both fields are optional; a host that supplies neither changes nothing.
592
+ */
593
+ interface ModelAccessPort {
594
+ /**
595
+ * Set = every cloud model is listed disabled with this one-line reason (never hidden), e.g. a
596
+ * confidential chat in an organization that requires its own hardware: "Blocked by your organization".
597
+ */
598
+ cloudBlockedReason?: string | null;
599
+ /**
600
+ * True when the person's own API key serves this model: the picker and the chat's model chip say
601
+ * "Your key" and show no model points.
602
+ */
603
+ usesOwnKey?(modelId: string): boolean;
604
+ }
589
605
 
590
606
  /**
591
607
  * THE SETTINGS WRITE TARGET — the one seam between THE model-settings panel
@@ -683,6 +699,8 @@ interface ModelRowTarget {
683
699
  };
684
700
  /** The person's local models (a chat host's port). */
685
701
  localModels?: LocalModelsPort | undefined;
702
+ /** The picker's cloud-access rules for this conversation (blocked cloud models, own-key labels). */
703
+ access?: ModelAccessPort | undefined;
686
704
  disabled?: boolean;
687
705
  /** Said on the read-only picker when `disabled`. */
688
706
  disabledTitle?: string;
@@ -1054,6 +1054,7 @@ function ModelRow({ model, resetLabel, idPrefix, disabled }) {
1054
1054
  onOfferingPinChange: model.changePin,
1055
1055
  ...model.emptyChoice ? { emptyOptionLabel: model.emptyChoice.label, onClear: model.emptyChoice.choose, placeholder: model.emptyChoice.label } : {},
1056
1056
  ...model.localModels ? { localModels: model.localModels } : {},
1057
+ ...model.access ? { access: model.access } : {},
1057
1058
  ...model.disabledTitle ? { disabledTitle: model.disabledTitle } : {}
1058
1059
  }
1059
1060
  ),
@@ -1,7 +1,7 @@
1
1
  import { components } from '@ai-matrx/api/types';
2
2
  import * as React from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { LocalModelsPort } from '@ai-matrx/agents/models/react';
4
+ import { LocalModelsPort, ModelAccessPort } from '@ai-matrx/agents/models/react';
5
5
 
6
6
  /**
7
7
  * Agent Settings — Type Definitions
@@ -348,6 +348,8 @@ interface ModelRowTarget {
348
348
  };
349
349
  /** The person's local models (a chat host's port). */
350
350
  localModels?: LocalModelsPort | undefined;
351
+ /** The picker's cloud-access rules for this conversation (blocked cloud models, own-key labels). */
352
+ access?: ModelAccessPort | undefined;
351
353
  disabled?: boolean;
352
354
  /** Said on the read-only picker when `disabled`. */
353
355
  disabledTitle?: string;
@@ -1,7 +1,7 @@
1
1
  import { components } from '@ai-matrx/api/types';
2
2
  import * as React from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { LocalModelsPort } from '@ai-matrx/agents/models/react';
4
+ import { LocalModelsPort, ModelAccessPort } from '@ai-matrx/agents/models/react';
5
5
 
6
6
  /**
7
7
  * Agent Settings — Type Definitions
@@ -348,6 +348,8 @@ interface ModelRowTarget {
348
348
  };
349
349
  /** The person's local models (a chat host's port). */
350
350
  localModels?: LocalModelsPort | undefined;
351
+ /** The picker's cloud-access rules for this conversation (blocked cloud models, own-key labels). */
352
+ access?: ModelAccessPort | undefined;
351
353
  disabled?: boolean;
352
354
  /** Said on the read-only picker when `disabled`. */
353
355
  disabledTitle?: string;
@@ -1051,6 +1051,7 @@ function ModelRow({ model, resetLabel, idPrefix, disabled }) {
1051
1051
  onOfferingPinChange: model.changePin,
1052
1052
  ...model.emptyChoice ? { emptyOptionLabel: model.emptyChoice.label, onClear: model.emptyChoice.choose, placeholder: model.emptyChoice.label } : {},
1053
1053
  ...model.localModels ? { localModels: model.localModels } : {},
1054
+ ...model.access ? { access: model.access } : {},
1054
1055
  ...model.disabledTitle ? { disabledTitle: model.disabledTitle } : {}
1055
1056
  }
1056
1057
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-matrx/agents",
3
- "version": "0.59.225",
3
+ "version": "0.59.227",
4
4
  "description": "Portable AI Matrx agent stream protocol, event projection, and safe result-presentation primitives.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -509,10 +509,10 @@
509
509
  "sideEffects": false,
510
510
  "dependencies": {
511
511
  "@ai-matrx/alchemy": "latest",
512
- "@ai-matrx/api": "latest",
513
512
  "@ai-matrx/associations": "latest",
514
- "@ai-matrx/data": "latest",
513
+ "@ai-matrx/api": "latest",
515
514
  "@ai-matrx/content-ir": "latest",
515
+ "@ai-matrx/data": "latest",
516
516
  "@ai-matrx/design-system": "latest",
517
517
  "@ai-matrx/diff": "latest",
518
518
  "@ai-matrx/icons": "latest",
@@ -561,5 +561,5 @@
561
561
  "verify:tarball": "node ./scripts/verify-tarball.mjs",
562
562
  "typegen:self-test": "node scripts/typegen/drop-guard.self-test.mjs && node scripts/typegen/check-api-types-fresh.mjs --self-test"
563
563
  },
564
- "gitHead": "285980545216bea41c11ff6e8af8705328508ddc"
564
+ "gitHead": "351d20c8350b3bbc8010d4bd5d426f4c4742fb30"
565
565
  }