@columenlabs/lumen-ai 0.1.252 → 0.1.254
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 +1 -1
- package/dist/agent/agent.d.ts +11 -0
- package/dist/agent/agent.js +33 -0
- package/dist/agent/stream.d.ts +21 -0
- package/dist/agent/stream.js +61 -4
- package/dist/assistant/engine.js +4 -1
- package/dist/assistant/state.d.ts +21 -3
- package/dist/assistant/state.js +36 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -254,7 +254,7 @@ Drafts and writes are cards in the chat's own storage (`lumen_cards` in the agen
|
|
|
254
254
|
|
|
255
255
|
**The panel.** `LumenAssistant` reads a Think chat with no change. `ask(input, sink)` runs one answer and sends the panel's own events (`AssistantEvent`: the chat, the text, each tool line, cards, hand-off sections, advice, the use) to `sink`. The app's `POST /chat` returns `askResponse((sink) => chat.ask(input, sink), { signal, stop: () => chat.stop() })`, the same server-sent events `assistantRoutes` sends. `view()` gives a stored chat as the panel's items, and `usage()` gives the chat's use so far. A card's id is its tool call's, so the panel's Save, Dismiss and Undo go to `saveCard`, `dismissCard` and `undoCard`. The app keeps the list of chats, and each chat is its own agent, such as a facet of the person's store. `chunkEvents` and `itemsOf` are the mapping, for an app that streams some other way.
|
|
256
256
|
|
|
257
|
-
**Tool lines.** Each tool call shows as a line with the tool's own `title` (an MCP tool's `title`, a plain tool's `title`), kept in the chat's storage so a chat read back words its lines the same; a tool with no title shows its name. An app that keeps its insides off the screen overrides `plainSteps()` to return `true`: every line reads its tool's title, else a word for what it did (Look something up, Make a change, Draft a change), with no tool id, row count or error text (a failed line says `didn't work`), and a hand-off section reads Take a closer look with no tier or task. The details go to the app's log instead: `stepFailed({ tool, error, ms })` hears each failed call with its whole error (override it to log it with the chat's ids), and `onStep` carries `error` and `caller` on a failed tool or hand-off. `stepTitleOf(name, kind)` gives a line's title for an app's own card answers.
|
|
257
|
+
**Tool lines.** Each tool call shows as a line with the tool's own `title` (an MCP tool's `title`, a plain tool's `title`), kept in the chat's storage so a chat read back words its lines the same; a tool with no title shows its name. An app that keeps its insides off the screen overrides `plainSteps()` to return `true`: every line reads its tool's title, else a word for what it did (Look something up, Make a change, Draft a change), with no tool id, row count or error text (a failed line says `didn't work`), and a hand-off section reads Take a closer look with no tier or task. The details go to the app's log instead: `stepFailed({ tool, error, ms })` hears each failed call with its whole error (override it to log it with the chat's ids), and `onStep` carries `error` and `caller` on a failed tool or hand-off. `stepTitleOf(name, kind)` gives a line's title for an app's own card answers. **Step lines say what a call did** (`stepLine(c)`): override it to word each call as a verb and what it acted on, from the call's own arguments and answer (`c.input`, `c.output`): present tense while it runs (`state: 'running'`, sent again once the arguments are in), past tense with a short result when it answered (`'done'`), and "Couldn't …" when it failed (`'failed'`, which then shows no `didn't work`). Return `undefined` to keep the tool's title. The kit words its own web lookup (Looking up / Looked up the question). A finished line is kept with the chat, so a chat read back reads the same. In a plain chat the tool search that turns more tools on shows no line.
|
|
258
258
|
|
|
259
259
|
**A write in progress.** A tool line starts (`step_start`) as soon as the model starts the call, while it is still writing the arguments. For a Read + write tool it carries `write: true`, and `useAssistantChat` marks that running step `writing` until its result lands or the answer ends, so an app can show what the tool changes as being changed now (a pending edit is not ready to accept while the model rewrites it).
|
|
260
260
|
|
package/dist/agent/agent.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { type ModelMessage, type ToolSet } from 'ai';
|
|
|
17
17
|
import type { ModelDef } from '../assistant/models.js';
|
|
18
18
|
import { type AgentConnector, type ConnectorAt } from './connectors.js';
|
|
19
19
|
import { type ModelKey } from './models.js';
|
|
20
|
+
import { type StepCall } from './stream.js';
|
|
20
21
|
import { type AiToolsOptions, type OpenedTools, type SourceAt } from './tools.js';
|
|
21
22
|
import { UsageMeter } from './usage.js';
|
|
22
23
|
/** What a plan's extra tools get: the open tools, a meter of their own and the cap. */
|
|
@@ -197,6 +198,16 @@ export declare abstract class LumenAgent<Env = unknown> extends Think<Env> {
|
|
|
197
198
|
* (`false`) unless the app overrides it; the app logs the details from `onStep`.
|
|
198
199
|
*/
|
|
199
200
|
protected plainSteps(): boolean;
|
|
201
|
+
/**
|
|
202
|
+
* A tool call's line in the person's words: a verb and what it acted on, from the call's own
|
|
203
|
+
* arguments and answer. `running` is present tense ("Editing Lid"), `done` past tense with a short
|
|
204
|
+
* result when it helps ("Edited Lid: wall 2 → 3 mm"), `failed` says it did not ("Couldn't edit
|
|
205
|
+
* Lid"). Return `undefined` to keep the tool's title. A plain chat's line names no tool id, model,
|
|
206
|
+
* engine or code. The kit words its own web lookup; the app words its tools by overriding this
|
|
207
|
+
* (and may call `super.stepLine(c)` for the kit's). A finished line is kept with the chat, so a
|
|
208
|
+
* chat read back shows the same words.
|
|
209
|
+
*/
|
|
210
|
+
protected stepLine(c: StepCall): string | undefined;
|
|
200
211
|
/**
|
|
201
212
|
* A tool call this answer failed: its name and whole error (a plain line shows neither). Does
|
|
202
213
|
* nothing unless the app overrides it, e.g. to log it with the chat's ids.
|
package/dist/agent/agent.js
CHANGED
|
@@ -201,12 +201,45 @@ export class LumenAgent extends Think {
|
|
|
201
201
|
plainSteps() {
|
|
202
202
|
return false;
|
|
203
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* A tool call's line in the person's words: a verb and what it acted on, from the call's own
|
|
206
|
+
* arguments and answer. `running` is present tense ("Editing Lid"), `done` past tense with a short
|
|
207
|
+
* result when it helps ("Edited Lid: wall 2 → 3 mm"), `failed` says it did not ("Couldn't edit
|
|
208
|
+
* Lid"). Return `undefined` to keep the tool's title. A plain chat's line names no tool id, model,
|
|
209
|
+
* engine or code. The kit words its own web lookup; the app words its tools by overriding this
|
|
210
|
+
* (and may call `super.stepLine(c)` for the kit's). A finished line is kept with the chat, so a
|
|
211
|
+
* chat read back shows the same words.
|
|
212
|
+
*/
|
|
213
|
+
stepLine(c) {
|
|
214
|
+
if (c.tool !== 'web__lookup')
|
|
215
|
+
return undefined;
|
|
216
|
+
const q = c.input?.question;
|
|
217
|
+
const question = typeof q === 'string' ? q.trim() : '';
|
|
218
|
+
if (!question)
|
|
219
|
+
return undefined;
|
|
220
|
+
return c.state === 'running'
|
|
221
|
+
? `Looking up ${question}`
|
|
222
|
+
: c.state === 'failed'
|
|
223
|
+
? `Couldn't look up ${question}`
|
|
224
|
+
: `Looked up ${question}`;
|
|
225
|
+
}
|
|
204
226
|
/** How this chat words its tool lines: plain or not, with the titles of every tool it offered. */
|
|
205
227
|
#names() {
|
|
206
228
|
return {
|
|
207
229
|
plain: this.plainSteps(),
|
|
208
230
|
titleOf: (n) => this.#turn?.tools.info.find((i) => i.name === n)?.title ??
|
|
209
231
|
this.#get('titles')?.[n],
|
|
232
|
+
lineOf: (c) => {
|
|
233
|
+
if (c.state === 'running')
|
|
234
|
+
return this.stepLine(c);
|
|
235
|
+
const kept = this.#get(`line:${c.id}`);
|
|
236
|
+
if (kept)
|
|
237
|
+
return kept;
|
|
238
|
+
const line = this.stepLine(c);
|
|
239
|
+
if (line)
|
|
240
|
+
this.#put(`line:${c.id}`, line.slice(0, 200));
|
|
241
|
+
return line;
|
|
242
|
+
},
|
|
210
243
|
};
|
|
211
244
|
}
|
|
212
245
|
/**
|
package/dist/agent/stream.d.ts
CHANGED
|
@@ -17,7 +17,28 @@ export interface StepNames {
|
|
|
17
17
|
plain?: boolean;
|
|
18
18
|
/** The tool's own title, when it has one. */
|
|
19
19
|
titleOf?: (name: string) => string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* A line that says what this call did and to what (`LumenAgent.stepLine`): present tense while it
|
|
22
|
+
* runs, past tense with a short result when it answered, "Couldn't …" when it failed. Left out
|
|
23
|
+
* (or `undefined`), the line reads the tool's title.
|
|
24
|
+
*/
|
|
25
|
+
lineOf?: (c: StepCall) => string | undefined;
|
|
20
26
|
}
|
|
27
|
+
/** One tool call as its line is worded: its name, arguments, answer and where it stands. */
|
|
28
|
+
export interface StepCall {
|
|
29
|
+
id: string;
|
|
30
|
+
/** The tool's name as the model called it (`group__tool`). */
|
|
31
|
+
tool: string;
|
|
32
|
+
input?: unknown;
|
|
33
|
+
/** Its answer, once it answered. */
|
|
34
|
+
output?: unknown;
|
|
35
|
+
state: 'running' | 'done' | 'failed';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Whether a call shows no line at all: in a plain chat, the tool search that turns more tools on is
|
|
39
|
+
* the assistant finding its own tools, not something it did for the person.
|
|
40
|
+
*/
|
|
41
|
+
export declare function hiddenStep(name: string, names?: StepNames): boolean;
|
|
21
42
|
/** What a plain line says when a call failed: the assistant reads the error and goes on. */
|
|
22
43
|
export declare const PLAIN_FAILED = "didn't work";
|
|
23
44
|
/** A plain hand-off's heading: a closer look, never the tier it went to. */
|
package/dist/agent/stream.js
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
import { rowCount } from '../assistant/tools.js';
|
|
2
2
|
import { webStep } from '../assistant/web.js';
|
|
3
|
+
import { TOOL_SEARCH } from './search.js';
|
|
3
4
|
/** A tool's name as a line's title: `notes__find_notes` → `find notes`. */
|
|
4
5
|
export function stepTitle(name) {
|
|
5
6
|
const own = name.includes('__') ? name.slice(name.indexOf('__') + 2) : name;
|
|
6
7
|
return own.replace(/_/g, ' ');
|
|
7
8
|
}
|
|
9
|
+
/** A line's text kept to one short line. */
|
|
10
|
+
const oneLine = (t) => {
|
|
11
|
+
const s = (t ?? '').replace(/\s+/g, ' ').trim();
|
|
12
|
+
return s ? s.slice(0, 120) : undefined;
|
|
13
|
+
};
|
|
14
|
+
/** The line the app gave for this call, when it gave one; never throws. */
|
|
15
|
+
function lineFor(names, c) {
|
|
16
|
+
try {
|
|
17
|
+
return oneLine(names.lineOf?.(c));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Whether a call shows no line at all: in a plain chat, the tool search that turns more tools on is
|
|
25
|
+
* the assistant finding its own tools, not something it did for the person.
|
|
26
|
+
*/
|
|
27
|
+
export function hiddenStep(name, names = {}) {
|
|
28
|
+
return !!names.plain && name === TOOL_SEARCH;
|
|
29
|
+
}
|
|
8
30
|
/** What a plain line says when a call failed: the assistant reads the error and goes on. */
|
|
9
31
|
export const PLAIN_FAILED = "didn't work";
|
|
10
32
|
/** A plain hand-off's heading: a closer look, never the tier it went to. */
|
|
@@ -88,15 +110,27 @@ function settledCall(id, c, out, ms, cards, screens = new Map(), names = {}) {
|
|
|
88
110
|
const image = stepImage(o?.image);
|
|
89
111
|
const web = webStep(c.name, c.input, out.output, !!error || obj(out.output)?.ok === false);
|
|
90
112
|
const wrote = card?.kind === 'write' || (o && typeof o.change === 'string');
|
|
113
|
+
// The app's own line says what the call did; a failed one already says it did not ("Couldn't …").
|
|
114
|
+
const line = lineFor(names, {
|
|
115
|
+
id,
|
|
116
|
+
tool: c.name,
|
|
117
|
+
input: c.input,
|
|
118
|
+
...(error
|
|
119
|
+
? {}
|
|
120
|
+
: { output: o && 'result' in o && o.change !== undefined ? o.result : out.output }),
|
|
121
|
+
state: error ? 'failed' : 'done',
|
|
122
|
+
});
|
|
91
123
|
const step = {
|
|
92
124
|
kind: 'step',
|
|
93
125
|
id,
|
|
94
126
|
tool: c.name,
|
|
95
|
-
title: shownTitle(c.name, names, wrote ? 'write' : 'read'),
|
|
127
|
+
title: line ?? shownTitle(c.name, names, wrote ? 'write' : 'read'),
|
|
96
128
|
ok: !error,
|
|
97
129
|
ms,
|
|
98
130
|
...(rows !== undefined && !error && !names.plain ? { rows } : {}),
|
|
99
|
-
...(error
|
|
131
|
+
...(error && !(line && names.plain)
|
|
132
|
+
? { error: names.plain ? PLAIN_FAILED : error.slice(0, 300) }
|
|
133
|
+
: {}),
|
|
100
134
|
...(wrote
|
|
101
135
|
? { write: { undo: true, status: card?.status === 'undone' ? 'undone' : 'done' } }
|
|
102
136
|
: {}),
|
|
@@ -129,7 +163,10 @@ export function chunkEvents(o = {}) {
|
|
|
129
163
|
advising = true;
|
|
130
164
|
return [{ event: 'advice', data: { tier: 'advisor', label: o.advisor ?? 'Advice' } }];
|
|
131
165
|
}
|
|
132
|
-
if (name === 'escalate' ||
|
|
166
|
+
if (name === 'escalate' ||
|
|
167
|
+
name === 'screen' ||
|
|
168
|
+
o.kindOf?.(name) === 'draft' ||
|
|
169
|
+
hiddenStep(name, names))
|
|
133
170
|
return [];
|
|
134
171
|
// A tool that makes a change at once says so as it starts, so an app can show that what it
|
|
135
172
|
// changes is being changed now, before the model has finished writing the call.
|
|
@@ -150,7 +187,23 @@ export function chunkEvents(o = {}) {
|
|
|
150
187
|
case 'tool-input-available': {
|
|
151
188
|
const c = calls.get(id) ?? { name: String(chunk.toolName ?? ''), t0: Date.now() };
|
|
152
189
|
calls.set(id, { ...c, input: chunk.input });
|
|
153
|
-
|
|
190
|
+
// Its arguments are in: the running line can say what it is doing and to what.
|
|
191
|
+
if (c.name === 'advisor' ||
|
|
192
|
+
c.name === 'escalate' ||
|
|
193
|
+
c.name === 'screen' ||
|
|
194
|
+
o.kindOf?.(c.name) === 'draft' ||
|
|
195
|
+
hiddenStep(c.name, names))
|
|
196
|
+
return [];
|
|
197
|
+
const line = lineFor(names, { id, tool: c.name, input: chunk.input, state: 'running' });
|
|
198
|
+
if (!line)
|
|
199
|
+
return [];
|
|
200
|
+
const write = o.kindOf?.(c.name) === 'write';
|
|
201
|
+
return [
|
|
202
|
+
{
|
|
203
|
+
event: 'step_start',
|
|
204
|
+
data: { id, tool: c.name, title: line, ...(write ? { write } : {}) },
|
|
205
|
+
},
|
|
206
|
+
];
|
|
154
207
|
}
|
|
155
208
|
case 'tool-output-available':
|
|
156
209
|
case 'tool-output-error': {
|
|
@@ -164,6 +217,8 @@ export function chunkEvents(o = {}) {
|
|
|
164
217
|
return [{ event: 'advice_done', data: error ? { error } : {} }];
|
|
165
218
|
}
|
|
166
219
|
const ms = Date.now() - c.t0;
|
|
220
|
+
if (hiddenStep(c.name, names))
|
|
221
|
+
return [];
|
|
167
222
|
const out = obj(chunk.output);
|
|
168
223
|
const said = error ?? (out && typeof out.error === 'string' ? out.error : undefined);
|
|
169
224
|
if (said !== undefined) {
|
|
@@ -256,6 +311,8 @@ export function itemsOf(messages, cards, screens = new Map(), steers = {}, names
|
|
|
256
311
|
items.push({ kind: 'advice', tier: 'advisor', label: 'Advice' });
|
|
257
312
|
continue;
|
|
258
313
|
}
|
|
314
|
+
if (hiddenStep(tool, names))
|
|
315
|
+
continue;
|
|
259
316
|
const ended = p.state === 'output-available'
|
|
260
317
|
? { output: p.output }
|
|
261
318
|
: p.state === 'output-error'
|
package/dist/assistant/engine.js
CHANGED
|
@@ -16,7 +16,7 @@ import { webLookupTools } from '../subagents/web.js';
|
|
|
16
16
|
import { bearings } from './bearings.js';
|
|
17
17
|
import { CHATGPT_REDIRECT, SigninError, finishChatgpt, startChatgpt, testChatgpt, } from './chatgpt.js';
|
|
18
18
|
import { ALL_MODELS, DEFAULT_OUT, Meter, PROVIDERS, XAI_BASE_URL, effortParams, } from './models.js';
|
|
19
|
-
import { DEFAULT_CAP, State, keyDocName, keyState, ownKeyUsd, ownPlanCalls, planDocName, waitText, withProvider, } from './state.js';
|
|
19
|
+
import { DEFAULT_CAP, State, keyDocName, keyState, ownKeyUsd, ownPlanCalls, ownPlanUsd, planDocName, waitText, withProvider, } from './state.js';
|
|
20
20
|
import { Docs } from './store.js';
|
|
21
21
|
import { toolName, withDeadline } from './tools.js';
|
|
22
22
|
import { scrapeqlTools, webFetchTools } from './web.js';
|
|
@@ -410,6 +410,7 @@ export class Assistant {
|
|
|
410
410
|
title: message.split('\n')[0] || 'Chat',
|
|
411
411
|
own_usd: ownKeyUsd(paying, onPayer),
|
|
412
412
|
own_plan_calls: ownPlanCalls(paying, onPayer),
|
|
413
|
+
own_plan_usd: ownPlanUsd(paying, onPayer),
|
|
413
414
|
}, end.parts)
|
|
414
415
|
.catch(() => undefined);
|
|
415
416
|
const total = end.parts.reduce((a, m) => ({
|
|
@@ -1096,6 +1097,7 @@ export class Assistant {
|
|
|
1096
1097
|
title: o.run?.title ?? task?.label ?? o.task,
|
|
1097
1098
|
own_usd: ownKeyUsd(paying, [part]),
|
|
1098
1099
|
own_plan_calls: ownPlanCalls(paying, [part]),
|
|
1100
|
+
own_plan_usd: ownPlanUsd(paying, [part]),
|
|
1099
1101
|
}, [part])
|
|
1100
1102
|
.catch(() => undefined);
|
|
1101
1103
|
}
|
|
@@ -1185,6 +1187,7 @@ export class Assistant {
|
|
|
1185
1187
|
title: o.run?.title ?? task?.label ?? o.task,
|
|
1186
1188
|
own_usd: ownKeyUsd(paying, [part]),
|
|
1187
1189
|
own_plan_calls: ownPlanCalls(paying, [part]),
|
|
1190
|
+
own_plan_usd: ownPlanUsd(paying, [part]),
|
|
1188
1191
|
}, [part])
|
|
1189
1192
|
.catch(() => undefined);
|
|
1190
1193
|
}
|
|
@@ -104,8 +104,19 @@ export type SpendRow = UsagePart & {
|
|
|
104
104
|
};
|
|
105
105
|
/** The provider a part ran on: by its model id, else by the model's name at the end of its label. */
|
|
106
106
|
export declare function providerOfPart(p: UsagePart): AssistantProvider | undefined;
|
|
107
|
-
/**
|
|
108
|
-
|
|
107
|
+
/**
|
|
108
|
+
* What a part's tokens cost at its model's API rates: the estimate shown for a plan sign-in's calls,
|
|
109
|
+
* which cost the person nothing. Cache writes count as plain input. Undefined for an unknown model.
|
|
110
|
+
*/
|
|
111
|
+
export declare function listUsd(p: UsagePart): number | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Spend rows with the provider each ran on, so a page can tell a removed key's spend apart, and
|
|
114
|
+
* a $0 row (a plan's calls) with what it would cost at API rates (`list_usd`).
|
|
115
|
+
*/
|
|
116
|
+
export declare function withProvider<R extends UsagePart>(rows: R[]): (R & {
|
|
117
|
+
provider?: "anthropic" | "openai" | "xai" | undefined;
|
|
118
|
+
list_usd?: number | undefined;
|
|
119
|
+
})[];
|
|
109
120
|
/** USD of `parts` the person's own API keys paid: a provider signed in with a plan costs $0. */
|
|
110
121
|
export declare function ownKeyUsd(paying: {
|
|
111
122
|
payer: AssistantPayer;
|
|
@@ -116,6 +127,11 @@ export declare function ownPlanCalls(paying: {
|
|
|
116
127
|
payer: AssistantPayer;
|
|
117
128
|
plans: Paying['plans'];
|
|
118
129
|
}, parts: UsagePart[]): number;
|
|
130
|
+
/** Those plan calls at the models' API rates: an estimate of what the plan saved the person. */
|
|
131
|
+
export declare function ownPlanUsd(paying: {
|
|
132
|
+
payer: AssistantPayer;
|
|
133
|
+
plans: Paying['plans'];
|
|
134
|
+
}, parts: UsagePart[]): number;
|
|
119
135
|
/**
|
|
120
136
|
* What counts toward the person's own cap: own rows paid by an API key (a plan sign-in costs
|
|
121
137
|
* nothing per call), on a provider they can still pay with. A row from before ways were recorded
|
|
@@ -282,7 +298,8 @@ export declare class State {
|
|
|
282
298
|
* Adds one answer's use to a chat or run in this month's history (`usage:YYYY-MM`): one entry a
|
|
283
299
|
* chat or run, its parts merged, the newest `USAGE_KEPT` kept. `title` names a new entry only;
|
|
284
300
|
* `own_usd` is what of this answer the person's own key or plan paid, added to the entry's;
|
|
285
|
-
* `own_plan_calls` the calls their plan sign-in answered (free to them, so counted, not priced)
|
|
301
|
+
* `own_plan_calls` the calls their plan sign-in answered (free to them, so counted, not priced),
|
|
302
|
+
* `own_plan_usd` those calls at API rates (an estimate).
|
|
286
303
|
*/
|
|
287
304
|
addUsage(id: string, e: {
|
|
288
305
|
id: string;
|
|
@@ -291,6 +308,7 @@ export declare class State {
|
|
|
291
308
|
title: string;
|
|
292
309
|
own_usd?: number;
|
|
293
310
|
own_plan_calls?: number;
|
|
311
|
+
own_plan_usd?: number;
|
|
294
312
|
}, parts: UsagePart[]): Promise<void>;
|
|
295
313
|
/** One month's history (this month by default) beside its spend, and the months that have one. */
|
|
296
314
|
usage(id: string, month?: string): Promise<UsageHistory>;
|
package/dist/assistant/state.js
CHANGED
|
@@ -14,11 +14,30 @@ export function providerOfPart(p) {
|
|
|
14
14
|
KNOWN_MODELS.find((x) => p.label === x.label || p.label.endsWith(` · ${x.label}`));
|
|
15
15
|
return m?.provider;
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* What a part's tokens cost at its model's API rates: the estimate shown for a plan sign-in's calls,
|
|
19
|
+
* which cost the person nothing. Cache writes count as plain input. Undefined for an unknown model.
|
|
20
|
+
*/
|
|
21
|
+
export function listUsd(p) {
|
|
22
|
+
const m = KNOWN_MODELS.find((x) => x.id === p.model) ??
|
|
23
|
+
KNOWN_MODELS.find((x) => p.label === x.label || p.label.endsWith(` · ${x.label}`));
|
|
24
|
+
if (!m)
|
|
25
|
+
return undefined;
|
|
26
|
+
return (Math.max(0, p.in - p.read) * m.in + p.read * m.read + p.out * m.out) / 1e6;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Spend rows with the provider each ran on, so a page can tell a removed key's spend apart, and
|
|
30
|
+
* a $0 row (a plan's calls) with what it would cost at API rates (`list_usd`).
|
|
31
|
+
*/
|
|
18
32
|
export function withProvider(rows) {
|
|
19
33
|
return rows.map((r) => {
|
|
20
34
|
const provider = providerOfPart(r);
|
|
21
|
-
|
|
35
|
+
const list = r.calls && r.usd < 0.0005 ? listUsd(r) : undefined;
|
|
36
|
+
return {
|
|
37
|
+
...r,
|
|
38
|
+
...(provider ? { provider } : {}),
|
|
39
|
+
...(list !== undefined ? { list_usd: list } : {}),
|
|
40
|
+
};
|
|
22
41
|
});
|
|
23
42
|
}
|
|
24
43
|
/** USD of `parts` the person's own API keys paid: a provider signed in with a plan costs $0. */
|
|
@@ -43,6 +62,17 @@ export function ownPlanCalls(paying, parts) {
|
|
|
43
62
|
})
|
|
44
63
|
.reduce((a, p) => a + p.calls, 0);
|
|
45
64
|
}
|
|
65
|
+
/** Those plan calls at the models' API rates: an estimate of what the plan saved the person. */
|
|
66
|
+
export function ownPlanUsd(paying, parts) {
|
|
67
|
+
if (paying.payer !== 'own')
|
|
68
|
+
return 0;
|
|
69
|
+
return parts
|
|
70
|
+
.filter((p) => {
|
|
71
|
+
const prov = providerOfPart(p);
|
|
72
|
+
return !!(prov && paying.plans[prov]);
|
|
73
|
+
})
|
|
74
|
+
.reduce((a, p) => a + (listUsd(p) ?? 0), 0);
|
|
75
|
+
}
|
|
46
76
|
/**
|
|
47
77
|
* What counts toward the person's own cap: own rows paid by an API key (a plan sign-in costs
|
|
48
78
|
* nothing per call), on a provider they can still pay with. A row from before ways were recorded
|
|
@@ -364,7 +394,8 @@ export class State {
|
|
|
364
394
|
* Adds one answer's use to a chat or run in this month's history (`usage:YYYY-MM`): one entry a
|
|
365
395
|
* chat or run, its parts merged, the newest `USAGE_KEPT` kept. `title` names a new entry only;
|
|
366
396
|
* `own_usd` is what of this answer the person's own key or plan paid, added to the entry's;
|
|
367
|
-
* `own_plan_calls` the calls their plan sign-in answered (free to them, so counted, not priced)
|
|
397
|
+
* `own_plan_calls` the calls their plan sign-in answered (free to them, so counted, not priced),
|
|
398
|
+
* `own_plan_usd` those calls at API rates (an estimate).
|
|
368
399
|
*/
|
|
369
400
|
async addUsage(id, e, parts) {
|
|
370
401
|
if (!parts.some((p) => p.calls))
|
|
@@ -378,6 +409,7 @@ export class State {
|
|
|
378
409
|
const sum = (k) => merged.reduce((a, p) => a + p[k], 0);
|
|
379
410
|
const own = (prev?.own_usd ?? 0) + (e.own_usd ?? 0);
|
|
380
411
|
const plan = (prev?.own_plan_calls ?? 0) + (e.own_plan_calls ?? 0);
|
|
412
|
+
const planUsd = (prev?.own_plan_usd ?? 0) + (e.own_plan_usd ?? 0);
|
|
381
413
|
entries.unshift({
|
|
382
414
|
id: e.id,
|
|
383
415
|
kind: e.kind,
|
|
@@ -392,6 +424,7 @@ export class State {
|
|
|
392
424
|
usd: sum('usd'),
|
|
393
425
|
...(own > 0 ? { own_usd: own } : {}),
|
|
394
426
|
...(plan > 0 ? { own_plan_calls: plan } : {}),
|
|
427
|
+
...(planUsd > 0 ? { own_plan_usd: planUsd } : {}),
|
|
395
428
|
parts: merged,
|
|
396
429
|
});
|
|
397
430
|
return { entries: entries.slice(0, USAGE_KEPT) };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.254",
|
|
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",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"@ai-sdk/openai": "4.0.108",
|
|
57
57
|
"@anthropic-ai/sdk": "0.133.0",
|
|
58
58
|
"@cloudflare/think": "0.23.0",
|
|
59
|
-
"@columenlabs/lumen-ops": "0.1.
|
|
60
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
59
|
+
"@columenlabs/lumen-ops": "0.1.254",
|
|
60
|
+
"@columenlabs/lumen-ui-types": "0.1.254",
|
|
61
61
|
"agents": "0.32.0",
|
|
62
62
|
"ai": "7.0.162",
|
|
63
63
|
"just-bash": "3.6.0",
|