openzoo 0.19.0 → 0.20.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.
package/lib/cursorcfg.js CHANGED
@@ -113,6 +113,42 @@ export function writeEditorProviderConfig(which, { baseUrl, models }) {
113
113
  }
114
114
  doc.availableAPIKeyModels = existing;
115
115
 
116
+ // ALSO register in availableDefaultModels2, which is what the Models UI
117
+ // actually renders. A model present only in availableAPIKeyModels never
118
+ // appears in the picker or the search box ("No models available" for a name
119
+ // that was definitely written) — the one hand-added entry that DID show,
120
+ // `lecore`, lives here with isUserAdded:true. Mirror that shape exactly.
121
+ const defs = Array.isArray(doc.availableDefaultModels2) ? doc.availableDefaultModels2 : [];
122
+ const defNames = new Set(defs.map((m) => m?.name).filter(Boolean));
123
+ for (const m of models) {
124
+ if (defNames.has(m)) continue;
125
+ defs.push({
126
+ name: m,
127
+ defaultOn: true,
128
+ supportsAgent: true,
129
+ degradationStatus: 0,
130
+ supportsThinking: true,
131
+ supportsImages: true,
132
+ supportsMaxMode: true,
133
+ supportsNonMaxMode: true,
134
+ serverModelName: m,
135
+ isRecommendedForBackgroundComposer: false,
136
+ supportsPlanMode: true,
137
+ supportsSandboxing: true,
138
+ isUserAdded: true,
139
+ inputboxShortModelName: m,
140
+ parameterDefinitions: [],
141
+ variants: [],
142
+ legacySlugs: [],
143
+ idAliases: [],
144
+ namedModelSectionIndex: 1,
145
+ cloudAgentEffortModes: [],
146
+ modelPickerBadges: [],
147
+ });
148
+ defNames.add(m);
149
+ }
150
+ doc.availableDefaultModels2 = defs;
151
+
116
152
  // SELECT it, don't just offer it. Adding a model to the picker leaves the
117
153
  // editor on whatever it had — `featureModelConfigs.composer.defaultModel`
118
154
  // was "default", i.e. Cursor's Auto router, which chooses ITS OWN models
