aegis-desktop 1.0.10 → 1.0.13
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/auto.js +11 -7
- package/lib/fingerprint.js +76 -17
- package/lib/google-connect.js +171 -0
- package/lib/local/claude-pro.js +134 -11
- package/lib/local/engine.js +111 -26
- package/lib/local/prompt.js +67 -37
- package/lib/local/tools.js +49 -13
- package/lib/settings.js +65 -24
- package/main.js +270 -36
- package/package.json +5 -2
- package/preload.js +45 -12
- package/renderer/app.js +691 -22
- package/renderer/budget.js +18 -9
- package/renderer/companion.js +225 -0
- package/renderer/fingerprint.css +16 -0
- package/renderer/fingerprint.js +136 -2
- package/renderer/index.html +302 -121
- package/renderer/provider-toolbar.js +136 -0
- package/renderer/providers.js +261 -0
- package/renderer/style.css +547 -171
- package/vendor/aegis.js +169 -3
- package/vendor/credentials.js +9 -2
- package/vendor/drive-connect.js +87 -0
- package/vendor/drive-upload.js +176 -0
- package/vendor/google-signin.js +127 -0
- package/vendor/record-vault.js +431 -113
- package/vendor/update.js +366 -8
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
* provider-toolbar.js — the Providers & models card: one list, every provider
|
|
10
|
+
* and every model, each with the URL its turn is sent to.
|
|
11
|
+
*
|
|
12
|
+
* Paints data from providers.js and nothing else; it never fetches, so the
|
|
13
|
+
* list cannot disagree with the engine's own catalog. Two rules the markup
|
|
14
|
+
* obeys:
|
|
15
|
+
*
|
|
16
|
+
* - A row is a <button> (not a div with a click handler) when it is
|
|
17
|
+
* selectable, so it is reachable by keyboard and announces itself. A
|
|
18
|
+
* non-selectable row (a class's state: "no key", "no daemon") is a plain
|
|
19
|
+
* <li> — making it a button would promise a click that does nothing.
|
|
20
|
+
* - The URL line is text, never a link: these are addresses the app sends to,
|
|
21
|
+
* not things a user should open. A chrome-less window opening its own IPC
|
|
22
|
+
* base in a browser is noise at best.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
(function () {
|
|
26
|
+
if (typeof window === 'undefined') return;
|
|
27
|
+
const A = window.aegisProviders;
|
|
28
|
+
if (!A) return; // providers.js missing: leave the card on its placeholder
|
|
29
|
+
|
|
30
|
+
function el(tag, cls, text) {
|
|
31
|
+
const n = document.createElement(tag);
|
|
32
|
+
if (cls) n.className = cls;
|
|
33
|
+
if (text != null) n.textContent = text;
|
|
34
|
+
return n;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {object} opts
|
|
39
|
+
* @param {HTMLElement} opts.list container for the rows
|
|
40
|
+
* @param {HTMLElement} opts.count "N of M" line
|
|
41
|
+
* @param {HTMLInputElement} opts.search
|
|
42
|
+
* @param {HTMLElement} opts.hint
|
|
43
|
+
* @param {(sel:{class:string,model:string})=>void} opts.onSelect
|
|
44
|
+
*/
|
|
45
|
+
function mount(opts) {
|
|
46
|
+
const o = opts || {};
|
|
47
|
+
let entries = [];
|
|
48
|
+
|
|
49
|
+
function paintRow(e) {
|
|
50
|
+
const li = el('li', 'pt-row');
|
|
51
|
+
if (e.selectable) {
|
|
52
|
+
const btn = el('button', 'pt-pick');
|
|
53
|
+
btn.type = 'button';
|
|
54
|
+
btn.appendChild(el('span', 'pt-model', e.modelLabel || e.model));
|
|
55
|
+
if (e.model && e.modelLabel && e.modelLabel !== e.model) {
|
|
56
|
+
btn.appendChild(el('span', 'pt-id', e.model));
|
|
57
|
+
}
|
|
58
|
+
btn.appendChild(el('span', 'pt-target', e.url ? `${e.method} ${e.url}` : 'no network route — runs as a local process'));
|
|
59
|
+
btn.addEventListener('click', () => {
|
|
60
|
+
if (o.onSelect) o.onSelect(A.selectionFor(e));
|
|
61
|
+
});
|
|
62
|
+
li.appendChild(btn);
|
|
63
|
+
} else {
|
|
64
|
+
li.appendChild(el('span', 'pt-model pt-muted', e.modelLabel));
|
|
65
|
+
li.appendChild(el('span', 'pt-target', e.url ? `${e.method} ${e.url}` : '—'));
|
|
66
|
+
}
|
|
67
|
+
if (e.billing) li.appendChild(el('span', 'pt-billing', e.billing));
|
|
68
|
+
if (e.blocked) li.appendChild(el('span', 'pt-blocked', e.blocked));
|
|
69
|
+
return li;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Group rows under their class heading, preserving class order. */
|
|
73
|
+
function paint(query) {
|
|
74
|
+
const shown = A.filterEntries(entries, query);
|
|
75
|
+
if (o.count) {
|
|
76
|
+
o.count.textContent = entries.length
|
|
77
|
+
? `${shown.length} of ${entries.length} provider route${entries.length === 1 ? '' : 's'}`
|
|
78
|
+
: 'no providers reported';
|
|
79
|
+
}
|
|
80
|
+
if (o.list) {
|
|
81
|
+
o.list.innerHTML = '';
|
|
82
|
+
let group = null;
|
|
83
|
+
let ul = null;
|
|
84
|
+
for (const e of shown) {
|
|
85
|
+
if (e.group !== group) {
|
|
86
|
+
group = e.group;
|
|
87
|
+
const section = el('li', 'pt-group');
|
|
88
|
+
section.appendChild(el('h3', 'pt-group-title', group));
|
|
89
|
+
ul = el('ul', 'pt-rows');
|
|
90
|
+
section.appendChild(ul);
|
|
91
|
+
o.list.appendChild(section);
|
|
92
|
+
}
|
|
93
|
+
ul.appendChild(paintRow(e));
|
|
94
|
+
}
|
|
95
|
+
if (!shown.length) {
|
|
96
|
+
o.list.appendChild(el('li', 'pt-empty', entries.length
|
|
97
|
+
? 'No provider or model matches that filter.'
|
|
98
|
+
: 'No provider paths yet — press Refresh once a key is saved.'));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (o.hint && query && shown.length) {
|
|
102
|
+
// Say where each hit goes, in one line, so the tallest question the
|
|
103
|
+
// list answers ("does this leave my machine?") is answerable at a
|
|
104
|
+
// glance rather than row by row.
|
|
105
|
+
const remote = shown.filter((e) => e.url && !/localhost|127\.0\.0\.1/.test(e.url)).length;
|
|
106
|
+
const local = shown.filter((e) => e.url && /localhost|127\.0\.0\.1/.test(e.url)).length;
|
|
107
|
+
const off = shown.filter((e) => !e.url).length;
|
|
108
|
+
o.hint.textContent = [
|
|
109
|
+
remote ? `${remote} over the network` : '',
|
|
110
|
+
local ? `${local} on this machine` : '',
|
|
111
|
+
off ? `${off} with no route` : '',
|
|
112
|
+
].filter(Boolean).join(' · ');
|
|
113
|
+
} else if (o.hint && !query) {
|
|
114
|
+
o.hint.textContent = entries.length
|
|
115
|
+
? 'Every provider this build can address, and the URL a turn to it uses.'
|
|
116
|
+
: '';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (o.search) {
|
|
121
|
+
o.search.addEventListener('input', () => paint(o.search.value));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
/** Replace the list. `rows` is the same shape buildProviderPaths takes. */
|
|
126
|
+
set(rows) {
|
|
127
|
+
entries = A.buildProviderPaths(rows);
|
|
128
|
+
paint(o.search ? o.search.value : '');
|
|
129
|
+
return entries;
|
|
130
|
+
},
|
|
131
|
+
entries: () => entries.slice(),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
window.aegisProviderToolbar = { mount };
|
|
136
|
+
})();
|
|
@@ -0,0 +1,261 @@
|
|
|
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
|
+
* providers.js — one flat list of every provider and every model this build can
|
|
10
|
+
* address, each with the URL a turn to it actually goes to.
|
|
11
|
+
*
|
|
12
|
+
* WHY THIS EXISTS. The Model card answers "what is selected"; it never answers
|
|
13
|
+
* "what can I select, and where does that go". Those are different questions,
|
|
14
|
+
* and the second one had no surface at all: a user could see four provider
|
|
15
|
+
* classes and one model dropdown, and still have no way to learn that
|
|
16
|
+
* `byok:anthropic:claude-sonnet-5` is sent to the relay, that `local:llama3.2`
|
|
17
|
+
* goes to localhost, or that `pro` is not a URL at all but a child process.
|
|
18
|
+
*
|
|
19
|
+
* TWO DELIBERATE OMISSIONS, and they are the point of the file's shape:
|
|
20
|
+
*
|
|
21
|
+
* - No φ(α) seal row. The authorship panel is not a provider; it publishes a
|
|
22
|
+
* record of a repo, and putting it in a provider table would invite the
|
|
23
|
+
* reader to think a turn can be addressed to it.
|
|
24
|
+
* - No autonomous-box row. The fan-out is a *property of a turn* on the
|
|
25
|
+
* Aegis Cloud class (engine.js AUTONOMOUS_CLASS), not an endpoint — it
|
|
26
|
+
* reaches no route the pooled row does not already name. It already has its
|
|
27
|
+
* own toggle in the Model card, next to the class that has it at all.
|
|
28
|
+
*
|
|
29
|
+
* Both omissions are asserted in test/renderer-providers.test.mjs so a later
|
|
30
|
+
* "why is this missing" pass cannot quietly re-add them.
|
|
31
|
+
*
|
|
32
|
+
* The URLS ARE READ OFF THE TRANSPORT, not guessed:
|
|
33
|
+
* - pooled : vendor/aegis.js -> '/api/v1/chat/completions', under
|
|
34
|
+
* DEFAULT_API_BASE ('https://aegiscloud.org')
|
|
35
|
+
* - byok : vendor/aegis.js -> '/api/v1/byok/chat/completions', same base,
|
|
36
|
+
* with a per-request X-Provider-Key
|
|
37
|
+
* - local : lib/local/local.js -> `${base}/v1/chat/completions`, base
|
|
38
|
+
* DEFAULT_BASE ('http://localhost:11434')
|
|
39
|
+
* - pro : no route — a `claude` child process (claude-pro.js PRO_MODELS
|
|
40
|
+
* are CLI aliases, not wire ids), so `url` is null and the row
|
|
41
|
+
* says so instead of printing a plausible-looking URL.
|
|
42
|
+
*
|
|
43
|
+
* Pure: no DOM, no Electron, no fetch. Takes the engine's own listClasses /
|
|
44
|
+
* listModels payloads and returns data; the toolbar paints it.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/** Paths, as the transport spells them. Not templates — literal strings. */
|
|
48
|
+
const POOLED_PATH = '/api/v1/chat/completions';
|
|
49
|
+
const BYOK_PATH = '/api/v1/byok/chat/completions';
|
|
50
|
+
const LOCAL_PATH = '/v1/chat/completions';
|
|
51
|
+
const DEFAULT_CLOUD_BASE = 'https://aegiscloud.org';
|
|
52
|
+
const DEFAULT_LOCAL_BASE = 'http://localhost:11434';
|
|
53
|
+
|
|
54
|
+
/** Join a base and a path without doubling or dropping the separator. */
|
|
55
|
+
function joinUrl(base, suffix) {
|
|
56
|
+
const b = typeof base === 'string' ? base.trim() : '';
|
|
57
|
+
const p = typeof suffix === 'string' ? suffix.trim() : '';
|
|
58
|
+
if (!b) return '';
|
|
59
|
+
// An absolute URL in the path slot wins: a deployment may publish a full
|
|
60
|
+
// base, and prefixing that with another base produces a URL that resolves to
|
|
61
|
+
// nothing while looking correct on screen.
|
|
62
|
+
if (/^https?:\/\//i.test(p)) return p;
|
|
63
|
+
if (!p) return b.replace(/\/+$/, '');
|
|
64
|
+
return `${b.replace(/\/+$/, '')}/${p.replace(/^\/+/, '')}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The billing line for a row. Read from what the engine already reported rather
|
|
69
|
+
* than assumed: a byok row's fee note only exists when the server publishes one
|
|
70
|
+
* (`fee.require_balance`), and saying "funded balance required" on a
|
|
71
|
+
* deployment that does not gate that way is the same lie pointing the other
|
|
72
|
+
* way (see engine.listModels' byok branch).
|
|
73
|
+
*/
|
|
74
|
+
function billingFor(cls, classRow, modelRow, listPayload) {
|
|
75
|
+
if (cls === 'pro') {
|
|
76
|
+
return 'Claude Code subscription — no AEGIS tokens spent';
|
|
77
|
+
}
|
|
78
|
+
if (cls === 'local') {
|
|
79
|
+
return 'free — runs on this machine';
|
|
80
|
+
}
|
|
81
|
+
if (cls === 'byok') {
|
|
82
|
+
const fee = (listPayload && listPayload.fee) || null;
|
|
83
|
+
if (fee && fee.require_balance) {
|
|
84
|
+
return 'your provider key pays the vendor; an AEGIS key and a funded balance pay the handling fee';
|
|
85
|
+
}
|
|
86
|
+
return 'your provider key pays the vendor; an AEGIS key attributes the handling fee';
|
|
87
|
+
}
|
|
88
|
+
// aegis (pooled): the account pays. No claim is made about which plan covers
|
|
89
|
+
// it — `configured` already says whether a key is present, and the Status
|
|
90
|
+
// card is where plan and balance actually live.
|
|
91
|
+
return 'billed to your AEGIS account';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Why a row cannot be used, or null when it can. Deliberately one short reason
|
|
96
|
+
* per row: the Model card's hint already carries the full remedy for the
|
|
97
|
+
* selected class, and duplicating that copy per row makes the list unreadable.
|
|
98
|
+
*/
|
|
99
|
+
function blockedReason(cls, classRow, modelRow, listPayload) {
|
|
100
|
+
if (cls === 'aegis' && listPayload && listPayload.needsKey) {
|
|
101
|
+
return 'no AEGIS API key on this machine';
|
|
102
|
+
}
|
|
103
|
+
if (cls === 'local' && listPayload && listPayload.needsDaemon) {
|
|
104
|
+
return 'no model server listening';
|
|
105
|
+
}
|
|
106
|
+
if (cls === 'local' && listPayload && listPayload.listed === false) {
|
|
107
|
+
return 'server answered but lists no models';
|
|
108
|
+
}
|
|
109
|
+
if (cls === 'byok' && modelRow && modelRow.configured === false) {
|
|
110
|
+
// The precise reason when the env file REFUSED a value it was given, rather
|
|
111
|
+
// than never having been given one — that distinction is the difference
|
|
112
|
+
// between "add a key" and "the key you added was rejected".
|
|
113
|
+
return modelRow.keyRejected
|
|
114
|
+
? `provider key rejected: ${modelRow.keyRejected}`
|
|
115
|
+
: 'no key saved for this provider';
|
|
116
|
+
}
|
|
117
|
+
if (cls === 'pro' && classRow && classRow.configured === false) {
|
|
118
|
+
return 'not signed in to Claude Code';
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* One row per addressable model, in class order, plus a placeholder row for a
|
|
125
|
+
* class that offers no model list but is still a target (a pooled turn with no
|
|
126
|
+
* model pinned, a local daemon with nothing pulled yet).
|
|
127
|
+
*
|
|
128
|
+
* `rows` is `[{ class, classLabel, classConfigured, list }]`, where `list` is
|
|
129
|
+
* whatever `models.listModels(cls)` resolved to — including its error/state
|
|
130
|
+
* shape, so a class that failed to list still gets a row that says why.
|
|
131
|
+
*/
|
|
132
|
+
function buildProviderPaths(rows) {
|
|
133
|
+
const out = [];
|
|
134
|
+
const input = Array.isArray(rows) ? rows : [];
|
|
135
|
+
for (const r of input) {
|
|
136
|
+
if (!r || typeof r.class !== 'string') continue;
|
|
137
|
+
const cls = r.class;
|
|
138
|
+
const classRow = { configured: r.classConfigured !== false };
|
|
139
|
+
const list = r.list && typeof r.list === 'object' ? r.list : null;
|
|
140
|
+
const models = Array.isArray(list && list.models) ? list.models : [];
|
|
141
|
+
const cloudBase = (list && (list.apiBase || list.baseURL)) || DEFAULT_CLOUD_BASE;
|
|
142
|
+
const localBase = (list && list.baseURL) || DEFAULT_LOCAL_BASE;
|
|
143
|
+
|
|
144
|
+
if (!models.length) {
|
|
145
|
+
// No model row still means a real destination for two classes: the pooled
|
|
146
|
+
// class runs the server's default when nothing is pinned, and a local
|
|
147
|
+
// daemon with nothing pulled still answers on the same route once a tag
|
|
148
|
+
// is typed (engine.listModels' local branch). For byok and pro an empty
|
|
149
|
+
// list is genuinely "nothing to address", so only the reason is shown.
|
|
150
|
+
const placeholderOk = cls === 'aegis' || cls === 'local';
|
|
151
|
+
out.push({
|
|
152
|
+
class: cls,
|
|
153
|
+
classLabel: r.classLabel || cls,
|
|
154
|
+
model: '',
|
|
155
|
+
modelLabel:
|
|
156
|
+
cls === 'aegis'
|
|
157
|
+
? 'server default (auto)'
|
|
158
|
+
: cls === 'local'
|
|
159
|
+
? 'type a model tag in the Model card'
|
|
160
|
+
: '—',
|
|
161
|
+
provider: '',
|
|
162
|
+
uniqueModel: cls,
|
|
163
|
+
group: r.classLabel || cls,
|
|
164
|
+
url: placeholderOk
|
|
165
|
+
? joinUrl(cls === 'local' ? localBase : cloudBase, cls === 'local' ? LOCAL_PATH : POOLED_PATH)
|
|
166
|
+
: null,
|
|
167
|
+
method: placeholderOk ? 'POST' : null,
|
|
168
|
+
billing: billingFor(cls, classRow, null, list),
|
|
169
|
+
blocked: blockedReason(cls, classRow, null, list),
|
|
170
|
+
// A row with no model id is not a selectable target: it is the class's
|
|
171
|
+
// own state, printed where its models would have gone.
|
|
172
|
+
selectable: false,
|
|
173
|
+
});
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const m of models) {
|
|
178
|
+
if (!m || !m.id) continue;
|
|
179
|
+
const provider = typeof m.provider === 'string' && m.provider ? m.provider : cls;
|
|
180
|
+
let url = null;
|
|
181
|
+
let method = null;
|
|
182
|
+
if (cls === 'aegis') {
|
|
183
|
+
url = joinUrl(cloudBase, POOLED_PATH);
|
|
184
|
+
method = 'POST';
|
|
185
|
+
} else if (cls === 'byok') {
|
|
186
|
+
// The relay's route, INCLUDING for provider `custom`. That row looks
|
|
187
|
+
// like an exception and is not: this app never dials the user's own
|
|
188
|
+
// endpoint. It posts to the relay with `base_url` in the body and the
|
|
189
|
+
// server makes the outbound call (client/aegis.js byokChatCompletion,
|
|
190
|
+
// aegis1 `_byok_custom_base_url`). Printing the endpoint here would
|
|
191
|
+
// describe a connection no part of this client opens.
|
|
192
|
+
url = joinUrl(cloudBase, BYOK_PATH);
|
|
193
|
+
method = 'POST';
|
|
194
|
+
} else if (cls === 'local') {
|
|
195
|
+
url = joinUrl(localBase, LOCAL_PATH);
|
|
196
|
+
method = 'POST';
|
|
197
|
+
}
|
|
198
|
+
// cls === 'pro' leaves both null: it is a child process, not a route.
|
|
199
|
+
out.push({
|
|
200
|
+
class: cls,
|
|
201
|
+
classLabel: r.classLabel || cls,
|
|
202
|
+
model: m.id,
|
|
203
|
+
modelLabel: m.label || m.id,
|
|
204
|
+
provider,
|
|
205
|
+
uniqueModel: `${cls}:${m.id}`,
|
|
206
|
+
group: r.classLabel || cls,
|
|
207
|
+
url,
|
|
208
|
+
method,
|
|
209
|
+
billing: billingFor(cls, classRow, m, list),
|
|
210
|
+
blocked: blockedReason(cls, classRow, m, list),
|
|
211
|
+
selectable: true,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return out;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Case-insensitive substring match across everything a user might type: the
|
|
220
|
+
* model id, its label, the provider, the class, and the URL's host+path.
|
|
221
|
+
* Multi-term: every whitespace-separated term must match somewhere, so
|
|
222
|
+
* "anthropic sonnet" narrows instead of returning nothing.
|
|
223
|
+
*/
|
|
224
|
+
function filterEntries(entries, query) {
|
|
225
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
226
|
+
const q = String(query == null ? '' : query).trim().toLowerCase();
|
|
227
|
+
if (!q) return list.slice();
|
|
228
|
+
const terms = q.split(/\s+/).filter(Boolean);
|
|
229
|
+
return list.filter((e) => {
|
|
230
|
+
const hay = [e.model, e.modelLabel, e.provider, e.class, e.classLabel, e.url || '']
|
|
231
|
+
.join('\n')
|
|
232
|
+
.toLowerCase();
|
|
233
|
+
return terms.every((t) => hay.includes(t));
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The selection a row would produce, in the shape the Model card's own controls
|
|
239
|
+
* take: `{ class, model }` for the two <select>s. `model` is '' for a class
|
|
240
|
+
* whose picker offers the "auto" entry, which is exactly what selecting that
|
|
241
|
+
* option means today.
|
|
242
|
+
*/
|
|
243
|
+
function selectionFor(entry) {
|
|
244
|
+
if (!entry) return null;
|
|
245
|
+
return {
|
|
246
|
+
class: entry.class,
|
|
247
|
+
model: entry.class === 'aegis' && !entry.model ? '' : entry.model,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
252
|
+
module.exports = {
|
|
253
|
+
buildProviderPaths,
|
|
254
|
+
filterEntries,
|
|
255
|
+
selectionFor,
|
|
256
|
+
joinUrl,
|
|
257
|
+
POOLED_PATH,
|
|
258
|
+
BYOK_PATH,
|
|
259
|
+
LOCAL_PATH,
|
|
260
|
+
};
|
|
261
|
+
}
|