@mmmbuto/nexuscrew 0.9.75 → 0.9.77
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 +12 -0
- package/frontend/dist/assets/index-Cgd3OQws.js +169 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/dist/version.json +1 -1
- package/lib/fleet/builtin.js +127 -37
- package/lib/fleet/definitions.js +10 -1
- package/lib/fleet/managed.js +54 -14
- package/lib/fleet/model-discovery.js +307 -69
- 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-Cgd3OQws.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.77"}
|
package/lib/fleet/builtin.js
CHANGED
|
@@ -41,7 +41,7 @@ const {
|
|
|
41
41
|
CATALOG, CLIENT_LABELS, CLIENT_ORDER, engineClient, ordinaEngine, isStandardClient,
|
|
42
42
|
credential: resolveProfileCredential, extraModelsFrom, knownMcpServerNames, profileFor,
|
|
43
43
|
normalizeManagedSpec, codexSurfaceNames,
|
|
44
|
-
engineModelDiscoveryPlan, ollamaCloudCatalogModels, declaredModelsFor, ALIBABA_PI_MODELS,
|
|
44
|
+
engineModelDiscoveryPlan, modelListingCredential, ollamaCloudCatalogModels, declaredModelsFor, ALIBABA_PI_MODELS,
|
|
45
45
|
} = require('./managed.js');
|
|
46
46
|
const { probeModel, probeCustomEndpoint } = require('./model-probe.js');
|
|
47
47
|
const { createModelDiscovery, DISCOVERY_PROPOSAL_CAP } = require('./model-discovery.js');
|
|
@@ -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,67 @@ 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
|
+
}
|
|
1826
|
+
// Backup proposals for fields no layer could fill: the missing-data dialog
|
|
1827
|
+
// offers what the internal catalog knows, and the operator's confirmation
|
|
1828
|
+
// is what turns a proposal into a user value.
|
|
1829
|
+
function withBackupDefaults(records, profile) {
|
|
1830
|
+
const internalIndex = internalModelIndex(profile);
|
|
1831
|
+
return (Array.isArray(records) ? records : []).map((m) => {
|
|
1832
|
+
const backupValues = {};
|
|
1833
|
+
for (const field of ['contextWindow', 'maxTokens']) {
|
|
1834
|
+
if (m[field] === undefined) {
|
|
1835
|
+
const known = internalValueFor(internalIndex, m.id);
|
|
1836
|
+
if (known && known[field] !== undefined) backupValues[field] = known[field];
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
return Object.keys(backupValues).length ? { ...m, backupValues } : m;
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1774
1843
|
async function discoverEngineModels(engineId, opts = {}) {
|
|
1775
1844
|
const id = String(engineId || '');
|
|
1776
1845
|
if (!id || id.length > 64 || !/^[A-Za-z0-9._-]+$/.test(id)) {
|
|
@@ -1791,16 +1860,10 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
1791
1860
|
spec = profile ? { client: profile.client, provider: profile.provider, ...(profile.credentialProfile ? { credentialProfile: profile.credentialProfile } : {}) } : null;
|
|
1792
1861
|
}
|
|
1793
1862
|
if (!profile) { const e = new Error(`engine sconosciuto: ${id}`); e.status = 404; throw e; }
|
|
1794
|
-
const plan = engineModelDiscoveryPlan(profile, spec, cfg, home, id);
|
|
1795
|
-
if (!plan) {
|
|
1796
|
-
// No HTTP listing to interrogate (login/native flows, local providers
|
|
1797
|
-
// without a base URL): an honest empty answer, never an error, so the
|
|
1798
|
-
// editor stays usable. The UI hides the section on `supported: false`.
|
|
1799
|
-
return { engine: id, supported: false, source: 'unsupported', models: [] };
|
|
1800
|
-
}
|
|
1801
1863
|
// User layer: models declared in fleet.json for this engine (or its
|
|
1802
1864
|
// profile) — the values the operator chose, always on top and never
|
|
1803
|
-
// rewritten by discovery.
|
|
1865
|
+
// rewritten by discovery. Built BEFORE the plan: an engine without a
|
|
1866
|
+
// listing still has ids worth enriching.
|
|
1804
1867
|
const extraModels = extraModelsFrom(defs);
|
|
1805
1868
|
const userModels = [];
|
|
1806
1869
|
const seenUser = new Set();
|
|
@@ -1812,43 +1875,69 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
1812
1875
|
id: m.id,
|
|
1813
1876
|
...(Number.isFinite(m.contextWindow) ? { contextWindow: m.contextWindow } : {}),
|
|
1814
1877
|
...(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).
|
|
1878
|
+
// definitions.js parses the three flags as typed booleans: `false`
|
|
1879
|
+
// is an explicit operator answer, not an absence. Keep it exactly
|
|
1880
|
+
// like that, or a provider's `true` would override the user layer
|
|
1881
|
+
// here (the merge itself already handles booleans correctly).
|
|
1882
|
+
...(typeof m.tools === 'boolean' ? { tools: m.tools } : {}),
|
|
1883
|
+
...(typeof m.vision === 'boolean' ? { vision: m.vision } : {}),
|
|
1819
1884
|
...(typeof m.reasoning === 'boolean' ? { reasoning: m.reasoning } : {}),
|
|
1820
1885
|
});
|
|
1821
1886
|
}
|
|
1822
1887
|
}
|
|
1823
1888
|
const backupModels = backupModelsFor(profile);
|
|
1824
1889
|
const wanted = [...new Set([...userModels, ...backupModels].map((m) => m.id))].slice(0, 32);
|
|
1890
|
+
// The plan is the HTTP listing, when the profile has one. When it does
|
|
1891
|
+
// not, the same composition still runs with the API layer explicitly
|
|
1892
|
+
// unavailable: known ids are enriched from the public snapshot, with no
|
|
1893
|
+
// invented collector, request or auth attempt.
|
|
1894
|
+
const plan = engineModelDiscoveryPlan(profile, spec, cfg, home, id);
|
|
1895
|
+
// The active model is configuration the operator already chose. It counts
|
|
1896
|
+
// as a known id only when it is the ONLY one: with a declared model or a
|
|
1897
|
+
// profile backup the proposal already has its ids, and adding it would
|
|
1898
|
+
// duplicate a row or change the order.
|
|
1899
|
+
const activeModel = spec && typeof spec.model === 'string' ? spec.model.trim() : '';
|
|
1900
|
+
const knownIds = !userModels.length && !backupModels.length && activeModel ? [activeModel] : [];
|
|
1825
1901
|
const result = await engineModelDiscovery.discoverEngine({
|
|
1826
|
-
...plan,
|
|
1902
|
+
...(plan || {}),
|
|
1903
|
+
engine: id,
|
|
1827
1904
|
home,
|
|
1828
1905
|
userModels, backupModels,
|
|
1906
|
+
knownIds,
|
|
1829
1907
|
...(wanted.length ? { detailIds: wanted } : {}),
|
|
1830
|
-
publicCatalog: {
|
|
1908
|
+
publicCatalog: {
|
|
1909
|
+
provider: profile.provider,
|
|
1910
|
+
...(plan && plan.baseUrl ? { baseUrl: plan.baseUrl } : {}),
|
|
1911
|
+
...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
|
|
1912
|
+
},
|
|
1831
1913
|
...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
|
|
1832
1914
|
...(opts.refresh === true ? { noCache: true } : {}),
|
|
1915
|
+
...(plan ? {} : { apiAvailable: false }),
|
|
1833
1916
|
});
|
|
1834
1917
|
// Curated response: `plan` (baseUrl, apiKey) stays out; apiModels is
|
|
1835
|
-
// already merged (with provenance) into `models`.
|
|
1836
|
-
//
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1918
|
+
// already merged (with provenance) into `models`. `backupValues` rides
|
|
1919
|
+
// along only for fields NO layer could fill.
|
|
1920
|
+
const models = withBackupDefaults(result.models, profile);
|
|
1921
|
+
if (!plan) {
|
|
1922
|
+
const cred = modelListingCredential(profile, spec, cfg, home);
|
|
1923
|
+
return {
|
|
1924
|
+
engine: id,
|
|
1925
|
+
supported: false,
|
|
1926
|
+
source: 'unsupported',
|
|
1927
|
+
status: result.status,
|
|
1928
|
+
stale: false,
|
|
1929
|
+
error: '',
|
|
1930
|
+
auth: {
|
|
1931
|
+
credentialState: cred.credentialState,
|
|
1932
|
+
...(cred.envKey ? { envKey: cred.envKey } : {}),
|
|
1933
|
+
attempted: false,
|
|
1934
|
+
credentialSent: false,
|
|
1935
|
+
},
|
|
1936
|
+
...(result.publicCatalog ? { publicCatalog: result.publicCatalog } : {}),
|
|
1937
|
+
models,
|
|
1938
|
+
cap: DISCOVERY_PROPOSAL_CAP,
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1852
1941
|
return {
|
|
1853
1942
|
engine: id,
|
|
1854
1943
|
supported: true,
|
|
@@ -1856,6 +1945,7 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
1856
1945
|
status: result.status,
|
|
1857
1946
|
stale: result.stale === true,
|
|
1858
1947
|
error: result.error || '',
|
|
1948
|
+
auth: modelListingAuthDiagnosis(plan, result),
|
|
1859
1949
|
...(result.fetchedAt ? { fetchedAt: result.fetchedAt } : {}),
|
|
1860
1950
|
...(result.publicCatalog ? { publicCatalog: result.publicCatalog } : {}),
|
|
1861
1951
|
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
|
@@ -1313,32 +1313,72 @@ const sharedModelDiscovery = createModelDiscovery();
|
|
|
1313
1313
|
// (catalog engines) the profile id IS the engine id.
|
|
1314
1314
|
function engineModelDiscoveryPlan(profile, spec, cfg, home, engineId) {
|
|
1315
1315
|
if (!profile) return null;
|
|
1316
|
-
const
|
|
1317
|
-
|
|
1316
|
+
const httpUrl = (value) => (typeof value === 'string' && /^https?:\/\//i.test(value) ? value : '');
|
|
1317
|
+
// Endpoint precedence follows the launcher: the normalized spec, the
|
|
1318
|
+
// generated provider extension, then the profile's own endpoint. A native
|
|
1319
|
+
// provider has no explicit endpoint here, and is never pointed at another
|
|
1320
|
+
// client's profile.
|
|
1321
|
+
const specBase = httpUrl(spec && spec.baseUrl);
|
|
1322
|
+
const extensionBase = httpUrl(profile.piExtension && profile.piExtension.baseUrl);
|
|
1323
|
+
const profileBase = httpUrl(profile.endpoint);
|
|
1324
|
+
// The wire name may come hyphenated (`openai-completions`, the Pi form) or
|
|
1325
|
+
// underscored (the normalized form). It is the same transport.
|
|
1326
|
+
const rawWire = spec && typeof spec.protocol === 'string' && spec.protocol
|
|
1327
|
+
? spec.protocol : (typeof profile.protocol === 'string' ? profile.protocol : '');
|
|
1328
|
+
const wire = rawWire.replace(/-/g, '_');
|
|
1318
1329
|
let collector = null;
|
|
1319
1330
|
let baseUrl = '';
|
|
1320
1331
|
if (profile.provider === 'ollama' || profile.provider === 'ollama-cloud') {
|
|
1332
|
+
// Ollama speaks its own listing API, whatever wire a client uses for chat.
|
|
1321
1333
|
collector = 'ollama';
|
|
1322
|
-
baseUrl = specBase || profileBase || 'http://127.0.0.1:11434';
|
|
1334
|
+
baseUrl = specBase || extensionBase || profileBase || 'http://127.0.0.1:11434';
|
|
1323
1335
|
} else if (profile.provider === 'lmstudio') {
|
|
1324
1336
|
collector = 'lmstudio';
|
|
1325
|
-
baseUrl = specBase || 'http://127.0.0.1:1234';
|
|
1326
|
-
} else if (
|
|
1337
|
+
baseUrl = specBase || extensionBase || 'http://127.0.0.1:1234';
|
|
1338
|
+
} else if (wire === 'anthropic_messages') {
|
|
1327
1339
|
collector = 'anthropic';
|
|
1328
|
-
baseUrl = specBase || profileBase;
|
|
1329
|
-
} else if (
|
|
1340
|
+
baseUrl = specBase || extensionBase || profileBase;
|
|
1341
|
+
} else if (wire === 'openai_responses' || wire === 'openai_completions') {
|
|
1330
1342
|
collector = 'openai-compat';
|
|
1331
|
-
baseUrl = specBase || profileBase;
|
|
1343
|
+
baseUrl = specBase || extensionBase || profileBase;
|
|
1332
1344
|
} else {
|
|
1345
|
+
// `pi_native`, login flows and any other client-owned wire: no explicit
|
|
1346
|
+
// listing contract, so no collector. An explicit endpoint alone is not
|
|
1347
|
+
// proof of a listing API.
|
|
1333
1348
|
return null;
|
|
1334
1349
|
}
|
|
1335
1350
|
if (!baseUrl) return null;
|
|
1336
|
-
|
|
1351
|
+
return { engine: engineId || profile.id, collector, baseUrl, ...modelListingCredential(profile, spec, cfg, home) };
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Credential resolution for a model listing, from the same path the launcher
|
|
1355
|
+
// uses (the caller never re-reads the credential sources): none / configured /
|
|
1356
|
+
// missing / unavailable, plus the env NAME when one exists and the editor
|
|
1357
|
+
// already shows it. A file that exists but cannot be read is "unavailable",
|
|
1358
|
+
// not "missing" — the same distinction describeManaged makes for launching.
|
|
1359
|
+
// The value, its provenance paths/hashes and any exception text stay here:
|
|
1360
|
+
// the value is handed to a collector in memory and nothing else leaves. An
|
|
1361
|
+
// engine with no listing still gets the STATE: the diagnosis refers to the
|
|
1362
|
+
// listing only, never to the client's own login.
|
|
1363
|
+
function modelListingCredential(profile, spec, cfg, home) {
|
|
1364
|
+
const out = { credentialState: 'unavailable' };
|
|
1337
1365
|
try {
|
|
1338
|
-
const cred = credential(profile, spec || {}, cfg, home);
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1366
|
+
const cred = credential(profile, spec || {}, cfg, home, { blocked: [] });
|
|
1367
|
+
out.credentialState = !cred ? 'unavailable'
|
|
1368
|
+
: cred.value ? 'configured'
|
|
1369
|
+
: cred.source === 'unreadable' ? 'unavailable'
|
|
1370
|
+
: cred.source === 'missing' ? 'missing'
|
|
1371
|
+
: 'none';
|
|
1372
|
+
if (cred && cred.value) out.apiKey = cred.value;
|
|
1373
|
+
const envKey = cred && typeof cred.envKey === 'string' ? cred.envKey : '';
|
|
1374
|
+
// 'login'/'none' resolve to a pseudo key name: never invent one for them.
|
|
1375
|
+
if (out.credentialState !== 'none' && ENV_KEY_RE.test(envKey || '')) out.envKey = envKey;
|
|
1376
|
+
} catch (_) {
|
|
1377
|
+
// Resolution itself failed: the key is not known to be missing — it is
|
|
1378
|
+
// unavailable, and an anonymous rejection will say exactly that.
|
|
1379
|
+
out.credentialState = 'unavailable';
|
|
1380
|
+
}
|
|
1381
|
+
return out;
|
|
1342
1382
|
}
|
|
1343
1383
|
async function discoverOllamaModels(opts = {}) {
|
|
1344
1384
|
const now = Date.now(); const ttl = opts.ttlMs === undefined ? 30000 : opts.ttlMs;
|
|
@@ -3145,7 +3185,7 @@ module.exports = {
|
|
|
3145
3185
|
OPENCODE_GO_MESSAGES_MODELS, OPENCODE_GO_RESPONSES_MODELS, OPENCODE_GO_CHAT_MODELS, OPENCODE_GO_LIMITS,
|
|
3146
3186
|
OPENCODE_GO_ANTHROPIC_ROOT, OPENCODE_GO_API_BASE,
|
|
3147
3187
|
CLIENT_LABELS, CLIENT_ORDER, engineClient, ordinaEngine, isStandardClient, normalizeManagedSpec, profileFor,
|
|
3148
|
-
defaultDefinitions, defaultShellEngine, defaultAgyEngine, defaultKimiEngine, defaultGrokEngine, defaultVlEngine, describeManaged, describeCatalogCredential, discoverOllamaModels, engineModelDiscoveryPlan, resolveManagedEngine, needsExplicitNode,
|
|
3188
|
+
defaultDefinitions, defaultShellEngine, defaultAgyEngine, defaultKimiEngine, defaultGrokEngine, defaultVlEngine, describeManaged, describeCatalogCredential, discoverOllamaModels, engineModelDiscoveryPlan, modelListingCredential, resolveManagedEngine, needsExplicitNode,
|
|
3149
3189
|
discoverPiModels, EXTERNAL_DISCOVERY_TIMEOUT_MS, parseEnvFile, parseProviderShellFile, findBinary, publicCatalog, writePiProviderExtension, customCatalogFor,
|
|
3150
3190
|
codexSurfaceNames, writeCellCodexProfile,
|
|
3151
3191
|
writeVlCellPrompt, vlSystemAppendGate,
|