@@ -168,6 +204,20 @@ export function pinEditorProviderConfig(which, { baseUrl, models }) {
168
204
  if (!fs.existsSync(db)) return null;
169
205
  const esc = (v) => String(v).replace(/'/g, "''");
170
206
  const modelJson = esc(JSON.stringify(models.map((m) => ({ name: m, defaultOn: true, supportsAgent: true }))));
207
+ // The UI list must be pinned TOO. Pinning only availableAPIKeyModels meant
208
+ // the editor could wipe availableDefaultModels2 — the list the Models pane
209
+ // actually renders — and nothing put it back: measured, 413 entries in the
210
+ // API list and 0 in the UI, i.e. the models vanished from the picker in real
211
+ // time while the config still claimed they were there.
212
+ const uiJson = esc(JSON.stringify(models.map((m) => ({
213
+ name: m, defaultOn: true, supportsAgent: true, degradationStatus: 0,
214
+ supportsThinking: true, supportsImages: true, supportsMaxMode: true,
215
+ supportsNonMaxMode: true, serverModelName: m,
216
+ isRecommendedForBackgroundComposer: false, supportsPlanMode: true,
217
+ supportsSandboxing: true, isUserAdded: true, inputboxShortModelName: m,
218
+ parameterDefinitions: [], variants: [], legacySlugs: [], idAliases: [],
219
+ namedModelSectionIndex: 1, cloudAgentEffortModes: [], modelPickerBadges: [],
220
+ }))));
171
221
  const primary = esc(models[0]);
172
222
  const base = esc(baseUrl);
173
223
  const sql = `
@@ -175,13 +225,15 @@ DROP TRIGGER IF EXISTS openzoo_pin;
175
225
  CREATE TRIGGER openzoo_pin AFTER UPDATE ON ItemTable
176
226
  WHEN NEW.key = '${KEY}'
177
227
  AND (json_extract(NEW.value,'$.openAIBaseUrl') IS NOT '${base}'
178
- OR json_extract(NEW.value,'$.featureModelConfigs.composer.defaultModel') IS NOT '${primary}')
228
+ OR json_extract(NEW.value,'$.featureModelConfigs.composer.defaultModel') IS NOT '${primary}'
229
+ OR json_array_length(json_extract(NEW.value,'$.availableDefaultModels2')) IS NOT ${models.length})
179
230
  BEGIN
180
231
  UPDATE ItemTable SET value = json_set(
181
232
  NEW.value,
182
233
  '$.openAIBaseUrl', '${base}',
183
234
  '$.useOpenAIKey', json('true'),
184
235
  '$.availableAPIKeyModels', json('${modelJson}'),
236
+ '$.availableDefaultModels2', json('${uiJson}'),
185
237
  '$.featureModelConfigs.composer.defaultModel', '${primary}',
186
238
  '$.featureModelConfigs.cmdK.defaultModel', '${primary}'
187
239
  ) WHERE key = NEW.key;
package/lib/setup.js CHANGED
@@ -33,22 +33,39 @@ import { writeEditorProviderConfig, editorRunning, quitEditor, pinEditorProvider
33
33
  * qwen/qwen-2.5-coder-32b-instruct), all verified against the live catalog.
34
34
  * Override with OPENZOO_MODELS.
35
35
  */
36
- const DEFAULT_MODELS = (process.env.OPENZOO_MODELS
37
- ? process.env.OPENZOO_MODELS.split(',').map((m) => m.trim()).filter(Boolean)
38
- : [
39
- 'openzoo-claude-opus-5',
40
- 'openzoo-claude-sonnet-5',
41
- 'openzoo-gpt-5.6-sol-pro',
42
- 'openzoo-grok-4.6',
43
- 'openzoo-glm-5.2',
44
- 'openzoo-gemini-3.1-pro-preview-customtools',
45
- 'openzoo-kimi-k3',
46
- 'openzoo-deepseek-v4-pro-0813',
47
- 'openzoo-qwen3.8-2.4t-a95b',
48
- 'openzoo-gpt-5.3-codex',
49
- 'openzoo-claude-haiku-4.5',
50
- 'openzoo-seed-2.0-code',
51
- ]);
36
+ /**
37
+ * Every model the zoo serves, offered in the editor's picker as its openzoo-*
38
+ * twin — pulled LIVE, never hardcoded.
39
+ *
40
+ * A hand-maintained list goes stale the moment the catalog changes, and it made
41
+ * the shim lie about what is available. The prefix matters: an editor claims any
42
+ * name from its OWN catalog (claude-opus-5, grok-4.6) and routes it to its
43
+ * backend, so a name it does not recognise is what forces the custom endpoint.
44
+ *
45
+ * OPENZOO_MODELS overrides with an explicit comma-separated list;
46
+ * OPENZOO_MODEL_LIMIT caps how many are written (default all).
47
+ */
48
+ async function catalogModels(base) {
49
+ if (process.env.OPENZOO_MODELS) {
50
+ return process.env.OPENZOO_MODELS.split(',').map((m) => m.trim()).filter(Boolean);
51
+ }
52
+ try {
53
+ const r = await fetch(`${base}/models`, { signal: AbortSignal.timeout(15000) });
54
+ const d = await r.json();
55
+ const twins = (d.data || []).map((m) => m.id).filter((id) => id.startsWith('openzoo-'));
56
+ // Full-strength first: :free/:batch variants are opt-in, not what someone
57
+ // wants preselected, and a flagship should be the default entry.
58
+ const plain = twins.filter((t) => !t.includes(':'));
59
+ const rest = twins.filter((t) => t.includes(':'));
60
+ const preferred = ['openzoo-claude-opus-5', 'openzoo-claude-sonnet-5', 'openzoo-gpt-5.6-sol-pro'];
61
+ const head = preferred.filter((p) => plain.includes(p));
62
+ const ordered = [...head, ...plain.filter((t) => !head.includes(t)), ...rest];
63
+ const limit = Number(process.env.OPENZOO_MODEL_LIMIT || 0);
64
+ return limit > 0 ? ordered.slice(0, limit) : ordered;
65
+ } catch {
66
+ return ['openzoo-claude-opus-5', 'openzoo-deepseek-v4-pro-0813']; // catalog unreachable
67
+ }
68
+ }
52
69
 
53
70
  const MCP_FILES = {
54
71
  cursor: path.join(os.homedir(), '.cursor', 'mcp.json'),
@@ -211,7 +228,7 @@ export async function setupEditor(which, target) {
211
228
  const q = await quitEditor(target0);
212
229
  if (!q.quit) console.log(` could not close ${target0} automatically; settings may not persist`);
213
230
  }
214
- const models = [DEFAULT_MODELS[0], ...DEFAULT_MODELS.slice(1)];
231
+ const models = await catalogModels(base);
215
232
  let wrote = null;
216
233
  try { wrote = writeEditorProviderConfig(target0, { baseUrl: base, models }); } catch (e) { wrote = { error: e.message }; }
217
234
  if (wrote?.error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
4
4
  "description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
5
5
  "license": "MIT",
6
6
  "type": "module",