@columenlabs/lumen-ai 0.1.208 → 0.1.210
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/README.md +4 -2
- package/dist/assistant/engine.d.ts +20 -1
- package/dist/assistant/engine.js +33 -7
- package/dist/assistant/models.js +1 -1
- package/dist/assistant/routes.js +8 -0
- package/dist/assistant/state.d.ts +29 -1
- package/dist/assistant/state.js +74 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -103,7 +103,8 @@ app.all('/api/assistant/*', (c) => routes(c.req.raw));
|
|
|
103
103
|
- **Hand off and advice.** The `escalate` tool hands one task to a higher tier (the person sets next, any or only when asked); Anthropic's advisor tool lets a higher tier send a plan, at most two an answer.
|
|
104
104
|
- **Chats** run on the agent (`./agent`, `LumenAgent`, below): the app routes `POST /chat` and each chat's reads, cards and Undo to its own agent, and `assistantRoutes` answers `POST /chat` with 410 `moved`. Chats saved by the old turn loop stay readable (`GET /chats`, `GET|DELETE /chats/:id`) until `assistant.purge()` drops them 30 days after their last message.
|
|
105
105
|
- **Screen moves**: while the person's Settings ▸ Screen is Move (the default), the panel sends its page map (`AssistantMessage.screen`: routes, nav pages, Settings sections, cards on screen, rows to open) and `LumenAgent` gives the model a `screen` tool (`./agent` `screen.ts`). Each call's moves (go, settings, chart, open, point) are checked against the map, kept in the chat and streamed as a `screen` event; the panel runs them through `AssistantScreenProvider` (mounted by `LumenShell`) with an Undo chip, and their outcomes go back with the next message (`outcomes`). Nothing is saved by a move.
|
|
106
|
-
- **Background work** an app runs for a person (a score, a summary) is `assistant.complete(caller, { task, system, prompt, images?, maxTokens })`: one answer on the task's tier (`tasks`, so the person picks its model in Settings ▸ Models), no chat and no tools (`images`, base64 with their media type, are read with the prompt: a scanned page, a screenshot), billed to their payer and counted in their spend and allowance like a chat turn. It throws `AssistantError` `not_connected`, `app_cap` or `own_cap` when nobody can pay, so the caller waits and tries later.
|
|
106
|
+
- **Background work** an app runs for a person (a score, a summary) is `assistant.complete(caller, { task, system, prompt, images?, maxTokens })`: one answer on the task's tier (`tasks`, so the person picks its model in Settings ▸ Models), no chat and no tools (`images`, base64 with their media type, are read with the prompt: a scanned page, a screenshot), billed to their payer and counted in their spend and allowance like a chat turn. `maxTokens` (default 1,000) goes up to `COMPLETE_MAX_TOKENS` (32,000, thinking included); the answer is streamed, so a long one keeps its connection, and `cut: true` says it stopped at the cap before it ended (the app can ask again, shorter, or continue). It throws `AssistantError` `not_connected`, `app_cap` or `own_cap` when nobody can pay, so the caller waits and tries later. It returns the answer's `usd`, `model` and `usage` (tokens in, cache reads, out, per model). `run: { id, title }` makes the calls that share an `id` one line of the person's usage history (every round of one rebuild); without it a task's calls that day share a line.
|
|
107
|
+
- **Usage history.** Each chat (`agentTurn` with `chat`) and each run of background work is one line of the person's month (`usage:YYYY-MM`, the newest 300): its title, first and last use, tokens, cost and its parts per tier and model. `GET /usage?month=YYYY-MM` returns it beside that month's spend and the months that have any; `LumenAssistant`'s footer shows this month's total and opens it (`UsageHistoryView`).
|
|
107
108
|
|
|
108
109
|
### Commands, `@` and `#`
|
|
109
110
|
|
|
@@ -243,7 +244,8 @@ class Chat extends LumenAgent<Env> {
|
|
|
243
244
|
#settle?: (end: AgentTurnEnd) => Promise<void>;
|
|
244
245
|
async plan(turn: TurnContext) {
|
|
245
246
|
// this.input: what the person sent (message, page, refs, tier, time zone).
|
|
246
|
-
|
|
247
|
+
// chat: this chat's id, so its use is one line of the person's usage history.
|
|
248
|
+
const { plan, settle } = await assistant.agentTurn(caller, { ...(this.input ?? { message: '' }), chat: this.name });
|
|
247
249
|
this.#settle = settle;
|
|
248
250
|
return plan;
|
|
249
251
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AllowanceRequest, AllowanceState, AllowanceUnit, AssistantAccess, AssistantChat, AssistantChatRow, AssistantMessage, AssistantOwnWay, AssistantProvider, AssistantStatus } from '@columenlabs/lumen-ui-types/assistant';
|
|
1
|
+
import type { AllowanceRequest, AllowanceState, AllowanceUnit, AssistantAccess, AssistantChat, AssistantChatRow, AssistantMessage, AssistantOwnWay, AssistantProvider, AssistantStatus, UsagePart } from '@columenlabs/lumen-ui-types/assistant';
|
|
2
2
|
import type { AgentTurnEnd, AgentTurnPlan } from '../agent/agent.js';
|
|
3
3
|
import { type BearingsExtra, type BearingsNow } from './bearings.js';
|
|
4
4
|
import { type ModelDef, type TaskDef, type TierDef } from './models.js';
|
|
@@ -56,6 +56,8 @@ export interface AllowanceNotice {
|
|
|
56
56
|
request: AllowanceRequest;
|
|
57
57
|
unit: AllowanceUnit;
|
|
58
58
|
}
|
|
59
|
+
/** The largest answer `complete` asks for, tokens (thinking included). Streamed, so minutes are fine. */
|
|
60
|
+
export declare const COMPLETE_MAX_TOKENS = 32000;
|
|
59
61
|
/** A change a write tool made at once, as `undo` gets it. */
|
|
60
62
|
export interface AssistantWrite {
|
|
61
63
|
/** The tool's own name (not the `group__tool` the model calls). */
|
|
@@ -184,6 +186,11 @@ export interface AgentTurnInput {
|
|
|
184
186
|
time_zone?: string;
|
|
185
187
|
/** When the chat's last message was, for "since you last asked". */
|
|
186
188
|
last_at?: number;
|
|
189
|
+
/**
|
|
190
|
+
* The chat this turn is in (the agent's name), so its use shows as one line of the person's
|
|
191
|
+
* usage history (`GET /usage`). Left out, the turn counts toward spend only.
|
|
192
|
+
*/
|
|
193
|
+
chat?: string;
|
|
187
194
|
}
|
|
188
195
|
export declare class Assistant {
|
|
189
196
|
private readonly o;
|
|
@@ -292,12 +299,24 @@ export declare class Assistant {
|
|
|
292
299
|
prompt: string;
|
|
293
300
|
/** Photos read with the prompt (a scanned page, a screenshot). */
|
|
294
301
|
images?: CompleteImage[];
|
|
302
|
+
/** The answer's token cap, thinking included (default 1,000, at most COMPLETE_MAX_TOKENS). */
|
|
295
303
|
maxTokens?: number;
|
|
296
304
|
signal?: AbortSignal;
|
|
305
|
+
/**
|
|
306
|
+
* The run this call belongs to, as one line of the person's usage history (`GET /usage`):
|
|
307
|
+
* calls with the same `id` add up (every round of one rebuild). Left out, the task's calls
|
|
308
|
+
* that day share one line named after the task.
|
|
309
|
+
*/
|
|
310
|
+
run?: {
|
|
311
|
+
id: string;
|
|
312
|
+
title: string;
|
|
313
|
+
};
|
|
297
314
|
}): Promise<{
|
|
298
315
|
text: string;
|
|
299
316
|
usd: number;
|
|
300
317
|
model: string;
|
|
318
|
+
cut: boolean;
|
|
319
|
+
usage: UsagePart;
|
|
301
320
|
}>;
|
|
302
321
|
/** Starts a person's ChatGPT plan sign-in: the address to open in a new tab. */
|
|
303
322
|
startSignin(caller: Caller, provider: AssistantProvider): Promise<{
|
package/dist/assistant/engine.js
CHANGED
|
@@ -23,6 +23,8 @@ const DOLLARS = { one: 'dollar', many: 'dollars', usd: 1 };
|
|
|
23
23
|
const round2 = (n) => Math.round(n * 100) / 100;
|
|
24
24
|
/** A request may ask for at most this many times the starter or monthly cap. */
|
|
25
25
|
const REQUEST_MAX_TIMES = 100;
|
|
26
|
+
/** The largest answer `complete` asks for, tokens (thinking included). Streamed, so minutes are fine. */
|
|
27
|
+
export const COMPLETE_MAX_TOKENS = 32_000;
|
|
26
28
|
/** A turn's failure the person can act on. */
|
|
27
29
|
export class AssistantError extends Error {
|
|
28
30
|
code;
|
|
@@ -330,6 +332,10 @@ export class Assistant {
|
|
|
330
332
|
};
|
|
331
333
|
const settle = async (end) => {
|
|
332
334
|
await this.state.addSpend(caller.id, paying.payer, end.parts).catch(() => undefined);
|
|
335
|
+
if (input.chat)
|
|
336
|
+
await this.state
|
|
337
|
+
.addUsage(caller.id, { id: `chat:${input.chat}`, kind: 'chat', title: message.split('\n')[0] || 'Chat' }, end.parts)
|
|
338
|
+
.catch(() => undefined);
|
|
333
339
|
const total = end.parts.reduce((a, m) => ({
|
|
334
340
|
calls: a.calls + m.calls,
|
|
335
341
|
in: a.in + m.in,
|
|
@@ -887,7 +893,10 @@ export class Assistant {
|
|
|
887
893
|
cache_read_input_tokens: read,
|
|
888
894
|
}, plan);
|
|
889
895
|
}
|
|
890
|
-
return
|
|
896
|
+
return {
|
|
897
|
+
text: text || done?.output_text || '',
|
|
898
|
+
cut: done?.status === 'incomplete' && done.incomplete_details?.reason === 'max_output_tokens',
|
|
899
|
+
};
|
|
891
900
|
}
|
|
892
901
|
// ---------------------------------------------------------------- one-shot work
|
|
893
902
|
/**
|
|
@@ -904,15 +913,20 @@ export class Assistant {
|
|
|
904
913
|
throw new AssistantError(waitText(code, s.model.provider, !!this.o.allowance), code, 402);
|
|
905
914
|
}
|
|
906
915
|
const clients = this.clients(paying);
|
|
907
|
-
const max = Math.min(o.maxTokens ?? 1000,
|
|
916
|
+
const max = Math.min(o.maxTokens ?? 1000, COMPLETE_MAX_TOKENS);
|
|
908
917
|
let text = '';
|
|
918
|
+
// The answer stopped at `max` before it ended (its thinking counts toward the cap too).
|
|
919
|
+
let cut = false;
|
|
909
920
|
try {
|
|
910
921
|
if ((s.model.provider === 'openai' && clients.openai) ||
|
|
911
922
|
(s.model.provider === 'xai' && clients.xai)) {
|
|
912
|
-
text = await this.openaiOnce(clients, s, o.system, o.prompt, max, o.signal, o.images);
|
|
923
|
+
({ text, cut } = await this.openaiOnce(clients, s, o.system, o.prompt, max, o.signal, o.images));
|
|
913
924
|
}
|
|
914
925
|
else if (clients.anthropic) {
|
|
915
|
-
|
|
926
|
+
// Streamed and read whole: a long answer (minutes at a high cap) keeps its connection alive,
|
|
927
|
+
// and the SDK refuses a long answer as one unstreamed request.
|
|
928
|
+
const msg = await clients.anthropic.messages
|
|
929
|
+
.stream({
|
|
916
930
|
model: s.model.id,
|
|
917
931
|
max_tokens: max,
|
|
918
932
|
system: o.system,
|
|
@@ -935,8 +949,10 @@ export class Assistant {
|
|
|
935
949
|
},
|
|
936
950
|
],
|
|
937
951
|
...effortParams(s.effort),
|
|
938
|
-
}, o.signal ? { signal: o.signal } : undefined)
|
|
952
|
+
}, o.signal ? { signal: o.signal } : undefined)
|
|
953
|
+
.finalMessage();
|
|
939
954
|
s.meter.add(msg.usage);
|
|
955
|
+
cut = msg.stop_reason === 'max_tokens';
|
|
940
956
|
text = msg.content
|
|
941
957
|
.filter((b) => b.type === 'text')
|
|
942
958
|
.map((b) => b.text)
|
|
@@ -951,9 +967,19 @@ export class Assistant {
|
|
|
951
967
|
throw new AssistantError(`${s.model.label} did not answer: ${String(e?.message ?? e).slice(0, 200)}`, 'upstream', 502);
|
|
952
968
|
}
|
|
953
969
|
finally {
|
|
954
|
-
|
|
970
|
+
const part = s.meter.part();
|
|
971
|
+
await this.state.addSpend(caller.id, paying.payer, [part]);
|
|
972
|
+
const task = this.tasks.find((t) => t.id === o.task);
|
|
973
|
+
await this.state
|
|
974
|
+
.addUsage(caller.id, {
|
|
975
|
+
id: `task:${o.task}:${o.run?.id ?? new Date().toISOString().slice(0, 10)}`,
|
|
976
|
+
kind: 'task',
|
|
977
|
+
task: o.task,
|
|
978
|
+
title: o.run?.title ?? task?.label ?? o.task,
|
|
979
|
+
}, [part])
|
|
980
|
+
.catch(() => undefined);
|
|
955
981
|
}
|
|
956
|
-
return { text, usd: s.meter.usd, model: s.model.id };
|
|
982
|
+
return { text, usd: s.meter.usd, model: s.model.id, cut, usage: s.meter.part() };
|
|
957
983
|
}
|
|
958
984
|
// ---------------------------------------------------------------- keys
|
|
959
985
|
/** Starts a person's ChatGPT plan sign-in: the address to open in a new tab. */
|
package/dist/assistant/models.js
CHANGED
|
@@ -126,7 +126,7 @@ export class Meter {
|
|
|
126
126
|
}
|
|
127
127
|
part() {
|
|
128
128
|
const { label, calls, read, out, usd } = this;
|
|
129
|
-
return { label, calls, in: this.in, read, out, usd };
|
|
129
|
+
return { label, calls, in: this.in, read, out, usd, model: this.model.id };
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
/** The request's effort setting: nothing at the model's own default. */
|
package/dist/assistant/routes.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* PUT /web/scrapeql {client_id, client_secret}, DELETE /web/scrapeql, POST /web/test admin
|
|
21
21
|
* POST /chat one answer as server-sent events; closing the request stops it
|
|
22
22
|
* GET /chats, GET|DELETE /chats/:id
|
|
23
|
+
* GET /usage?month=YYYY-MM the person's use that month: total, per tier and model, each chat and run
|
|
23
24
|
* POST /chats/:id/drafts/:draft {action: save | dismiss}
|
|
24
25
|
* POST /chats/:id/writes/:step/undo
|
|
25
26
|
* POST /allowance/request {amount, why} asks for more allowance (one open at a time)
|
|
@@ -321,6 +322,13 @@ export function assistantRoutes(app, o) {
|
|
|
321
322
|
return fail('Chats run on the agent now. Start a new chat.', 410, 'moved');
|
|
322
323
|
case 'GET /chats':
|
|
323
324
|
return json(await a.listChats(caller));
|
|
325
|
+
// The person's own use: one month's total, per tier and model, and each chat and run.
|
|
326
|
+
case 'GET /usage': {
|
|
327
|
+
const month = url.searchParams.get('month') ?? undefined;
|
|
328
|
+
if (month !== undefined && !/^\d{4}-\d{2}$/.test(month))
|
|
329
|
+
return fail('month is YYYY-MM.');
|
|
330
|
+
return json(await a.state.usage(caller.id, month));
|
|
331
|
+
}
|
|
324
332
|
case 'POST /allowance/request': {
|
|
325
333
|
const b = await body(req);
|
|
326
334
|
const amount = typeof b.amount === 'number' && Number.isFinite(b.amount) ? b.amount : 0;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* tier runs on and money left this month, else the other; with neither the assistant waits and says
|
|
20
20
|
* why. A cap covers every provider of its payer.
|
|
21
21
|
*/
|
|
22
|
-
import type { AllowanceRequest, AssistantAccess, AssistantEffort, AssistantOwnWay, AssistantPayer, AssistantProvider, AssistantScreenSetting, AssistantWait, HandOffPolicy, UsagePart } from '@columenlabs/lumen-ui-types/assistant';
|
|
22
|
+
import type { AllowanceRequest, AssistantAccess, AssistantEffort, AssistantOwnWay, AssistantPayer, AssistantProvider, AssistantScreenSetting, AssistantWait, HandOffPolicy, UsageEntry, UsageHistory, UsagePart } from '@columenlabs/lumen-ui-types/assistant';
|
|
23
23
|
import { type ChatgptTokens } from './chatgpt.js';
|
|
24
24
|
import { type Docs } from './store.js';
|
|
25
25
|
export interface KeyTest {
|
|
@@ -98,6 +98,22 @@ export type SpendRow = UsagePart & {
|
|
|
98
98
|
export interface SpendDoc {
|
|
99
99
|
rows: SpendRow[];
|
|
100
100
|
}
|
|
101
|
+
/** Chats and runs kept in one month's usage history: the newest this many. */
|
|
102
|
+
export declare const USAGE_KEPT = 300;
|
|
103
|
+
export interface UsageDoc {
|
|
104
|
+
entries: UsageEntry[];
|
|
105
|
+
}
|
|
106
|
+
/** Adds `more` to `parts`, merged by label (the model kept from the first). */
|
|
107
|
+
export declare function mergeParts(parts: UsagePart[], more: UsagePart[]): {
|
|
108
|
+
label: string;
|
|
109
|
+
calls: number;
|
|
110
|
+
in: number;
|
|
111
|
+
read: number;
|
|
112
|
+
out: number;
|
|
113
|
+
usd: number;
|
|
114
|
+
runs?: number;
|
|
115
|
+
model?: string;
|
|
116
|
+
}[];
|
|
101
117
|
/** USD a month when nobody set a cap. */
|
|
102
118
|
export declare const DEFAULT_CAP = 10;
|
|
103
119
|
/** `2026-10` in the app's time zone. */
|
|
@@ -191,6 +207,18 @@ export declare class State {
|
|
|
191
207
|
spend(id: string, month?: string): Promise<SpendDoc>;
|
|
192
208
|
/** Adds an answer's use to this month's spend, by label and payer. */
|
|
193
209
|
addSpend(id: string, payer: AssistantPayer, parts: UsagePart[]): Promise<void>;
|
|
210
|
+
/**
|
|
211
|
+
* Adds one answer's use to a chat or run in this month's history (`usage:YYYY-MM`): one entry a
|
|
212
|
+
* chat or run, its parts merged, the newest `USAGE_KEPT` kept. `title` names a new entry only.
|
|
213
|
+
*/
|
|
214
|
+
addUsage(id: string, e: {
|
|
215
|
+
id: string;
|
|
216
|
+
kind: UsageEntry['kind'];
|
|
217
|
+
task?: string;
|
|
218
|
+
title: string;
|
|
219
|
+
}, parts: UsagePart[]): Promise<void>;
|
|
220
|
+
/** One month's history (this month by default) beside its spend, and the months that have one. */
|
|
221
|
+
usage(id: string, month?: string): Promise<UsageHistory>;
|
|
194
222
|
/** The payer's key of each provider, those set, and which are plan sign-ins. */
|
|
195
223
|
keysOf(id: string, payer: AssistantPayer): Promise<{
|
|
196
224
|
keys: Partial<Record<"anthropic" | "openai" | "xai", string>>;
|
package/dist/assistant/state.js
CHANGED
|
@@ -7,6 +7,33 @@ export const keyDocName = (provider) => provider === 'anthropic' ? 'key' : `key:
|
|
|
7
7
|
export const planDocName = (provider) => `plan:${provider}`;
|
|
8
8
|
/** Requests kept for approvers: the newest this many. */
|
|
9
9
|
const REQUESTS_KEPT = 300;
|
|
10
|
+
/** Chats and runs kept in one month's usage history: the newest this many. */
|
|
11
|
+
export const USAGE_KEPT = 300;
|
|
12
|
+
/** Months of usage history offered (`usage:YYYY-MM` documents are listed this far back). */
|
|
13
|
+
const USAGE_MONTHS = 24;
|
|
14
|
+
/** Adds `more` to `parts`, merged by label (the model kept from the first). */
|
|
15
|
+
export function mergeParts(parts, more) {
|
|
16
|
+
const out = parts.map((p) => ({ ...p }));
|
|
17
|
+
for (const p of more) {
|
|
18
|
+
if (!p.calls)
|
|
19
|
+
continue;
|
|
20
|
+
const prev = out.find((r) => r.label === p.label);
|
|
21
|
+
if (!prev)
|
|
22
|
+
out.push({ ...p });
|
|
23
|
+
else {
|
|
24
|
+
prev.calls += p.calls;
|
|
25
|
+
prev.in += p.in;
|
|
26
|
+
prev.read += p.read;
|
|
27
|
+
prev.out += p.out;
|
|
28
|
+
prev.usd += p.usd;
|
|
29
|
+
if (p.runs)
|
|
30
|
+
prev.runs = (prev.runs ?? 0) + p.runs;
|
|
31
|
+
if (!prev.model && p.model)
|
|
32
|
+
prev.model = p.model;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
10
37
|
/** USD a month when nobody set a cap. */
|
|
11
38
|
export const DEFAULT_CAP = 10;
|
|
12
39
|
/** `2026-10` in the app's time zone. */
|
|
@@ -227,6 +254,53 @@ export class State {
|
|
|
227
254
|
await this.setAllowance(id, (d) => ({ ...d, spent_usd: (d.spent_usd ?? 0) + usd }));
|
|
228
255
|
}
|
|
229
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Adds one answer's use to a chat or run in this month's history (`usage:YYYY-MM`): one entry a
|
|
259
|
+
* chat or run, its parts merged, the newest `USAGE_KEPT` kept. `title` names a new entry only.
|
|
260
|
+
*/
|
|
261
|
+
async addUsage(id, e, parts) {
|
|
262
|
+
if (!parts.some((p) => p.calls))
|
|
263
|
+
return;
|
|
264
|
+
const now = Date.now();
|
|
265
|
+
await this.docs.update(id, `usage:${this.month()}`, (d) => {
|
|
266
|
+
const entries = [...(d?.entries ?? [])];
|
|
267
|
+
const i = entries.findIndex((x) => x.id === e.id);
|
|
268
|
+
const prev = i >= 0 ? entries.splice(i, 1)[0] : undefined;
|
|
269
|
+
const merged = mergeParts(prev?.parts ?? [], parts);
|
|
270
|
+
const sum = (k) => merged.reduce((a, p) => a + p[k], 0);
|
|
271
|
+
entries.unshift({
|
|
272
|
+
id: e.id,
|
|
273
|
+
kind: e.kind,
|
|
274
|
+
...(e.task ? { task: e.task } : {}),
|
|
275
|
+
title: prev?.title || e.title.slice(0, 120),
|
|
276
|
+
first: prev?.first ?? now,
|
|
277
|
+
at: now,
|
|
278
|
+
calls: sum('calls'),
|
|
279
|
+
in: sum('in'),
|
|
280
|
+
read: sum('read'),
|
|
281
|
+
out: sum('out'),
|
|
282
|
+
usd: sum('usd'),
|
|
283
|
+
parts: merged,
|
|
284
|
+
});
|
|
285
|
+
return { entries: entries.slice(0, USAGE_KEPT) };
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
/** One month's history (this month by default) beside its spend, and the months that have one. */
|
|
289
|
+
async usage(id, month = this.month()) {
|
|
290
|
+
const [doc, spend, listed] = await Promise.all([
|
|
291
|
+
this.docs.read(id, `usage:${month}`),
|
|
292
|
+
this.spend(id, month),
|
|
293
|
+
this.docs.list(id, 'usage:', USAGE_MONTHS),
|
|
294
|
+
]);
|
|
295
|
+
const months = [...new Set([this.month(), ...listed.map((r) => r.key.slice(6))])].sort((a, b) => b.localeCompare(a));
|
|
296
|
+
return {
|
|
297
|
+
month,
|
|
298
|
+
months,
|
|
299
|
+
usd: spend.rows.reduce((a, r) => a + r.usd, 0),
|
|
300
|
+
rows: spend.rows,
|
|
301
|
+
entries: doc?.entries ?? [],
|
|
302
|
+
};
|
|
303
|
+
}
|
|
230
304
|
/** The payer's key of each provider, those set, and which are plan sign-ins. */
|
|
231
305
|
async keysOf(id, payer) {
|
|
232
306
|
const ids = Object.keys(PROVIDERS);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.210",
|
|
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",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"@ai-sdk/openai": "4.0.108",
|
|
53
53
|
"@anthropic-ai/sdk": "0.133.0",
|
|
54
54
|
"@cloudflare/think": "0.23.0",
|
|
55
|
-
"@columenlabs/lumen-ops": "0.1.
|
|
56
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
55
|
+
"@columenlabs/lumen-ops": "0.1.210",
|
|
56
|
+
"@columenlabs/lumen-ui-types": "0.1.210",
|
|
57
57
|
"agents": "0.32.0",
|
|
58
58
|
"ai": "7.0.162",
|
|
59
59
|
"just-bash": "3.6.0",
|