bermudis-pi-goodies 0.26.0 → 0.27.1
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 +19 -7
- package/package.json +1 -1
- package/provider-balance.ts +18 -9
- package/side-state.ts +37 -0
- package/side.ts +57 -4
- package/vision-core.ts +25 -5
- package/vision.ts +25 -15
package/README.md
CHANGED
|
@@ -23,10 +23,17 @@ extensions. One entry point, thirteen independent features.
|
|
|
23
23
|
|
|
24
24
|
## Install
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
First install, pinned to a published version:
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
pi install npm:bermudis-pi-goodies@0.
|
|
29
|
+
pi install npm:bermudis-pi-goodies@0.27.1
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Picking up a new release on an existing install (`pi install` is not the
|
|
33
|
+
update path):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pi update --extensions
|
|
30
37
|
```
|
|
31
38
|
|
|
32
39
|
Remove any old `bermudis-pi-goodies.ts` symlink before reloading Pi. Each
|
|
@@ -373,11 +380,16 @@ session. `/side-exit` closes it and chooses what the main agent receives.
|
|
|
373
380
|
How it works:
|
|
374
381
|
|
|
375
382
|
- **Badge**: while a side session is open, the footer's model readout becomes
|
|
376
|
-
`side: (zai) glm-5.3 • max` — the provider-balance footer
|
|
377
|
-
badge to the stats line instead of adding a separate `side:`
|
|
378
|
-
(which remains the fallback when that footer is not mounted).
|
|
379
|
-
|
|
380
|
-
|
|
383
|
+
`side: (zai) glm-5.3 • max · ~46k lensed` — the provider-balance footer
|
|
384
|
+
prepends the badge to the stats line instead of adding a separate `side:`
|
|
385
|
+
status line (which remains the fallback when that footer is not mounted).
|
|
386
|
+
The `~46k lensed` suffix is the estimated size of the request the lens will
|
|
387
|
+
actually send (quote + side turns, pi's own chars/4 estimator): pi's footer
|
|
388
|
+
context-usage figure estimates the raw session branch, which the lens then
|
|
389
|
+
rewrites at the wire, so it over-reports during side sessions. The suffix
|
|
390
|
+
is dropped first when the line gets tight. The readout tracks mid-side
|
|
391
|
+
ctrl+l switches, and the badge clears on exit or when switching to a
|
|
392
|
+
session that is not in a side consultation.
|
|
381
393
|
- **Entry**: `/side` opens pi's own model selector (falls back to a plain
|
|
382
394
|
selector when pi's internal runtime is not reachable; in headless modes pass
|
|
383
395
|
the model explicitly as `/side provider/model-id[:level]`). The main agent's
|
package/package.json
CHANGED
package/provider-balance.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
import { getKiloCatalogStatus, type KiloCatalogStatus } from "./kilo.ts";
|
|
33
33
|
import { logGoodiesEvent, reportFailure } from "./goodies-log.ts";
|
|
34
34
|
import {
|
|
35
|
+
formatSideLensSuffix,
|
|
35
36
|
getSideSessionModel,
|
|
36
37
|
onSideBadgeChange,
|
|
37
38
|
setMergedSideBadgeInstalled,
|
|
@@ -1306,11 +1307,13 @@ const SGR_SEQUENCE = /\x1b\[[0-9;:]*m/g;
|
|
|
1306
1307
|
|
|
1307
1308
|
/**
|
|
1308
1309
|
* Prepend a badge to the right-aligned model readout of the stats line pi's
|
|
1309
|
-
* FooterComponent produced: `side: (zai) glm-5.3 • max
|
|
1310
|
-
* as `<dim statsLeft><dim padding + model>` — split at the
|
|
1311
|
-
* final styled run (the last SGR sequence is its closer, so
|
|
1312
|
-
* the second-to-last), keep the left half verbatim (it may
|
|
1313
|
-
* context percentage), and rebuild the right half around
|
|
1310
|
+
* FooterComponent produced: `side: (zai) glm-5.3 • max · ~46k lensed`. The
|
|
1311
|
+
* line is built as `<dim statsLeft><dim padding + model>` — split at the
|
|
1312
|
+
* opener of the final styled run (the last SGR sequence is its closer, so
|
|
1313
|
+
* the opener is the second-to-last), keep the left half verbatim (it may
|
|
1314
|
+
* carry a colored context percentage), and rebuild the right half around
|
|
1315
|
+
* the badge. A suffix that does not fit is dropped before the badge itself
|
|
1316
|
+
* is ever truncated — the model name outranks the token estimate.
|
|
1314
1317
|
*
|
|
1315
1318
|
* Returns undefined when the line lacks that two-run shape — the caller then
|
|
1316
1319
|
* leaves the line untouched and the /side badge falls back to a status line.
|
|
@@ -1322,6 +1325,7 @@ export function mergeSideBadgeIntoStatsLine(
|
|
|
1322
1325
|
width: number,
|
|
1323
1326
|
theme: FooterTheme,
|
|
1324
1327
|
prefix = "side: ",
|
|
1328
|
+
suffix = "",
|
|
1325
1329
|
): string | undefined {
|
|
1326
1330
|
if (!line || width <= 0) return undefined;
|
|
1327
1331
|
const runs = [...line.matchAll(SGR_SEQUENCE)];
|
|
@@ -1332,14 +1336,17 @@ export function mergeSideBadgeIntoStatsLine(
|
|
|
1332
1336
|
const tail = stripTerminalSequences(line.slice(opener)).trim();
|
|
1333
1337
|
if (!tail || !stripTerminalSequences(head).trim()) return undefined;
|
|
1334
1338
|
|
|
1335
|
-
const
|
|
1339
|
+
const withSuffix = `${prefix}${tail}${suffix}`;
|
|
1340
|
+
const bare = `${prefix}${tail}`;
|
|
1336
1341
|
const headWidth = visibleWidth(head);
|
|
1337
1342
|
const room = width - headWidth - 2; // pi keeps ≥2 columns between the halves
|
|
1338
1343
|
if (room <= 0) return undefined;
|
|
1339
1344
|
const right =
|
|
1340
|
-
visibleWidth(
|
|
1341
|
-
?
|
|
1342
|
-
:
|
|
1345
|
+
visibleWidth(withSuffix) <= room
|
|
1346
|
+
? withSuffix
|
|
1347
|
+
: visibleWidth(bare) <= room
|
|
1348
|
+
? bare
|
|
1349
|
+
: truncateToWidth(bare, room, "");
|
|
1343
1350
|
const padding = " ".repeat(
|
|
1344
1351
|
Math.max(2, width - headWidth - visibleWidth(right)),
|
|
1345
1352
|
);
|
|
@@ -1781,6 +1788,8 @@ export default function providerBalance(
|
|
|
1781
1788
|
lines[1],
|
|
1782
1789
|
width,
|
|
1783
1790
|
theme,
|
|
1791
|
+
"side: ",
|
|
1792
|
+
formatSideLensSuffix(),
|
|
1784
1793
|
);
|
|
1785
1794
|
if (statsLine !== undefined) {
|
|
1786
1795
|
lines[1] = statsLine;
|
package/side-state.ts
CHANGED
|
@@ -54,6 +54,42 @@ export function getSideSessionModel(): SideModelRef | undefined {
|
|
|
54
54
|
return activeModel;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
let lensTokens: number | undefined;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* side.ts: estimated token size of the LENSED request (the quote + side
|
|
61
|
+
* turns actually sent to the side model), via pi's own chars/4 estimator
|
|
62
|
+
* so it stays comparable with the footer's raw-branch figure. Undefined on
|
|
63
|
+
* non-side branches — pi's footer estimate is correct there.
|
|
64
|
+
*
|
|
65
|
+
* Why: pi's footer context-usage and pre-send compaction threshold both
|
|
66
|
+
* estimate the RAW session projection; the side lens rewrites the request
|
|
67
|
+
* at the wire, after every estimator. The footer therefore over-reports
|
|
68
|
+
* while a side session is active. The badge carries this estimate as the
|
|
69
|
+
* honest counterpoint.
|
|
70
|
+
*/
|
|
71
|
+
export function setSideLensTokens(tokens: number | undefined): void {
|
|
72
|
+
if (lensTokens === tokens) return;
|
|
73
|
+
lensTokens = tokens;
|
|
74
|
+
emit();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getSideLensTokens(): number | undefined {
|
|
78
|
+
return lensTokens;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Badge suffix for the lensed estimate: " · ~46k lensed", or "" when off. */
|
|
82
|
+
export function formatSideLensSuffix(): string {
|
|
83
|
+
if (lensTokens === undefined || lensTokens < 1_000) return "";
|
|
84
|
+
return ` · ~${formatSideLensTokens(lensTokens)} lensed`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatSideLensTokens(tokens: number): string {
|
|
88
|
+
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`;
|
|
89
|
+
if (tokens >= 10_000) return `${Math.round(tokens / 1_000)}k`;
|
|
90
|
+
return `${(tokens / 1_000).toFixed(1)}k`;
|
|
91
|
+
}
|
|
92
|
+
|
|
57
93
|
/** provider-balance: its footer now owns (or handed back) the stats line. */
|
|
58
94
|
export function setMergedSideBadgeInstalled(installed: boolean): void {
|
|
59
95
|
if (mergedInstalled === installed) return;
|
|
@@ -88,6 +124,7 @@ export function onSideBadgeChange(listener: () => void): () => void {
|
|
|
88
124
|
/** Test-only: restore the pristine module state (undefined/false/false). */
|
|
89
125
|
export function resetSideBadgeState(): void {
|
|
90
126
|
activeModel = undefined;
|
|
127
|
+
lensTokens = undefined;
|
|
91
128
|
mergedInstalled = false;
|
|
92
129
|
mergedRendered = false;
|
|
93
130
|
listeners.clear();
|
package/side.ts
CHANGED
|
@@ -28,9 +28,13 @@
|
|
|
28
28
|
* limb that is ever resumed delivers only newer turns. v1 always starts a
|
|
29
29
|
* fresh side limb, but the bookkeeping keeps that future-safe.
|
|
30
30
|
*
|
|
31
|
-
* Known
|
|
32
|
-
* raw session branch, not the lens
|
|
33
|
-
* session is active
|
|
31
|
+
* Known limitation: pi's footer context-usage estimate (and its pre-send
|
|
32
|
+
* compaction threshold) reflect the raw session branch, not the lens
|
|
33
|
+
* output — the footer over-reports while a side session is active, and
|
|
34
|
+
* premature auto-compaction is possible on large side consults (the lens
|
|
35
|
+
* is compaction-aware and survives it; the main limb is never touched).
|
|
36
|
+
* The side badge carries pi's own estimator applied to the lensed messages
|
|
37
|
+
* ("· ~46k lensed") as the honest counterpoint to the inflated figure.
|
|
34
38
|
*/
|
|
35
39
|
|
|
36
40
|
import type {
|
|
@@ -43,15 +47,18 @@ import type {
|
|
|
43
47
|
} from "@earendil-works/pi-coding-agent";
|
|
44
48
|
import {
|
|
45
49
|
ModelSelectorComponent,
|
|
50
|
+
estimateTokens,
|
|
46
51
|
sessionEntryToContextMessages,
|
|
47
52
|
} from "@earendil-works/pi-coding-agent";
|
|
48
53
|
import { describeError, extractTextParts } from "./json-file.ts";
|
|
49
54
|
import { buildTrajectory, renderTrajectory } from "./copy-trajectory.ts";
|
|
50
55
|
import { logGoodiesEvent } from "./goodies-log.ts";
|
|
51
56
|
import {
|
|
57
|
+
formatSideLensSuffix,
|
|
52
58
|
getSideSessionModel,
|
|
53
59
|
isMergedSideBadgeActive,
|
|
54
60
|
onSideBadgeChange,
|
|
61
|
+
setSideLensTokens,
|
|
55
62
|
setSideSessionModel,
|
|
56
63
|
type SideModelRef,
|
|
57
64
|
} from "./side-state.ts";
|
|
@@ -298,6 +305,22 @@ export function buildLensMessages(
|
|
|
298
305
|
return messages;
|
|
299
306
|
}
|
|
300
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Estimate the token size of the lensed request pi will actually send
|
|
310
|
+
* (quote + side-native messages), using pi's own chars/4 estimator so the
|
|
311
|
+
* number is comparable with the footer's raw-branch estimate. Undefined
|
|
312
|
+
* when markerIdx does not mark a side session.
|
|
313
|
+
*/
|
|
314
|
+
export function estimateLensedContextTokens(
|
|
315
|
+
awareEntries: readonly SessionEntry[],
|
|
316
|
+
rawBranch: readonly SessionEntry[],
|
|
317
|
+
markerIdx: number,
|
|
318
|
+
): number | undefined {
|
|
319
|
+
const messages = buildLensMessages(awareEntries, rawBranch, markerIdx, []);
|
|
320
|
+
if (!messages) return undefined;
|
|
321
|
+
return messages.reduce((sum, message) => sum + estimateTokens(message), 0);
|
|
322
|
+
}
|
|
323
|
+
|
|
301
324
|
// ---------------------------------------------------------------------------
|
|
302
325
|
// Handoff: what the main agent is given at exit
|
|
303
326
|
// ---------------------------------------------------------------------------
|
|
@@ -511,7 +534,7 @@ export function applySideStatus(ctx: ExtensionContext): void {
|
|
|
511
534
|
const model: SideModelRef | undefined = getSideSessionModel();
|
|
512
535
|
const text =
|
|
513
536
|
model && !isMergedSideBadgeActive()
|
|
514
|
-
? `side: ${modelRef(model)}`
|
|
537
|
+
? `side: ${modelRef(model)}${formatSideLensSuffix()}`
|
|
515
538
|
: undefined;
|
|
516
539
|
ctx.ui.setStatus("side", text);
|
|
517
540
|
}
|
|
@@ -547,6 +570,26 @@ export function activeSideModel(
|
|
|
547
570
|
return markerData.sideModel;
|
|
548
571
|
}
|
|
549
572
|
|
|
573
|
+
/**
|
|
574
|
+
* Refresh the badge's lensed-context estimate from the live branch —
|
|
575
|
+
* undefined on non-side branches, so the suffix also clears on exit and
|
|
576
|
+
* navigation. O(branch text) char counting: cheap per turn, far too hot
|
|
577
|
+
* for per-frame footer renders (those read the cached side-state value).
|
|
578
|
+
*/
|
|
579
|
+
function refreshSideLens(ctx: ExtensionContext): void {
|
|
580
|
+
const raw = ctx.sessionManager.getBranch();
|
|
581
|
+
const markerIdx = findSideBoundary(raw);
|
|
582
|
+
let tokens: number | undefined;
|
|
583
|
+
if (markerIdx !== -1) {
|
|
584
|
+
tokens = estimateLensedContextTokens(
|
|
585
|
+
ctx.sessionManager.buildContextEntries(),
|
|
586
|
+
raw,
|
|
587
|
+
markerIdx,
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
setSideLensTokens(tokens);
|
|
591
|
+
}
|
|
592
|
+
|
|
550
593
|
async function summarizeDelta(
|
|
551
594
|
ctx: ExtensionCommandContext,
|
|
552
595
|
sideModel: ModelRef,
|
|
@@ -627,6 +670,7 @@ export default function side(pi: ExtensionAPI): void {
|
|
|
627
670
|
setSideSessionModel(undefined);
|
|
628
671
|
applySideStatus(ctx);
|
|
629
672
|
}
|
|
673
|
+
refreshSideLens(ctx);
|
|
630
674
|
});
|
|
631
675
|
|
|
632
676
|
// Restore the badge when a session resumes already on a side limb. The
|
|
@@ -650,8 +694,17 @@ export default function side(pi: ExtensionAPI): void {
|
|
|
650
694
|
setSideSessionModel(undefined);
|
|
651
695
|
applySideStatus(ctx);
|
|
652
696
|
}
|
|
697
|
+
refreshSideLens(ctx);
|
|
653
698
|
});
|
|
654
699
|
|
|
700
|
+
// The lensed-context estimate the badge carries: the quote is frozen at
|
|
701
|
+
// entry, so it only moves when the side limb grows, compaction rewrites
|
|
702
|
+
// the view, or navigation changes the limb. Non-side events are a cheap
|
|
703
|
+
// marker scan that ends in a no-op set.
|
|
704
|
+
pi.on("turn_start", (_event, ctx) => refreshSideLens(ctx));
|
|
705
|
+
pi.on("message_end", (_event, ctx) => refreshSideLens(ctx));
|
|
706
|
+
pi.on("session_compact", (_event, ctx) => refreshSideLens(ctx));
|
|
707
|
+
|
|
655
708
|
// The footer-rendered badge needs a re-render when side state changes;
|
|
656
709
|
// the status-line fallback needs re-applying when the merged footer
|
|
657
710
|
// appears or disappears (install order between the two modules is not
|
package/vision-core.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* vision-core — pure logic for the `vision` tool.
|
|
3
3
|
*
|
|
4
|
-
* No pi imports: structural types stand in for
|
|
5
|
-
*
|
|
4
|
+
* No pi runtime imports: structural types stand in for registry/model objects.
|
|
5
|
+
* The request context uses pi's types so incomplete replay messages cannot
|
|
6
|
+
* silently cross the completion boundary.
|
|
6
7
|
* index.ts wires these against the real pi APIs.
|
|
7
8
|
*/
|
|
8
9
|
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
10
|
import { readFile, stat } from "node:fs/promises";
|
|
10
11
|
import { homedir } from "node:os";
|
|
11
12
|
import { dirname, extname, join, resolve } from "node:path";
|
|
13
|
+
import type { Context, Message } from "@earendil-works/pi-ai/compat";
|
|
12
14
|
|
|
13
15
|
// --- structural stand-ins for pi types ---------------------------------------
|
|
14
16
|
|
|
@@ -16,6 +18,7 @@ import { dirname, extname, join, resolve } from "node:path";
|
|
|
16
18
|
export interface ModelLike {
|
|
17
19
|
id: string;
|
|
18
20
|
provider: string;
|
|
21
|
+
api: string;
|
|
19
22
|
input: string[];
|
|
20
23
|
}
|
|
21
24
|
|
|
@@ -558,9 +561,10 @@ export const VISION_SYSTEM_PROMPT = [
|
|
|
558
561
|
export function buildVisionContext(
|
|
559
562
|
prompt: string,
|
|
560
563
|
image: { data: string; mimeType: string },
|
|
564
|
+
model: ModelLike,
|
|
561
565
|
history: StoredTurn[] = [],
|
|
562
|
-
):
|
|
563
|
-
const past = history.flatMap((turn) => [
|
|
566
|
+
): Context {
|
|
567
|
+
const past = history.flatMap<Message>((turn) => [
|
|
564
568
|
{
|
|
565
569
|
role: "user",
|
|
566
570
|
content: [{ type: "text", text: turn.question }],
|
|
@@ -569,6 +573,21 @@ export function buildVisionContext(
|
|
|
569
573
|
{
|
|
570
574
|
role: "assistant",
|
|
571
575
|
content: [{ type: "text", text: turn.answer }],
|
|
576
|
+
api: model.api,
|
|
577
|
+
provider: model.provider,
|
|
578
|
+
model: model.id,
|
|
579
|
+
stopReason: "stop",
|
|
580
|
+
// These are text-only replays, not the original provider responses.
|
|
581
|
+
// Zero usage makes Pi estimate this request's actual content instead of
|
|
582
|
+
// treating a previous request's token count as an authoritative prefix.
|
|
583
|
+
usage: {
|
|
584
|
+
input: 0,
|
|
585
|
+
output: 0,
|
|
586
|
+
cacheRead: 0,
|
|
587
|
+
cacheWrite: 0,
|
|
588
|
+
totalTokens: 0,
|
|
589
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
590
|
+
},
|
|
572
591
|
timestamp: Date.now(),
|
|
573
592
|
},
|
|
574
593
|
]);
|
|
@@ -644,7 +663,7 @@ export interface VisionToolDeps {
|
|
|
644
663
|
/** pi-ai completeSimple (injected so tests stub it). */
|
|
645
664
|
complete(
|
|
646
665
|
model: ModelLike,
|
|
647
|
-
context:
|
|
666
|
+
context: Context,
|
|
648
667
|
options: {
|
|
649
668
|
apiKey?: string;
|
|
650
669
|
headers?: Record<string, string | null>;
|
|
@@ -760,6 +779,7 @@ export async function runVisionTool(
|
|
|
760
779
|
data: image.data as string,
|
|
761
780
|
mimeType: image.mimeType as string,
|
|
762
781
|
},
|
|
782
|
+
transport.model,
|
|
763
783
|
history,
|
|
764
784
|
),
|
|
765
785
|
{
|
package/vision.ts
CHANGED
|
@@ -133,6 +133,13 @@ function questionPreview(prompt: unknown, cap: number): string {
|
|
|
133
133
|
return head;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/** Expanded views show the raw truth: the whole prompt, uncapped — the cap
|
|
137
|
+
* is a compact-view aid, and ctrl+o exists to reveal what was actually
|
|
138
|
+
* asked (bash's expanded-command rule; multi-line renders multi-line). */
|
|
139
|
+
function fullQuestion(prompt: unknown): string {
|
|
140
|
+
return typeof prompt === "string" ? prompt : "";
|
|
141
|
+
}
|
|
142
|
+
|
|
136
143
|
/** Answer text of a recorded vision result (its content is one text block). */
|
|
137
144
|
function answerText(result: unknown): string {
|
|
138
145
|
const content = (result as { content?: Array<{ text?: string }> } | undefined)
|
|
@@ -160,8 +167,10 @@ const visionBurstSpec = {
|
|
|
160
167
|
// Read-view rule: info that is missing is skipped, never rendered as
|
|
161
168
|
// junk (no `: ""` when a prompt is absent) — and long answers get the
|
|
162
169
|
// same 12-line preview + "... N more lines" note as every other tool.
|
|
170
|
+
// Details render only when expanded, so questions show uncapped
|
|
171
|
+
// (same rule as bash's expanded details).
|
|
163
172
|
const follow = e.args.followUp ? " (follow-up)" : "";
|
|
164
|
-
const q =
|
|
173
|
+
const q = fullQuestion(e.args.prompt);
|
|
165
174
|
const label = `— ${shortenPath(e.args.path || "...")}${follow}${q ? `: "${q}"` : ""}`;
|
|
166
175
|
if (!e.result) return `\n${theme.fg("warning", `${label} (pending)`)}`;
|
|
167
176
|
const txt = answerText(e.result);
|
|
@@ -170,9 +179,14 @@ const visionBurstSpec = {
|
|
|
170
179
|
})
|
|
171
180
|
.join("");
|
|
172
181
|
},
|
|
173
|
-
soloHeader(args: any, theme: any) {
|
|
182
|
+
soloHeader(args: any, theme: any, ctx: any) {
|
|
174
183
|
const follow = args.followUp ? theme.fg("muted", " (follow-up)") : "";
|
|
175
|
-
|
|
184
|
+
// Expanded shows the full question, uncapped — the 90-char preview is a
|
|
185
|
+
// compact-view aid, and the whole point of ctrl+o is to reveal what was
|
|
186
|
+
// actually asked (same rule as bash's expanded command).
|
|
187
|
+
const q = ctx?.expanded
|
|
188
|
+
? fullQuestion(args.prompt)
|
|
189
|
+
: questionPreview(args.prompt, 90);
|
|
176
190
|
return `${theme.fg("toolTitle", theme.bold("vision"))} ${theme.fg("accent", shortenPath(args.path || "..."))}${follow}${q ? ` ${theme.fg("toolOutput", `"${q}"`)}` : ""}`;
|
|
177
191
|
},
|
|
178
192
|
soloExpanded(entry: any, _args: any, theme: any) {
|
|
@@ -367,18 +381,14 @@ export default function (pi: ExtensionAPI): void {
|
|
|
367
381
|
) as Promise<{ content: ContentBlockLike[]; isError?: boolean }>,
|
|
368
382
|
readRaw: readRawImage,
|
|
369
383
|
complete: (model, context, options) =>
|
|
370
|
-
completeSimple(
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
timeoutMs: 120_000,
|
|
379
|
-
maxRetries: 1,
|
|
380
|
-
} as never,
|
|
381
|
-
) as Promise<never>,
|
|
384
|
+
completeSimple(model as never, context, {
|
|
385
|
+
apiKey: options.apiKey,
|
|
386
|
+
headers: options.headers,
|
|
387
|
+
maxTokens: options.maxTokens,
|
|
388
|
+
signal: options.signal,
|
|
389
|
+
timeoutMs: 120_000,
|
|
390
|
+
maxRetries: 1,
|
|
391
|
+
} as never) as Promise<never>,
|
|
382
392
|
},
|
|
383
393
|
(text) =>
|
|
384
394
|
onUpdate?.({
|