@nghyane/arcane 0.1.13 → 0.1.15
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 +28 -0
- package/package.json +21 -70
- package/scripts/format-prompts.ts +1 -3
- package/src/cli/args.ts +2 -7
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli/update-cli.ts +1 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli.ts +0 -1
- package/src/commands/config.ts +1 -1
- package/src/commands/grep.ts +1 -1
- package/src/commands/jupyter.ts +1 -1
- package/src/commands/plugin.ts +1 -1
- package/src/commands/setup.ts +1 -1
- package/src/commands/shell.ts +1 -1
- package/src/commands/ssh.ts +1 -1
- package/src/commands/stats.ts +1 -1
- package/src/commands/update.ts +1 -1
- package/src/config/model-registry.ts +3 -4
- package/src/config/model-resolver.ts +36 -9
- package/src/config/prompt-templates.ts +1 -9
- package/src/config/settings-schema.ts +32 -88
- package/src/config/settings.ts +3 -4
- package/src/debug/index.ts +1 -1
- package/src/debug/log-formatting.ts +1 -1
- package/src/debug/log-viewer.ts +2 -2
- package/src/discovery/helpers.ts +13 -3
- package/src/exa/index.ts +1 -35
- package/src/exa/render.ts +30 -190
- package/src/export/html/index.ts +1 -1
- package/src/extensibility/custom-tools/loader.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +5 -1
- package/src/extensibility/custom-tools/wrapper.ts +1 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/extensions/wrapper.ts +7 -15
- package/src/extensibility/hooks/runner.ts +1 -1
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +1 -1
- package/src/index.ts +13 -13
- package/src/lsp/index.ts +77 -24
- package/src/lsp/render.ts +34 -583
- package/src/lsp/types.ts +3 -3
- package/src/lsp/utils.ts +1 -1
- package/src/main.ts +1 -1
- package/src/mcp/tool-bridge.ts +1 -24
- package/src/modes/components/assistant-message.ts +7 -7
- package/src/modes/components/bash-execution.ts +50 -112
- package/src/modes/components/bordered-loader.ts +1 -1
- package/src/modes/components/branch-summary-message.ts +16 -10
- package/src/modes/components/compaction-summary-message.ts +20 -12
- package/src/modes/components/context-group.ts +106 -0
- package/src/modes/components/custom-message.ts +4 -5
- package/src/modes/components/diff.ts +2 -2
- package/src/modes/components/dynamic-border.ts +1 -1
- package/src/modes/components/extensions/extension-dashboard.ts +1 -1
- package/src/modes/components/extensions/extension-list.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +1 -1
- package/src/modes/components/footer.ts +2 -2
- package/src/modes/components/history-search.ts +1 -1
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-input.ts +1 -1
- package/src/modes/components/hook-message.ts +4 -5
- package/src/modes/components/hook-selector.ts +1 -1
- package/src/modes/components/index.ts +0 -2
- package/src/modes/components/keybinding-hints.ts +1 -1
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/mcp-add-wizard.ts +1 -1
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/oauth-selector.ts +1 -1
- package/src/modes/components/plugin-settings.ts +1 -1
- package/src/modes/components/python-execution.ts +51 -91
- package/src/modes/components/queue-mode-selector.ts +1 -1
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-defs.ts +5 -10
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/show-images-selector.ts +1 -1
- package/src/modes/components/skill-message.ts +4 -4
- package/src/modes/components/status-line/segments.ts +2 -2
- package/src/modes/components/status-line/separators.ts +1 -1
- package/src/modes/components/status-line-segment-editor.ts +1 -1
- package/src/modes/components/status-line.ts +1 -1
- package/src/modes/components/theme-selector.ts +1 -1
- package/src/modes/components/thinking-selector.ts +1 -1
- package/src/modes/components/todo-display.ts +2 -4
- package/src/modes/components/todo-reminder.ts +4 -4
- package/src/modes/components/tool-execution.ts +118 -440
- package/src/modes/components/tool-image-display.ts +107 -0
- package/src/modes/components/tree-selector.ts +2 -2
- package/src/modes/components/ttsr-notification.ts +4 -17
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +9 -10
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +58 -187
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +3 -1
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +3 -26
- package/src/modes/controllers/ssh-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +3 -7
- package/src/modes/print-mode.ts +5 -5
- package/src/modes/rpc/rpc-mode.ts +1 -1
- package/src/modes/types.ts +1 -2
- package/src/modes/utils/ui-helpers.ts +34 -32
- package/src/patch/edit-tool.ts +742 -0
- package/src/patch/index.ts +32 -898
- package/src/patch/schemas.ts +208 -0
- package/src/patch/shared.ts +83 -151
- package/src/prompts/agents/explore.md +22 -37
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/agents/librarian.md +29 -20
- package/src/prompts/agents/oracle.md +9 -2
- package/src/prompts/agents/reviewer.md +14 -48
- package/src/prompts/agents/task.md +16 -8
- package/src/prompts/compaction/branch-summary.md +4 -1
- package/src/prompts/compaction/compaction-summary.md +4 -1
- package/src/prompts/system/subagent-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +162 -178
- package/src/prompts/system/verification-reminder.md +6 -0
- package/src/sdk.ts +0 -9
- package/src/session/agent-session.ts +244 -1459
- package/src/session/model-controller.ts +406 -0
- package/src/session/retry-utils.ts +71 -0
- package/src/session/session-manager.ts +22 -186
- package/src/session/session-types.ts +312 -0
- package/src/session/stats.ts +387 -0
- package/src/session/streaming-edit.ts +258 -0
- package/src/session/ttsr.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +0 -8
- package/src/stt/recorder.ts +2 -2
- package/src/system-prompt.ts +1 -14
- package/src/task/agents.ts +7 -33
- package/src/task/executor.ts +50 -438
- package/src/task/index.ts +104 -71
- package/src/task/progress-tracker.ts +390 -0
- package/src/task/render.ts +371 -187
- package/src/task/subprocess-tool-registry.ts +1 -1
- package/src/task/types.ts +14 -47
- package/src/tools/ask.ts +31 -42
- package/src/tools/bash-interactive.ts +2 -2
- package/src/tools/bash-interceptor.ts +2 -2
- package/src/tools/bash-normalize.ts +1 -1
- package/src/tools/bash-skill-urls.ts +2 -2
- package/src/tools/bash.ts +87 -136
- package/src/tools/browser.ts +54 -84
- package/src/tools/create-tools.ts +186 -0
- package/src/tools/default-renderer.ts +104 -0
- package/src/tools/explore.ts +11 -10
- package/src/tools/fetch.ts +24 -114
- package/src/tools/find.ts +48 -132
- package/src/tools/gemini-image.ts +5 -15
- package/src/tools/github.ts +450 -0
- package/src/tools/grep.ts +43 -179
- package/src/tools/index.ts +35 -198
- package/src/tools/json-tree.ts +3 -3
- package/src/tools/librarian.ts +18 -18
- package/src/tools/list-limit.ts +2 -2
- package/src/tools/notebook.ts +35 -87
- package/src/tools/oracle.ts +25 -25
- package/src/tools/output-meta.ts +89 -4
- package/src/tools/output-utils.ts +2 -2
- package/src/tools/python.ts +86 -637
- package/src/tools/read.ts +36 -119
- package/src/tools/reviewer-tool.ts +19 -21
- package/src/tools/search-code.ts +128 -0
- package/src/tools/ssh.ts +67 -126
- package/src/tools/subagent-tool.ts +197 -123
- package/src/tools/todo-write.ts +15 -31
- package/src/tools/tool-errors.ts +0 -30
- package/src/tools/undo-edit.ts +30 -67
- package/src/tools/write.ts +78 -127
- package/src/tui/code-cell.ts +4 -4
- package/src/tui/file-list.ts +2 -2
- package/src/tui/output-block.ts +1 -1
- package/src/tui/status-line.ts +1 -1
- package/src/tui/tree-list.ts +2 -2
- package/src/tui/types.ts +1 -1
- package/src/tui/utils.ts +1 -1
- package/src/{tools → ui}/render-utils.ts +87 -126
- package/src/utils/external-editor.ts +4 -4
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/index.ts +30 -0
- package/src/utils/tools-manager.ts +9 -19
- package/src/web/github-client.ts +290 -0
- package/src/web/scrapers/github.ts +11 -62
- package/src/web/search/auth.ts +1 -3
- package/src/web/search/index.ts +82 -46
- package/src/web/search/provider.ts +11 -16
- package/src/web/search/providers/grep.ts +160 -0
- package/src/web/search/render.ts +48 -235
- package/src/web/search/types.ts +1 -1
- package/src/commands/commit.ts +0 -36
- package/src/commit/agentic/agent.ts +0 -311
- package/src/commit/agentic/fallback.ts +0 -96
- package/src/commit/agentic/index.ts +0 -359
- package/src/commit/agentic/prompts/analyze-file.md +0 -22
- package/src/commit/agentic/prompts/session-user.md +0 -25
- package/src/commit/agentic/prompts/split-confirm.md +0 -1
- package/src/commit/agentic/prompts/system.md +0 -38
- package/src/commit/agentic/state.ts +0 -69
- package/src/commit/agentic/tools/analyze-file.ts +0 -118
- package/src/commit/agentic/tools/git-file-diff.ts +0 -194
- package/src/commit/agentic/tools/git-hunk.ts +0 -50
- package/src/commit/agentic/tools/git-overview.ts +0 -84
- package/src/commit/agentic/tools/index.ts +0 -56
- package/src/commit/agentic/tools/propose-changelog.ts +0 -128
- package/src/commit/agentic/tools/propose-commit.ts +0 -154
- package/src/commit/agentic/tools/recent-commits.ts +0 -81
- package/src/commit/agentic/tools/split-commit.ts +0 -280
- package/src/commit/agentic/topo-sort.ts +0 -44
- package/src/commit/agentic/trivial.ts +0 -51
- package/src/commit/agentic/validation.ts +0 -200
- package/src/commit/analysis/conventional.ts +0 -165
- package/src/commit/analysis/index.ts +0 -4
- package/src/commit/analysis/scope.ts +0 -242
- package/src/commit/analysis/summary.ts +0 -112
- package/src/commit/analysis/validation.ts +0 -66
- package/src/commit/changelog/detect.ts +0 -37
- package/src/commit/changelog/generate.ts +0 -110
- package/src/commit/changelog/index.ts +0 -234
- package/src/commit/changelog/parse.ts +0 -44
- package/src/commit/cli.ts +0 -93
- package/src/commit/git/diff.ts +0 -148
- package/src/commit/git/errors.ts +0 -9
- package/src/commit/git/index.ts +0 -211
- package/src/commit/git/operations.ts +0 -54
- package/src/commit/index.ts +0 -5
- package/src/commit/map-reduce/index.ts +0 -64
- package/src/commit/map-reduce/map-phase.ts +0 -178
- package/src/commit/map-reduce/reduce-phase.ts +0 -145
- package/src/commit/map-reduce/utils.ts +0 -9
- package/src/commit/message.ts +0 -11
- package/src/commit/model-selection.ts +0 -69
- package/src/commit/pipeline.ts +0 -243
- package/src/commit/prompts/analysis-system.md +0 -148
- package/src/commit/prompts/analysis-user.md +0 -38
- package/src/commit/prompts/changelog-system.md +0 -50
- package/src/commit/prompts/changelog-user.md +0 -18
- package/src/commit/prompts/file-observer-system.md +0 -24
- package/src/commit/prompts/file-observer-user.md +0 -8
- package/src/commit/prompts/reduce-system.md +0 -50
- package/src/commit/prompts/reduce-user.md +0 -17
- package/src/commit/prompts/summary-retry.md +0 -3
- package/src/commit/prompts/summary-system.md +0 -38
- package/src/commit/prompts/summary-user.md +0 -13
- package/src/commit/prompts/types-description.md +0 -2
- package/src/commit/types.ts +0 -109
- package/src/commit/utils/exclusions.ts +0 -42
- package/src/mcp/render.ts +0 -123
- package/src/modes/components/agent-dashboard.ts +0 -1130
- package/src/modes/components/codemode-group.ts +0 -369
- package/src/modes/components/read-tool-group.ts +0 -119
- package/src/modes/components/visual-truncate.ts +0 -63
- package/src/prompts/system/subagent-user-prompt.md +0 -8
- package/src/prompts/tools/ask.md +0 -44
- package/src/prompts/tools/bash.md +0 -24
- package/src/prompts/tools/browser.md +0 -33
- package/src/prompts/tools/calculator.md +0 -12
- package/src/prompts/tools/explore.md +0 -29
- package/src/prompts/tools/fetch.md +0 -16
- package/src/prompts/tools/find.md +0 -18
- package/src/prompts/tools/gemini-image.md +0 -23
- package/src/prompts/tools/grep.md +0 -28
- package/src/prompts/tools/hashline.md +0 -232
- package/src/prompts/tools/librarian.md +0 -24
- package/src/prompts/tools/lsp.md +0 -28
- package/src/prompts/tools/oracle.md +0 -26
- package/src/prompts/tools/patch.md +0 -74
- package/src/prompts/tools/python.md +0 -66
- package/src/prompts/tools/read.md +0 -36
- package/src/prompts/tools/replace.md +0 -38
- package/src/prompts/tools/reviewer.md +0 -41
- package/src/prompts/tools/ssh.md +0 -51
- package/src/prompts/tools/task-summary.md +0 -28
- package/src/prompts/tools/task.md +0 -146
- package/src/prompts/tools/todo-write.md +0 -65
- package/src/prompts/tools/undo-edit.md +0 -7
- package/src/prompts/tools/web-search.md +0 -19
- package/src/prompts/tools/write.md +0 -18
- package/src/task/batch.ts +0 -102
- package/src/task/discovery.ts +0 -126
- package/src/task/parallel.ts +0 -84
- package/src/task/template.ts +0 -32
- package/src/tools/calculator.ts +0 -537
- package/src/tools/jtd-to-typescript.ts +0 -198
- package/src/tools/renderers.ts +0 -60
- package/src/tools/tool-result.ts +0 -86
- /package/src/{modes/theme → theme}/dark.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-catppuccin.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-dracula.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-gruvbox.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-solarized.json +0 -0
- /package/src/{modes/theme → theme}/defaults/dark-tokyo-night.json +0 -0
- /package/src/{modes/theme → theme}/defaults/index.ts +0 -0
- /package/src/{modes/theme → theme}/defaults/light-catppuccin.json +0 -0
- /package/src/{modes/theme → theme}/defaults/light-github.json +0 -0
- /package/src/{modes/theme → theme}/defaults/light-solarized.json +0 -0
- /package/src/{modes/theme → theme}/light.json +0 -0
- /package/src/{modes/theme → theme}/mermaid-cache.ts +0 -0
- /package/src/{modes/theme → theme}/theme-schema.json +0 -0
- /package/src/{modes/theme → theme}/theme.ts +0 -0
package/src/task/parallel.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parallel execution with concurrency control.
|
|
3
|
-
*/
|
|
4
|
-
/** Result of parallel execution */
|
|
5
|
-
export interface ParallelResult<R> {
|
|
6
|
-
/** Results array - undefined entries indicate tasks that were skipped due to abort */
|
|
7
|
-
results: (R | undefined)[];
|
|
8
|
-
/** Whether execution was aborted before all tasks completed */
|
|
9
|
-
aborted: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Execute items with a concurrency limit using a worker pool pattern.
|
|
14
|
-
* Results are returned in the same order as input items.
|
|
15
|
-
*
|
|
16
|
-
* On abort: returns partial results with `aborted: true`. Completed tasks are preserved,
|
|
17
|
-
* in-progress tasks will complete with their abort handling, skipped tasks are `undefined`.
|
|
18
|
-
*
|
|
19
|
-
* On error: fails fast - does not wait for other workers to complete.
|
|
20
|
-
*
|
|
21
|
-
* @param items - Items to process
|
|
22
|
-
* @param concurrency - Maximum concurrent operations
|
|
23
|
-
* @param fn - Async function to execute for each item
|
|
24
|
-
* @param signal - Optional abort signal to stop scheduling new work
|
|
25
|
-
*/
|
|
26
|
-
export async function mapWithConcurrencyLimit<T, R>(
|
|
27
|
-
items: T[],
|
|
28
|
-
concurrency: number,
|
|
29
|
-
fn: (item: T, index: number) => Promise<R>,
|
|
30
|
-
signal?: AbortSignal,
|
|
31
|
-
): Promise<ParallelResult<R>> {
|
|
32
|
-
const normalizedConcurrency = Number.isFinite(concurrency) ? Math.floor(concurrency) : items.length;
|
|
33
|
-
const effectiveConcurrency = normalizedConcurrency > 0 ? normalizedConcurrency : items.length;
|
|
34
|
-
const limit = Math.max(1, Math.min(effectiveConcurrency, items.length));
|
|
35
|
-
const results: (R | undefined)[] = new Array(items.length);
|
|
36
|
-
let nextIndex = 0;
|
|
37
|
-
|
|
38
|
-
// Create internal abort controller to cancel workers on any rejection
|
|
39
|
-
const abortController = new AbortController();
|
|
40
|
-
const workerSignal = signal ? AbortSignal.any([signal, abortController.signal]) : abortController.signal;
|
|
41
|
-
|
|
42
|
-
// Promise that rejects on first error - used to fail fast (not for abort)
|
|
43
|
-
let rejectFirst: (error: unknown) => void;
|
|
44
|
-
const firstErrorPromise = new Promise<never>((_, reject) => {
|
|
45
|
-
rejectFirst = reject;
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const worker = async (): Promise<void> => {
|
|
49
|
-
while (true) {
|
|
50
|
-
// On abort, stop picking up new work - but don't throw
|
|
51
|
-
if (workerSignal.aborted) return;
|
|
52
|
-
const index = nextIndex++;
|
|
53
|
-
if (index >= items.length) return;
|
|
54
|
-
try {
|
|
55
|
-
results[index] = await fn(items[index], index);
|
|
56
|
-
} catch (error) {
|
|
57
|
-
// On abort, the fn itself handles it and returns a result
|
|
58
|
-
// Only propagate non-abort errors
|
|
59
|
-
if (!workerSignal.aborted) {
|
|
60
|
-
abortController.abort();
|
|
61
|
-
rejectFirst(error);
|
|
62
|
-
throw error;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// Create worker pool
|
|
69
|
-
const workers = Array(limit)
|
|
70
|
-
.fill(null)
|
|
71
|
-
.map(() => worker());
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
await Promise.race([Promise.all(workers), firstErrorPromise]);
|
|
75
|
-
} catch (error) {
|
|
76
|
-
// If aborted, don't rethrow - return partial results
|
|
77
|
-
if (signal?.aborted) {
|
|
78
|
-
return { results, aborted: true };
|
|
79
|
-
}
|
|
80
|
-
throw error;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return { results, aborted: signal?.aborted ?? false };
|
|
84
|
-
}
|
package/src/task/template.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
2
|
-
import subagentUserPromptTemplate from "../prompts/system/subagent-user-prompt.md" with { type: "text" };
|
|
3
|
-
import type { TaskItem } from "./types";
|
|
4
|
-
|
|
5
|
-
interface RenderResult {
|
|
6
|
-
/** Full task text sent to the subagent */
|
|
7
|
-
task: string;
|
|
8
|
-
id: string;
|
|
9
|
-
description: string;
|
|
10
|
-
skills?: string[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Build the full task text from shared context and per-task assignment.
|
|
15
|
-
*
|
|
16
|
-
* If context is provided, it is prepended with a separator.
|
|
17
|
-
*/
|
|
18
|
-
export function renderTemplate(context: string, task: TaskItem): RenderResult {
|
|
19
|
-
let { id, description, assignment, skills } = task;
|
|
20
|
-
assignment = assignment.trim();
|
|
21
|
-
context = context?.trim();
|
|
22
|
-
|
|
23
|
-
if (!context || !assignment) {
|
|
24
|
-
return { task: assignment || context!, id, description, skills };
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
task: renderPromptTemplate(subagentUserPromptTemplate, { context, assignment }),
|
|
28
|
-
id,
|
|
29
|
-
description,
|
|
30
|
-
skills,
|
|
31
|
-
};
|
|
32
|
-
}
|
package/src/tools/calculator.ts
DELETED
|
@@ -1,537 +0,0 @@
|
|
|
1
|
-
import type { AgentTool, AgentToolResult } from "@nghyane/arcane-agent";
|
|
2
|
-
import type { Component } from "@nghyane/arcane-tui";
|
|
3
|
-
import { Text } from "@nghyane/arcane-tui";
|
|
4
|
-
import { untilAborted } from "@nghyane/arcane-utils";
|
|
5
|
-
import { type Static, Type } from "@sinclair/typebox";
|
|
6
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
7
|
-
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
8
|
-
import type { Theme } from "../modes/theme/theme";
|
|
9
|
-
import calculatorDescription from "../prompts/tools/calculator.md" with { type: "text" };
|
|
10
|
-
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
11
|
-
import type { ToolSession } from ".";
|
|
12
|
-
import { formatCount, formatEmptyMessage, formatErrorMessage, PREVIEW_LIMITS, TRUNCATE_LENGTHS } from "./render-utils";
|
|
13
|
-
|
|
14
|
-
// =============================================================================
|
|
15
|
-
// Token Types
|
|
16
|
-
// =============================================================================
|
|
17
|
-
|
|
18
|
-
/** Supported arithmetic operators (** is exponentiation). */
|
|
19
|
-
type Operator = "+" | "-" | "*" | "/" | "%" | "**";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Lexer token variants:
|
|
23
|
-
* - number: parsed numeric value with original string for error messages
|
|
24
|
-
* - operator: arithmetic operator
|
|
25
|
-
* - paren: grouping parenthesis
|
|
26
|
-
*/
|
|
27
|
-
type Token =
|
|
28
|
-
| { type: "number"; value: number; raw: string }
|
|
29
|
-
| { type: "operator"; value: Operator }
|
|
30
|
-
| { type: "paren"; value: "(" | ")" };
|
|
31
|
-
|
|
32
|
-
const calculatorSchema = Type.Object({
|
|
33
|
-
calculations: Type.Array(
|
|
34
|
-
Type.Object({
|
|
35
|
-
expression: Type.String({ description: "Math expression to evaluate" }),
|
|
36
|
-
prefix: Type.String({ description: "Text to prepend to the result" }),
|
|
37
|
-
suffix: Type.String({ description: "Text to append to the result" }),
|
|
38
|
-
}),
|
|
39
|
-
{ description: "List of calculations to evaluate" },
|
|
40
|
-
),
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
export interface CalculatorToolDetails {
|
|
44
|
-
results: Array<{ expression: string; value: number; output: string }>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// =============================================================================
|
|
48
|
-
// Character classification helpers for numeric literal parsing
|
|
49
|
-
// =============================================================================
|
|
50
|
-
|
|
51
|
-
function isDigit(ch: string): boolean {
|
|
52
|
-
return ch >= "0" && ch <= "9";
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function isHexDigit(ch: string): boolean {
|
|
56
|
-
return (ch >= "0" && ch <= "9") || (ch >= "a" && ch <= "f") || (ch >= "A" && ch <= "F");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function isBinaryDigit(ch: string): boolean {
|
|
60
|
-
return ch === "0" || ch === "1";
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function isOctalDigit(ch: string): boolean {
|
|
64
|
-
return ch >= "0" && ch <= "7";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// =============================================================================
|
|
68
|
-
// Tokenizer
|
|
69
|
-
// =============================================================================
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Tokenize a math expression into numbers, operators, and parentheses.
|
|
73
|
-
*
|
|
74
|
-
* Number formats supported:
|
|
75
|
-
* - Decimal: 123, 3.14, .5
|
|
76
|
-
* - Scientific: 1e10, 2.5E-3
|
|
77
|
-
* - Hexadecimal: 0xFF
|
|
78
|
-
* - Binary: 0b1010
|
|
79
|
-
* - Octal: 0o755
|
|
80
|
-
*/
|
|
81
|
-
function tokenizeExpression(expression: string): Token[] {
|
|
82
|
-
const tokens: Token[] = [];
|
|
83
|
-
let i = 0;
|
|
84
|
-
|
|
85
|
-
while (i < expression.length) {
|
|
86
|
-
const ch = expression[i];
|
|
87
|
-
|
|
88
|
-
// Skip whitespace
|
|
89
|
-
if (ch.trim() === "") {
|
|
90
|
-
i += 1;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (ch === "(" || ch === ")") {
|
|
95
|
-
tokens.push({ type: "paren", value: ch });
|
|
96
|
-
i += 1;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Check ** before single * to handle exponentiation
|
|
101
|
-
if (ch === "*" && expression[i + 1] === "*") {
|
|
102
|
-
tokens.push({ type: "operator", value: "**" });
|
|
103
|
-
i += 2;
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (ch === "+" || ch === "-" || ch === "*" || ch === "/" || ch === "%") {
|
|
108
|
-
tokens.push({ type: "operator", value: ch });
|
|
109
|
-
i += 1;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Number parsing: starts with digit or decimal point followed by digit
|
|
114
|
-
const next = expression[i + 1];
|
|
115
|
-
const numberStart = isDigit(ch) || (ch === "." && next !== undefined && isDigit(next));
|
|
116
|
-
if (!numberStart) {
|
|
117
|
-
throw new Error(`Invalid character "${ch}" in expression`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const start = i;
|
|
121
|
-
|
|
122
|
-
// Handle prefixed literals (0x, 0b, 0o)
|
|
123
|
-
if (ch === "0" && next !== undefined) {
|
|
124
|
-
const prefix = next.toLowerCase();
|
|
125
|
-
if (prefix === "x" || prefix === "b" || prefix === "o") {
|
|
126
|
-
i += 2; // Skip "0x" / "0b" / "0o"
|
|
127
|
-
let hasDigit = false;
|
|
128
|
-
while (i < expression.length) {
|
|
129
|
-
const digit = expression[i];
|
|
130
|
-
const valid =
|
|
131
|
-
prefix === "x" ? isHexDigit(digit) : prefix === "b" ? isBinaryDigit(digit) : isOctalDigit(digit);
|
|
132
|
-
if (!valid) break;
|
|
133
|
-
hasDigit = true;
|
|
134
|
-
i += 1;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!hasDigit) {
|
|
138
|
-
throw new Error(`Invalid numeric literal starting at "${expression.slice(start, i)}"`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const raw = expression.slice(start, i);
|
|
142
|
-
const value = Number(raw); // JS Number() handles 0x/0b/0o natively
|
|
143
|
-
if (!Number.isFinite(value)) {
|
|
144
|
-
throw new Error(`Invalid number "${raw}"`);
|
|
145
|
-
}
|
|
146
|
-
tokens.push({ type: "number", value, raw });
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Parse decimal number: integer part
|
|
152
|
-
let hasDigits = false;
|
|
153
|
-
while (i < expression.length && isDigit(expression[i])) {
|
|
154
|
-
hasDigits = true;
|
|
155
|
-
i += 1;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Fractional part
|
|
159
|
-
if (expression[i] === ".") {
|
|
160
|
-
i += 1;
|
|
161
|
-
while (i < expression.length && isDigit(expression[i])) {
|
|
162
|
-
hasDigits = true;
|
|
163
|
-
i += 1;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (!hasDigits) {
|
|
168
|
-
throw new Error(`Invalid number starting at "${expression.slice(start, i + 1)}"`);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Scientific notation exponent (e.g., 1e10, 2.5E-3)
|
|
172
|
-
if (expression[i] === "e" || expression[i] === "E") {
|
|
173
|
-
i += 1;
|
|
174
|
-
if (expression[i] === "+" || expression[i] === "-") {
|
|
175
|
-
i += 1;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
let hasExponentDigits = false;
|
|
179
|
-
while (i < expression.length && isDigit(expression[i])) {
|
|
180
|
-
hasExponentDigits = true;
|
|
181
|
-
i += 1;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (!hasExponentDigits) {
|
|
185
|
-
throw new Error(`Invalid exponent in "${expression.slice(start, i)}"`);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const raw = expression.slice(start, i);
|
|
190
|
-
const value = Number(raw);
|
|
191
|
-
if (!Number.isFinite(value)) {
|
|
192
|
-
throw new Error(`Invalid number "${raw}"`);
|
|
193
|
-
}
|
|
194
|
-
tokens.push({ type: "number", value, raw });
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return tokens;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// =============================================================================
|
|
201
|
-
// Recursive Descent Parser
|
|
202
|
-
// =============================================================================
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Recursive descent parser for arithmetic expressions.
|
|
206
|
-
*
|
|
207
|
-
* Operator precedence (lowest to highest):
|
|
208
|
-
* 1. Addition, subtraction (+, -)
|
|
209
|
-
* 2. Multiplication, division, modulo (*, /, %)
|
|
210
|
-
* 3. Unary plus/minus (+x, -x)
|
|
211
|
-
* 4. Exponentiation (**)
|
|
212
|
-
* 5. Parentheses and literals
|
|
213
|
-
*
|
|
214
|
-
* Each precedence level has its own parse method. Lower precedence methods
|
|
215
|
-
* call higher precedence methods, building the AST implicitly through
|
|
216
|
-
* the call stack.
|
|
217
|
-
*/
|
|
218
|
-
class ExpressionParser {
|
|
219
|
-
#index = 0;
|
|
220
|
-
|
|
221
|
-
constructor(private readonly tokens: Token[]) {}
|
|
222
|
-
|
|
223
|
-
/** Parse the full expression and ensure all tokens are consumed. */
|
|
224
|
-
parse(): number {
|
|
225
|
-
const value = this.#parseExpression();
|
|
226
|
-
if (this.#index < this.tokens.length) {
|
|
227
|
-
throw new Error("Unexpected token in expression");
|
|
228
|
-
}
|
|
229
|
-
return value;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Parse addition and subtraction (lowest precedence).
|
|
234
|
-
* Left-associative: 1 - 2 - 3 = (1 - 2) - 3
|
|
235
|
-
*/
|
|
236
|
-
#parseExpression(): number {
|
|
237
|
-
let value = this.#parseTerm();
|
|
238
|
-
while (true) {
|
|
239
|
-
if (this.#matchOperator("+")) {
|
|
240
|
-
value += this.#parseTerm();
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (this.#matchOperator("-")) {
|
|
244
|
-
value -= this.#parseTerm();
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
return value;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Parse multiplication, division, and modulo.
|
|
254
|
-
* Left-associative: 8 / 4 / 2 = (8 / 4) / 2
|
|
255
|
-
*/
|
|
256
|
-
#parseTerm(): number {
|
|
257
|
-
let value = this.#parseUnary();
|
|
258
|
-
while (true) {
|
|
259
|
-
if (this.#matchOperator("*")) {
|
|
260
|
-
value *= this.#parseUnary();
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
if (this.#matchOperator("/")) {
|
|
264
|
-
value /= this.#parseUnary();
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
if (this.#matchOperator("%")) {
|
|
268
|
-
value %= this.#parseUnary();
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
return value;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Parse unary + and - operators.
|
|
278
|
-
* Recursive to handle chained unary: --x, +-x
|
|
279
|
-
*/
|
|
280
|
-
#parseUnary(): number {
|
|
281
|
-
if (this.#matchOperator("+")) {
|
|
282
|
-
return this.#parseUnary();
|
|
283
|
-
}
|
|
284
|
-
if (this.#matchOperator("-")) {
|
|
285
|
-
return -this.#parseUnary();
|
|
286
|
-
}
|
|
287
|
-
return this.#parsePower();
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Parse exponentiation operator.
|
|
292
|
-
* Right-associative: 2 ** 3 ** 2 = 2 ** (3 ** 2) = 512
|
|
293
|
-
* Achieved by recursive call to parsePower for the right operand.
|
|
294
|
-
*/
|
|
295
|
-
#parsePower(): number {
|
|
296
|
-
let value = this.#parsePrimary();
|
|
297
|
-
if (this.#matchOperator("**")) {
|
|
298
|
-
value = value ** this.#parsePower(); // Right-associative via recursion
|
|
299
|
-
}
|
|
300
|
-
return value;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Parse primary expressions: number literals and parenthesized subexpressions.
|
|
305
|
-
* Parentheses restart parsing at lowest precedence (parseExpression).
|
|
306
|
-
*/
|
|
307
|
-
#parsePrimary(): number {
|
|
308
|
-
const token = this.#peek();
|
|
309
|
-
if (!token) {
|
|
310
|
-
throw new Error("Unexpected end of expression");
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (token.type === "number") {
|
|
314
|
-
this.#index += 1;
|
|
315
|
-
return token.value;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (token.type === "paren" && token.value === "(") {
|
|
319
|
-
this.#index += 1;
|
|
320
|
-
const value = this.#parseExpression(); // Reset to lowest precedence
|
|
321
|
-
if (!this.#matchParen(")")) {
|
|
322
|
-
throw new Error("Missing closing parenthesis");
|
|
323
|
-
}
|
|
324
|
-
return value;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
throw new Error("Unexpected token in expression");
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/** Consume operator if it matches, advancing the token index. */
|
|
331
|
-
#matchOperator(value: Operator): boolean {
|
|
332
|
-
const token = this.tokens[this.#index];
|
|
333
|
-
if (token && token.type === "operator" && token.value === value) {
|
|
334
|
-
this.#index += 1;
|
|
335
|
-
return true;
|
|
336
|
-
}
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/** Consume parenthesis if it matches, advancing the token index. */
|
|
341
|
-
#matchParen(value: "(" | ")"): boolean {
|
|
342
|
-
const token = this.tokens[this.#index];
|
|
343
|
-
if (token && token.type === "paren" && token.value === value) {
|
|
344
|
-
this.#index += 1;
|
|
345
|
-
return true;
|
|
346
|
-
}
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/** Look at current token without consuming it. */
|
|
351
|
-
#peek(): Token | undefined {
|
|
352
|
-
return this.tokens[this.#index];
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// =============================================================================
|
|
357
|
-
// Expression Evaluator
|
|
358
|
-
// =============================================================================
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Evaluate a math expression string and return the numeric result.
|
|
362
|
-
*
|
|
363
|
-
* Pipeline: expression string -> tokens -> parse tree (implicit) -> value
|
|
364
|
-
*
|
|
365
|
-
* @throws Error on syntax errors, empty expressions, or non-finite results (Infinity, NaN)
|
|
366
|
-
*/
|
|
367
|
-
function evaluateExpression(expression: string): number {
|
|
368
|
-
const tokens = tokenizeExpression(expression);
|
|
369
|
-
if (tokens.length === 0) {
|
|
370
|
-
throw new Error("Expression is empty");
|
|
371
|
-
}
|
|
372
|
-
const parser = new ExpressionParser(tokens);
|
|
373
|
-
const value = parser.parse();
|
|
374
|
-
if (!Number.isFinite(value)) {
|
|
375
|
-
throw new Error("Expression result is not a finite number");
|
|
376
|
-
}
|
|
377
|
-
// Normalize -0 to 0 for consistent output
|
|
378
|
-
return Object.is(value, -0) ? 0 : value;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function formatResult(value: number): string {
|
|
382
|
-
return String(value);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
386
|
-
// Tool Class
|
|
387
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
388
|
-
|
|
389
|
-
type CalculatorParams = Static<typeof calculatorSchema>;
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Calculator tool for evaluating mathematical expressions.
|
|
393
|
-
*
|
|
394
|
-
* Supports decimal, hex (0x), binary (0b), octal (0o) literals,
|
|
395
|
-
* standard arithmetic operators, and parentheses.
|
|
396
|
-
*/
|
|
397
|
-
export class CalculatorTool implements AgentTool<typeof calculatorSchema, CalculatorToolDetails> {
|
|
398
|
-
readonly name = "calc";
|
|
399
|
-
readonly label = "Calc";
|
|
400
|
-
readonly description: string;
|
|
401
|
-
readonly parameters = calculatorSchema;
|
|
402
|
-
|
|
403
|
-
constructor(_session: ToolSession) {
|
|
404
|
-
this.description = renderPromptTemplate(calculatorDescription);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
async execute(
|
|
408
|
-
_toolCallId: string,
|
|
409
|
-
{ calculations }: CalculatorParams,
|
|
410
|
-
signal?: AbortSignal,
|
|
411
|
-
): Promise<AgentToolResult<CalculatorToolDetails>> {
|
|
412
|
-
return untilAborted(signal, async () => {
|
|
413
|
-
const results = calculations.map(calc => {
|
|
414
|
-
const value = evaluateExpression(calc.expression);
|
|
415
|
-
const output = `${calc.prefix}${formatResult(value)}${calc.suffix}`;
|
|
416
|
-
return { expression: calc.expression, value, output };
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
const outputText = results.map(result => result.output).join("\n");
|
|
420
|
-
return {
|
|
421
|
-
content: [{ type: "text", text: outputText }],
|
|
422
|
-
details: { results },
|
|
423
|
-
};
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// =============================================================================
|
|
429
|
-
// TUI Renderer
|
|
430
|
-
// =============================================================================
|
|
431
|
-
|
|
432
|
-
interface CalculatorRenderArgs {
|
|
433
|
-
calculations?: Array<{ expression: string; prefix?: string; suffix?: string }>;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
const COLLAPSED_LIST_LIMIT = PREVIEW_LIMITS.COLLAPSED_ITEMS;
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* TUI renderer for calculator tool calls and results.
|
|
440
|
-
* Handles both collapsed (preview) and expanded (full) display modes.
|
|
441
|
-
*/
|
|
442
|
-
export const calculatorToolRenderer = {
|
|
443
|
-
/**
|
|
444
|
-
* Render the tool call header showing the first expression and count.
|
|
445
|
-
* Format: "Calc <expression> (N calcs)"
|
|
446
|
-
*/
|
|
447
|
-
renderCall(args: CalculatorRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
448
|
-
const count = args.calculations?.length ?? 0;
|
|
449
|
-
const firstExpression = args.calculations?.[0]?.expression;
|
|
450
|
-
const description = firstExpression ? truncateToWidth(firstExpression, TRUNCATE_LENGTHS.TITLE) : undefined;
|
|
451
|
-
const meta = count > 0 ? [formatCount("calc", count)] : [];
|
|
452
|
-
const text = renderStatusLine({ icon: "pending", title: "Calc", description, meta }, uiTheme);
|
|
453
|
-
return new Text(text, 0, 0);
|
|
454
|
-
},
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Render calculation results as a tree list.
|
|
458
|
-
* Collapsed mode shows first N items with expand hint; expanded shows all.
|
|
459
|
-
*/
|
|
460
|
-
renderResult(
|
|
461
|
-
result: { content: Array<{ type: string; text?: string }>; details?: CalculatorToolDetails; isError?: boolean },
|
|
462
|
-
options: RenderResultOptions,
|
|
463
|
-
uiTheme: Theme,
|
|
464
|
-
args?: CalculatorRenderArgs,
|
|
465
|
-
): Component {
|
|
466
|
-
const details = result.details;
|
|
467
|
-
const textContent = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
468
|
-
if (result.isError) {
|
|
469
|
-
const header = renderStatusLine({ icon: "error", title: "Calc" }, uiTheme);
|
|
470
|
-
const renderedLines = [header, formatErrorMessage(textContent, uiTheme)];
|
|
471
|
-
return {
|
|
472
|
-
render() {
|
|
473
|
-
return renderedLines;
|
|
474
|
-
},
|
|
475
|
-
invalidate() {},
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// Prefer structured details; fall back to parsing text content
|
|
480
|
-
let outputs = details?.results?.map(entry => `${entry.expression} = ${entry.output}`) ?? [];
|
|
481
|
-
if (outputs.length === 0 && textContent.trim()) {
|
|
482
|
-
const rawOutputs = textContent.split("\n").filter(line => line.trim().length > 0);
|
|
483
|
-
const expressions = args?.calculations?.map(calc => calc.expression) ?? [];
|
|
484
|
-
if (expressions.length === rawOutputs.length && expressions.length > 0) {
|
|
485
|
-
outputs = rawOutputs.map((output, index) => `${expressions[index]} = ${output}`);
|
|
486
|
-
} else {
|
|
487
|
-
outputs = rawOutputs;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (outputs.length === 0) {
|
|
492
|
-
const header = renderStatusLine({ icon: "warning", title: "Calc" }, uiTheme);
|
|
493
|
-
const renderedLines = [header, formatEmptyMessage("No results", uiTheme)];
|
|
494
|
-
return {
|
|
495
|
-
render() {
|
|
496
|
-
return renderedLines;
|
|
497
|
-
},
|
|
498
|
-
invalidate() {},
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
const description = args?.calculations?.[0]?.expression
|
|
503
|
-
? truncateToWidth(args.calculations[0].expression, TRUNCATE_LENGTHS.TITLE)
|
|
504
|
-
: undefined;
|
|
505
|
-
const header = renderStatusLine(
|
|
506
|
-
{ icon: "success", title: "Calc", description, meta: [formatCount("result", outputs.length)] },
|
|
507
|
-
uiTheme,
|
|
508
|
-
);
|
|
509
|
-
|
|
510
|
-
let cached: RenderCache | undefined;
|
|
511
|
-
|
|
512
|
-
return {
|
|
513
|
-
render(width) {
|
|
514
|
-
const { expanded } = options;
|
|
515
|
-
const key = new Hasher().bool(expanded).u32(width).digest();
|
|
516
|
-
if (cached?.key === key) return cached.lines;
|
|
517
|
-
const treeLines = renderTreeList(
|
|
518
|
-
{
|
|
519
|
-
items: outputs,
|
|
520
|
-
expanded,
|
|
521
|
-
maxCollapsed: COLLAPSED_LIST_LIMIT,
|
|
522
|
-
itemType: "result",
|
|
523
|
-
renderItem: output => uiTheme.fg("toolOutput", output),
|
|
524
|
-
},
|
|
525
|
-
uiTheme,
|
|
526
|
-
);
|
|
527
|
-
const lines = [header, ...treeLines].map(l => truncateToWidth(l, width, Ellipsis.Omit));
|
|
528
|
-
cached = { key, lines };
|
|
529
|
-
return lines;
|
|
530
|
-
},
|
|
531
|
-
invalidate() {
|
|
532
|
-
cached = undefined;
|
|
533
|
-
},
|
|
534
|
-
};
|
|
535
|
-
},
|
|
536
|
-
mergeCallAndResult: true,
|
|
537
|
-
};
|