@columenlabs/lumen-ai 0.1.229 → 0.1.231
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 +9 -0
- package/dist/assistant/engine.d.ts +36 -2
- package/dist/assistant/engine.js +58 -15
- package/dist/assistant/routes.js +9 -0
- package/dist/assistant/state.d.ts +14 -3
- package/dist/assistant/state.js +24 -8
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -72,6 +72,7 @@ const assistant = createAssistant({
|
|
|
72
72
|
name: 'Helper',
|
|
73
73
|
system: 'You help people with their notes. Use the notes tools for facts.',
|
|
74
74
|
hideModel: true, // never names its model, provider or tier; asked, it is just "Helper"
|
|
75
|
+
// pickTier: false, // the app picks each turn's tier (agentTurn input.tier); no tier menu in the panel
|
|
75
76
|
// Smallest first. The app names them; a person picks each tier's model and effort.
|
|
76
77
|
tiers: [
|
|
77
78
|
{ id: 'fast', label: 'Fast', about: 'Quick lookups', model: 'claude-haiku-5-5' },
|
|
@@ -86,7 +87,15 @@ const assistant = createAssistant({
|
|
|
86
87
|
sealer, // the app's own sealing: keys and chats are stored sealed
|
|
87
88
|
// A provider's key when none is set in Settings.
|
|
88
89
|
appKey: async (provider) => (provider === 'openai' ? env.OPENAI_API_KEY : env.ANTHROPIC_API_KEY),
|
|
90
|
+
// Bring your own AI, per caller (a feature flag, a person's plan): which own ways Settings offers
|
|
91
|
+
// and the routes accept, whether their own key falls back to the shared one, and who finishes a
|
|
92
|
+
// turn when a ChatGPT plan runs out ('shared': the same model on the app's key, shared spend).
|
|
93
|
+
// ownWays: async (caller) => ({ openai: ['key', 'plan'] }),
|
|
94
|
+
// ownFallback: async (caller) => true,
|
|
95
|
+
// planFallback: 'shared',
|
|
89
96
|
});
|
|
97
|
+
// A check the app always runs on its own key, whoever pays the person's answers:
|
|
98
|
+
// await assistant.complete(caller, { task: 'scope', system, prompt, payer: 'app' });
|
|
90
99
|
|
|
91
100
|
const routes = assistantRoutes(assistant, {
|
|
92
101
|
base: '/api/assistant',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { AllowanceRequest, AllowanceState, AllowanceUnit, AssistantAccess, AssistantChat, AssistantChatRow, AssistantEffort, AssistantMessage, AssistantOwnWay, AssistantProvider, AssistantStatus, UsagePart } from '@columenlabs/lumen-ui-types/assistant';
|
|
1
|
+
import type { AllowanceRequest, AllowanceState, AllowanceUnit, AssistantAccess, AssistantChat, AssistantChatRow, AssistantEffort, AssistantMessage, AssistantOwnWay, AssistantPayer, 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';
|
|
5
|
-
import { State } from './state.js';
|
|
5
|
+
import { type OwnWays, State } from './state.js';
|
|
6
6
|
import { type AssistantStore, type Sealer } from './store.js';
|
|
7
7
|
import { type ToolSource } from './tools.js';
|
|
8
8
|
import { type AccessToken, type WebFetchOptions } from './web.js';
|
|
@@ -142,6 +142,12 @@ export interface AssistantOptions {
|
|
|
142
142
|
* it, and answers "which model are you?" with its own `name`. Off by default (it names them).
|
|
143
143
|
*/
|
|
144
144
|
hideModel?: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Who picks the tier a chat turn runs on. False: the app does, each turn, through
|
|
147
|
+
* `AgentTurnInput.tier` (any of its tiers; left out, the chat task's tier). The person's saved
|
|
148
|
+
* tier and task picks are ignored and the panel offers no tier menu. True by default.
|
|
149
|
+
*/
|
|
150
|
+
pickTier?: boolean;
|
|
145
151
|
/**
|
|
146
152
|
* The app's slash commands, listed in the composer when the person types `/`. A message
|
|
147
153
|
* `/name words` is sent to the model as `prompt` with `{args}` replaced by the words (or what the
|
|
@@ -188,6 +194,23 @@ export interface AssistantOptions {
|
|
|
188
194
|
keepDays?: number;
|
|
189
195
|
/** The fetch the model providers and ScrapeQL calls go through. */
|
|
190
196
|
fetch?: typeof fetch;
|
|
197
|
+
/**
|
|
198
|
+
* Which of their own ways (an API key, a plan sign-in) a person may pay with, per provider: a
|
|
199
|
+
* provider left out has none, and Settings shows only these. Left out, every way of every
|
|
200
|
+
* provider (feature flags or a person's plan decide it per caller).
|
|
201
|
+
*/
|
|
202
|
+
ownWays?: (caller: Caller) => OwnWays | Promise<OwnWays>;
|
|
203
|
+
/**
|
|
204
|
+
* Whether a person who pays with their own key first moves to the shared key when theirs is at
|
|
205
|
+
* its cap or their plan is used up. True by default.
|
|
206
|
+
*/
|
|
207
|
+
ownFallback?: (caller: Caller) => boolean | Promise<boolean>;
|
|
208
|
+
/**
|
|
209
|
+
* Who answers when a ChatGPT plan reaches its usage limit mid-turn. 'claude' (default): the
|
|
210
|
+
* app's smallest Claude on the same payer's Anthropic key. 'shared': the same model on the app's
|
|
211
|
+
* shared key, counted as shared spend (only when `ownFallback` allows it).
|
|
212
|
+
*/
|
|
213
|
+
planFallback?: 'claude' | 'shared';
|
|
191
214
|
onStep?: (s: StepRecord) => void | Promise<void>;
|
|
192
215
|
}
|
|
193
216
|
/** A turn's failure the person can act on. */
|
|
@@ -244,6 +267,12 @@ export declare class Assistant {
|
|
|
244
267
|
private mainTier;
|
|
245
268
|
private access;
|
|
246
269
|
private groupById;
|
|
270
|
+
/** The own ways the app lets this person pay with (`ownWays`); undefined is every way. */
|
|
271
|
+
ownWaysOf(caller: Caller): Promise<OwnWays | undefined>;
|
|
272
|
+
/** Whether this person may use `way` of their own on `provider`. */
|
|
273
|
+
mayOwn(caller: Caller, provider: AssistantProvider, way: AssistantOwnWay): Promise<boolean>;
|
|
274
|
+
/** The app's rules for who pays this person's answers (own ways, fallback). */
|
|
275
|
+
private payingRules;
|
|
247
276
|
/**
|
|
248
277
|
* One turn as a Think agent's plan (`LumenAgent` in `@columenlabs/lumen-ai/agent`): the person's
|
|
249
278
|
* tier and its model on whoever pays, the tool groups at their access, the kit's rules, the
|
|
@@ -343,6 +372,11 @@ export declare class Assistant {
|
|
|
343
372
|
* task does more than one kind of work (a first try, a repair). Left out, it is the task.
|
|
344
373
|
*/
|
|
345
374
|
tag?: string;
|
|
375
|
+
/**
|
|
376
|
+
* Only this payer: 'app' runs the call on the shared key whatever the person pays with (a
|
|
377
|
+
* check the app always runs on its own key). Left out, whoever pays the person's answers.
|
|
378
|
+
*/
|
|
379
|
+
payer?: AssistantPayer;
|
|
346
380
|
}): Promise<{
|
|
347
381
|
text: string;
|
|
348
382
|
usd: number;
|
package/dist/assistant/engine.js
CHANGED
|
@@ -216,7 +216,7 @@ export class Assistant {
|
|
|
216
216
|
}
|
|
217
217
|
/** The tier the person's chat runs on: their pick, else the chat task's start tier. */
|
|
218
218
|
async mainTier(caller, task = 'chat') {
|
|
219
|
-
const p = await this.state.person(caller.id);
|
|
219
|
+
const p = this.o.pickTier === false ? {} : await this.state.person(caller.id);
|
|
220
220
|
const startId = p.tasks?.[task] ?? this.tasks.find((t) => t.id === task)?.tier;
|
|
221
221
|
return (this.tierById(task === 'chat' ? (p.tier ?? startId) : startId) ?? this.tiers[0]);
|
|
222
222
|
}
|
|
@@ -229,6 +229,23 @@ export class Assistant {
|
|
|
229
229
|
groupById(id) {
|
|
230
230
|
return this.groups.find((g) => g.id === id);
|
|
231
231
|
}
|
|
232
|
+
/** The own ways the app lets this person pay with (`ownWays`); undefined is every way. */
|
|
233
|
+
async ownWaysOf(caller) {
|
|
234
|
+
return this.o.ownWays ? await this.o.ownWays(caller) : undefined;
|
|
235
|
+
}
|
|
236
|
+
/** Whether this person may use `way` of their own on `provider`. */
|
|
237
|
+
async mayOwn(caller, provider, way) {
|
|
238
|
+
const ways = await this.ownWaysOf(caller);
|
|
239
|
+
return !ways || !!ways[provider]?.includes(way);
|
|
240
|
+
}
|
|
241
|
+
/** The app's rules for who pays this person's answers (own ways, fallback). */
|
|
242
|
+
async payingRules(caller, only) {
|
|
243
|
+
const [ways, fallback] = await Promise.all([
|
|
244
|
+
this.ownWaysOf(caller),
|
|
245
|
+
this.o.ownFallback ? this.o.ownFallback(caller) : true,
|
|
246
|
+
]);
|
|
247
|
+
return { ...(ways ? { ways } : {}), fallback, ...(only ? { only } : {}) };
|
|
248
|
+
}
|
|
232
249
|
// ---------------------------------------------------------------- a turn on Think
|
|
233
250
|
/**
|
|
234
251
|
* One turn as a Think agent's plan (`LumenAgent` in `@columenlabs/lumen-ai/agent`): the person's
|
|
@@ -240,11 +257,14 @@ export class Assistant {
|
|
|
240
257
|
async agentTurn(caller, input) {
|
|
241
258
|
const message = input.message.trim();
|
|
242
259
|
const picked = await this.mainTier(caller);
|
|
243
|
-
|
|
260
|
+
// The app picks the tier (pickTier: false): any of its tiers, never a hand off.
|
|
261
|
+
const byApp = this.o.pickTier === false;
|
|
262
|
+
const lower = input.tier && (byApp || !this.above(picked).some((t) => t.id === input.tier))
|
|
244
263
|
? this.tierById(input.tier)
|
|
245
264
|
: undefined;
|
|
246
265
|
const main = await this.session(caller, lower ?? picked);
|
|
247
|
-
const
|
|
266
|
+
const rules = await this.payingRules(caller);
|
|
267
|
+
const { paying, wait } = await this.state.paying(caller.id, main.model.provider, rules);
|
|
248
268
|
if (!paying) {
|
|
249
269
|
const code = wait ?? 'not_connected';
|
|
250
270
|
throw new AssistantError(waitText(code, main.model.provider, !!this.o.allowance), code, 402);
|
|
@@ -254,17 +274,30 @@ export class Assistant {
|
|
|
254
274
|
return apiKey ? { apiKey, ...(paying.plans[provider] ? { plan: true } : {}) } : null;
|
|
255
275
|
};
|
|
256
276
|
const key = keyOf(main.model.provider) ?? { apiKey: paying.apiKey };
|
|
257
|
-
// A ChatGPT plan used up for now: the
|
|
258
|
-
// the
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
277
|
+
// A ChatGPT plan used up for now: rather than the turn ending in the limit's error, the same
|
|
278
|
+
// model on the app's shared key ('shared'), or the app's first Claude (its smallest) on the
|
|
279
|
+
// same payer's Anthropic key.
|
|
280
|
+
let fallback;
|
|
281
|
+
let sharedLabel;
|
|
282
|
+
if (key.plan && this.o.planFallback === 'shared') {
|
|
283
|
+
const shared = rules.fallback !== false
|
|
284
|
+
? (await this.state.paying(caller.id, main.model.provider, { only: 'app' })).paying
|
|
285
|
+
: null;
|
|
286
|
+
if (shared) {
|
|
287
|
+
sharedLabel = `${main.meter.label} · shared key`;
|
|
288
|
+
fallback = { model: main.model, key: { apiKey: shared.apiKey }, label: sharedLabel };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else if (key.plan) {
|
|
292
|
+
const claude = this.models.find((m) => m.provider === 'anthropic');
|
|
293
|
+
const claudeKey = claude ? keyOf('anthropic') : null;
|
|
294
|
+
if (claude && claudeKey)
|
|
295
|
+
fallback = { model: claude, key: claudeKey, label: `${main.tier.label} · ${claude.label}` };
|
|
296
|
+
}
|
|
264
297
|
const p = await this.state.person(caller.id);
|
|
265
298
|
const policy = p.hand_off ?? 'next';
|
|
266
299
|
const advisor = await this.advisorFor(caller, p.advice, main);
|
|
267
|
-
const handTo = input.tier ? this.above(main.tier).find((t) => t.id === input.tier) : undefined;
|
|
300
|
+
const handTo = input.tier && !byApp ? this.above(main.tier).find((t) => t.id === input.tier) : undefined;
|
|
268
301
|
const appSystem = typeof this.o.system === 'string' ? this.o.system : await this.o.system(caller);
|
|
269
302
|
const asked = await this.expand(caller, message);
|
|
270
303
|
const told = await bearings({
|
|
@@ -339,9 +372,16 @@ export class Assistant {
|
|
|
339
372
|
: {},
|
|
340
373
|
};
|
|
341
374
|
const settle = async (end) => {
|
|
375
|
+
// What the shared key answered after a plan ran out is the app's spend, not the person's.
|
|
376
|
+
const onShared = sharedLabel ? end.parts.filter((x) => x.label === sharedLabel) : [];
|
|
377
|
+
const onPayer = onShared.length ? end.parts.filter((x) => !onShared.includes(x)) : end.parts;
|
|
342
378
|
await this.state
|
|
343
|
-
.addSpend(caller.id, paying.payer,
|
|
379
|
+
.addSpend(caller.id, paying.payer, onPayer, { task: 'chat', ms: end.ms })
|
|
344
380
|
.catch(() => undefined);
|
|
381
|
+
if (onShared.length)
|
|
382
|
+
await this.state
|
|
383
|
+
.addSpend(caller.id, 'app', onShared, { task: 'chat' })
|
|
384
|
+
.catch(() => undefined);
|
|
345
385
|
if (input.chat)
|
|
346
386
|
await this.state
|
|
347
387
|
.addUsage(caller.id, { id: `chat:${input.chat}`, kind: 'chat', title: message.split('\n')[0] || 'Chat' }, end.parts)
|
|
@@ -493,6 +533,7 @@ export class Assistant {
|
|
|
493
533
|
const mainModel = (await this.pick(caller, main)).model;
|
|
494
534
|
// The binding's last test has no token doc to sit in; it is read beside the rest.
|
|
495
535
|
const linkTest = this.o.web?.scrapeql?.fetch ? this.state.scrapeqlLink() : null;
|
|
536
|
+
const ownWays = await this.ownWaysOf(caller);
|
|
496
537
|
const [p, providers, scrapeql, spend, appCap, ownCap, { paying, wait }] = await Promise.all([
|
|
497
538
|
this.state.person(caller.id),
|
|
498
539
|
Promise.all(this.providers.map(async (id) => {
|
|
@@ -518,13 +559,14 @@ export class Assistant {
|
|
|
518
559
|
? { plan: keyState(plan?.doc ?? null, (d) => d.api_key) }
|
|
519
560
|
: {}),
|
|
520
561
|
...(use ? { use } : {}),
|
|
562
|
+
...(ownWays ? { own_ways: ownWays[id] ?? [] } : {}),
|
|
521
563
|
};
|
|
522
564
|
})),
|
|
523
565
|
this.o.web?.scrapeql ? this.state.scrapeql() : Promise.resolve(null),
|
|
524
566
|
this.state.spend(caller.id),
|
|
525
567
|
this.state.appCap(),
|
|
526
568
|
this.state.ownCap(caller.id),
|
|
527
|
-
this.state.paying(caller.id, mainModel.provider),
|
|
569
|
+
this.payingRules(caller).then((r) => this.state.paying(caller.id, mainModel.provider, r)),
|
|
528
570
|
]);
|
|
529
571
|
const link = await linkTest;
|
|
530
572
|
const tiers = await Promise.all(this.tiers.map(async (t) => {
|
|
@@ -551,6 +593,7 @@ export class Assistant {
|
|
|
551
593
|
admin: caller.admin,
|
|
552
594
|
tier: main.id,
|
|
553
595
|
tiers,
|
|
596
|
+
...(this.o.pickTier === false ? { pick_tier: false } : {}),
|
|
554
597
|
models: this.models.map((m) => ({
|
|
555
598
|
id: m.id,
|
|
556
599
|
label: m.label,
|
|
@@ -922,7 +965,7 @@ export class Assistant {
|
|
|
922
965
|
async complete(caller, o) {
|
|
923
966
|
const began = Date.now();
|
|
924
967
|
const s = await this.session(caller, await this.mainTier(caller, o.task));
|
|
925
|
-
const { paying, wait } = await this.state.paying(caller.id, s.model.provider);
|
|
968
|
+
const { paying, wait } = await this.state.paying(caller.id, s.model.provider, await this.payingRules(caller, o.payer));
|
|
926
969
|
if (!paying) {
|
|
927
970
|
const code = wait ?? 'not_connected';
|
|
928
971
|
throw new AssistantError(waitText(code, s.model.provider, !!this.o.allowance), code, 402);
|
|
@@ -1009,7 +1052,7 @@ export class Assistant {
|
|
|
1009
1052
|
*/
|
|
1010
1053
|
async image(caller, o) {
|
|
1011
1054
|
const began = Date.now();
|
|
1012
|
-
const { paying, wait } = await this.state.paying(caller.id, 'openai');
|
|
1055
|
+
const { paying, wait } = await this.state.paying(caller.id, 'openai', await this.payingRules(caller));
|
|
1013
1056
|
if (!paying) {
|
|
1014
1057
|
const code = wait ?? 'not_connected';
|
|
1015
1058
|
throw new AssistantError(waitText(code, 'openai', !!this.o.allowance), code, 402);
|
package/dist/assistant/routes.js
CHANGED
|
@@ -173,6 +173,15 @@ export function assistantRoutes(app, o) {
|
|
|
173
173
|
return fail(`This app runs no ${asked} models.`, 404);
|
|
174
174
|
const prov = provider ?? 'anthropic';
|
|
175
175
|
const docName = keyDocName(prov);
|
|
176
|
+
// Adding a way of their own the app does not let this person use (`ownWays`); removing one
|
|
177
|
+
// always works.
|
|
178
|
+
const addsOwn = route === 'PUT /my-key'
|
|
179
|
+
? 'key'
|
|
180
|
+
: route === 'POST /my-signin' || route === 'POST /my-signin/finish'
|
|
181
|
+
? 'plan'
|
|
182
|
+
: null;
|
|
183
|
+
if (addsOwn && !(await a.mayOwn(caller, prov, addsOwn)))
|
|
184
|
+
return fail("That isn't available on your account.", 403, 'forbidden');
|
|
176
185
|
try {
|
|
177
186
|
switch (route) {
|
|
178
187
|
case 'GET /status':
|
|
@@ -97,6 +97,17 @@ export interface RequestsDoc {
|
|
|
97
97
|
export type SpendRow = UsagePart & {
|
|
98
98
|
payer: AssistantPayer;
|
|
99
99
|
};
|
|
100
|
+
/** The ways of their own a person may pay with, per provider; a provider left out has none. */
|
|
101
|
+
export type OwnWays = Partial<Record<AssistantProvider, AssistantOwnWay[]>>;
|
|
102
|
+
/** How `paying` may choose: the app's limits on a person's own keys, and one payer only. */
|
|
103
|
+
export interface PayingRules {
|
|
104
|
+
/** The own ways this person may use (`AssistantOptions.ownWays`); left out, every way. */
|
|
105
|
+
ways?: OwnWays;
|
|
106
|
+
/** False: a person who pays with their own key first never moves to the shared key. */
|
|
107
|
+
fallback?: boolean;
|
|
108
|
+
/** Only this payer: an app's own check always runs on the shared key (`complete({ payer })`). */
|
|
109
|
+
only?: AssistantPayer;
|
|
110
|
+
}
|
|
100
111
|
/**
|
|
101
112
|
* One task's use of one model this month: what a kind of work cost (a chat answer, a background
|
|
102
113
|
* task), beside `rows`, which stay per tier and payer.
|
|
@@ -219,7 +230,7 @@ export declare class State {
|
|
|
219
230
|
/** The plan's access token, refreshed first when it is about to run out; null once it fails. */
|
|
220
231
|
planToken(id: string, provider: AssistantProvider): Promise<string | null>;
|
|
221
232
|
/** The person's own key or plan, the one in use first, the other when that one fails. */
|
|
222
|
-
ownApiKey(id: string, provider: AssistantProvider): Promise<{
|
|
233
|
+
ownApiKey(id: string, provider: AssistantProvider, ways?: OwnWays): Promise<{
|
|
223
234
|
key: string;
|
|
224
235
|
plan: boolean;
|
|
225
236
|
} | null>;
|
|
@@ -253,12 +264,12 @@ export declare class State {
|
|
|
253
264
|
/** One month's history (this month by default) beside its spend, and the months that have one. */
|
|
254
265
|
usage(id: string, month?: string): Promise<UsageHistory>;
|
|
255
266
|
/** The payer's key of each provider, those set, and which are plan sign-ins. */
|
|
256
|
-
keysOf(id: string, payer: AssistantPayer): Promise<{
|
|
267
|
+
keysOf(id: string, payer: AssistantPayer, ways?: OwnWays): Promise<{
|
|
257
268
|
keys: Partial<Record<"anthropic" | "openai" | "xai", string>>;
|
|
258
269
|
plans: Partial<Record<"anthropic" | "openai" | "xai", boolean>>;
|
|
259
270
|
}>;
|
|
260
271
|
/** Who pays the person's next answer on `provider`'s model, or why nobody can. */
|
|
261
|
-
paying(id: string, provider: AssistantProvider): Promise<{
|
|
272
|
+
paying(id: string, provider: AssistantProvider, rules?: PayingRules): Promise<{
|
|
262
273
|
paying: Paying | null;
|
|
263
274
|
wait: AssistantWait | null;
|
|
264
275
|
}>;
|
package/dist/assistant/state.js
CHANGED
|
@@ -193,7 +193,10 @@ export class State {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
/** The person's own key or plan, the one in use first, the other when that one fails. */
|
|
196
|
-
async ownApiKey(id, provider) {
|
|
196
|
+
async ownApiKey(id, provider, ways) {
|
|
197
|
+
const allowed = ways ? (ways[provider] ?? []) : null;
|
|
198
|
+
if (allowed && !allowed.length)
|
|
199
|
+
return null;
|
|
197
200
|
const use = await this.ownUse(id, provider);
|
|
198
201
|
if (!use)
|
|
199
202
|
return null;
|
|
@@ -205,7 +208,12 @@ export class State {
|
|
|
205
208
|
const t = await this.planToken(id, provider);
|
|
206
209
|
return t ? { key: t, plan: true } : null;
|
|
207
210
|
};
|
|
208
|
-
|
|
211
|
+
const may = (w) => !allowed || allowed.includes(w);
|
|
212
|
+
const tryKey = async () => (may('key') ? key() : null);
|
|
213
|
+
const tryPlan = async () => (may('plan') ? plan() : null);
|
|
214
|
+
return use === 'key'
|
|
215
|
+
? ((await tryKey()) ?? (await tryPlan()))
|
|
216
|
+
: ((await tryPlan()) ?? (await tryKey()));
|
|
209
217
|
}
|
|
210
218
|
/** USD a month on the person's own keys. */
|
|
211
219
|
async ownCap(id) {
|
|
@@ -340,11 +348,11 @@ export class State {
|
|
|
340
348
|
};
|
|
341
349
|
}
|
|
342
350
|
/** The payer's key of each provider, those set, and which are plan sign-ins. */
|
|
343
|
-
async keysOf(id, payer) {
|
|
351
|
+
async keysOf(id, payer, ways) {
|
|
344
352
|
const ids = Object.keys(PROVIDERS);
|
|
345
353
|
const found = await Promise.all(ids.map((p) => payer === 'app'
|
|
346
354
|
? this.appApiKey(p).then((k) => (k ? { key: k.key, plan: false } : null))
|
|
347
|
-
: this.ownApiKey(id, p)));
|
|
355
|
+
: this.ownApiKey(id, p, ways)));
|
|
348
356
|
const keys = {};
|
|
349
357
|
const plans = {};
|
|
350
358
|
ids.forEach((p, i) => {
|
|
@@ -357,11 +365,11 @@ export class State {
|
|
|
357
365
|
return { keys, plans };
|
|
358
366
|
}
|
|
359
367
|
/** Who pays the person's next answer on `provider`'s model, or why nobody can. */
|
|
360
|
-
async paying(id, provider) {
|
|
368
|
+
async paying(id, provider, rules = {}) {
|
|
361
369
|
const [person, appKeys, ownKeys, spend, ownCap] = await Promise.all([
|
|
362
370
|
this.person(id),
|
|
363
371
|
this.keysOf(id, 'app'),
|
|
364
|
-
this.keysOf(id, 'own'),
|
|
372
|
+
this.keysOf(id, 'own', rules.ways),
|
|
365
373
|
this.spend(id),
|
|
366
374
|
this.ownCap(id),
|
|
367
375
|
]);
|
|
@@ -389,8 +397,16 @@ export class State {
|
|
|
389
397
|
}
|
|
390
398
|
: null,
|
|
391
399
|
};
|
|
392
|
-
const first = person.pay ?? 'app';
|
|
393
|
-
const order =
|
|
400
|
+
const first = rules.only ?? person.pay ?? 'app';
|
|
401
|
+
const order = rules.only
|
|
402
|
+
? [rules.only]
|
|
403
|
+
: first === 'own'
|
|
404
|
+
? rules.fallback === false && sources.own
|
|
405
|
+
? ['own']
|
|
406
|
+
: ['own', 'app']
|
|
407
|
+
: ['app', 'own'];
|
|
408
|
+
if (rules.only)
|
|
409
|
+
sources[rules.only === 'app' ? 'own' : 'app'] = null;
|
|
394
410
|
for (const p of order) {
|
|
395
411
|
const s = sources[p];
|
|
396
412
|
if (s && s.left > 0)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.231",
|
|
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.231",
|
|
56
|
+
"@columenlabs/lumen-ui-types": "0.1.231",
|
|
57
57
|
"agents": "0.32.0",
|
|
58
58
|
"ai": "7.0.162",
|
|
59
59
|
"just-bash": "3.6.0",
|