@gonrocca/zero-pi 0.1.18 → 0.1.19

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.
@@ -21,7 +21,6 @@ import { join } from "node:path";
21
21
 
22
22
  import { readAutotuneMode, type AutotuneMode } from "./autotune.ts";
23
23
  import type { AutotunePending } from "./autotune-extension.ts";
24
- import { discoverModels } from "./opencode-models.ts";
25
24
 
26
25
  /** The SDD phases, in pipeline order. */
27
26
  export const PHASES = ["explore", "plan", "build", "veredicto"] as const;
@@ -214,45 +213,30 @@ const CUSTOM_PROVIDER = "— otro provider (escribir) —";
214
213
  /**
215
214
  * Group models by provider for the picker.
216
215
  *
217
- * The primary source is OpenCode's model catalogthe same one the `zero`
218
- * installer's picker uses — so every provider the user authenticated there
219
- * (opencode-go, openai/codex, anthropic, …) is offered, with only tool-call-
220
- * capable models (the SDD phases need tool calls). When OpenCode is not
221
- * present it falls back to pi's own model registry, then to an empty map.
216
+ * The source is pi's own model registry`getAll()` returns every model from
217
+ * every provider the user is authenticated for (`anthropic`, `openai-codex`,
218
+ * `opencode-go`, …), each with the exact provider id and model id pi resolves
219
+ * at runtime. Using pi's registry rather than a foreign catalog like
220
+ * OpenCode's guarantees the names written to `~/.pi/zero.json` are names pi
221
+ * actually understands, so a configured phase model never fails to resolve.
222
222
  */
223
223
  function providerGroups(registry: PiModelRegistry | undefined): Map<string, string[]> {
224
- try {
225
- const discovered = discoverModels().filter((m) => m.toolCall);
226
- if (discovered.length > 0) return groupByProvider(discovered);
227
- } catch {
228
- /* fall through to pi's registry */
229
- }
230
224
  if (registry && typeof registry.getAll === "function") {
231
225
  try {
232
226
  const all = registry.getAll();
233
227
  if (all && all.length > 0) return groupByProvider(all);
234
228
  } catch {
235
- /* fall through */
229
+ /* fall through to an empty map */
236
230
  }
237
231
  }
238
232
  return new Map();
239
233
  }
240
234
 
