@columenlabs/lumen-ai 0.1.83 → 0.1.85

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 type { AssistantAccess, AssistantChat, AssistantChatRow, AssistantMessage, AssistantProvider, AssistantStatus, DraftItem, StepItem } from '@columenlabs/lumen-ui-types/assistant';
1
+ import type { AllowanceRequest, AllowanceState, AllowanceUnit, AssistantAccess, AssistantChat, AssistantChatRow, AssistantMessage, AssistantProvider, AssistantStatus, DraftItem, StepItem } from '@columenlabs/lumen-ui-types/assistant';
2
2
  import { type ModelDef, type TaskDef, type TierDef } from './models.js';
3
3
  import { State } from './state.js';
4
4
  import { type AssistantStore, type Sealer } from './store.js';
@@ -19,6 +19,35 @@ export interface AssistantCommandDef {
19
19
  export interface Caller {
20
20
  id: string;
21
21
  admin: boolean;
22
+ /** What approvers see a request from them as: their email or name. */
23
+ label?: string;
24
+ }
25
+ /**
26
+ * How an app counts allowance on its shared keys, and how a person asks for more. The kit holds
27
+ * the ledger and the forms; the app picks the unit, the size, who approves and how they hear.
28
+ */
29
+ export interface AllowanceOptions {
30
+ /** What it is counted in; dollars by default. */
31
+ unit?: AllowanceUnit;
32
+ /** `month` (default): the admin's monthly cap plus grants made that month; `once`: one balance. */
33
+ period?: 'month' | 'once';
34
+ /**
35
+ * Units: each person's balance to start (once), or the monthly cap before an admin sets one
36
+ * (month).
37
+ */
38
+ starter?: number;
39
+ /** Amounts the Request more form offers. */
40
+ amounts?: number[];
41
+ /** Who may grant or decline requests; admins by default. */
42
+ approver?: (caller: Caller) => boolean;
43
+ /** Hears each request and each decision: tell the approvers, then the person. */
44
+ notify?: (n: AllowanceNotice) => void | Promise<void>;
45
+ }
46
+ /** A request made, or decided. */
47
+ export interface AllowanceNotice {
48
+ kind: 'requested' | 'granted' | 'declined';
49
+ request: AllowanceRequest;
50
+ unit: AllowanceUnit;
22
51
  }
23
52
  /** A change a write tool made at once, as `undo` gets it. */
24
53
  export interface AssistantWrite {
@@ -103,8 +132,10 @@ export interface AssistantOptions {
103
132
  sealer?: Sealer;
104
133
  /** The app's key of a provider when none is stored in Settings (a Worker secret). */
105
134
  appKey?: (provider: AssistantProvider) => Promise<string | null | undefined>;
106
- /** USD a person may spend a month when nobody set a cap; 10. */
135
+ /** USD a person may spend a month when nobody set a cap; 10 (or `allowance.starter`). */
107
136
  defaultCap?: number;
137
+ /** An allowance on the shared keys a person can ask to raise (Request more). */
138
+ allowance?: AllowanceOptions;
108
139
  /** Months and dates in this zone; UTC by default. */
109
140
  timeZone?: string;
110
141
  /** Chats are deleted this many days after their last message (`purge`); 30. */
@@ -145,6 +176,17 @@ export declare class Assistant {
145
176
  private access;
146
177
  private groupById;
147
178
  status(caller: Caller): Promise<AssistantStatus>;
179
+ private get unit();
180
+ private approver;
181
+ /** The person's allowance in the app's unit, and their newest request. */
182
+ allowanceState(caller: Caller): Promise<AllowanceState>;
183
+ private notify;
184
+ /** Asks the approvers for more; one open request at a time. */
185
+ requestMore(caller: Caller, amount: number, why: string): Promise<AllowanceRequest>;
186
+ /** Approvers: every request, newest first. */
187
+ allowanceRequests(caller: Caller): Promise<AllowanceRequest[]>;
188
+ /** Approvers: grants (any amount, the asked one by default) or declines an open request. */
189
+ decideRequest(caller: Caller, id: string, action: 'grant' | 'decline', amount?: number): Promise<AllowanceRequest>;
148
190
  listChats(caller: Caller): Promise<AssistantChatRow[]>;
149
191
  readChat(caller: Caller, id: string): Promise<AssistantChat | null>;
150
192
  removeChat(caller: Caller, id: string): Promise<void>;
@@ -25,6 +25,10 @@ import { DEFAULT_CAP, State, keyDocName, keyState, waitText, } from './state.js'
25
25
  import { Docs } from './store.js';
26
26
  import { ToolError, rowCount, toolName, withDeadline, } from './tools.js';
27
27
  import { scrapeqlTools, webFetchTools } from './web.js';
28
+ const DOLLARS = { one: 'dollar', many: 'dollars', usd: 1 };
29
+ const round2 = (n) => Math.round(n * 100) / 100;
30
+ /** A request may ask for at most this many times the starter or monthly cap. */
31
+ const REQUEST_MAX_TIMES = 100;
28
32
  /** A turn's failure the person can act on. */
29
33
  export class AssistantError extends Error {
30
34
  code;
@@ -149,11 +153,23 @@ export class Assistant {
149
153
  { id: 'chat', label: 'Chat', about: 'The side panel', tier: o.tiers[0].id },
150
154
  ];
151
155
  this.groups = o.groups ?? [];
156
+ const al = o.allowance;
157
+ const unit = al?.unit ?? DOLLARS;
152
158
  this.state = new State(new Docs(o.store, o.sealer), {
153
159
  timeZone: o.timeZone ?? 'UTC',
154
- defaultCap: o.defaultCap ?? DEFAULT_CAP,
160
+ defaultCap: o.defaultCap ??
161
+ (al?.starter !== undefined && al.period !== 'once' ? al.starter * unit.usd : DEFAULT_CAP),
155
162
  ...(o.appKey ? { appKey: o.appKey } : {}),
156
163
  ...(o.fetch ? { fetch: o.fetch } : {}),
164
+ ...(al
165
+ ? {
166
+ allowance: {
167
+ usd: unit.usd,
168
+ period: al.period ?? 'month',
169
+ starter: al.starter ?? DEFAULT_CAP / unit.usd,
170
+ },
171
+ }
172
+ : {}),
157
173
  });
158
174
  }
159
175
  anthropic(apiKey) {
@@ -310,7 +326,111 @@ export class Assistant {
310
326
  payer: paying?.payer ?? null,
311
327
  rows: spend.rows,
312
328
  },
329
+ ...(this.o.allowance ? { allowance: await this.allowanceState(caller) } : {}),
330
+ };
331
+ }
332
+ // ---------------------------------------------------------------- allowance
333
+ get unit() {
334
+ return this.o.allowance?.unit ?? DOLLARS;
335
+ }
336
+ approver(caller) {
337
+ return this.o.allowance?.approver ? this.o.allowance.approver(caller) : caller.admin;
338
+ }
339
+ /** The person's allowance in the app's unit, and their newest request. */
340
+ async allowanceState(caller) {
341
+ const al = this.o.allowance ?? {};
342
+ const u = this.unit;
343
+ const [budget, doc, approver] = await Promise.all([
344
+ this.state.appBudget(caller.id),
345
+ this.state.allowance(caller.id),
346
+ this.approver(caller),
347
+ ]);
348
+ const total = round2(budget.total / u.usd);
349
+ const used = round2(budget.used / u.usd);
350
+ const base = this.state.allowanceConfig?.period === 'once'
351
+ ? (this.state.allowanceConfig?.starter ?? total)
352
+ : (await this.state.appCap()) / u.usd;
353
+ const waiting = approver
354
+ ? (await this.state.requests()).rows.filter((r) => r.status === 'open').length
355
+ : undefined;
356
+ return {
357
+ unit: u,
358
+ period: al.period ?? 'month',
359
+ total,
360
+ used,
361
+ left: round2(Math.max(0, total - used)),
362
+ request: doc.request ?? null,
363
+ amounts: al.amounts ?? [...new Set([base, base * 3].map((n) => Math.max(1, Math.round(n))))],
364
+ approver,
365
+ ...(waiting !== undefined ? { waiting } : {}),
366
+ };
367
+ }
368
+ notify(kind, request) {
369
+ const n = this.o.allowance?.notify;
370
+ if (!n)
371
+ return;
372
+ // A notice that fails is logged; the request or decision stands.
373
+ return Promise.resolve(n({ kind, request, unit: this.unit })).catch((err) => console.error(JSON.stringify({ event: 'assistant_allowance_notify_failed', kind, error: String(err) })));
374
+ }
375
+ /** Asks the approvers for more; one open request at a time. */
376
+ async requestMore(caller, amount, why) {
377
+ if (!this.o.allowance)
378
+ throw new AssistantError('This app has no allowance to raise.', 'not_found', 404);
379
+ const s = await this.allowanceState(caller);
380
+ if (s.request?.status === 'open')
381
+ throw new AssistantError('Your last request is still waiting.', 'open_request', 409);
382
+ const max = Math.max(...s.amounts, 1) * REQUEST_MAX_TIMES;
383
+ if (!(amount > 0) || amount > max)
384
+ throw new AssistantError(`Ask for 1 to ${max} ${this.unit.many}.`, 'bad_amount');
385
+ const request = {
386
+ id: `ar_${crypto.randomUUID().replace(/-/g, '').slice(0, 16)}`,
387
+ person: caller.id,
388
+ who: caller.label ?? caller.id,
389
+ amount: round2(amount),
390
+ why: why.slice(0, 500),
391
+ at: Date.now(),
392
+ status: 'open',
313
393
  };
394
+ await this.state.setAllowance(caller.id, (d) => ({ ...d, request }));
395
+ await this.state.putRequest(request);
396
+ await this.notify('requested', request);
397
+ return request;
398
+ }
399
+ /** Approvers: every request, newest first. */
400
+ async allowanceRequests(caller) {
401
+ if (!(await this.approver(caller)))
402
+ throw new AssistantError('Only an approver can see requests.', 'forbidden', 403);
403
+ return (await this.state.requests()).rows;
404
+ }
405
+ /** Approvers: grants (any amount, the asked one by default) or declines an open request. */
406
+ async decideRequest(caller, id, action, amount) {
407
+ if (!(await this.approver(caller)))
408
+ throw new AssistantError('Only an approver can decide requests.', 'forbidden', 403);
409
+ const r = (await this.state.requests()).rows.find((x) => x.id === id);
410
+ if (!r)
411
+ throw new AssistantError('That request is gone.', 'not_found', 404);
412
+ if (r.status !== 'open')
413
+ throw new AssistantError('That request was already decided.', 'decided', 409);
414
+ const granted = action === 'grant' ? round2(amount ?? r.amount) : undefined;
415
+ if (granted !== undefined && !(granted > 0))
416
+ throw new AssistantError(`Grant more than 0 ${this.unit.many}.`, 'bad_amount');
417
+ const done = {
418
+ ...r,
419
+ status: action === 'grant' ? 'granted' : 'declined',
420
+ ...(granted !== undefined ? { granted } : {}),
421
+ decided_at: Date.now(),
422
+ decided_by: caller.label ?? caller.id,
423
+ };
424
+ await this.state.setAllowance(r.person, (d) => ({
425
+ ...d,
426
+ grants: granted !== undefined
427
+ ? [...d.grants, { at: Date.now(), amount: granted, by: caller.id, request: r.id }]
428
+ : d.grants,
429
+ ...(d.request?.id === r.id ? { request: done } : {}),
430
+ }));
431
+ await this.state.putRequest(done);
432
+ await this.notify(action === 'grant' ? 'granted' : 'declined', done);
433
+ return done;
314
434
  }
315
435
  // ---------------------------------------------------------------- chats
316
436
  async listChats(caller) {
@@ -475,7 +595,7 @@ export class Assistant {
475
595
  const { paying, wait } = await this.state.paying(caller.id, main.model.provider);
476
596
  if (!paying) {
477
597
  const code = wait ?? 'not_connected';
478
- const text = waitText(code, main.model.provider);
598
+ const text = waitText(code, main.model.provider, !!this.o.allowance);
479
599
  doc.items.push({ kind: 'error', text, code });
480
600
  await this.writeChat(caller, chatId, doc);
481
601
  emit('error', { message: text, code });
@@ -541,7 +661,7 @@ export class Assistant {
541
661
  }
542
662
  const page = this.cardNews(doc) +
543
663
  (req.refs?.length
544
- ? `The person points at: ${JSON.stringify(req.refs).slice(0, 16000)}\n\n`
664
+ ? `The person points at these parts: they are what "this" means, and when the person asks for a change they are the only parts to change. Read anything else you need (the rest of the page, other records, your tools) for context, but leave it as it is unless they ask.\n${JSON.stringify(req.refs).slice(0, 16000)}\n\n`
545
665
  : '') +
546
666
  (req.context
547
667
  ? `Page: ${req.context.label}${req.context.note === undefined ? '' : `\n${JSON.stringify(req.context.note).slice(0, 4000)}`}\n\n`
@@ -667,6 +787,8 @@ export class Assistant {
667
787
  'A tool marked DRAFT ONLY makes a card the person saves or dismisses; it changes nothing by itself. Say what the card will do; never claim it is done. The next message tells you how each card ended.',
668
788
  'A tool marked WRITES AT ONCE makes its change when you call it; the person can undo it from the chat. Change only what they asked for, and say what you changed.',
669
789
  ];
790
+ if (main)
791
+ lines.push(`You run on the ${main.tier.label} tier: ${main.model.label}, from ${PROVIDERS[main.model.provider].label}. Say so when asked which tier or model you are.`);
670
792
  if (!fns.size)
671
793
  lines.push('No tools are on now: the person can turn them on in Settings ▸ Tools.');
672
794
  if (failed)
@@ -19,6 +19,9 @@
19
19
  * GET /chats, GET|DELETE /chats/:id
20
20
  * POST /chats/:id/drafts/:draft {action: save | dismiss}
21
21
  * POST /chats/:id/writes/:step/undo
22
+ * POST /allowance/request {amount, why} asks for more allowance (one open at a time)
23
+ * GET /allowance/requests approvers: every request
24
+ * POST /allowance/requests/:id {action: grant | decline, amount?} approvers
22
25
  *
23
26
  * Writes must be same-origin JSON: a signed-in browser session is a cookie, so a foreign page's
24
27
  * form or fetch is refused before it reaches a rule. Keys and tokens are write-only: no route
@@ -300,6 +303,23 @@ export function assistantRoutes(a, o) {
300
303
  }
301
304
  case 'GET /chats':
302
305
  return json(await a.listChats(caller));
306
+ case 'POST /allowance/request': {
307
+ const b = await body(req);
308
+ const amount = typeof b.amount === 'number' && Number.isFinite(b.amount) ? b.amount : 0;
309
+ const why = typeof b.why === 'string' ? b.why.trim() : '';
310
+ await a.requestMore(caller, amount, why);
311
+ return status();
312
+ }
313
+ case 'GET /allowance/requests':
314
+ return json(await a.allowanceRequests(caller));
315
+ }
316
+ if (parts[0] === 'allowance' && parts[1] === 'requests' && parts[2] && m === 'POST') {
317
+ const b = await body(req);
318
+ const action = b.action;
319
+ if (action !== 'grant' && action !== 'decline')
320
+ return fail('action is grant or decline.');
321
+ const amount = typeof b.amount === 'number' && Number.isFinite(b.amount) ? b.amount : undefined;
322
+ return json(await a.decideRequest(caller, parts[2], action, amount));
303
323
  }
304
324
  if (parts[0] === 'chats' && parts[1]) {
305
325
  const id = parts[1];
@@ -11,12 +11,15 @@
11
11
  * plan sign-in, refreshed before it runs out
12
12
  * signin:<p> (a person's) the app's sign-in registration for them, kept across sign-outs
13
13
  * spend:YYYY-MM what each tier cost them this month, by payer
14
+ * allowance (a person's) grants of more allowance, their newest request, and (when it is
15
+ * once for good) what they spent on the app's keys in all
16
+ * allowance_requests (the app's) every request, newest first, for approvers
14
17
  *
15
18
  * Who pays: the person's pick (the app's keys by default) while it has a key for the provider the
16
19
  * tier runs on and money left this month, else the other; with neither the assistant waits and says
17
20
  * why. A cap covers every provider of its payer.
18
21
  */
19
- import type { AssistantAccess, AssistantEffort, AssistantPayer, AssistantProvider, AssistantWait, HandOffPolicy, UsagePart } from '@columenlabs/lumen-ui-types/assistant';
22
+ import type { AllowanceRequest, AssistantAccess, AssistantEffort, AssistantPayer, AssistantProvider, AssistantWait, HandOffPolicy, UsagePart } from '@columenlabs/lumen-ui-types/assistant';
20
23
  import { type ChatgptTokens } from './chatgpt.js';
21
24
  import { type Docs } from './store.js';
22
25
  export interface KeyTest {
@@ -60,6 +63,29 @@ export interface PersonDoc {
60
63
  /** USD a month on their own keys. */
61
64
  own_cap?: number;
62
65
  }
66
+ /** How the app counts allowance on its shared keys (`createAssistant` `allowance`). */
67
+ export interface AllowanceConfig {
68
+ /** USD one unit buys. */
69
+ usd: number;
70
+ /** `month`: the cap resets each month, grants count in their month; `once`: one balance for good. */
71
+ period: 'month' | 'once';
72
+ /** Units each person starts with (once); a month's cap is the admin's cap in Settings. */
73
+ starter: number;
74
+ }
75
+ export interface AllowanceDoc {
76
+ grants: {
77
+ at: number;
78
+ amount: number;
79
+ by: string;
80
+ request?: string;
81
+ }[];
82
+ /** USD spent on the app's keys in all (counted only when the allowance is once for good). */
83
+ spent_usd?: number;
84
+ request?: AllowanceRequest;
85
+ }
86
+ export interface RequestsDoc {
87
+ rows: AllowanceRequest[];
88
+ }
63
89
  export type SpendRow = UsagePart & {
64
90
  payer: AssistantPayer;
65
91
  };
@@ -99,7 +125,22 @@ export declare class State {
99
125
  defaultCap: number;
100
126
  appKey?: (provider: AssistantProvider) => Promise<string | null | undefined>;
101
127
  fetch?: typeof fetch;
128
+ allowance?: AllowanceConfig;
102
129
  });
130
+ get allowanceConfig(): AllowanceConfig | undefined;
131
+ allowance(id: string): Promise<AllowanceDoc>;
132
+ setAllowance(id: string, change: (d: AllowanceDoc) => AllowanceDoc): Promise<AllowanceDoc | null>;
133
+ requests(): Promise<RequestsDoc>;
134
+ /** Adds or replaces one request in the approvers' list. */
135
+ putRequest(r: AllowanceRequest): Promise<RequestsDoc | null>;
136
+ /**
137
+ * USD the person has on the app's keys for the period and USD used: the admin's monthly cap plus
138
+ * this month's grants, or (once for good) the starter and every grant against all spend.
139
+ */
140
+ appBudget(id: string, spentThisMonth?: number): Promise<{
141
+ total: number;
142
+ used: number;
143
+ }>;
103
144
  /** USD a month when nobody set a cap. */
104
145
  get defaultCap(): number;
105
146
  month(): string;
@@ -137,4 +178,4 @@ export declare class State {
137
178
  wait: AssistantWait | null;
138
179
  }>;
139
180
  }
140
- export declare function waitText(wait: AssistantWait, provider: AssistantProvider): string;
181
+ export declare function waitText(wait: AssistantWait, provider: AssistantProvider, canRequest?: boolean): string;
@@ -3,6 +3,8 @@ import { PROVIDERS, keyHint } from './models.js';
3
3
  import { APP_SCOPE } from './store.js';
4
4
  /** Where a provider's key is kept: `key` for Anthropic, `key:<provider>` for the rest. */
5
5
  export const keyDocName = (provider) => provider === 'anthropic' ? 'key' : `key:${provider}`;
6
+ /** Requests kept for approvers: the newest this many. */
7
+ const REQUESTS_KEPT = 300;
6
8
  /** USD a month when nobody set a cap. */
7
9
  export const DEFAULT_CAP = 10;
8
10
  /** `2026-10` in the app's time zone. */
@@ -30,6 +32,47 @@ export class State {
30
32
  this.docs = docs;
31
33
  this.o = o;
32
34
  }
35
+ get allowanceConfig() {
36
+ return this.o.allowance;
37
+ }
38
+ allowance(id) {
39
+ return this.docs.read(id, 'allowance').then((d) => d ?? { grants: [] });
40
+ }
41
+ setAllowance(id, change) {
42
+ return this.docs.update(id, 'allowance', (d) => change(d ?? { grants: [] }));
43
+ }
44
+ requests() {
45
+ return this.docs
46
+ .read(APP_SCOPE, 'allowance_requests')
47
+ .then((d) => d ?? { rows: [] });
48
+ }
49
+ /** Adds or replaces one request in the approvers' list. */
50
+ putRequest(r) {
51
+ return this.docs.update(APP_SCOPE, 'allowance_requests', (d) => ({
52
+ rows: [r, ...(d?.rows ?? []).filter((x) => x.id !== r.id)].slice(0, REQUESTS_KEPT),
53
+ }));
54
+ }
55
+ /**
56
+ * USD the person has on the app's keys for the period and USD used: the admin's monthly cap plus
57
+ * this month's grants, or (once for good) the starter and every grant against all spend.
58
+ */
59
+ async appBudget(id, spentThisMonth) {
60
+ const al = this.o.allowance;
61
+ const used = spentThisMonth ??
62
+ (await this.spend(id)).rows.filter((r) => r.payer === 'app').reduce((a, r) => a + r.usd, 0);
63
+ if (!al)
64
+ return { total: await this.appCap(), used };
65
+ const d = await this.allowance(id);
66
+ if (al.period === 'once') {
67
+ const units = al.starter + d.grants.reduce((a, g) => a + g.amount, 0);
68
+ return { total: units * al.usd, used: d.spent_usd ?? 0 };
69
+ }
70
+ const month = this.month();
71
+ const granted = d.grants
72
+ .filter((g) => monthIn(this.o.timeZone, new Date(g.at)) === month)
73
+ .reduce((a, g) => a + g.amount, 0);
74
+ return { total: (await this.appCap()) + granted * al.usd, used };
75
+ }
33
76
  /** USD a month when nobody set a cap. */
34
77
  get defaultCap() {
35
78
  return this.o.defaultCap;
@@ -133,6 +176,11 @@ export class State {
133
176
  }
134
177
  return { rows };
135
178
  });
179
+ if (payer === 'app' && this.o.allowance?.period === 'once') {
180
+ const usd = parts.reduce((a, p) => a + p.usd, 0);
181
+ if (usd > 0)
182
+ await this.setAllowance(id, (d) => ({ ...d, spent_usd: (d.spent_usd ?? 0) + usd }));
183
+ }
136
184
  }
137
185
  /** The payer's key of each provider, those set, and which are plan sign-ins. */
138
186
  async keysOf(id, payer) {
@@ -153,15 +201,15 @@ export class State {
153
201
  }
154
202
  /** Who pays the person's next answer on `provider`'s model, or why nobody can. */
155
203
  async paying(id, provider) {
156
- const [person, appKeys, ownKeys, spend, appCap, ownCap] = await Promise.all([
204
+ const [person, appKeys, ownKeys, spend, ownCap] = await Promise.all([
157
205
  this.person(id),
158
206
  this.keysOf(id, 'app'),
159
207
  this.keysOf(id, 'own'),
160
208
  this.spend(id),
161
- this.appCap(),
162
209
  this.ownCap(id),
163
210
  ]);
164
211
  const spent = (p) => spend.rows.filter((r) => r.payer === p).reduce((a, r) => a + r.usd, 0);
212
+ const budget = await this.appBudget(id, spent('app'));
165
213
  const app = appKeys.keys[provider];
166
214
  const own = ownKeys.keys[provider];
167
215
  const sources = {
@@ -170,8 +218,8 @@ export class State {
170
218
  payer: 'app',
171
219
  apiKey: app,
172
220
  ...appKeys,
173
- cap: appCap,
174
- left: appCap - spent('app'),
221
+ cap: budget.total,
222
+ left: budget.total - budget.used,
175
223
  }
176
224
  : null,
177
225
  own: own
@@ -198,11 +246,13 @@ export class State {
198
246
  return { paying: null, wait: capped === 'own' ? 'own_cap' : 'app_cap' };
199
247
  }
200
248
  }
201
- export function waitText(wait, provider) {
249
+ export function waitText(wait, provider, canRequest = false) {
202
250
  const label = PROVIDERS[provider].label;
203
251
  if (wait === 'not_connected')
204
252
  return `No ${label} key is set: add one in Settings ▸ Model providers, or pick another model in Settings ▸ Models.`;
205
253
  if (wait === 'app_cap')
206
- return "You reached this month's cap on the shared keys. Add your own key, or wait for next month.";
254
+ return canRequest
255
+ ? 'You used your allowance on the shared keys. Request more in Settings, or add your own key.'
256
+ : "You reached this month's cap on the shared keys. Add your own key, or wait for next month.";
207
257
  return 'Your own keys reached their monthly cap: raise it in Settings ▸ Model providers.';
208
258
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ai",
3
- "version": "0.1.83",
3
+ "version": "0.1.85",
4
4
  "description": "Lumen's assistant and agent parts: a chat assistant's server (Claude tiers, tools over MCP, the web, hand off, spend caps), serving an MCP server from a Worker, read-only tool sets and metered tool calls.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,8 +41,8 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@anthropic-ai/sdk": "0.133.0",
44
- "@columenlabs/lumen-ops": "0.1.83",
45
- "@columenlabs/lumen-ui-types": "0.1.83",
44
+ "@columenlabs/lumen-ops": "0.1.85",
45
+ "@columenlabs/lumen-ui-types": "0.1.85",
46
46
  "openai": "7.32.0",
47
47
  "zod": "^4.3.6"
48
48
  },