@north-light/api 0.3.550 → 0.3.552

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,5 @@
1
1
  import { ApiError } from './errors.js';
2
+ import type { ChargeListDTO, SpendLimitListDTO, SpendLimitRemovedDTO, SpendLimitSetRequest, LimitDTO } from './dto/charges.js';
2
3
  import type { AppLimitsChangeDTO, LimitsDTO, SetAppLimitsRequest } from './dto/limits.js';
3
4
  import type { DaemonAdmitDTO, DaemonRestartDTO, DueDTO, HealthDTO, ReadyDTO, MigrateStateDTO, MigrateStateRequest, StatusDTO } from './dto/health.js';
4
5
  import type { BashJobStatusDTO, BashJobStopResultDTO } from './dto/bash-jobs.js';
@@ -230,6 +231,16 @@ export declare class CrtrClient {
230
231
  showLimits(): Promise<LimitsDTO>;
231
232
  setAppLimits(app: string, req: SetAppLimitsRequest): Promise<AppLimitsChangeDTO>;
232
233
  removeAppLimits(app: string, fields: Array<'memory' | 'disk'>): Promise<AppLimitsChangeDTO>;
234
+ /** One page of this runtime's charge rows for a month, or the rows under a reference, with the month's totals; pass `next` back as `after`. */
235
+ listCharges(query?: {
236
+ month?: string;
237
+ under?: string;
238
+ after?: string;
239
+ limit?: number;
240
+ }): Promise<ChargeListDTO>;
241
+ listSpendLimits(): Promise<SpendLimitListDTO>;
242
+ setSpendLimit(id: string, req: SpendLimitSetRequest): Promise<LimitDTO>;
243
+ removeSpendLimit(id: string): Promise<SpendLimitRemovedDTO>;
233
244
  /** Every space on the runtime with its size. */
234
245
  listSpaces(): Promise<SpaceListDTO>;
235
246
  subscribe(id: string, req: SubscribeRequest): Promise<SubscriptionDTO>;
@@ -402,6 +402,19 @@ export class CrtrClient {
402
402
  removeAppLimits(app, fields) {
403
403
  return this.request('DELETE', withQuery(routes.limitsApp(app), { fields: fields.join(',') }));
404
404
  }
405
+ /** One page of this runtime's charge rows for a month, or the rows under a reference, with the month's totals; pass `next` back as `after`. */
406
+ listCharges(query = {}) {
407
+ return this.request('GET', withQuery(routes.charges(), query));
408
+ }
409
+ listSpendLimits() {
410
+ return this.request('GET', routes.spendLimits());
411
+ }
412
+ setSpendLimit(id, req) {
413
+ return this.request('PUT', routes.spendLimit(id), req);
414
+ }
415
+ removeSpendLimit(id) {
416
+ return this.request('DELETE', routes.spendLimit(id));
417
+ }
405
418
  /** Every space on the runtime with its size. */
406
419
  listSpaces() {
407
420
  return this.request('GET', routes.spaces());
@@ -1,5 +1,5 @@
1
1
  /** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
2
- export declare const CONTRACT_LEVEL = "1.31";
2
+ export declare const CONTRACT_LEVEL = "1.32";
3
3
  /** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
4
4
  export declare const REQUIRED_CONTRACT_HEADER = "Crouter-Required-Contract";
5
5
  /** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
@@ -12,7 +12,7 @@
12
12
  //
13
13
  // PURITY (spec §3.1): Node built-ins + `src/api/*` only.
14
14
  /** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
15
- export const CONTRACT_LEVEL = '1.31';
15
+ export const CONTRACT_LEVEL = '1.32';
16
16
  /** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
17
17
  export const REQUIRED_CONTRACT_HEADER = 'Crouter-Required-Contract';
18
18
  /** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
@@ -0,0 +1,122 @@
1
+ export type ChargeKindDTO = 'model' | 'tool' | 'sandbox' | 'job' | 'storage' | 'take';
2
+ export type FundingDTO = 'bill' | 'subscription' | 'key' | 'endpoint' | 'take' | 'legacy';
3
+ export type LimitSourceDTO = 'grant_sync' | 'person' | 'operator';
4
+ /** One charge row. Every field of the ledger's `charges` table. */
5
+ export interface ChargeDTO {
6
+ /** `receipt:<provider>:<receipt id>` for a tool charge; `legacy:…` for a migrated aggregate; otherwise a fresh id. */
7
+ id: string;
8
+ /** The `Request-Id` of the call that spent. Null on legacy rows. */
9
+ request: string | null;
10
+ at: string;
11
+ /** UTC `YYYY-MM`. */
12
+ month: string;
13
+ kind: ChargeKindDTO;
14
+ /** The model that answered, or `<provider id>:<tool path>` for a tool. */
15
+ what: string;
16
+ /** `{input, output, cache_read, cache_write}` tokens, or `{units, unit}` from a receipt; null on a legacy tool row. */
17
+ units: Record<string, unknown> | null;
18
+ /** List-price dollars; null when the model has no list price; 0 on a refused row. */
19
+ usd: number | null;
20
+ /** The principal charged: `user:<id>` or `app:<id>`. */
21
+ payer: string;
22
+ funding: FundingDTO;
23
+ /** The person the work is for. */
24
+ sub: string | null;
25
+ /** The app the charge is attributed to. */
26
+ grantee: string;
27
+ node: string | null;
28
+ run: string | null;
29
+ /** The vendor or provider app the money goes to. */
30
+ provider: string | null;
31
+ /** `message:<id>`, `request:<id>`, `cron_run:<id>`, or null for a root. */
32
+ caused_by: string | null;
33
+ /** The vendor's request id for a model row; the receipt id for a tool row. */
34
+ vendor_ref: string | null;
35
+ /** Set when the check refused this call: the limit id and reason. */
36
+ refused: {
37
+ limit: string;
38
+ reason: string;
39
+ } | null;
40
+ }
41
+ /** One limit row. A null filter means any. */
42
+ export interface LimitDTO {
43
+ id: string;
44
+ payer: string;
45
+ funding: FundingDTO | null;
46
+ grantee: string | null;
47
+ provider: string | null;
48
+ sub: string | null;
49
+ /** 0 is an emergency stop. */
50
+ usd_per_month: number;
51
+ source: LimitSourceDTO;
52
+ updated_at: string;
53
+ }
54
+ /** One month's dollars from the rollup, with the limit rows. */
55
+ export interface ChargeTotalsDTO {
56
+ month: string;
57
+ by_payer: Array<{
58
+ payer: string;
59
+ usd: number;
60
+ }>;
61
+ by_funding: Array<{
62
+ payer: string;
63
+ funding: string;
64
+ usd: number;
65
+ }>;
66
+ limits: LimitDTO[];
67
+ }
68
+ /** `GET /v1/charges?month=&under=&after=&limit=`: this runtime's rows in `(at, id)` order, newest last. An app sees only rows whose grantee it is. */
69
+ export interface ChargeListDTO {
70
+ /** The month the rows are filtered to: the requested one, else the current month. Absent when `under` is given without `month`, because the chain spans months. */
71
+ month?: string;
72
+ /** At most `limit` rows (default 1,000, at most 10,000). */
73
+ charges: ChargeDTO[];
74
+ /** Pass as `after` to read the next page; null when this page is the last. */
75
+ next: string | null;
76
+ /** Month totals by payer and funding with the matching limits, over every row of the month rather than this page. Present for the person and a terminal node; absent for an app and when `under` is given. */
77
+ totals?: ChargeTotalsDTO;
78
+ }
79
+ /** `GET /v1/spend-limits`. */
80
+ export interface SpendLimitListDTO {
81
+ limits: LimitDTO[];
82
+ }
83
+ /** `PUT /v1/spend-limits/:id`: the id must start `person:`. Writes a limit row with source `person`; an existing row of that id is replaced. */
84
+ export interface SpendLimitSetRequest {
85
+ /** `user:<id>` (the person) or `app:<id>`. */
86
+ payer: string;
87
+ usd_per_month: number;
88
+ funding?: FundingDTO | null;
89
+ grantee?: string | null;
90
+ provider?: string | null;
91
+ sub?: string | null;
92
+ }
93
+ /** `DELETE /v1/spend-limits/:id`: clears a person-set limit row. */
94
+ export interface SpendLimitRemovedDTO {
95
+ id: string;
96
+ removed: true;
97
+ }
98
+ /** The `details` of a `402 cap_exhausted` refusal. */
99
+ export interface CapExhaustedDetails {
100
+ limit: {
101
+ id: string;
102
+ payer: string;
103
+ filters: {
104
+ funding: FundingDTO | null;
105
+ grantee: string | null;
106
+ provider: string | null;
107
+ sub: string | null;
108
+ };
109
+ usd_per_month: number;
110
+ source: LimitSourceDTO;
111
+ };
112
+ /** The dollars counted against the limit this month. */
113
+ usd: number;
114
+ /** When the count starts over. */
115
+ reset_at: string;
116
+ /** What the person can do: raise the app's cap, or clear their own limit; absent for an operator limit. */
117
+ user_action?: 'raise_cap' | 'clear_limit';
118
+ /** @deprecated Use `limit.usd_per_month`. */
119
+ spend_cap: number;
120
+ /** @deprecated Use `usd`. */
121
+ usd_equivalent: number;
122
+ }
@@ -0,0 +1,3 @@
1
+ // The money ledger's read and limit surfaces: `charges.list`, `spend_limits.list`, `spend_limits.set`, `spend_limits.remove`.
2
+ // (`limits.*` and `/v1/limits` are the per-app memory and disk caps; these are dollar limits.)
3
+ export {};
@@ -6,12 +6,12 @@ export interface SpendUsageModelRowDTO {
6
6
  /** List-price cost; null when the served model has no list price (an app-registered endpoint, or an unpriced model). */
7
7
  usd_equivalent: number | null;
8
8
  }
9
- /** `usage.get`: the caller's grantee's month-to-date spend (daemon operations, `usage.get`). */
9
+ /** `usage.get` (deprecated in favour of `charges.list`): the caller's grantee's month-to-date spend (daemon operations, `usage.get`). */
10
10
  export interface SpendUsageDTO {
11
11
  grantee: string;
12
12
  /** UTC calendar month, `YYYY-MM`. */
13
13
  month: string;
14
- /** User-paid spend this month: every model completion and provider receipt. Receipts have no `by_model` row, so this can exceed the sum of `by_model`. */
14
+ /** User-paid spend this month for this grantee: every model completion and every priced tool call. Tool calls have no `by_model` row, so this can exceed the sum of `by_model`. */
15
15
  usd_equivalent: number;
16
16
  /** User-paid model completions, one row per served model. */
17
17
  by_model: SpendUsageModelRowDTO[];
@@ -20,7 +20,7 @@ export interface SpendUsageDTO {
20
20
  usd_equivalent: number;
21
21
  by_model: SpendUsageModelRowDTO[];
22
22
  };
23
- /** The monthly cap on user-paid spend; once `usd_equivalent` reaches it, user-paid model calls answer `cap_exhausted` until `reset_at`. Null when uncapped. */
23
+ /** The monthly cap on user-paid spend, which now counts priced tool calls as well as model calls; once `usd_equivalent` reaches it, calls billed to the user answer `cap_exhausted` until `reset_at`. Null when no limit row exists. */
24
24
  spend_cap: number | null;
25
25
  /** 00:00Z on the first day of the next UTC month, when the count starts over. */
26
26
  reset_at: string;
@@ -17,6 +17,7 @@ export * from './dto/health.js';
17
17
  export * from './dto/limits.js';
18
18
  export * from './dto/grants.js';
19
19
  export * from './dto/usage.js';
20
+ export * from './dto/charges.js';
20
21
  export * from './dto/nodes.js';
21
22
  export * from './dto/node-outcomes.js';
22
23
  export * from './dto/node-events.js';
package/dist/api/index.js CHANGED
@@ -14,6 +14,7 @@ export * from './dto/health.js';
14
14
  export * from './dto/limits.js';
15
15
  export * from './dto/grants.js';
16
16
  export * from './dto/usage.js';
17
+ export * from './dto/charges.js';
17
18
  export * from './dto/nodes.js';
18
19
  export * from './dto/node-outcomes.js';
19
20
  export * from './dto/node-events.js';
@@ -74,6 +74,9 @@ export declare const routes: {
74
74
  readonly spaces: () => string;
75
75
  readonly limits: () => string;
76
76
  readonly limitsApp: (app: string) => string;
77
+ readonly charges: () => string;
78
+ readonly spendLimits: () => string;
79
+ readonly spendLimit: (id: string) => string;
77
80
  readonly nodeAttach: (id: string) => string;
78
81
  readonly focuses: () => string;
79
82
  readonly focusByNode: () => string;
@@ -92,6 +92,9 @@ export const routes = {
92
92
  spaces: () => `${V}/spaces`,
93
93
  limits: () => `${V}/limits`,
94
94
  limitsApp: (app) => `${V}/limits/apps/${encodeURIComponent(app)}`,
95
+ charges: () => `${V}/charges`,
96
+ spendLimits: () => `${V}/spend-limits`,
97
+ spendLimit: (id) => `${V}/spend-limits/${encodeURIComponent(id)}`,
95
98
  nodeAttach: (id) => `${V}/nodes/${id}/attach`,
96
99
  // Focuses (viewer registry — the canvas.db `focuses` table)
97
100
  focuses: () => `${V}/focuses`,
@@ -850,6 +850,10 @@ function mergeConfig(partial) {
850
850
  if (paths?.user_files !== undefined && (typeof paths.user_files !== 'string' || !paths.user_files.startsWith('/'))) {
851
851
  throw new Error('paths.user_files must be an absolute path');
852
852
  }
853
+ const ledger = partial.ledger;
854
+ if (ledger?.take_pct !== undefined && (typeof ledger.take_pct !== 'number' || !Number.isFinite(ledger.take_pct) || ledger.take_pct < 0 || ledger.take_pct > 100)) {
855
+ throw new Error('ledger.take_pct must be a number from 0 to 100');
856
+ }
853
857
  const store = partial.store;
854
858
  const runtime = partial.runtime;
855
859
  if (runtime?.person_uids !== undefined && (!Array.isArray(runtime.person_uids)
@@ -859,7 +863,7 @@ function mergeConfig(partial) {
859
863
  const spawnEnv = mergeSpawnEnv(partial.spawnEnv, defaults.spawnEnv?.allow);
860
864
  const bin = normalizeBin(partial.bin);
861
865
  const humanActions = normalizeHumanActions(partial.humanActions);
862
- return { bin, humanActions, schema_version, marketplaces, plugins, auto_update, max_panes_per_window, auto_open_child_viewers, brokerThresholds, lifecycle, completion_bell, working_gerunds, whip_mode, page_components, page_surface, whip_messages, whip_message_mode, mouse_mode_default, live_cycles, condensed_history, bash_tool_purpose, fold_finished_tools, summarize_tool_calls, detailed_tool_recaps, document_port, consolidate_replies, keybindings, tmux_passthrough, modelLadders, providerOptions, ...(Object.keys(modelRoutes).length > 0 ? { modelRoutes } : {}), ...(Object.keys(routes.legacy).length > 0 ? { legacyModelRoutes: routes.legacy } : {}), ...(modelRouting !== undefined ? { modelRouting } : {}), kinds, remoteCanvas, api, ...(paths ? { paths } : {}), ...(store ? { store } : {}), ...(runtime ? { runtime } : {}), spawnEnv };
866
+ return { bin, humanActions, schema_version, marketplaces, plugins, auto_update, max_panes_per_window, auto_open_child_viewers, brokerThresholds, lifecycle, completion_bell, working_gerunds, whip_mode, page_components, page_surface, whip_messages, whip_message_mode, mouse_mode_default, live_cycles, condensed_history, bash_tool_purpose, fold_finished_tools, summarize_tool_calls, detailed_tool_recaps, document_port, consolidate_replies, keybindings, tmux_passthrough, modelLadders, providerOptions, ...(Object.keys(modelRoutes).length > 0 ? { modelRoutes } : {}), ...(Object.keys(routes.legacy).length > 0 ? { legacyModelRoutes: routes.legacy } : {}), ...(modelRouting !== undefined ? { modelRouting } : {}), kinds, remoteCanvas, api, ...(paths ? { paths } : {}), ...(ledger ? { ledger } : {}), ...(store ? { store } : {}), ...(runtime ? { runtime } : {}), spawnEnv };
863
867
  }
864
868
  /** Raw (un-defaulted) partial config for one scope, or null if the scope has
865
869
  * no root or no config.json. Used by `readMergedLaunchConfig` to layer
@@ -377,6 +377,10 @@ export declare const CONFIG_SETTINGS_COVERAGE: {
377
377
  surface: "specialized";
378
378
  owner: string;
379
379
  };
380
+ ledger: {
381
+ surface: "specialized";
382
+ owner: string;
383
+ };
380
384
  store: {
381
385
  surface: "specialized";
382
386
  owner: string;
@@ -469,6 +469,7 @@ export const CONFIG_SETTINGS_COVERAGE = {
469
469
  remoteCanvas: { surface: 'specialized', owner: 'canvas config commands' },
470
470
  api: { surface: 'specialized', owner: 'sys connect' },
471
471
  paths: { surface: 'specialized', owner: 'runtime layout' },
472
+ ledger: { surface: 'specialized', owner: 'money ledger (take_pct)' },
472
473
  store: { surface: 'specialized', owner: 'daemon conversation store' },
473
474
  runtime: { surface: 'specialized', owner: 'app-facing listener' },
474
475
  spawnEnv: { surface: 'specialized', owner: 'spawn environment allow-list' },
package/dist/types.d.ts CHANGED
@@ -402,6 +402,10 @@ export interface ScopeConfig {
402
402
  paths?: {
403
403
  user_files?: string;
404
404
  };
405
+ /** The money ledger. `take_pct` (0-100, default 0) is Northlight's take on each priced model or tool charge; at 0 no take rows are written. */
406
+ ledger?: {
407
+ take_pct?: number;
408
+ };
405
409
  /** Conversation store configuration for the daemon. */
406
410
  store?: {
407
411
  root?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/api",
3
- "version": "0.3.550",
3
+ "version": "0.3.552",
4
4
  "description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, the CrtrClient, and the command-plugin manifest format. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/api/index.js",