@gonrocca/nodd 0.4.0 → 0.4.1

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.
@@ -279,3 +279,39 @@ test("nothing in this extension names forge's config file", () => {
279
279
  assert.ok(!source.includes(`zero${"."}json`), "NODD never opens forge's config");
280
280
  assert.ok(source.includes("nodd.json") || source.includes("noddConfigPath"), "it uses its own");
281
281
  });
282
+
283
+ test("the picker offers the providers you can actually use, not the whole catalogue", () => {
284
+ // pi's registry carries every provider it knows how to speak to — bedrock,
285
+ // baseten, huggingface, cloudflare — whether or not you have credentials for
286
+ // any of them. Browsing all of it meant scrolling past dozens of providers
287
+ // that cannot answer. `getAvailable()` is pi's own answer to "which of these
288
+ // has configured auth", so that is what the picker lists.
289
+ const registry = {
290
+ getAll: () => [
291
+ { provider: "cliproxy", id: "ds/deepseek-flash" },
292
+ { provider: "amazon-bedrock", id: "claude-3" },
293
+ { provider: "huggingface", id: "meta-llama/x" },
294
+ ],
295
+ getAvailable: () => [{ provider: "cliproxy", id: "ds/deepseek-flash" }],
296
+ };
297
+
298
+ const groups = register.groupsFrom(registry);
299
+ assert.deepEqual([...groups.keys()], ["cliproxy/ds"], "only the configured provider is browsable");
300
+ });
301
+
302
+ test("with no availability information the full registry is still offered", () => {
303
+ // An older host, or one that cannot answer, must not leave the picker empty:
304
+ // showing everything beats showing nothing.
305
+ const groups = register.groupsFrom({ getAll: () => [{ provider: "anthropic", id: "claude-opus-4-1" }] });
306
+ assert.deepEqual([...groups.keys()], ["anthropic"]);
307
+ });
308
+
309
+ test("an empty availability list falls back rather than offering nothing", () => {
310
+ // Availability is refreshed asynchronously, so early in a session it can be
311
+ // legitimately empty. Trusting it blindly would show an empty provider list.
312
+ const groups = register.groupsFrom({
313
+ getAll: () => [{ provider: "anthropic", id: "claude-opus-4-1" }],
314
+ getAvailable: () => [],
315
+ });
316
+ assert.deepEqual([...groups.keys()], ["anthropic"], "an empty answer is not an answer");
317
+ });
@@ -48,13 +48,31 @@ export function fileConfigIo(path: string = noddConfigPath()): ConfigIo {
48
48
  };
49
49
  }
50
50
 
51
- type ModelRegistry = { getAll(): RegistryModel[] };
51
+ type ModelRegistry = {
52
+ getAll(): RegistryModel[];
53
+ /** pi's own answer to "which providers have configured auth". */
54
+ getAvailable?(): RegistryModel[];
55
+ };
52
56
 
53
57
  const USAGE =
54
58
  "uso: /nodd-models · /nodd-models <slot>=<provider>/<modelo> · /nodd-models profile [list|new <n>|save [n]|use <n>|delete <n>]";
55
59
 
60
+ /**
61
+ * The models worth browsing.
62
+ *
63
+ * pi's registry lists every provider it can speak to — bedrock, baseten,
64
+ * huggingface — whether or not credentials exist for any of them, which buried
65
+ * the few usable ones under dozens that cannot answer. `getAvailable()` narrows
66
+ * that to the configured providers.
67
+ *
68
+ * An empty or missing answer falls back to the full registry: availability is
69
+ * refreshed asynchronously, so early in a session it can be legitimately empty,
70
+ * and an empty picker is worse than a long one.
71
+ */
56
72
  function groupsFrom(registry: ModelRegistry | undefined): Map<string, string[]> {
57
73
  try {
74
+ const available = registry?.getAvailable?.();
75
+ if (available && available.length > 0) return groupByProvider(available);
58
76
  const all = registry?.getAll?.();
59
77
  return all && all.length > 0 ? groupByProvider(all) : new Map();
60
78
  } catch {
@@ -362,5 +380,6 @@ function register(pi?: PiApi): void {
362
380
  register.runPicker = runPicker;
363
381
  register.createComponent = createComponent;
364
382
  register.pickerInput = pickerInput;
383
+ register.groupsFrom = groupsFrom;
365
384
 
366
385
  export default register;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonrocca/nodd",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Non-negotiable Organic Driven Development — the ODD protocol as runtime mechanism for pi: blocking gates, observed evidence, and promotion to /forge.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -572,3 +572,16 @@ test("with no profile at all there is nothing to preview", () => {
572
572
  const state = createPickerState({ models: {}, thinking: {}, groups: new Map(), profiles: {}, activeProfile: null });
573
573
  assert.deepEqual(previewRows(state), [], "an empty preview is what suppresses the second panel");
574
574
  });
575
+
576
+ test("choosing through a prefixed group does not double the prefix", () => {
577
+ // The group key is `cliproxy/ds` and the id inside it is `ds/deepseek-v4-pro`:
578
+ // joining them naively produced `cliproxy/ds/ds/deepseek-v4-pro`, a model id
579
+ // that resolves to nothing. The key's prefix belongs to the id, not to the
580
+ // provider, so only the provider part may be prepended.
581
+ let state = open({ groups: new Map([["cliproxy/ds", ["ds/deepseek-v4-pro"]]]) });
582
+ state = press(state, "edit-loose");
583
+ state = assign(state, "implement", "cliproxy/ds", "ds/deepseek-v4-pro", "high");
584
+
585
+ assert.equal(state.edits.models.implement, "cliproxy/ds/deepseek-v4-pro");
586
+ assert.equal(state.edits.thinking.implement, "high");
587
+ });
@@ -528,9 +528,15 @@ function commitDrill(state: PickerState, level: ThinkingLevel): PickerState {
528
528
  const { drillSlot, drillProvider, drillModel } = state;
529
529
  if (drillSlot === null || drillModel === null) return state;
530
530
 
531
- // No provider is a bare model id which is what the empty-registry path
531
+ // `drillProvider` is a group key, and a pooled provider's key carries the
532
+ // pool prefix (`cliproxy/ds`) which the id already repeats
533
+ // (`ds/deepseek-v4-pro`). Only the provider part is prepended, or the commit
534
+ // doubles the prefix into an id that resolves to nothing.
535
+ //
536
+ // No provider at all is a bare model id — what the empty-registry path
532
537
  // produces, and what the command's validation accepts.
533
- const qualified = drillProvider ? `${drillProvider}/${drillModel}` : drillModel;
538
+ const provider = drillProvider?.split("/")[0] ?? null;
539
+ const qualified = provider ? `${provider}/${drillModel}` : drillModel;
534
540
  return {
535
541
  ...state,
536
542
  edits: {