openzoo 0.18.11 → 0.20.0
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 +36 -0
- package/lib/models.js +38 -1
- package/lib/setup.js +34 -17
- package/package.json +1 -1
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
|
package/lib/models.js
CHANGED
|
@@ -90,6 +90,17 @@ const tokensOf = (id) => id.toLowerCase().split(/[^a-z0-9.]+/).filter((t) => t &
|
|
|
90
90
|
*/
|
|
91
91
|
export function resolveModel(requested, ids) {
|
|
92
92
|
if (!requested || !ids?.length || ids.includes(requested)) return null;
|
|
93
|
+
// `openzoo-` prefixed names exist so an editor cannot mistake them for its
|
|
94
|
+
// OWN models: Cursor claims any name in its catalog (claude-opus-5, grok-4.6)
|
|
95
|
+
// and routes it to its backend, ignoring the custom endpoint entirely —
|
|
96
|
+
// measured, zero connections ever reached the proxy. A name it does not know
|
|
97
|
+
// is forced down the custom path. Strip the marker before matching so
|
|
98
|
+
// openzoo-opus-5 still resolves to anthropic/claude-opus-5.
|
|
99
|
+
const bare = String(requested).replace(/^openzoo[-/]/i, '');
|
|
100
|
+
if (bare !== requested) {
|
|
101
|
+
if (ids.includes(bare)) return bare;
|
|
102
|
+
requested = bare;
|
|
103
|
+
}
|
|
93
104
|
const env = process.env.OPENZOO_DEFAULT_MODEL;
|
|
94
105
|
if (env && ids.includes(env)) return env;
|
|
95
106
|
|
|
@@ -147,6 +158,32 @@ export function augmentModelList(payload) {
|
|
|
147
158
|
const data = Array.isArray(payload?.data) ? payload.data : [];
|
|
148
159
|
const have = new Set(data.map((m) => m.id));
|
|
149
160
|
const ids = data.map((m) => m.id);
|
|
161
|
+
// openzoo-* twins of the popular models. An editor that validates a custom
|
|
162
|
+
// model against THIS list (Cursor's "Add model" box reports "No models
|
|
163
|
+
// available" for anything missing here) can only offer what we publish — and
|
|
164
|
+
// the openzoo- prefix is what stops it claiming the name as one of its own
|
|
165
|
+
// built-ins and routing to its backend instead of to us.
|
|
166
|
+
const branded = [];
|
|
167
|
+
for (const src0 of data) {
|
|
168
|
+
const id = src0.id;
|
|
169
|
+
// Brand only REAL upstream models. Anything we synthesised (a twin or a
|
|
170
|
+
// harness alias) must be skipped, or augmenting an already-augmented
|
|
171
|
+
// payload mints openzoo-openzoo-* and the catalog grows every pass.
|
|
172
|
+
if (!id || id.startsWith('openzoo-') || String(src0.owned_by || '').startsWith('openzoo')) continue;
|
|
173
|
+
const short = id.includes('/') ? id.split('/')[1] : id;
|
|
174
|
+
const name = `openzoo-${short}`;
|
|
175
|
+
if (have.has(name)) continue;
|
|
176
|
+
const src = data.find((m) => m.id === id);
|
|
177
|
+
branded.push({
|
|
178
|
+
id: name,
|
|
179
|
+
object: 'model',
|
|
180
|
+
owned_by: 'openzoo',
|
|
181
|
+
served_by: id,
|
|
182
|
+
...(src?.context_length ? { context_length: src.context_length, context_window: src.context_window ?? src.context_length } : {}),
|
|
183
|
+
...(src?.pricing ? { pricing: src.pricing } : {}),
|
|
184
|
+
});
|
|
185
|
+
have.add(name);
|
|
186
|
+
}
|
|
150
187
|
const aliases = ALIAS_IDS.filter((id) => !have.has(id)).map((id) => {
|
|
151
188
|
const target = data.find((m) => m.id === resolveModel(id, ids));
|
|
152
189
|
return {
|
|
@@ -158,7 +195,7 @@ export function augmentModelList(payload) {
|
|
|
158
195
|
...(target ? { served_by: target.id } : {}),
|
|
159
196
|
};
|
|
160
197
|
});
|
|
161
|
-
return { ...payload, object: payload?.object || 'list', data: [...data, ...aliases] };
|
|
198
|
+
return { ...payload, object: payload?.object || 'list', data: [...data, ...branded, ...aliases] };
|
|
162
199
|
}
|
|
163
200
|
|
|
164
201
|
/**
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
'
|
|
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 =
|
|
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.
|
|
3
|
+
"version": "0.20.0",
|
|
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",
|