@mmmbuto/nexuscrew 0.9.75 → 0.9.76
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 +7 -0
- package/frontend/dist/assets/index-BURtPwUi.js +169 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/dist/version.json +1 -1
- package/lib/fleet/builtin.js +64 -9
- package/lib/fleet/definitions.js +10 -1
- package/lib/fleet/managed.js +22 -2
- package/lib/fleet/model-discovery.js +287 -64
- package/package.json +1 -1
- package/frontend/dist/assets/index-DMfpKI3n.js +0 -169
package/frontend/dist/index.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<meta name="apple-mobile-web-app-title" content="NexusCrew" />
|
|
12
12
|
<link rel="manifest" href="/manifest.json" />
|
|
13
13
|
<title>NexusCrew</title>
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-BURtPwUi.js"></script>
|
|
15
15
|
<link rel="stylesheet" crossorigin href="/assets/index-B9GW2LHm.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.9.
|
|
1
|
+
{"version":"0.9.76"}
|
package/lib/fleet/builtin.js
CHANGED
|
@@ -965,7 +965,7 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
965
965
|
// I campi non-identita' che `parseModel` ammette per una dichiarazione:
|
|
966
966
|
// id ed engine SONO l'identita' (il chiaro per cui esiste edit-model e' non
|
|
967
967
|
// doverla ritoccare), tutto il resto e' patchabile con la stessa validazione.
|
|
968
|
-
const EDIT_MODEL_FIELDS = new Set(['label', 'contextWindow', 'maxTokens', 'reasoning']);
|
|
968
|
+
const EDIT_MODEL_FIELDS = new Set(['label', 'contextWindow', 'maxTokens', 'tools', 'vision', 'reasoning']);
|
|
969
969
|
|
|
970
970
|
async function editModel(id, engine, patch) {
|
|
971
971
|
if (readonly()) throw httpError(403, 'READONLY: edit-model bloccato');
|
|
@@ -984,12 +984,18 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
984
984
|
}
|
|
985
985
|
}
|
|
986
986
|
for (const key of ['contextWindow', 'maxTokens']) {
|
|
987
|
-
|
|
988
|
-
|
|
987
|
+
// Stessa scala del parser: un valore che il dialogo accetta e il file
|
|
988
|
+
// rifiuta farebbe fallire l'intero salvataggio, non il solo campo.
|
|
989
|
+
if (patch[key] !== undefined && (!Number.isSafeInteger(patch[key]) || patch[key] < 1024 || patch[key] > 100000000)) {
|
|
990
|
+
throw httpError(400, `${key} non valido: intero fra 1024 e 100000000`);
|
|
989
991
|
}
|
|
990
992
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
+
// `tools` e `vision` sono booleani tipizzati come `reasoning`: `false` e'
|
|
994
|
+
// una risposta, quindi non si rifiuta e non si scambia per assenza.
|
|
995
|
+
for (const key of ['tools', 'vision', 'reasoning']) {
|
|
996
|
+
if (patch[key] !== undefined && typeof patch[key] !== 'boolean') {
|
|
997
|
+
throw httpError(400, `${key} non valido: booleano`);
|
|
998
|
+
}
|
|
993
999
|
}
|
|
994
1000
|
const defs = reloadDefs();
|
|
995
1001
|
if (!(defs.models || []).some((m) => m.id === wantedId && m.engine === wantedEngine)) {
|
|
@@ -1605,6 +1611,8 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
1605
1611
|
// arriverebbe al client come comportamento inspiegabile.
|
|
1606
1612
|
contextWindow: { type: 'integer', required: false, min: 1024, max: 100000000 },
|
|
1607
1613
|
maxTokens: { type: 'integer', required: false, min: 1024, max: 100000000 },
|
|
1614
|
+
tools: { type: 'boolean', required: false },
|
|
1615
|
+
vision: { type: 'boolean', required: false },
|
|
1608
1616
|
reasoning: { type: 'boolean', required: false },
|
|
1609
1617
|
},
|
|
1610
1618
|
cell: {
|
|
@@ -1771,6 +1779,50 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
1771
1779
|
const base = id.replace(/:[^:/]+$/, '');
|
|
1772
1780
|
return base !== id && index.has(base) ? index.get(base) : null;
|
|
1773
1781
|
}
|
|
1782
|
+
// Curated authentication diagnosis of the LISTING attempt. The route sees
|
|
1783
|
+
// only this block: the credential state, the env NAME (already public in
|
|
1784
|
+
// the editor), whether an attempt happened, whether it carried the
|
|
1785
|
+
// credential, and a fixed cause. Never the key value, its provenance, the
|
|
1786
|
+
// endpoint, or any transport exception text. Config (state) and transport
|
|
1787
|
+
// (attempt) stay separate: a present key is not a sent key, and a cached
|
|
1788
|
+
// answer is neither. A fresh cache makes no claim at all about the key
|
|
1789
|
+
// (attempted=false, no cause), and a SUCCESS never claims the endpoint does
|
|
1790
|
+
// not need a key — it only says the listing came back without one.
|
|
1791
|
+
function modelListingAuthDiagnosis(plan, result) {
|
|
1792
|
+
const credentialState = plan && ['none', 'configured', 'missing', 'unavailable'].includes(plan.credentialState)
|
|
1793
|
+
? plan.credentialState : 'none';
|
|
1794
|
+
const envKey = typeof plan?.envKey === 'string' && plan.envKey ? plan.envKey : '';
|
|
1795
|
+
const attempt = result && result.attempt && typeof result.attempt === 'object' ? result.attempt : null;
|
|
1796
|
+
const attempted = !!attempt;
|
|
1797
|
+
const credentialSent = !!(attempt && attempt.credentialSent === true);
|
|
1798
|
+
let cause = '';
|
|
1799
|
+
if (attempt) {
|
|
1800
|
+
if (result.status === 'api') {
|
|
1801
|
+
// Success: the only outcome worth a line is the anonymous one, stated
|
|
1802
|
+
// as a fact about THIS listing, never about the endpoint in general.
|
|
1803
|
+
if (!credentialSent) cause = 'anonymous-success';
|
|
1804
|
+
} else if (attempt.category === 'network' || attempt.category === 'timeout') {
|
|
1805
|
+
cause = 'endpoint-unreachable';
|
|
1806
|
+
} else if (attempt.category === 'invalid-response') {
|
|
1807
|
+
cause = 'invalid-response';
|
|
1808
|
+
} else if (attempt.category === 'http') {
|
|
1809
|
+
const status = Number.isSafeInteger(attempt.status) ? attempt.status : 0;
|
|
1810
|
+
if (status === 403) cause = 'access-denied'; // with OR without the key: permissions or policy
|
|
1811
|
+
else if (status === 401) {
|
|
1812
|
+
if (credentialSent) cause = 'key-rejected';
|
|
1813
|
+
else if (credentialState === 'unavailable') cause = 'credential-unavailable';
|
|
1814
|
+
else cause = 'missing-key';
|
|
1815
|
+
} else cause = 'provider-error';
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
return {
|
|
1819
|
+
credentialState,
|
|
1820
|
+
...(envKey ? { envKey } : {}),
|
|
1821
|
+
attempted,
|
|
1822
|
+
credentialSent,
|
|
1823
|
+
...(cause ? { cause } : {}),
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1774
1826
|
async function discoverEngineModels(engineId, opts = {}) {
|
|
1775
1827
|
const id = String(engineId || '');
|
|
1776
1828
|
if (!id || id.length > 64 || !/^[A-Za-z0-9._-]+$/.test(id)) {
|
|
@@ -1812,10 +1864,12 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
1812
1864
|
id: m.id,
|
|
1813
1865
|
...(Number.isFinite(m.contextWindow) ? { contextWindow: m.contextWindow } : {}),
|
|
1814
1866
|
...(Number.isFinite(m.maxTokens) ? { maxTokens: m.maxTokens } : {}),
|
|
1815
|
-
// definitions.js parses
|
|
1816
|
-
// explicit operator answer, not an absence. Keep it exactly
|
|
1817
|
-
// that, or a provider's `true` would override the user layer
|
|
1818
|
-
// (the merge itself already handles booleans correctly).
|
|
1867
|
+
// definitions.js parses the three flags as typed booleans: `false`
|
|
1868
|
+
// is an explicit operator answer, not an absence. Keep it exactly
|
|
1869
|
+
// like that, or a provider's `true` would override the user layer
|
|
1870
|
+
// here (the merge itself already handles booleans correctly).
|
|
1871
|
+
...(typeof m.tools === 'boolean' ? { tools: m.tools } : {}),
|
|
1872
|
+
...(typeof m.vision === 'boolean' ? { vision: m.vision } : {}),
|
|
1819
1873
|
...(typeof m.reasoning === 'boolean' ? { reasoning: m.reasoning } : {}),
|
|
1820
1874
|
});
|
|
1821
1875
|
}
|
|
@@ -1856,6 +1910,7 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
1856
1910
|
status: result.status,
|
|
1857
1911
|
stale: result.stale === true,
|
|
1858
1912
|
error: result.error || '',
|
|
1913
|
+
auth: modelListingAuthDiagnosis(plan, result),
|
|
1859
1914
|
...(result.fetchedAt ? { fetchedAt: result.fetchedAt } : {}),
|
|
1860
1915
|
...(result.publicCatalog ? { publicCatalog: result.publicCatalog } : {}),
|
|
1861
1916
|
models,
|
package/lib/fleet/definitions.js
CHANGED
|
@@ -196,7 +196,7 @@ const MODEL_ID_MAX = 128;
|
|
|
196
196
|
|
|
197
197
|
function parseModel(m) {
|
|
198
198
|
if (!m || typeof m !== 'object' || Array.isArray(m)) return null;
|
|
199
|
-
const allowed = new Set(['id', 'engine', 'label', 'contextWindow', 'maxTokens', 'reasoning']);
|
|
199
|
+
const allowed = new Set(['id', 'engine', 'label', 'contextWindow', 'maxTokens', 'tools', 'vision', 'reasoning']);
|
|
200
200
|
if (Object.keys(m).some((k) => !allowed.has(k))) return null;
|
|
201
201
|
const id = typeof m.id === 'string' ? m.id.trim() : '';
|
|
202
202
|
// Stessa forma accettata dal gate dei modelli: nessun byte di controllo, un
|
|
@@ -221,6 +221,15 @@ function parseModel(m) {
|
|
|
221
221
|
if (typeof m.reasoning !== 'boolean') return null;
|
|
222
222
|
out.reasoning = m.reasoning;
|
|
223
223
|
}
|
|
224
|
+
// `tools` e `vision`: stessi booleani tipizzati di `reasoning`. `false` e'
|
|
225
|
+
// una RISPOSTA dell'operatore, non un'assenza: si conserva tale, cosi' la
|
|
226
|
+
// scoperta (user > api > public) non lascia salire il `true` di un catalogo
|
|
227
|
+
// sopra la parola esplicita di chi dichiara.
|
|
228
|
+
for (const key of ['tools', 'vision']) {
|
|
229
|
+
if (m[key] === undefined) continue;
|
|
230
|
+
if (typeof m[key] !== 'boolean') return null;
|
|
231
|
+
out[key] = m[key];
|
|
232
|
+
}
|
|
224
233
|
return out;
|
|
225
234
|
}
|
|
226
235
|
|
package/lib/fleet/managed.js
CHANGED
|
@@ -1334,10 +1334,30 @@ function engineModelDiscoveryPlan(profile, spec, cfg, home, engineId) {
|
|
|
1334
1334
|
}
|
|
1335
1335
|
if (!baseUrl) return null;
|
|
1336
1336
|
const plan = { engine: engineId || profile.id, collector, baseUrl };
|
|
1337
|
+
// The credential STATE travels with the plan, from the same resolution the
|
|
1338
|
+
// launcher uses (the caller never re-reads the credential sources):
|
|
1339
|
+
// none / configured / missing / unavailable, plus the env NAME when one
|
|
1340
|
+
// exists and the editor already shows it. A file that exists but cannot be
|
|
1341
|
+
// read is "unavailable", not "missing" — the same distinction describeManaged
|
|
1342
|
+
// makes for launching. The value, its provenance paths/hashes and any
|
|
1343
|
+
// exception text stay here: the plan hands the key to the collector in
|
|
1344
|
+
// memory and nothing else leaves.
|
|
1337
1345
|
try {
|
|
1338
|
-
const cred = credential(profile, spec || {}, cfg, home);
|
|
1346
|
+
const cred = credential(profile, spec || {}, cfg, home, { blocked: [] });
|
|
1347
|
+
plan.credentialState = !cred ? 'unavailable'
|
|
1348
|
+
: cred.value ? 'configured'
|
|
1349
|
+
: cred.source === 'unreadable' ? 'unavailable'
|
|
1350
|
+
: cred.source === 'missing' ? 'missing'
|
|
1351
|
+
: 'none';
|
|
1339
1352
|
if (cred && cred.value) plan.apiKey = cred.value;
|
|
1340
|
-
|
|
1353
|
+
const envKey = cred && typeof cred.envKey === 'string' ? cred.envKey : '';
|
|
1354
|
+
// 'login'/'none' resolve to a pseudo key name: never invent one for them.
|
|
1355
|
+
if (plan.credentialState !== 'none' && ENV_KEY_RE.test(envKey || '')) plan.envKey = envKey;
|
|
1356
|
+
} catch (_) {
|
|
1357
|
+
// Resolution itself failed: the key is not known to be missing — it is
|
|
1358
|
+
// unavailable, and an anonymous rejection will say exactly that.
|
|
1359
|
+
plan.credentialState = 'unavailable';
|
|
1360
|
+
}
|
|
1341
1361
|
return plan;
|
|
1342
1362
|
}
|
|
1343
1363
|
async function discoverOllamaModels(opts = {}) {
|