@hachej/boring-core 0.1.70 → 0.1.72

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.
@@ -1,4 +1,4 @@
1
- import { C as CoreConfig } from './types-DVgeIw1d.js';
1
+ import { C as CoreConfig } from './types-DDnayJjI.js';
2
2
  import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
3
3
 
4
4
  interface RunMigrationsOptions {
@@ -89,7 +89,7 @@ interface RecordUsageResult {
89
89
  inserted: boolean;
90
90
  }
91
91
  type ReservationFinalStatus = 'settled' | 'released';
92
- interface FinishReservationInput {
92
+ interface FinishReservationInput$1 {
93
93
  /** Preferred key: the id returned by reserve(). */
94
94
  reservationId?: string;
95
95
  /** Fallback key; pair with userId to scope across tenants. */
@@ -203,7 +203,7 @@ declare class PostgresMeteringStore {
203
203
  * retry), so the runId fallback resolves to the single newest matching row
204
204
  * — a settle never flips both the dead and the live reservation together.
205
205
  */
206
- finishReservation(input: FinishReservationInput, status: ReservationFinalStatus): Promise<{
206
+ finishReservation(input: FinishReservationInput$1, status: ReservationFinalStatus): Promise<{
207
207
  updated: boolean;
208
208
  }>;
209
209
  /** Expire stale active reservations without charging. Returns the count. */
@@ -225,6 +225,8 @@ declare class PostgresMeteringStore {
225
225
  private sumActiveReservations;
226
226
  }
227
227
 
228
+ type BudgetReservationStatus = 'active' | 'settled' | 'released' | 'expired';
229
+ type BudgetReservationScope = 'model' | 'user';
228
230
  declare class ModelBudgetExceededError extends Error {
229
231
  readonly usedMicros: number;
230
232
  readonly heldMicros: number;
@@ -234,37 +236,117 @@ declare class ModelBudgetExceededError extends Error {
234
236
  readonly code = "MODEL_BUDGET_EXCEEDED";
235
237
  constructor(usedMicros: number, heldMicros: number, budgetMicros: number, requestedMicros: number);
236
238
  }
237
- interface ReserveModelBudgetInput {
239
+ declare class UserBudgetExceededError extends Error {
240
+ readonly usedMicros: number;
241
+ readonly heldMicros: number;
242
+ readonly budgetMicros: number;
243
+ readonly requestedMicros: number;
244
+ readonly statusCode = 402;
245
+ readonly code = "MODEL_BUDGET_EXCEEDED";
246
+ constructor(usedMicros: number, heldMicros: number, budgetMicros: number, requestedMicros: number);
247
+ }
248
+ interface ReserveBudgetBaseInput {
238
249
  userId: string;
239
250
  workspaceId?: string;
240
251
  sessionId?: string;
241
252
  runId: string;
242
- provider: string;
243
- model: string;
244
253
  budgetMicros: number;
245
254
  holdMicros: number;
246
255
  ttlSeconds: number;
247
256
  now?: Date;
248
257
  }
249
- interface ReserveModelBudgetResult {
258
+ type ReserveBudgetInput = (ReserveBudgetBaseInput & {
259
+ scope: 'user';
260
+ provider?: never;
261
+ model?: never;
262
+ }) | (ReserveBudgetBaseInput & {
263
+ scope: 'model';
264
+ provider: string;
265
+ model: string;
266
+ });
267
+ interface ReserveBudgetResult {
268
+ scope: BudgetReservationScope;
250
269
  reservationId: string;
251
270
  created: boolean;
252
271
  period: string;
253
272
  }
254
- interface FinishModelBudgetReservationInput {
273
+ interface BudgetReservationAdmissionInput {
274
+ user?: Extract<ReserveBudgetInput, {
275
+ scope: 'user';
276
+ }>;
277
+ model: Extract<ReserveBudgetInput, {
278
+ scope: 'model';
279
+ }>;
280
+ }
281
+ interface BudgetReservationAdmission {
282
+ user?: ReserveBudgetResult & {
283
+ scope: 'user';
284
+ };
285
+ model: ReserveBudgetResult & {
286
+ scope: 'model';
287
+ };
288
+ }
289
+ interface FinishReservationInput {
290
+ scope: BudgetReservationScope;
255
291
  reservationId?: string;
256
292
  runId?: string;
257
293
  userId?: string;
258
294
  }
259
- declare class PostgresModelBudgetStore {
295
+ declare class PostgresBudgetReservationStore {
260
296
  private db;
297
+ private readonly options;
298
+ constructor(db: PostgresJsDatabase, options?: {
299
+ eligibleLegacySources?: readonly string[];
300
+ });
301
+ static monthPeriodUtc(now?: Date): string;
302
+ sweepExpired(now?: Date): Promise<number>;
303
+ reserve(input: ReserveBudgetInput): Promise<ReserveBudgetResult>;
304
+ reserveAdmission(input: BudgetReservationAdmissionInput): Promise<BudgetReservationAdmission>;
305
+ reserveMany(inputs: readonly ReserveBudgetInput[]): Promise<ReserveBudgetResult[]>;
306
+ private reserveInTransaction;
307
+ settle(input: FinishReservationInput): Promise<void>;
308
+ release(input: FinishReservationInput): Promise<void>;
309
+ metadataForAdmission(admission: BudgetReservationAdmission): Record<string, string>;
310
+ releaseCreated(admission: BudgetReservationAdmission): Promise<void>;
311
+ finishAdmission(admission: BudgetReservationAdmission, status: Exclude<BudgetReservationStatus, 'active' | 'expired'>): Promise<void>;
312
+ private handlesForAdmission;
313
+ finishRun(input: {
314
+ userId: string;
315
+ runId: string;
316
+ }, status: Exclude<BudgetReservationStatus, 'active' | 'expired'>): Promise<void>;
317
+ finishMany(inputs: readonly FinishReservationInput[], status: Exclude<BudgetReservationStatus, 'active' | 'expired'>): Promise<void>;
318
+ private scopeBudgetWhere;
319
+ private reservationTotal;
320
+ private spend;
321
+ private spendTotals;
322
+ private finish;
323
+ private finishInTransaction;
324
+ }
325
+
326
+ interface ReserveModelBudgetInput {
327
+ userId: string;
328
+ workspaceId?: string;
329
+ sessionId?: string;
330
+ runId: string;
331
+ provider: string;
332
+ model: string;
333
+ budgetMicros: number;
334
+ holdMicros: number;
335
+ ttlSeconds: number;
336
+ now?: Date;
337
+ }
338
+ type ReserveModelBudgetResult = Omit<ReserveBudgetResult, 'scope'>;
339
+ interface FinishModelBudgetReservationInput extends Omit<FinishReservationInput, 'scope'> {
340
+ scope?: 'model';
341
+ }
342
+ declare class PostgresModelBudgetStore {
343
+ private readonly store;
261
344
  constructor(db: PostgresJsDatabase);
262
345
  static monthPeriodUtc(now?: Date): string;
263
346
  sweepExpired(now?: Date): Promise<number>;
264
347
  reserve(input: ReserveModelBudgetInput): Promise<ReserveModelBudgetResult>;
265
348
  settle(input: FinishModelBudgetReservationInput): Promise<void>;
266
349
  release(input: FinishModelBudgetReservationInput): Promise<void>;
267
- private finish;
268
350
  }
269
351
 
270
- export { type CreditLedgerEntry as C, type FinishReservationInput as F, type GrantOnceInput as G, InsufficientCreditError as I, type MeteringBalance as M, PostgresMeteringStore as P, type RecordUsageInput as R, ModelBudgetExceededError as a, PostgresModelBudgetStore as b, type RecordUsageResult as c, type ReservationFinalStatus as d, type ReserveInput as e, type ReserveResult as f, type RunMigrationsOptions as g, runMigrations as r };
352
+ export { type BudgetReservationAdmission as B, type CreditLedgerEntry as C, type FinishReservationInput as F, type GrantOnceInput as G, InsufficientCreditError as I, type MeteringBalance as M, PostgresBudgetReservationStore as P, type RecordUsageInput as R, UserBudgetExceededError as U, type BudgetReservationAdmissionInput as a, type BudgetReservationScope as b, type FinishReservationInput$1 as c, ModelBudgetExceededError as d, PostgresMeteringStore as e, PostgresModelBudgetStore as f, type RecordUsageResult as g, type ReservationFinalStatus as h, type ReserveBudgetInput as i, type ReserveBudgetResult as j, type ReserveInput as k, type ReserveResult as l, type RunMigrationsOptions as m, runMigrations as r };
@@ -154,13 +154,17 @@ interface CreditLedgerEntry {
154
154
  /** Format SIGNED credit micros as a currency string with an explicit +/− sign.
155
155
  * `currency` is the configured display currency (1 credit-unit = 1 major unit); defaults
156
156
  * to EUR for callers without a configured purchase currency. */
157
- declare function formatSignedCreditMicros(micros: number, currency?: string, locale?: string): string;
157
+ interface CreditFormatOptions {
158
+ /** Show sub-cent/sub-rappen/sub-credit usage instead of rounding tiny amounts to 0.00. */
159
+ highPrecision?: boolean;
160
+ }
161
+ declare function formatSignedCreditMicros(micros: number, currency?: string, locale?: string, options?: CreditFormatOptions): string;
158
162
  /** Format a minor-unit price (e.g. cents) in its currency for pack labels/buttons. */
159
163
  declare function formatMinorPrice(priceMinor: number, currency: string, locale?: string): string;
160
164
  /** Format credit micros as a currency string. 1 credit-unit = 1 major unit of the
161
165
  * configured display `currency` (µ/1e6). Defaults to EUR for callers without a
162
166
  * configured purchase currency (e.g. a consumption-only deployment). */
163
- declare function formatCreditMicros(micros: number, currency?: string, locale?: string): string;
167
+ declare function formatCreditMicros(micros: number, currency?: string, locale?: string, options?: CreditFormatOptions): string;
164
168
  /** True when the remaining balance is at or below the low-balance threshold. */
165
169
  declare function isLowBalance(micros: number, thresholdMicros?: number): boolean;
166
170
  /** Stable server error code for an out-of-credits rejection (mirrors the agent's
@@ -916,19 +916,26 @@ function creditNetMicros(balance) {
916
916
  const debt = Number.isFinite(balance.debtMicros) ? balance.debtMicros : 0;
917
917
  return remaining - debt;
918
918
  }
919
- function formatSignedCreditMicros(micros, currency = "EUR", locale) {
919
+ function creditFractionDigits(major, options) {
920
+ if (!options?.highPrecision) return {};
921
+ return {
922
+ minimumFractionDigits: 2,
923
+ maximumFractionDigits: Math.abs(major) > 0 && Math.abs(major) < 0.01 ? 6 : 2
924
+ };
925
+ }
926
+ function formatSignedCreditMicros(micros, currency = "EUR", locale, options) {
920
927
  const major = (Number.isFinite(micros) ? micros : 0) / 1e6;
921
928
  const sign = major > 0 ? "+" : major < 0 ? "\u2212" : "";
922
- const abs = new Intl.NumberFormat(locale, { style: "currency", currency }).format(Math.abs(major));
929
+ const abs = new Intl.NumberFormat(locale, { style: "currency", currency, ...creditFractionDigits(major, options) }).format(Math.abs(major));
923
930
  return `${sign}${abs}`;
924
931
  }
925
932
  function formatMinorPrice(priceMinor, currency, locale) {
926
933
  const major = (Number.isFinite(priceMinor) ? priceMinor : 0) / 100;
927
934
  return new Intl.NumberFormat(locale, { style: "currency", currency, maximumFractionDigits: major % 1 === 0 ? 0 : 2 }).format(major);
928
935
  }
929
- function formatCreditMicros(micros, currency = "EUR", locale) {
936
+ function formatCreditMicros(micros, currency = "EUR", locale, options) {
930
937
  const major = (Number.isFinite(micros) ? Math.max(0, micros) : 0) / 1e6;
931
- return new Intl.NumberFormat(locale, { style: "currency", currency }).format(major);
938
+ return new Intl.NumberFormat(locale, { style: "currency", currency, ...creditFractionDigits(major, options) }).format(major);
932
939
  }
933
940
  function isLowBalance(micros, thresholdMicros = 5e5) {
934
941
  return Number.isFinite(micros) && micros <= thresholdMicros;
@@ -1253,7 +1260,7 @@ function CreditsSettingsPanel({ apiBaseUrl = "", locale }) {
1253
1260
  inDebt ? `\u2212${formatCreditMicros(balance.debtMicros, currency, locale)}` : formatCreditMicros(balance.remainingMicros, currency, locale),
1254
1261
  updating && /* @__PURE__ */ jsx7("span", { className: "ml-2 text-[11px] font-normal text-muted-foreground", "aria-live": "polite", children: "Updating\u2026" })
1255
1262
  ] }) }),
1256
- /* @__PURE__ */ jsx7(DetailLine, { label: "Used so far", children: /* @__PURE__ */ jsx7("p", { style: { fontVariantNumeric: "tabular-nums" }, children: formatCreditMicros(balance.usedMicros, currency, locale) }) })
1263
+ /* @__PURE__ */ jsx7(DetailLine, { label: "Used so far", children: /* @__PURE__ */ jsx7("p", { style: { fontVariantNumeric: "tabular-nums" }, children: formatCreditMicros(balance.usedMicros, currency, locale, { highPrecision: true }) }) })
1257
1264
  ] }),
1258
1265
  showBuy && packs.length > 0 && /* @__PURE__ */ jsxs6("fieldset", { className: "space-y-2", children: [
1259
1266
  /* @__PURE__ */ jsx7("legend", { className: "text-[12px] font-medium text-foreground", children: "Buy credits" }),
@@ -1322,7 +1329,7 @@ function CreditsSettingsPanel({ apiBaseUrl = "", locale }) {
1322
1329
  {
1323
1330
  style: { fontVariantNumeric: "tabular-nums" },
1324
1331
  className: e.amountMicros >= 0 ? "text-foreground" : "text-muted-foreground",
1325
- children: formatSignedCreditMicros(e.amountMicros, currency, locale)
1332
+ children: formatSignedCreditMicros(e.amountMicros, currency, locale, e.kind === "usage" ? { highPrecision: true } : void 0)
1326
1333
  }
1327
1334
  )
1328
1335
  ] }, e.id)) })
