@pentoshi/clai 2.0.52 → 2.0.54
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 -3
- package/dist/agent/runner.js +100 -69
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/tool-call-parser.js +6 -1
- package/dist/agent/tool-call-parser.js.map +1 -1
- package/dist/commands/providers.js +2 -0
- package/dist/commands/providers.js.map +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/llm/capabilities.js +5 -2
- package/dist/llm/capabilities.js.map +1 -1
- package/dist/llm/gemini.js +74 -22
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/groq.d.ts +1 -0
- package/dist/llm/groq.js +14 -0
- package/dist/llm/groq.js.map +1 -1
- package/dist/llm/http.d.ts +10 -2
- package/dist/llm/http.js +107 -45
- package/dist/llm/http.js.map +1 -1
- package/dist/llm/nvidia.js +5 -1
- package/dist/llm/nvidia.js.map +1 -1
- package/dist/llm/provider.js +7 -1
- package/dist/llm/provider.js.map +1 -1
- package/dist/llm/qwen-cloud.d.ts +2 -0
- package/dist/llm/qwen-cloud.js +81 -0
- package/dist/llm/qwen-cloud.js.map +1 -0
- package/dist/llm/router.js +9 -3
- package/dist/llm/router.js.map +1 -1
- package/dist/prompts/index.d.ts +6 -0
- package/dist/prompts/index.js +49 -0
- package/dist/prompts/index.js.map +1 -1
- package/dist/repl/slash-commands.js +7 -0
- package/dist/repl/slash-commands.js.map +1 -1
- package/dist/repl.js +35 -34
- package/dist/repl.js.map +1 -1
- package/dist/store/config.js +1 -0
- package/dist/store/config.js.map +1 -1
- package/dist/tui/App.js +126 -71
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/hooks/useAgentRunner.d.ts +3 -1
- package/dist/tui/hooks/useAgentRunner.js +8 -3
- package/dist/tui/hooks/useAgentRunner.js.map +1 -1
- package/dist/types.d.ts +8 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/tui/App.js
CHANGED
|
@@ -116,6 +116,11 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
|
|
|
116
116
|
const [selected, setSelected] = useState(0);
|
|
117
117
|
const [secretRequest, setSecretRequest] = useState();
|
|
118
118
|
const secretResolver = useRef();
|
|
119
|
+
// Provider picker selection is asynchronous when a key is required. Keep
|
|
120
|
+
// repeated Enter events (or a fast double-submit) from opening a second
|
|
121
|
+
// secret prompt before the first activation has finished persisting.
|
|
122
|
+
const providerActivationRef = useRef(undefined);
|
|
123
|
+
const exitRef = useRef(undefined);
|
|
119
124
|
const [scroll, setScroll] = useState(0); // lines scrolled up from bottom
|
|
120
125
|
const [compacting, setCompacting] = useState(false);
|
|
121
126
|
const [compactStartedAt, setCompactStartedAt] = useState(undefined);
|
|
@@ -139,6 +144,7 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
|
|
|
139
144
|
const titleRef = useRef({
|
|
140
145
|
titledAtUserCount: 0,
|
|
141
146
|
inFlight: false,
|
|
147
|
+
requestId: 0,
|
|
142
148
|
});
|
|
143
149
|
const compactAbortRef = useRef(undefined);
|
|
144
150
|
const cancelCompaction = useCallback(() => {
|
|
@@ -185,16 +191,23 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
|
|
|
185
191
|
setDefaultMode("agent");
|
|
186
192
|
dispatch({ type: "notice", level: "info", text: "mode → agent" });
|
|
187
193
|
}, []),
|
|
194
|
+
onForceExit: useCallback(() => exitRef.current?.(), []),
|
|
188
195
|
});
|
|
189
196
|
const exitTui = useCallback(() => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
cancelCompaction();
|
|
198
|
+
runner.abort();
|
|
199
|
+
if (autosaveTimer.current) {
|
|
200
|
+
clearTimeout(autosaveTimer.current);
|
|
201
|
+
autosaveTimer.current = undefined;
|
|
202
|
+
}
|
|
203
|
+
const messages = runner.getMessages();
|
|
204
|
+
if (!noHistory && !getConfig().privateMode && messages.length > 0) {
|
|
205
|
+
// Shutdown must not wait for a slow history backend.
|
|
206
|
+
void upsertSession(runner.getSession().sessionId, messages, undefined, latestItems.current).catch(() => undefined);
|
|
207
|
+
}
|
|
208
|
+
exit();
|
|
209
|
+
}, [cancelCompaction, exit, noHistory, runner]);
|
|
210
|
+
exitRef.current = exitTui;
|
|
198
211
|
// Generate (and later refresh) a concise, model-written title for the
|
|
199
212
|
// active session. Runs only when a real exchange exists, throttles by user
|
|
200
213
|
// turn count, and guards against overlapping requests and session resets.
|
|
@@ -215,24 +228,34 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
|
|
|
215
228
|
if (!shouldGenerate)
|
|
216
229
|
return;
|
|
217
230
|
titleRef.current.inFlight = true;
|
|
231
|
+
const requestId = ++titleRef.current.requestId;
|
|
218
232
|
const targetCount = userCount;
|
|
219
233
|
const ctx = ctxRef.current;
|
|
234
|
+
// Keep immutable snapshots. The user may start `/new`, resume another
|
|
235
|
+
// session, or exit while the title request is in flight.
|
|
236
|
+
const titleMessages = messages.map((message) => ({ ...message }));
|
|
237
|
+
const titleTranscript = latestItems.current.map((item) => ({ ...item }));
|
|
220
238
|
void generateSessionTitle(messages, {
|
|
221
239
|
provider: ctx.provider,
|
|
222
240
|
model: ctx.model,
|
|
223
241
|
})
|
|
224
242
|
.then((title) => {
|
|
225
|
-
titleRef.current.
|
|
243
|
+
if (titleRef.current.requestId === requestId) {
|
|
244
|
+
titleRef.current.inFlight = false;
|
|
245
|
+
}
|
|
226
246
|
if (!title)
|
|
227
247
|
return;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
248
|
+
if (titleRef.current.requestId === requestId) {
|
|
249
|
+
titleRef.current.titledAtUserCount = targetCount;
|
|
250
|
+
}
|
|
251
|
+
// Persist against the original session, not whatever session is
|
|
252
|
+
// currently visible in the TUI.
|
|
253
|
+
void upsertSession(sessionId, titleMessages, title, titleTranscript).catch(() => undefined);
|
|
233
254
|
})
|
|
234
255
|
.catch(() => {
|
|
235
|
-
titleRef.current.
|
|
256
|
+
if (titleRef.current.requestId === requestId) {
|
|
257
|
+
titleRef.current.inFlight = false;
|
|
258
|
+
}
|
|
236
259
|
});
|
|
237
260
|
}, [noHistory, runner]);
|
|
238
261
|
useEffect(() => {
|
|
@@ -428,43 +451,64 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
|
|
|
428
451
|
});
|
|
429
452
|
}, [model, provider]);
|
|
430
453
|
const activateProvider = useCallback(async (next) => {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
const trimmedKey = key.trim();
|
|
448
|
-
if (!getProvider(next).validateKey(trimmedKey)) {
|
|
449
|
-
dispatch({
|
|
450
|
-
type: "notice",
|
|
451
|
-
level: "warn",
|
|
452
|
-
text: `invalid API key format for ${next}`,
|
|
454
|
+
if (providerActivationRef.current)
|
|
455
|
+
return;
|
|
456
|
+
providerActivationRef.current = next;
|
|
457
|
+
// Unmount the picker before opening the secret prompt. This makes the
|
|
458
|
+
// prompt the sole owner of stdin while activation is in progress.
|
|
459
|
+
setOverlay({ kind: "none" });
|
|
460
|
+
try {
|
|
461
|
+
const configured = next === "ollama" ||
|
|
462
|
+
Boolean(envValue(next)) ||
|
|
463
|
+
Boolean((await getProviderSecret(next)).value);
|
|
464
|
+
if (!configured) {
|
|
465
|
+
const key = await requestSecret({
|
|
466
|
+
title: `${next} API key`,
|
|
467
|
+
prompt: `No API key is configured for ${next}. Enter it now to activate this provider.`,
|
|
453
468
|
});
|
|
454
|
-
|
|
469
|
+
if (!key) {
|
|
470
|
+
dispatch({
|
|
471
|
+
type: "notice",
|
|
472
|
+
level: "warn",
|
|
473
|
+
text: `provider unchanged · ${next} needs an API key`,
|
|
474
|
+
});
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const trimmedKey = key.trim();
|
|
478
|
+
if (!getProvider(next).validateKey(trimmedKey)) {
|
|
479
|
+
dispatch({
|
|
480
|
+
type: "notice",
|
|
481
|
+
level: "warn",
|
|
482
|
+
text: `invalid API key format for ${next}`,
|
|
483
|
+
});
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
await setProviderSecret(next, trimmedKey);
|
|
487
|
+
const saved = await getProviderSecret(next);
|
|
488
|
+
if (saved.value !== trimmedKey) {
|
|
489
|
+
throw new Error(`could not persist the ${next} API key; provider was not changed`);
|
|
490
|
+
}
|
|
455
491
|
}
|
|
456
|
-
|
|
492
|
+
const nextModel = getProviderModel(next);
|
|
493
|
+
setDefaultProvider(next);
|
|
494
|
+
setProvider(next);
|
|
495
|
+
setModel(nextModel);
|
|
496
|
+
dispatch({
|
|
497
|
+
type: "notice",
|
|
498
|
+
level: "info",
|
|
499
|
+
text: `provider → ${next} · model → ${nextModel}`,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
dispatch({
|
|
504
|
+
type: "notice",
|
|
505
|
+
level: "warn",
|
|
506
|
+
text: error instanceof Error ? error.message : String(error),
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
finally {
|
|
510
|
+
providerActivationRef.current = undefined;
|
|
457
511
|
}
|
|
458
|
-
const nextModel = getProviderModel(next);
|
|
459
|
-
setDefaultProvider(next);
|
|
460
|
-
setProvider(next);
|
|
461
|
-
setModel(nextModel);
|
|
462
|
-
setOverlay({ kind: "none" });
|
|
463
|
-
dispatch({
|
|
464
|
-
type: "notice",
|
|
465
|
-
level: "info",
|
|
466
|
-
text: `provider → ${next} · model → ${nextModel}`,
|
|
467
|
-
});
|
|
468
512
|
}, [requestSecret]);
|
|
469
513
|
const chooseProvider = useCallback(() => {
|
|
470
514
|
setOverlay({
|
|
@@ -593,25 +637,36 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
|
|
|
593
637
|
return true;
|
|
594
638
|
case "/new":
|
|
595
639
|
cancelCompaction();
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
640
|
+
runner.abort();
|
|
641
|
+
if (autosaveTimer.current) {
|
|
642
|
+
clearTimeout(autosaveTimer.current);
|
|
643
|
+
autosaveTimer.current = undefined;
|
|
644
|
+
}
|
|
645
|
+
const messages = runner.getMessages();
|
|
646
|
+
if (!noHistory &&
|
|
647
|
+
!getConfig().privateMode &&
|
|
648
|
+
messages.some((m) => m.role === "user")) {
|
|
649
|
+
// Persist in the background; the new session must be interactive
|
|
650
|
+
// immediately even if the history backend is slow or locked.
|
|
651
|
+
void upsertSession(runner.getSession().sessionId, messages, undefined, latestItems.current).catch(() => undefined);
|
|
652
|
+
}
|
|
653
|
+
runner.reset();
|
|
654
|
+
titleRef.current = {
|
|
655
|
+
titledAtUserCount: 0,
|
|
656
|
+
inFlight: false,
|
|
657
|
+
requestId: titleRef.current.requestId + 1,
|
|
658
|
+
};
|
|
659
|
+
dispatch({ type: "reset" });
|
|
660
|
+
info("fresh session started");
|
|
610
661
|
return true;
|
|
611
662
|
case "/clean":
|
|
612
663
|
cancelCompaction();
|
|
613
664
|
runner.reset();
|
|
614
|
-
titleRef.current = {
|
|
665
|
+
titleRef.current = {
|
|
666
|
+
titledAtUserCount: 0,
|
|
667
|
+
inFlight: false,
|
|
668
|
+
requestId: titleRef.current.requestId + 1,
|
|
669
|
+
};
|
|
615
670
|
dispatch({ type: "reset" });
|
|
616
671
|
info("fresh session started");
|
|
617
672
|
return true;
|
|
@@ -919,6 +974,7 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
|
|
|
919
974
|
titleRef.current = {
|
|
920
975
|
titledAtUserCount: session.messages.filter((m) => m.role === "user").length,
|
|
921
976
|
inFlight: false,
|
|
977
|
+
requestId: titleRef.current.requestId + 1,
|
|
922
978
|
};
|
|
923
979
|
setOverlay({ kind: "none" });
|
|
924
980
|
dispatch({
|
|
@@ -1870,15 +1926,14 @@ export function App({ version, initialMode, provider: initialProvider, initialMo
|
|
|
1870
1926
|
return;
|
|
1871
1927
|
}
|
|
1872
1928
|
if (key.ctrl && ch === "c") {
|
|
1873
|
-
if (runner.isRunning()) {
|
|
1874
|
-
runner.abort();
|
|
1875
|
-
return;
|
|
1876
|
-
}
|
|
1877
1929
|
const now = Date.now();
|
|
1878
|
-
if (now - lastCtrlC.current < 1500)
|
|
1930
|
+
if (now - lastCtrlC.current < 1500) {
|
|
1879
1931
|
exitTui();
|
|
1880
|
-
|
|
1881
|
-
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
lastCtrlC.current = now;
|
|
1935
|
+
if (runner.isRunning())
|
|
1936
|
+
runner.abort();
|
|
1882
1937
|
return;
|
|
1883
1938
|
}
|
|
1884
1939
|
// Slash menu navigation
|