241
- /**
242
- * Find the provider that owns a model id — checked against OpenCode's catalog
243
- * first, then pi's own registry.
244
- */
235
+ /** Find the provider that owns a model id, per pi's own model registry. */
245
236
  function resolveProvider(
246
237
  registry: PiModelRegistry | undefined,
247
238
  modelId: string,
248
239
  ): string | undefined {
249
- try {
250
- for (const m of discoverModels()) {
251
- if (m.id === modelId) return m.provider;
252
- }
253
- } catch {
254
- /* ignore */
255
- }
256
240
  if (registry && typeof registry.getAll === "function") {
257
241
  try {
258
242
  for (const m of registry.getAll()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonrocca/zero-pi",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "zero-pi — an installable layer for pi (pi.dev): the zero spec-driven development workflow, skill auto-learning, and an animated ZERO startup banner. Adds capability to pi without modifying pi.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -44,7 +44,6 @@
44
44
  "extensions/sdd-agents.ts",
45
45
  "extensions/conversation-resume.ts",
46
46
  "extensions/zero-models.ts",
47
- "extensions/opencode-models.ts",
48
47
  "extensions/autotune.ts",
49
48
  "extensions/autotune-extension.ts",
50
49
  "extensions/spec-merge.ts",
@@ -1,111 +0,0 @@
1
- // zero-pi — OpenCode model-catalog discovery.
2
- //
3
- // Surfaces the model providers a user can run by cross-referencing OpenCode's
4
- // model catalog cache with the user's OpenCode auth file. This is the same
5
- // approach the `zero` installer's model picker uses, so `/zero-models` offers
6
- // the same providers — opencode-go, openai (codex/gpt), anthropic, … — as the
7
- // installer, instead of a hardcoded Claude list.
8
- //
9
- // Sources, under the user's home (ZERO_HOME-overridable for tests):
10
- // ~/.cache/opencode/models.json — the full provider/model catalog
11
- // ~/.local/share/opencode/auth.json — the providers the user authenticated
12
- //
13
- // Discovery is total: a missing or corrupt file yields an empty list, so a
14
- // user without OpenCode simply falls back to pi's own model registry.
15
-
16
- import { readFileSync } from "node:fs";
17
- import { homedir } from "node:os";
18
- import { join } from "node:path";
19
-
20
- /** A model discovered from OpenCode's catalog. */
21
- export interface DiscoveredModel {
22
- /** The provider id, e.g. `opencode-go`, `openai`, `anthropic`. */
23
- provider: string;
24
- /** The model id, e.g. `gpt-5-codex`. */
25
- id: string;
26
- /** The human-readable model name. */
27
- name: string;
28
- /** Whether the model supports tool calls — required for the SDD phases. */
29
- toolCall: boolean;
30
- /** Whether the model exposes a reasoning-effort setting. */
31
- reasoning: boolean;
32
- }
33
-
34
- /** The home directory discovery resolves OpenCode paths against. */
35
- function discoveryHome(): string {
36
- const override = process.env.ZERO_HOME;
37
- return typeof override === "string" && override.trim() !== "" ? override : homedir();
38
- }
39
-
40
- /** True for a plain (non-array) object. */
41
- function isObject(value: unknown): value is Record<string, unknown> {
42
- return typeof value === "object" && value !== null && !Array.isArray(value);
43
- }
44
-
45
- /** Read and parse a JSON file; returns null on any failure. */
46
- function readJson(path: string): unknown {
47
- try {
48
- return JSON.parse(readFileSync(path, "utf8")) as unknown;
49
- } catch {
50
- return null;
51
- }
52
- }
53
-
54
- /**
55
- * Extract a provider's models from its OpenCode catalog entry.
56
- *
57
- * Exported for tests.
58
- */
59
- export function extractModels(
60
- providerId: string,
61
- entry: Record<string, unknown>,
62
- ): DiscoveredModel[] {
63
- const raw = entry.models;
64
- if (!isObject(raw)) return [];
65
-
66
- const models: DiscoveredModel[] = [];
67
- for (const [id, model] of Object.entries(raw)) {
68
- if (!isObject(model)) continue;
69
- models.push({
70
- provider: providerId,
71
- id,
72
- name: typeof model.name === "string" ? model.name : id,
73
- toolCall: model.tool_call === true,
74
- reasoning: model.reasoning === true,
75
- });
76
- }
77
- return models;
78
- }
79
-
80
- /**
81
- * Discover every model the user can run, flattened across all OpenCode-
82
- * authenticated providers.
83
- *
84
- * `catalogJson` and `authJson` are injectable for tests; in normal use they
85
- * are read from OpenCode's cache and auth files on disk. The result is empty
86
- * when either source is absent or malformed.
87
- */
88
- export function discoverModels(
89
- catalogJson?: unknown,
90
- authJson?: unknown,
91
- ): DiscoveredModel[] {
92
- // Only touch disk when an argument is genuinely absent — an explicit value
93
- // (including `null`) is used as given, so tests never read real files.
94
- const auth =
95
- authJson === undefined
96
- ? readJson(join(discoveryHome(), ".local", "share", "opencode", "auth.json"))
97
- : authJson;
98
- const catalog =
99
- catalogJson === undefined
100
- ? readJson(join(discoveryHome(), ".cache", "opencode", "models.json"))
101
- : catalogJson;
102
- if (!isObject(auth) || !isObject(catalog)) return [];
103
-
104
- const models: DiscoveredModel[] = [];
105
- for (const providerId of Object.keys(auth)) {
106
- const entry = catalog[providerId];
107
- if (!isObject(entry)) continue;
108
- models.push(...extractModels(providerId, entry));
109
- }
110
- return models;
111
- }