aegis-desktop 0.4.3 → 0.4.4
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/local/engine.js +26 -1
- package/package.json +1 -1
- package/renderer/app.js +27 -1
package/lib/local/engine.js
CHANGED
|
@@ -473,6 +473,17 @@ function createLocalEngine({ aegis, settings, ollama, providers, tools, promptBu
|
|
|
473
473
|
|
|
474
474
|
async function listModels(cls) {
|
|
475
475
|
if (cls === 'aegis') {
|
|
476
|
+
// The catalog is behind the account's key: GET /api/v1/models answers
|
|
477
|
+
// `401 {"error":{"message":"No API key"}}` without one (verified against
|
|
478
|
+
// aegiscloud.org). Aegis Cloud is the *default* class, so firing the call
|
|
479
|
+
// anyway painted a raw "listModels failed: … 401" over the default model
|
|
480
|
+
// picker for every user who had just installed the app and not yet
|
|
481
|
+
// pasted a key — the one state where the UI must say what unblocks it and
|
|
482
|
+
// not what went wrong. Report the missing key as a state (`needsKey`) and
|
|
483
|
+
// let the renderer invite the user to connect; nothing else about the
|
|
484
|
+
// class changes, and the model dropdown keeps its "server default (auto)"
|
|
485
|
+
// entry so the class is usable the moment a key lands.
|
|
486
|
+
if (!aegis.apiKey) return { class: cls, models: [], needsKey: true };
|
|
476
487
|
const data = await aegis.listModels();
|
|
477
488
|
return { class: cls, models: filterAegisCatalog(normalizeCatalog(data && data.models)) };
|
|
478
489
|
}
|
|
@@ -535,7 +546,7 @@ function createLocalEngine({ aegis, settings, ollama, providers, tools, promptBu
|
|
|
535
546
|
model: opts.model,
|
|
536
547
|
mode: opts.mode,
|
|
537
548
|
maxTokens: opts.maxTokens,
|
|
538
|
-
stream:
|
|
549
|
+
stream: opts.stream !== false,
|
|
539
550
|
// The pooled (Nexus) brain is streamed, and an OpenAI-compatible SSE
|
|
540
551
|
// stream reports no token usage unless asked. Without this the Aegis
|
|
541
552
|
// Cloud class — the desktop's default — was the one class that answered
|
|
@@ -699,6 +710,11 @@ function createLocalEngine({ aegis, settings, ollama, providers, tools, promptBu
|
|
|
699
710
|
workers: payload && payload.workers,
|
|
700
711
|
onReasoning,
|
|
701
712
|
idleTimeoutMs: autonomous ? AUTONOMOUS_IDLE_TIMEOUT_MS : undefined,
|
|
713
|
+
// A caller with no live streaming surface (a `--no-stream` CLI flag, a
|
|
714
|
+
// one-shot script) can ask for the buffered non-stream wire form
|
|
715
|
+
// instead. Undefined/anything but `false` keeps every existing caller
|
|
716
|
+
// (the desktop renderer never sets this) on the streamed path.
|
|
717
|
+
stream: payload && payload.stream === false ? false : true,
|
|
702
718
|
};
|
|
703
719
|
|
|
704
720
|
// No round cap: a model that keeps calling tools keeps going for as
|
|
@@ -782,6 +798,15 @@ function createLocalEngine({ aegis, settings, ollama, providers, tools, promptBu
|
|
|
782
798
|
}
|
|
783
799
|
addUsage(res);
|
|
784
800
|
|
|
801
|
+
// A cancelled turn is over. Both recoveries below exist for "the model
|
|
802
|
+
// said nothing", and an aborted request resolves with exactly that —
|
|
803
|
+
// no text — so without this guard a user pressing Esc to stop a slow
|
|
804
|
+
// turn immediately issued ANOTHER billed provider call (and, because
|
|
805
|
+
// every transport concatenates the deltas it forwards, streamed the
|
|
806
|
+
// partial answer a second time onto the same bubble). Measured before
|
|
807
|
+
// the guard: one interrupted turn == two dispatches.
|
|
808
|
+
if (signal.aborted) return withTurnUsage(res);
|
|
809
|
+
|
|
785
810
|
// Budget exhausted before the answer was written. Doubling it costs
|
|
786
811
|
// one request and converts a dead turn into a real one; a second
|
|
787
812
|
// 'length' result is accepted as-is so a hard-capped model can't
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aegis-desktop",
|
|
3
3
|
"productName": "AEGIS Desktop",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.4",
|
|
5
5
|
"description": "Thin Electron host for AEGIS — a local chat UI over the shared client/aegis.js transport. Ships transport + UI only; engine logic stays server-side.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "AEGIS Code",
|
package/renderer/app.js
CHANGED
|
@@ -171,6 +171,11 @@ const CUSTOM_MODEL_PRESETS = {
|
|
|
171
171
|
// already filters out of settings.list(); never render it as a provider row
|
|
172
172
|
// even if a stale store still surfaces it (defect #1).
|
|
173
173
|
const RESERVED_PROVIDERS = new Set(['__aegis', 'aegis']);
|
|
174
|
+
// Where a user without a key gets one. The class picker defaults to Aegis Cloud
|
|
175
|
+
// and the catalog is key-gated, so this is the first thing a new install needs;
|
|
176
|
+
// it lives here rather than inline so the Model hint and any future "connect"
|
|
177
|
+
// affordance cannot drift to two different pages.
|
|
178
|
+
const GET_AEGIS_KEY_URL = 'https://aegiscloud.org';
|
|
174
179
|
|
|
175
180
|
let pendingEl = null;
|
|
176
181
|
let pendingSessionId = null;
|
|
@@ -1858,6 +1863,11 @@ async function loadModels(cls) {
|
|
|
1858
1863
|
try {
|
|
1859
1864
|
const data = await models.listModels(cls);
|
|
1860
1865
|
const list = Array.isArray(data && data.models) ? data.models : [];
|
|
1866
|
+
// No key on the default class: the engine reports the missing credential as
|
|
1867
|
+
// a state instead of letting the catalog call 401 (see engine.listModels).
|
|
1868
|
+
// The hint is where a user finds out they can connect at all — a raw
|
|
1869
|
+
// "listModels failed" told them only that something was broken.
|
|
1870
|
+
const needsKey = Boolean(data && data.needsKey);
|
|
1861
1871
|
for (const m of list) {
|
|
1862
1872
|
modelMeta.set(m.id, m);
|
|
1863
1873
|
const opt = document.createElement('option');
|
|
@@ -1866,12 +1876,28 @@ async function loadModels(cls) {
|
|
|
1866
1876
|
els.modelSelect.appendChild(opt);
|
|
1867
1877
|
}
|
|
1868
1878
|
let hint;
|
|
1869
|
-
if (
|
|
1879
|
+
if (needsKey) {
|
|
1880
|
+
hint = null; // carries a link, built below
|
|
1881
|
+
} else if (!list.length) {
|
|
1870
1882
|
hint = cls === 'ollama' ? 'Ollama not running or no models pulled.' : 'No models listed.';
|
|
1871
1883
|
} else {
|
|
1872
1884
|
hint = `${list.length} model${list.length === 1 ? '' : 's'} available.`;
|
|
1873
1885
|
}
|
|
1874
1886
|
const ceiling = applyMaxTokensClamp(els.modelSelect.value);
|
|
1887
|
+
if (needsKey) {
|
|
1888
|
+
// The hint elements are bare <p>s, so the link has to be a real child
|
|
1889
|
+
// node — a text assignment would wipe it (same shape as capNotice).
|
|
1890
|
+
els.modelHint.textContent =
|
|
1891
|
+
'Connect Aegis Cloud to load its models: paste your AEGIS API key in the Status card above (';
|
|
1892
|
+
const a = document.createElement('a');
|
|
1893
|
+
a.href = GET_AEGIS_KEY_URL;
|
|
1894
|
+
a.target = '_blank';
|
|
1895
|
+
a.rel = 'noreferrer noopener';
|
|
1896
|
+
a.textContent = 'free key at aegiscloud.org';
|
|
1897
|
+
els.modelHint.appendChild(a);
|
|
1898
|
+
els.modelHint.appendChild(document.createTextNode('), then Save.'));
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1875
1901
|
els.modelHint.textContent =
|
|
1876
1902
|
ceiling < FLAT_CEILING ? `${hint} · max output: ${ceiling.toLocaleString()}` : hint;
|
|
1877
1903
|
} catch (err) {
|