aegis-desktop 1.0.0 → 1.0.2

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.
Files changed (96) hide show
  1. package/bin/aegis.js +5 -0
  2. package/lib/auto.js +5 -0
  3. package/lib/avatar/companion.js +5 -0
  4. package/lib/avatar/cosmetics.js +5 -0
  5. package/lib/avatar/events.js +5 -0
  6. package/lib/avatar/holder-turn.js +5 -0
  7. package/lib/avatar/holders.js +5 -0
  8. package/lib/avatar/identity.js +5 -0
  9. package/lib/avatar/level.js +5 -0
  10. package/lib/avatar/mirror.js +5 -0
  11. package/lib/avatar/persona.js +5 -0
  12. package/lib/avatar/profile.js +5 -0
  13. package/lib/avatar/register.js +5 -0
  14. package/lib/avatar/store.js +5 -0
  15. package/lib/avatar/tiers.js +5 -0
  16. package/lib/avatar/transfer.js +5 -0
  17. package/lib/avatar/turn.js +5 -0
  18. package/lib/avatar/voice.js +5 -0
  19. package/lib/avatar/xp.js +5 -0
  20. package/lib/deep-link.js +5 -0
  21. package/lib/fingerprint.js +5 -0
  22. package/lib/local/aegis-seal.mjs +5 -0
  23. package/lib/local/agents.js +5 -0
  24. package/lib/local/autonomous.js +5 -0
  25. package/lib/local/context.js +5 -0
  26. package/lib/local/covenant.js +5 -0
  27. package/lib/local/engine.js +129 -0
  28. package/lib/local/entitlement.js +5 -0
  29. package/lib/local/git-scope.js +5 -0
  30. package/lib/local/local.js +5 -0
  31. package/lib/local/prompt.js +5 -0
  32. package/lib/local/queue.js +5 -0
  33. package/lib/local/session-rounds.js +5 -0
  34. package/lib/local/shell.js +88 -9
  35. package/lib/local/telemetry.js +5 -0
  36. package/lib/local/tools.js +13 -6
  37. package/lib/local/turn-guard.js +5 -0
  38. package/lib/local/worktree-lock.js +5 -0
  39. package/lib/provider-key-mirror.js +5 -0
  40. package/lib/quick-launcher.js +5 -0
  41. package/lib/settings.js +5 -0
  42. package/lib/sync/memory-queue.js +5 -0
  43. package/lib/sync/persist-gate.js +5 -0
  44. package/lib/sync/sessions.js +5 -0
  45. package/lib/window-state.js +5 -0
  46. package/main.js +34 -0
  47. package/package.json +1 -1
  48. package/preload.js +14 -0
  49. package/renderer/app.js +111 -0
  50. package/renderer/avatar/assemble.js +5 -0
  51. package/renderer/avatar/avatar.css +7 -0
  52. package/renderer/avatar/avatar.js +5 -0
  53. package/renderer/avatar/cards.js +5 -0
  54. package/renderer/avatar/hud.js +5 -0
  55. package/renderer/avatar/machine.js +5 -0
  56. package/renderer/avatar/pane.js +5 -0
  57. package/renderer/avatar/parts.js +5 -0
  58. package/renderer/budget.js +5 -0
  59. package/renderer/diff.js +5 -0
  60. package/renderer/diffview.js +5 -0
  61. package/renderer/fingerprint.css +7 -0
  62. package/renderer/fingerprint.js +5 -0
  63. package/renderer/fonts/fonts.css +7 -0
  64. package/renderer/index.html +7 -0
  65. package/renderer/markdown.js +5 -0
  66. package/renderer/quick.css +7 -0
  67. package/renderer/quick.html +7 -0
  68. package/renderer/quick.js +5 -0
  69. package/renderer/stream-policy.js +5 -0
  70. package/renderer/style.css +7 -0
  71. package/renderer/transcript-view.js +5 -0
  72. package/renderer/usage.js +5 -0
  73. package/vendor/aegis.js +5 -0
  74. package/vendor/agent-gate.mjs +23 -0
  75. package/vendor/attest.js +5 -0
  76. package/vendor/authorship-key.js +5 -0
  77. package/vendor/auto-provision.js +5 -0
  78. package/vendor/brain-catalog.js +5 -0
  79. package/vendor/byok-catalog.js +5 -0
  80. package/vendor/credentials.js +5 -0
  81. package/vendor/device-fingerprint.js +5 -0
  82. package/vendor/env-file.js +5 -0
  83. package/vendor/fingerprint.js +5 -0
  84. package/vendor/foreign-memory.js +5 -0
  85. package/vendor/hooks.js +5 -0
  86. package/vendor/model-discovery.js +332 -0
  87. package/vendor/platform.js +5 -0
  88. package/vendor/prefs.js +5 -0
  89. package/vendor/presence-constants.js +5 -0
  90. package/vendor/presence.js +5 -0
  91. package/vendor/record-vault.js +5 -0
  92. package/vendor/session-store.js +5 -0
  93. package/vendor/topup.js +5 -0
  94. package/vendor/touch.js +5 -0
  95. package/vendor/update.js +5 -0
  96. package/vendor/user-models.js +5 -0
