@pi-kaush/pi-tool-call-markers 0.3.8 → 0.3.10
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/CHANGELOG.md +9 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/index.ts +70 -3
- package/src/thinking-block-merger.ts +16 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Give the codemode tool its own `¢¢` anchor and collapsed label. Codemode
|
|
6
|
+
rows used to collapse to a script preview; they now show the nested
|
|
7
|
+
calls the script ran (repeats deduped to `name ×N`) followed by the
|
|
8
|
+
script's `// @options:` params — `¢¢ read ×2, fffind · "max_output_tokens":
|
|
9
|
+
2500, "timeout_ms": 120000` — so the row says what the script did and
|
|
10
|
+
how it ran. While the script streams, the call list grows live; scripts
|
|
11
|
+
with neither calls nor a parsable options line keep the generic args
|
|
12
|
+
summary, and click-to-expand still restores the full script and result.
|
|
13
|
+
|
|
5
14
|
- Render an asked question as a user-input moment instead of a raw tool row.
|
|
6
15
|
`ask_user_question` used to collapse to `│ * {"questions":…`, which is a tool
|
|
7
16
|
call marker doing the work of a prompt. An asked question now takes the
|
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Collapsed tool rows use semantic theme colors with no gear, background fill, box
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
- **Two-column outer inset.** Tool markers and image output align with an inset conversation surface. Very narrow terminals reduce the decoration before useful content.
|
|
24
|
-
- **`│` tool-call rail.** Every collapsed call anchors on a bold glyph instead of its tool name — `●` read, `+` write, `±` edit, `○` local search, `≡` ls, `↗` web search and fetch, and `$` bash; unmapped tools fall back to `*` — with a single space between the anchor and the call text, no joining colon. Grouped rows flow directly across tool types.
|
|
24
|
+
- **`│` tool-call rail.** Every collapsed call anchors on a bold glyph instead of its tool name — `●` read, `+` write, `±` edit, `○` local search, `≡` ls, `↗` web search and fetch, `¢¢` codemode, and `$` bash; unmapped tools fall back to `*` — with a single space between the anchor and the call text, no joining colon. Grouped rows flow directly across tool types.
|
|
25
25
|
- **Semantic, low-contrast status.** The anchor and the call content share the collapsed mute, pending state is warning-colored, and failures remain error-colored. Ordinary tool states have no background.
|
|
26
26
|
- **Width-safe outcome tails.** Long summaries truncate before useful tails such as `→ done`, `→ 42 lines`, `→ +2/-1`, or a `bash` duration.
|
|
27
27
|
- **Stable running groups.** Adjacent calls group as they appear, including settled failures, which stay error-colored inside the group. Pending state and elapsed `bash` time settle into the final outcome without changing the row count.
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -42,6 +42,7 @@ const TOOL_CALL_GLYPHS: ReadonlyMap<string, string> = new Map([
|
|
|
42
42
|
["web_search", "↗"],
|
|
43
43
|
["fetch_content", "↗"],
|
|
44
44
|
["get_search_content", "↗"],
|
|
45
|
+
["codemode", "¢¢"],
|
|
45
46
|
]);
|
|
46
47
|
const PRESENTATION_PATCHED = Symbol.for("kg.pi.toolPresentation.v3");
|
|
47
48
|
const LEGACY_PRESENTATION_PATCHED = Symbol.for("kg.pi.toolPresentation.v2");
|
|
@@ -835,6 +836,68 @@ function renderedCallSummary(
|
|
|
835
836
|
: fgCollapsed(theme, "muted", "(no arguments)");
|
|
836
837
|
}
|
|
837
838
|
|
|
839
|
+
// Codemode carries its tunables in the script's first line —
|
|
840
|
+
// `// @options: {"max_output_tokens": 2500, "timeout_ms": 120000}` — so a
|
|
841
|
+
// collapsed row shows those params instead of a script preview. The
|
|
842
|
+
// prefix and first-line handling mirror pi-codemode's parseCodemodeSource;
|
|
843
|
+
// anything absent or unparsable returns undefined so the row keeps the
|
|
844
|
+
// ordinary summary paths.
|
|
845
|
+
const CODEMODE_OPTIONS_PREFIX = "// @options:";
|
|
846
|
+
|
|
847
|
+
function codemodeOptionsLabel(row: ToolExecutionRow): string | undefined {
|
|
848
|
+
if (row.toolName !== "codemode") return undefined;
|
|
849
|
+
const code = (row.args as { code?: unknown } | undefined)?.code;
|
|
850
|
+
if (typeof code !== "string") return undefined;
|
|
851
|
+
const newline = code.indexOf("\n");
|
|
852
|
+
const head = newline === -1 ? code : code.slice(0, newline);
|
|
853
|
+
const firstLine = head.replace(/\r$/, "").trimStart();
|
|
854
|
+
if (!firstLine.startsWith(CODEMODE_OPTIONS_PREFIX)) return undefined;
|
|
855
|
+
const directive = firstLine.slice(CODEMODE_OPTIONS_PREFIX.length).trim();
|
|
856
|
+
let parsed: unknown;
|
|
857
|
+
try {
|
|
858
|
+
parsed = JSON.parse(directive);
|
|
859
|
+
} catch {
|
|
860
|
+
return undefined;
|
|
861
|
+
}
|
|
862
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
863
|
+
return undefined;
|
|
864
|
+
const params = Object.entries(parsed as Record<string, unknown>).map(
|
|
865
|
+
([key, value]) => `${JSON.stringify(key)}: ${JSON.stringify(value)}`,
|
|
866
|
+
);
|
|
867
|
+
return params.length > 0 ? params.join(", ") : undefined;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// The nested calls a script makes stream through result.details.calls with
|
|
871
|
+
// a status per call — the clearest one-line account of what the script did
|
|
872
|
+
// (or is doing, while partial). Repeats collapse to `name ×N` in first-seen
|
|
873
|
+
// order; anything unshaped returns undefined.
|
|
874
|
+
function codemodeCallsSummary(row: ToolExecutionRow): string | undefined {
|
|
875
|
+
const calls = row.result?.details?.calls;
|
|
876
|
+
if (!Array.isArray(calls) || calls.length === 0) return undefined;
|
|
877
|
+
const counts = new Map<string, number>();
|
|
878
|
+
for (const call of calls) {
|
|
879
|
+
if (!isRecord(call)) continue;
|
|
880
|
+
const name = call.name;
|
|
881
|
+
if (typeof name !== "string" || name.length === 0) continue;
|
|
882
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
883
|
+
}
|
|
884
|
+
if (counts.size === 0) return undefined;
|
|
885
|
+
return [...counts]
|
|
886
|
+
.map(([name, count]) => (count > 1 ? `${name} ×${count}` : name))
|
|
887
|
+
.join(", ");
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
// Codemode's collapsed label pairs what the script did with how it ran:
|
|
891
|
+
// `read ×2, fffind · "max_output_tokens": 2500`. Without either part the row
|
|
892
|
+
// falls through to the ordinary summary paths.
|
|
893
|
+
function codemodeCallLabel(row: ToolExecutionRow): string | undefined {
|
|
894
|
+
if (row.toolName !== "codemode") return undefined;
|
|
895
|
+
const parts = [codemodeCallsSummary(row), codemodeOptionsLabel(row)].filter(
|
|
896
|
+
(part) => part !== undefined,
|
|
897
|
+
);
|
|
898
|
+
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
899
|
+
}
|
|
900
|
+
|
|
838
901
|
// The collapsed-row seam lives here and nowhere else: which anchor a row
|
|
839
902
|
// takes, how that anchor is styled, and the call text that follows it.
|
|
840
903
|
// Anchors always render bold and take no joining colon — `│ ● src/a.ts`,
|
|
@@ -864,9 +927,13 @@ function collapsedSeam(
|
|
|
864
927
|
// is gone once settled — while ordinary rows scrape the rendered call
|
|
865
928
|
// line, which already drops its heading token.
|
|
866
929
|
const selfRendered = row.getRenderShell?.() === "self";
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
930
|
+
// Codemode rows pair the script's nested calls with its `@options` params;
|
|
931
|
+
// with neither, they fall through to the ordinary summary paths.
|
|
932
|
+
const source =
|
|
933
|
+
codemodeCallLabel(row) ??
|
|
934
|
+
(selfRendered
|
|
935
|
+
? selfRenderedCallLabel(row)
|
|
936
|
+
: renderedCallSummary(row, budget, theme));
|
|
870
937
|
const plain = sanitizeInline(stripAnsi(source)).trim();
|
|
871
938
|
// The anchor stands in for the label's leading token. Only self-rendered
|
|
872
939
|
// labels still carry it; a subagent fallback keeps `subagent` as its own
|
|
@@ -148,10 +148,23 @@ type MarkdownLikeChild = {
|
|
|
148
148
|
// the italic default style is what identifies the trace: Pi passes it for
|
|
149
149
|
// thinking content and for nothing else. Callers gate on the row actually
|
|
150
150
|
// carrying thinking, so no other italic block is touched.
|
|
151
|
-
|
|
151
|
+
// Pi 1.0 wraps each thinking child in a MouseRegion (click toggles
|
|
152
|
+
// visibility); older versions hang the Markdown/Text directly. Walk both
|
|
153
|
+
// shapes so the restyle reaches the node either way.
|
|
154
|
+
function contentNodes(row: AssistantMessageRow): unknown[] {
|
|
152
155
|
const children = row.contentContainer?.children;
|
|
153
|
-
if (!Array.isArray(children)) return;
|
|
156
|
+
if (!Array.isArray(children)) return [];
|
|
157
|
+
const nodes: unknown[] = [];
|
|
154
158
|
for (const child of children) {
|
|
159
|
+
nodes.push(child);
|
|
160
|
+
const inner = (child as { child?: unknown } | undefined)?.child;
|
|
161
|
+
if (inner !== undefined && inner !== null) nodes.push(inner);
|
|
162
|
+
}
|
|
163
|
+
return nodes;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function unitalicizeSettledThinking(row: AssistantMessageRow): void {
|
|
167
|
+
for (const child of contentNodes(row)) {
|
|
155
168
|
const markdown = child as MarkdownLikeChild | undefined;
|
|
156
169
|
const style = markdown?.defaultTextStyle;
|
|
157
170
|
if (style?.italic !== true) continue;
|
|
@@ -174,11 +187,9 @@ function restyleHiddenThinkingLabel(row: AssistantMessageRow): void {
|
|
|
174
187
|
return;
|
|
175
188
|
}
|
|
176
189
|
const label = row.hiddenThinkingLabel;
|
|
177
|
-
const children = row.contentContainer?.children;
|
|
178
|
-
if (!Array.isArray(children)) return;
|
|
179
190
|
if (!stylesThinkingLabels()) return;
|
|
180
191
|
const styled = visibleThoughtLabel(label);
|
|
181
|
-
for (const child of
|
|
192
|
+
for (const child of contentNodes(row)) {
|
|
182
193
|
const textChild = child as TextLikeChild | undefined;
|
|
183
194
|
if (
|
|
184
195
|
typeof textChild?.text !== "string" ||
|