aegis-desktop 0.7.7 → 0.7.8

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/renderer/usage.js CHANGED
@@ -159,23 +159,54 @@ const RATES = {
159
159
  deepseek: { input: 0.14, output: 0.28, cacheRead: 0.014, cacheWrite: 0.14 },
160
160
  };
161
161
 
162
+ /**
163
+ * The rate rows a model id may name as a whole word rather than as a prefix.
164
+ * Anthropic spells its family *inside* the id — "claude-opus-4" matches no
165
+ * prefix — so prefix matching alone cannot see it.
166
+ */
167
+ const RATE_FAMILIES = Object.keys(RATES).filter((k) => k !== 'default');
168
+
162
169
  /**
163
170
  * The rate row for a model id, provider, or alias. Exact id wins, then the
164
- * longest matching prefix, then the Sonnet-class default.
171
+ * longest matching prefix, then a family named inside the id, then the
172
+ * Sonnet-class default.
165
173
  */
166
174
  function ratesFor(model) {
167
- const id = String(model || '').toLowerCase();
168
- if (!id) return RATES.default;
169
- if (RATES[id]) return RATES[id];
170
- let best = null;
171
- let bestLen = 0;
172
- for (const [prefix, rates] of Object.entries(RATES)) {
173
- if (id.startsWith(prefix) && prefix.length > bestLen) {
174
- best = rates;
175
- bestLen = prefix.length;
175
+ const raw = String(model || '').toLowerCase();
176
+ if (!raw) return RATES.default;
177
+ // A byok model id carries its provider as a routing label:
178
+ // "anthropic:claude-opus-4", "deepseek:deepseek-v4-flash". The label is not
179
+ // a rate, and rating it as written fell through every prefix to
180
+ // RATES.default — so an Opus BYOK turn was priced at Sonnet's $3/$15 against
181
+ // the vendor's real $5/$25. 15x, on the one class whose figure can only ever
182
+ // be an estimate, because there the vendor bills the caller and AEGIS only
183
+ // relays. Try the whole id first, then the model behind the label.
184
+ const ids = [raw];
185
+ const tail = raw.slice(raw.lastIndexOf(':') + 1);
186
+ if (tail && tail !== raw) ids.push(tail);
187
+ for (const id of ids) {
188
+ if (RATES[id]) return RATES[id];
189
+ let best = null;
190
+ let bestLen = 0;
191
+ for (const [prefix, rates] of Object.entries(RATES)) {
192
+ if (id.startsWith(prefix) && prefix.length > bestLen) {
193
+ best = rates;
194
+ bestLen = prefix.length;
195
+ }
196
+ }
197
+ if (best) return best;
198
+ }
199
+ // Longest family-key hit wins, so an overlapping pair resolves the same way
200
+ // twice running rather than by object key order.
201
+ let family = null;
202
+ let familyLen = 0;
203
+ for (const key of RATE_FAMILIES) {
204
+ if (raw.includes(key) && key.length > familyLen) {
205
+ family = RATES[key];
206
+ familyLen = key.length;
176
207
  }
177
208
  }
178
- return best || RATES.default;
209
+ return family || RATES.default;
179
210
  }
180
211
 
181
212
  /**
package/vendor/aegis.js CHANGED
@@ -354,9 +354,27 @@ function createClient(opts = {}) {
354
354
  *
355
355
  * The caller's *AEGIS* key is sent alongside the provider key as X-AEGIS-Key.
356
356
  * Two credentials, two headers, on purpose: the provider key authenticates the
357
- * upstream call, the AEGIS key says whose bank pays the handling fee. With no
358
- * AEGIS key configured the header is omitted and the call stays anonymous —
359
- * allowed, just unbilled (see _byok_identify_user on the server).
357
+ * upstream call, the AEGIS key says whose bank pays the handling fee —
358
+ * `_byok_identify_user` on the server resolves the payer from it, and nothing
359
+ * else. With no AEGIS key the header is omitted and the call is anonymous;
360
+ * THIS function still sends it, because it is a transport and the route is
361
+ * unauthenticated by design (aegis1 tests/test_byok_fee.py pins the anonymous
362
+ * path as allowed) — refusing here would break every other caller of a
363
+ * published endpoint.
364
+ *
365
+ * Attribution is the whole game, because an unattributed turn is the one way
366
+ * a BYOK call escapes the fee: the relay serves it, `charge_byok` never runs
367
+ * (`app.py` charges only `if _byok_user_id:`), and the call is logged against
368
+ * user 0 as uncollected — a free ride. So the shipped HOSTS refuse a keyless
369
+ * BYOK send before the relay is reached: desktop/lib/local/engine.js raises
370
+ * 401, and the desktop app and the CLI share that engine (the CLI stages it
371
+ * as cli/vendor/desktop/lib/local/engine.js), so one gate covers both. Every
372
+ * turn is then attributable, and therefore billable: a funded balance is
373
+ * debited, an unfunded one is recorded as owed — `charge_byok` clamps the
374
+ * debit at zero and never refuses the call, so "cannot pay" and "must not be
375
+ * billed" stop being the same thing. `AEGIS_BYOK_REQUIRE_BALANCE` (off by
376
+ * default) remains the server's own, separate 402 gate and is not what makes
377
+ * the fee land.
360
378
  */
361
379
  async function byokChatCompletion({
362
380
  provider = 'openai',