@@ -2,10 +2,10 @@ import { RuntimeProvisioningContribution, RegisterAgentRoutesOptions } from '@ha
2
2
  import { DirPluginEntry, CreateWorkspaceAgentServerOptions } from '@hachej/boring-workspace/app/server';
3
3
  import { WorkspaceServerPlugin, WorkspaceBridgeOperationDefinition, WorkspaceBridgeHandler, WorkspaceBridgeRuntimeEnvOptions, WorkspaceBridgeCallRequest, WorkspaceBridgeCallResponse } from '@hachej/boring-workspace/server';
4
4
  import { FastifyInstance } from 'fastify';
5
- import { C as CoreConfig } from '../../types-DVgeIw1d.js';
5
+ import { C as CoreConfig } from '../../types-DDnayJjI.js';
6
6
  import { T as TelemetrySink } from '../../telemetry-DR18MeI0.js';
7
- import { B as BetterAuthInstance, L as LoadConfigOptions } from '../../authHook-BUSGTx_o.js';
8
- import { D as Database, U as UserStore, W as WorkspaceStore } from '../../connection-D9s7Td0I.js';
7
+ import { B as BetterAuthInstance, L as LoadConfigOptions } from '../../loadConfig-C3iHuZNw.js';
8
+ import { D as Database, U as UserStore, W as WorkspaceStore } from '../../types-Dm11Zm56.js';
9
9
  import { IncomingMessage, ServerResponse } from 'node:http';
