brainjar 0.3.1226 → 0.3.1230
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 +249 -117
- 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.1230";
|
|
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",
|
|
@@ -6517,7 +6521,16 @@ var init_settings = __esm({
|
|
|
6517
6521
|
// a provider limit has less than this share left
|
|
6518
6522
|
// The self-host license token (BJ-132), pasted on Operator > Plan; BRAINJAR_LICENSE_TOKEN
|
|
6519
6523
|
// overrides. Signed, so it is checked on every read, never trusted as stored.
|
|
6520
|
-
"license_token"
|
|
6524
|
+
"license_token",
|
|
6525
|
+
// Billing on the SaaS instance (BJ-132 step 2): the Stripe price ids that sell each plan and
|
|
6526
|
+
// license, and the consent text buyers confirm in checkout (EU right of withdrawal).
|
|
6527
|
+
"stripe_price_pro",
|
|
6528
|
+
"stripe_price_company",
|
|
6529
|
+
"stripe_price_license_pro",
|
|
6530
|
+
"stripe_price_license_company",
|
|
6531
|
+
"billing_consent_text",
|
|
6532
|
+
// Credit packs (BJ-132 step 3): one per line, "<Stripe price id> <credits>".
|
|
6533
|
+
"credit_packs"
|
|
6521
6534
|
];
|
|
6522
6535
|
PROVIDER_KEY_SETTINGS = {
|
|
6523
6536
|
anthropic: "anthropic_api_key",
|
|
@@ -6540,7 +6553,7 @@ MCowBQYDK2VwAyEAKOUWx/Qgav+dKg78fIlckGby22CRKeuMWR5tkT1rYuw=
|
|
|
6540
6553
|
|
|
6541
6554
|
// packages/db/src/plan.ts
|
|
6542
6555
|
import { createPublicKey, sign, verify } from "node:crypto";
|
|
6543
|
-
var trustedKeys;
|
|
6556
|
+
var trustedKeys, PAID_STATUSES;
|
|
6544
6557
|
var init_plan = __esm({
|
|
6545
6558
|
"packages/db/src/plan.ts"() {
|
|
6546
6559
|
"use strict";
|
|
@@ -6549,6 +6562,7 @@ var init_plan = __esm({
|
|
|
6549
6562
|
init_settings();
|
|
6550
6563
|
init_license_key();
|
|
6551
6564
|
trustedKeys = [createPublicKey(LICENSE_PUBLIC_KEY)];
|
|
6565
|
+
PAID_STATUSES = ["active", "trialing", "past_due"];
|
|
6552
6566
|
}
|
|
6553
6567
|
});
|
|
6554
6568
|
|
|
@@ -8182,9 +8196,6 @@ var init_health = __esm({
|
|
|
8182
8196
|
});
|
|
8183
8197
|
|
|
8184
8198
|
// packages/db/src/spend.ts
|
|
8185
|
-
function monthKey(ownerId, at = /* @__PURE__ */ new Date()) {
|
|
8186
|
-
return `ws:${ownerId}:${at.toISOString().slice(0, 7)}`;
|
|
8187
|
-
}
|
|
8188
8199
|
function dayKey(at = /* @__PURE__ */ new Date()) {
|
|
8189
8200
|
return `day:${at.toISOString().slice(0, 10)}`;
|
|
8190
8201
|
}
|
|
@@ -8196,19 +8207,59 @@ function dayRenewal(at = /* @__PURE__ */ new Date()) {
|
|
|
8196
8207
|
Date.UTC(at.getUTCFullYear(), at.getUTCMonth(), at.getUTCDate() + 1)
|
|
8197
8208
|
);
|
|
8198
8209
|
}
|
|
8199
|
-
async function addSpend(brainId, usd, at = /* @__PURE__ */ new Date(), pool2 = getPool()) {
|
|
8210
|
+
async function addSpend(brainId, usd, at = /* @__PURE__ */ new Date(), pool2 = getPool(), credits) {
|
|
8200
8211
|
if (!(usd > 0)) return;
|
|
8201
|
-
await pool2.query(
|
|
8212
|
+
const r = await pool2.query(
|
|
8202
8213
|
`INSERT INTO llm_spend (key, usd)
|
|
8203
8214
|
SELECT k, $2 FROM (
|
|
8204
8215
|
SELECT $3::text AS k
|
|
8205
8216
|
UNION ALL
|
|
8206
|
-
SELECT 'ws:' || b.owner_id || ':' || $4 FROM brain b
|
|
8207
|
-
|
|
8217
|
+
(SELECT 'ws:' || b.owner_id || ':' || $4 FROM brain b
|
|
8218
|
+
WHERE b.id = $1 AND b.owner_id IS NOT NULL
|
|
8219
|
+
UNION
|
|
8220
|
+
SELECT credit_key(b.owner_id, $5) FROM brain b
|
|
8221
|
+
WHERE b.id = $1 AND b.owner_id IS NOT NULL)
|
|
8208
8222
|
) keys
|
|
8209
|
-
ON CONFLICT (key) DO UPDATE SET usd = llm_spend.usd + EXCLUDED.usd, updated_at = now()
|
|
8210
|
-
|
|
8223
|
+
ON CONFLICT (key) DO UPDATE SET usd = llm_spend.usd + EXCLUDED.usd, updated_at = now()
|
|
8224
|
+
RETURNING key, usd`,
|
|
8225
|
+
[brainId, usd, dayKey(at), at.toISOString().slice(0, 7), at]
|
|
8226
|
+
);
|
|
8227
|
+
if (!credits) return;
|
|
8228
|
+
const row = r.rows.find((x) => x.key.startsWith("wsp:")) ?? r.rows.find((x) => x.key.startsWith("ws:"));
|
|
8229
|
+
if (!row) return;
|
|
8230
|
+
const over = Math.min(
|
|
8231
|
+
usd,
|
|
8232
|
+
Math.max(0, Number(row.usd) - credits.includedUsd)
|
|
8233
|
+
);
|
|
8234
|
+
if (over > 0)
|
|
8235
|
+
await pool2.query(
|
|
8236
|
+
`UPDATE credit_balance SET credits = greatest(0, credits - $2), updated_at = now()
|
|
8237
|
+
WHERE user_id = $1`,
|
|
8238
|
+
[row.key.split(":")[1], over * credits.creditsPerUsd]
|
|
8239
|
+
);
|
|
8240
|
+
}
|
|
8241
|
+
async function creditPeriod(ownerId, at = /* @__PURE__ */ new Date(), pool2 = getPool()) {
|
|
8242
|
+
const r = await pool2.query(
|
|
8243
|
+
`SELECT credit_key($1, $2) AS key, ap.period_start, ap.period_end
|
|
8244
|
+
FROM (SELECT 1) one
|
|
8245
|
+
LEFT JOIN account_plan ap ON ap.user_id = $1
|
|
8246
|
+
AND ap.status IN ('active', 'trialing', 'past_due')
|
|
8247
|
+
AND ap.period_start <= $2 AND ap.period_end > $2`,
|
|
8248
|
+
[ownerId, at]
|
|
8249
|
+
);
|
|
8250
|
+
const row = r.rows[0];
|
|
8251
|
+
return row.key.startsWith("wsp:") && row.period_start && row.period_end ? { key: row.key, start: row.period_start, end: row.period_end } : {
|
|
8252
|
+
key: row.key,
|
|
8253
|
+
start: new Date(Date.UTC(at.getUTCFullYear(), at.getUTCMonth(), 1)),
|
|
8254
|
+
end: monthRenewal(at)
|
|
8255
|
+
};
|
|
8256
|
+
}
|
|
8257
|
+
async function purchasedCredits(ownerId, pool2 = getPool()) {
|
|
8258
|
+
const r = await pool2.query(
|
|
8259
|
+
"SELECT credits FROM credit_balance WHERE user_id = $1",
|
|
8260
|
+
[ownerId]
|
|
8211
8261
|
);
|
|
8262
|
+
return Number(r.rows[0]?.credits ?? 0);
|
|
8212
8263
|
}
|
|
8213
8264
|
async function spendUsd(key, pool2 = getPool()) {
|
|
8214
8265
|
const r = await pool2.query(
|
|
@@ -8234,6 +8285,31 @@ var init_spend = __esm({
|
|
|
8234
8285
|
}
|
|
8235
8286
|
});
|
|
8236
8287
|
|
|
8288
|
+
// packages/db/src/billing.ts
|
|
8289
|
+
async function accountPlanRow(userId, pool2 = getPool()) {
|
|
8290
|
+
const r = await pool2.query(
|
|
8291
|
+
`SELECT plan, seats, status, period_end, cancel_at_period_end, ended_at
|
|
8292
|
+
FROM account_plan WHERE user_id = $1`,
|
|
8293
|
+
[userId]
|
|
8294
|
+
);
|
|
8295
|
+
return r.rows[0] ?? null;
|
|
8296
|
+
}
|
|
8297
|
+
async function paidAccountPlan(userId, pool2 = getPool()) {
|
|
8298
|
+
const row = await accountPlanRow(userId, pool2);
|
|
8299
|
+
return row && isPaid(row.status) ? { plan: row.plan, seats: row.seats } : null;
|
|
8300
|
+
}
|
|
8301
|
+
var isPaid;
|
|
8302
|
+
var init_billing = __esm({
|
|
8303
|
+
"packages/db/src/billing.ts"() {
|
|
8304
|
+
"use strict";
|
|
8305
|
+
init_src();
|
|
8306
|
+
init_pool();
|
|
8307
|
+
init_settings();
|
|
8308
|
+
init_plan();
|
|
8309
|
+
isPaid = (status) => PAID_STATUSES.includes(status);
|
|
8310
|
+
}
|
|
8311
|
+
});
|
|
8312
|
+
|
|
8237
8313
|
// packages/db/src/provider-rate.ts
|
|
8238
8314
|
async function saveRate(keyId2, model, s, pool2 = getPool()) {
|
|
8239
8315
|
await pool2.query(
|
|
@@ -8297,7 +8373,7 @@ var init_ruleset = __esm({
|
|
|
8297
8373
|
});
|
|
8298
8374
|
|
|
8299
8375
|
// packages/db/src/usage.ts
|
|
8300
|
-
async function recordLlmUsage(u) {
|
|
8376
|
+
async function recordLlmUsage(u, credits) {
|
|
8301
8377
|
await getPool().query(
|
|
8302
8378
|
`INSERT INTO llm_usage (brain_id, workload, model, triggered_by, input_tokens, cached_input_tokens, output_tokens)
|
|
8303
8379
|
VALUES ($1,$2,$3,$4,$5,$6,$7)`,
|
|
@@ -8317,7 +8393,10 @@ async function recordLlmUsage(u) {
|
|
|
8317
8393
|
input: u.inputTokens,
|
|
8318
8394
|
cachedInput: u.cachedInputTokens,
|
|
8319
8395
|
output: u.outputTokens
|
|
8320
|
-
})
|
|
8396
|
+
}),
|
|
8397
|
+
void 0,
|
|
8398
|
+
void 0,
|
|
8399
|
+
await credits?.().catch(() => null)
|
|
8321
8400
|
);
|
|
8322
8401
|
}
|
|
8323
8402
|
var init_usage = __esm({
|
|
@@ -8355,6 +8434,7 @@ var init_src2 = __esm({
|
|
|
8355
8434
|
init_health();
|
|
8356
8435
|
init_spend();
|
|
8357
8436
|
init_plan();
|
|
8437
|
+
init_billing();
|
|
8358
8438
|
init_provider_rate();
|
|
8359
8439
|
init_ruleset();
|
|
8360
8440
|
init_usage();
|
|
@@ -13327,6 +13407,145 @@ var init_provider_rate2 = __esm({
|
|
|
13327
13407
|
}
|
|
13328
13408
|
});
|
|
13329
13409
|
|
|
13410
|
+
// packages/ingest/src/allowance.ts
|
|
13411
|
+
async function aiBudgetSettings() {
|
|
13412
|
+
const env = loadEnv();
|
|
13413
|
+
const stored = await getAppSettings().catch(
|
|
13414
|
+
() => ({})
|
|
13415
|
+
);
|
|
13416
|
+
const pick = (key, envValue) => envValue?.trim() || stored[key];
|
|
13417
|
+
const contingent = pick("ai_contingent", env.BRAINJAR_AI_CONTINGENT);
|
|
13418
|
+
const exempt = pick("ai_operator_exempt", env.BRAINJAR_AI_OPERATOR_EXEMPT);
|
|
13419
|
+
const daily = amount(
|
|
13420
|
+
pick("ai_daily_budget_usd", env.BRAINJAR_AI_DAILY_BUDGET_USD)
|
|
13421
|
+
);
|
|
13422
|
+
return {
|
|
13423
|
+
contingent: contingent ? contingent === "on" : loadConfig().environment === "saas",
|
|
13424
|
+
freeUsd: amount(pick("ai_free_monthly_usd", env.BRAINJAR_FREE_MONTHLY_USD)) ?? DEFAULT_FREE_MONTHLY_USD,
|
|
13425
|
+
proUsd: amount(pick("ai_pro_monthly_usd", env.BRAINJAR_PRO_MONTHLY_USD)) ?? DEFAULT_PRO_MONTHLY_USD,
|
|
13426
|
+
teamSeatUsd: amount(
|
|
13427
|
+
pick("ai_team_seat_monthly_usd", env.BRAINJAR_TEAM_SEAT_MONTHLY_USD)
|
|
13428
|
+
) ?? DEFAULT_TEAM_SEAT_MONTHLY_USD,
|
|
13429
|
+
creditsPerUsd: positive(pick("credits_per_usd", env.BRAINJAR_CREDITS_PER_USD)) ?? DEFAULT_CREDITS_PER_USD,
|
|
13430
|
+
operatorExempt: exempt ? exempt !== "false" : true,
|
|
13431
|
+
dailyBudgetUsd: daily && daily > 0 ? daily : null,
|
|
13432
|
+
releasedDay: pick("ai_budget_released_day", void 0) ?? null
|
|
13433
|
+
};
|
|
13434
|
+
}
|
|
13435
|
+
async function aiAllowance(brainId, now = /* @__PURE__ */ new Date()) {
|
|
13436
|
+
const cfg = await aiBudgetSettings();
|
|
13437
|
+
if (cfg.dailyBudgetUsd !== null && cfg.releasedDay !== day(now)) {
|
|
13438
|
+
const used2 = await spendUsd(dayKey(now));
|
|
13439
|
+
if (used2 >= cfg.dailyBudgetUsd) {
|
|
13440
|
+
if (brainId && await swapAppSetting("ai_budget_alerted_day", day(now)))
|
|
13441
|
+
await enqueueJob(
|
|
13442
|
+
brainId,
|
|
13443
|
+
"budget-alert",
|
|
13444
|
+
{ usedUsd: used2, budgetUsd: cfg.dailyBudgetUsd, day: day(now) },
|
|
13445
|
+
{ priority: JOB_PRIORITY.interactive, dedupeKey: day(now) }
|
|
13446
|
+
).catch(() => {
|
|
13447
|
+
});
|
|
13448
|
+
return {
|
|
13449
|
+
reason: "daily_budget",
|
|
13450
|
+
usedUsd: used2,
|
|
13451
|
+
limitUsd: cfg.dailyBudgetUsd,
|
|
13452
|
+
renewsAt: dayRenewal(now)
|
|
13453
|
+
};
|
|
13454
|
+
}
|
|
13455
|
+
}
|
|
13456
|
+
if (!cfg.contingent || !brainId) return null;
|
|
13457
|
+
const ws = await brainWorkspace(brainId);
|
|
13458
|
+
if (!ws || ws.operator && cfg.operatorExempt) return null;
|
|
13459
|
+
const [limit, period] = await Promise.all([
|
|
13460
|
+
includedUsd(ws.ownerId, cfg),
|
|
13461
|
+
creditPeriod(ws.ownerId, now)
|
|
13462
|
+
]);
|
|
13463
|
+
const used = await spendUsd(period.key);
|
|
13464
|
+
if (used < limit) {
|
|
13465
|
+
if (used >= limit * WARN_SHARE)
|
|
13466
|
+
await warnOwnerOnce(ws.ownerId, brainId, period, now).catch(() => {
|
|
13467
|
+
});
|
|
13468
|
+
return null;
|
|
13469
|
+
}
|
|
13470
|
+
if (await purchasedCredits(ws.ownerId) > 0) return null;
|
|
13471
|
+
await tellOwnerOnce(ws.ownerId, brainId, period, now).catch(() => {
|
|
13472
|
+
});
|
|
13473
|
+
return {
|
|
13474
|
+
reason: "contingent",
|
|
13475
|
+
usedUsd: used,
|
|
13476
|
+
limitUsd: limit,
|
|
13477
|
+
renewsAt: period.end
|
|
13478
|
+
};
|
|
13479
|
+
}
|
|
13480
|
+
async function includedUsd(ownerId, cfg) {
|
|
13481
|
+
const paid = await paidAccountPlan(ownerId).catch(() => null);
|
|
13482
|
+
if (!paid) return cfg.freeUsd;
|
|
13483
|
+
return paid.plan === "pro" ? cfg.proUsd : paid.seats * cfg.teamSeatUsd;
|
|
13484
|
+
}
|
|
13485
|
+
async function creditTerms(brainId) {
|
|
13486
|
+
const cfg = await aiBudgetSettings();
|
|
13487
|
+
if (!cfg.contingent || !brainId) return null;
|
|
13488
|
+
const ws = await brainWorkspace(brainId);
|
|
13489
|
+
if (!ws || ws.operator && cfg.operatorExempt) return null;
|
|
13490
|
+
return {
|
|
13491
|
+
includedUsd: await includedUsd(ws.ownerId, cfg),
|
|
13492
|
+
creditsPerUsd: cfg.creditsPerUsd
|
|
13493
|
+
};
|
|
13494
|
+
}
|
|
13495
|
+
async function tellOwnerOnce(ownerId, brainId, period, _now) {
|
|
13496
|
+
if (await hasUserNotificationSince(ownerId, "ai_contingent", period.start))
|
|
13497
|
+
return;
|
|
13498
|
+
await createNotification({
|
|
13499
|
+
kind: "ai_contingent",
|
|
13500
|
+
brainId,
|
|
13501
|
+
userId: ownerId,
|
|
13502
|
+
title: "This month's AI credits are used up",
|
|
13503
|
+
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.`,
|
|
13504
|
+
href: creditsHref(brainId)
|
|
13505
|
+
});
|
|
13506
|
+
}
|
|
13507
|
+
async function warnOwnerOnce(ownerId, brainId, period, _now) {
|
|
13508
|
+
if (await hasUserNotificationSince(ownerId, "ai_credits_low", period.start))
|
|
13509
|
+
return;
|
|
13510
|
+
await createNotification({
|
|
13511
|
+
kind: "ai_credits_low",
|
|
13512
|
+
brainId,
|
|
13513
|
+
userId: ownerId,
|
|
13514
|
+
title: "80 percent of this month's AI credits are used",
|
|
13515
|
+
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.`,
|
|
13516
|
+
href: creditsHref(brainId)
|
|
13517
|
+
});
|
|
13518
|
+
}
|
|
13519
|
+
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;
|
|
13520
|
+
var init_allowance = __esm({
|
|
13521
|
+
"packages/ingest/src/allowance.ts"() {
|
|
13522
|
+
"use strict";
|
|
13523
|
+
init_src2();
|
|
13524
|
+
init_src();
|
|
13525
|
+
DEFAULT_FREE_MONTHLY_USD = 1;
|
|
13526
|
+
DEFAULT_PRO_MONTHLY_USD = 10;
|
|
13527
|
+
DEFAULT_TEAM_SEAT_MONTHLY_USD = 5;
|
|
13528
|
+
DEFAULT_CREDITS_PER_USD = 100;
|
|
13529
|
+
amount = (v) => {
|
|
13530
|
+
if (v === void 0 || v.trim() === "") return null;
|
|
13531
|
+
const n2 = Number(v);
|
|
13532
|
+
return Number.isFinite(n2) && n2 >= 0 ? n2 : null;
|
|
13533
|
+
};
|
|
13534
|
+
positive = (v) => {
|
|
13535
|
+
const n2 = amount(v);
|
|
13536
|
+
return n2 !== null && n2 > 0 ? n2 : null;
|
|
13537
|
+
};
|
|
13538
|
+
day = (d) => d.toISOString().slice(0, 10);
|
|
13539
|
+
WARN_SHARE = 0.8;
|
|
13540
|
+
renewalDate = (d) => d.toLocaleDateString("en-GB", {
|
|
13541
|
+
day: "numeric",
|
|
13542
|
+
month: "long",
|
|
13543
|
+
timeZone: "UTC"
|
|
13544
|
+
});
|
|
13545
|
+
creditsHref = (brainId) => loadConfig().environment === "saas" ? "/account?tab=plan" : `/settings?brain=${encodeURIComponent(brainId)}&tab=usage`;
|
|
13546
|
+
}
|
|
13547
|
+
});
|
|
13548
|
+
|
|
13330
13549
|
// packages/ingest/src/llm.ts
|
|
13331
13550
|
function rationaleModelId(env = process.env) {
|
|
13332
13551
|
return env.BRAINJAR_RATIONALE_MODEL?.trim() || "claude-opus-5";
|
|
@@ -13336,16 +13555,20 @@ function logUsage(meta, model, usage) {
|
|
|
13336
13555
|
console.warn(
|
|
13337
13556
|
`brainjar: AI call without a Brain (workload ${meta?.workload ?? "other"}); it counts only toward the daily budget`
|
|
13338
13557
|
);
|
|
13339
|
-
void recordLlmUsage(
|
|
13340
|
-
|
|
13341
|
-
|
|
13342
|
-
|
|
13343
|
-
|
|
13344
|
-
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
|
|
13558
|
+
void recordLlmUsage(
|
|
13559
|
+
{
|
|
13560
|
+
brainId: meta?.brainId ?? null,
|
|
13561
|
+
workload: meta?.workload ?? "other",
|
|
13562
|
+
triggeredBy: meta?.actor ?? null,
|
|
13563
|
+
model,
|
|
13564
|
+
// Cache writes bill near the input rate; booked as input to keep the ledger simple.
|
|
13565
|
+
inputTokens: (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0),
|
|
13566
|
+
cachedInputTokens: usage.cache_read_input_tokens ?? 0,
|
|
13567
|
+
outputTokens: usage.output_tokens ?? 0
|
|
13568
|
+
},
|
|
13569
|
+
// The workspace's credit terms decide what comes off purchased credits (BJ-132).
|
|
13570
|
+
() => creditTerms(meta?.brainId)
|
|
13571
|
+
).catch(() => {
|
|
13349
13572
|
});
|
|
13350
13573
|
}
|
|
13351
13574
|
function anthropicClient(apiKey, model, meta) {
|
|
@@ -13362,6 +13585,7 @@ var init_llm = __esm({
|
|
|
13362
13585
|
init_sdk();
|
|
13363
13586
|
init_src2();
|
|
13364
13587
|
init_provider_rate2();
|
|
13588
|
+
init_allowance();
|
|
13365
13589
|
cachedSystem = (system) => [
|
|
13366
13590
|
{
|
|
13367
13591
|
type: "text",
|
|
@@ -13600,98 +13824,6 @@ var init_openai = __esm({
|
|
|
13600
13824
|
}
|
|
13601
13825
|
});
|
|
13602
13826
|
|
|
13603
|
-
// packages/ingest/src/allowance.ts
|
|
13604
|
-
async function aiBudgetSettings() {
|
|
13605
|
-
const env = loadEnv();
|
|
13606
|
-
const stored = await getAppSettings().catch(
|
|
13607
|
-
() => ({})
|
|
13608
|
-
);
|
|
13609
|
-
const pick = (key, envValue) => envValue?.trim() || stored[key];
|
|
13610
|
-
const contingent = pick("ai_contingent", env.BRAINJAR_AI_CONTINGENT);
|
|
13611
|
-
const exempt = pick("ai_operator_exempt", env.BRAINJAR_AI_OPERATOR_EXEMPT);
|
|
13612
|
-
const daily = amount(
|
|
13613
|
-
pick("ai_daily_budget_usd", env.BRAINJAR_AI_DAILY_BUDGET_USD)
|
|
13614
|
-
);
|
|
13615
|
-
return {
|
|
13616
|
-
contingent: contingent ? contingent === "on" : loadConfig().environment === "saas",
|
|
13617
|
-
freeUsd: amount(pick("ai_free_monthly_usd", env.BRAINJAR_FREE_MONTHLY_USD)) ?? DEFAULT_FREE_MONTHLY_USD,
|
|
13618
|
-
teamSeatUsd: amount(
|
|
13619
|
-
pick("ai_team_seat_monthly_usd", env.BRAINJAR_TEAM_SEAT_MONTHLY_USD)
|
|
13620
|
-
) ?? DEFAULT_TEAM_SEAT_MONTHLY_USD,
|
|
13621
|
-
operatorExempt: exempt ? exempt !== "false" : true,
|
|
13622
|
-
dailyBudgetUsd: daily && daily > 0 ? daily : null,
|
|
13623
|
-
releasedDay: pick("ai_budget_released_day", void 0) ?? null
|
|
13624
|
-
};
|
|
13625
|
-
}
|
|
13626
|
-
async function aiAllowance(brainId, now = /* @__PURE__ */ new Date()) {
|
|
13627
|
-
const cfg = await aiBudgetSettings();
|
|
13628
|
-
if (cfg.dailyBudgetUsd !== null && cfg.releasedDay !== day(now)) {
|
|
13629
|
-
const used2 = await spendUsd(dayKey(now));
|
|
13630
|
-
if (used2 >= cfg.dailyBudgetUsd) {
|
|
13631
|
-
if (brainId && await swapAppSetting("ai_budget_alerted_day", day(now)))
|
|
13632
|
-
await enqueueJob(
|
|
13633
|
-
brainId,
|
|
13634
|
-
"budget-alert",
|
|
13635
|
-
{ usedUsd: used2, budgetUsd: cfg.dailyBudgetUsd, day: day(now) },
|
|
13636
|
-
{ priority: JOB_PRIORITY.interactive, dedupeKey: day(now) }
|
|
13637
|
-
).catch(() => {
|
|
13638
|
-
});
|
|
13639
|
-
return {
|
|
13640
|
-
reason: "daily_budget",
|
|
13641
|
-
usedUsd: used2,
|
|
13642
|
-
limitUsd: cfg.dailyBudgetUsd,
|
|
13643
|
-
renewsAt: dayRenewal(now)
|
|
13644
|
-
};
|
|
13645
|
-
}
|
|
13646
|
-
}
|
|
13647
|
-
if (!cfg.contingent || !brainId) return null;
|
|
13648
|
-
const ws = await brainWorkspace(brainId);
|
|
13649
|
-
if (!ws || ws.operator && cfg.operatorExempt) return null;
|
|
13650
|
-
const limit = cfg.freeUsd;
|
|
13651
|
-
const used = await spendUsd(monthKey(ws.ownerId, now));
|
|
13652
|
-
if (used < limit) return null;
|
|
13653
|
-
const renewsAt = monthRenewal(now);
|
|
13654
|
-
await tellOwnerOnce(ws.ownerId, brainId, renewsAt, now).catch(() => {
|
|
13655
|
-
});
|
|
13656
|
-
return { reason: "contingent", usedUsd: used, limitUsd: limit, renewsAt };
|
|
13657
|
-
}
|
|
13658
|
-
async function tellOwnerOnce(ownerId, brainId, renewsAt, now) {
|
|
13659
|
-
const monthStart = new Date(
|
|
13660
|
-
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)
|
|
13661
|
-
);
|
|
13662
|
-
if (await hasUserNotificationSince(ownerId, "ai_contingent", monthStart))
|
|
13663
|
-
return;
|
|
13664
|
-
await createNotification({
|
|
13665
|
-
kind: "ai_contingent",
|
|
13666
|
-
brainId,
|
|
13667
|
-
userId: ownerId,
|
|
13668
|
-
title: "Monthly AI contingent used up",
|
|
13669
|
-
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.`,
|
|
13670
|
-
href: `/settings?brain=${encodeURIComponent(brainId)}&tab=usage`
|
|
13671
|
-
});
|
|
13672
|
-
}
|
|
13673
|
-
var DEFAULT_FREE_MONTHLY_USD, DEFAULT_TEAM_SEAT_MONTHLY_USD, amount, day, renewalDate;
|
|
13674
|
-
var init_allowance = __esm({
|
|
13675
|
-
"packages/ingest/src/allowance.ts"() {
|
|
13676
|
-
"use strict";
|
|
13677
|
-
init_src2();
|
|
13678
|
-
init_src();
|
|
13679
|
-
DEFAULT_FREE_MONTHLY_USD = 1;
|
|
13680
|
-
DEFAULT_TEAM_SEAT_MONTHLY_USD = 5;
|
|
13681
|
-
amount = (v) => {
|
|
13682
|
-
if (v === void 0 || v.trim() === "") return null;
|
|
13683
|
-
const n2 = Number(v);
|
|
13684
|
-
return Number.isFinite(n2) && n2 >= 0 ? n2 : null;
|
|
13685
|
-
};
|
|
13686
|
-
day = (d) => d.toISOString().slice(0, 10);
|
|
13687
|
-
renewalDate = (d) => d.toLocaleDateString("en-GB", {
|
|
13688
|
-
day: "numeric",
|
|
13689
|
-
month: "long",
|
|
13690
|
-
timeZone: "UTC"
|
|
13691
|
-
});
|
|
13692
|
-
}
|
|
13693
|
-
});
|
|
13694
|
-
|
|
13695
13827
|
// packages/ingest/src/routing.ts
|
|
13696
13828
|
async function tierClient(tier, meta, effort) {
|
|
13697
13829
|
try {
|