clixad 0.0.1-beta.24 → 0.0.1-beta.26
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 +319 -15
- 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
|
@@ -94,14 +94,62 @@ var init_estimate = __esm({
|
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
// src/sponsor/formats.ts
|
|
97
|
-
|
|
97
|
+
function isAdFormat(value) {
|
|
98
|
+
return typeof value === "string" && AD_FORMATS.includes(value);
|
|
99
|
+
}
|
|
100
|
+
var AD_FORMATS, EXTRA_AD_FORMATS;
|
|
98
101
|
var init_formats = __esm({
|
|
99
102
|
"src/sponsor/formats.ts"() {
|
|
100
103
|
"use strict";
|
|
104
|
+
AD_FORMATS = ["top_bar", "sidebar_card", "status_bar"];
|
|
101
105
|
EXTRA_AD_FORMATS = ["sidebar_card", "status_bar"];
|
|
102
106
|
}
|
|
103
107
|
});
|
|
104
108
|
|
|
109
|
+
// src/sponsor/direct.ts
|
|
110
|
+
function sanitizeAdText(raw) {
|
|
111
|
+
return raw.replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g, "").replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").replace(/[--]/g, "").replace(/[-]/g, "").replace(/\s+/g, " ").trim();
|
|
112
|
+
}
|
|
113
|
+
function parseDirectLines(body) {
|
|
114
|
+
const raw = body?.lines;
|
|
115
|
+
if (!Array.isArray(raw)) return [];
|
|
116
|
+
const out = [];
|
|
117
|
+
const seen = /* @__PURE__ */ new Set();
|
|
118
|
+
for (const item of raw) {
|
|
119
|
+
if (!item || typeof item !== "object") continue;
|
|
120
|
+
const e = item;
|
|
121
|
+
if (!isAdFormat(e.format) || seen.has(e.format)) continue;
|
|
122
|
+
if (e.hold === true) {
|
|
123
|
+
out.push({ format: e.format, hold: true });
|
|
124
|
+
seen.add(e.format);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (typeof e.text !== "string" || typeof e.serve_token !== "string" || typeof e.click_url !== "string") continue;
|
|
128
|
+
const text = sanitizeAdText(e.text).slice(0, MAX_TEXT);
|
|
129
|
+
if (!text || !e.serve_token || e.serve_token.length > 2e3) continue;
|
|
130
|
+
let clickUrl;
|
|
131
|
+
try {
|
|
132
|
+
const url = new URL(e.click_url);
|
|
133
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") continue;
|
|
134
|
+
clickUrl = url.toString();
|
|
135
|
+
} catch {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
out.push({ format: e.format, text, serveToken: e.serve_token, clickUrl });
|
|
139
|
+
seen.add(e.format);
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
var DIRECT_ASK_TIMEOUT_MS, MAX_TEXT;
|
|
144
|
+
var init_direct = __esm({
|
|
145
|
+
"src/sponsor/direct.ts"() {
|
|
146
|
+
"use strict";
|
|
147
|
+
init_formats();
|
|
148
|
+
DIRECT_ASK_TIMEOUT_MS = 1500;
|
|
149
|
+
MAX_TEXT = 200;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
105
153
|
// src/edge.ts
|
|
106
154
|
function detectEdgeBlock(status, headers, body) {
|
|
107
155
|
if (status !== 403) return void 0;
|
|
@@ -498,6 +546,7 @@ var init_client = __esm({
|
|
|
498
546
|
"use strict";
|
|
499
547
|
init_estimate();
|
|
500
548
|
init_formats();
|
|
549
|
+
init_direct();
|
|
501
550
|
init_edge();
|
|
502
551
|
CHAT_ENCODED_CONTENT_TYPE = "application/vnd.clixad.chat+gzip";
|
|
503
552
|
LEDGER_REASONS = {
|
|
@@ -645,6 +694,7 @@ var init_client = __esm({
|
|
|
645
694
|
return { status: "closed", message: closed.message, waitlistUrl: closed.waitlist_url };
|
|
646
695
|
}
|
|
647
696
|
if (!res.ok && !("status" in data && data.status)) {
|
|
697
|
+
if (res.status >= 500 || res.status === 429) return { status: "unavailable", error: `http ${res.status}` };
|
|
648
698
|
return { status: "error", error: `http ${res.status}` };
|
|
649
699
|
}
|
|
650
700
|
return data;
|
|
@@ -981,6 +1031,51 @@ var init_client = __esm({
|
|
|
981
1031
|
return 0;
|
|
982
1032
|
}
|
|
983
1033
|
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Ask for booked lines (step 2): one ask per model call, naming the slots
|
|
1036
|
+
* that fit the terminal now. See `sponsor/direct.ts` for the contract.
|
|
1037
|
+
*
|
|
1038
|
+
* Never throws, for `sponsorConfig`'s reason. A 404 means a gateway that
|
|
1039
|
+
* does not know the route, so `supported: false` tells the caller to stop
|
|
1040
|
+
* asking for the rest of the session; every other failure (a timeout, a 429,
|
|
1041
|
+
* a 5xx, a malformed body) is simply "nothing booked" this time.
|
|
1042
|
+
*/
|
|
1043
|
+
async directSponsor(formats, cols, signal) {
|
|
1044
|
+
try {
|
|
1045
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/direct`, {
|
|
1046
|
+
method: "POST",
|
|
1047
|
+
headers: this.headers(),
|
|
1048
|
+
...signal ? { signal } : {},
|
|
1049
|
+
body: JSON.stringify({ formats, cols })
|
|
1050
|
+
});
|
|
1051
|
+
if (res.status === 404) return { supported: false, lines: [] };
|
|
1052
|
+
if (!res.ok) return { supported: true, lines: [] };
|
|
1053
|
+
return { supported: true, lines: parseDirectLines(await res.json()) };
|
|
1054
|
+
} catch {
|
|
1055
|
+
return { supported: true, lines: [] };
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Report the booked lines a turn drew, in one call, after the turn is over.
|
|
1060
|
+
* Never to `/v1/sponsor/impression`, which would credit them as ADtention's.
|
|
1061
|
+
* Never throws; answers what was credited, for the tally and for tests.
|
|
1062
|
+
*/
|
|
1063
|
+
async reportDirectImpressions(impressions, signal) {
|
|
1064
|
+
if (impressions.length === 0) return 0;
|
|
1065
|
+
try {
|
|
1066
|
+
const res = await gatewayFetch(`${this.config.gatewayUrl}/v1/sponsor/direct/impression`, {
|
|
1067
|
+
method: "POST",
|
|
1068
|
+
headers: this.headers(),
|
|
1069
|
+
...signal ? { signal } : {},
|
|
1070
|
+
body: JSON.stringify({ impressions: impressions.map((i) => ({ format: i.format, serve_token: i.serveToken })) })
|
|
1071
|
+
});
|
|
1072
|
+
if (!res.ok) return 0;
|
|
1073
|
+
const body = await res.json();
|
|
1074
|
+
return typeof body.credited === "number" ? body.credited : 0;
|
|
1075
|
+
} catch {
|
|
1076
|
+
return 0;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
984
1079
|
/**
|
|
985
1080
|
* Search the web, through the gateway.
|
|
986
1081
|
*
|
|
@@ -1133,9 +1228,9 @@ var init_client = __esm({
|
|
|
1133
1228
|
return retry;
|
|
1134
1229
|
}
|
|
1135
1230
|
/** Non-streaming chat. Kept for scripted/one-shot paths; the REPL streams. */
|
|
1136
|
-
async chat(messages, model, tools, signal) {
|
|
1231
|
+
async chat(messages, model, tools, signal, effort) {
|
|
1137
1232
|
const res = await this.postChat(
|
|
1138
|
-
{ model, messages, stream: false, ...tools ? { tools } : {} },
|
|
1233
|
+
{ model, messages, stream: false, ...tools ? { tools } : {}, ...effort ? { reasoning_effort: effort } : {} },
|
|
1139
1234
|
signal
|
|
1140
1235
|
);
|
|
1141
1236
|
if (res.status === 401) throw await authFailure(res, "chat");
|
|
@@ -1162,7 +1257,13 @@ var init_client = __esm({
|
|
|
1162
1257
|
*/
|
|
1163
1258
|
async chatStream(messages, model, opts = {}) {
|
|
1164
1259
|
const res = await this.postChat(
|
|
1165
|
-
{
|
|
1260
|
+
{
|
|
1261
|
+
model,
|
|
1262
|
+
messages,
|
|
1263
|
+
stream: true,
|
|
1264
|
+
...opts.tools ? { tools: opts.tools } : {},
|
|
1265
|
+
...opts.effort ? { reasoning_effort: opts.effort } : {}
|
|
1266
|
+
},
|
|
1166
1267
|
opts.signal
|
|
1167
1268
|
);
|
|
1168
1269
|
if (res.status === 401) throw await authFailure(res, "chat");
|
|
@@ -1365,6 +1466,18 @@ function saveConfig(config) {
|
|
|
1365
1466
|
} catch {
|
|
1366
1467
|
}
|
|
1367
1468
|
}
|
|
1469
|
+
function saveEffort(config, model, level) {
|
|
1470
|
+
const current = readStored();
|
|
1471
|
+
const onDisk = current.problem ? void 0 : current.stored.effort;
|
|
1472
|
+
const next = {};
|
|
1473
|
+
for (const [id, value] of Object.entries(onDisk && typeof onDisk === "object" ? onDisk : {})) {
|
|
1474
|
+
if (typeof value === "string") next[id] = value;
|
|
1475
|
+
}
|
|
1476
|
+
if (level === void 0) delete next[model];
|
|
1477
|
+
else next[model] = level;
|
|
1478
|
+
config.effort = Object.keys(next).length ? next : void 0;
|
|
1479
|
+
saveConfig(config);
|
|
1480
|
+
}
|
|
1368
1481
|
function configPath() {
|
|
1369
1482
|
return CONFIG_PATH;
|
|
1370
1483
|
}
|
|
@@ -1443,11 +1556,24 @@ async function githubLogin(client, config, start, invite, report, signal) {
|
|
|
1443
1556
|
openBrowser(start.verification_uri);
|
|
1444
1557
|
const deadline = Date.now() + start.expires_in * 1e3;
|
|
1445
1558
|
let interval = Math.max(start.interval, 1);
|
|
1559
|
+
let unavailableSince;
|
|
1446
1560
|
while (Date.now() < deadline) {
|
|
1447
1561
|
await sleep2(interval * 1e3, signal);
|
|
1448
1562
|
if (signal?.aborted) return { status: "aborted" };
|
|
1449
1563
|
report.tick?.();
|
|
1450
|
-
|
|
1564
|
+
let poll;
|
|
1565
|
+
try {
|
|
1566
|
+
poll = await client.devicePoll(start.session, invite);
|
|
1567
|
+
} catch (err) {
|
|
1568
|
+
if (!(err instanceof GatewayUnreachableError)) throw err;
|
|
1569
|
+
poll = { status: "unavailable", error: err.message };
|
|
1570
|
+
}
|
|
1571
|
+
if (poll.status === "unavailable") {
|
|
1572
|
+
unavailableSince ??= Date.now();
|
|
1573
|
+
if (Date.now() - unavailableSince > UNAVAILABLE_GIVE_UP_MS) return { status: "failed", error: poll.error };
|
|
1574
|
+
continue;
|
|
1575
|
+
}
|
|
1576
|
+
unavailableSince = void 0;
|
|
1451
1577
|
if (poll.status === "pending") {
|
|
1452
1578
|
if (poll.interval) interval = poll.interval;
|
|
1453
1579
|
continue;
|
|
@@ -1475,10 +1601,19 @@ async function githubLogin(client, config, start, invite, report, signal) {
|
|
|
1475
1601
|
}
|
|
1476
1602
|
};
|
|
1477
1603
|
}
|
|
1478
|
-
return { status: "failed", error: poll.error ?? poll.status };
|
|
1604
|
+
return { status: "failed", error: describePollError(poll.error ?? poll.status) };
|
|
1479
1605
|
}
|
|
1480
1606
|
return { status: "timeout" };
|
|
1481
1607
|
}
|
|
1608
|
+
function describePollError(error) {
|
|
1609
|
+
if (error === "unknown_session") {
|
|
1610
|
+
return "the server restarted while you were signing in, so this code no longer works. Sign in again.";
|
|
1611
|
+
}
|
|
1612
|
+
if (error === "login_unavailable") {
|
|
1613
|
+
return "GitHub approved the sign-in, but the server could not finish it. Try again in a minute.";
|
|
1614
|
+
}
|
|
1615
|
+
return error;
|
|
1616
|
+
}
|
|
1482
1617
|
async function devLogin(client, config, email, invite) {
|
|
1483
1618
|
let res;
|
|
1484
1619
|
try {
|
|
@@ -1517,7 +1652,7 @@ function sleep2(ms, signal) {
|
|
|
1517
1652
|
signal.addEventListener("abort", done, { once: true });
|
|
1518
1653
|
});
|
|
1519
1654
|
}
|
|
1520
|
-
var TERMS_URL, PRIVACY_URL, TERMS_NOTICE;
|
|
1655
|
+
var TERMS_URL, PRIVACY_URL, TERMS_NOTICE, UNAVAILABLE_GIVE_UP_MS;
|
|
1521
1656
|
var init_login = __esm({
|
|
1522
1657
|
"src/login.ts"() {
|
|
1523
1658
|
"use strict";
|
|
@@ -1529,6 +1664,7 @@ var init_login = __esm({
|
|
|
1529
1664
|
TERMS_NOTICE = `By creating an account you confirm that you are at least 18 and that you agree to
|
|
1530
1665
|
the Terms \u2014 ${TERMS_URL}
|
|
1531
1666
|
and the Privacy Policy \u2014 ${PRIVACY_URL}`;
|
|
1667
|
+
UNAVAILABLE_GIVE_UP_MS = 18e4;
|
|
1532
1668
|
}
|
|
1533
1669
|
});
|
|
1534
1670
|
|
|
@@ -4055,6 +4191,9 @@ async function runAgent(client, task, opts) {
|
|
|
4055
4191
|
onProgress?.(`${description}: starting`);
|
|
4056
4192
|
const sub = await runAgent(client, prompt, {
|
|
4057
4193
|
model: opts.model,
|
|
4194
|
+
// The setting somebody chose for this model, the same as the parent's
|
|
4195
|
+
// calls: a sub-agent's credits land on the same turn.
|
|
4196
|
+
effort: opts.effort,
|
|
4058
4197
|
root: opts.root,
|
|
4059
4198
|
// A sub-agent that was told it can search the web, on a gateway that
|
|
4060
4199
|
// cannot, spends its whole step budget discovering that.
|
|
@@ -4108,6 +4247,7 @@ ${NO_SEARCH_INSTRUCTION}`,
|
|
|
4108
4247
|
let turn;
|
|
4109
4248
|
try {
|
|
4110
4249
|
turn = await client.chatStream(messages, opts.model, {
|
|
4250
|
+
effort: opts.effort,
|
|
4111
4251
|
tools: opts.tools ? opts.tools() : TOOL_SCHEMA,
|
|
4112
4252
|
signal: opts.signal,
|
|
4113
4253
|
onDelta: (text) => emit({ type: "delta", text })
|
|
@@ -4979,6 +5119,50 @@ var init_version = __esm({
|
|
|
4979
5119
|
}
|
|
4980
5120
|
});
|
|
4981
5121
|
|
|
5122
|
+
// src/effort.ts
|
|
5123
|
+
function isLevel(value) {
|
|
5124
|
+
return typeof value === "string" && EFFORT_LEVELS.includes(value);
|
|
5125
|
+
}
|
|
5126
|
+
function effortsOf(info) {
|
|
5127
|
+
return (info?.efforts ?? []).filter(isLevel);
|
|
5128
|
+
}
|
|
5129
|
+
function parseEffortArg(arg) {
|
|
5130
|
+
const v = arg.trim().toLowerCase();
|
|
5131
|
+
if (v === "auto" || v === "default") return "auto";
|
|
5132
|
+
if (v === "none") return "off";
|
|
5133
|
+
return isLevel(v) ? v : void 0;
|
|
5134
|
+
}
|
|
5135
|
+
function resolveEffort(info, requested) {
|
|
5136
|
+
if (!isLevel(requested)) return void 0;
|
|
5137
|
+
const efforts = effortsOf(info);
|
|
5138
|
+
if (efforts.length === 0) return void 0;
|
|
5139
|
+
if (!isLevel(info?.default_effort)) return requested === "off" && efforts.includes("off") ? "off" : void 0;
|
|
5140
|
+
if (efforts.includes(requested)) return requested;
|
|
5141
|
+
const below = efforts.filter((e) => rank(e) <= rank(requested));
|
|
5142
|
+
return below.length ? below[below.length - 1] : efforts[0];
|
|
5143
|
+
}
|
|
5144
|
+
function savedEffort(config, model) {
|
|
5145
|
+
const value = config.effort?.[model];
|
|
5146
|
+
return typeof value === "string" ? value : void 0;
|
|
5147
|
+
}
|
|
5148
|
+
function effortRaised(info, level) {
|
|
5149
|
+
if (!level || !isLevel(info?.default_effort)) return false;
|
|
5150
|
+
return rank(level) > rank(info.default_effort);
|
|
5151
|
+
}
|
|
5152
|
+
function effortLabel(level) {
|
|
5153
|
+
if (level === "off") return "thinking off";
|
|
5154
|
+
if (level === "on") return "thinking on";
|
|
5155
|
+
return `${level} effort`;
|
|
5156
|
+
}
|
|
5157
|
+
var EFFORT_LEVELS, rank;
|
|
5158
|
+
var init_effort = __esm({
|
|
5159
|
+
"src/effort.ts"() {
|
|
5160
|
+
"use strict";
|
|
5161
|
+
EFFORT_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
5162
|
+
rank = (level) => EFFORT_LEVELS.indexOf(level);
|
|
5163
|
+
}
|
|
5164
|
+
});
|
|
5165
|
+
|
|
4982
5166
|
// src/mentions.ts
|
|
4983
5167
|
function findMentions(text) {
|
|
4984
5168
|
const out = [];
|
|
@@ -5069,7 +5253,9 @@ async function compact(client, model, messages, opts = {}) {
|
|
|
5069
5253
|
const turn = await client.chatStream(
|
|
5070
5254
|
[...messages.slice(0, cut), { role: "user", content: SUMMARY_PROMPT }],
|
|
5071
5255
|
model,
|
|
5072
|
-
|
|
5256
|
+
// The `/effort` setting applies to every call on the model, a summary
|
|
5257
|
+
// included: somebody who turned it down to save credits saves them here too.
|
|
5258
|
+
{ signal: opts.signal, effort: opts.effort }
|
|
5073
5259
|
);
|
|
5074
5260
|
const summary = messageText(turn.message).trim() || "(no summary returned)";
|
|
5075
5261
|
const compacted = [
|
|
@@ -5342,6 +5528,10 @@ var init_commands = __esm({
|
|
|
5342
5528
|
{ name: "help", desc: "list the commands" },
|
|
5343
5529
|
{ name: "model", desc: "switch model" },
|
|
5344
5530
|
{ name: "models", desc: "list models + prices" },
|
|
5531
|
+
// Deliberately *no* `args`, for `/model`'s reason: an arg hint makes enter
|
|
5532
|
+
// complete the command instead of running it, and the picker is the point.
|
|
5533
|
+
// `/effort low` still works when typed in full.
|
|
5534
|
+
{ name: "effort", desc: "how long the model thinks (more thinking, more credits)" },
|
|
5345
5535
|
{ name: "mode", args: "[name]", desc: "permission mode (or press shift+tab)" },
|
|
5346
5536
|
{ name: "wallet", desc: "balance & ads today" },
|
|
5347
5537
|
// Not "watch an ad": nothing on the wall is a video. CPX serves sign-up
|
|
@@ -5865,6 +6055,7 @@ function App({
|
|
|
5865
6055
|
notices: notices2,
|
|
5866
6056
|
sponsorServe,
|
|
5867
6057
|
sponsorFormats,
|
|
6058
|
+
sponsorDirect,
|
|
5868
6059
|
initHint
|
|
5869
6060
|
}) {
|
|
5870
6061
|
const { exit } = useApp();
|
|
@@ -5905,6 +6096,7 @@ function App({
|
|
|
5905
6096
|
const [spent, setSpent] = useState(0);
|
|
5906
6097
|
const [freeTier, setFreeTier] = useState(wallet?.free_tier);
|
|
5907
6098
|
const [model, setModel2] = useState(config.model);
|
|
6099
|
+
const [effortBy, setEffortBy] = useState(() => config.effort ?? {});
|
|
5908
6100
|
const modelRef = useRef(config.model);
|
|
5909
6101
|
const [mode, setMode] = useState("normal");
|
|
5910
6102
|
const switchModel = useCallback((id) => {
|
|
@@ -5973,6 +6165,8 @@ function App({
|
|
|
5973
6165
|
const sponsorPrevRef = useRef(void 0);
|
|
5974
6166
|
const sponsorCallRef = useRef(0);
|
|
5975
6167
|
const pendingImpressionRef = useRef(/* @__PURE__ */ new Map());
|
|
6168
|
+
const pendingDirectRef = useRef(/* @__PURE__ */ new Map());
|
|
6169
|
+
const directSupportedRef = useRef(true);
|
|
5976
6170
|
const drawnRef = useRef(/* @__PURE__ */ new Set());
|
|
5977
6171
|
const sponsorSlotRef = useRef(
|
|
5978
6172
|
/* @__PURE__ */ new Map()
|
|
@@ -6184,8 +6378,7 @@ function App({
|
|
|
6184
6378
|
} else {
|
|
6185
6379
|
setSponsor(null);
|
|
6186
6380
|
}
|
|
6187
|
-
|
|
6188
|
-
if (!adFitsFormat(format, colsRef.current)) continue;
|
|
6381
|
+
const fetchNetwork = (format) => {
|
|
6189
6382
|
void remoteSponsorRef.current[format].fetch().then((line2) => {
|
|
6190
6383
|
if (!line2 || call !== sponsorCallRef.current) return;
|
|
6191
6384
|
if (!adFitsFormat(format, colsRef.current)) return;
|
|
@@ -6203,15 +6396,55 @@ function App({
|
|
|
6203
6396
|
recordImpression(tallyRef.current, line2.id);
|
|
6204
6397
|
pendingImpressionRef.current.set(format, { call, impression: line2.impression });
|
|
6205
6398
|
});
|
|
6206
|
-
}
|
|
6399
|
+
};
|
|
6400
|
+
const paintDirect = (line2) => {
|
|
6401
|
+
const { format } = line2;
|
|
6402
|
+
if (call !== sponsorCallRef.current) return;
|
|
6403
|
+
if (!adFitsFormat(format, colsRef.current)) return;
|
|
6404
|
+
if (format !== "top_bar" && modalOpenRef.current) return;
|
|
6405
|
+
const drawnId = `direct:${call}:${format}`;
|
|
6406
|
+
if (format === "top_bar") {
|
|
6407
|
+
if (!sponsorRow(line2.text, colsRef.current, "network")) return;
|
|
6408
|
+
setSponsor({ text: line2.text, origin: "network", impressionId: drawnId });
|
|
6409
|
+
} else {
|
|
6410
|
+
setAdSlots((slots) => ({ ...slots, [format]: { text: line2.text, impressionId: drawnId } }));
|
|
6411
|
+
}
|
|
6412
|
+
pendingDirectRef.current.set(format, { call, serveToken: line2.serveToken, drawnId });
|
|
6413
|
+
};
|
|
6414
|
+
const fitting = AD_FORMATS.filter((format) => adFitsFormat(format, colsRef.current));
|
|
6415
|
+
void (async () => {
|
|
6416
|
+
let direct = [];
|
|
6417
|
+
if (directSupportedRef.current && fitting.length > 0) {
|
|
6418
|
+
const ask3 = sponsorDirect ?? ((formats, cols2, signal) => client.directSponsor(formats, cols2, signal));
|
|
6419
|
+
let answer = { supported: true, lines: [] };
|
|
6420
|
+
try {
|
|
6421
|
+
answer = await ask3(fitting, colsRef.current, AbortSignal.timeout(DIRECT_ASK_TIMEOUT_MS));
|
|
6422
|
+
} catch {
|
|
6423
|
+
}
|
|
6424
|
+
if (!answer.supported) directSupportedRef.current = false;
|
|
6425
|
+
direct = answer.lines;
|
|
6426
|
+
}
|
|
6427
|
+
if (call !== sponsorCallRef.current) return;
|
|
6428
|
+
for (const format of ["top_bar", ...EXTRA_AD_FORMATS]) {
|
|
6429
|
+
if (!adFitsFormat(format, colsRef.current)) continue;
|
|
6430
|
+
const booked = direct.find((line2) => line2.format === format);
|
|
6431
|
+
if (booked && "hold" in booked) continue;
|
|
6432
|
+
if (booked) paintDirect(booked);
|
|
6433
|
+
else fetchNetwork(format);
|
|
6434
|
+
}
|
|
6435
|
+
})();
|
|
6207
6436
|
},
|
|
6208
|
-
[config.sponsor, stdout, wallet]
|
|
6437
|
+
[config.sponsor, stdout, wallet, client, sponsorDirect]
|
|
6209
6438
|
);
|
|
6210
6439
|
const reportSponsor = useCallback(() => {
|
|
6211
6440
|
const pending = [...pendingImpressionRef.current.values()];
|
|
6441
|
+
const pendingDirect = [...pendingDirectRef.current.entries()];
|
|
6212
6442
|
const drawn = drawnRef.current;
|
|
6213
6443
|
pendingImpressionRef.current = /* @__PURE__ */ new Map();
|
|
6444
|
+
pendingDirectRef.current = /* @__PURE__ */ new Map();
|
|
6214
6445
|
drawnRef.current = /* @__PURE__ */ new Set();
|
|
6446
|
+
const directReports = pendingDirect.filter(([, p]) => p.call === sponsorCallRef.current && drawn.has(p.drawnId)).map(([format, p]) => ({ format, serveToken: p.serveToken }));
|
|
6447
|
+
if (directReports.length > 0) void client.reportDirectImpressions(directReports);
|
|
6215
6448
|
const reports = pending.filter((p) => p.call === sponsorCallRef.current).filter((p) => drawn.has(p.impression.impressionId)).map((p) => p.impression);
|
|
6216
6449
|
if (reports.length === 0) return;
|
|
6217
6450
|
void (async () => {
|
|
@@ -6353,7 +6586,7 @@ function App({
|
|
|
6353
6586
|
const res = await compact(client, model2, [
|
|
6354
6587
|
{ role: "system", content: contextRef.current.systemPrompt },
|
|
6355
6588
|
...history
|
|
6356
|
-
], { signal: ac.signal });
|
|
6589
|
+
], { signal: ac.signal, effort: savedEffort(config, model2) });
|
|
6357
6590
|
if (res) {
|
|
6358
6591
|
history = res.messages.slice(1);
|
|
6359
6592
|
setMessages(history);
|
|
@@ -6367,6 +6600,10 @@ function App({
|
|
|
6367
6600
|
}
|
|
6368
6601
|
const result = await runAgent(client, task, {
|
|
6369
6602
|
model: model2,
|
|
6603
|
+
// Read now rather than from the render, for `modelRef`'s reason: a
|
|
6604
|
+
// setting chosen in the same tick as this turn starts is in `config`
|
|
6605
|
+
// already and not yet in any state.
|
|
6606
|
+
effort: savedEffort(config, model2),
|
|
6370
6607
|
root,
|
|
6371
6608
|
history,
|
|
6372
6609
|
attachments: images,
|
|
@@ -6836,7 +7073,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6836
7073
|
client,
|
|
6837
7074
|
model,
|
|
6838
7075
|
[{ role: "system", content: contextRef.current.systemPrompt }, ...messagesRef.current],
|
|
6839
|
-
{ signal }
|
|
7076
|
+
{ signal, effort: savedEffort(config, model) }
|
|
6840
7077
|
);
|
|
6841
7078
|
if (!res) return void push({ kind: "notice", text: " nothing to compact yet" });
|
|
6842
7079
|
setMessages(res.messages.slice(1));
|
|
@@ -6888,6 +7125,64 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6888
7125
|
});
|
|
6889
7126
|
});
|
|
6890
7127
|
return;
|
|
7128
|
+
// How long the model thinks, saved per model. The picker offers only
|
|
7129
|
+
// what the current model takes, so a stored setting is always one it
|
|
7130
|
+
// has; a typed level it does not have moves to the nearest one below,
|
|
7131
|
+
// the gateway's rule, and says so. Every figure about cost stays in
|
|
7132
|
+
// words: what a setting costs depends on the task, and the meta line
|
|
7133
|
+
// under each turn already shows what it did cost.
|
|
7134
|
+
case "effort":
|
|
7135
|
+
await runBusy(LOADING, async (signal) => {
|
|
7136
|
+
const models = catalogRef.current.length ? catalogRef.current : await client.models(signal);
|
|
7137
|
+
catalogRef.current = models;
|
|
7138
|
+
const info2 = models.find((m) => m.id === model);
|
|
7139
|
+
const name = info2?.label ?? model;
|
|
7140
|
+
const levels = effortsOf(info2);
|
|
7141
|
+
const fallback = info2?.default_effort;
|
|
7142
|
+
if (!info2 || levels.length === 0) {
|
|
7143
|
+
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`;
|
|
7144
|
+
return void push({ kind: "notice", text: ` ${why}` });
|
|
7145
|
+
}
|
|
7146
|
+
const defaultWords = fallback ? effortLabel(fallback) : "its default";
|
|
7147
|
+
const apply2 = (choice, note = "") => {
|
|
7148
|
+
saveEffort(config, model, choice);
|
|
7149
|
+
setEffortBy({ ...config.effort ?? {} });
|
|
7150
|
+
const now = choice ? effortLabel(choice) : `auto (${defaultWords})`;
|
|
7151
|
+
push({ kind: "notice", text: ` ${name}: ${now}${note}` });
|
|
7152
|
+
};
|
|
7153
|
+
if (arg) {
|
|
7154
|
+
const wanted = parseEffortArg(arg);
|
|
7155
|
+
if (!wanted) {
|
|
7156
|
+
return void push({
|
|
7157
|
+
kind: "notice",
|
|
7158
|
+
tone: "warn",
|
|
7159
|
+
text: ` unknown effort: ${arg}. ${name} takes ${["auto", ...levels].join(", ")}`
|
|
7160
|
+
});
|
|
7161
|
+
}
|
|
7162
|
+
if (wanted === "auto") return apply2(void 0);
|
|
7163
|
+
const resolved = resolveEffort(info2, wanted);
|
|
7164
|
+
if (!resolved) return apply2(void 0, `. ${name} can only switch its thinking off`);
|
|
7165
|
+
return apply2(resolved, resolved === wanted ? "" : `. It has no ${wanted}, so this is the nearest below`);
|
|
7166
|
+
}
|
|
7167
|
+
const current = resolveEffort(info2, savedEffort(config, model));
|
|
7168
|
+
const items = [
|
|
7169
|
+
{ value: "auto", label: "auto", hint: `the model's default (${defaultWords})`, current: !current },
|
|
7170
|
+
...levels.map((level) => ({
|
|
7171
|
+
value: level,
|
|
7172
|
+
label: level,
|
|
7173
|
+
hint: level === fallback ? "same as auto" : effortRaised(info2, level) ? "more thinking, more credits" : "less thinking, fewer credits",
|
|
7174
|
+
current: current === level
|
|
7175
|
+
}))
|
|
7176
|
+
];
|
|
7177
|
+
setPickerSel(Math.max(0, items.findIndex((i) => i.current)));
|
|
7178
|
+
setPicker({
|
|
7179
|
+
title: `Effort for ${name}`,
|
|
7180
|
+
subtitle: "Saved for this model. Thinking is billed as output, like the answer.",
|
|
7181
|
+
items,
|
|
7182
|
+
onPick: (choice) => apply2(choice.value === "auto" ? void 0 : choice.value)
|
|
7183
|
+
});
|
|
7184
|
+
});
|
|
7185
|
+
return;
|
|
6891
7186
|
case "models":
|
|
6892
7187
|
await runBusy(LOADING, async (signal) => {
|
|
6893
7188
|
const ms = await client.models(signal);
|
|
@@ -7562,8 +7857,9 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
7562
7857
|
const typed = toText(editor);
|
|
7563
7858
|
let estimate;
|
|
7564
7859
|
const info = catalogRef.current.find((m) => m.id === model);
|
|
7860
|
+
const effortNow = info ? resolveEffort(info, effortBy[model]) : effortBy[model];
|
|
7565
7861
|
const shape = client.taskShape;
|
|
7566
|
-
if (!busy && info && shape && balance !== void 0 && typed.trim() !== "" && !/^[/!]/.test(typed.trimStart())) {
|
|
7862
|
+
if (!busy && info && shape && balance !== void 0 && !effortRaised(info, resolveEffort(info, effortBy[model])) && typed.trim() !== "" && !/^[/!]/.test(typed.trimStart())) {
|
|
7567
7863
|
const range = estimateTask(info, conversationUsed + schemaTokens + estimateTokens(typed), shape);
|
|
7568
7864
|
const onFree = Boolean(info.free && freeTier?.models.includes(model));
|
|
7569
7865
|
const verdict = taskVerdict(range, { balance, ...onFree ? { freeLeft: freeTier.credits_left } : {} });
|
|
@@ -7574,6 +7870,7 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
7574
7870
|
` ${statusLine(
|
|
7575
7871
|
{
|
|
7576
7872
|
model,
|
|
7873
|
+
...effortNow ? { effort: effortNow } : {},
|
|
7577
7874
|
balance,
|
|
7578
7875
|
spent,
|
|
7579
7876
|
contextPct,
|
|
@@ -7772,6 +8069,8 @@ function statusRow(o, estimate, cols) {
|
|
|
7772
8069
|
const burn = o.spent > 0 && o.minutes >= 1 ? `${Math.round(o.spent / o.minutes).toLocaleString("en-US")} cr/min` : void 0;
|
|
7773
8070
|
const clauses = [
|
|
7774
8071
|
{ text: o.model, rank: KEEP_ALWAYS },
|
|
8072
|
+
// Beside the model it qualifies, and given up with the free allowance.
|
|
8073
|
+
{ text: o.effort === void 0 ? void 0 : effortLabel(o.effort), rank: 4 },
|
|
7775
8074
|
{
|
|
7776
8075
|
// "? cr" rather than "0 cr": unknown is not empty, and an empty-looking
|
|
7777
8076
|
// wallet is the one misreading on this row that frightens somebody.
|
|
@@ -7857,6 +8156,7 @@ var init_app = __esm({
|
|
|
7857
8156
|
init_todos();
|
|
7858
8157
|
init_shells();
|
|
7859
8158
|
init_config();
|
|
8159
|
+
init_effort();
|
|
7860
8160
|
init_login();
|
|
7861
8161
|
init_banner();
|
|
7862
8162
|
init_agent();
|
|
@@ -7876,6 +8176,7 @@ var init_app = __esm({
|
|
|
7876
8176
|
init_provider();
|
|
7877
8177
|
init_network();
|
|
7878
8178
|
init_formats();
|
|
8179
|
+
init_direct();
|
|
7879
8180
|
init_render();
|
|
7880
8181
|
init_commands();
|
|
7881
8182
|
init_editor();
|
|
@@ -8004,6 +8305,7 @@ function clearTerminalTitle(out = titleStream()) {
|
|
|
8004
8305
|
|
|
8005
8306
|
// src/main.ts
|
|
8006
8307
|
init_version();
|
|
8308
|
+
init_effort();
|
|
8007
8309
|
var c = palette({ isTTY: process.stdout.isTTY, env: process.env });
|
|
8008
8310
|
async function main() {
|
|
8009
8311
|
setTerminalTitle();
|
|
@@ -8300,6 +8602,7 @@ async function printCmd(client, config, task) {
|
|
|
8300
8602
|
try {
|
|
8301
8603
|
const res = await runAgent(client, task, {
|
|
8302
8604
|
model: config.model,
|
|
8605
|
+
effort: savedEffort(config, config.model),
|
|
8303
8606
|
root: process.cwd(),
|
|
8304
8607
|
systemPrompt: collectContext(process.cwd()).systemPrompt + (capabilities?.web_search ? "" : `
|
|
8305
8608
|
|
|
@@ -8445,6 +8748,7 @@ async function runTurn(client, config, messages) {
|
|
|
8445
8748
|
let started = false;
|
|
8446
8749
|
try {
|
|
8447
8750
|
const result = await client.chatStream(messages, config.model, {
|
|
8751
|
+
effort: savedEffort(config, config.model),
|
|
8448
8752
|
onDelta: (chunk) => {
|
|
8449
8753
|
if (!started) process.stdout.write(c.green("assistant "));
|
|
8450
8754
|
started = true;
|