@spexcode/transcript-ui 0.7.0-next.7 → 0.7.0-next.8
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/dist/ToolLine.js +4 -3
- package/dist/vocabulary.d.ts +5 -0
- package/dist/vocabulary.js +27 -1
- package/package.json +2 -2
- package/styles.css +1 -0
package/dist/ToolLine.js
CHANGED
|
@@ -3,7 +3,7 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { useTranscriptUi } from './context.js';
|
|
4
4
|
import { Caret, Spinner } from './icons.js';
|
|
5
5
|
import { isRunning } from './segments.js';
|
|
6
|
-
import { runKinds, splitTarget, toolTarget, toolVerb } from './vocabulary.js';
|
|
6
|
+
import { prettyInput, runKinds, splitTarget, toolName, toolTarget, toolVerb } from './vocabulary.js';
|
|
7
7
|
// A LIVE FRAME WITHHOLDS OUTPUT BODIES: a recorded result is `null` on the wire, its size told, and the body
|
|
8
8
|
// is fetched once when a person opens the call, through the host's loader.
|
|
9
9
|
function WithheldOutput({ tool }) {
|
|
@@ -40,10 +40,11 @@ export function ToolLine({ tool, open, onToggle, live = false }) {
|
|
|
40
40
|
const canOpen = !!tool.input || tool.output !== undefined || withheld;
|
|
41
41
|
const running = isRunning(tool, live);
|
|
42
42
|
const outcome = tool.outcome;
|
|
43
|
-
const
|
|
43
|
+
const { server } = toolName(tool.name);
|
|
44
|
+
const row = (_jsxs(_Fragment, { children: [_jsx("span", { className: "tx-tool-verb", children: toolVerb(tool.name, vocabulary) }), server && _jsx("span", { className: "tx-tool-server", children: server }), lead && _jsx("span", { className: "tx-tool-target", children: lead }), trail && _jsx("span", { className: "tx-tool-trail", children: trail }), lines > 0 && _jsx("span", { className: "tx-tool-size", children: labels.lines(lines) }), running && _jsxs("span", { className: "tx-tool-running", children: [_jsx(Spinner, {}), labels.running] }), outcome && _jsx("span", { className: `tx-tool-outcome is-${outcome}`, children: outcome === 'failed' ? labels.failed : labels.rejected }), canOpen && _jsx(Caret, { open: open, className: "tx-tool-caret" })] }));
|
|
44
45
|
return (_jsxs("div", { className: `tx-tool${running ? ' is-running' : ''}${outcome ? ` is-${outcome}` : ''}`, children: [canOpen
|
|
45
46
|
? _jsx("button", { type: "button", onClick: onToggle, "aria-expanded": open, className: "tx-tool-row is-openable", children: row })
|
|
46
|
-
: _jsx("div", { className: "tx-tool-row", children: row }), open && canOpen && _jsxs(_Fragment, { children: [tool.input && _jsx("pre", { className: "tx-tool-in", children: tool.input }), withheld
|
|
47
|
+
: _jsx("div", { className: "tx-tool-row", children: row }), open && canOpen && _jsxs(_Fragment, { children: [tool.input && _jsx("pre", { className: "tx-tool-in", children: prettyInput(tool.input) }), withheld
|
|
47
48
|
? _jsx(WithheldOutput, { tool: tool })
|
|
48
49
|
: tool.output !== undefined && _jsx("pre", { className: "tx-tool-out", children: tool.output })] })] }));
|
|
49
50
|
}
|
package/dist/vocabulary.d.ts
CHANGED
|
@@ -11,11 +11,16 @@ export declare function extendVocabulary(base: Vocabulary, extra: Partial<{
|
|
|
11
11
|
quiet: Iterable<string>;
|
|
12
12
|
targetKeys: readonly string[];
|
|
13
13
|
}>): Vocabulary;
|
|
14
|
+
export declare function toolName(name: string | undefined): {
|
|
15
|
+
tool: string;
|
|
16
|
+
server: string | null;
|
|
17
|
+
};
|
|
14
18
|
export declare const toolVerb: (name: string | undefined, vocabulary?: Readonly<{
|
|
15
19
|
verbs: Readonly<Record<string, string>>;
|
|
16
20
|
quiet: ReadonlySet<string>;
|
|
17
21
|
targetKeys: readonly string[];
|
|
18
22
|
}>) => string;
|
|
23
|
+
export declare function prettyInput(input: string | undefined): string;
|
|
19
24
|
export declare const isQuietTool: (name: string, vocabulary?: Readonly<{
|
|
20
25
|
verbs: Readonly<Record<string, string>>;
|
|
21
26
|
quiet: ReadonlySet<string>;
|
package/dist/vocabulary.js
CHANGED
|
@@ -19,7 +19,33 @@ export function extendVocabulary(base, extra) {
|
|
|
19
19
|
targetKeys: extra.targetKeys ? [...extra.targetKeys, ...base.targetKeys.filter((key) => !extra.targetKeys.includes(key))] : base.targetKeys,
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
// AN MCP TOOL IS NAMED BY ITS SERVER AND ITS TOOL. Every harness that speaks MCP writes the call as
|
|
23
|
+
// `mcp__<server>__<tool>`; the reader wants both halves, apart — measured across four transcript renderers,
|
|
24
|
+
// three knew the server in their data and lost it on the screen. The vocabulary may name the full id or the
|
|
25
|
+
// bare tool; an unnamed MCP tool reads as its tool half, never the whole mangled id.
|
|
26
|
+
export function toolName(name) {
|
|
27
|
+
const m = name ? /^mcp__(.+?)__(.+)$/.exec(name) : null;
|
|
28
|
+
return m ? { tool: m[2], server: m[1] } : { tool: name || 'tool', server: null };
|
|
29
|
+
}
|
|
30
|
+
export const toolVerb = (name, vocabulary = defaultVocabulary) => {
|
|
31
|
+
if (name && vocabulary.verbs[name])
|
|
32
|
+
return vocabulary.verbs[name];
|
|
33
|
+
const { tool } = toolName(name);
|
|
34
|
+
return vocabulary.verbs[tool] || tool;
|
|
35
|
+
};
|
|
36
|
+
// THE ARGUMENTS, WHEN OPENED, READ AS THE ARGUMENTS: a JSON object pretty-printed one field per line, a bare
|
|
37
|
+
// string (a script, a command) as itself. The wire form is one line; nobody reads one line of JSON.
|
|
38
|
+
export function prettyInput(input) {
|
|
39
|
+
if (typeof input !== 'string' || !input)
|
|
40
|
+
return '';
|
|
41
|
+
try {
|
|
42
|
+
const parsed = JSON.parse(input);
|
|
43
|
+
return parsed && typeof parsed === 'object' ? JSON.stringify(parsed, null, 2) : input;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return input;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
23
49
|
export const isQuietTool = (name, vocabulary = defaultVocabulary) => vocabulary.quiet.has(name);
|
|
24
50
|
// The target, from the call's own arguments. `input` is the raw JSON of the arguments (or a bare string), so
|
|
25
51
|
// this reads the field the tool actually names and shows NOTHING when it cannot — a wrong target is worse
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spexcode/transcript-ui",
|
|
3
|
-
"version": "0.7.0-next.
|
|
3
|
+
"version": "0.7.0-next.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "React components that draw a normalized agent transcript — the person quoted, the agent as the page, a tool call as a sentence, the work folded behind its answer — with the fold, the prose renderer, the tool vocabulary, the labels and the design tokens all tunable.",
|
|
6
6
|
"files": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"test": "npm run build && tsx --test src/*.test.tsx"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@spexcode/transcript": "0.7.0-next.
|
|
27
|
+
"@spexcode/transcript": "0.7.0-next.8"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"react": "^18.2.0 || ^19.0.0"
|
package/styles.css
CHANGED
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
.tx-tool-target { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--tx-ink); }
|
|
86
86
|
.tx-tool-trail { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--tx-muted); }
|
|
87
87
|
.tx-tool-size { flex: none; color: var(--tx-muted); font-variant-numeric: tabular-nums; }
|
|
88
|
+
.tx-tool-server { flex: none; color: var(--tx-muted); font-size: var(--tx-type-caption); padding: 0 5px; border: 1px solid var(--tx-edge); border-radius: 999px; }
|
|
88
89
|
.tx-tool-row.is-run .tx-tool-verb { color: var(--tx-muted); font-weight: inherit; }
|
|
89
90
|
.tx-tool-kids { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; margin-left: 8px; padding-left: 10px; border-left: 1px solid var(--tx-edge); }
|
|
90
91
|
.tx-tool-in, .tx-tool-out {
|