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.
- package/bin/aegis.js +5 -0
- package/lib/auto.js +5 -0
- package/lib/avatar/companion.js +5 -0
- package/lib/avatar/cosmetics.js +5 -0
- package/lib/avatar/events.js +5 -0
- package/lib/avatar/holder-turn.js +5 -0
- package/lib/avatar/holders.js +5 -0
- package/lib/avatar/identity.js +5 -0
- package/lib/avatar/level.js +5 -0
- package/lib/avatar/mirror.js +5 -0
- package/lib/avatar/persona.js +5 -0
- package/lib/avatar/profile.js +5 -0
- package/lib/avatar/register.js +5 -0
- package/lib/avatar/store.js +5 -0
- package/lib/avatar/tiers.js +5 -0
- package/lib/avatar/transfer.js +5 -0
- package/lib/avatar/turn.js +5 -0
- package/lib/avatar/voice.js +5 -0
- package/lib/avatar/xp.js +5 -0
- package/lib/deep-link.js +5 -0
- package/lib/fingerprint.js +5 -0
- package/lib/local/aegis-seal.mjs +5 -0
- package/lib/local/agents.js +5 -0
- package/lib/local/autonomous.js +5 -0
- package/lib/local/context.js +5 -0
- package/lib/local/covenant.js +5 -0
- package/lib/local/engine.js +129 -0
- package/lib/local/entitlement.js +5 -0
- package/lib/local/git-scope.js +5 -0
- package/lib/local/local.js +5 -0
- package/lib/local/prompt.js +5 -0
- package/lib/local/queue.js +5 -0
- package/lib/local/session-rounds.js +5 -0
- package/lib/local/shell.js +88 -9
- package/lib/local/telemetry.js +5 -0
- package/lib/local/tools.js +13 -6
- package/lib/local/turn-guard.js +5 -0
- package/lib/local/worktree-lock.js +5 -0
- package/lib/provider-key-mirror.js +5 -0
- package/lib/quick-launcher.js +5 -0
- package/lib/settings.js +5 -0
- package/lib/sync/memory-queue.js +5 -0
- package/lib/sync/persist-gate.js +5 -0
- package/lib/sync/sessions.js +5 -0
- package/lib/window-state.js +5 -0
- package/main.js +34 -0
- package/package.json +1 -1
- package/preload.js +14 -0
- package/renderer/app.js +111 -0
- package/renderer/avatar/assemble.js +5 -0
- package/renderer/avatar/avatar.css +7 -0
- package/renderer/avatar/avatar.js +5 -0
- package/renderer/avatar/cards.js +5 -0
- package/renderer/avatar/hud.js +5 -0
- package/renderer/avatar/machine.js +5 -0
- package/renderer/avatar/pane.js +5 -0
- package/renderer/avatar/parts.js +5 -0
- package/renderer/budget.js +5 -0
- package/renderer/diff.js +5 -0
- package/renderer/diffview.js +5 -0
- package/renderer/fingerprint.css +7 -0
- package/renderer/fingerprint.js +5 -0
- package/renderer/fonts/fonts.css +7 -0
- package/renderer/index.html +7 -0
- package/renderer/markdown.js +5 -0
- package/renderer/quick.css +7 -0
- package/renderer/quick.html +7 -0
- package/renderer/quick.js +5 -0
- package/renderer/stream-policy.js +5 -0
- package/renderer/style.css +7 -0
- package/renderer/transcript-view.js +5 -0
- package/renderer/usage.js +5 -0
- package/vendor/aegis.js +5 -0
- package/vendor/agent-gate.mjs +23 -0
- package/vendor/attest.js +5 -0
- package/vendor/authorship-key.js +5 -0
- package/vendor/auto-provision.js +5 -0
- package/vendor/brain-catalog.js +5 -0
- package/vendor/byok-catalog.js +5 -0
- package/vendor/credentials.js +5 -0
- package/vendor/device-fingerprint.js +5 -0
- package/vendor/env-file.js +5 -0
- package/vendor/fingerprint.js +5 -0
- package/vendor/foreign-memory.js +5 -0
- package/vendor/hooks.js +5 -0
- package/vendor/model-discovery.js +332 -0
- package/vendor/platform.js +5 -0
- package/vendor/prefs.js +5 -0
- package/vendor/presence-constants.js +5 -0
- package/vendor/presence.js +5 -0
- package/vendor/record-vault.js +5 -0
- package/vendor/session-store.js +5 -0
- package/vendor/topup.js +5 -0
- package/vendor/touch.js +5 -0
- package/vendor/update.js +5 -0
- 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
|
/**
|
package/vendor/authorship-key.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/auto-provision.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/brain-catalog.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/byok-catalog.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/credentials.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
|
/**
|
package/vendor/env-file.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/fingerprint.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/foreign-memory.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/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
|
+
};
|
package/vendor/platform.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/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
|
/**
|
package/vendor/presence.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/record-vault.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/session-store.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/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
|
*
|
package/vendor/user-models.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
|
/**
|