clixad 0.0.1-beta.25 → 0.0.1-beta.27
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 +6 -0
- package/dist/clixad.mjs +399 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,12 @@ cover it, for example `task ≈ 560–3.1k · free` or `task ≈ 7.5k–47k · n
|
|
|
15
15
|
estimate from the size of the conversation and how tasks usually run; what is charged is what the
|
|
16
16
|
model actually used.
|
|
17
17
|
|
|
18
|
+
`/effort` sets how long the model thinks before it answers, saved separately for each model.
|
|
19
|
+
Thinking is billed as output tokens like the answer, so a higher setting costs more credits per turn
|
|
20
|
+
and a lower one costs less. `auto` leaves the model at its own default, and a model with nothing to
|
|
21
|
+
adjust says so. The cost preview is not shown while a setting above the model's default is active,
|
|
22
|
+
because it is measured at the defaults.
|
|
23
|
+
|
|
18
24
|
**Early beta.** Commands, interfaces and credit mechanics may change between beta versions. Run the
|
|
19
25
|
most recent published build with:
|
|
20
26
|
|
package/dist/clixad.mjs
CHANGED
|
@@ -302,6 +302,23 @@ import { gzipSync } from "node:zlib";
|
|
|
302
302
|
function encodeChatBody(json) {
|
|
303
303
|
return gzipSync(Buffer.from(json, "utf8"));
|
|
304
304
|
}
|
|
305
|
+
function accessColumn(m, premium) {
|
|
306
|
+
if (m.access === void 0) return m.tier.padEnd(8);
|
|
307
|
+
if (m.access === "budget") return "budget ";
|
|
308
|
+
return premiumLocked(premium) ? "locked " : "premium ";
|
|
309
|
+
}
|
|
310
|
+
function premiumLocked(premium) {
|
|
311
|
+
return premium !== void 0 && !premium.unlocked && premium.credits <= 0;
|
|
312
|
+
}
|
|
313
|
+
function premiumUnlockHint(unlockUsd) {
|
|
314
|
+
return typeof unlockUsd === "number" && unlockUsd > 0 ? `premium: a survey or ${usd(unlockUsd)} unlocks it` : "premium: a survey unlocks it";
|
|
315
|
+
}
|
|
316
|
+
function premiumHeadline(unlockUsd) {
|
|
317
|
+
return typeof unlockUsd === "number" && unlockUsd > 0 ? `Premium: complete a survey or unlock from ${usd(unlockUsd)}.` : "Premium: complete a survey to unlock it.";
|
|
318
|
+
}
|
|
319
|
+
function usd(v) {
|
|
320
|
+
return Number.isInteger(v) ? `$${v}` : `$${v.toFixed(2)}`;
|
|
321
|
+
}
|
|
305
322
|
function adsPerTaskLabel(estAdsPerTask) {
|
|
306
323
|
const effort = estAdsPerTask >= 1 ? `~${estAdsPerTask} ads/task` : "<1 ad/task";
|
|
307
324
|
return `${effort} on completion (median)`;
|
|
@@ -405,16 +422,35 @@ function asSignupClosed(status, data) {
|
|
|
405
422
|
const d = data;
|
|
406
423
|
return d && d.error === "signup_closed" && typeof d.message === "string" ? { error: d.error, message: d.message, waitlist_url: d.waitlist_url ?? "https://clixad.io" } : void 0;
|
|
407
424
|
}
|
|
425
|
+
function premiumLockedError(message, model, body) {
|
|
426
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
427
|
+
return new PremiumLockedError(
|
|
428
|
+
message,
|
|
429
|
+
typeof body?.model === "string" ? body.model : model,
|
|
430
|
+
num(body?.needed_credits),
|
|
431
|
+
num(body?.premium_credits),
|
|
432
|
+
typeof body?.unlock_usd === "number" && body.unlock_usd > 0 ? body.unlock_usd : null,
|
|
433
|
+
num(body?.grant_per_ad),
|
|
434
|
+
freeAlternatives(body?.free_models)
|
|
435
|
+
);
|
|
436
|
+
}
|
|
408
437
|
function shortfallHeadline(err, model) {
|
|
409
438
|
const n = (v) => Math.max(0, Math.round(v)).toLocaleString("en-US");
|
|
410
439
|
if (err.balance <= 0) return `Out of credits (this needs about ${n(err.estimatedCost)}).`;
|
|
411
440
|
return `Not enough credits for this turn${model ? ` on ${model}` : ""}: it needs about ${n(err.estimatedCost)}, and the balance is ${n(err.balance)}.`;
|
|
412
441
|
}
|
|
413
442
|
function paywallError(body) {
|
|
414
|
-
|
|
443
|
+
return new PaywallError(body.balance, body.estimated_cost, body.grant_per_ad, freeAlternatives(body.free_models));
|
|
444
|
+
}
|
|
445
|
+
function freeAlternatives(list) {
|
|
446
|
+
return Array.isArray(list) ? list.filter(
|
|
415
447
|
(m) => !!m && typeof m.id === "string" && m.id.length > 0 && Number.isFinite(m.turns_left)
|
|
416
448
|
).map((m) => ({ id: m.id, turnsLeft: Math.max(0, Math.floor(m.turns_left)) })).filter((m) => m.turnsLeft > 0) : [];
|
|
417
|
-
|
|
449
|
+
}
|
|
450
|
+
async function refusal402(res, model) {
|
|
451
|
+
const body = await res.json();
|
|
452
|
+
if (body.error === "premium_locked") return premiumLockedError(body.message ?? "", model, body.premium);
|
|
453
|
+
return paywallError(body);
|
|
418
454
|
}
|
|
419
455
|
function neverConnected(code) {
|
|
420
456
|
return code !== void 0 && NEVER_CONNECTED.has(code);
|
|
@@ -540,7 +576,7 @@ async function* parseSSE(body) {
|
|
|
540
576
|
}
|
|
541
577
|
}
|
|
542
578
|
}
|
|
543
|
-
var CHAT_ENCODED_CONTENT_TYPE, LEDGER_REASONS, SignupClosedError, PaywallError, GatewayUnreachableError, NEVER_CONNECTED, AuthError, GatewayClient;
|
|
579
|
+
var CHAT_ENCODED_CONTENT_TYPE, LEDGER_REASONS, SignupClosedError, PaywallError, PremiumLockedError, GatewayUnreachableError, NEVER_CONNECTED, AuthError, GatewayClient;
|
|
544
580
|
var init_client = __esm({
|
|
545
581
|
"src/client.ts"() {
|
|
546
582
|
"use strict";
|
|
@@ -590,6 +626,24 @@ var init_client = __esm({
|
|
|
590
626
|
grantPerAd;
|
|
591
627
|
freeModels;
|
|
592
628
|
};
|
|
629
|
+
PremiumLockedError = class extends Error {
|
|
630
|
+
constructor(message, model, neededCredits, premiumCredits, unlockUsd, grantPerAd, freeModels) {
|
|
631
|
+
super(message);
|
|
632
|
+
this.model = model;
|
|
633
|
+
this.neededCredits = neededCredits;
|
|
634
|
+
this.premiumCredits = premiumCredits;
|
|
635
|
+
this.unlockUsd = unlockUsd;
|
|
636
|
+
this.grantPerAd = grantPerAd;
|
|
637
|
+
this.freeModels = freeModels;
|
|
638
|
+
this.name = "PremiumLockedError";
|
|
639
|
+
}
|
|
640
|
+
model;
|
|
641
|
+
neededCredits;
|
|
642
|
+
premiumCredits;
|
|
643
|
+
unlockUsd;
|
|
644
|
+
grantPerAd;
|
|
645
|
+
freeModels;
|
|
646
|
+
};
|
|
593
647
|
GatewayUnreachableError = class extends Error {
|
|
594
648
|
constructor(host, code) {
|
|
595
649
|
const where = `${host}${code ? ` (${code})` : ""}`;
|
|
@@ -886,6 +940,7 @@ var init_client = __esm({
|
|
|
886
940
|
if (!res.ok) throw new Error(`models failed: ${res.status}`);
|
|
887
941
|
const data = await res.json();
|
|
888
942
|
this.seen = { web_search: data.capabilities?.web_search === true };
|
|
943
|
+
this.unlockUsd = typeof data.premium_unlock_usd === "number" && data.premium_unlock_usd > 0 ? data.premium_unlock_usd : null;
|
|
889
944
|
this.shape = parseTaskShape(data.task_shape);
|
|
890
945
|
this.latest = typeof data.cli_latest === "string" ? data.cli_latest : void 0;
|
|
891
946
|
const live = new Set(data.data.map((m) => m.id));
|
|
@@ -936,8 +991,23 @@ var init_client = __esm({
|
|
|
936
991
|
return this.seen;
|
|
937
992
|
}
|
|
938
993
|
seen = { web_search: false };
|
|
939
|
-
|
|
940
|
-
|
|
994
|
+
/**
|
|
995
|
+
* What unlocks premium for every credit on the account (the cheapest pack,
|
|
996
|
+
* USD), as of the last catalog fetch. Null where the gateway sells none or
|
|
997
|
+
* did not say, which the hints read as "a survey unlocks it".
|
|
998
|
+
*/
|
|
999
|
+
get premiumUnlockUsd() {
|
|
1000
|
+
return this.unlockUsd;
|
|
1001
|
+
}
|
|
1002
|
+
unlockUsd = null;
|
|
1003
|
+
/**
|
|
1004
|
+
* `premium: true` asks for premium access too, which costs the gateway a read
|
|
1005
|
+
* of this account's whole ledger. Asked for where it is shown (`/model`,
|
|
1006
|
+
* `clixad models`), never by the polls `/earn` and a purchase run.
|
|
1007
|
+
*/
|
|
1008
|
+
async wallet(signal, opts = {}) {
|
|
1009
|
+
const url = `${this.config.gatewayUrl}/v1/wallet${opts.premium ? "?premium=1" : ""}`;
|
|
1010
|
+
const res = await gatewayFetch(url, { headers: this.headers(), signal });
|
|
941
1011
|
if (res.status === 401) throw await authFailure(res, "wallet");
|
|
942
1012
|
if (!res.ok) throw new Error(`wallet failed: ${res.status}`);
|
|
943
1013
|
return res.json();
|
|
@@ -1228,15 +1298,13 @@ var init_client = __esm({
|
|
|
1228
1298
|
return retry;
|
|
1229
1299
|
}
|
|
1230
1300
|
/** Non-streaming chat. Kept for scripted/one-shot paths; the REPL streams. */
|
|
1231
|
-
async chat(messages, model, tools, signal) {
|
|
1301
|
+
async chat(messages, model, tools, signal, effort) {
|
|
1232
1302
|
const res = await this.postChat(
|
|
1233
|
-
{ model, messages, stream: false, ...tools ? { tools } : {} },
|
|
1303
|
+
{ model, messages, stream: false, ...tools ? { tools } : {}, ...effort ? { reasoning_effort: effort } : {} },
|
|
1234
1304
|
signal
|
|
1235
1305
|
);
|
|
1236
1306
|
if (res.status === 401) throw await authFailure(res, "chat");
|
|
1237
|
-
if (res.status === 402)
|
|
1238
|
-
throw paywallError(await res.json());
|
|
1239
|
-
}
|
|
1307
|
+
if (res.status === 402) throw await refusal402(res, model);
|
|
1240
1308
|
if (!res.ok) throw new Error(await httpFailure(res, "chat"));
|
|
1241
1309
|
const data = await res.json();
|
|
1242
1310
|
const choice = data.choices[0];
|
|
@@ -1257,13 +1325,17 @@ var init_client = __esm({
|
|
|
1257
1325
|
*/
|
|
1258
1326
|
async chatStream(messages, model, opts = {}) {
|
|
1259
1327
|
const res = await this.postChat(
|
|
1260
|
-
{
|
|
1328
|
+
{
|
|
1329
|
+
model,
|
|
1330
|
+
messages,
|
|
1331
|
+
stream: true,
|
|
1332
|
+
...opts.tools ? { tools: opts.tools } : {},
|
|
1333
|
+
...opts.effort ? { reasoning_effort: opts.effort } : {}
|
|
1334
|
+
},
|
|
1261
1335
|
opts.signal
|
|
1262
1336
|
);
|
|
1263
1337
|
if (res.status === 401) throw await authFailure(res, "chat");
|
|
1264
|
-
if (res.status === 402)
|
|
1265
|
-
throw paywallError(await res.json());
|
|
1266
|
-
}
|
|
1338
|
+
if (res.status === 402) throw await refusal402(res, model);
|
|
1267
1339
|
if (!res.ok || !res.body) throw new Error(await httpFailure(res, "chat"));
|
|
1268
1340
|
let content = "";
|
|
1269
1341
|
let finishReason = "stop";
|
|
@@ -1271,6 +1343,7 @@ var init_client = __esm({
|
|
|
1271
1343
|
let free = {};
|
|
1272
1344
|
let balance = 0;
|
|
1273
1345
|
let streamError;
|
|
1346
|
+
let premiumRefusal;
|
|
1274
1347
|
const calls = /* @__PURE__ */ new Map();
|
|
1275
1348
|
const reasoning = [];
|
|
1276
1349
|
for await (const evt of parseSSE(res.body)) {
|
|
@@ -1293,12 +1366,16 @@ var init_client = __esm({
|
|
|
1293
1366
|
mergeReasoningDetails(reasoning, choice?.delta?.reasoning_details);
|
|
1294
1367
|
if (choice?.finish_reason) finishReason = choice.finish_reason;
|
|
1295
1368
|
if (json.error) streamError = json.error.message || "the model stream failed";
|
|
1369
|
+
if (json.error?.type === "premium_locked") {
|
|
1370
|
+
premiumRefusal = premiumLockedError(streamError ?? "", model, json.clixad?.premium);
|
|
1371
|
+
}
|
|
1296
1372
|
if (json.clixad) {
|
|
1297
1373
|
creditsCharged = json.clixad.credits_charged;
|
|
1298
1374
|
balance = json.clixad.balance;
|
|
1299
1375
|
free = freeMeta(json.clixad);
|
|
1300
1376
|
}
|
|
1301
1377
|
}
|
|
1378
|
+
if (premiumRefusal) throw premiumRefusal;
|
|
1302
1379
|
if (streamError) throw new Error(streamError);
|
|
1303
1380
|
const message = { role: "assistant", content: content || null };
|
|
1304
1381
|
if (calls.size > 0) {
|
|
@@ -1460,6 +1537,18 @@ function saveConfig(config) {
|
|
|
1460
1537
|
} catch {
|
|
1461
1538
|
}
|
|
1462
1539
|
}
|
|
1540
|
+
function saveEffort(config, model, level) {
|
|
1541
|
+
const current = readStored();
|
|
1542
|
+
const onDisk = current.problem ? void 0 : current.stored.effort;
|
|
1543
|
+
const next = {};
|
|
1544
|
+
for (const [id, value] of Object.entries(onDisk && typeof onDisk === "object" ? onDisk : {})) {
|
|
1545
|
+
if (typeof value === "string") next[id] = value;
|
|
1546
|
+
}
|
|
1547
|
+
if (level === void 0) delete next[model];
|
|
1548
|
+
else next[model] = level;
|
|
1549
|
+
config.effort = Object.keys(next).length ? next : void 0;
|
|
1550
|
+
saveConfig(config);
|
|
1551
|
+
}
|
|
1463
1552
|
function configPath() {
|
|
1464
1553
|
return CONFIG_PATH;
|
|
1465
1554
|
}
|
|
@@ -4173,6 +4262,9 @@ async function runAgent(client, task, opts) {
|
|
|
4173
4262
|
onProgress?.(`${description}: starting`);
|
|
4174
4263
|
const sub = await runAgent(client, prompt, {
|
|
4175
4264
|
model: opts.model,
|
|
4265
|
+
// The setting somebody chose for this model, the same as the parent's
|
|
4266
|
+
// calls: a sub-agent's credits land on the same turn.
|
|
4267
|
+
effort: opts.effort,
|
|
4176
4268
|
root: opts.root,
|
|
4177
4269
|
// A sub-agent that was told it can search the web, on a gateway that
|
|
4178
4270
|
// cannot, spends its whole step budget discovering that.
|
|
@@ -4226,6 +4318,7 @@ ${NO_SEARCH_INSTRUCTION}`,
|
|
|
4226
4318
|
let turn;
|
|
4227
4319
|
try {
|
|
4228
4320
|
turn = await client.chatStream(messages, opts.model, {
|
|
4321
|
+
effort: opts.effort,
|
|
4229
4322
|
tools: opts.tools ? opts.tools() : TOOL_SCHEMA,
|
|
4230
4323
|
signal: opts.signal,
|
|
4231
4324
|
onDelta: (text) => emit({ type: "delta", text })
|
|
@@ -5097,6 +5190,50 @@ var init_version = __esm({
|
|
|
5097
5190
|
}
|
|
5098
5191
|
});
|
|
5099
5192
|
|
|
5193
|
+
// src/effort.ts
|
|
5194
|
+
function isLevel(value) {
|
|
5195
|
+
return typeof value === "string" && EFFORT_LEVELS.includes(value);
|
|
5196
|
+
}
|
|
5197
|
+
function effortsOf(info) {
|
|
5198
|
+
return (info?.efforts ?? []).filter(isLevel);
|
|
5199
|
+
}
|
|
5200
|
+
function parseEffortArg(arg) {
|
|
5201
|
+
const v = arg.trim().toLowerCase();
|
|
5202
|
+
if (v === "auto" || v === "default") return "auto";
|
|
5203
|
+
if (v === "none") return "off";
|
|
5204
|
+
return isLevel(v) ? v : void 0;
|
|
5205
|
+
}
|
|
5206
|
+
function resolveEffort(info, requested) {
|
|
5207
|
+
if (!isLevel(requested)) return void 0;
|
|
5208
|
+
const efforts = effortsOf(info);
|
|
5209
|
+
if (efforts.length === 0) return void 0;
|
|
5210
|
+
if (!isLevel(info?.default_effort)) return requested === "off" && efforts.includes("off") ? "off" : void 0;
|
|
5211
|
+
if (efforts.includes(requested)) return requested;
|
|
5212
|
+
const below = efforts.filter((e) => rank(e) <= rank(requested));
|
|
5213
|
+
return below.length ? below[below.length - 1] : efforts[0];
|
|
5214
|
+
}
|
|
5215
|
+
function savedEffort(config, model) {
|
|
5216
|
+
const value = config.effort?.[model];
|
|
5217
|
+
return typeof value === "string" ? value : void 0;
|
|
5218
|
+
}
|
|
5219
|
+
function effortRaised(info, level) {
|
|
5220
|
+
if (!level || !isLevel(info?.default_effort)) return false;
|
|
5221
|
+
return rank(level) > rank(info.default_effort);
|
|
5222
|
+
}
|
|
5223
|
+
function effortLabel(level) {
|
|
5224
|
+
if (level === "off") return "thinking off";
|
|
5225
|
+
if (level === "on") return "thinking on";
|
|
5226
|
+
return `${level} effort`;
|
|
5227
|
+
}
|
|
5228
|
+
var EFFORT_LEVELS, rank;
|
|
5229
|
+
var init_effort = __esm({
|
|
5230
|
+
"src/effort.ts"() {
|
|
5231
|
+
"use strict";
|
|
5232
|
+
EFFORT_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
5233
|
+
rank = (level) => EFFORT_LEVELS.indexOf(level);
|
|
5234
|
+
}
|
|
5235
|
+
});
|
|
5236
|
+
|
|
5100
5237
|
// src/mentions.ts
|
|
5101
5238
|
function findMentions(text) {
|
|
5102
5239
|
const out = [];
|
|
@@ -5187,7 +5324,9 @@ async function compact(client, model, messages, opts = {}) {
|
|
|
5187
5324
|
const turn = await client.chatStream(
|
|
5188
5325
|
[...messages.slice(0, cut), { role: "user", content: SUMMARY_PROMPT }],
|
|
5189
5326
|
model,
|
|
5190
|
-
|
|
5327
|
+
// The `/effort` setting applies to every call on the model, a summary
|
|
5328
|
+
// included: somebody who turned it down to save credits saves them here too.
|
|
5329
|
+
{ signal: opts.signal, effort: opts.effort }
|
|
5191
5330
|
);
|
|
5192
5331
|
const summary = messageText(turn.message).trim() || "(no summary returned)";
|
|
5193
5332
|
const compacted = [
|
|
@@ -5460,6 +5599,10 @@ var init_commands = __esm({
|
|
|
5460
5599
|
{ name: "help", desc: "list the commands" },
|
|
5461
5600
|
{ name: "model", desc: "switch model" },
|
|
5462
5601
|
{ name: "models", desc: "list models + prices" },
|
|
5602
|
+
// Deliberately *no* `args`, for `/model`'s reason: an arg hint makes enter
|
|
5603
|
+
// complete the command instead of running it, and the picker is the point.
|
|
5604
|
+
// `/effort low` still works when typed in full.
|
|
5605
|
+
{ name: "effort", desc: "how long the model thinks (more thinking, more credits)" },
|
|
5463
5606
|
{ name: "mode", args: "[name]", desc: "permission mode (or press shift+tab)" },
|
|
5464
5607
|
{ name: "wallet", desc: "balance & ads today" },
|
|
5465
5608
|
// Not "watch an ad": nothing on the wall is a video. CPX serves sign-up
|
|
@@ -6024,6 +6167,7 @@ function App({
|
|
|
6024
6167
|
const [spent, setSpent] = useState(0);
|
|
6025
6168
|
const [freeTier, setFreeTier] = useState(wallet?.free_tier);
|
|
6026
6169
|
const [model, setModel2] = useState(config.model);
|
|
6170
|
+
const [effortBy, setEffortBy] = useState(() => config.effort ?? {});
|
|
6027
6171
|
const modelRef = useRef(config.model);
|
|
6028
6172
|
const [mode, setMode] = useState("normal");
|
|
6029
6173
|
const switchModel = useCallback((id) => {
|
|
@@ -6077,6 +6221,16 @@ function App({
|
|
|
6077
6221
|
const pendingFreeRef = useRef([]);
|
|
6078
6222
|
const pendingGrantRef = useRef(0);
|
|
6079
6223
|
const pendingBalanceRef = useRef(void 0);
|
|
6224
|
+
const pendingPremiumRef = useRef(null);
|
|
6225
|
+
const [premium, setPremiumState] = useState(wallet?.premium);
|
|
6226
|
+
const premiumRef = useRef(wallet?.premium);
|
|
6227
|
+
const setPremium = useCallback((next) => {
|
|
6228
|
+
premiumRef.current = next;
|
|
6229
|
+
setPremiumState(next);
|
|
6230
|
+
}, []);
|
|
6231
|
+
const refreshPremium = useCallback(() => {
|
|
6232
|
+
void client.wallet(void 0, { premium: true }).then((w) => setPremium(w.premium)).catch(() => void 0);
|
|
6233
|
+
}, [client, setPremium]);
|
|
6080
6234
|
const mcpRef = useRef(McpHub.empty());
|
|
6081
6235
|
const todosRef = useRef(new TodoList());
|
|
6082
6236
|
const lastTurnRef = useRef(null);
|
|
@@ -6186,9 +6340,10 @@ function App({
|
|
|
6186
6340
|
useEffect(() => {
|
|
6187
6341
|
if (wallet) return;
|
|
6188
6342
|
const ctl = new AbortController();
|
|
6189
|
-
void client.wallet(ctl.signal).then((w) => {
|
|
6343
|
+
void client.wallet(ctl.signal, { premium: true }).then((w) => {
|
|
6190
6344
|
setBalance((current) => current ?? w.balance);
|
|
6191
6345
|
setFreeTier((current) => current ?? w.free_tier);
|
|
6346
|
+
if (premiumRef.current === void 0) setPremium(w.premium);
|
|
6192
6347
|
}).catch(() => void 0);
|
|
6193
6348
|
return () => ctl.abort();
|
|
6194
6349
|
}, []);
|
|
@@ -6483,6 +6638,13 @@ function App({
|
|
|
6483
6638
|
return;
|
|
6484
6639
|
case "usage":
|
|
6485
6640
|
setBalance(event.balance);
|
|
6641
|
+
if (event.creditsCharged > 0 && premiumRef.current) {
|
|
6642
|
+
const p = premiumRef.current;
|
|
6643
|
+
setPremium({
|
|
6644
|
+
unlocked: p.unlocked,
|
|
6645
|
+
credits: Math.max(0, p.unlocked ? event.balance : Math.min(event.balance, p.credits - event.creditsCharged))
|
|
6646
|
+
});
|
|
6647
|
+
}
|
|
6486
6648
|
setSpent((s) => s + event.creditsCharged);
|
|
6487
6649
|
if (event.free) {
|
|
6488
6650
|
setFreeTier(
|
|
@@ -6494,11 +6656,12 @@ function App({
|
|
|
6494
6656
|
return;
|
|
6495
6657
|
}
|
|
6496
6658
|
},
|
|
6497
|
-
[cols, flushDelta, push]
|
|
6659
|
+
[cols, flushDelta, push, setPremium]
|
|
6498
6660
|
);
|
|
6499
6661
|
const runTurn2 = useCallback(
|
|
6500
6662
|
async (task, images = []) => {
|
|
6501
6663
|
pendingFreeRef.current = [];
|
|
6664
|
+
pendingPremiumRef.current = null;
|
|
6502
6665
|
const model2 = modelRef.current;
|
|
6503
6666
|
const ac = new AbortController();
|
|
6504
6667
|
abortRef.current = ac;
|
|
@@ -6513,7 +6676,7 @@ function App({
|
|
|
6513
6676
|
const res = await compact(client, model2, [
|
|
6514
6677
|
{ role: "system", content: contextRef.current.systemPrompt },
|
|
6515
6678
|
...history
|
|
6516
|
-
], { signal: ac.signal });
|
|
6679
|
+
], { signal: ac.signal, effort: savedEffort(config, model2) });
|
|
6517
6680
|
if (res) {
|
|
6518
6681
|
history = res.messages.slice(1);
|
|
6519
6682
|
setMessages(history);
|
|
@@ -6527,6 +6690,10 @@ function App({
|
|
|
6527
6690
|
}
|
|
6528
6691
|
const result = await runAgent(client, task, {
|
|
6529
6692
|
model: model2,
|
|
6693
|
+
// Read now rather than from the render, for `modelRef`'s reason: a
|
|
6694
|
+
// setting chosen in the same tick as this turn starts is in `config`
|
|
6695
|
+
// already and not yet in any state.
|
|
6696
|
+
effort: savedEffort(config, model2),
|
|
6530
6697
|
root,
|
|
6531
6698
|
history,
|
|
6532
6699
|
attachments: images,
|
|
@@ -6626,7 +6793,20 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6626
6793
|
}
|
|
6627
6794
|
}
|
|
6628
6795
|
} catch (err) {
|
|
6629
|
-
if (err instanceof
|
|
6796
|
+
if (err instanceof PremiumLockedError) {
|
|
6797
|
+
pendingTaskRef.current = { task, images };
|
|
6798
|
+
pendingPremiumRef.current = err;
|
|
6799
|
+
setPremium({ unlocked: false, credits: err.premiumCredits });
|
|
6800
|
+
const short2 = err.premiumCredits > 0 ? ` This turn needs about ${err.neededCredits.toLocaleString("en-US")} of those credits, and you have ${err.premiumCredits.toLocaleString("en-US")}.
|
|
6801
|
+
` : "";
|
|
6802
|
+
push({
|
|
6803
|
+
kind: "notice",
|
|
6804
|
+
tone: "warn",
|
|
6805
|
+
text: ` ${premiumHeadline(err.unlockUsd)}
|
|
6806
|
+
${err.model} is a premium model. It runs on credits you earned on the offer walls or bought.
|
|
6807
|
+
` + short2 + " Your bonus credits (the signup bonus, our own surveys) still work on budget models.\n Press enter to unlock it or to switch to a budget model. Your request is kept."
|
|
6808
|
+
});
|
|
6809
|
+
} else if (err instanceof PaywallError) {
|
|
6630
6810
|
pendingTaskRef.current = { task, images };
|
|
6631
6811
|
pendingFreeRef.current = err.freeModels;
|
|
6632
6812
|
pendingGrantRef.current = err.grantPerAd;
|
|
@@ -6821,8 +7001,9 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6821
7001
|
text: " no credit yet \u2014 offers take a while to confirm, and a screenout pays nothing.\n That is normal: start another with /earn, or check /wallet later."
|
|
6822
7002
|
});
|
|
6823
7003
|
push({ kind: "notice", tone: "good", text: " credits added \u2014 continuing" });
|
|
7004
|
+
refreshPremium();
|
|
6824
7005
|
if (pending) await runTurn2(pending.task, pending.images);
|
|
6825
|
-
}, [balance, client, config.dashboardUrl, push, runBusy, runTurn2]);
|
|
7006
|
+
}, [balance, client, config.dashboardUrl, push, refreshPremium, runBusy, runTurn2]);
|
|
6826
7007
|
const runPaywallChoice = useCallback(() => {
|
|
6827
7008
|
const free = pendingFreeRef.current.filter((m) => m.id !== EARN_ROW);
|
|
6828
7009
|
if (free.length === 0) return void runAdWall();
|
|
@@ -6892,6 +7073,47 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6892
7073
|
});
|
|
6893
7074
|
});
|
|
6894
7075
|
}, [client, push, runBusy]);
|
|
7076
|
+
const runPremiumChoice = useCallback(() => {
|
|
7077
|
+
const refusal = pendingPremiumRef.current;
|
|
7078
|
+
if (!refusal) return void runPaywallChoice();
|
|
7079
|
+
const items = [
|
|
7080
|
+
{
|
|
7081
|
+
value: EARN_ROW,
|
|
7082
|
+
label: "complete a survey",
|
|
7083
|
+
hint: `opens the offer walls, offers pay ${grantLabel(refusal.grantPerAd)}`
|
|
7084
|
+
},
|
|
7085
|
+
...refusal.unlockUsd ? [
|
|
7086
|
+
{
|
|
7087
|
+
value: BUY_ROW,
|
|
7088
|
+
label: `unlock from ${usd(refusal.unlockUsd)}`,
|
|
7089
|
+
hint: "any pack, and premium then takes all your credits"
|
|
7090
|
+
}
|
|
7091
|
+
] : [],
|
|
7092
|
+
...refusal.freeModels.filter((m) => m.id !== EARN_ROW && m.id !== BUY_ROW).map((m) => ({
|
|
7093
|
+
value: m.id,
|
|
7094
|
+
label: m.id,
|
|
7095
|
+
hint: `budget, free: ~${m.turnsLeft} turn${m.turnsLeft === 1 ? "" : "s"} left today`
|
|
7096
|
+
}))
|
|
7097
|
+
];
|
|
7098
|
+
setPickerSel(0);
|
|
7099
|
+
setPicker({
|
|
7100
|
+
title: premiumHeadline(refusal.unlockUsd),
|
|
7101
|
+
subtitle: "Unlock premium, or carry on with a budget model.",
|
|
7102
|
+
items,
|
|
7103
|
+
onPick: (choice) => {
|
|
7104
|
+
if (choice.value === EARN_ROW) return void runAdWall();
|
|
7105
|
+
if (choice.value === BUY_ROW) return void runBuy();
|
|
7106
|
+
const pending = pendingTaskRef.current;
|
|
7107
|
+
pendingTaskRef.current = null;
|
|
7108
|
+
pendingPremiumRef.current = null;
|
|
7109
|
+
config.model = choice.value;
|
|
7110
|
+
saveConfig(config);
|
|
7111
|
+
switchModel(choice.value);
|
|
7112
|
+
push({ kind: "notice", tone: "good", text: ` model \u2192 ${choice.value} (budget) \xB7 continuing where you left off` });
|
|
7113
|
+
if (pending) void runTurn2(pending.task, pending.images);
|
|
7114
|
+
}
|
|
7115
|
+
});
|
|
7116
|
+
}, [config, push, runAdWall, runBuy, runPaywallChoice, runTurn2, switchModel]);
|
|
6895
7117
|
const runCheckout = useCallback(
|
|
6896
7118
|
async (pack) => {
|
|
6897
7119
|
let before = balance ?? (await client.wallet().catch(() => void 0))?.balance;
|
|
@@ -6929,11 +7151,20 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6929
7151
|
stopped = signal.aborted;
|
|
6930
7152
|
});
|
|
6931
7153
|
if (credited) {
|
|
6932
|
-
|
|
7154
|
+
push({
|
|
6933
7155
|
kind: "notice",
|
|
6934
7156
|
tone: "good",
|
|
6935
7157
|
text: " credits added \u2014 an invoice is on its way to the address on the payment."
|
|
6936
7158
|
});
|
|
7159
|
+
refreshPremium();
|
|
7160
|
+
const pending = pendingPremiumRef.current ? pendingTaskRef.current : null;
|
|
7161
|
+
if (pending) {
|
|
7162
|
+
pendingTaskRef.current = null;
|
|
7163
|
+
pendingPremiumRef.current = null;
|
|
7164
|
+
push({ kind: "notice", tone: "good", text: " premium is unlocked \xB7 continuing where you left off" });
|
|
7165
|
+
await runTurn2(pending.task, pending.images);
|
|
7166
|
+
}
|
|
7167
|
+
return;
|
|
6937
7168
|
}
|
|
6938
7169
|
push({
|
|
6939
7170
|
kind: "notice",
|
|
@@ -6941,7 +7172,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6941
7172
|
text: stopped ? " stopped waiting. Credits still land when the payment clears \u2014 /wallet checks the balance." : " no credits yet \u2014 the payment may still be clearing. /wallet checks the balance."
|
|
6942
7173
|
});
|
|
6943
7174
|
},
|
|
6944
|
-
[balance, client, push, runBusy]
|
|
7175
|
+
[balance, client, push, refreshPremium, runBusy, runTurn2]
|
|
6945
7176
|
);
|
|
6946
7177
|
const runCommand = useCallback(
|
|
6947
7178
|
async (line2) => {
|
|
@@ -6996,7 +7227,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6996
7227
|
client,
|
|
6997
7228
|
model,
|
|
6998
7229
|
[{ role: "system", content: contextRef.current.systemPrompt }, ...messagesRef.current],
|
|
6999
|
-
{ signal }
|
|
7230
|
+
{ signal, effort: savedEffort(config, model) }
|
|
7000
7231
|
);
|
|
7001
7232
|
if (!res) return void push({ kind: "notice", text: " nothing to compact yet" });
|
|
7002
7233
|
setMessages(res.messages.slice(1));
|
|
@@ -7022,15 +7253,21 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7022
7253
|
if (arg && !models.some((m) => m.id === arg)) {
|
|
7023
7254
|
push({ kind: "notice", tone: "warn", text: ` unknown model: ${arg}` });
|
|
7024
7255
|
}
|
|
7025
|
-
const free = await client.wallet(signal).then((w) => {
|
|
7256
|
+
const free = await client.wallet(signal, { premium: true }).then((w) => {
|
|
7026
7257
|
setBalance(w.balance);
|
|
7027
7258
|
setFreeTier(w.free_tier);
|
|
7259
|
+
setPremium(w.premium);
|
|
7028
7260
|
return w.free_tier;
|
|
7029
|
-
}).catch(() =>
|
|
7261
|
+
}).catch(() => {
|
|
7262
|
+
setPremium(void 0);
|
|
7263
|
+
return freeTier;
|
|
7264
|
+
});
|
|
7265
|
+
const premium2 = premiumRef.current;
|
|
7266
|
+
const unlockUsd = client.premiumUnlockUsd;
|
|
7030
7267
|
const items = models.map((m) => ({
|
|
7031
7268
|
value: m.id,
|
|
7032
7269
|
label: m.id,
|
|
7033
|
-
hint:
|
|
7270
|
+
hint: accessColumn(m, premium2) + (m.access === "premium" && premiumLocked(premium2) ? premiumUnlockHint(unlockUsd) : modelHint(m, free)),
|
|
7034
7271
|
current: m.id === model
|
|
7035
7272
|
}));
|
|
7036
7273
|
const preselect = items.findIndex((i) => i.value === arg);
|
|
@@ -7044,17 +7281,85 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7044
7281
|
saveConfig(config);
|
|
7045
7282
|
switchModel(choice.value);
|
|
7046
7283
|
push({ kind: "notice", text: ` model \u2192 ${choice.value}` });
|
|
7284
|
+
const picked = models.find((m) => m.id === choice.value);
|
|
7285
|
+
if (picked?.access === "premium" && premiumLocked(premium2)) {
|
|
7286
|
+
push({
|
|
7287
|
+
kind: "notice",
|
|
7288
|
+
tone: "warn",
|
|
7289
|
+
text: ` ${premiumHeadline(unlockUsd)}
|
|
7290
|
+
${choice.value} runs on credits you earned on the offer walls or bought.
|
|
7291
|
+
/earn opens the offer walls` + (unlockUsd ? `, /buy unlocks premium from ${usd(unlockUsd)}.` : ".") + " Your bonus credits work on budget models."
|
|
7292
|
+
});
|
|
7293
|
+
}
|
|
7047
7294
|
}
|
|
7048
7295
|
});
|
|
7049
7296
|
});
|
|
7050
7297
|
return;
|
|
7298
|
+
// How long the model thinks, saved per model. The picker offers only
|
|
7299
|
+
// what the current model takes, so a stored setting is always one it
|
|
7300
|
+
// has; a typed level it does not have moves to the nearest one below,
|
|
7301
|
+
// the gateway's rule, and says so. Every figure about cost stays in
|
|
7302
|
+
// words: what a setting costs depends on the task, and the meta line
|
|
7303
|
+
// under each turn already shows what it did cost.
|
|
7304
|
+
case "effort":
|
|
7305
|
+
await runBusy(LOADING, async (signal) => {
|
|
7306
|
+
const models = catalogRef.current.length ? catalogRef.current : await client.models(signal);
|
|
7307
|
+
catalogRef.current = models;
|
|
7308
|
+
const info2 = models.find((m) => m.id === model);
|
|
7309
|
+
const name = info2?.label ?? model;
|
|
7310
|
+
const levels = effortsOf(info2);
|
|
7311
|
+
const fallback = info2?.default_effort;
|
|
7312
|
+
if (!info2 || levels.length === 0) {
|
|
7313
|
+
const why = info2?.efforts === void 0 ? "this gateway does not offer effort settings" : fallback === "on" ? `${name} always thinks before it answers, and has no setting for how long` : `${name} has no effort setting`;
|
|
7314
|
+
return void push({ kind: "notice", text: ` ${why}` });
|
|
7315
|
+
}
|
|
7316
|
+
const defaultWords = fallback ? effortLabel(fallback) : "its default";
|
|
7317
|
+
const apply2 = (choice, note = "") => {
|
|
7318
|
+
saveEffort(config, model, choice);
|
|
7319
|
+
setEffortBy({ ...config.effort ?? {} });
|
|
7320
|
+
const now = choice ? effortLabel(choice) : `auto (${defaultWords})`;
|
|
7321
|
+
push({ kind: "notice", text: ` ${name}: ${now}${note}` });
|
|
7322
|
+
};
|
|
7323
|
+
if (arg) {
|
|
7324
|
+
const wanted = parseEffortArg(arg);
|
|
7325
|
+
if (!wanted) {
|
|
7326
|
+
return void push({
|
|
7327
|
+
kind: "notice",
|
|
7328
|
+
tone: "warn",
|
|
7329
|
+
text: ` unknown effort: ${arg}. ${name} takes ${["auto", ...levels].join(", ")}`
|
|
7330
|
+
});
|
|
7331
|
+
}
|
|
7332
|
+
if (wanted === "auto") return apply2(void 0);
|
|
7333
|
+
const resolved = resolveEffort(info2, wanted);
|
|
7334
|
+
if (!resolved) return apply2(void 0, `. ${name} can only switch its thinking off`);
|
|
7335
|
+
return apply2(resolved, resolved === wanted ? "" : `. It has no ${wanted}, so this is the nearest below`);
|
|
7336
|
+
}
|
|
7337
|
+
const current = resolveEffort(info2, savedEffort(config, model));
|
|
7338
|
+
const items = [
|
|
7339
|
+
{ value: "auto", label: "auto", hint: `the model's default (${defaultWords})`, current: !current },
|
|
7340
|
+
...levels.map((level) => ({
|
|
7341
|
+
value: level,
|
|
7342
|
+
label: level,
|
|
7343
|
+
hint: level === fallback ? "same as auto" : effortRaised(info2, level) ? "more thinking, more credits" : "less thinking, fewer credits",
|
|
7344
|
+
current: current === level
|
|
7345
|
+
}))
|
|
7346
|
+
];
|
|
7347
|
+
setPickerSel(Math.max(0, items.findIndex((i) => i.current)));
|
|
7348
|
+
setPicker({
|
|
7349
|
+
title: `Effort for ${name}`,
|
|
7350
|
+
subtitle: "Saved for this model. Thinking is billed as output, like the answer.",
|
|
7351
|
+
items,
|
|
7352
|
+
onPick: (choice) => apply2(choice.value === "auto" ? void 0 : choice.value)
|
|
7353
|
+
});
|
|
7354
|
+
});
|
|
7355
|
+
return;
|
|
7051
7356
|
case "models":
|
|
7052
7357
|
await runBusy(LOADING, async (signal) => {
|
|
7053
7358
|
const ms = await client.models(signal);
|
|
7054
7359
|
catalogRef.current = ms;
|
|
7055
7360
|
push({
|
|
7056
7361
|
kind: "notice",
|
|
7057
|
-
text: ms.map((m) => ` ${m.id.padEnd(24)} ${modelHint(m, freeTier)}`).join("\n")
|
|
7362
|
+
text: ms.map((m) => ` ${m.id.padEnd(24)} ${accessColumn(m, premium)}${modelHint(m, freeTier)}`).join("\n")
|
|
7058
7363
|
});
|
|
7059
7364
|
});
|
|
7060
7365
|
return;
|
|
@@ -7216,7 +7521,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7216
7521
|
push({ kind: "notice", tone: "warn", text: ` unknown command: /${cmd} \u2014 try /help` });
|
|
7217
7522
|
}
|
|
7218
7523
|
},
|
|
7219
|
-
[client, cols, config, cycleMode, exit, model, push, root, runAdWall, runBusy, runBuy, runLogin, runTurn2, writeToStdout]
|
|
7524
|
+
[client, cols, config, cycleMode, exit, model, premium, push, root, runAdWall, runBusy, runBuy, runLogin, runTurn2, setPremium, writeToStdout]
|
|
7220
7525
|
);
|
|
7221
7526
|
const runShell = useCallback(
|
|
7222
7527
|
async (command) => {
|
|
@@ -7546,7 +7851,9 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
7546
7851
|
if (key.return) {
|
|
7547
7852
|
if (key.meta || key.shift) return setEditor(newline(editor));
|
|
7548
7853
|
if (endsWithContinuation(editor)) return setEditor(continueLine(editor));
|
|
7549
|
-
if (!busy && pendingTaskRef.current && isEmpty(editor))
|
|
7854
|
+
if (!busy && pendingTaskRef.current && isEmpty(editor)) {
|
|
7855
|
+
return void (pendingPremiumRef.current ? runPremiumChoice() : runPaywallChoice());
|
|
7856
|
+
}
|
|
7550
7857
|
return void submit(toText(editor));
|
|
7551
7858
|
}
|
|
7552
7859
|
if (key.ctrl && ch === "j") return setEditor(newline(editor));
|
|
@@ -7722,11 +8029,16 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
7722
8029
|
const typed = toText(editor);
|
|
7723
8030
|
let estimate;
|
|
7724
8031
|
const info = catalogRef.current.find((m) => m.id === model);
|
|
8032
|
+
const effortNow = info ? resolveEffort(info, effortBy[model]) : effortBy[model];
|
|
7725
8033
|
const shape = client.taskShape;
|
|
7726
|
-
if (!busy && info && shape && balance !== void 0 && typed.trim() !== "" && !/^[/!]/.test(typed.trimStart())) {
|
|
8034
|
+
if (!busy && info && shape && balance !== void 0 && !effortRaised(info, resolveEffort(info, effortBy[model])) && typed.trim() !== "" && !/^[/!]/.test(typed.trimStart())) {
|
|
7727
8035
|
const range = estimateTask(info, conversationUsed + schemaTokens + estimateTokens(typed), shape);
|
|
7728
8036
|
const onFree = Boolean(info.free && freeTier?.models.includes(model));
|
|
7729
|
-
const
|
|
8037
|
+
const spendable = info.access === "premium" && premium && !premium.unlocked ? Math.min(balance, premium.credits) : balance;
|
|
8038
|
+
const verdict = taskVerdict(range, {
|
|
8039
|
+
balance: spendable,
|
|
8040
|
+
...onFree ? { freeLeft: freeTier.credits_left } : {}
|
|
8041
|
+
});
|
|
7730
8042
|
estimate = estimateClause(range, verdict, !onFree && (freeTier?.credits_left ?? 0) > 0);
|
|
7731
8043
|
}
|
|
7732
8044
|
const modeText = ` ${MODE_STYLE[mode].glyph} ${MODE_LABEL[mode]}`;
|
|
@@ -7734,6 +8046,7 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
7734
8046
|
` ${statusLine(
|
|
7735
8047
|
{
|
|
7736
8048
|
model,
|
|
8049
|
+
...effortNow ? { effort: effortNow } : {},
|
|
7737
8050
|
balance,
|
|
7738
8051
|
spent,
|
|
7739
8052
|
contextPct,
|
|
@@ -7932,6 +8245,8 @@ function statusRow(o, estimate, cols) {
|
|
|
7932
8245
|
const burn = o.spent > 0 && o.minutes >= 1 ? `${Math.round(o.spent / o.minutes).toLocaleString("en-US")} cr/min` : void 0;
|
|
7933
8246
|
const clauses = [
|
|
7934
8247
|
{ text: o.model, rank: KEEP_ALWAYS },
|
|
8248
|
+
// Beside the model it qualifies, and given up with the free allowance.
|
|
8249
|
+
{ text: o.effort === void 0 ? void 0 : effortLabel(o.effort), rank: 4 },
|
|
7935
8250
|
{
|
|
7936
8251
|
// "? cr" rather than "0 cr": unknown is not empty, and an empty-looking
|
|
7937
8252
|
// wallet is the one misreading on this row that frightens somebody.
|
|
@@ -8008,7 +8323,7 @@ function tailLines(text, max) {
|
|
|
8008
8323
|
const lines = text.split("\n");
|
|
8009
8324
|
return lines.length <= max ? text : lines.slice(-max).join("\n");
|
|
8010
8325
|
}
|
|
8011
|
-
var WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, SIGNING_IN, RUNNING, SENDING_FEEDBACK, WAITING_FOR_PAYMENT, FEEDBACK_TURN_INTERVAL, NOT_SIGNED_IN, LIVE_OUTPUT_LINES, COMMITTED_OUTPUT_LINES, DELTA_FLUSH_MS, MAX_INPUT_ROWS, MAX_PICKER_ROWS, MAX_QUEUE_ROWS, QUEUE_MARKER, PICKER_CURRENT, MIN_PICKER_LABEL_COLS, PICKER_CHROME, PICKER_CHROME_COMPACT, ASK_CHROME, PLAN_CHROME, BUY_CHROME, BUY_CHROME_COMPACT, ASK_HINT, PLAN_HINT, BUY_HINT, BUY_CONFIRM_WORD, BUY_TYPED_MAX, BUY_TOO_SHORT, EXPAND_MAX_LINES, FALLBACK_ROWS, FALLBACK_COLS, INIT_HINT_AFTER_TURNS, EARN_ROW, CONTEXT_NOTICE_AT, KEEP_ALWAYS;
|
|
8326
|
+
var WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, SIGNING_IN, RUNNING, SENDING_FEEDBACK, WAITING_FOR_PAYMENT, FEEDBACK_TURN_INTERVAL, NOT_SIGNED_IN, LIVE_OUTPUT_LINES, COMMITTED_OUTPUT_LINES, DELTA_FLUSH_MS, MAX_INPUT_ROWS, MAX_PICKER_ROWS, MAX_QUEUE_ROWS, QUEUE_MARKER, PICKER_CURRENT, MIN_PICKER_LABEL_COLS, PICKER_CHROME, PICKER_CHROME_COMPACT, ASK_CHROME, PLAN_CHROME, BUY_CHROME, BUY_CHROME_COMPACT, ASK_HINT, PLAN_HINT, BUY_HINT, BUY_CONFIRM_WORD, BUY_TYPED_MAX, BUY_TOO_SHORT, EXPAND_MAX_LINES, FALLBACK_ROWS, FALLBACK_COLS, INIT_HINT_AFTER_TURNS, EARN_ROW, BUY_ROW, CONTEXT_NOTICE_AT, KEEP_ALWAYS;
|
|
8012
8327
|
var init_app = __esm({
|
|
8013
8328
|
"src/tui/app.tsx"() {
|
|
8014
8329
|
"use strict";
|
|
@@ -8017,6 +8332,7 @@ var init_app = __esm({
|
|
|
8017
8332
|
init_todos();
|
|
8018
8333
|
init_shells();
|
|
8019
8334
|
init_config();
|
|
8335
|
+
init_effort();
|
|
8020
8336
|
init_login();
|
|
8021
8337
|
init_banner();
|
|
8022
8338
|
init_agent();
|
|
@@ -8091,6 +8407,7 @@ var init_app = __esm({
|
|
|
8091
8407
|
FALLBACK_COLS = 80;
|
|
8092
8408
|
INIT_HINT_AFTER_TURNS = 3;
|
|
8093
8409
|
EARN_ROW = "\0earn";
|
|
8410
|
+
BUY_ROW = "\0buy";
|
|
8094
8411
|
CONTEXT_NOTICE_AT = 0.6;
|
|
8095
8412
|
KEEP_ALWAYS = Number.POSITIVE_INFINITY;
|
|
8096
8413
|
}
|
|
@@ -8165,6 +8482,7 @@ function clearTerminalTitle(out = titleStream()) {
|
|
|
8165
8482
|
|
|
8166
8483
|
// src/main.ts
|
|
8167
8484
|
init_version();
|
|
8485
|
+
init_effort();
|
|
8168
8486
|
var c = palette({ isTTY: process.stdout.isTTY, env: process.env });
|
|
8169
8487
|
async function main() {
|
|
8170
8488
|
setTerminalTitle();
|
|
@@ -8328,17 +8646,40 @@ function earnedToday(w) {
|
|
|
8328
8646
|
}
|
|
8329
8647
|
async function listModels(client) {
|
|
8330
8648
|
const models = await client.models();
|
|
8331
|
-
const
|
|
8649
|
+
const wallet = await client.wallet(void 0, { premium: true }).catch(() => void 0);
|
|
8650
|
+
const free = wallet?.free_tier;
|
|
8651
|
+
const premium = wallet?.premium;
|
|
8332
8652
|
console.log(c.bold("Models (credit prices per 1M tokens)"));
|
|
8333
8653
|
for (const m of models) {
|
|
8334
8654
|
const tag = m.ad_fundable ? "" : c.dim(" [paid/BYOK]");
|
|
8335
8655
|
const turns2 = free?.models.includes(m.id) ? free.turns_left[m.id] ?? 0 : void 0;
|
|
8336
8656
|
const freeTag = !m.free ? "" : turns2 === void 0 ? c.green(" [free]") : turns2 > 0 ? c.green(` [free: ~${turns2} turn${turns2 === 1 ? "" : "s"} left today]`) : c.dim(" [free: none left today]");
|
|
8657
|
+
const lockTag = m.access === "premium" && premiumLocked(premium) ? c.yellow(` [${premiumHeadline(client.premiumUnlockUsd).replace(/\.$/, "")}]`) : "";
|
|
8337
8658
|
console.log(
|
|
8338
|
-
` ${c.cyan(m.id.padEnd(22))} ${m
|
|
8659
|
+
` ${c.cyan(m.id.padEnd(22))} ${accessColumn(m, premium)} in ${String(m.credits_per_mtoken_input).padStart(9)} out ${String(m.credits_per_mtoken_output).padStart(9)} ${adsPerTaskLabel(m.est_ads_per_task)}${tag}${freeTag}${lockTag}`
|
|
8339
8660
|
);
|
|
8340
8661
|
}
|
|
8341
8662
|
}
|
|
8663
|
+
function printPremiumRefusal(err) {
|
|
8664
|
+
console.log(c.yellow(premiumHeadline(err.unlockUsd)));
|
|
8665
|
+
console.log(
|
|
8666
|
+
c.yellow(`${err.model} is a premium model. It runs on credits you earned on the offer walls or bought.`)
|
|
8667
|
+
);
|
|
8668
|
+
if (err.premiumCredits > 0) {
|
|
8669
|
+
console.log(
|
|
8670
|
+
c.yellow(
|
|
8671
|
+
`This turn needs about ${err.neededCredits.toLocaleString("en-US")} of those credits, and you have ${err.premiumCredits.toLocaleString("en-US")}.`
|
|
8672
|
+
)
|
|
8673
|
+
);
|
|
8674
|
+
}
|
|
8675
|
+
console.log(c.yellow(`Run \`clixad earn\` for a survey on the offer walls. Offers pay ${grantLabel(err.grantPerAd)}.`));
|
|
8676
|
+
if (err.unlockUsd) {
|
|
8677
|
+
console.log(c.yellow(`Or unlock premium from ${usd(err.unlockUsd)}: start \`clixad\` and type /buy.`));
|
|
8678
|
+
}
|
|
8679
|
+
const freeLine = freeAlternativeLine(err.freeModels);
|
|
8680
|
+
console.log(c.green(freeLine ?? "Your bonus credits still work on budget models: `clixad models` lists them."));
|
|
8681
|
+
console.log(c.dim(" Nothing was charged for this request."));
|
|
8682
|
+
}
|
|
8342
8683
|
async function setModel(client, config, model) {
|
|
8343
8684
|
if (!model) return console.log(`current model: ${c.cyan(config.model)}`);
|
|
8344
8685
|
const known = await client.models().catch(() => void 0);
|
|
@@ -8352,6 +8693,18 @@ async function setModel(client, config, model) {
|
|
|
8352
8693
|
config.model = model;
|
|
8353
8694
|
saveConfig(config);
|
|
8354
8695
|
console.log(c.green(`\u2713 model set to ${model}`));
|
|
8696
|
+
const picked = known?.find((m) => m.id === model);
|
|
8697
|
+
if (picked?.access === "premium") {
|
|
8698
|
+
const w = await client.wallet(void 0, { premium: true }).catch(() => void 0);
|
|
8699
|
+
if (premiumLocked(w?.premium)) {
|
|
8700
|
+
console.log(c.yellow(premiumHeadline(client.premiumUnlockUsd)));
|
|
8701
|
+
console.log(
|
|
8702
|
+
c.yellow(
|
|
8703
|
+
`${model} runs on credits you earned on the offer walls or bought. Run \`clixad earn\` for a survey` + (client.premiumUnlockUsd ? `, or start \`clixad\` and type /buy.` : ".")
|
|
8704
|
+
)
|
|
8705
|
+
);
|
|
8706
|
+
}
|
|
8707
|
+
}
|
|
8355
8708
|
}
|
|
8356
8709
|
async function showWallet(client) {
|
|
8357
8710
|
const streak = await client.claimStreak();
|
|
@@ -8461,6 +8814,7 @@ async function printCmd(client, config, task) {
|
|
|
8461
8814
|
try {
|
|
8462
8815
|
const res = await runAgent(client, task, {
|
|
8463
8816
|
model: config.model,
|
|
8817
|
+
effort: savedEffort(config, config.model),
|
|
8464
8818
|
root: process.cwd(),
|
|
8465
8819
|
systemPrompt: collectContext(process.cwd()).systemPrompt + (capabilities?.web_search ? "" : `
|
|
8466
8820
|
|
|
@@ -8484,7 +8838,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
8484
8838
|
[${cost} \xB7 balance ${res.balance} \xB7 ${res.steps} step(s)]`)
|
|
8485
8839
|
);
|
|
8486
8840
|
} catch (err) {
|
|
8487
|
-
if (err instanceof
|
|
8841
|
+
if (err instanceof PremiumLockedError) {
|
|
8842
|
+
console.log("");
|
|
8843
|
+
printPremiumRefusal(err);
|
|
8844
|
+
} else if (err instanceof PaywallError) {
|
|
8488
8845
|
console.log(c.yellow(`
|
|
8489
8846
|
${shortfallHeadline(err, config.model)}`));
|
|
8490
8847
|
const freeLine = freeAlternativeLine(err.freeModels);
|
|
@@ -8606,6 +8963,7 @@ async function runTurn(client, config, messages) {
|
|
|
8606
8963
|
let started = false;
|
|
8607
8964
|
try {
|
|
8608
8965
|
const result = await client.chatStream(messages, config.model, {
|
|
8966
|
+
effort: savedEffort(config, config.model),
|
|
8609
8967
|
onDelta: (chunk) => {
|
|
8610
8968
|
if (!started) process.stdout.write(c.green("assistant "));
|
|
8611
8969
|
started = true;
|
|
@@ -8618,7 +8976,9 @@ async function runTurn(client, config, messages) {
|
|
|
8618
8976
|
return result;
|
|
8619
8977
|
} catch (err) {
|
|
8620
8978
|
if (started) process.stdout.write("\n");
|
|
8621
|
-
if (err instanceof
|
|
8979
|
+
if (err instanceof PremiumLockedError) {
|
|
8980
|
+
printPremiumRefusal(err);
|
|
8981
|
+
} else if (err instanceof PaywallError) {
|
|
8622
8982
|
console.log(c.yellow(shortfallHeadline(err, config.model)));
|
|
8623
8983
|
const freeLine = freeAlternativeLine(err.freeModels);
|
|
8624
8984
|
if (freeLine) console.log(c.green(freeLine));
|
|
@@ -8642,9 +9002,9 @@ async function showStreak(client) {
|
|
|
8642
9002
|
console.log(streak.claimed ? c.green(` ${line2}`) : c.dim(` ${line2}`));
|
|
8643
9003
|
return streak;
|
|
8644
9004
|
}
|
|
8645
|
-
async function safeWallet(client) {
|
|
9005
|
+
async function safeWallet(client, opts = {}) {
|
|
8646
9006
|
try {
|
|
8647
|
-
return await client.wallet();
|
|
9007
|
+
return await client.wallet(void 0, opts);
|
|
8648
9008
|
} catch {
|
|
8649
9009
|
return void 0;
|
|
8650
9010
|
}
|
|
@@ -8670,7 +9030,7 @@ async function startupReads(client, gatewayUrl) {
|
|
|
8670
9030
|
)
|
|
8671
9031
|
];
|
|
8672
9032
|
try {
|
|
8673
|
-
const [streak, wallet] = await Promise.all([client.claimStreak(), safeWallet(client)]);
|
|
9033
|
+
const [streak, wallet] = await Promise.all([client.claimStreak(), safeWallet(client, { premium: true })]);
|
|
8674
9034
|
const balance = streak?.claimed ? streak.balance : wallet?.balance;
|
|
8675
9035
|
return {
|
|
8676
9036
|
streak,
|