brainjar 0.3.1228 → 0.3.1232
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/brainjar.js +244 -129
- package/package.json +1 -1
package/brainjar.js
CHANGED
|
@@ -107,7 +107,7 @@ var CLI_VERSION, CACHE, DAY;
|
|
|
107
107
|
var init_version_check = __esm({
|
|
108
108
|
"packages/cli/src/version-check.ts"() {
|
|
109
109
|
"use strict";
|
|
110
|
-
CLI_VERSION = "0.3.
|
|
110
|
+
CLI_VERSION = "0.3.1232";
|
|
111
111
|
CACHE = path.join(os.homedir(), ".brainjar", "version-check.json");
|
|
112
112
|
DAY = 864e5;
|
|
113
113
|
}
|
|
@@ -6496,8 +6496,12 @@ var init_settings = __esm({
|
|
|
6496
6496
|
// "on" | "off": monthly contingent per workspace (default on in SaaS)
|
|
6497
6497
|
"ai_free_monthly_usd",
|
|
6498
6498
|
// Free workspace, USD per month
|
|
6499
|
+
"ai_pro_monthly_usd",
|
|
6500
|
+
// Pro workspace, USD per billing period (BJ-132)
|
|
6499
6501
|
"ai_team_seat_monthly_usd",
|
|
6500
|
-
//
|
|
6502
|
+
// Company, USD per seat and billing period, pooled (BJ-132)
|
|
6503
|
+
"credits_per_usd",
|
|
6504
|
+
// what SaaS customers see: credits per USD of inference (BJ-132)
|
|
6501
6505
|
"ai_operator_exempt",
|
|
6502
6506
|
// "true" | "false": operator accounts are never capped
|
|
6503
6507
|
"ai_daily_budget_usd",
|
|
@@ -6524,7 +6528,9 @@ var init_settings = __esm({
|
|
|
6524
6528
|
"stripe_price_company",
|
|
6525
6529
|
"stripe_price_license_pro",
|
|
6526
6530
|
"stripe_price_license_company",
|
|
6527
|
-
"billing_consent_text"
|
|
6531
|
+
"billing_consent_text",
|
|
6532
|
+
// Credit packs (BJ-132 step 3): one per line, "<Stripe price id> <credits>".
|
|
6533
|
+
"credit_packs"
|
|
6528
6534
|
];
|
|
6529
6535
|
PROVIDER_KEY_SETTINGS = {
|
|
6530
6536
|
anthropic: "anthropic_api_key",
|
|
@@ -6533,6 +6539,18 @@ var init_settings = __esm({
|
|
|
6533
6539
|
}
|
|
6534
6540
|
});
|
|
6535
6541
|
|
|
6542
|
+
// packages/db/src/health.ts
|
|
6543
|
+
var NO_WORKER_SECONDS, PROVIDER_FRESH_MS;
|
|
6544
|
+
var init_health = __esm({
|
|
6545
|
+
"packages/db/src/health.ts"() {
|
|
6546
|
+
"use strict";
|
|
6547
|
+
init_pool();
|
|
6548
|
+
init_settings();
|
|
6549
|
+
NO_WORKER_SECONDS = 5 * 60;
|
|
6550
|
+
PROVIDER_FRESH_MS = 10 * 6e4;
|
|
6551
|
+
}
|
|
6552
|
+
});
|
|
6553
|
+
|
|
6536
6554
|
// packages/db/src/license-key.ts
|
|
6537
6555
|
var LICENSE_PUBLIC_KEY;
|
|
6538
6556
|
var init_license_key = __esm({
|
|
@@ -6547,15 +6565,18 @@ MCowBQYDK2VwAyEAKOUWx/Qgav+dKg78fIlckGby22CRKeuMWR5tkT1rYuw=
|
|
|
6547
6565
|
|
|
6548
6566
|
// packages/db/src/plan.ts
|
|
6549
6567
|
import { createPublicKey, sign, verify } from "node:crypto";
|
|
6550
|
-
var trustedKeys;
|
|
6568
|
+
var trustedKeys, RENEW_AFTER_MS, PAID_STATUSES;
|
|
6551
6569
|
var init_plan = __esm({
|
|
6552
6570
|
"packages/db/src/plan.ts"() {
|
|
6553
6571
|
"use strict";
|
|
6554
6572
|
init_src();
|
|
6555
6573
|
init_pool();
|
|
6556
6574
|
init_settings();
|
|
6575
|
+
init_health();
|
|
6557
6576
|
init_license_key();
|
|
6558
6577
|
trustedKeys = [createPublicKey(LICENSE_PUBLIC_KEY)];
|
|
6578
|
+
RENEW_AFTER_MS = 20 * 36e5;
|
|
6579
|
+
PAID_STATUSES = ["active", "trialing", "past_due"];
|
|
6559
6580
|
}
|
|
6560
6581
|
});
|
|
6561
6582
|
|
|
@@ -8176,22 +8197,7 @@ var init_tickets = __esm({
|
|
|
8176
8197
|
}
|
|
8177
8198
|
});
|
|
8178
8199
|
|
|
8179
|
-
// packages/db/src/health.ts
|
|
8180
|
-
var NO_WORKER_SECONDS, PROVIDER_FRESH_MS;
|
|
8181
|
-
var init_health = __esm({
|
|
8182
|
-
"packages/db/src/health.ts"() {
|
|
8183
|
-
"use strict";
|
|
8184
|
-
init_pool();
|
|
8185
|
-
init_settings();
|
|
8186
|
-
NO_WORKER_SECONDS = 5 * 60;
|
|
8187
|
-
PROVIDER_FRESH_MS = 10 * 6e4;
|
|
8188
|
-
}
|
|
8189
|
-
});
|
|
8190
|
-
|
|
8191
8200
|
// packages/db/src/spend.ts
|
|
8192
|
-
function monthKey(ownerId, at = /* @__PURE__ */ new Date()) {
|
|
8193
|
-
return `ws:${ownerId}:${at.toISOString().slice(0, 7)}`;
|
|
8194
|
-
}
|
|
8195
8201
|
function dayKey(at = /* @__PURE__ */ new Date()) {
|
|
8196
8202
|
return `day:${at.toISOString().slice(0, 10)}`;
|
|
8197
8203
|
}
|
|
@@ -8203,19 +8209,59 @@ function dayRenewal(at = /* @__PURE__ */ new Date()) {
|
|
|
8203
8209
|
Date.UTC(at.getUTCFullYear(), at.getUTCMonth(), at.getUTCDate() + 1)
|
|
8204
8210
|
);
|
|
8205
8211
|
}
|
|
8206
|
-
async function addSpend(brainId, usd, at = /* @__PURE__ */ new Date(), pool2 = getPool()) {
|
|
8212
|
+
async function addSpend(brainId, usd, at = /* @__PURE__ */ new Date(), pool2 = getPool(), credits) {
|
|
8207
8213
|
if (!(usd > 0)) return;
|
|
8208
|
-
await pool2.query(
|
|
8214
|
+
const r = await pool2.query(
|
|
8209
8215
|
`INSERT INTO llm_spend (key, usd)
|
|
8210
8216
|
SELECT k, $2 FROM (
|
|
8211
8217
|
SELECT $3::text AS k
|
|
8212
8218
|
UNION ALL
|
|
8213
|
-
SELECT 'ws:' || b.owner_id || ':' || $4 FROM brain b
|
|
8214
|
-
|
|
8219
|
+
(SELECT 'ws:' || b.owner_id || ':' || $4 FROM brain b
|
|
8220
|
+
WHERE b.id = $1 AND b.owner_id IS NOT NULL
|
|
8221
|
+
UNION
|
|
8222
|
+
SELECT credit_key(b.owner_id, $5) FROM brain b
|
|
8223
|
+
WHERE b.id = $1 AND b.owner_id IS NOT NULL)
|
|
8215
8224
|
) keys
|
|
8216
|
-
ON CONFLICT (key) DO UPDATE SET usd = llm_spend.usd + EXCLUDED.usd, updated_at = now()
|
|
8217
|
-
|
|
8225
|
+
ON CONFLICT (key) DO UPDATE SET usd = llm_spend.usd + EXCLUDED.usd, updated_at = now()
|
|
8226
|
+
RETURNING key, usd`,
|
|
8227
|
+
[brainId, usd, dayKey(at), at.toISOString().slice(0, 7), at]
|
|
8218
8228
|
);
|
|
8229
|
+
if (!credits) return;
|
|
8230
|
+
const row = r.rows.find((x) => x.key.startsWith("wsp:")) ?? r.rows.find((x) => x.key.startsWith("ws:"));
|
|
8231
|
+
if (!row) return;
|
|
8232
|
+
const over = Math.min(
|
|
8233
|
+
usd,
|
|
8234
|
+
Math.max(0, Number(row.usd) - credits.includedUsd)
|
|
8235
|
+
);
|
|
8236
|
+
if (over > 0)
|
|
8237
|
+
await pool2.query(
|
|
8238
|
+
`UPDATE credit_balance SET credits = greatest(0, credits - $2), updated_at = now()
|
|
8239
|
+
WHERE user_id = $1`,
|
|
8240
|
+
[row.key.split(":")[1], over * credits.creditsPerUsd]
|
|
8241
|
+
);
|
|
8242
|
+
}
|
|
8243
|
+
async function creditPeriod(ownerId, at = /* @__PURE__ */ new Date(), pool2 = getPool()) {
|
|
8244
|
+
const r = await pool2.query(
|
|
8245
|
+
`SELECT credit_key($1, $2) AS key, ap.period_start, ap.period_end
|
|
8246
|
+
FROM (SELECT 1) one
|
|
8247
|
+
LEFT JOIN account_plan ap ON ap.user_id = $1
|
|
8248
|
+
AND ap.status IN ('active', 'trialing', 'past_due')
|
|
8249
|
+
AND ap.period_start <= $2 AND ap.period_end > $2`,
|
|
8250
|
+
[ownerId, at]
|
|
8251
|
+
);
|
|
8252
|
+
const row = r.rows[0];
|
|
8253
|
+
return row.key.startsWith("wsp:") && row.period_start && row.period_end ? { key: row.key, start: row.period_start, end: row.period_end } : {
|
|
8254
|
+
key: row.key,
|
|
8255
|
+
start: new Date(Date.UTC(at.getUTCFullYear(), at.getUTCMonth(), 1)),
|
|
8256
|
+
end: monthRenewal(at)
|
|
8257
|
+
};
|
|
8258
|
+
}
|
|
8259
|
+
async function purchasedCredits(ownerId, pool2 = getPool()) {
|
|
8260
|
+
const r = await pool2.query(
|
|
8261
|
+
"SELECT credits FROM credit_balance WHERE user_id = $1",
|
|
8262
|
+
[ownerId]
|
|
8263
|
+
);
|
|
8264
|
+
return Number(r.rows[0]?.credits ?? 0);
|
|
8219
8265
|
}
|
|
8220
8266
|
async function spendUsd(key, pool2 = getPool()) {
|
|
8221
8267
|
const r = await pool2.query(
|
|
@@ -8242,6 +8288,19 @@ var init_spend = __esm({
|
|
|
8242
8288
|
});
|
|
8243
8289
|
|
|
8244
8290
|
// packages/db/src/billing.ts
|
|
8291
|
+
async function accountPlanRow(userId, pool2 = getPool()) {
|
|
8292
|
+
const r = await pool2.query(
|
|
8293
|
+
`SELECT plan, seats, status, period_end, cancel_at_period_end, ended_at
|
|
8294
|
+
FROM account_plan WHERE user_id = $1`,
|
|
8295
|
+
[userId]
|
|
8296
|
+
);
|
|
8297
|
+
return r.rows[0] ?? null;
|
|
8298
|
+
}
|
|
8299
|
+
async function paidAccountPlan(userId, pool2 = getPool()) {
|
|
8300
|
+
const row = await accountPlanRow(userId, pool2);
|
|
8301
|
+
return row && isPaid(row.status) ? { plan: row.plan, seats: row.seats } : null;
|
|
8302
|
+
}
|
|
8303
|
+
var isPaid;
|
|
8245
8304
|
var init_billing = __esm({
|
|
8246
8305
|
"packages/db/src/billing.ts"() {
|
|
8247
8306
|
"use strict";
|
|
@@ -8249,6 +8308,7 @@ var init_billing = __esm({
|
|
|
8249
8308
|
init_pool();
|
|
8250
8309
|
init_settings();
|
|
8251
8310
|
init_plan();
|
|
8311
|
+
isPaid = (status) => PAID_STATUSES.includes(status);
|
|
8252
8312
|
}
|
|
8253
8313
|
});
|
|
8254
8314
|
|
|
@@ -8315,7 +8375,7 @@ var init_ruleset = __esm({
|
|
|
8315
8375
|
});
|
|
8316
8376
|
|
|
8317
8377
|
// packages/db/src/usage.ts
|
|
8318
|
-
async function recordLlmUsage(u) {
|
|
8378
|
+
async function recordLlmUsage(u, credits) {
|
|
8319
8379
|
await getPool().query(
|
|
8320
8380
|
`INSERT INTO llm_usage (brain_id, workload, model, triggered_by, input_tokens, cached_input_tokens, output_tokens)
|
|
8321
8381
|
VALUES ($1,$2,$3,$4,$5,$6,$7)`,
|
|
@@ -8335,7 +8395,10 @@ async function recordLlmUsage(u) {
|
|
|
8335
8395
|
input: u.inputTokens,
|
|
8336
8396
|
cachedInput: u.cachedInputTokens,
|
|
8337
8397
|
output: u.outputTokens
|
|
8338
|
-
})
|
|
8398
|
+
}),
|
|
8399
|
+
void 0,
|
|
8400
|
+
void 0,
|
|
8401
|
+
await credits?.().catch(() => null)
|
|
8339
8402
|
);
|
|
8340
8403
|
}
|
|
8341
8404
|
var init_usage = __esm({
|
|
@@ -13346,6 +13409,145 @@ var init_provider_rate2 = __esm({
|
|
|
13346
13409
|
}
|
|
13347
13410
|
});
|
|
13348
13411
|
|
|
13412
|
+
// packages/ingest/src/allowance.ts
|
|
13413
|
+
async function aiBudgetSettings() {
|
|
13414
|
+
const env = loadEnv();
|
|
13415
|
+
const stored = await getAppSettings().catch(
|
|
13416
|
+
() => ({})
|
|
13417
|
+
);
|
|
13418
|
+
const pick = (key, envValue) => envValue?.trim() || stored[key];
|
|
13419
|
+
const contingent = pick("ai_contingent", env.BRAINJAR_AI_CONTINGENT);
|
|
13420
|
+
const exempt = pick("ai_operator_exempt", env.BRAINJAR_AI_OPERATOR_EXEMPT);
|
|
13421
|
+
const daily = amount(
|
|
13422
|
+
pick("ai_daily_budget_usd", env.BRAINJAR_AI_DAILY_BUDGET_USD)
|
|
13423
|
+
);
|
|
13424
|
+
return {
|
|
13425
|
+
contingent: contingent ? contingent === "on" : loadConfig().environment === "saas",
|
|
13426
|
+
freeUsd: amount(pick("ai_free_monthly_usd", env.BRAINJAR_FREE_MONTHLY_USD)) ?? DEFAULT_FREE_MONTHLY_USD,
|
|
13427
|
+
proUsd: amount(pick("ai_pro_monthly_usd", env.BRAINJAR_PRO_MONTHLY_USD)) ?? DEFAULT_PRO_MONTHLY_USD,
|
|
13428
|
+
teamSeatUsd: amount(
|
|
13429
|
+
pick("ai_team_seat_monthly_usd", env.BRAINJAR_TEAM_SEAT_MONTHLY_USD)
|
|
13430
|
+
) ?? DEFAULT_TEAM_SEAT_MONTHLY_USD,
|
|
13431
|
+
creditsPerUsd: positive(pick("credits_per_usd", env.BRAINJAR_CREDITS_PER_USD)) ?? DEFAULT_CREDITS_PER_USD,
|
|
13432
|
+
operatorExempt: exempt ? exempt !== "false" : true,
|
|
13433
|
+
dailyBudgetUsd: daily && daily > 0 ? daily : null,
|
|
13434
|
+
releasedDay: pick("ai_budget_released_day", void 0) ?? null
|
|
13435
|
+
};
|
|
13436
|
+
}
|
|
13437
|
+
async function aiAllowance(brainId, now = /* @__PURE__ */ new Date()) {
|
|
13438
|
+
const cfg = await aiBudgetSettings();
|
|
13439
|
+
if (cfg.dailyBudgetUsd !== null && cfg.releasedDay !== day(now)) {
|
|
13440
|
+
const used2 = await spendUsd(dayKey(now));
|
|
13441
|
+
if (used2 >= cfg.dailyBudgetUsd) {
|
|
13442
|
+
if (brainId && await swapAppSetting("ai_budget_alerted_day", day(now)))
|
|
13443
|
+
await enqueueJob(
|
|
13444
|
+
brainId,
|
|
13445
|
+
"budget-alert",
|
|
13446
|
+
{ usedUsd: used2, budgetUsd: cfg.dailyBudgetUsd, day: day(now) },
|
|
13447
|
+
{ priority: JOB_PRIORITY.interactive, dedupeKey: day(now) }
|
|
13448
|
+
).catch(() => {
|
|
13449
|
+
});
|
|
13450
|
+
return {
|
|
13451
|
+
reason: "daily_budget",
|
|
13452
|
+
usedUsd: used2,
|
|
13453
|
+
limitUsd: cfg.dailyBudgetUsd,
|
|
13454
|
+
renewsAt: dayRenewal(now)
|
|
13455
|
+
};
|
|
13456
|
+
}
|
|
13457
|
+
}
|
|
13458
|
+
if (!cfg.contingent || !brainId) return null;
|
|
13459
|
+
const ws = await brainWorkspace(brainId);
|
|
13460
|
+
if (!ws || ws.operator && cfg.operatorExempt) return null;
|
|
13461
|
+
const [limit, period] = await Promise.all([
|
|
13462
|
+
includedUsd(ws.ownerId, cfg),
|
|
13463
|
+
creditPeriod(ws.ownerId, now)
|
|
13464
|
+
]);
|
|
13465
|
+
const used = await spendUsd(period.key);
|
|
13466
|
+
if (used < limit) {
|
|
13467
|
+
if (used >= limit * WARN_SHARE)
|
|
13468
|
+
await warnOwnerOnce(ws.ownerId, brainId, period, now).catch(() => {
|
|
13469
|
+
});
|
|
13470
|
+
return null;
|
|
13471
|
+
}
|
|
13472
|
+
if (await purchasedCredits(ws.ownerId) > 0) return null;
|
|
13473
|
+
await tellOwnerOnce(ws.ownerId, brainId, period, now).catch(() => {
|
|
13474
|
+
});
|
|
13475
|
+
return {
|
|
13476
|
+
reason: "contingent",
|
|
13477
|
+
usedUsd: used,
|
|
13478
|
+
limitUsd: limit,
|
|
13479
|
+
renewsAt: period.end
|
|
13480
|
+
};
|
|
13481
|
+
}
|
|
13482
|
+
async function includedUsd(ownerId, cfg) {
|
|
13483
|
+
const paid = await paidAccountPlan(ownerId).catch(() => null);
|
|
13484
|
+
if (!paid) return cfg.freeUsd;
|
|
13485
|
+
return paid.plan === "pro" ? cfg.proUsd : paid.seats * cfg.teamSeatUsd;
|
|
13486
|
+
}
|
|
13487
|
+
async function creditTerms(brainId) {
|
|
13488
|
+
const cfg = await aiBudgetSettings();
|
|
13489
|
+
if (!cfg.contingent || !brainId) return null;
|
|
13490
|
+
const ws = await brainWorkspace(brainId);
|
|
13491
|
+
if (!ws || ws.operator && cfg.operatorExempt) return null;
|
|
13492
|
+
return {
|
|
13493
|
+
includedUsd: await includedUsd(ws.ownerId, cfg),
|
|
13494
|
+
creditsPerUsd: cfg.creditsPerUsd
|
|
13495
|
+
};
|
|
13496
|
+
}
|
|
13497
|
+
async function tellOwnerOnce(ownerId, brainId, period, _now) {
|
|
13498
|
+
if (await hasUserNotificationSince(ownerId, "ai_contingent", period.start))
|
|
13499
|
+
return;
|
|
13500
|
+
await createNotification({
|
|
13501
|
+
kind: "ai_contingent",
|
|
13502
|
+
brainId,
|
|
13503
|
+
userId: ownerId,
|
|
13504
|
+
title: "This month's AI credits are used up",
|
|
13505
|
+
body: `BrainJar keeps working without AI until ${renewalDate(period.end)}: capture uses the basic extraction, docs are built without AI text, verification and written summaries pause. Pro and Company can buy more credits under Account, Plan.`,
|
|
13506
|
+
href: creditsHref(brainId)
|
|
13507
|
+
});
|
|
13508
|
+
}
|
|
13509
|
+
async function warnOwnerOnce(ownerId, brainId, period, _now) {
|
|
13510
|
+
if (await hasUserNotificationSince(ownerId, "ai_credits_low", period.start))
|
|
13511
|
+
return;
|
|
13512
|
+
await createNotification({
|
|
13513
|
+
kind: "ai_credits_low",
|
|
13514
|
+
brainId,
|
|
13515
|
+
userId: ownerId,
|
|
13516
|
+
title: "80 percent of this month's AI credits are used",
|
|
13517
|
+
body: `The rest lasts until ${renewalDate(period.end)}. When it is used up, BrainJar keeps working without AI until then; Pro and Company can buy more credits under Account, Plan.`,
|
|
13518
|
+
href: creditsHref(brainId)
|
|
13519
|
+
});
|
|
13520
|
+
}
|
|
13521
|
+
var DEFAULT_FREE_MONTHLY_USD, DEFAULT_PRO_MONTHLY_USD, DEFAULT_TEAM_SEAT_MONTHLY_USD, DEFAULT_CREDITS_PER_USD, amount, positive, day, WARN_SHARE, renewalDate, creditsHref;
|
|
13522
|
+
var init_allowance = __esm({
|
|
13523
|
+
"packages/ingest/src/allowance.ts"() {
|
|
13524
|
+
"use strict";
|
|
13525
|
+
init_src2();
|
|
13526
|
+
init_src();
|
|
13527
|
+
DEFAULT_FREE_MONTHLY_USD = 1;
|
|
13528
|
+
DEFAULT_PRO_MONTHLY_USD = 10;
|
|
13529
|
+
DEFAULT_TEAM_SEAT_MONTHLY_USD = 5;
|
|
13530
|
+
DEFAULT_CREDITS_PER_USD = 100;
|
|
13531
|
+
amount = (v) => {
|
|
13532
|
+
if (v === void 0 || v.trim() === "") return null;
|
|
13533
|
+
const n2 = Number(v);
|
|
13534
|
+
return Number.isFinite(n2) && n2 >= 0 ? n2 : null;
|
|
13535
|
+
};
|
|
13536
|
+
positive = (v) => {
|
|
13537
|
+
const n2 = amount(v);
|
|
13538
|
+
return n2 !== null && n2 > 0 ? n2 : null;
|
|
13539
|
+
};
|
|
13540
|
+
day = (d) => d.toISOString().slice(0, 10);
|
|
13541
|
+
WARN_SHARE = 0.8;
|
|
13542
|
+
renewalDate = (d) => d.toLocaleDateString("en-GB", {
|
|
13543
|
+
day: "numeric",
|
|
13544
|
+
month: "long",
|
|
13545
|
+
timeZone: "UTC"
|
|
13546
|
+
});
|
|
13547
|
+
creditsHref = (brainId) => loadConfig().environment === "saas" ? "/account?tab=plan" : `/settings?brain=${encodeURIComponent(brainId)}&tab=usage`;
|
|
13548
|
+
}
|
|
13549
|
+
});
|
|
13550
|
+
|
|
13349
13551
|
// packages/ingest/src/llm.ts
|
|
13350
13552
|
function rationaleModelId(env = process.env) {
|
|
13351
13553
|
return env.BRAINJAR_RATIONALE_MODEL?.trim() || "claude-opus-5";
|
|
@@ -13355,16 +13557,20 @@ function logUsage(meta, model, usage) {
|
|
|
13355
13557
|
console.warn(
|
|
13356
13558
|
`brainjar: AI call without a Brain (workload ${meta?.workload ?? "other"}); it counts only toward the daily budget`
|
|
13357
13559
|
);
|
|
13358
|
-
void recordLlmUsage(
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
|
|
13560
|
+
void recordLlmUsage(
|
|
13561
|
+
{
|
|
13562
|
+
brainId: meta?.brainId ?? null,
|
|
13563
|
+
workload: meta?.workload ?? "other",
|
|
13564
|
+
triggeredBy: meta?.actor ?? null,
|
|
13565
|
+
model,
|
|
13566
|
+
// Cache writes bill near the input rate; booked as input to keep the ledger simple.
|
|
13567
|
+
inputTokens: (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0),
|
|
13568
|
+
cachedInputTokens: usage.cache_read_input_tokens ?? 0,
|
|
13569
|
+
outputTokens: usage.output_tokens ?? 0
|
|
13570
|
+
},
|
|
13571
|
+
// The workspace's credit terms decide what comes off purchased credits (BJ-132).
|
|
13572
|
+
() => creditTerms(meta?.brainId)
|
|
13573
|
+
).catch(() => {
|
|
13368
13574
|
});
|
|
13369
13575
|
}
|
|
13370
13576
|
function anthropicClient(apiKey, model, meta) {
|
|
@@ -13381,6 +13587,7 @@ var init_llm = __esm({
|
|
|
13381
13587
|
init_sdk();
|
|
13382
13588
|
init_src2();
|
|
13383
13589
|
init_provider_rate2();
|
|
13590
|
+
init_allowance();
|
|
13384
13591
|
cachedSystem = (system) => [
|
|
13385
13592
|
{
|
|
13386
13593
|
type: "text",
|
|
@@ -13619,98 +13826,6 @@ var init_openai = __esm({
|
|
|
13619
13826
|
}
|
|
13620
13827
|
});
|
|
13621
13828
|
|
|
13622
|
-
// packages/ingest/src/allowance.ts
|
|
13623
|
-
async function aiBudgetSettings() {
|
|
13624
|
-
const env = loadEnv();
|
|
13625
|
-
const stored = await getAppSettings().catch(
|
|
13626
|
-
() => ({})
|
|
13627
|
-
);
|
|
13628
|
-
const pick = (key, envValue) => envValue?.trim() || stored[key];
|
|
13629
|
-
const contingent = pick("ai_contingent", env.BRAINJAR_AI_CONTINGENT);
|
|
13630
|
-
const exempt = pick("ai_operator_exempt", env.BRAINJAR_AI_OPERATOR_EXEMPT);
|
|
13631
|
-
const daily = amount(
|
|
13632
|
-
pick("ai_daily_budget_usd", env.BRAINJAR_AI_DAILY_BUDGET_USD)
|
|
13633
|
-
);
|
|
13634
|
-
return {
|
|
13635
|
-
contingent: contingent ? contingent === "on" : loadConfig().environment === "saas",
|
|
13636
|
-
freeUsd: amount(pick("ai_free_monthly_usd", env.BRAINJAR_FREE_MONTHLY_USD)) ?? DEFAULT_FREE_MONTHLY_USD,
|
|
13637
|
-
teamSeatUsd: amount(
|
|
13638
|
-
pick("ai_team_seat_monthly_usd", env.BRAINJAR_TEAM_SEAT_MONTHLY_USD)
|
|
13639
|
-
) ?? DEFAULT_TEAM_SEAT_MONTHLY_USD,
|
|
13640
|
-
operatorExempt: exempt ? exempt !== "false" : true,
|
|
13641
|
-
dailyBudgetUsd: daily && daily > 0 ? daily : null,
|
|
13642
|
-
releasedDay: pick("ai_budget_released_day", void 0) ?? null
|
|
13643
|
-
};
|
|
13644
|
-
}
|
|
13645
|
-
async function aiAllowance(brainId, now = /* @__PURE__ */ new Date()) {
|
|
13646
|
-
const cfg = await aiBudgetSettings();
|
|
13647
|
-
if (cfg.dailyBudgetUsd !== null && cfg.releasedDay !== day(now)) {
|
|
13648
|
-
const used2 = await spendUsd(dayKey(now));
|
|
13649
|
-
if (used2 >= cfg.dailyBudgetUsd) {
|
|
13650
|
-
if (brainId && await swapAppSetting("ai_budget_alerted_day", day(now)))
|
|
13651
|
-
await enqueueJob(
|
|
13652
|
-
brainId,
|
|
13653
|
-
"budget-alert",
|
|
13654
|
-
{ usedUsd: used2, budgetUsd: cfg.dailyBudgetUsd, day: day(now) },
|
|
13655
|
-
{ priority: JOB_PRIORITY.interactive, dedupeKey: day(now) }
|
|
13656
|
-
).catch(() => {
|
|
13657
|
-
});
|
|
13658
|
-
return {
|
|
13659
|
-
reason: "daily_budget",
|
|
13660
|
-
usedUsd: used2,
|
|
13661
|
-
limitUsd: cfg.dailyBudgetUsd,
|
|
13662
|
-
renewsAt: dayRenewal(now)
|
|
13663
|
-
};
|
|
13664
|
-
}
|
|
13665
|
-
}
|
|
13666
|
-
if (!cfg.contingent || !brainId) return null;
|
|
13667
|
-
const ws = await brainWorkspace(brainId);
|
|
13668
|
-
if (!ws || ws.operator && cfg.operatorExempt) return null;
|
|
13669
|
-
const limit = cfg.freeUsd;
|
|
13670
|
-
const used = await spendUsd(monthKey(ws.ownerId, now));
|
|
13671
|
-
if (used < limit) return null;
|
|
13672
|
-
const renewsAt = monthRenewal(now);
|
|
13673
|
-
await tellOwnerOnce(ws.ownerId, brainId, renewsAt, now).catch(() => {
|
|
13674
|
-
});
|
|
13675
|
-
return { reason: "contingent", usedUsd: used, limitUsd: limit, renewsAt };
|
|
13676
|
-
}
|
|
13677
|
-
async function tellOwnerOnce(ownerId, brainId, renewsAt, now) {
|
|
13678
|
-
const monthStart = new Date(
|
|
13679
|
-
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)
|
|
13680
|
-
);
|
|
13681
|
-
if (await hasUserNotificationSince(ownerId, "ai_contingent", monthStart))
|
|
13682
|
-
return;
|
|
13683
|
-
await createNotification({
|
|
13684
|
-
kind: "ai_contingent",
|
|
13685
|
-
brainId,
|
|
13686
|
-
userId: ownerId,
|
|
13687
|
-
title: "Monthly AI contingent used up",
|
|
13688
|
-
body: `BrainJar keeps working without AI until ${renewalDate(renewsAt)}: capture uses the basic extraction, docs are built without AI text, verification and written summaries pause.`,
|
|
13689
|
-
href: `/settings?brain=${encodeURIComponent(brainId)}&tab=usage`
|
|
13690
|
-
});
|
|
13691
|
-
}
|
|
13692
|
-
var DEFAULT_FREE_MONTHLY_USD, DEFAULT_TEAM_SEAT_MONTHLY_USD, amount, day, renewalDate;
|
|
13693
|
-
var init_allowance = __esm({
|
|
13694
|
-
"packages/ingest/src/allowance.ts"() {
|
|
13695
|
-
"use strict";
|
|
13696
|
-
init_src2();
|
|
13697
|
-
init_src();
|
|
13698
|
-
DEFAULT_FREE_MONTHLY_USD = 1;
|
|
13699
|
-
DEFAULT_TEAM_SEAT_MONTHLY_USD = 5;
|
|
13700
|
-
amount = (v) => {
|
|
13701
|
-
if (v === void 0 || v.trim() === "") return null;
|
|
13702
|
-
const n2 = Number(v);
|
|
13703
|
-
return Number.isFinite(n2) && n2 >= 0 ? n2 : null;
|
|
13704
|
-
};
|
|
13705
|
-
day = (d) => d.toISOString().slice(0, 10);
|
|
13706
|
-
renewalDate = (d) => d.toLocaleDateString("en-GB", {
|
|
13707
|
-
day: "numeric",
|
|
13708
|
-
month: "long",
|
|
13709
|
-
timeZone: "UTC"
|
|
13710
|
-
});
|
|
13711
|
-
}
|
|
13712
|
-
});
|
|
13713
|
-
|
|
13714
13829
|
// packages/ingest/src/routing.ts
|
|
13715
13830
|
async function tierClient(tier, meta, effort) {
|
|
13716
13831
|
try {
|