package/vendor/attest.js CHANGED
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
package/vendor/hooks.js CHANGED
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -0,0 +1,332 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
6
+ 'use strict';
7
+
8
+ /**
9
+ * Ask the VENDOR what it serves, so declaring a model stops depending on the
10
+ * user remembering an id.
11
+ *
12
+ * `client/user-models.js` is how a model the AEGIS catalog does not name
13
+ * becomes a pickable row, and `/model-add` is how a host writes one — but a
14
+ * path whose only input is "type the vendor's model id" moves the release-lag
15
+ * problem onto the user's memory instead of removing it. The cost is visible
16
+ * in this repo: `client/byok-catalog.js` carries a hand-copied, hand-verified
17
+ * list of Anthropic's ids ("Verified live 2026-10-11 against `GET
18
+ * https://api.anthropic.com/v1/models`") plus a paragraph on which id is
19
+ * retired — a chore the list endpoint answers in one request, and which goes
20
+ * stale the day the vendor ships.
21
+ *
22
+ * So this module asks. It is pure in the parts that matter:
23
+ * - no filesystem and no key store: the caller resolves the key and passes
24
+ * it in, exactly as the callers of `env-file.js` already do;
25
+ * - no network at load time and no module-scope `process` read;
26
+ * - `fetchImpl` is injectable, so every test runs against a stub;
27
+ * - it NEVER throws. A wrong key, a blocked network or a vendor changing
28
+ * its response shape returns `{ ok: false, error }` for the caller to
29
+ * report. Discovery sits on top of a path that already works by typing
30
+ * the id, so a failure here must not be able to take that path down.
31
+ *
32
+ * Only ids `client/user-models.js` would accept come back, because the store
33
+ * that is about to be written is the one with the rule — a discovery list that
34
+ * offered an id `/model-add` then refused would be worse than no list.
35
+ */
36
+
37
+ const { sanitizeModel } = require('./user-models.js');
38
+
39
+ /** How long a vendor gets to answer before discovery stands down. */
40
+ const DISCOVERY_TIMEOUT_MS = 8000;
41
+
42
+ /** `Authorization: Bearer <key>` — what most OpenAI-compatible vendors take. */
43
+ function bearer(key) {
44
+ return { authorization: `Bearer ${key}` };
45
+ }
46
+
47
+ /**
48
+ * The providers whose OWN list endpoint this module knows, by the id the BYOK
49
+ * relay uses. A provider that is not here is not an error: it is a provider
50
+ * whose list is either undocumented or has no stable endpoint, and the caller
51
+ * falls back to asking the user to type the id.
52
+ *
53
+ * Keys are lowercase provider ids; `aliases` maps the other spellings a user
54
+ * might type (`/model-discover xai` and `/model-discover grok` must hit the
55
+ * same vendor).
56
+ */
57
+ const ENDPOINTS = Object.freeze({
58
+ anthropic: Object.freeze({
59
+ url: 'https://api.anthropic.com/v1/models',
60
+ headers: (key) => ({ 'x-api-key': key, 'anthropic-version': '2023-06-01' }),
61
+ note: 'the vendor requires anthropic-version; the console key is sent as x-api-key',
62
+ }),
63
+ openai: Object.freeze({
64
+ url: 'https://api.openai.com/v1/models',
65
+ headers: bearer,
66
+ note: 'OpenAI-compatible: { data: [ { id } ] }',
67
+ }),
68
+ deepseek: Object.freeze({
69
+ url: 'https://api.deepseek.com/models',
70
+ headers: bearer,
71
+ note: 'OpenAI-compatible; /v1/models answers the same list',
72
+ }),
73
+ grok: Object.freeze({
74
+ url: 'https://api.x.ai/v1/models',
75
+ headers: bearer,
76
+ note: 'xAI answers on /v1/models under the vendor id grok',
77
+ }),
78
+ gemini: Object.freeze({
79
+ url: 'https://generativelanguage.googleapis.com/v1beta/models',
80
+ headers: () => ({}),
81
+ query: (key) => ({ key }),
82
+ note: 'the key travels in the query string; ids come back as "models/<id>"',
83
+ }),
84
+ groq: Object.freeze({
85
+ url: 'https://api.groq.com/openai/v1/models',
86
+ headers: bearer,
87
+ note: 'OpenAI-compatible under /openai/v1',
88
+ }),
89
+ mistral: Object.freeze({
90
+ url: 'https://api.mistral.ai/v1/models',
91
+ headers: bearer,
92
+ note: 'OpenAI-compatible',
93
+ }),
94
+ openrouter: Object.freeze({
95
+ url: 'https://openrouter.ai/api/v1/models',
96
+ headers: bearer,
97
+ note: 'OpenAI-compatible; ids are prefixed "vendor/model"',
98
+ }),
99
+ together: Object.freeze({
100
+ url: 'https://api.together.xyz/v1/models',
101
+ headers: bearer,
102
+ note: 'OpenAI-compatible',
103
+ }),
104
+ });
105
+
106
+ /** Other spellings a user types for a provider this module knows. */
107
+ const ALIASES = Object.freeze({
108
+ 'x.ai': 'grok',
109
+ xai: 'grok',
110
+ 'google': 'gemini',
111
+ 'google-ai': 'gemini',
112
+ 'googleai': 'gemini',
113
+ 'open-ai': 'openai',
114
+ 'deep-seek': 'deepseek',
115
+ });
116
+
117
+ /**
118
+ * The families that are NOT chat models, so a picker is not 400 lines of
119
+ * embedding rows. These are not dropped — they come back in `others`, because
120
+ * "the vendor lists 60 ids and the picker showed 12" is a fact the user is
121
+ * owed, not a silent filter.
122
+ */
123
+ const NON_CHAT_RE = /embed|whisper|\btts\b|dall-e|moderation|rerank|imagen|\bveo\b|\baqa\b|transcribe|audio|speech|synthia|guard-content/i;
124
+
125
+ /** The frozen endpoint for a provider id (or `null` when unknown). */
126
+ function endpointFor(provider) {
127
+ const raw = String(provider == null ? '' : provider).trim().toLowerCase();
128
+ if (!raw) return null;
129
+ const id = ALIASES[raw] || raw;
130
+ const entry = ENDPOINTS[id];
131
+ return entry ? Object.freeze({ id, ...entry }) : null;
132
+ }
133
+
134
+ /** True when discovery knows how to ask this provider at all. */
135
+ function canDiscover(provider) {
136
+ return Boolean(endpointFor(provider));
137
+ }
138
+
139
+ /** Every provider id discovery can ask, sorted — for a hint line. */
140
+ function discoverableProviders() {
141
+ return Object.keys(ENDPOINTS).sort();
142
+ }
143
+
144
+ /** True when a vendor id names something other than a chat model. */
145
+ function looksNonChat(id) {
146
+ return NON_CHAT_RE.test(String(id));
147
+ }
148
+
149
+ /**
150
+ * Pull candidate ids out of whatever shape the vendor answered with.
151
+ *
152
+ * Handled: `{ data: [ { id } ] }` (OpenAI, DeepSeek, xAI, Groq, Mistral,
153
+ * OpenRouter, Together), Anthropic's identical `{ data: [ { id } ] }`, Gemini's
154
+ * `{ models: [ { name: "models/<id>" } ] }`, and a bare array of strings or
155
+ * objects — vendors that answer unversioned occasionally answer either.
156
+ *
157
+ * Returns `null` when the payload has no list at all, which the caller reports
158
+ * as "the vendor answered in a shape this client does not know" rather than as
159
+ * an empty catalog (an empty list is a real, and very different, answer).
160
+ */
161
+ function idsFromPayload(payload) {
162
+ const rows = Array.isArray(payload)
163
+ ? payload
164
+ : Array.isArray(payload && payload.data)
165
+ ? payload.data
166
+ : Array.isArray(payload && payload.models)
167
+ ? payload.models
168
+ : null;
169
+ if (!rows) return null;
170
+ const ids = [];
171
+ const others = [];
172
+ for (const row of rows) {
173
+ if (row == null) continue;
174
+ const raw = typeof row === 'string' ? row : row.id || row.name || row.model;
175
+ if (typeof raw !== 'string' || !raw.trim()) continue;
176
+ // Gemini names rows "models/<id>"; the vendor id is the part after it.
177
+ const candidate = raw.trim().replace(/^models\//, '');
178
+ // A row that declares the methods it supports and does NOT list
179
+ // generateContent is not a chat model (Imagen says "predict"). That is a
180
+ // stronger signal than the name, so it is used before the name is.
181
+ const methods = row && Array.isArray(row.supportedGenerationMethods)
182
+ ? row.supportedGenerationMethods
183
+ : row && Array.isArray(row.supported_actions)
184
+ ? row.supported_actions
185
+ : null;
186
+ const chatty = methods ? methods.includes('generateContent') || methods.includes('generate') : null;
187
+ let id;
188
+ try {
189
+ id = sanitizeModel(candidate);
190
+ } catch {
191
+ continue; // not a string the declaration store would accept either
192
+ }
193
+ if (chatty === false || (chatty === null && looksNonChat(id))) others.push(id);
194
+ else ids.push(id);
195
+ }
196
+ const uniq = (list) => [...new Set(list)];
197
+ return { ids: uniq(ids), others: uniq(others).filter((id) => !ids.includes(id)) };
198
+ }
199
+
200
+ /**
201
+ * Ask a provider what it serves.
202
+ *
203
+ * `opts.key` is the user's own vendor key — required, and never read or logged
204
+ * here. `opts.fetchImpl` defaults to the global fetch. `opts.timeoutMs` bounds
205
+ * the request; the timer is always cleared, and the caller's `opts.signal` is
206
+ * honoured so a cancelled turn cancels the request.
207
+ *
208
+ * Resolves — never rejects — with one of:
209
+ * { ok: true, provider, endpoint, ids, others, count }
210
+ * { ok: false, provider, supported, error }
211
+ * `supported: false` means this module has no endpoint for that vendor, which
212
+ * is a different thing from a vendor that refused the key.
213
+ */
214
+ async function discoverModels(provider, opts = {}) {
215
+ const entry = endpointFor(provider);
216
+ const asked = String(provider == null ? '' : provider).trim().toLowerCase();
217
+ const key = String(opts.key == null ? '' : opts.key).trim();
218
+ if (!entry) {
219
+ return {
220
+ ok: false,
221
+ provider: asked,
222
+ supported: false,
223
+ error: `${asked || 'that provider'} has no model-list endpoint this client knows`,
224
+ };
225
+ }
226
+ if (!key) {
227
+ return { ok: false, provider: entry.id, supported: true, error: `no ${entry.id} key on this machine` };
228
+ }
229
+ const fetchImpl = opts.fetchImpl || (typeof fetch === 'function' ? fetch : null);
230
+ if (!fetchImpl) {
231
+ return { ok: false, provider: entry.id, supported: true, error: 'no fetch implementation available' };
232
+ }
233
+ const timeoutMs = Number.isFinite(opts.timeoutMs) ? opts.timeoutMs : DISCOVERY_TIMEOUT_MS;
234
+ const controller = typeof AbortController === 'function' ? new AbortController() : null;
235
+ const timer = controller ? setTimeout(() => controller.abort(), timeoutMs) : null;
236
+ // Deliberately NOT unref'd: the timer is the thing that must fire when a
237
+ // vendor goes quiet. An unref'd timer in a process whose only other pending
238
+ // work is the await would be collected before it fired, and the hang this
239
+ // timeout exists to prevent is exactly what that produces. It is cleared in
240
+ // the `finally` below, so it never outlives the request either way.
241
+ const onAbort = () => controller && controller.abort();
242
+ if (controller && opts.signal) {
243
+ if (opts.signal.aborted) onAbort();
244
+ else if (typeof opts.signal.addEventListener === 'function') opts.signal.addEventListener('abort', onAbort, { once: true });
245
+ }
246
+ try {
247
+ const url = new URL(entry.url);
248
+ if (typeof entry.query === 'function') {
249
+ for (const [k, v] of Object.entries(entry.query(key) || {})) url.searchParams.set(k, v);
250
+ }
251
+ const headers = { accept: 'application/json' };
252
+ for (const [k, v] of Object.entries(entry.headers(key) || {})) {
253
+ // Vendors that want the header spelled a particular way get it; the
254
+ // table stores the canonical lowercase name.
255
+ headers[k === 'authorization' ? 'Authorization' : k] = v;
256
+ }
257
+ const res = await fetchImpl(url.toString(), {
258
+ method: 'GET',
259
+ headers,
260
+ signal: controller ? controller.signal : undefined,
261
+ });
262
+ const status = res && typeof res.status === 'number' ? res.status : 0;
263
+ if (!res || (typeof res.ok === 'boolean' && !res.ok)) {
264
+ // 401/403 is the common case and deserves its own sentence: the fix is
265
+ // a different key, not a different provider.
266
+ const why = status === 401 || status === 403
267
+ ? `the vendor refused the key (HTTP ${status})`
268
+ : status === 404
269
+ ? `the vendor has no model list at ${url.pathname} (HTTP 404)`
270
+ : `the vendor answered HTTP ${status || 'nothing'}`;
271
+ return { ok: false, provider: entry.id, supported: true, status, error: why };
272
+ }
273
+ let payload;
274
+ try {
275
+ payload = await res.json();
276
+ } catch (err) {
277
+ return {
278
+ ok: false,
279
+ provider: entry.id,
280
+ supported: true,
281
+ status,
282
+ error: `the vendor's reply was not JSON (${(err && err.message) || err})`,
283
+ };
284
+ }
285
+ const parsed = idsFromPayload(payload);
286
+ if (!parsed) {
287
+ return {
288
+ ok: false,
289
+ provider: entry.id,
290
+ supported: true,
291
+ status,
292
+ error: `the vendor answered in a shape this client does not know (no "data" or "models" list)`,
293
+ };
294
+ }
295
+ return {
296
+ ok: true,
297
+ provider: entry.id,
298
+ endpoint: entry.url,
299
+ ids: parsed.ids,
300
+ others: parsed.others,
301
+ count: parsed.ids.length,
302
+ };
303
+ } catch (err) {
304
+ const aborted = (err && (err.name === 'AbortError' || err.code === 'ABORT_ERR')) || (controller && controller.signal.aborted);
305
+ return {
306
+ ok: false,
307
+ provider: entry.id,
308
+ supported: true,
309
+ error: aborted
310
+ ? `the vendor did not answer within ${timeoutMs} ms`
311
+ : `could not reach the vendor (${(err && err.message) || err})`,
312
+ };
313
+ } finally {
314
+ if (timer) clearTimeout(timer);
315
+ if (controller && opts.signal && typeof opts.signal.removeEventListener === 'function') {
316
+ opts.signal.removeEventListener('abort', onAbort);
317
+ }
318
+ }
319
+ }
320
+
321
+ module.exports = {
322
+ DISCOVERY_TIMEOUT_MS,
323
+ ENDPOINTS,
324
+ ALIASES,
325
+ NON_CHAT_RE,
326
+ endpointFor,
327
+ canDiscover,
328
+ discoverableProviders,
329
+ looksNonChat,
330
+ idsFromPayload,
331
+ discoverModels,
332
+ };
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
package/vendor/prefs.js CHANGED
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
package/vendor/topup.js CHANGED
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
package/vendor/touch.js CHANGED
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**
package/vendor/update.js CHANGED
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  /**
2
7
  * "There is a newer version" — the notice this CLI never had.
3
8
  *
@@ -1,3 +1,8 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (C) 2026 Niklas Borneklint — AEGIS Code (aegiscode) / Aegiscloud.
3
+ // AEGIS Code (aegiscode) — source: https://github.com/aegisinfo/aegiscode-plugin
4
+ // Conveying a modified copy, or running one as a network service, owes the
5
+ // corresponding source under this licence (§5/§13) — see LICENSING.md.
1
6
  'use strict';
2
7
 
3
8
  /**