@columenlabs/lumen-ai 0.1.91 → 0.1.93
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/dist/assistant/engine.d.ts +12 -4
- package/dist/assistant/engine.js +46 -15
- package/dist/assistant/routes.js +29 -5
- package/dist/assistant/state.d.ts +18 -2
- package/dist/assistant/state.js +53 -16
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AllowanceRequest, AllowanceState, AllowanceUnit, AssistantAccess, AssistantChat, AssistantChatRow, AssistantMessage, AssistantProvider, AssistantStatus, DraftItem, StepItem } from '@columenlabs/lumen-ui-types/assistant';
|
|
1
|
+
import type { AllowanceRequest, AllowanceState, AllowanceUnit, AssistantAccess, AssistantChat, AssistantChatRow, AssistantMessage, AssistantOwnWay, 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';
|
|
@@ -234,10 +234,18 @@ export declare class Assistant {
|
|
|
234
234
|
url: string;
|
|
235
235
|
redirect: string;
|
|
236
236
|
}>;
|
|
237
|
-
/**
|
|
237
|
+
/**
|
|
238
|
+
* Finishes it from the address it ended on. The plan is kept beside the person's key; a person who
|
|
239
|
+
* already has a key keeps running on it until they pick the plan.
|
|
240
|
+
*/
|
|
238
241
|
finishSignin(caller: Caller, provider: AssistantProvider, pasted: string): Promise<void>;
|
|
239
|
-
/**
|
|
240
|
-
|
|
242
|
+
/** Signs the person out of their plan; their key, if any, runs from then on. */
|
|
243
|
+
signOut(caller: Caller, provider: AssistantProvider): Promise<void>;
|
|
244
|
+
/**
|
|
245
|
+
* Tests one of the person's own (`way`, else the one in use), refreshing a plan first, and keeps
|
|
246
|
+
* the result on it.
|
|
247
|
+
*/
|
|
248
|
+
testOwn(caller: Caller, provider: AssistantProvider, way?: AssistantOwnWay): Promise<import("./state.js").KeyTest | null>;
|
|
241
249
|
/** One free call (reading a model) that says whether the provider takes the key. */
|
|
242
250
|
testKey(provider: AssistantProvider, apiKey: string): Promise<{
|
|
243
251
|
ok: boolean;
|
package/dist/assistant/engine.js
CHANGED
|
@@ -21,7 +21,7 @@ import { HAND_OFF_WHYS } from '@columenlabs/lumen-ui-types/assistant';
|
|
|
21
21
|
import OpenAI from 'openai';
|
|
22
22
|
import { CHATGPT_REDIRECT, SigninError, finishChatgpt, startChatgpt, testChatgpt, } from './chatgpt.js';
|
|
23
23
|
import { ALL_MODELS, DEFAULT_OUT, Meter, PROVIDERS, effortParams, } from './models.js';
|
|
24
|
-
import { DEFAULT_CAP, State, keyDocName, keyState, waitText, } from './state.js';
|
|
24
|
+
import { DEFAULT_CAP, State, keyDocName, keyState, planDocName, 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';
|
|
@@ -248,10 +248,12 @@ export class Assistant {
|
|
|
248
248
|
const [p, providers, scrapeql, spend, appCap, ownCap, { paying, wait }] = await Promise.all([
|
|
249
249
|
this.state.person(caller.id),
|
|
250
250
|
Promise.all(this.providers.map(async (id) => {
|
|
251
|
-
const [doc, key, own] = await Promise.all([
|
|
251
|
+
const [doc, key, own, plan, use] = await Promise.all([
|
|
252
252
|
this.state.appKey(id),
|
|
253
253
|
this.state.appApiKey(id),
|
|
254
254
|
this.state.ownKey(caller.id, id),
|
|
255
|
+
PROVIDERS[id].signin ? this.state.ownPlan(caller.id, id) : Promise.resolve(null),
|
|
256
|
+
this.state.ownUse(caller.id, id),
|
|
255
257
|
]);
|
|
256
258
|
const app = caller.admin
|
|
257
259
|
? keyState(doc, (d) => d.api_key)
|
|
@@ -264,6 +266,10 @@ export class Assistant {
|
|
|
264
266
|
...(PROVIDERS[id].signin ? { signin: PROVIDERS[id].signin } : {}),
|
|
265
267
|
app: { ...app, ...(key?.fromEnv ? { from_env: true } : {}) },
|
|
266
268
|
own: keyState(own, (d) => d.api_key),
|
|
269
|
+
...(PROVIDERS[id].signin
|
|
270
|
+
? { plan: keyState(plan?.doc ?? null, (d) => d.api_key) }
|
|
271
|
+
: {}),
|
|
272
|
+
...(use ? { use } : {}),
|
|
267
273
|
};
|
|
268
274
|
})),
|
|
269
275
|
this.o.web?.scrapeql ? this.state.scrapeql() : Promise.resolve(null),
|
|
@@ -1421,19 +1427,27 @@ export class Assistant {
|
|
|
1421
1427
|
await this.state.docs.write(caller.id, name, reg);
|
|
1422
1428
|
return { url, redirect: CHATGPT_REDIRECT };
|
|
1423
1429
|
}
|
|
1424
|
-
/**
|
|
1430
|
+
/**
|
|
1431
|
+
* Finishes it from the address it ended on. The plan is kept beside the person's key; a person who
|
|
1432
|
+
* already has a key keeps running on it until they pick the plan.
|
|
1433
|
+
*/
|
|
1425
1434
|
async finishSignin(caller, provider, pasted) {
|
|
1426
1435
|
const name = `signin:${provider}`;
|
|
1427
1436
|
try {
|
|
1428
1437
|
const { tokens, reg } = await finishChatgpt(await this.state.docs.read(caller.id, name), pasted, this.o.fetch ?? fetch);
|
|
1429
1438
|
await this.state.docs.write(caller.id, name, reg);
|
|
1430
1439
|
const test = await testChatgpt(tokens, this.o.fetch ?? fetch);
|
|
1431
|
-
await this.state.
|
|
1440
|
+
const legacy = await this.state.ownPlan(caller.id, provider);
|
|
1441
|
+
await this.state.docs.write(caller.id, planDocName(provider), {
|
|
1432
1442
|
api_key: tokens.access_token,
|
|
1433
1443
|
saved_at: Date.now(),
|
|
1434
1444
|
test,
|
|
1435
1445
|
chatgpt: tokens,
|
|
1436
1446
|
});
|
|
1447
|
+
if (legacy && legacy.name !== planDocName(provider))
|
|
1448
|
+
await this.state.docs.remove(caller.id, legacy.name);
|
|
1449
|
+
if (await this.state.ownKey(caller.id, provider))
|
|
1450
|
+
await this.state.setPerson(caller.id, (d) => d.own_use?.[provider] ? d : { ...d, own_use: { ...d.own_use, [provider]: 'key' } });
|
|
1437
1451
|
}
|
|
1438
1452
|
catch (err) {
|
|
1439
1453
|
if (err instanceof SigninError)
|
|
@@ -1441,18 +1455,35 @@ export class Assistant {
|
|
|
1441
1455
|
throw err;
|
|
1442
1456
|
}
|
|
1443
1457
|
}
|
|
1444
|
-
/**
|
|
1445
|
-
async
|
|
1446
|
-
const
|
|
1447
|
-
if (
|
|
1458
|
+
/** Signs the person out of their plan; their key, if any, runs from then on. */
|
|
1459
|
+
async signOut(caller, provider) {
|
|
1460
|
+
const found = await this.state.ownPlan(caller.id, provider);
|
|
1461
|
+
if (found)
|
|
1462
|
+
await this.state.docs.remove(caller.id, found.name);
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Tests one of the person's own (`way`, else the one in use), refreshing a plan first, and keeps
|
|
1466
|
+
* the result on it.
|
|
1467
|
+
*/
|
|
1468
|
+
async testOwn(caller, provider, way) {
|
|
1469
|
+
const w = way ?? (await this.state.ownUse(caller.id, provider));
|
|
1470
|
+
if (w === 'key') {
|
|
1471
|
+
const d = await this.state.ownKey(caller.id, provider);
|
|
1472
|
+
if (!d)
|
|
1473
|
+
return null;
|
|
1474
|
+
const test = await this.testKey(provider, d.api_key);
|
|
1475
|
+
await this.state.docs.write(caller.id, keyDocName(provider), { ...d, test });
|
|
1476
|
+
return test;
|
|
1477
|
+
}
|
|
1478
|
+
if (w !== 'plan' || !(await this.state.ownPlan(caller.id, provider)))
|
|
1448
1479
|
return null;
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
return
|
|
1480
|
+
const token = await this.state.planToken(caller.id, provider);
|
|
1481
|
+
const now = await this.state.ownPlan(caller.id, provider);
|
|
1482
|
+
if (!token || !now)
|
|
1483
|
+
return now?.doc.test ?? { ok: false, detail: 'Sign in again.', at: Date.now() };
|
|
1484
|
+
const test = await testChatgpt(now.doc.chatgpt, this.o.fetch ?? fetch);
|
|
1485
|
+
await this.state.docs.write(caller.id, now.name, { ...now.doc, test });
|
|
1486
|
+
return test;
|
|
1456
1487
|
}
|
|
1457
1488
|
/** One free call (reading a model) that says whether the provider takes the key. */
|
|
1458
1489
|
async testKey(provider, apiKey) {
|
package/dist/assistant/routes.js
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
* PUT /my-key/cap {cap_usd} what the person may spend a month on their own keys
|
|
15
15
|
* POST /my-signin starts a ChatGPT plan sign-in (`?provider=openai`): {url, redirect}
|
|
16
16
|
* POST /my-signin/finish {url} finishes it from the 127.0.0.1 address the sign-in ended on
|
|
17
|
+
* DELETE /my-signin signs out of the plan (the person's key, if set, runs on)
|
|
18
|
+
* (`POST /my-key/test?way=key|plan` tests one; the plan and the
|
|
19
|
+
* key are kept side by side, `own_use` in /settings picks)
|
|
17
20
|
* PUT /web/scrapeql {client_id, client_secret}, DELETE /web/scrapeql, POST /web/test admin
|
|
18
21
|
* POST /chat one answer as server-sent events; closing the request stops it
|
|
19
22
|
* GET /chats, GET|DELETE /chats/:id
|
|
@@ -27,10 +30,10 @@
|
|
|
27
30
|
* form or fetch is refused before it reaches a rule. Keys and tokens are write-only: no route
|
|
28
31
|
* returns one, only a hint (`sk-ant-…abcd`).
|
|
29
32
|
*/
|
|
30
|
-
import { ASSISTANT_ACCESS, ASSISTANT_EFFORTS, ASSISTANT_PAYERS, HAND_OFF_POLICIES, } from '@columenlabs/lumen-ui-types/assistant';
|
|
33
|
+
import { ASSISTANT_ACCESS, ASSISTANT_EFFORTS, ASSISTANT_OWN_WAYS, ASSISTANT_PAYERS, HAND_OFF_POLICIES, } from '@columenlabs/lumen-ui-types/assistant';
|
|
31
34
|
import { AssistantError } from './engine.js';
|
|
32
35
|
import { PROVIDERS } from './models.js';
|
|
33
|
-
import { keyDocName } from './state.js';
|
|
36
|
+
import { keyDocName, planDocName } from './state.js';
|
|
34
37
|
import { APP_SCOPE } from './store.js';
|
|
35
38
|
const MUTATING = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
36
39
|
const MAX_MESSAGE = 20_000;
|
|
@@ -101,6 +104,16 @@ function settingsChange(a, groups, b) {
|
|
|
101
104
|
return 'web is {fetch: true | false}.';
|
|
102
105
|
if (p.pay !== undefined && !oneOf(ASSISTANT_PAYERS, p.pay))
|
|
103
106
|
return 'pay is app or own.';
|
|
107
|
+
if (p.own_use !== undefined) {
|
|
108
|
+
if (!p.own_use || typeof p.own_use !== 'object')
|
|
109
|
+
return 'own_use is {provider: key | plan}.';
|
|
110
|
+
for (const [prov, w] of Object.entries(p.own_use)) {
|
|
111
|
+
if (!a.providers.includes(prov))
|
|
112
|
+
return `No provider ${prov}.`;
|
|
113
|
+
if (!oneOf(ASSISTANT_OWN_WAYS, w))
|
|
114
|
+
return `No way ${w}.`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
104
117
|
return (d) => {
|
|
105
118
|
const tiers = { ...(d.tiers ?? {}) };
|
|
106
119
|
for (const [id, t] of Object.entries(p.tiers ?? {})) {
|
|
@@ -118,6 +131,7 @@ function settingsChange(a, groups, b) {
|
|
|
118
131
|
...(p.access !== undefined ? { access: { ...(d.access ?? {}), ...p.access } } : {}),
|
|
119
132
|
...(p.web !== undefined ? { web: p.web.fetch } : {}),
|
|
120
133
|
...(p.pay !== undefined ? { pay: p.pay } : {}),
|
|
134
|
+
...(p.own_use !== undefined ? { own_use: { ...(d.own_use ?? {}), ...p.own_use } } : {}),
|
|
121
135
|
};
|
|
122
136
|
};
|
|
123
137
|
}
|
|
@@ -195,6 +209,10 @@ export function assistantRoutes(app, o) {
|
|
|
195
209
|
if (!test.ok)
|
|
196
210
|
return fail(test.detail, 400, 'key_refused');
|
|
197
211
|
const scope = mine ? caller.id : APP_SCOPE;
|
|
212
|
+
// A plan sign-in from before 2026-10-27 sits in the key's doc: move it beside the key.
|
|
213
|
+
const legacy = mine ? await a.state.ownPlan(caller.id, prov) : null;
|
|
214
|
+
if (legacy?.name === docName)
|
|
215
|
+
await docs.write(scope, planDocName(prov), legacy.doc);
|
|
198
216
|
await docs.write(scope, docName, { api_key: key, saved_at: Date.now(), test });
|
|
199
217
|
return status();
|
|
200
218
|
}
|
|
@@ -218,6 +236,9 @@ export function assistantRoutes(app, o) {
|
|
|
218
236
|
}
|
|
219
237
|
case 'POST /my-signin':
|
|
220
238
|
return json(await a.startSignin(caller, prov));
|
|
239
|
+
case 'DELETE /my-signin':
|
|
240
|
+
await a.signOut(caller, prov);
|
|
241
|
+
return status();
|
|
221
242
|
case 'POST /my-signin/finish': {
|
|
222
243
|
const pasted = str((await body(req)).url, 4000);
|
|
223
244
|
if (!pasted)
|
|
@@ -226,10 +247,13 @@ export function assistantRoutes(app, o) {
|
|
|
226
247
|
return status();
|
|
227
248
|
}
|
|
228
249
|
case 'POST /my-key/test': {
|
|
229
|
-
const
|
|
250
|
+
const askedWay = url.searchParams.get('way');
|
|
251
|
+
const way = oneOf(ASSISTANT_OWN_WAYS, askedWay);
|
|
252
|
+
if (askedWay !== null && !way)
|
|
253
|
+
return fail('way is key or plan.');
|
|
254
|
+
const test = await a.testOwn(caller, prov, way);
|
|
230
255
|
if (!test)
|
|
231
|
-
return fail('No key is set.', 404);
|
|
232
|
-
await docs.update(caller.id, docName, (d) => (d ? { ...d, test } : null));
|
|
256
|
+
return fail(way === 'plan' ? 'Not signed in.' : 'No key is set.', 404);
|
|
233
257
|
return json(test);
|
|
234
258
|
}
|
|
235
259
|
case 'POST /key/test': {
|
|
@@ -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, AssistantPayer, AssistantProvider, AssistantWait, HandOffPolicy, UsagePart } from '@columenlabs/lumen-ui-types/assistant';
|
|
22
|
+
import type { AllowanceRequest, AssistantAccess, AssistantEffort, AssistantOwnWay, AssistantPayer, AssistantProvider, AssistantWait, HandOffPolicy, 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 {
|
|
@@ -38,6 +38,8 @@ export interface KeyDoc {
|
|
|
38
38
|
}
|
|
39
39
|
/** Where a provider's key is kept: `key` for Anthropic, `key:<provider>` for the rest. */
|
|
40
40
|
export declare const keyDocName: (provider: AssistantProvider) => string;
|
|
41
|
+
/** Where a person's plan sign-in is kept, beside their key (before 2026-10-27 it replaced the key). */
|
|
42
|
+
export declare const planDocName: (provider: AssistantProvider) => string;
|
|
41
43
|
export interface TokenDoc {
|
|
42
44
|
client_id: string;
|
|
43
45
|
client_secret: string;
|
|
@@ -62,6 +64,8 @@ export interface PersonDoc {
|
|
|
62
64
|
pay?: AssistantPayer;
|
|
63
65
|
/** USD a month on their own keys. */
|
|
64
66
|
own_cap?: number;
|
|
67
|
+
/** Which of their own runs on a provider when both a key and a plan are set. */
|
|
68
|
+
own_use?: Partial<Record<AssistantProvider, AssistantOwnWay>>;
|
|
65
69
|
}
|
|
66
70
|
/** How the app counts allowance on its shared keys (`createAssistant` `allowance`). */
|
|
67
71
|
export interface AllowanceConfig {
|
|
@@ -156,8 +160,20 @@ export declare class State {
|
|
|
156
160
|
key: string;
|
|
157
161
|
fromEnv: boolean;
|
|
158
162
|
} | null>;
|
|
163
|
+
/** The person's own API key (a key doc that holds a plan sign-in is their plan). */
|
|
159
164
|
ownKey(id: string, provider: AssistantProvider): Promise<KeyDoc | null>;
|
|
160
|
-
/** The person's
|
|
165
|
+
/** The person's plan sign-in and the doc it is kept in (the key's, for a sign-in from before). */
|
|
166
|
+
ownPlan(id: string, provider: AssistantProvider): Promise<{
|
|
167
|
+
doc: KeyDoc & {
|
|
168
|
+
chatgpt: ChatgptTokens;
|
|
169
|
+
};
|
|
170
|
+
name: string;
|
|
171
|
+
} | null>;
|
|
172
|
+
/** Which of their own runs: the one they picked while it is set, else the plan, else the key. */
|
|
173
|
+
ownUse(id: string, provider: AssistantProvider): Promise<AssistantOwnWay | null>;
|
|
174
|
+
/** The plan's access token, refreshed first when it is about to run out; null once it fails. */
|
|
175
|
+
planToken(id: string, provider: AssistantProvider): Promise<string | null>;
|
|
176
|
+
/** The person's own key or plan, the one in use first, the other when that one fails. */
|
|
161
177
|
ownApiKey(id: string, provider: AssistantProvider): Promise<{
|
|
162
178
|
key: string;
|
|
163
179
|
plan: boolean;
|
package/dist/assistant/state.js
CHANGED
|
@@ -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
|
+
/** Where a person's plan sign-in is kept, beside their key (before 2026-10-27 it replaced the key). */
|
|
7
|
+
export const planDocName = (provider) => `plan:${provider}`;
|
|
6
8
|
/** Requests kept for approvers: the newest this many. */
|
|
7
9
|
const REQUESTS_KEPT = 300;
|
|
8
10
|
/** USD a month when nobody set a cap. */
|
|
@@ -107,35 +109,70 @@ export class State {
|
|
|
107
109
|
const env = await this.o.appKey?.(provider);
|
|
108
110
|
return env ? { key: env, fromEnv: true } : null;
|
|
109
111
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
/** The person's own API key (a key doc that holds a plan sign-in is their plan). */
|
|
113
|
+
async ownKey(id, provider) {
|
|
114
|
+
const d = await this.docs.read(id, keyDocName(provider));
|
|
115
|
+
return d && !d.chatgpt ? d : null;
|
|
112
116
|
}
|
|
113
|
-
/** The person's
|
|
114
|
-
async
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
+
/** The person's plan sign-in and the doc it is kept in (the key's, for a sign-in from before). */
|
|
118
|
+
async ownPlan(id, provider) {
|
|
119
|
+
for (const name of [planDocName(provider), keyDocName(provider)]) {
|
|
120
|
+
const doc = await this.docs.read(id, name);
|
|
121
|
+
if (doc?.chatgpt)
|
|
122
|
+
return { doc: doc, name };
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
/** Which of their own runs: the one they picked while it is set, else the plan, else the key. */
|
|
127
|
+
async ownUse(id, provider) {
|
|
128
|
+
const [p, key, plan] = await Promise.all([
|
|
129
|
+
this.person(id),
|
|
130
|
+
this.ownKey(id, provider),
|
|
131
|
+
this.ownPlan(id, provider),
|
|
132
|
+
]);
|
|
133
|
+
const use = p.own_use?.[provider];
|
|
134
|
+
if (use === 'key' && key)
|
|
135
|
+
return 'key';
|
|
136
|
+
if (use === 'plan' && plan)
|
|
137
|
+
return 'plan';
|
|
138
|
+
return plan ? 'plan' : key ? 'key' : null;
|
|
139
|
+
}
|
|
140
|
+
/** The plan's access token, refreshed first when it is about to run out; null once it fails. */
|
|
141
|
+
async planToken(id, provider) {
|
|
142
|
+
const found = await this.ownPlan(id, provider);
|
|
143
|
+
if (!found)
|
|
117
144
|
return null;
|
|
118
|
-
|
|
119
|
-
return { key: d.api_key, plan: false };
|
|
145
|
+
const { doc: d, name } = found;
|
|
120
146
|
if (!staleChatgpt(d.chatgpt))
|
|
121
|
-
return
|
|
147
|
+
return d.chatgpt.access_token;
|
|
122
148
|
try {
|
|
123
149
|
const t = await refreshChatgpt(d.chatgpt, this.o.fetch ?? fetch);
|
|
124
|
-
await this.docs.write(id,
|
|
125
|
-
|
|
126
|
-
api_key: t.access_token,
|
|
127
|
-
chatgpt: t,
|
|
128
|
-
});
|
|
129
|
-
return { key: t.access_token, plan: true };
|
|
150
|
+
await this.docs.write(id, name, { ...d, api_key: t.access_token, chatgpt: t });
|
|
151
|
+
return t.access_token;
|
|
130
152
|
}
|
|
131
153
|
catch (err) {
|
|
132
|
-
await this.docs.write(id,
|
|
154
|
+
await this.docs.write(id, name, {
|
|
133
155
|
...d,
|
|
134
156
|
test: { at: Date.now(), ok: false, detail: err.message },
|
|
135
157
|
});
|
|
136
158
|
return null;
|
|
137
159
|
}
|
|
138
160
|
}
|
|
161
|
+
/** The person's own key or plan, the one in use first, the other when that one fails. */
|
|
162
|
+
async ownApiKey(id, provider) {
|
|
163
|
+
const use = await this.ownUse(id, provider);
|
|
164
|
+
if (!use)
|
|
165
|
+
return null;
|
|
166
|
+
const key = async () => {
|
|
167
|
+
const d = await this.ownKey(id, provider);
|
|
168
|
+
return d ? { key: d.api_key, plan: false } : null;
|
|
169
|
+
};
|
|
170
|
+
const plan = async () => {
|
|
171
|
+
const t = await this.planToken(id, provider);
|
|
172
|
+
return t ? { key: t, plan: true } : null;
|
|
173
|
+
};
|
|
174
|
+
return use === 'key' ? ((await key()) ?? (await plan())) : ((await plan()) ?? (await key()));
|
|
175
|
+
}
|
|
139
176
|
/** USD a month on the person's own keys. */
|
|
140
177
|
async ownCap(id) {
|
|
141
178
|
const p = await this.person(id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.93",
|
|
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.
|
|
45
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
44
|
+
"@columenlabs/lumen-ops": "0.1.93",
|
|
45
|
+
"@columenlabs/lumen-ui-types": "0.1.93",
|
|
46
46
|
"openai": "7.32.0",
|
|
47
47
|
"zod": "^4.3.6"
|
|
48
48
|
},
|