@columenlabs/lumen-ai 0.1.239 → 0.1.240
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 -0
- package/dist/assistant/engine.d.ts +10 -0
- package/dist/assistant/engine.js +18 -8
- package/dist/assistant/state.d.ts +6 -1
- package/dist/assistant/state.js +10 -7
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -92,6 +92,7 @@ const assistant = createAssistant({
|
|
|
92
92
|
// turn when a ChatGPT plan runs out ('shared': the same model on the app's key, shared spend).
|
|
93
93
|
// ownWays: async (caller) => ({ openai: ['key', 'plan'] }),
|
|
94
94
|
// ownFallback: async (caller) => true,
|
|
95
|
+
// credits: (tier) => tier !== 'reviewer', // false: that tier runs on the app's key, never counted against allowance
|
|
95
96
|
// planFallback: 'shared',
|
|
96
97
|
});
|
|
97
98
|
// A check the app always runs on its own key, whoever pays the person's answers:
|
|
@@ -205,6 +205,12 @@ export interface AssistantOptions {
|
|
|
205
205
|
* its cap or their plan is used up. True by default.
|
|
206
206
|
*/
|
|
207
207
|
ownFallback?: (caller: Caller) => boolean | Promise<boolean>;
|
|
208
|
+
/**
|
|
209
|
+
* Whether a tier's work counts against the person's allowance (Admin ▸ Models' Credits switch).
|
|
210
|
+
* False: it runs on the app's key whatever the person pays with or has left, and its spend is
|
|
211
|
+
* kept as free (shown, never counted). Left out, every tier counts.
|
|
212
|
+
*/
|
|
213
|
+
credits?: (tier: string) => boolean | Promise<boolean>;
|
|
208
214
|
/**
|
|
209
215
|
* Who answers when a ChatGPT plan reaches its usage limit mid-turn. 'claude' (default): the
|
|
210
216
|
* app's smallest Claude on the same payer's Anthropic key. 'shared': the same model on the app's
|
|
@@ -273,6 +279,10 @@ export declare class Assistant {
|
|
|
273
279
|
mayOwn(caller: Caller, provider: AssistantProvider, way: AssistantOwnWay): Promise<boolean>;
|
|
274
280
|
/** The app's rules for who pays this person's answers (own ways, fallback). */
|
|
275
281
|
private payingRules;
|
|
282
|
+
/** True when this tier's work is the app's own (`credits` answers false): the app's key, not counted. */
|
|
283
|
+
private freeTier;
|
|
284
|
+
/** The paying rules for a tier: its own rules, or the app's key uncounted for a free tier. */
|
|
285
|
+
private rulesFor;
|
|
276
286
|
/**
|
|
277
287
|
* One turn as a Think agent's plan (`LumenAgent` in `@columenlabs/lumen-ai/agent`): the person's
|
|
278
288
|
* tier and its model on whoever pays, the tool groups at their access, the kit's rules, the
|
package/dist/assistant/engine.js
CHANGED
|
@@ -246,6 +246,18 @@ export class Assistant {
|
|
|
246
246
|
]);
|
|
247
247
|
return { ...(ways ? { ways } : {}), fallback, ...(only ? { only } : {}) };
|
|
248
248
|
}
|
|
249
|
+
/** True when this tier's work is the app's own (`credits` answers false): the app's key, not counted. */
|
|
250
|
+
async freeTier(tier) {
|
|
251
|
+
if (!this.o.credits)
|
|
252
|
+
return false;
|
|
253
|
+
return (await Promise.resolve(this.o.credits(tier)).catch(() => true)) === false;
|
|
254
|
+
}
|
|
255
|
+
/** The paying rules for a tier: its own rules, or the app's key uncounted for a free tier. */
|
|
256
|
+
async rulesFor(caller, tier, only) {
|
|
257
|
+
const free = await this.freeTier(tier);
|
|
258
|
+
const rules = await this.payingRules(caller, free ? 'app' : only);
|
|
259
|
+
return { rules: free ? { ...rules, free: true } : rules, free };
|
|
260
|
+
}
|
|
249
261
|
// ---------------------------------------------------------------- a turn on Think
|
|
250
262
|
/**
|
|
251
263
|
* One turn as a Think agent's plan (`LumenAgent` in `@columenlabs/lumen-ai/agent`): the person's
|
|
@@ -263,7 +275,7 @@ export class Assistant {
|
|
|
263
275
|
? this.tierById(input.tier)
|
|
264
276
|
: undefined;
|
|
265
277
|
const main = await this.session(caller, lower ?? picked);
|
|
266
|
-
const rules = await this.
|
|
278
|
+
const { rules, free } = await this.rulesFor(caller, main.tier.id);
|
|
267
279
|
const { paying, wait } = await this.state.paying(caller.id, main.model.provider, rules);
|
|
268
280
|
if (!paying) {
|
|
269
281
|
const code = wait ?? 'not_connected';
|
|
@@ -376,7 +388,7 @@ export class Assistant {
|
|
|
376
388
|
const onShared = sharedLabel ? end.parts.filter((x) => x.label === sharedLabel) : [];
|
|
377
389
|
const onPayer = onShared.length ? end.parts.filter((x) => !onShared.includes(x)) : end.parts;
|
|
378
390
|
await this.state
|
|
379
|
-
.addSpend(caller.id, paying.payer, onPayer, { task: 'chat', ms: end.ms })
|
|
391
|
+
.addSpend(caller.id, paying.payer, onPayer, { task: 'chat', ms: end.ms }, free)
|
|
380
392
|
.catch(() => undefined);
|
|
381
393
|
if (onShared.length)
|
|
382
394
|
await this.state
|
|
@@ -571,7 +583,7 @@ export class Assistant {
|
|
|
571
583
|
this.state.spend(caller.id),
|
|
572
584
|
this.state.appCap(),
|
|
573
585
|
this.state.ownCap(caller.id),
|
|
574
|
-
this.
|
|
586
|
+
this.rulesFor(caller, main.id).then(({ rules }) => this.state.paying(caller.id, mainModel.provider, rules)),
|
|
575
587
|
]);
|
|
576
588
|
const link = await linkTest;
|
|
577
589
|
const tiers = await Promise.all(this.tiers.map(async (t) => {
|
|
@@ -970,7 +982,8 @@ export class Assistant {
|
|
|
970
982
|
async complete(caller, o) {
|
|
971
983
|
const began = Date.now();
|
|
972
984
|
const s = await this.session(caller, await this.mainTier(caller, o.task));
|
|
973
|
-
const {
|
|
985
|
+
const { rules, free } = await this.rulesFor(caller, s.tier.id, o.payer);
|
|
986
|
+
const { paying, wait } = await this.state.paying(caller.id, s.model.provider, rules);
|
|
974
987
|
if (!paying) {
|
|
975
988
|
const code = wait ?? 'not_connected';
|
|
976
989
|
throw new AssistantError(waitText(code, s.model.provider, !!this.o.allowance), code, 402);
|
|
@@ -1031,10 +1044,7 @@ export class Assistant {
|
|
|
1031
1044
|
}
|
|
1032
1045
|
finally {
|
|
1033
1046
|
const part = s.meter.part();
|
|
1034
|
-
await this.state.addSpend(caller.id, paying.payer, [part], {
|
|
1035
|
-
task: o.tag ?? o.task,
|
|
1036
|
-
ms: Date.now() - began,
|
|
1037
|
-
});
|
|
1047
|
+
await this.state.addSpend(caller.id, paying.payer, [part], { task: o.tag ?? o.task, ms: Date.now() - began }, free);
|
|
1038
1048
|
const task = this.tasks.find((t) => t.id === o.task);
|
|
1039
1049
|
await this.state
|
|
1040
1050
|
.addUsage(caller.id, {
|
|
@@ -94,8 +94,11 @@ export interface AllowanceDoc {
|
|
|
94
94
|
export interface RequestsDoc {
|
|
95
95
|
rows: AllowanceRequest[];
|
|
96
96
|
}
|
|
97
|
+
/** `free`: run on the app's key for a job the app does not count against the person's allowance
|
|
98
|
+
* (`AssistantOptions.credits`). Kept apart so the admin still sees what it cost. */
|
|
97
99
|
export type SpendRow = UsagePart & {
|
|
98
100
|
payer: AssistantPayer;
|
|
101
|
+
free?: true;
|
|
99
102
|
};
|
|
100
103
|
/** The ways of their own a person may pay with, per provider; a provider left out has none. */
|
|
101
104
|
export type OwnWays = Partial<Record<AssistantProvider, AssistantOwnWay[]>>;
|
|
@@ -107,6 +110,8 @@ export interface PayingRules {
|
|
|
107
110
|
fallback?: boolean;
|
|
108
111
|
/** Only this payer: an app's own check always runs on the shared key (`complete({ payer })`). */
|
|
109
112
|
only?: AssistantPayer;
|
|
113
|
+
/** The app's key, not counted against the person's allowance and never stopped by it. */
|
|
114
|
+
free?: boolean;
|
|
110
115
|
}
|
|
111
116
|
/**
|
|
112
117
|
* One task's use of one model this month: what a kind of work cost (a chat answer, a background
|
|
@@ -250,7 +255,7 @@ export declare class State {
|
|
|
250
255
|
addSpend(id: string, payer: AssistantPayer, parts: UsagePart[], at?: {
|
|
251
256
|
task: string;
|
|
252
257
|
ms?: number;
|
|
253
|
-
}): Promise<void>;
|
|
258
|
+
}, free?: boolean): Promise<void>;
|
|
254
259
|
/**
|
|
255
260
|
* Adds one answer's use to a chat or run in this month's history (`usage:YYYY-MM`): one entry a
|
|
256
261
|
* chat or run, its parts merged, the newest `USAGE_KEPT` kept. `title` names a new entry only;
|
package/dist/assistant/state.js
CHANGED
|
@@ -92,7 +92,9 @@ export class State {
|
|
|
92
92
|
async appBudget(id, spentThisMonth) {
|
|
93
93
|
const al = this.o.allowance;
|
|
94
94
|
const used = spentThisMonth ??
|
|
95
|
-
(await this.spend(id)).rows
|
|
95
|
+
(await this.spend(id)).rows
|
|
96
|
+
.filter((r) => r.payer === 'app' && !r.free)
|
|
97
|
+
.reduce((a, r) => a + r.usd, 0);
|
|
96
98
|
if (!al)
|
|
97
99
|
return { total: await this.appCap(), used };
|
|
98
100
|
const d = await this.allowance(id);
|
|
@@ -239,7 +241,7 @@ export class State {
|
|
|
239
241
|
* Adds an answer's use to this month's spend, by label and payer. `at` names the task the answer
|
|
240
242
|
* was for and how long it took, so the month also adds up per task and model (`tasks`).
|
|
241
243
|
*/
|
|
242
|
-
async addSpend(id, payer, parts, at) {
|
|
244
|
+
async addSpend(id, payer, parts, at, free = false) {
|
|
243
245
|
if (!parts.some((p) => p.calls))
|
|
244
246
|
return;
|
|
245
247
|
await this.docs.update(id, `spend:${this.month()}`, (d) => {
|
|
@@ -275,7 +277,7 @@ export class State {
|
|
|
275
277
|
for (const p of parts) {
|
|
276
278
|
if (!p.calls)
|
|
277
279
|
continue;
|
|
278
|
-
const i = rows.findIndex((r) => r.label === p.label && r.payer === payer);
|
|
280
|
+
const i = rows.findIndex((r) => r.label === p.label && r.payer === payer && !!r.free === free);
|
|
279
281
|
const prev = i >= 0 ? rows[i] : undefined;
|
|
280
282
|
const row = prev
|
|
281
283
|
? {
|
|
@@ -286,7 +288,7 @@ export class State {
|
|
|
286
288
|
out: prev.out + p.out,
|
|
287
289
|
usd: prev.usd + p.usd,
|
|
288
290
|
}
|
|
289
|
-
: { ...p, payer };
|
|
291
|
+
: { ...p, payer, ...(free ? { free: true } : {}) };
|
|
290
292
|
if (i >= 0)
|
|
291
293
|
rows[i] = row;
|
|
292
294
|
else
|
|
@@ -294,7 +296,7 @@ export class State {
|
|
|
294
296
|
}
|
|
295
297
|
return tasks.length ? { rows, tasks } : { rows };
|
|
296
298
|
});
|
|
297
|
-
if (payer === 'app' && this.o.allowance?.period === 'once') {
|
|
299
|
+
if (payer === 'app' && !free && this.o.allowance?.period === 'once') {
|
|
298
300
|
const usd = parts.reduce((a, p) => a + p.usd, 0);
|
|
299
301
|
if (usd > 0)
|
|
300
302
|
await this.setAllowance(id, (d) => ({ ...d, spent_usd: (d.spent_usd ?? 0) + usd }));
|
|
@@ -376,7 +378,7 @@ export class State {
|
|
|
376
378
|
this.spend(id),
|
|
377
379
|
this.ownCap(id),
|
|
378
380
|
]);
|
|
379
|
-
const spent = (p) => spend.rows.filter((r) => r.payer === p).reduce((a, r) => a + r.usd, 0);
|
|
381
|
+
const spent = (p) => spend.rows.filter((r) => r.payer === p && !r.free).reduce((a, r) => a + r.usd, 0);
|
|
380
382
|
const budget = await this.appBudget(id, spent('app'));
|
|
381
383
|
const app = appKeys.keys[provider];
|
|
382
384
|
const own = ownKeys.keys[provider];
|
|
@@ -387,7 +389,8 @@ export class State {
|
|
|
387
389
|
apiKey: app,
|
|
388
390
|
...appKeys,
|
|
389
391
|
cap: budget.total,
|
|
390
|
-
|
|
392
|
+
// A free job runs on the app's key whatever the person's allowance holds.
|
|
393
|
+
left: rules.free ? Number.POSITIVE_INFINITY : budget.total - budget.used,
|
|
391
394
|
}
|
|
392
395
|
: null,
|
|
393
396
|
own: own
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.240",
|
|
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.240",
|
|
56
|
+
"@columenlabs/lumen-ui-types": "0.1.240",
|
|
57
57
|
"agents": "0.32.0",
|
|
58
58
|
"ai": "7.0.162",
|
|
59
59
|
"just-bash": "3.6.0",
|