clixad 0.0.1-beta.25 → 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 +150 -7
- 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
|
@@ -1228,9 +1228,9 @@ var init_client = __esm({
|
|
|
1228
1228
|
return retry;
|
|
1229
1229
|
}
|
|
1230
1230
|
/** Non-streaming chat. Kept for scripted/one-shot paths; the REPL streams. */
|
|
1231
|
-
async chat(messages, model, tools, signal) {
|
|
1231
|
+
async chat(messages, model, tools, signal, effort) {
|
|
1232
1232
|
const res = await this.postChat(
|
|
1233
|
-
{ model, messages, stream: false, ...tools ? { tools } : {} },
|
|
1233
|
+
{ model, messages, stream: false, ...tools ? { tools } : {}, ...effort ? { reasoning_effort: effort } : {} },
|
|
1234
1234
|
signal
|
|
1235
1235
|
);
|
|
1236
1236
|
if (res.status === 401) throw await authFailure(res, "chat");
|
|
@@ -1257,7 +1257,13 @@ var init_client = __esm({
|
|
|
1257
1257
|
*/
|
|
1258
1258
|
async chatStream(messages, model, opts = {}) {
|
|
1259
1259
|
const res = await this.postChat(
|
|
1260
|
-
{
|
|
1260
|
+
{
|
|
1261
|
+
model,
|
|
1262
|
+
messages,
|
|
1263
|
+
stream: true,
|
|
1264
|
+
...opts.tools ? { tools: opts.tools } : {},
|
|
1265
|
+
...opts.effort ? { reasoning_effort: opts.effort } : {}
|
|
1266
|
+
},
|
|
1261
1267
|
opts.signal
|
|
1262
1268
|
);
|
|
1263
1269
|
if (res.status === 401) throw await authFailure(res, "chat");
|
|
@@ -1460,6 +1466,18 @@ function saveConfig(config) {
|
|
|
1460
1466
|
} catch {
|
|
1461
1467
|
}
|
|
1462
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
|
+
}
|
|
1463
1481
|
function configPath() {
|
|
1464
1482
|
return CONFIG_PATH;
|
|
1465
1483
|
}
|
|
@@ -4173,6 +4191,9 @@ async function runAgent(client, task, opts) {
|
|
|
4173
4191
|
onProgress?.(`${description}: starting`);
|
|
4174
4192
|
const sub = await runAgent(client, prompt, {
|
|
4175
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,
|
|
4176
4197
|
root: opts.root,
|
|
4177
4198
|
// A sub-agent that was told it can search the web, on a gateway that
|
|
4178
4199
|
// cannot, spends its whole step budget discovering that.
|
|
@@ -4226,6 +4247,7 @@ ${NO_SEARCH_INSTRUCTION}`,
|
|
|
4226
4247
|
let turn;
|
|
4227
4248
|
try {
|
|
4228
4249
|
turn = await client.chatStream(messages, opts.model, {
|
|
4250
|
+
effort: opts.effort,
|
|
4229
4251
|
tools: opts.tools ? opts.tools() : TOOL_SCHEMA,
|
|
4230
4252
|
signal: opts.signal,
|
|
4231
4253
|
onDelta: (text) => emit({ type: "delta", text })
|
|
@@ -5097,6 +5119,50 @@ var init_version = __esm({
|
|
|
5097
5119
|
}
|
|
5098
5120
|
});
|
|
5099
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
|
+
|
|
5100
5166
|
// src/mentions.ts
|
|
5101
5167
|
function findMentions(text) {
|
|
5102
5168
|
const out = [];
|
|
@@ -5187,7 +5253,9 @@ async function compact(client, model, messages, opts = {}) {
|
|
|
5187
5253
|
const turn = await client.chatStream(
|
|
5188
5254
|
[...messages.slice(0, cut), { role: "user", content: SUMMARY_PROMPT }],
|
|
5189
5255
|
model,
|
|
5190
|
-
|
|
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 }
|
|
5191
5259
|
);
|
|
5192
5260
|
const summary = messageText(turn.message).trim() || "(no summary returned)";
|
|
5193
5261
|
const compacted = [
|
|
@@ -5460,6 +5528,10 @@ var init_commands = __esm({
|
|
|
5460
5528
|
{ name: "help", desc: "list the commands" },
|
|
5461
5529
|
{ name: "model", desc: "switch model" },
|
|
5462
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)" },
|
|
5463
5535
|
{ name: "mode", args: "[name]", desc: "permission mode (or press shift+tab)" },
|
|
5464
5536
|
{ name: "wallet", desc: "balance & ads today" },
|
|
5465
5537
|
// Not "watch an ad": nothing on the wall is a video. CPX serves sign-up
|
|
@@ -6024,6 +6096,7 @@ function App({
|
|
|
6024
6096
|
const [spent, setSpent] = useState(0);
|
|
6025
6097
|
const [freeTier, setFreeTier] = useState(wallet?.free_tier);
|
|
6026
6098
|
const [model, setModel2] = useState(config.model);
|
|
6099
|
+
const [effortBy, setEffortBy] = useState(() => config.effort ?? {});
|
|
6027
6100
|
const modelRef = useRef(config.model);
|
|
6028
6101
|
const [mode, setMode] = useState("normal");
|
|
6029
6102
|
const switchModel = useCallback((id) => {
|
|
@@ -6513,7 +6586,7 @@ function App({
|
|
|
6513
6586
|
const res = await compact(client, model2, [
|
|
6514
6587
|
{ role: "system", content: contextRef.current.systemPrompt },
|
|
6515
6588
|
...history
|
|
6516
|
-
], { signal: ac.signal });
|
|
6589
|
+
], { signal: ac.signal, effort: savedEffort(config, model2) });
|
|
6517
6590
|
if (res) {
|
|
6518
6591
|
history = res.messages.slice(1);
|
|
6519
6592
|
setMessages(history);
|
|
@@ -6527,6 +6600,10 @@ function App({
|
|
|
6527
6600
|
}
|
|
6528
6601
|
const result = await runAgent(client, task, {
|
|
6529
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),
|
|
6530
6607
|
root,
|
|
6531
6608
|
history,
|
|
6532
6609
|
attachments: images,
|
|
@@ -6996,7 +7073,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
6996
7073
|
client,
|
|
6997
7074
|
model,
|
|
6998
7075
|
[{ role: "system", content: contextRef.current.systemPrompt }, ...messagesRef.current],
|
|
6999
|
-
{ signal }
|
|
7076
|
+
{ signal, effort: savedEffort(config, model) }
|
|
7000
7077
|
);
|
|
7001
7078
|
if (!res) return void push({ kind: "notice", text: " nothing to compact yet" });
|
|
7002
7079
|
setMessages(res.messages.slice(1));
|
|
@@ -7048,6 +7125,64 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
7048
7125
|
});
|
|
7049
7126
|
});
|
|
7050
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;
|
|
7051
7186
|
case "models":
|
|
7052
7187
|
await runBusy(LOADING, async (signal) => {
|
|
7053
7188
|
const ms = await client.models(signal);
|
|
@@ -7722,8 +7857,9 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
7722
7857
|
const typed = toText(editor);
|
|
7723
7858
|
let estimate;
|
|
7724
7859
|
const info = catalogRef.current.find((m) => m.id === model);
|
|
7860
|
+
const effortNow = info ? resolveEffort(info, effortBy[model]) : effortBy[model];
|
|
7725
7861
|
const shape = client.taskShape;
|
|
7726
|
-
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())) {
|
|
7727
7863
|
const range = estimateTask(info, conversationUsed + schemaTokens + estimateTokens(typed), shape);
|
|
7728
7864
|
const onFree = Boolean(info.free && freeTier?.models.includes(model));
|
|
7729
7865
|
const verdict = taskVerdict(range, { balance, ...onFree ? { freeLeft: freeTier.credits_left } : {} });
|
|
@@ -7734,6 +7870,7 @@ ${dropped.map((l) => ` \xB7 ${l}`).join("\n")}`
|
|
|
7734
7870
|
` ${statusLine(
|
|
7735
7871
|
{
|
|
7736
7872
|
model,
|
|
7873
|
+
...effortNow ? { effort: effortNow } : {},
|
|
7737
7874
|
balance,
|
|
7738
7875
|
spent,
|
|
7739
7876
|
contextPct,
|
|
@@ -7932,6 +8069,8 @@ function statusRow(o, estimate, cols) {
|
|
|
7932
8069
|
const burn = o.spent > 0 && o.minutes >= 1 ? `${Math.round(o.spent / o.minutes).toLocaleString("en-US")} cr/min` : void 0;
|
|
7933
8070
|
const clauses = [
|
|
7934
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 },
|
|
7935
8074
|
{
|
|
7936
8075
|
// "? cr" rather than "0 cr": unknown is not empty, and an empty-looking
|
|
7937
8076
|
// wallet is the one misreading on this row that frightens somebody.
|
|
@@ -8017,6 +8156,7 @@ var init_app = __esm({
|
|
|
8017
8156
|
init_todos();
|
|
8018
8157
|
init_shells();
|
|
8019
8158
|
init_config();
|
|
8159
|
+
init_effort();
|
|
8020
8160
|
init_login();
|
|
8021
8161
|
init_banner();
|
|
8022
8162
|
init_agent();
|
|
@@ -8165,6 +8305,7 @@ function clearTerminalTitle(out = titleStream()) {
|
|
|
8165
8305
|
|
|
8166
8306
|
// src/main.ts
|
|
8167
8307
|
init_version();
|
|
8308
|
+
init_effort();
|
|
8168
8309
|
var c = palette({ isTTY: process.stdout.isTTY, env: process.env });
|
|
8169
8310
|
async function main() {
|
|
8170
8311
|
setTerminalTitle();
|
|
@@ -8461,6 +8602,7 @@ async function printCmd(client, config, task) {
|
|
|
8461
8602
|
try {
|
|
8462
8603
|
const res = await runAgent(client, task, {
|
|
8463
8604
|
model: config.model,
|
|
8605
|
+
effort: savedEffort(config, config.model),
|
|
8464
8606
|
root: process.cwd(),
|
|
8465
8607
|
systemPrompt: collectContext(process.cwd()).systemPrompt + (capabilities?.web_search ? "" : `
|
|
8466
8608
|
|
|
@@ -8606,6 +8748,7 @@ async function runTurn(client, config, messages) {
|
|
|
8606
8748
|
let started = false;
|
|
8607
8749
|
try {
|
|
8608
8750
|
const result = await client.chatStream(messages, config.model, {
|
|
8751
|
+
effort: savedEffort(config, config.model),
|
|
8609
8752
|
onDelta: (chunk) => {
|
|
8610
8753
|
if (!started) process.stdout.write(c.green("assistant "));
|
|
8611
8754
|
started = true;
|