10
10
  import 'better-auth';
11
11
  import 'drizzle-orm/postgres-js';
@@ -9,13 +9,13 @@ import {
9
9
  registerRoutes,
10
10
  registerSettingsRoutes,
11
11
  registerWorkspaceRoutes
12
- } from "../../chunk-CIRY3ZLU.js";
12
+ } from "../../chunk-DSENO5O4.js";
13
13
  import {
14
14
  PostgresUserStore,
15
15
  PostgresWorkspaceStore,
16
16
  createDatabase,
17
17
  telemetryEvents
18
- } from "../../chunk-EFM5IWTK.js";
18
+ } from "../../chunk-JBEKMYST.js";
19
19
  import {
20
20
  noopTelemetry,
21
21
  safeCapture
@@ -2,6 +2,7 @@ import {
2
2
  creditGrants,
3
3
  creditPurchases,
4
4
  idempotencyKeys,
5
+ modelBudgetReservations,
5
6
  schema_exports,
6
7
  usageLedger,
7
8
  usageReservations,
@@ -12,7 +13,7 @@ import {
12
13
  workspaceRuntimes,
13
14
  workspaceSettings,
14
15
  workspaces
15
- } from "./chunk-EFM5IWTK.js";
16
+ } from "./chunk-JBEKMYST.js";
16
17
  import {
17
18
  noopTelemetry,
18
19
  safeCapture
@@ -928,6 +929,7 @@ async function deleteUserCompletely(userId, deps) {
928
929
  await tx.delete(verification_tokens).where(eq(verification_tokens.identifier, userRow.email));
929
930
  }
930
931
  await tx.delete(usageReservations).where(eq(usageReservations.userId, userId));
932
+ await tx.delete(modelBudgetReservations).where(eq(modelBudgetReservations.userId, userId));
931
933
  await tx.delete(usageLedger).where(eq(usageLedger.userId, userId));
932
934
  await tx.delete(creditGrants).where(eq(creditGrants.userId, userId));
933
935
  await tx.delete(creditPurchases).where(eq(creditPurchases.userId, userId));