apex-code 0.1.1 → 0.2.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/CHANGELOG.md +30 -0
- package/README.md +3 -4
- package/dist/core/permissions/gate.d.ts.map +1 -1
- package/dist/core/permissions/gate.js +5 -2
- package/dist/core/permissions/gate.js.map +1 -1
- package/dist/core/permissions/modes.d.ts +1 -1
- package/dist/core/permissions/modes.d.ts.map +1 -1
- package/dist/core/permissions/modes.js +12 -2
- package/dist/core/permissions/modes.js.map +1 -1
- package/dist/core/permissions/protected-paths.d.ts +23 -0
- package/dist/core/permissions/protected-paths.d.ts.map +1 -0
- package/dist/core/permissions/protected-paths.js +41 -0
- package/dist/core/permissions/protected-paths.js.map +1 -0
- package/dist/core/removed-settings.d.ts +26 -0
- package/dist/core/removed-settings.d.ts.map +1 -0
- package/dist/core/removed-settings.js +40 -0
- package/dist/core/removed-settings.js.map +1 -0
- package/dist/core/settings-diagnostics.d.ts.map +1 -1
- package/dist/core/settings-diagnostics.js +16 -1
- package/dist/core/settings-diagnostics.js.map +1 -1
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +4 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts +6 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +71 -24
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/contract.d.ts +15 -0
- package/dist/core/tools/contract.d.ts.map +1 -1
- package/dist/core/tools/contract.js.map +1 -1
- package/dist/core/tools/path-permission.d.ts.map +1 -1
- package/dist/core/tools/path-permission.js +8 -1
- package/dist/core/tools/path-permission.js.map +1 -1
- package/dist/core/tools/shell-operation.d.ts +34 -0
- package/dist/core/tools/shell-operation.d.ts.map +1 -0
- package/dist/core/tools/shell-operation.js +69 -0
- package/dist/core/tools/shell-operation.js.map +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +10 -0
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/message-spine.d.ts +14 -0
- package/dist/modes/interactive/components/message-spine.d.ts.map +1 -0
- package/dist/modes/interactive/components/message-spine.js +19 -0
- package/dist/modes/interactive/components/message-spine.js.map +1 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +1 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +4 -1
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/utils/paths.d.ts +10 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +34 -2
- package/dist/utils/paths.js.map +1 -1
- package/npm-shrinkwrap.json +5 -5
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-operation.d.ts","sourceRoot":"","sources":["../../../src/core/tools/shell-operation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,cAAc,GACvB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAClF;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpC,MAAM,MAAM,mBAAmB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,cAAc,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAqB1G,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,mBAAmB,CAuDxE","sourcesContent":["/**\n * The one shell operation a set of tool arguments requests.\n *\n * The shell schema advertises a flat bag of fields because providers ignore\n * `anyOf` (see `toolUnion` in `contract.ts`). That advertisement cannot enforce\n * anything: a model may send any combination of fields, and it does. Consumers\n * that each re-read the raw bag disagreed about which operation a mixed call\n * requested — permissions keyed on `command`, execution keyed on `handle` — so\n * an allowed command could carry a denied background kill past the gate.\n *\n * Parse once, here, and let permissions, execution, display, and evidence all\n * read the same answer.\n */\nexport type ShellOperation =\n\t| { kind: \"run\"; command: string; timeout: number | undefined; background: boolean }\n\t| { kind: \"retrieve\"; handle: string }\n\t| { kind: \"kill\"; handle: string };\n\nexport type ShellOperationParse = { ok: true; operation: ShellOperation } | { ok: false; reason: string };\n\n/**\n * Placeholder policy, stated once and applied everywhere.\n *\n * Models routinely populate every advertised field, sending `handle: \"\"` or\n * `timeout: null` beside a real command. A value that is absent, null, or an\n * empty string carries no request and is dropped. A value that would change\n * behaviour if honoured is never dropped: it either selects the operation or,\n * when it belongs to a different operation, makes the call ambiguous.\n */\nfunction meaningful(value: unknown): boolean {\n\treturn value !== undefined && value !== null && value !== \"\";\n}\n\nfunction presentString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nconst RUN_HINT = \"Supply `command` to run a command, or `handle` to retrieve or kill a background command.\";\n\nexport function parseShellOperation(params: unknown): ShellOperationParse {\n\tif (typeof params !== \"object\" || params === null) {\n\t\treturn { ok: false, reason: `Shell arguments must be an object. ${RUN_HINT}` };\n\t}\n\tconst raw = params as Record<string, unknown>;\n\tconst command = presentString(raw.command);\n\tconst handle = presentString(raw.handle);\n\tconst wantsKill = raw.kill === true;\n\n\tif (command !== undefined && handle !== undefined) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\treason: `Ambiguous shell call: \\`command\\` and \\`handle\\` request different operations. ${RUN_HINT} Send one, not both.`,\n\t\t};\n\t}\n\n\tif (handle !== undefined) {\n\t\tconst stray: string[] = [];\n\t\tif (typeof raw.timeout === \"number\") stray.push(\"timeout\");\n\t\tif (raw.background === true) stray.push(\"background\");\n\t\tif (stray.length > 0) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\treason: `A background handle call takes only \\`handle\\` and optional \\`kill\\`. Remove: ${stray.join(\", \")}.`,\n\t\t\t};\n\t\t}\n\t\treturn { ok: true, operation: wantsKill ? { kind: \"kill\", handle } : { kind: \"retrieve\", handle } };\n\t}\n\n\tif (command !== undefined) {\n\t\tif (wantsKill) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\treason:\n\t\t\t\t\t\"`kill: true` terminates a background command and requires its `handle`. It cannot be combined with `command`.\",\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\toperation: {\n\t\t\t\tkind: \"run\",\n\t\t\t\tcommand,\n\t\t\t\ttimeout: typeof raw.timeout === \"number\" ? raw.timeout : undefined,\n\t\t\t\tbackground: raw.background === true,\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\tok: false,\n\t\treason:\n\t\t\t\"handle\" in raw && !meaningful(raw.handle)\n\t\t\t\t? `\\`handle\\` is empty, which is not a background handle. ${RUN_HINT}`\n\t\t\t\t: `Shell call names no operation. ${RUN_HINT}`,\n\t};\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placeholder policy, stated once and applied everywhere.
|
|
3
|
+
*
|
|
4
|
+
* Models routinely populate every advertised field, sending `handle: ""` or
|
|
5
|
+
* `timeout: null` beside a real command. A value that is absent, null, or an
|
|
6
|
+
* empty string carries no request and is dropped. A value that would change
|
|
7
|
+
* behaviour if honoured is never dropped: it either selects the operation or,
|
|
8
|
+
* when it belongs to a different operation, makes the call ambiguous.
|
|
9
|
+
*/
|
|
10
|
+
function meaningful(value) {
|
|
11
|
+
return value !== undefined && value !== null && value !== "";
|
|
12
|
+
}
|
|
13
|
+
function presentString(value) {
|
|
14
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
15
|
+
}
|
|
16
|
+
const RUN_HINT = "Supply `command` to run a command, or `handle` to retrieve or kill a background command.";
|
|
17
|
+
export function parseShellOperation(params) {
|
|
18
|
+
if (typeof params !== "object" || params === null) {
|
|
19
|
+
return { ok: false, reason: `Shell arguments must be an object. ${RUN_HINT}` };
|
|
20
|
+
}
|
|
21
|
+
const raw = params;
|
|
22
|
+
const command = presentString(raw.command);
|
|
23
|
+
const handle = presentString(raw.handle);
|
|
24
|
+
const wantsKill = raw.kill === true;
|
|
25
|
+
if (command !== undefined && handle !== undefined) {
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
reason: `Ambiguous shell call: \`command\` and \`handle\` request different operations. ${RUN_HINT} Send one, not both.`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (handle !== undefined) {
|
|
32
|
+
const stray = [];
|
|
33
|
+
if (typeof raw.timeout === "number")
|
|
34
|
+
stray.push("timeout");
|
|
35
|
+
if (raw.background === true)
|
|
36
|
+
stray.push("background");
|
|
37
|
+
if (stray.length > 0) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
reason: `A background handle call takes only \`handle\` and optional \`kill\`. Remove: ${stray.join(", ")}.`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { ok: true, operation: wantsKill ? { kind: "kill", handle } : { kind: "retrieve", handle } };
|
|
44
|
+
}
|
|
45
|
+
if (command !== undefined) {
|
|
46
|
+
if (wantsKill) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
reason: "`kill: true` terminates a background command and requires its `handle`. It cannot be combined with `command`.",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
operation: {
|
|
55
|
+
kind: "run",
|
|
56
|
+
command,
|
|
57
|
+
timeout: typeof raw.timeout === "number" ? raw.timeout : undefined,
|
|
58
|
+
background: raw.background === true,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
reason: "handle" in raw && !meaningful(raw.handle)
|
|
65
|
+
? `\`handle\` is empty, which is not a background handle. ${RUN_HINT}`
|
|
66
|
+
: `Shell call names no operation. ${RUN_HINT}`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=shell-operation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-operation.js","sourceRoot":"","sources":["../../../src/core/tools/shell-operation.ts"],"names":[],"mappings":"AAoBA;;;;;;;;GAQG;AACH,SAAS,UAAU,CAAC,KAAc,EAAW;IAC5C,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;AAAA,CAC7D;AAED,SAAS,aAAa,CAAC,KAAc,EAAsB;IAC1D,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CACzE;AAED,MAAM,QAAQ,GAAG,0FAA0F,CAAC;AAE5G,MAAM,UAAU,mBAAmB,CAAC,MAAe,EAAuB;IACzE,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACnD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sCAAsC,QAAQ,EAAE,EAAE,CAAC;IAChF,CAAC;IACD,MAAM,GAAG,GAAG,MAAiC,CAAC;IAC9C,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC;IAEpC,IAAI,OAAO,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACnD,OAAO;YACN,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,kFAAkF,QAAQ,sBAAsB;SACxH,CAAC;IACH,CAAC;IAED,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3D,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACtD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO;gBACN,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,iFAAiF,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;aAC5G,CAAC;QACH,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC;IACrG,CAAC;IAED,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC3B,IAAI,SAAS,EAAE,CAAC;YACf,OAAO;gBACN,EAAE,EAAE,KAAK;gBACT,MAAM,EACL,+GAA+G;aAChH,CAAC;QACH,CAAC;QACD,OAAO;YACN,EAAE,EAAE,IAAI;YACR,SAAS,EAAE;gBACV,IAAI,EAAE,KAAK;gBACX,OAAO;gBACP,OAAO,EAAE,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;gBAClE,UAAU,EAAE,GAAG,CAAC,UAAU,KAAK,IAAI;aACnC;SACD,CAAC;IACH,CAAC;IAED,OAAO;QACN,EAAE,EAAE,KAAK;QACT,MAAM,EACL,QAAQ,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC;YACzC,CAAC,CAAC,0DAA0D,QAAQ,EAAE;YACtE,CAAC,CAAC,kCAAkC,QAAQ,EAAE;KAChD,CAAC;AAAA,CACF","sourcesContent":["/**\n * The one shell operation a set of tool arguments requests.\n *\n * The shell schema advertises a flat bag of fields because providers ignore\n * `anyOf` (see `toolUnion` in `contract.ts`). That advertisement cannot enforce\n * anything: a model may send any combination of fields, and it does. Consumers\n * that each re-read the raw bag disagreed about which operation a mixed call\n * requested — permissions keyed on `command`, execution keyed on `handle` — so\n * an allowed command could carry a denied background kill past the gate.\n *\n * Parse once, here, and let permissions, execution, display, and evidence all\n * read the same answer.\n */\nexport type ShellOperation =\n\t| { kind: \"run\"; command: string; timeout: number | undefined; background: boolean }\n\t| { kind: \"retrieve\"; handle: string }\n\t| { kind: \"kill\"; handle: string };\n\nexport type ShellOperationParse = { ok: true; operation: ShellOperation } | { ok: false; reason: string };\n\n/**\n * Placeholder policy, stated once and applied everywhere.\n *\n * Models routinely populate every advertised field, sending `handle: \"\"` or\n * `timeout: null` beside a real command. A value that is absent, null, or an\n * empty string carries no request and is dropped. A value that would change\n * behaviour if honoured is never dropped: it either selects the operation or,\n * when it belongs to a different operation, makes the call ambiguous.\n */\nfunction meaningful(value: unknown): boolean {\n\treturn value !== undefined && value !== null && value !== \"\";\n}\n\nfunction presentString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" && value.length > 0 ? value : undefined;\n}\n\nconst RUN_HINT = \"Supply `command` to run a command, or `handle` to retrieve or kill a background command.\";\n\nexport function parseShellOperation(params: unknown): ShellOperationParse {\n\tif (typeof params !== \"object\" || params === null) {\n\t\treturn { ok: false, reason: `Shell arguments must be an object. ${RUN_HINT}` };\n\t}\n\tconst raw = params as Record<string, unknown>;\n\tconst command = presentString(raw.command);\n\tconst handle = presentString(raw.handle);\n\tconst wantsKill = raw.kill === true;\n\n\tif (command !== undefined && handle !== undefined) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\treason: `Ambiguous shell call: \\`command\\` and \\`handle\\` request different operations. ${RUN_HINT} Send one, not both.`,\n\t\t};\n\t}\n\n\tif (handle !== undefined) {\n\t\tconst stray: string[] = [];\n\t\tif (typeof raw.timeout === \"number\") stray.push(\"timeout\");\n\t\tif (raw.background === true) stray.push(\"background\");\n\t\tif (stray.length > 0) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\treason: `A background handle call takes only \\`handle\\` and optional \\`kill\\`. Remove: ${stray.join(\", \")}.`,\n\t\t\t};\n\t\t}\n\t\treturn { ok: true, operation: wantsKill ? { kind: \"kill\", handle } : { kind: \"retrieve\", handle } };\n\t}\n\n\tif (command !== undefined) {\n\t\tif (wantsKill) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\treason:\n\t\t\t\t\t\"`kill: true` terminates a background command and requires its `handle`. It cannot be combined with `command`.\",\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\tok: true,\n\t\t\toperation: {\n\t\t\t\tkind: \"run\",\n\t\t\t\tcommand,\n\t\t\t\ttimeout: typeof raw.timeout === \"number\" ? raw.timeout : undefined,\n\t\t\t\tbackground: raw.background === true,\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\tok: false,\n\t\treason:\n\t\t\t\"handle\" in raw && !meaningful(raw.handle)\n\t\t\t\t? `\\`handle\\` is empty, which is not a background handle. ${RUN_HINT}`\n\t\t\t\t: `Shell call names no operation. ${RUN_HINT}`,\n\t};\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAY,KAAK,aAAa,EAAgB,MAAM,wBAAwB,CAAC;AAC/F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAiC7E;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAqC7D;AAcD;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;IACvD,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,oBAAoB,CAAiC;IAC7D,OAAO,CAAC,WAAW,CAAC,CAAmB;IACvC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAiB;IAEjC,YACC,OAAO,CAAC,EAAE,gBAAgB,EAC1B,iBAAiB,UAAQ,EACzB,aAAa,GAAE,aAAkC,EACjD,mBAAmB,SAAgB,EACnC,SAAS,SAAI,EACb,oBAAoB,GAAE,SAAS,mBAAmB,EAAO,EAiBzD;IAEQ,UAAU,IAAI,IAAI,CAM1B;IAED,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAMxC;IAED,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAM1C;IAED,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAMlC;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CASvC;IAED,aAAa,CAAC,OAAO,EAAE,gBAAgB,EAAE,WAAW,UAAmB,GAAG,IAAI,CA0C7E;IAED,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,kBAAkB;IAyD1B,OAAO,CAAC,iBAAiB;IAqBzB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,eAAe;CA0BvB","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai\";\nimport { Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { MarkdownTransformer } from \"../../../core/extensions/types.ts\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.ts\";\nimport { createMarkdownTransform } from \"./markdown-transform.ts\";\n\nconst OSC133_ZONE_START = \"\\x1b]133;A\\x07\";\nconst OSC133_ZONE_END = \"\\x1b]133;B\\x07\";\nconst OSC133_ZONE_FINAL = \"\\x1b]133;C\\x07\";\n\nconst FENCE_OPENER = /^(```|~~~)/;\nconst FENCE_CLOSER = /^(```|~~~)\\s*$/;\nconst LIST_MARKER = /^([-*+]\\s|\\d+[.)]\\s)/;\nconst QUOTE_MARKER = /^>\\s?/;\nconst INDENTED_CODE = /^(?:\\t| {4})/;\n\nfunction lastNonEmpty(lines: string[]): string | undefined {\n\tfor (let i = lines.length - 1; i >= 0; i--) {\n\t\tif (lines[i].trim() !== \"\") return lines[i];\n\t}\n\treturn undefined;\n}\n\nfunction shouldSplitBefore(next: string, current: string[]): boolean {\n\t// Indented lines can continue indented code blocks; never split there.\n\tif (INDENTED_CODE.test(next)) return false;\n\tconst previous = lastNonEmpty(current);\n\tif (previous && INDENTED_CODE.test(previous)) return false;\n\t// A loose list (\"item\\n\\nitem\") and a blockquote with a blank line render\n\t// as one construct; splitting them would change the output.\n\tif (LIST_MARKER.test(next) && previous && LIST_MARKER.test(previous)) return false;\n\tif (QUOTE_MARKER.test(next) && previous && QUOTE_MARKER.test(previous)) return false;\n\treturn true;\n}\n\n/**\n * Split streamed markdown into render units at boundaries that render\n * independently: closed column-0 code fences, and blank-line block\n * boundaries that cannot continue a preceding construct.\n *\n * The result is prefix-stable: appending text can only add units at the end,\n * never change earlier ones, which is what makes the units cacheable while\n * a message streams.\n */\nexport function splitStreamingMarkdown(text: string): string[] {\n\tif (!text) return [];\n\tconst lines = text.split(\"\\n\");\n\tconst units: string[] = [];\n\tlet current: string[] = [];\n\tlet inFence = false;\n\tconst flush = (): void => {\n\t\tif (current.length > 0) {\n\t\t\tunits.push(current.join(\"\\n\"));\n\t\t\tcurrent = [];\n\t\t}\n\t};\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\tif (!inFence && FENCE_OPENER.test(line)) {\n\t\t\tinFence = true;\n\t\t\tcurrent.push(line);\n\t\t\tcontinue;\n\t\t}\n\t\tif (inFence) {\n\t\t\tcurrent.push(line);\n\t\t\tif (FENCE_CLOSER.test(line)) {\n\t\t\t\tflush();\n\t\t\t\tinFence = false;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tcurrent.push(line);\n\t\tif (line.trim() === \"\") {\n\t\t\tconst next = lines[i + 1];\n\t\t\tif (next !== undefined && next.trim() !== \"\" && shouldSplitBefore(next, current)) {\n\t\t\t\tflush();\n\t\t\t}\n\t\t}\n\t}\n\tflush();\n\treturn units;\n}\n\ntype SectionSpec =\n\t| { kind: \"spacer\" }\n\t| { kind: \"markdown-text\"; text: string }\n\t| { kind: \"markdown-thinking\"; text: string }\n\t| { kind: \"label\"; text: string };\n\ntype Section = { spec: SectionSpec; component: Container[\"children\"][number] };\n\nfunction specText(spec: SectionSpec): string | undefined {\n\treturn spec.kind === \"spacer\" ? undefined : spec.text;\n}\n\n/**\n * Component that renders a complete assistant message\n */\nexport class AssistantMessageComponent extends Container {\n\tprivate contentContainer: Container;\n\tprivate hideThinkingBlock: boolean;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate hiddenThinkingLabel: string;\n\tprivate outputPad: number;\n\tprivate markdownTransformers: readonly MarkdownTransformer[];\n\tprivate lastMessage?: AssistantMessage;\n\tprivate hasToolCalls = false;\n\tprivate isStreaming = false;\n\tprivate forceFullRebuild = true;\n\tprivate sections: Section[] = [];\n\n\tconstructor(\n\t\tmessage?: AssistantMessage,\n\t\thideThinkingBlock = false,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t\thiddenThinkingLabel = \"Thinking...\",\n\t\toutputPad = 1,\n\t\tmarkdownTransformers: readonly MarkdownTransformer[] = [],\n\t) {\n\t\tsuper();\n\n\t\tthis.hideThinkingBlock = hideThinkingBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.hiddenThinkingLabel = hiddenThinkingLabel;\n\t\tthis.outputPad = outputPad;\n\t\tthis.markdownTransformers = markdownTransformers;\n\n\t\t// Container for text/thinking content\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\tif (message) {\n\t\t\tthis.updateContent(message);\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.hideThinkingBlock = hide;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHiddenThinkingLabel(label: string): void {\n\t\tthis.hiddenThinkingLabel = label;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetOutputPad(padding: number): void {\n\t\tthis.outputPad = padding;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst lines = super.render(width);\n\t\tif (this.hasToolCalls || lines.length === 0) {\n\t\t\treturn lines;\n\t\t}\n\n\t\tlines[0] = OSC133_ZONE_START + lines[0];\n\t\tlines[lines.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + lines[lines.length - 1];\n\t\treturn lines;\n\t}\n\n\tupdateContent(message: AssistantMessage, isStreaming = this.isStreaming): void {\n\t\tthis.lastMessage = message;\n\t\t// The transform closures capture isStreaming, and non-streaming updates\n\t\t// render whole blocks rather than split units, so an isStreaming flip\n\t\t// rebuilds every section.\n\t\tconst rebuildAll = this.forceFullRebuild || isStreaming !== this.isStreaming;\n\t\tthis.isStreaming = isStreaming;\n\n\t\tconst specs = this.buildSectionSpecs(message);\n\t\tconst sections: Section[] = [];\n\t\tfor (let i = 0; i < specs.length; i++) {\n\t\t\tconst spec = specs[i];\n\t\t\tconst previous = rebuildAll ? undefined : this.sections[i];\n\t\t\tconst sameKind = previous !== undefined && previous.spec.kind === spec.kind;\n\t\t\tif (sameKind && (spec.kind === \"spacer\" || specText(previous.spec) === specText(spec))) {\n\t\t\t\tsections.push({ spec, component: previous.component });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (sameKind && spec.kind !== \"spacer\") {\n\t\t\t\t// Same slot, new content (the growing tail of a streamed\n\t\t\t\t// message): update in place so only this unit re-parses.\n\t\t\t\tconst component = previous.component as Text;\n\t\t\t\tcomponent.setText(specText(spec) ?? \"\");\n\t\t\t\tsections.push({ spec, component: previous.component });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsections.push({ spec, component: this.createComponent(spec) });\n\t\t}\n\n\t\tthis.forceFullRebuild = false;\n\t\tthis.sections = sections;\n\n\t\tthis.contentContainer.clear();\n\t\tfor (const section of sections) {\n\t\t\tthis.contentContainer.addChild(section.component);\n\t\t}\n\n\t\t// Check if incomplete/failed - show after partial content.\n\t\t// For aborted/error tool calls, tool execution components show the error.\n\t\t// Length stops can happen before a tool call is complete, so surface them here too.\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tthis.hasToolCalls = hasToolCalls;\n\t}\n\n\tprivate buildSectionSpecs(message: AssistantMessage): SectionSpec[] {\n\t\tconst specs: SectionSpec[] = [];\n\t\tthis.appendContentSpecs(message, specs);\n\t\tthis.appendStatusSpecs(message, specs);\n\t\treturn specs;\n\t}\n\n\tprivate appendContentSpecs(message: AssistantMessage, specs: SectionSpec[]): void {\n\t\tconst hasVisibleContent = message.content.some(\n\t\t\t(c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()),\n\t\t);\n\n\t\tif (hasVisibleContent) {\n\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t}\n\n\t\t// Render content in order\n\t\tfor (let i = 0; i < message.content.length; i++) {\n\t\t\tconst content = message.content[i];\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\t// Assistant text messages with no background - trim the text\n\t\t\t\t// Set paddingY=0 to avoid extra spacing before tool executions\n\t\t\t\tthis.pushMarkdownSpecs(specs, content.text.trim(), \"markdown-text\");\n\t\t\t} else if (content.type === \"thinking\") {\n\t\t\t\tconst thinkingBlocks: string[] = [];\n\t\t\t\tfor (; i < message.content.length; i++) {\n\t\t\t\t\tconst thinkingContent = message.content[i];\n\t\t\t\t\tif (thinkingContent.type !== \"thinking\") {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tconst thinking = thinkingContent.thinking.trim();\n\t\t\t\t\tif (thinking) {\n\t\t\t\t\t\tthinkingBlocks.push(thinking);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ti--;\n\n\t\t\t\tif (thinkingBlocks.length === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Add spacing only when another visible assistant content block follows.\n\t\t\t\t// This avoids a superfluous blank line before separately-rendered tool execution blocks.\n\t\t\t\tconst hasVisibleContentAfter = message.content\n\t\t\t\t\t.slice(i + 1)\n\t\t\t\t\t.some((c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()));\n\n\t\t\t\tif (this.hideThinkingBlock) {\n\t\t\t\t\t// Show one static label for each run of thinking blocks when hidden.\n\t\t\t\t\tspecs.push({\n\t\t\t\t\t\tkind: \"label\",\n\t\t\t\t\t\ttext: theme.italic(theme.fg(\"thinkingText\", this.hiddenThinkingLabel)),\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t// Render each run of thinking blocks as one Markdown section.\n\t\t\t\t\tthis.pushMarkdownSpecs(specs, thinkingBlocks.join(\"\\n\\n\"), \"markdown-thinking\");\n\t\t\t\t}\n\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate appendStatusSpecs(message: AssistantMessage, specs: SectionSpec[]): void {\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tif (message.stopReason === \"length\") {\n\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", \"Response was truncated before completion.\") });\n\t\t} else if (!hasToolCalls) {\n\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\tconst abortMessage =\n\t\t\t\t\tmessage.errorMessage && message.errorMessage !== \"Request was aborted\"\n\t\t\t\t\t\t? message.errorMessage\n\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", abortMessage) });\n\t\t\t} else if (message.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = message.errorMessage || \"Unknown error\";\n\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", `Error: ${errorMsg}`) });\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Push markdown sections for one content block. While streaming, the block\n\t * is split at render-independent boundaries so completed units keep their\n\t * rendered Markdown instances and only the active tail re-parses per\n\t * chunk; otherwise the block renders whole, byte-identical to the\n\t * pre-split behaviour.\n\t *\n\t * Transformers are assumed block-local (the built-in mermaid transformer\n\t * maps tokens and preserves the rest, so per-unit application is\n\t * equivalent). A transformer that genuinely needs whole-message context\n\t * still gets it: a non-streaming update always renders whole blocks.\n\t */\n\tprivate pushMarkdownSpecs(specs: SectionSpec[], text: string, kind: \"markdown-text\" | \"markdown-thinking\"): void {\n\t\tif (this.isStreaming) {\n\t\t\tfor (const unit of splitStreamingMarkdown(text)) {\n\t\t\t\tspecs.push({ kind, text: unit });\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tspecs.push({ kind, text });\n\t}\n\n\tprivate createComponent(spec: SectionSpec): Section[\"component\"] {\n\t\tswitch (spec.kind) {\n\t\t\tcase \"spacer\":\n\t\t\t\treturn new Spacer(1);\n\t\t\tcase \"label\":\n\t\t\t\treturn new Text(spec.text, this.outputPad, 0);\n\t\t\tcase \"markdown-text\":\n\t\t\t\treturn new Markdown(spec.text, this.outputPad, 0, this.markdownTheme, undefined, {\n\t\t\t\t\ttransform: createMarkdownTransform(\"assistant\", this.isStreaming, this.markdownTransformers),\n\t\t\t\t});\n\t\t\tcase \"markdown-thinking\":\n\t\t\t\treturn new Markdown(\n\t\t\t\t\tspec.text,\n\t\t\t\t\tthis.outputPad,\n\t\t\t\t\t0,\n\t\t\t\t\tthis.markdownTheme,\n\t\t\t\t\t{\n\t\t\t\t\t\tcolor: (text: string) => theme.fg(\"thinkingText\", text),\n\t\t\t\t\t\titalic: true,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\ttransform: createMarkdownTransform(\"assistant-thinking\", this.isStreaming, this.markdownTransformers),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"assistant-message.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAY,KAAK,aAAa,EAAgB,MAAM,wBAAwB,CAAC;AAC/F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAkC7E;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAqC7D;AAcD;;GAEG;AACH,qBAAa,yBAA0B,SAAQ,SAAS;IACvD,OAAO,CAAC,gBAAgB,CAAY;IACpC,OAAO,CAAC,iBAAiB,CAAU;IACnC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,oBAAoB,CAAiC;IAC7D,OAAO,CAAC,WAAW,CAAC,CAAmB;IACvC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAiB;IAEjC,YACC,OAAO,CAAC,EAAE,gBAAgB,EAC1B,iBAAiB,UAAQ,EACzB,aAAa,GAAE,aAAkC,EACjD,mBAAmB,SAAgB,EACnC,SAAS,SAAI,EACb,oBAAoB,GAAE,SAAS,mBAAmB,EAAO,EAiBzD;IAEQ,UAAU,IAAI,IAAI,CAM1B;IAED,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAMxC;IAED,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAM1C;IAED,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAMlC;IAEQ,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBvC;IAED,aAAa,CAAC,OAAO,EAAE,gBAAgB,EAAE,WAAW,UAAmB,GAAG,IAAI,CA0C7E;IAED,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,kBAAkB;IAyD1B,OAAO,CAAC,iBAAiB;IAqBzB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,eAAe;CA0BvB","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai\";\nimport { Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { MarkdownTransformer } from \"../../../core/extensions/types.ts\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.ts\";\nimport { createMarkdownTransform } from \"./markdown-transform.ts\";\nimport { withAccentSpine } from \"./message-spine.ts\";\n\nconst OSC133_ZONE_START = \"\\x1b]133;A\\x07\";\nconst OSC133_ZONE_END = \"\\x1b]133;B\\x07\";\nconst OSC133_ZONE_FINAL = \"\\x1b]133;C\\x07\";\n\nconst FENCE_OPENER = /^(```|~~~)/;\nconst FENCE_CLOSER = /^(```|~~~)\\s*$/;\nconst LIST_MARKER = /^([-*+]\\s|\\d+[.)]\\s)/;\nconst QUOTE_MARKER = /^>\\s?/;\nconst INDENTED_CODE = /^(?:\\t| {4})/;\n\nfunction lastNonEmpty(lines: string[]): string | undefined {\n\tfor (let i = lines.length - 1; i >= 0; i--) {\n\t\tif (lines[i].trim() !== \"\") return lines[i];\n\t}\n\treturn undefined;\n}\n\nfunction shouldSplitBefore(next: string, current: string[]): boolean {\n\t// Indented lines can continue indented code blocks; never split there.\n\tif (INDENTED_CODE.test(next)) return false;\n\tconst previous = lastNonEmpty(current);\n\tif (previous && INDENTED_CODE.test(previous)) return false;\n\t// A loose list (\"item\\n\\nitem\") and a blockquote with a blank line render\n\t// as one construct; splitting them would change the output.\n\tif (LIST_MARKER.test(next) && previous && LIST_MARKER.test(previous)) return false;\n\tif (QUOTE_MARKER.test(next) && previous && QUOTE_MARKER.test(previous)) return false;\n\treturn true;\n}\n\n/**\n * Split streamed markdown into render units at boundaries that render\n * independently: closed column-0 code fences, and blank-line block\n * boundaries that cannot continue a preceding construct.\n *\n * The result is prefix-stable: appending text can only add units at the end,\n * never change earlier ones, which is what makes the units cacheable while\n * a message streams.\n */\nexport function splitStreamingMarkdown(text: string): string[] {\n\tif (!text) return [];\n\tconst lines = text.split(\"\\n\");\n\tconst units: string[] = [];\n\tlet current: string[] = [];\n\tlet inFence = false;\n\tconst flush = (): void => {\n\t\tif (current.length > 0) {\n\t\t\tunits.push(current.join(\"\\n\"));\n\t\t\tcurrent = [];\n\t\t}\n\t};\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\tif (!inFence && FENCE_OPENER.test(line)) {\n\t\t\tinFence = true;\n\t\t\tcurrent.push(line);\n\t\t\tcontinue;\n\t\t}\n\t\tif (inFence) {\n\t\t\tcurrent.push(line);\n\t\t\tif (FENCE_CLOSER.test(line)) {\n\t\t\t\tflush();\n\t\t\t\tinFence = false;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tcurrent.push(line);\n\t\tif (line.trim() === \"\") {\n\t\t\tconst next = lines[i + 1];\n\t\t\tif (next !== undefined && next.trim() !== \"\" && shouldSplitBefore(next, current)) {\n\t\t\t\tflush();\n\t\t\t}\n\t\t}\n\t}\n\tflush();\n\treturn units;\n}\n\ntype SectionSpec =\n\t| { kind: \"spacer\" }\n\t| { kind: \"markdown-text\"; text: string }\n\t| { kind: \"markdown-thinking\"; text: string }\n\t| { kind: \"label\"; text: string };\n\ntype Section = { spec: SectionSpec; component: Container[\"children\"][number] };\n\nfunction specText(spec: SectionSpec): string | undefined {\n\treturn spec.kind === \"spacer\" ? undefined : spec.text;\n}\n\n/**\n * Component that renders a complete assistant message\n */\nexport class AssistantMessageComponent extends Container {\n\tprivate contentContainer: Container;\n\tprivate hideThinkingBlock: boolean;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate hiddenThinkingLabel: string;\n\tprivate outputPad: number;\n\tprivate markdownTransformers: readonly MarkdownTransformer[];\n\tprivate lastMessage?: AssistantMessage;\n\tprivate hasToolCalls = false;\n\tprivate isStreaming = false;\n\tprivate forceFullRebuild = true;\n\tprivate sections: Section[] = [];\n\n\tconstructor(\n\t\tmessage?: AssistantMessage,\n\t\thideThinkingBlock = false,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t\thiddenThinkingLabel = \"Thinking...\",\n\t\toutputPad = 1,\n\t\tmarkdownTransformers: readonly MarkdownTransformer[] = [],\n\t) {\n\t\tsuper();\n\n\t\tthis.hideThinkingBlock = hideThinkingBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.hiddenThinkingLabel = hiddenThinkingLabel;\n\t\tthis.outputPad = outputPad;\n\t\tthis.markdownTransformers = markdownTransformers;\n\n\t\t// Container for text/thinking content\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\tif (message) {\n\t\t\tthis.updateContent(message);\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.hideThinkingBlock = hide;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHiddenThinkingLabel(label: string): void {\n\t\tthis.hiddenThinkingLabel = label;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetOutputPad(padding: number): void {\n\t\tthis.outputPad = padding;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst lines = super.render(width);\n\t\tif (this.hasToolCalls || lines.length === 0) {\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Keep conversational messages on one visual axis. Tool panels own\n\t\t// their lifecycle spine; assistant text uses the quieter accent rail.\n\t\tif (this.outputPad > 0) {\n\t\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t\tif (lines[i].trim().length > 0) {\n\t\t\t\t\tlines[i] = withAccentSpine(lines[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlines[0] = OSC133_ZONE_START + lines[0];\n\t\tlines[lines.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + lines[lines.length - 1];\n\t\treturn lines;\n\t}\n\n\tupdateContent(message: AssistantMessage, isStreaming = this.isStreaming): void {\n\t\tthis.lastMessage = message;\n\t\t// The transform closures capture isStreaming, and non-streaming updates\n\t\t// render whole blocks rather than split units, so an isStreaming flip\n\t\t// rebuilds every section.\n\t\tconst rebuildAll = this.forceFullRebuild || isStreaming !== this.isStreaming;\n\t\tthis.isStreaming = isStreaming;\n\n\t\tconst specs = this.buildSectionSpecs(message);\n\t\tconst sections: Section[] = [];\n\t\tfor (let i = 0; i < specs.length; i++) {\n\t\t\tconst spec = specs[i];\n\t\t\tconst previous = rebuildAll ? undefined : this.sections[i];\n\t\t\tconst sameKind = previous !== undefined && previous.spec.kind === spec.kind;\n\t\t\tif (sameKind && (spec.kind === \"spacer\" || specText(previous.spec) === specText(spec))) {\n\t\t\t\tsections.push({ spec, component: previous.component });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (sameKind && spec.kind !== \"spacer\") {\n\t\t\t\t// Same slot, new content (the growing tail of a streamed\n\t\t\t\t// message): update in place so only this unit re-parses.\n\t\t\t\tconst component = previous.component as Text;\n\t\t\t\tcomponent.setText(specText(spec) ?? \"\");\n\t\t\t\tsections.push({ spec, component: previous.component });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsections.push({ spec, component: this.createComponent(spec) });\n\t\t}\n\n\t\tthis.forceFullRebuild = false;\n\t\tthis.sections = sections;\n\n\t\tthis.contentContainer.clear();\n\t\tfor (const section of sections) {\n\t\t\tthis.contentContainer.addChild(section.component);\n\t\t}\n\n\t\t// Check if incomplete/failed - show after partial content.\n\t\t// For aborted/error tool calls, tool execution components show the error.\n\t\t// Length stops can happen before a tool call is complete, so surface them here too.\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tthis.hasToolCalls = hasToolCalls;\n\t}\n\n\tprivate buildSectionSpecs(message: AssistantMessage): SectionSpec[] {\n\t\tconst specs: SectionSpec[] = [];\n\t\tthis.appendContentSpecs(message, specs);\n\t\tthis.appendStatusSpecs(message, specs);\n\t\treturn specs;\n\t}\n\n\tprivate appendContentSpecs(message: AssistantMessage, specs: SectionSpec[]): void {\n\t\tconst hasVisibleContent = message.content.some(\n\t\t\t(c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()),\n\t\t);\n\n\t\tif (hasVisibleContent) {\n\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t}\n\n\t\t// Render content in order\n\t\tfor (let i = 0; i < message.content.length; i++) {\n\t\t\tconst content = message.content[i];\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\t// Assistant text messages with no background - trim the text\n\t\t\t\t// Set paddingY=0 to avoid extra spacing before tool executions\n\t\t\t\tthis.pushMarkdownSpecs(specs, content.text.trim(), \"markdown-text\");\n\t\t\t} else if (content.type === \"thinking\") {\n\t\t\t\tconst thinkingBlocks: string[] = [];\n\t\t\t\tfor (; i < message.content.length; i++) {\n\t\t\t\t\tconst thinkingContent = message.content[i];\n\t\t\t\t\tif (thinkingContent.type !== \"thinking\") {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tconst thinking = thinkingContent.thinking.trim();\n\t\t\t\t\tif (thinking) {\n\t\t\t\t\t\tthinkingBlocks.push(thinking);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ti--;\n\n\t\t\t\tif (thinkingBlocks.length === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Add spacing only when another visible assistant content block follows.\n\t\t\t\t// This avoids a superfluous blank line before separately-rendered tool execution blocks.\n\t\t\t\tconst hasVisibleContentAfter = message.content\n\t\t\t\t\t.slice(i + 1)\n\t\t\t\t\t.some((c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()));\n\n\t\t\t\tif (this.hideThinkingBlock) {\n\t\t\t\t\t// Show one static label for each run of thinking blocks when hidden.\n\t\t\t\t\tspecs.push({\n\t\t\t\t\t\tkind: \"label\",\n\t\t\t\t\t\ttext: theme.italic(theme.fg(\"thinkingText\", this.hiddenThinkingLabel)),\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t// Render each run of thinking blocks as one Markdown section.\n\t\t\t\t\tthis.pushMarkdownSpecs(specs, thinkingBlocks.join(\"\\n\\n\"), \"markdown-thinking\");\n\t\t\t\t}\n\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate appendStatusSpecs(message: AssistantMessage, specs: SectionSpec[]): void {\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tif (message.stopReason === \"length\") {\n\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", \"Response was truncated before completion.\") });\n\t\t} else if (!hasToolCalls) {\n\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\tconst abortMessage =\n\t\t\t\t\tmessage.errorMessage && message.errorMessage !== \"Request was aborted\"\n\t\t\t\t\t\t? message.errorMessage\n\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", abortMessage) });\n\t\t\t} else if (message.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = message.errorMessage || \"Unknown error\";\n\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", `Error: ${errorMsg}`) });\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Push markdown sections for one content block. While streaming, the block\n\t * is split at render-independent boundaries so completed units keep their\n\t * rendered Markdown instances and only the active tail re-parses per\n\t * chunk; otherwise the block renders whole, byte-identical to the\n\t * pre-split behaviour.\n\t *\n\t * Transformers are assumed block-local (the built-in mermaid transformer\n\t * maps tokens and preserves the rest, so per-unit application is\n\t * equivalent). A transformer that genuinely needs whole-message context\n\t * still gets it: a non-streaming update always renders whole blocks.\n\t */\n\tprivate pushMarkdownSpecs(specs: SectionSpec[], text: string, kind: \"markdown-text\" | \"markdown-thinking\"): void {\n\t\tif (this.isStreaming) {\n\t\t\tfor (const unit of splitStreamingMarkdown(text)) {\n\t\t\t\tspecs.push({ kind, text: unit });\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tspecs.push({ kind, text });\n\t}\n\n\tprivate createComponent(spec: SectionSpec): Section[\"component\"] {\n\t\tswitch (spec.kind) {\n\t\t\tcase \"spacer\":\n\t\t\t\treturn new Spacer(1);\n\t\t\tcase \"label\":\n\t\t\t\treturn new Text(spec.text, this.outputPad, 0);\n\t\t\tcase \"markdown-text\":\n\t\t\t\treturn new Markdown(spec.text, this.outputPad, 0, this.markdownTheme, undefined, {\n\t\t\t\t\ttransform: createMarkdownTransform(\"assistant\", this.isStreaming, this.markdownTransformers),\n\t\t\t\t});\n\t\t\tcase \"markdown-thinking\":\n\t\t\t\treturn new Markdown(\n\t\t\t\t\tspec.text,\n\t\t\t\t\tthis.outputPad,\n\t\t\t\t\t0,\n\t\t\t\t\tthis.markdownTheme,\n\t\t\t\t\t{\n\t\t\t\t\t\tcolor: (text: string) => theme.fg(\"thinkingText\", text),\n\t\t\t\t\t\titalic: true,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\ttransform: createMarkdownTransform(\"assistant-thinking\", this.isStreaming, this.markdownTransformers),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
2
2
|
import { getMarkdownTheme, theme } from "../theme/theme.js";
|
|
3
3
|
import { createMarkdownTransform } from "./markdown-transform.js";
|
|
4
|
+
import { withAccentSpine } from "./message-spine.js";
|
|
4
5
|
const OSC133_ZONE_START = "\x1b]133;A\x07";
|
|
5
6
|
const OSC133_ZONE_END = "\x1b]133;B\x07";
|
|
6
7
|
const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
|
|
@@ -144,6 +145,15 @@ export class AssistantMessageComponent extends Container {
|
|
|
144
145
|
if (this.hasToolCalls || lines.length === 0) {
|
|
145
146
|
return lines;
|
|
146
147
|
}
|
|
148
|
+
// Keep conversational messages on one visual axis. Tool panels own
|
|
149
|
+
// their lifecycle spine; assistant text uses the quieter accent rail.
|
|
150
|
+
if (this.outputPad > 0) {
|
|
151
|
+
for (let i = 0; i < lines.length; i++) {
|
|
152
|
+
if (lines[i].trim().length > 0) {
|
|
153
|
+
lines[i] = withAccentSpine(lines[i]);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
147
157
|
lines[0] = OSC133_ZONE_START + lines[0];
|
|
148
158
|
lines[lines.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + lines[lines.length - 1];
|
|
149
159
|
return lines;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai\";\nimport { Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { MarkdownTransformer } from \"../../../core/extensions/types.ts\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.ts\";\nimport { createMarkdownTransform } from \"./markdown-transform.ts\";\n\nconst OSC133_ZONE_START = \"\\x1b]133;A\\x07\";\nconst OSC133_ZONE_END = \"\\x1b]133;B\\x07\";\nconst OSC133_ZONE_FINAL = \"\\x1b]133;C\\x07\";\n\nconst FENCE_OPENER = /^(```|~~~)/;\nconst FENCE_CLOSER = /^(```|~~~)\\s*$/;\nconst LIST_MARKER = /^([-*+]\\s|\\d+[.)]\\s)/;\nconst QUOTE_MARKER = /^>\\s?/;\nconst INDENTED_CODE = /^(?:\\t| {4})/;\n\nfunction lastNonEmpty(lines: string[]): string | undefined {\n\tfor (let i = lines.length - 1; i >= 0; i--) {\n\t\tif (lines[i].trim() !== \"\") return lines[i];\n\t}\n\treturn undefined;\n}\n\nfunction shouldSplitBefore(next: string, current: string[]): boolean {\n\t// Indented lines can continue indented code blocks; never split there.\n\tif (INDENTED_CODE.test(next)) return false;\n\tconst previous = lastNonEmpty(current);\n\tif (previous && INDENTED_CODE.test(previous)) return false;\n\t// A loose list (\"item\\n\\nitem\") and a blockquote with a blank line render\n\t// as one construct; splitting them would change the output.\n\tif (LIST_MARKER.test(next) && previous && LIST_MARKER.test(previous)) return false;\n\tif (QUOTE_MARKER.test(next) && previous && QUOTE_MARKER.test(previous)) return false;\n\treturn true;\n}\n\n/**\n * Split streamed markdown into render units at boundaries that render\n * independently: closed column-0 code fences, and blank-line block\n * boundaries that cannot continue a preceding construct.\n *\n * The result is prefix-stable: appending text can only add units at the end,\n * never change earlier ones, which is what makes the units cacheable while\n * a message streams.\n */\nexport function splitStreamingMarkdown(text: string): string[] {\n\tif (!text) return [];\n\tconst lines = text.split(\"\\n\");\n\tconst units: string[] = [];\n\tlet current: string[] = [];\n\tlet inFence = false;\n\tconst flush = (): void => {\n\t\tif (current.length > 0) {\n\t\t\tunits.push(current.join(\"\\n\"));\n\t\t\tcurrent = [];\n\t\t}\n\t};\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\tif (!inFence && FENCE_OPENER.test(line)) {\n\t\t\tinFence = true;\n\t\t\tcurrent.push(line);\n\t\t\tcontinue;\n\t\t}\n\t\tif (inFence) {\n\t\t\tcurrent.push(line);\n\t\t\tif (FENCE_CLOSER.test(line)) {\n\t\t\t\tflush();\n\t\t\t\tinFence = false;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tcurrent.push(line);\n\t\tif (line.trim() === \"\") {\n\t\t\tconst next = lines[i + 1];\n\t\t\tif (next !== undefined && next.trim() !== \"\" && shouldSplitBefore(next, current)) {\n\t\t\t\tflush();\n\t\t\t}\n\t\t}\n\t}\n\tflush();\n\treturn units;\n}\n\ntype SectionSpec =\n\t| { kind: \"spacer\" }\n\t| { kind: \"markdown-text\"; text: string }\n\t| { kind: \"markdown-thinking\"; text: string }\n\t| { kind: \"label\"; text: string };\n\ntype Section = { spec: SectionSpec; component: Container[\"children\"][number] };\n\nfunction specText(spec: SectionSpec): string | undefined {\n\treturn spec.kind === \"spacer\" ? undefined : spec.text;\n}\n\n/**\n * Component that renders a complete assistant message\n */\nexport class AssistantMessageComponent extends Container {\n\tprivate contentContainer: Container;\n\tprivate hideThinkingBlock: boolean;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate hiddenThinkingLabel: string;\n\tprivate outputPad: number;\n\tprivate markdownTransformers: readonly MarkdownTransformer[];\n\tprivate lastMessage?: AssistantMessage;\n\tprivate hasToolCalls = false;\n\tprivate isStreaming = false;\n\tprivate forceFullRebuild = true;\n\tprivate sections: Section[] = [];\n\n\tconstructor(\n\t\tmessage?: AssistantMessage,\n\t\thideThinkingBlock = false,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t\thiddenThinkingLabel = \"Thinking...\",\n\t\toutputPad = 1,\n\t\tmarkdownTransformers: readonly MarkdownTransformer[] = [],\n\t) {\n\t\tsuper();\n\n\t\tthis.hideThinkingBlock = hideThinkingBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.hiddenThinkingLabel = hiddenThinkingLabel;\n\t\tthis.outputPad = outputPad;\n\t\tthis.markdownTransformers = markdownTransformers;\n\n\t\t// Container for text/thinking content\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\tif (message) {\n\t\t\tthis.updateContent(message);\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.hideThinkingBlock = hide;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHiddenThinkingLabel(label: string): void {\n\t\tthis.hiddenThinkingLabel = label;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetOutputPad(padding: number): void {\n\t\tthis.outputPad = padding;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst lines = super.render(width);\n\t\tif (this.hasToolCalls || lines.length === 0) {\n\t\t\treturn lines;\n\t\t}\n\n\t\tlines[0] = OSC133_ZONE_START + lines[0];\n\t\tlines[lines.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + lines[lines.length - 1];\n\t\treturn lines;\n\t}\n\n\tupdateContent(message: AssistantMessage, isStreaming = this.isStreaming): void {\n\t\tthis.lastMessage = message;\n\t\t// The transform closures capture isStreaming, and non-streaming updates\n\t\t// render whole blocks rather than split units, so an isStreaming flip\n\t\t// rebuilds every section.\n\t\tconst rebuildAll = this.forceFullRebuild || isStreaming !== this.isStreaming;\n\t\tthis.isStreaming = isStreaming;\n\n\t\tconst specs = this.buildSectionSpecs(message);\n\t\tconst sections: Section[] = [];\n\t\tfor (let i = 0; i < specs.length; i++) {\n\t\t\tconst spec = specs[i];\n\t\t\tconst previous = rebuildAll ? undefined : this.sections[i];\n\t\t\tconst sameKind = previous !== undefined && previous.spec.kind === spec.kind;\n\t\t\tif (sameKind && (spec.kind === \"spacer\" || specText(previous.spec) === specText(spec))) {\n\t\t\t\tsections.push({ spec, component: previous.component });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (sameKind && spec.kind !== \"spacer\") {\n\t\t\t\t// Same slot, new content (the growing tail of a streamed\n\t\t\t\t// message): update in place so only this unit re-parses.\n\t\t\t\tconst component = previous.component as Text;\n\t\t\t\tcomponent.setText(specText(spec) ?? \"\");\n\t\t\t\tsections.push({ spec, component: previous.component });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsections.push({ spec, component: this.createComponent(spec) });\n\t\t}\n\n\t\tthis.forceFullRebuild = false;\n\t\tthis.sections = sections;\n\n\t\tthis.contentContainer.clear();\n\t\tfor (const section of sections) {\n\t\t\tthis.contentContainer.addChild(section.component);\n\t\t}\n\n\t\t// Check if incomplete/failed - show after partial content.\n\t\t// For aborted/error tool calls, tool execution components show the error.\n\t\t// Length stops can happen before a tool call is complete, so surface them here too.\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tthis.hasToolCalls = hasToolCalls;\n\t}\n\n\tprivate buildSectionSpecs(message: AssistantMessage): SectionSpec[] {\n\t\tconst specs: SectionSpec[] = [];\n\t\tthis.appendContentSpecs(message, specs);\n\t\tthis.appendStatusSpecs(message, specs);\n\t\treturn specs;\n\t}\n\n\tprivate appendContentSpecs(message: AssistantMessage, specs: SectionSpec[]): void {\n\t\tconst hasVisibleContent = message.content.some(\n\t\t\t(c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()),\n\t\t);\n\n\t\tif (hasVisibleContent) {\n\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t}\n\n\t\t// Render content in order\n\t\tfor (let i = 0; i < message.content.length; i++) {\n\t\t\tconst content = message.content[i];\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\t// Assistant text messages with no background - trim the text\n\t\t\t\t// Set paddingY=0 to avoid extra spacing before tool executions\n\t\t\t\tthis.pushMarkdownSpecs(specs, content.text.trim(), \"markdown-text\");\n\t\t\t} else if (content.type === \"thinking\") {\n\t\t\t\tconst thinkingBlocks: string[] = [];\n\t\t\t\tfor (; i < message.content.length; i++) {\n\t\t\t\t\tconst thinkingContent = message.content[i];\n\t\t\t\t\tif (thinkingContent.type !== \"thinking\") {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tconst thinking = thinkingContent.thinking.trim();\n\t\t\t\t\tif (thinking) {\n\t\t\t\t\t\tthinkingBlocks.push(thinking);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ti--;\n\n\t\t\t\tif (thinkingBlocks.length === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Add spacing only when another visible assistant content block follows.\n\t\t\t\t// This avoids a superfluous blank line before separately-rendered tool execution blocks.\n\t\t\t\tconst hasVisibleContentAfter = message.content\n\t\t\t\t\t.slice(i + 1)\n\t\t\t\t\t.some((c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()));\n\n\t\t\t\tif (this.hideThinkingBlock) {\n\t\t\t\t\t// Show one static label for each run of thinking blocks when hidden.\n\t\t\t\t\tspecs.push({\n\t\t\t\t\t\tkind: \"label\",\n\t\t\t\t\t\ttext: theme.italic(theme.fg(\"thinkingText\", this.hiddenThinkingLabel)),\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t// Render each run of thinking blocks as one Markdown section.\n\t\t\t\t\tthis.pushMarkdownSpecs(specs, thinkingBlocks.join(\"\\n\\n\"), \"markdown-thinking\");\n\t\t\t\t}\n\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate appendStatusSpecs(message: AssistantMessage, specs: SectionSpec[]): void {\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tif (message.stopReason === \"length\") {\n\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", \"Response was truncated before completion.\") });\n\t\t} else if (!hasToolCalls) {\n\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\tconst abortMessage =\n\t\t\t\t\tmessage.errorMessage && message.errorMessage !== \"Request was aborted\"\n\t\t\t\t\t\t? message.errorMessage\n\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", abortMessage) });\n\t\t\t} else if (message.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = message.errorMessage || \"Unknown error\";\n\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", `Error: ${errorMsg}`) });\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Push markdown sections for one content block. While streaming, the block\n\t * is split at render-independent boundaries so completed units keep their\n\t * rendered Markdown instances and only the active tail re-parses per\n\t * chunk; otherwise the block renders whole, byte-identical to the\n\t * pre-split behaviour.\n\t *\n\t * Transformers are assumed block-local (the built-in mermaid transformer\n\t * maps tokens and preserves the rest, so per-unit application is\n\t * equivalent). A transformer that genuinely needs whole-message context\n\t * still gets it: a non-streaming update always renders whole blocks.\n\t */\n\tprivate pushMarkdownSpecs(specs: SectionSpec[], text: string, kind: \"markdown-text\" | \"markdown-thinking\"): void {\n\t\tif (this.isStreaming) {\n\t\t\tfor (const unit of splitStreamingMarkdown(text)) {\n\t\t\t\tspecs.push({ kind, text: unit });\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tspecs.push({ kind, text });\n\t}\n\n\tprivate createComponent(spec: SectionSpec): Section[\"component\"] {\n\t\tswitch (spec.kind) {\n\t\t\tcase \"spacer\":\n\t\t\t\treturn new Spacer(1);\n\t\t\tcase \"label\":\n\t\t\t\treturn new Text(spec.text, this.outputPad, 0);\n\t\t\tcase \"markdown-text\":\n\t\t\t\treturn new Markdown(spec.text, this.outputPad, 0, this.markdownTheme, undefined, {\n\t\t\t\t\ttransform: createMarkdownTransform(\"assistant\", this.isStreaming, this.markdownTransformers),\n\t\t\t\t});\n\t\t\tcase \"markdown-thinking\":\n\t\t\t\treturn new Markdown(\n\t\t\t\t\tspec.text,\n\t\t\t\t\tthis.outputPad,\n\t\t\t\t\t0,\n\t\t\t\t\tthis.markdownTheme,\n\t\t\t\t\t{\n\t\t\t\t\t\tcolor: (text: string) => theme.fg(\"thinkingText\", text),\n\t\t\t\t\t\titalic: true,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\ttransform: createMarkdownTransform(\"assistant-thinking\", this.isStreaming, this.markdownTransformers),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"assistant-message.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/assistant-message.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai\";\nimport { Container, Markdown, type MarkdownTheme, Spacer, Text } from \"@earendil-works/pi-tui\";\nimport type { MarkdownTransformer } from \"../../../core/extensions/types.ts\";\nimport { getMarkdownTheme, theme } from \"../theme/theme.ts\";\nimport { createMarkdownTransform } from \"./markdown-transform.ts\";\nimport { withAccentSpine } from \"./message-spine.ts\";\n\nconst OSC133_ZONE_START = \"\\x1b]133;A\\x07\";\nconst OSC133_ZONE_END = \"\\x1b]133;B\\x07\";\nconst OSC133_ZONE_FINAL = \"\\x1b]133;C\\x07\";\n\nconst FENCE_OPENER = /^(```|~~~)/;\nconst FENCE_CLOSER = /^(```|~~~)\\s*$/;\nconst LIST_MARKER = /^([-*+]\\s|\\d+[.)]\\s)/;\nconst QUOTE_MARKER = /^>\\s?/;\nconst INDENTED_CODE = /^(?:\\t| {4})/;\n\nfunction lastNonEmpty(lines: string[]): string | undefined {\n\tfor (let i = lines.length - 1; i >= 0; i--) {\n\t\tif (lines[i].trim() !== \"\") return lines[i];\n\t}\n\treturn undefined;\n}\n\nfunction shouldSplitBefore(next: string, current: string[]): boolean {\n\t// Indented lines can continue indented code blocks; never split there.\n\tif (INDENTED_CODE.test(next)) return false;\n\tconst previous = lastNonEmpty(current);\n\tif (previous && INDENTED_CODE.test(previous)) return false;\n\t// A loose list (\"item\\n\\nitem\") and a blockquote with a blank line render\n\t// as one construct; splitting them would change the output.\n\tif (LIST_MARKER.test(next) && previous && LIST_MARKER.test(previous)) return false;\n\tif (QUOTE_MARKER.test(next) && previous && QUOTE_MARKER.test(previous)) return false;\n\treturn true;\n}\n\n/**\n * Split streamed markdown into render units at boundaries that render\n * independently: closed column-0 code fences, and blank-line block\n * boundaries that cannot continue a preceding construct.\n *\n * The result is prefix-stable: appending text can only add units at the end,\n * never change earlier ones, which is what makes the units cacheable while\n * a message streams.\n */\nexport function splitStreamingMarkdown(text: string): string[] {\n\tif (!text) return [];\n\tconst lines = text.split(\"\\n\");\n\tconst units: string[] = [];\n\tlet current: string[] = [];\n\tlet inFence = false;\n\tconst flush = (): void => {\n\t\tif (current.length > 0) {\n\t\t\tunits.push(current.join(\"\\n\"));\n\t\t\tcurrent = [];\n\t\t}\n\t};\n\tfor (let i = 0; i < lines.length; i++) {\n\t\tconst line = lines[i];\n\t\tif (!inFence && FENCE_OPENER.test(line)) {\n\t\t\tinFence = true;\n\t\t\tcurrent.push(line);\n\t\t\tcontinue;\n\t\t}\n\t\tif (inFence) {\n\t\t\tcurrent.push(line);\n\t\t\tif (FENCE_CLOSER.test(line)) {\n\t\t\t\tflush();\n\t\t\t\tinFence = false;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tcurrent.push(line);\n\t\tif (line.trim() === \"\") {\n\t\t\tconst next = lines[i + 1];\n\t\t\tif (next !== undefined && next.trim() !== \"\" && shouldSplitBefore(next, current)) {\n\t\t\t\tflush();\n\t\t\t}\n\t\t}\n\t}\n\tflush();\n\treturn units;\n}\n\ntype SectionSpec =\n\t| { kind: \"spacer\" }\n\t| { kind: \"markdown-text\"; text: string }\n\t| { kind: \"markdown-thinking\"; text: string }\n\t| { kind: \"label\"; text: string };\n\ntype Section = { spec: SectionSpec; component: Container[\"children\"][number] };\n\nfunction specText(spec: SectionSpec): string | undefined {\n\treturn spec.kind === \"spacer\" ? undefined : spec.text;\n}\n\n/**\n * Component that renders a complete assistant message\n */\nexport class AssistantMessageComponent extends Container {\n\tprivate contentContainer: Container;\n\tprivate hideThinkingBlock: boolean;\n\tprivate markdownTheme: MarkdownTheme;\n\tprivate hiddenThinkingLabel: string;\n\tprivate outputPad: number;\n\tprivate markdownTransformers: readonly MarkdownTransformer[];\n\tprivate lastMessage?: AssistantMessage;\n\tprivate hasToolCalls = false;\n\tprivate isStreaming = false;\n\tprivate forceFullRebuild = true;\n\tprivate sections: Section[] = [];\n\n\tconstructor(\n\t\tmessage?: AssistantMessage,\n\t\thideThinkingBlock = false,\n\t\tmarkdownTheme: MarkdownTheme = getMarkdownTheme(),\n\t\thiddenThinkingLabel = \"Thinking...\",\n\t\toutputPad = 1,\n\t\tmarkdownTransformers: readonly MarkdownTransformer[] = [],\n\t) {\n\t\tsuper();\n\n\t\tthis.hideThinkingBlock = hideThinkingBlock;\n\t\tthis.markdownTheme = markdownTheme;\n\t\tthis.hiddenThinkingLabel = hiddenThinkingLabel;\n\t\tthis.outputPad = outputPad;\n\t\tthis.markdownTransformers = markdownTransformers;\n\n\t\t// Container for text/thinking content\n\t\tthis.contentContainer = new Container();\n\t\tthis.addChild(this.contentContainer);\n\n\t\tif (message) {\n\t\t\tthis.updateContent(message);\n\t\t}\n\t}\n\n\toverride invalidate(): void {\n\t\tsuper.invalidate();\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHideThinkingBlock(hide: boolean): void {\n\t\tthis.hideThinkingBlock = hide;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetHiddenThinkingLabel(label: string): void {\n\t\tthis.hiddenThinkingLabel = label;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\tsetOutputPad(padding: number): void {\n\t\tthis.outputPad = padding;\n\t\tif (this.lastMessage) {\n\t\t\tthis.forceFullRebuild = true;\n\t\t\tthis.updateContent(this.lastMessage);\n\t\t}\n\t}\n\n\toverride render(width: number): string[] {\n\t\tconst lines = super.render(width);\n\t\tif (this.hasToolCalls || lines.length === 0) {\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Keep conversational messages on one visual axis. Tool panels own\n\t\t// their lifecycle spine; assistant text uses the quieter accent rail.\n\t\tif (this.outputPad > 0) {\n\t\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t\tif (lines[i].trim().length > 0) {\n\t\t\t\t\tlines[i] = withAccentSpine(lines[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlines[0] = OSC133_ZONE_START + lines[0];\n\t\tlines[lines.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + lines[lines.length - 1];\n\t\treturn lines;\n\t}\n\n\tupdateContent(message: AssistantMessage, isStreaming = this.isStreaming): void {\n\t\tthis.lastMessage = message;\n\t\t// The transform closures capture isStreaming, and non-streaming updates\n\t\t// render whole blocks rather than split units, so an isStreaming flip\n\t\t// rebuilds every section.\n\t\tconst rebuildAll = this.forceFullRebuild || isStreaming !== this.isStreaming;\n\t\tthis.isStreaming = isStreaming;\n\n\t\tconst specs = this.buildSectionSpecs(message);\n\t\tconst sections: Section[] = [];\n\t\tfor (let i = 0; i < specs.length; i++) {\n\t\t\tconst spec = specs[i];\n\t\t\tconst previous = rebuildAll ? undefined : this.sections[i];\n\t\t\tconst sameKind = previous !== undefined && previous.spec.kind === spec.kind;\n\t\t\tif (sameKind && (spec.kind === \"spacer\" || specText(previous.spec) === specText(spec))) {\n\t\t\t\tsections.push({ spec, component: previous.component });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (sameKind && spec.kind !== \"spacer\") {\n\t\t\t\t// Same slot, new content (the growing tail of a streamed\n\t\t\t\t// message): update in place so only this unit re-parses.\n\t\t\t\tconst component = previous.component as Text;\n\t\t\t\tcomponent.setText(specText(spec) ?? \"\");\n\t\t\t\tsections.push({ spec, component: previous.component });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsections.push({ spec, component: this.createComponent(spec) });\n\t\t}\n\n\t\tthis.forceFullRebuild = false;\n\t\tthis.sections = sections;\n\n\t\tthis.contentContainer.clear();\n\t\tfor (const section of sections) {\n\t\t\tthis.contentContainer.addChild(section.component);\n\t\t}\n\n\t\t// Check if incomplete/failed - show after partial content.\n\t\t// For aborted/error tool calls, tool execution components show the error.\n\t\t// Length stops can happen before a tool call is complete, so surface them here too.\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tthis.hasToolCalls = hasToolCalls;\n\t}\n\n\tprivate buildSectionSpecs(message: AssistantMessage): SectionSpec[] {\n\t\tconst specs: SectionSpec[] = [];\n\t\tthis.appendContentSpecs(message, specs);\n\t\tthis.appendStatusSpecs(message, specs);\n\t\treturn specs;\n\t}\n\n\tprivate appendContentSpecs(message: AssistantMessage, specs: SectionSpec[]): void {\n\t\tconst hasVisibleContent = message.content.some(\n\t\t\t(c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()),\n\t\t);\n\n\t\tif (hasVisibleContent) {\n\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t}\n\n\t\t// Render content in order\n\t\tfor (let i = 0; i < message.content.length; i++) {\n\t\t\tconst content = message.content[i];\n\t\t\tif (content.type === \"text\" && content.text.trim()) {\n\t\t\t\t// Assistant text messages with no background - trim the text\n\t\t\t\t// Set paddingY=0 to avoid extra spacing before tool executions\n\t\t\t\tthis.pushMarkdownSpecs(specs, content.text.trim(), \"markdown-text\");\n\t\t\t} else if (content.type === \"thinking\") {\n\t\t\t\tconst thinkingBlocks: string[] = [];\n\t\t\t\tfor (; i < message.content.length; i++) {\n\t\t\t\t\tconst thinkingContent = message.content[i];\n\t\t\t\t\tif (thinkingContent.type !== \"thinking\") {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tconst thinking = thinkingContent.thinking.trim();\n\t\t\t\t\tif (thinking) {\n\t\t\t\t\t\tthinkingBlocks.push(thinking);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ti--;\n\n\t\t\t\tif (thinkingBlocks.length === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Add spacing only when another visible assistant content block follows.\n\t\t\t\t// This avoids a superfluous blank line before separately-rendered tool execution blocks.\n\t\t\t\tconst hasVisibleContentAfter = message.content\n\t\t\t\t\t.slice(i + 1)\n\t\t\t\t\t.some((c) => (c.type === \"text\" && c.text.trim()) || (c.type === \"thinking\" && c.thinking.trim()));\n\n\t\t\t\tif (this.hideThinkingBlock) {\n\t\t\t\t\t// Show one static label for each run of thinking blocks when hidden.\n\t\t\t\t\tspecs.push({\n\t\t\t\t\t\tkind: \"label\",\n\t\t\t\t\t\ttext: theme.italic(theme.fg(\"thinkingText\", this.hiddenThinkingLabel)),\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\t// Render each run of thinking blocks as one Markdown section.\n\t\t\t\t\tthis.pushMarkdownSpecs(specs, thinkingBlocks.join(\"\\n\\n\"), \"markdown-thinking\");\n\t\t\t\t}\n\t\t\t\tif (hasVisibleContentAfter) {\n\t\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate appendStatusSpecs(message: AssistantMessage, specs: SectionSpec[]): void {\n\t\tconst hasToolCalls = message.content.some((c) => c.type === \"toolCall\");\n\t\tif (message.stopReason === \"length\") {\n\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", \"Response was truncated before completion.\") });\n\t\t} else if (!hasToolCalls) {\n\t\t\tif (message.stopReason === \"aborted\") {\n\t\t\t\tconst abortMessage =\n\t\t\t\t\tmessage.errorMessage && message.errorMessage !== \"Request was aborted\"\n\t\t\t\t\t\t? message.errorMessage\n\t\t\t\t\t\t: \"Operation aborted\";\n\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", abortMessage) });\n\t\t\t} else if (message.stopReason === \"error\") {\n\t\t\t\tconst errorMsg = message.errorMessage || \"Unknown error\";\n\t\t\t\tspecs.push({ kind: \"spacer\" });\n\t\t\t\tspecs.push({ kind: \"label\", text: theme.fg(\"error\", `Error: ${errorMsg}`) });\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Push markdown sections for one content block. While streaming, the block\n\t * is split at render-independent boundaries so completed units keep their\n\t * rendered Markdown instances and only the active tail re-parses per\n\t * chunk; otherwise the block renders whole, byte-identical to the\n\t * pre-split behaviour.\n\t *\n\t * Transformers are assumed block-local (the built-in mermaid transformer\n\t * maps tokens and preserves the rest, so per-unit application is\n\t * equivalent). A transformer that genuinely needs whole-message context\n\t * still gets it: a non-streaming update always renders whole blocks.\n\t */\n\tprivate pushMarkdownSpecs(specs: SectionSpec[], text: string, kind: \"markdown-text\" | \"markdown-thinking\"): void {\n\t\tif (this.isStreaming) {\n\t\t\tfor (const unit of splitStreamingMarkdown(text)) {\n\t\t\t\tspecs.push({ kind, text: unit });\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tspecs.push({ kind, text });\n\t}\n\n\tprivate createComponent(spec: SectionSpec): Section[\"component\"] {\n\t\tswitch (spec.kind) {\n\t\t\tcase \"spacer\":\n\t\t\t\treturn new Spacer(1);\n\t\t\tcase \"label\":\n\t\t\t\treturn new Text(spec.text, this.outputPad, 0);\n\t\t\tcase \"markdown-text\":\n\t\t\t\treturn new Markdown(spec.text, this.outputPad, 0, this.markdownTheme, undefined, {\n\t\t\t\t\ttransform: createMarkdownTransform(\"assistant\", this.isStreaming, this.markdownTransformers),\n\t\t\t\t});\n\t\t\tcase \"markdown-thinking\":\n\t\t\t\treturn new Markdown(\n\t\t\t\t\tspec.text,\n\t\t\t\t\tthis.outputPad,\n\t\t\t\t\t0,\n\t\t\t\t\tthis.markdownTheme,\n\t\t\t\t\t{\n\t\t\t\t\t\tcolor: (text: string) => theme.fg(\"thinkingText\", text),\n\t\t\t\t\t\titalic: true,\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\ttransform: createMarkdownTransform(\"assistant-thinking\", this.isStreaming, this.markdownTransformers),\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Put the accent spine in the first padding column of an already-rendered line.
|
|
3
|
+
*
|
|
4
|
+
* Both message components render their children with `outputPad` leading spaces, so the
|
|
5
|
+
* first column is padding and is free to take. Prefixing the spine instead makes the line
|
|
6
|
+
* one column wider than the width it was rendered for, and nothing downstream re-flows it:
|
|
7
|
+
* the user-message line then overflows the terminal by one column, and the assistant path
|
|
8
|
+
* hands it to `truncateToWidth`, which clips real content and appends an ellipsis.
|
|
9
|
+
*
|
|
10
|
+
* A line that does not start with a padding column is returned unchanged, because there is
|
|
11
|
+
* no free column to take and widening it is the defect this exists to avoid.
|
|
12
|
+
*/
|
|
13
|
+
export declare function withAccentSpine(line: string): string;
|
|
14
|
+
//# sourceMappingURL=message-spine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-spine.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/message-spine.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGpD","sourcesContent":["import { theme } from \"../theme/theme.ts\";\n\n/**\n * Put the accent spine in the first padding column of an already-rendered line.\n *\n * Both message components render their children with `outputPad` leading spaces, so the\n * first column is padding and is free to take. Prefixing the spine instead makes the line\n * one column wider than the width it was rendered for, and nothing downstream re-flows it:\n * the user-message line then overflows the terminal by one column, and the assistant path\n * hands it to `truncateToWidth`, which clips real content and appends an ellipsis.\n *\n * A line that does not start with a padding column is returned unchanged, because there is\n * no free column to take and widening it is the defect this exists to avoid.\n */\nexport function withAccentSpine(line: string): string {\n\tif (!line.startsWith(\" \")) return line;\n\treturn theme.fg(\"accent\", \"│\") + line.slice(1);\n}\n"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { theme } from "../theme/theme.js";
|
|
2
|
+
/**
|
|
3
|
+
* Put the accent spine in the first padding column of an already-rendered line.
|
|
4
|
+
*
|
|
5
|
+
* Both message components render their children with `outputPad` leading spaces, so the
|
|
6
|
+
* first column is padding and is free to take. Prefixing the spine instead makes the line
|
|
7
|
+
* one column wider than the width it was rendered for, and nothing downstream re-flows it:
|
|
8
|
+
* the user-message line then overflows the terminal by one column, and the assistant path
|
|
9
|
+
* hands it to `truncateToWidth`, which clips real content and appends an ellipsis.
|
|
10
|
+
*
|
|
11
|
+
* A line that does not start with a padding column is returned unchanged, because there is
|
|
12
|
+
* no free column to take and widening it is the defect this exists to avoid.
|
|
13
|
+
*/
|
|
14
|
+
export function withAccentSpine(line) {
|
|
15
|
+
if (!line.startsWith(" "))
|
|
16
|
+
return line;
|
|
17
|
+
return theme.fg("accent", "│") + line.slice(1);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=message-spine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-spine.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/message-spine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAU;IACrD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAAA,CAC/C","sourcesContent":["import { theme } from \"../theme/theme.ts\";\n\n/**\n * Put the accent spine in the first padding column of an already-rendered line.\n *\n * Both message components render their children with `outputPad` leading spaces, so the\n * first column is padding and is free to take. Prefixing the spine instead makes the line\n * one column wider than the width it was rendered for, and nothing downstream re-flows it:\n * the user-message line then overflows the terminal by one column, and the assistant path\n * hands it to `truncateToWidth`, which clips real content and appends an ellipsis.\n *\n * A line that does not start with a padding column is returned unchanged, because there is\n * no free column to take and widening it is the defect this exists to avoid.\n */\nexport function withAccentSpine(line: string): string {\n\tif (!line.startsWith(\" \")) return line;\n\treturn theme.fg(\"accent\", \"│\") + line.slice(1);\n}\n"]}
|