@ash-cloud/ash-ui 0.0.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/dist/design-tokens.cjs +275 -0
- package/dist/design-tokens.cjs.map +1 -0
- package/dist/design-tokens.d.cts +258 -0
- package/dist/design-tokens.d.ts +258 -0
- package/dist/design-tokens.js +261 -0
- package/dist/design-tokens.js.map +1 -0
- package/dist/icons.cjs +279 -0
- package/dist/icons.cjs.map +1 -0
- package/dist/icons.d.cts +53 -0
- package/dist/icons.d.ts +53 -0
- package/dist/icons.js +241 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.cjs +2430 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +535 -0
- package/dist/index.d.ts +535 -0
- package/dist/index.js +2318 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +1 -0
- package/dist/types.cjs +65 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +303 -0
- package/dist/types.d.ts +303 -0
- package/dist/types.js +50 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.cjs +415 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +135 -0
- package/dist/utils.d.ts +135 -0
- package/dist/utils.js +396 -0
- package/dist/utils.js.map +1 -0
- package/package.json +89 -0
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ActionType, ToolResult, CommandRunResult, NormalizedToolCall, NormalizedEntry, ToolDisplayConfig } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @ash-cloud/ash-ui - Utilities
|
|
5
|
+
*
|
|
6
|
+
* Utility functions for normalizing and formatting tool calls
|
|
7
|
+
* for display in agentic UIs.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Format MCP tool names from mcp__server__tool to mcp:server:tool
|
|
12
|
+
*/
|
|
13
|
+
declare function formatToolName(name: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Parse MCP tool name to extract server and tool names
|
|
16
|
+
*/
|
|
17
|
+
declare function parseMcpToolName(name: string): {
|
|
18
|
+
serverName: string;
|
|
19
|
+
toolName: string;
|
|
20
|
+
} | null;
|
|
21
|
+
/**
|
|
22
|
+
* Map a tool name and input to a structured ActionType
|
|
23
|
+
*/
|
|
24
|
+
declare function mapToolToActionType(toolName: string, input: unknown): ActionType;
|
|
25
|
+
/**
|
|
26
|
+
* Generate a human-readable summary for a tool call
|
|
27
|
+
*/
|
|
28
|
+
declare function generateToolSummary(_toolName: string, _input: unknown, actionType: ActionType): string;
|
|
29
|
+
/**
|
|
30
|
+
* Extract text content from various content formats
|
|
31
|
+
*/
|
|
32
|
+
declare function extractTextContent(content: unknown): string;
|
|
33
|
+
/**
|
|
34
|
+
* Normalize tool result content to markdown or JSON format
|
|
35
|
+
*/
|
|
36
|
+
declare function normalizeToolResult(content: unknown): ToolResult;
|
|
37
|
+
/**
|
|
38
|
+
* Parse command result from tool result content
|
|
39
|
+
*/
|
|
40
|
+
declare function parseCommandResult(content: unknown): CommandRunResult;
|
|
41
|
+
interface ToolUseInput {
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
input: unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a NormalizedToolCall from a tool_use content block
|
|
48
|
+
*/
|
|
49
|
+
declare function createToolCall(toolUse: ToolUseInput): NormalizedToolCall;
|
|
50
|
+
/**
|
|
51
|
+
* Update a NormalizedToolCall with its result
|
|
52
|
+
*/
|
|
53
|
+
declare function updateToolCallWithResult(toolCall: NormalizedToolCall, content: unknown, isError?: boolean): NormalizedToolCall;
|
|
54
|
+
/**
|
|
55
|
+
* Get display icon name for an action type
|
|
56
|
+
*/
|
|
57
|
+
declare function getActionIcon(actionType: ActionType): string;
|
|
58
|
+
/**
|
|
59
|
+
* Get display label for an action type
|
|
60
|
+
*/
|
|
61
|
+
declare function getActionLabel(actionType: ActionType): string;
|
|
62
|
+
/**
|
|
63
|
+
* Format a file size in bytes to a human-readable string
|
|
64
|
+
*/
|
|
65
|
+
declare function formatFileSize(bytes: number): string;
|
|
66
|
+
/**
|
|
67
|
+
* Format a timestamp to a locale time string
|
|
68
|
+
*/
|
|
69
|
+
declare function formatTimestamp(timestamp: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Truncate a string with ellipsis
|
|
72
|
+
*/
|
|
73
|
+
declare function truncate(str: string, maxLength: number): string;
|
|
74
|
+
/**
|
|
75
|
+
* Join class names, filtering out falsy values
|
|
76
|
+
*/
|
|
77
|
+
declare function cn(...classes: (string | undefined | null | false)[]): string;
|
|
78
|
+
/**
|
|
79
|
+
* A grouped entry that contains either a single entry or a group of consecutive tool calls
|
|
80
|
+
*/
|
|
81
|
+
type GroupedEntry = {
|
|
82
|
+
type: 'single';
|
|
83
|
+
entry: NormalizedEntry;
|
|
84
|
+
} | {
|
|
85
|
+
type: 'tool_group';
|
|
86
|
+
entries: NormalizedEntry[];
|
|
87
|
+
id: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Group consecutive tool call entries together based on display config.
|
|
91
|
+
*
|
|
92
|
+
* This is used in compact mode to merge consecutive tool calls into
|
|
93
|
+
* a single ToolExecutionGroup, while keeping text content separate.
|
|
94
|
+
*
|
|
95
|
+
* @param entries - Array of normalized entries
|
|
96
|
+
* @param config - Display configuration (uses breakEveryNToolCalls)
|
|
97
|
+
* @returns Array of grouped entries
|
|
98
|
+
*/
|
|
99
|
+
declare function groupEntriesForCompactMode(entries: NormalizedEntry[], config: ToolDisplayConfig): GroupedEntry[];
|
|
100
|
+
/**
|
|
101
|
+
* Extract tool calls from a group of entries
|
|
102
|
+
*/
|
|
103
|
+
declare function extractToolCallsFromGroup(entries: NormalizedEntry[]): NormalizedToolCall[];
|
|
104
|
+
/**
|
|
105
|
+
* A parsed option from assistant message content
|
|
106
|
+
*/
|
|
107
|
+
interface ParsedOption {
|
|
108
|
+
/** Option identifier (e.g., "1", "2", "A", "B") */
|
|
109
|
+
id: string;
|
|
110
|
+
/** Option title/label */
|
|
111
|
+
label: string;
|
|
112
|
+
/** Optional description text */
|
|
113
|
+
description?: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Result of parsing options from content
|
|
117
|
+
*/
|
|
118
|
+
interface ParsedOptionsResult {
|
|
119
|
+
/** Text before the options */
|
|
120
|
+
preamble: string;
|
|
121
|
+
/** Parsed options */
|
|
122
|
+
options: ParsedOption[];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Parse options from assistant message content.
|
|
126
|
+
* Detects patterns like:
|
|
127
|
+
* - "Option 1: Title" / "Option 2: Title"
|
|
128
|
+
* - "**Option 1:** Title" (bold markdown)
|
|
129
|
+
* - Numbered lists with descriptions
|
|
130
|
+
*
|
|
131
|
+
* @returns ParsedOptionsResult if options found, null otherwise
|
|
132
|
+
*/
|
|
133
|
+
declare function parseOptionsFromContent(content: string): ParsedOptionsResult | null;
|
|
134
|
+
|
|
135
|
+
export { type GroupedEntry, type ParsedOption, type ParsedOptionsResult, type ToolUseInput, cn, createToolCall, extractTextContent, extractToolCallsFromGroup, formatFileSize, formatTimestamp, formatToolName, generateToolSummary, getActionIcon, getActionLabel, groupEntriesForCompactMode, mapToolToActionType, normalizeToolResult, parseCommandResult, parseMcpToolName, parseOptionsFromContent, truncate, updateToolCallWithResult };
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
function formatToolName(name) {
|
|
3
|
+
if (name.startsWith("mcp__")) {
|
|
4
|
+
const parts = name.split("__");
|
|
5
|
+
if (parts.length >= 3) {
|
|
6
|
+
return `mcp:${parts[1]}:${parts.slice(2).join(":")}`;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return name;
|
|
10
|
+
}
|
|
11
|
+
function parseMcpToolName(name) {
|
|
12
|
+
if (name.startsWith("mcp__")) {
|
|
13
|
+
const parts = name.split("__");
|
|
14
|
+
if (parts.length >= 3 && parts[1] !== void 0) {
|
|
15
|
+
return {
|
|
16
|
+
serverName: parts[1],
|
|
17
|
+
toolName: parts.slice(2).join("__")
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function mapToolToActionType(toolName, input) {
|
|
24
|
+
const inputObj = input || {};
|
|
25
|
+
switch (toolName) {
|
|
26
|
+
case "Bash":
|
|
27
|
+
return {
|
|
28
|
+
action: "command_run",
|
|
29
|
+
command: inputObj.command || "",
|
|
30
|
+
description: inputObj.description
|
|
31
|
+
};
|
|
32
|
+
case "Read":
|
|
33
|
+
return {
|
|
34
|
+
action: "file_read",
|
|
35
|
+
path: inputObj.file_path || "",
|
|
36
|
+
offset: inputObj.offset,
|
|
37
|
+
limit: inputObj.limit
|
|
38
|
+
};
|
|
39
|
+
case "Edit":
|
|
40
|
+
return {
|
|
41
|
+
action: "file_edit",
|
|
42
|
+
path: inputObj.file_path || "",
|
|
43
|
+
oldString: inputObj.old_string,
|
|
44
|
+
newString: inputObj.new_string,
|
|
45
|
+
replaceAll: inputObj.replace_all
|
|
46
|
+
};
|
|
47
|
+
case "Write":
|
|
48
|
+
return {
|
|
49
|
+
action: "file_write",
|
|
50
|
+
path: inputObj.file_path || "",
|
|
51
|
+
content: inputObj.content
|
|
52
|
+
};
|
|
53
|
+
case "Grep":
|
|
54
|
+
return {
|
|
55
|
+
action: "search",
|
|
56
|
+
pattern: inputObj.pattern || "",
|
|
57
|
+
path: inputObj.path,
|
|
58
|
+
glob: inputObj.glob,
|
|
59
|
+
type: inputObj.type
|
|
60
|
+
};
|
|
61
|
+
case "Glob":
|
|
62
|
+
return {
|
|
63
|
+
action: "glob",
|
|
64
|
+
pattern: inputObj.pattern || "",
|
|
65
|
+
path: inputObj.path
|
|
66
|
+
};
|
|
67
|
+
case "WebFetch":
|
|
68
|
+
return {
|
|
69
|
+
action: "web_fetch",
|
|
70
|
+
url: inputObj.url || "",
|
|
71
|
+
prompt: inputObj.prompt
|
|
72
|
+
};
|
|
73
|
+
case "WebSearch":
|
|
74
|
+
return {
|
|
75
|
+
action: "web_search",
|
|
76
|
+
query: inputObj.query || ""
|
|
77
|
+
};
|
|
78
|
+
case "TodoWrite": {
|
|
79
|
+
const todos = inputObj.todos || [];
|
|
80
|
+
const stats = {
|
|
81
|
+
total: todos.length,
|
|
82
|
+
completed: todos.filter((t) => t.status === "completed").length,
|
|
83
|
+
inProgress: todos.filter((t) => t.status === "in_progress").length,
|
|
84
|
+
pending: todos.filter((t) => t.status === "pending").length
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
action: "todo_write",
|
|
88
|
+
todos,
|
|
89
|
+
stats
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
default: {
|
|
93
|
+
const mcpParts = parseMcpToolName(toolName);
|
|
94
|
+
if (mcpParts) {
|
|
95
|
+
return {
|
|
96
|
+
action: "mcp_tool",
|
|
97
|
+
serverName: mcpParts.serverName,
|
|
98
|
+
toolName: mcpParts.toolName,
|
|
99
|
+
arguments: input
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
action: "generic_tool",
|
|
104
|
+
toolName: formatToolName(toolName),
|
|
105
|
+
arguments: input
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function generateToolSummary(_toolName, _input, actionType) {
|
|
111
|
+
switch (actionType.action) {
|
|
112
|
+
case "command_run": {
|
|
113
|
+
const cmd = actionType.command;
|
|
114
|
+
return cmd.length > 60 ? cmd.substring(0, 57) + "..." : cmd;
|
|
115
|
+
}
|
|
116
|
+
case "file_read":
|
|
117
|
+
return actionType.path;
|
|
118
|
+
case "file_edit":
|
|
119
|
+
return actionType.path;
|
|
120
|
+
case "file_write":
|
|
121
|
+
return actionType.path;
|
|
122
|
+
case "search":
|
|
123
|
+
return `${actionType.pattern}${actionType.path ? ` in ${actionType.path}` : ""}`;
|
|
124
|
+
case "glob":
|
|
125
|
+
return actionType.pattern;
|
|
126
|
+
case "web_fetch":
|
|
127
|
+
return actionType.url;
|
|
128
|
+
case "web_search":
|
|
129
|
+
return actionType.query;
|
|
130
|
+
case "mcp_tool":
|
|
131
|
+
return `${actionType.serverName}:${actionType.toolName}`;
|
|
132
|
+
case "generic_tool":
|
|
133
|
+
return actionType.toolName;
|
|
134
|
+
case "todo_write": {
|
|
135
|
+
const { stats } = actionType;
|
|
136
|
+
if (stats) {
|
|
137
|
+
return `${stats.completed}/${stats.total} completed`;
|
|
138
|
+
}
|
|
139
|
+
return `${actionType.todos.length} tasks`;
|
|
140
|
+
}
|
|
141
|
+
default:
|
|
142
|
+
return "Unknown tool";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function extractTextContent(content) {
|
|
146
|
+
if (typeof content === "string") return content;
|
|
147
|
+
if (Array.isArray(content)) {
|
|
148
|
+
return content.filter((item) => typeof item?.text === "string").map((item) => item.text).join("\n");
|
|
149
|
+
}
|
|
150
|
+
if (content && typeof content === "object" && "text" in content) {
|
|
151
|
+
return String(content.text);
|
|
152
|
+
}
|
|
153
|
+
return JSON.stringify(content, null, 2);
|
|
154
|
+
}
|
|
155
|
+
function normalizeToolResult(content) {
|
|
156
|
+
if (typeof content === "string") {
|
|
157
|
+
try {
|
|
158
|
+
const parsed = JSON.parse(content);
|
|
159
|
+
return { type: "json", value: parsed };
|
|
160
|
+
} catch {
|
|
161
|
+
return { type: "markdown", value: content };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (Array.isArray(content)) {
|
|
165
|
+
const texts = content.filter(
|
|
166
|
+
(item) => item?.type === "text" && typeof item.text === "string"
|
|
167
|
+
).map((item) => item.text);
|
|
168
|
+
if (texts.length > 0) {
|
|
169
|
+
const joined = texts.join("\n\n");
|
|
170
|
+
try {
|
|
171
|
+
return { type: "json", value: JSON.parse(joined) };
|
|
172
|
+
} catch {
|
|
173
|
+
return { type: "markdown", value: joined };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { type: "json", value: content };
|
|
178
|
+
}
|
|
179
|
+
function parseCommandResult(content) {
|
|
180
|
+
const output = extractTextContent(content);
|
|
181
|
+
if (typeof content === "string") {
|
|
182
|
+
try {
|
|
183
|
+
const parsed = JSON.parse(content);
|
|
184
|
+
if (typeof parsed.exitCode === "number") {
|
|
185
|
+
return {
|
|
186
|
+
exitCode: parsed.exitCode,
|
|
187
|
+
output: parsed.output || output,
|
|
188
|
+
success: parsed.exitCode === 0
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
output,
|
|
196
|
+
success: true
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function createToolCall(toolUse) {
|
|
200
|
+
const actionType = mapToolToActionType(toolUse.name, toolUse.input);
|
|
201
|
+
const summary = generateToolSummary(toolUse.name, toolUse.input, actionType);
|
|
202
|
+
return {
|
|
203
|
+
id: toolUse.id,
|
|
204
|
+
toolName: toolUse.name,
|
|
205
|
+
actionType,
|
|
206
|
+
status: "pending",
|
|
207
|
+
summary,
|
|
208
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function updateToolCallWithResult(toolCall, content, isError) {
|
|
212
|
+
const updatedToolCall = { ...toolCall };
|
|
213
|
+
const actionType = { ...toolCall.actionType };
|
|
214
|
+
updatedToolCall.status = isError ? "failed" : "success";
|
|
215
|
+
updatedToolCall.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
216
|
+
updatedToolCall.isError = isError;
|
|
217
|
+
if (actionType.action === "command_run") {
|
|
218
|
+
const result = parseCommandResult(content);
|
|
219
|
+
actionType.result = result;
|
|
220
|
+
if (result.exitCode !== void 0 && result.exitCode !== 0) {
|
|
221
|
+
updatedToolCall.status = "failed";
|
|
222
|
+
updatedToolCall.isError = true;
|
|
223
|
+
}
|
|
224
|
+
} else if (actionType.action === "mcp_tool" || actionType.action === "generic_tool") {
|
|
225
|
+
actionType.result = normalizeToolResult(content);
|
|
226
|
+
}
|
|
227
|
+
updatedToolCall.actionType = actionType;
|
|
228
|
+
return updatedToolCall;
|
|
229
|
+
}
|
|
230
|
+
function getActionIcon(actionType) {
|
|
231
|
+
switch (actionType.action) {
|
|
232
|
+
case "command_run":
|
|
233
|
+
return "terminal";
|
|
234
|
+
case "file_read":
|
|
235
|
+
return "file-text";
|
|
236
|
+
case "file_edit":
|
|
237
|
+
return "edit";
|
|
238
|
+
case "file_write":
|
|
239
|
+
return "file-plus";
|
|
240
|
+
case "search":
|
|
241
|
+
return "search";
|
|
242
|
+
case "glob":
|
|
243
|
+
return "folder-search";
|
|
244
|
+
case "web_fetch":
|
|
245
|
+
return "globe";
|
|
246
|
+
case "web_search":
|
|
247
|
+
return "search";
|
|
248
|
+
case "mcp_tool":
|
|
249
|
+
return "plug";
|
|
250
|
+
case "generic_tool":
|
|
251
|
+
return "tool";
|
|
252
|
+
case "todo_write":
|
|
253
|
+
return "list-checks";
|
|
254
|
+
default:
|
|
255
|
+
return "tool";
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function getActionLabel(actionType) {
|
|
259
|
+
switch (actionType.action) {
|
|
260
|
+
case "command_run":
|
|
261
|
+
return "Command";
|
|
262
|
+
case "file_read":
|
|
263
|
+
return "Read";
|
|
264
|
+
case "file_edit":
|
|
265
|
+
return "Edit";
|
|
266
|
+
case "file_write":
|
|
267
|
+
return "Write";
|
|
268
|
+
case "search":
|
|
269
|
+
return "Search";
|
|
270
|
+
case "glob":
|
|
271
|
+
return "Glob";
|
|
272
|
+
case "web_fetch":
|
|
273
|
+
return "Fetch";
|
|
274
|
+
case "web_search":
|
|
275
|
+
return "Search";
|
|
276
|
+
case "mcp_tool":
|
|
277
|
+
return "MCP";
|
|
278
|
+
case "generic_tool":
|
|
279
|
+
return "Tool";
|
|
280
|
+
case "todo_write":
|
|
281
|
+
return "Tasks";
|
|
282
|
+
default:
|
|
283
|
+
return "Tool";
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function formatFileSize(bytes) {
|
|
287
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
288
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
289
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
290
|
+
}
|
|
291
|
+
function formatTimestamp(timestamp) {
|
|
292
|
+
try {
|
|
293
|
+
const date = new Date(timestamp);
|
|
294
|
+
return date.toLocaleTimeString("en-US", {
|
|
295
|
+
hour: "2-digit",
|
|
296
|
+
minute: "2-digit",
|
|
297
|
+
second: "2-digit",
|
|
298
|
+
hour12: false
|
|
299
|
+
});
|
|
300
|
+
} catch {
|
|
301
|
+
return timestamp;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function truncate(str, maxLength) {
|
|
305
|
+
if (str.length <= maxLength) return str;
|
|
306
|
+
return str.substring(0, maxLength - 3) + "...";
|
|
307
|
+
}
|
|
308
|
+
function cn(...classes) {
|
|
309
|
+
return classes.filter(Boolean).join(" ");
|
|
310
|
+
}
|
|
311
|
+
function groupEntriesForCompactMode(entries, config) {
|
|
312
|
+
const result = [];
|
|
313
|
+
let currentToolGroup = [];
|
|
314
|
+
let toolGroupCounter = 0;
|
|
315
|
+
const flushToolGroup = () => {
|
|
316
|
+
if (currentToolGroup.length > 0) {
|
|
317
|
+
result.push({
|
|
318
|
+
type: "tool_group",
|
|
319
|
+
entries: [...currentToolGroup],
|
|
320
|
+
id: `tool-group-${toolGroupCounter++}`
|
|
321
|
+
});
|
|
322
|
+
currentToolGroup = [];
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
for (const entry of entries) {
|
|
326
|
+
if (entry.entryType.type === "tool_call") {
|
|
327
|
+
currentToolGroup.push(entry);
|
|
328
|
+
if (config.breakEveryNToolCalls && config.breakEveryNToolCalls > 0 && currentToolGroup.length >= config.breakEveryNToolCalls) {
|
|
329
|
+
flushToolGroup();
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
flushToolGroup();
|
|
333
|
+
result.push({ type: "single", entry });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
flushToolGroup();
|
|
337
|
+
return result;
|
|
338
|
+
}
|
|
339
|
+
function extractToolCallsFromGroup(entries) {
|
|
340
|
+
return entries.filter(
|
|
341
|
+
(e) => e.entryType.type === "tool_call"
|
|
342
|
+
).map((e) => e.entryType.toolCall);
|
|
343
|
+
}
|
|
344
|
+
function parseOptionsFromContent(content) {
|
|
345
|
+
const optionPattern = /(?:\*\*)?Option\s+(\d+)(?:\*\*)?[:\-]\s*([^\n]+)(?:\n((?:(?!\n(?:\*\*)?Option\s+\d).)*?))?/gi;
|
|
346
|
+
const options = [];
|
|
347
|
+
let firstMatchStart = -1;
|
|
348
|
+
let match;
|
|
349
|
+
optionPattern.lastIndex = 0;
|
|
350
|
+
while ((match = optionPattern.exec(content)) !== null) {
|
|
351
|
+
if (firstMatchStart === -1) {
|
|
352
|
+
firstMatchStart = match.index;
|
|
353
|
+
}
|
|
354
|
+
const id = match[1] ?? "";
|
|
355
|
+
const labelRaw = match[2];
|
|
356
|
+
const label = labelRaw ? labelRaw.trim() : "";
|
|
357
|
+
const descriptionRaw = match[3];
|
|
358
|
+
let description;
|
|
359
|
+
if (descriptionRaw) {
|
|
360
|
+
const cleaned = descriptionRaw.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join(" ");
|
|
361
|
+
if (cleaned) {
|
|
362
|
+
description = cleaned;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
options.push({ id, label, description });
|
|
366
|
+
}
|
|
367
|
+
if (options.length >= 2) {
|
|
368
|
+
const preamble = firstMatchStart > 0 ? content.substring(0, firstMatchStart).trim() : "";
|
|
369
|
+
return { preamble, options };
|
|
370
|
+
}
|
|
371
|
+
const questionPattern = /^(.*?(?:what would you like to do\??|choose an option|select.*?:|here are your options.*?:))\s*\n+((?:\d+\.\s+.+\n?)+)/ims;
|
|
372
|
+
const questionMatch = content.match(questionPattern);
|
|
373
|
+
if (questionMatch && questionMatch[1] && questionMatch[2]) {
|
|
374
|
+
const preamble = questionMatch[1].trim();
|
|
375
|
+
const listSection = questionMatch[2];
|
|
376
|
+
const listPattern = /(\d+)\.\s+([^\n]+)/g;
|
|
377
|
+
let listMatch;
|
|
378
|
+
while ((listMatch = listPattern.exec(listSection)) !== null) {
|
|
379
|
+
const listId = listMatch[1] ?? "";
|
|
380
|
+
const listLabelRaw = listMatch[2];
|
|
381
|
+
const listLabel = listLabelRaw ? listLabelRaw.trim() : "";
|
|
382
|
+
options.push({
|
|
383
|
+
id: listId,
|
|
384
|
+
label: listLabel
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
if (options.length >= 2) {
|
|
388
|
+
return { preamble, options };
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export { cn, createToolCall, extractTextContent, extractToolCallsFromGroup, formatFileSize, formatTimestamp, formatToolName, generateToolSummary, getActionIcon, getActionLabel, groupEntriesForCompactMode, mapToolToActionType, normalizeToolResult, parseCommandResult, parseMcpToolName, parseOptionsFromContent, truncate, updateToolCallWithResult };
|
|
395
|
+
//# sourceMappingURL=utils.js.map
|
|
396
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts"],"names":[],"mappings":"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","file":"utils.js","sourcesContent":["/**\n * @ash-cloud/ash-ui - Utilities\n *\n * Utility functions for normalizing and formatting tool calls\n * for display in agentic UIs.\n */\n\nimport type {\n ActionType,\n ToolResult,\n CommandRunResult,\n NormalizedToolCall,\n NormalizedEntry,\n ToolDisplayConfig,\n TodoItem,\n} from './types';\n\n// =============================================================================\n// Tool Name Formatting\n// =============================================================================\n\n/**\n * Format MCP tool names from mcp__server__tool to mcp:server:tool\n */\nexport function formatToolName(name: string): string {\n if (name.startsWith('mcp__')) {\n const parts = name.split('__');\n if (parts.length >= 3) {\n return `mcp:${parts[1]}:${parts.slice(2).join(':')}`;\n }\n }\n return name;\n}\n\n/**\n * Parse MCP tool name to extract server and tool names\n */\nexport function parseMcpToolName(name: string): { serverName: string; toolName: string } | null {\n if (name.startsWith('mcp__')) {\n const parts = name.split('__');\n if (parts.length >= 3 && parts[1] !== undefined) {\n return {\n serverName: parts[1],\n toolName: parts.slice(2).join('__'),\n };\n }\n }\n return null;\n}\n\n// =============================================================================\n// Action Type Mapping\n// =============================================================================\n\n/**\n * Map a tool name and input to a structured ActionType\n */\nexport function mapToolToActionType(toolName: string, input: unknown): ActionType {\n const inputObj = (input as Record<string, unknown>) || {};\n\n switch (toolName) {\n case 'Bash':\n return {\n action: 'command_run',\n command: (inputObj.command as string) || '',\n description: inputObj.description as string | undefined,\n };\n\n case 'Read':\n return {\n action: 'file_read',\n path: (inputObj.file_path as string) || '',\n offset: inputObj.offset as number | undefined,\n limit: inputObj.limit as number | undefined,\n };\n\n case 'Edit':\n return {\n action: 'file_edit',\n path: (inputObj.file_path as string) || '',\n oldString: inputObj.old_string as string | undefined,\n newString: inputObj.new_string as string | undefined,\n replaceAll: inputObj.replace_all as boolean | undefined,\n };\n\n case 'Write':\n return {\n action: 'file_write',\n path: (inputObj.file_path as string) || '',\n content: inputObj.content as string | undefined,\n };\n\n case 'Grep':\n return {\n action: 'search',\n pattern: (inputObj.pattern as string) || '',\n path: inputObj.path as string | undefined,\n glob: inputObj.glob as string | undefined,\n type: inputObj.type as string | undefined,\n };\n\n case 'Glob':\n return {\n action: 'glob',\n pattern: (inputObj.pattern as string) || '',\n path: inputObj.path as string | undefined,\n };\n\n case 'WebFetch':\n return {\n action: 'web_fetch',\n url: (inputObj.url as string) || '',\n prompt: inputObj.prompt as string | undefined,\n };\n\n case 'WebSearch':\n return {\n action: 'web_search',\n query: (inputObj.query as string) || '',\n };\n\n case 'TodoWrite': {\n const todos = (inputObj.todos as TodoItem[]) || [];\n const stats = {\n total: todos.length,\n completed: todos.filter((t) => t.status === 'completed').length,\n inProgress: todos.filter((t) => t.status === 'in_progress').length,\n pending: todos.filter((t) => t.status === 'pending').length,\n };\n return {\n action: 'todo_write',\n todos,\n stats,\n };\n }\n\n default: {\n // Check if it's an MCP tool\n const mcpParts = parseMcpToolName(toolName);\n if (mcpParts) {\n return {\n action: 'mcp_tool',\n serverName: mcpParts.serverName,\n toolName: mcpParts.toolName,\n arguments: input,\n };\n }\n\n // Generic tool\n return {\n action: 'generic_tool',\n toolName: formatToolName(toolName),\n arguments: input,\n };\n }\n }\n}\n\n// =============================================================================\n// Summary Generation\n// =============================================================================\n\n/**\n * Generate a human-readable summary for a tool call\n */\nexport function generateToolSummary(\n _toolName: string,\n _input: unknown,\n actionType: ActionType\n): string {\n switch (actionType.action) {\n case 'command_run': {\n const cmd = actionType.command;\n return cmd.length > 60 ? cmd.substring(0, 57) + '...' : cmd;\n }\n\n case 'file_read':\n return actionType.path;\n\n case 'file_edit':\n return actionType.path;\n\n case 'file_write':\n return actionType.path;\n\n case 'search':\n return `${actionType.pattern}${actionType.path ? ` in ${actionType.path}` : ''}`;\n\n case 'glob':\n return actionType.pattern;\n\n case 'web_fetch':\n return actionType.url;\n\n case 'web_search':\n return actionType.query;\n\n case 'mcp_tool':\n return `${actionType.serverName}:${actionType.toolName}`;\n\n case 'generic_tool':\n return actionType.toolName;\n\n case 'todo_write': {\n const { stats } = actionType;\n if (stats) {\n return `${stats.completed}/${stats.total} completed`;\n }\n return `${actionType.todos.length} tasks`;\n }\n\n default:\n return 'Unknown tool';\n }\n}\n\n// =============================================================================\n// Result Normalization\n// =============================================================================\n\n/**\n * Extract text content from various content formats\n */\nexport function extractTextContent(content: unknown): string {\n if (typeof content === 'string') return content;\n\n if (Array.isArray(content)) {\n return content\n .filter((item): item is { text: string } => typeof item?.text === 'string')\n .map((item) => item.text)\n .join('\\n');\n }\n\n if (content && typeof content === 'object' && 'text' in content) {\n return String((content as { text: unknown }).text);\n }\n\n return JSON.stringify(content, null, 2);\n}\n\n/**\n * Normalize tool result content to markdown or JSON format\n */\nexport function normalizeToolResult(content: unknown): ToolResult {\n if (typeof content === 'string') {\n try {\n const parsed = JSON.parse(content);\n return { type: 'json', value: parsed };\n } catch {\n return { type: 'markdown', value: content };\n }\n }\n\n if (Array.isArray(content)) {\n const texts = content\n .filter(\n (item): item is { type: 'text'; text: string } =>\n item?.type === 'text' && typeof item.text === 'string'\n )\n .map((item) => item.text);\n\n if (texts.length > 0) {\n const joined = texts.join('\\n\\n');\n try {\n return { type: 'json', value: JSON.parse(joined) };\n } catch {\n return { type: 'markdown', value: joined };\n }\n }\n }\n\n return { type: 'json', value: content };\n}\n\n/**\n * Parse command result from tool result content\n */\nexport function parseCommandResult(content: unknown): CommandRunResult {\n const output = extractTextContent(content);\n\n if (typeof content === 'string') {\n try {\n const parsed = JSON.parse(content);\n if (typeof parsed.exitCode === 'number') {\n return {\n exitCode: parsed.exitCode,\n output: parsed.output || output,\n success: parsed.exitCode === 0,\n };\n }\n } catch {\n // Not JSON, use raw output\n }\n }\n\n return {\n output,\n success: true,\n };\n}\n\n// =============================================================================\n// Tool Call Creation\n// =============================================================================\n\nexport interface ToolUseInput {\n id: string;\n name: string;\n input: unknown;\n}\n\n/**\n * Create a NormalizedToolCall from a tool_use content block\n */\nexport function createToolCall(toolUse: ToolUseInput): NormalizedToolCall {\n const actionType = mapToolToActionType(toolUse.name, toolUse.input);\n const summary = generateToolSummary(toolUse.name, toolUse.input, actionType);\n\n return {\n id: toolUse.id,\n toolName: toolUse.name,\n actionType,\n status: 'pending',\n summary,\n startedAt: new Date().toISOString(),\n };\n}\n\n/**\n * Update a NormalizedToolCall with its result\n */\nexport function updateToolCallWithResult(\n toolCall: NormalizedToolCall,\n content: unknown,\n isError?: boolean\n): NormalizedToolCall {\n const updatedToolCall = { ...toolCall };\n const actionType = { ...toolCall.actionType };\n\n updatedToolCall.status = isError ? 'failed' : 'success';\n updatedToolCall.completedAt = new Date().toISOString();\n updatedToolCall.isError = isError;\n\n if (actionType.action === 'command_run') {\n const result = parseCommandResult(content);\n (actionType as typeof actionType).result = result;\n if (result.exitCode !== undefined && result.exitCode !== 0) {\n updatedToolCall.status = 'failed';\n updatedToolCall.isError = true;\n }\n } else if (actionType.action === 'mcp_tool' || actionType.action === 'generic_tool') {\n (actionType as typeof actionType).result = normalizeToolResult(content);\n }\n\n updatedToolCall.actionType = actionType;\n return updatedToolCall;\n}\n\n// =============================================================================\n// Display Helpers\n// =============================================================================\n\n/**\n * Get display icon name for an action type\n */\nexport function getActionIcon(actionType: ActionType): string {\n switch (actionType.action) {\n case 'command_run':\n return 'terminal';\n case 'file_read':\n return 'file-text';\n case 'file_edit':\n return 'edit';\n case 'file_write':\n return 'file-plus';\n case 'search':\n return 'search';\n case 'glob':\n return 'folder-search';\n case 'web_fetch':\n return 'globe';\n case 'web_search':\n return 'search';\n case 'mcp_tool':\n return 'plug';\n case 'generic_tool':\n return 'tool';\n case 'todo_write':\n return 'list-checks';\n default:\n return 'tool';\n }\n}\n\n/**\n * Get display label for an action type\n */\nexport function getActionLabel(actionType: ActionType): string {\n switch (actionType.action) {\n case 'command_run':\n return 'Command';\n case 'file_read':\n return 'Read';\n case 'file_edit':\n return 'Edit';\n case 'file_write':\n return 'Write';\n case 'search':\n return 'Search';\n case 'glob':\n return 'Glob';\n case 'web_fetch':\n return 'Fetch';\n case 'web_search':\n return 'Search';\n case 'mcp_tool':\n return 'MCP';\n case 'generic_tool':\n return 'Tool';\n case 'todo_write':\n return 'Tasks';\n default:\n return 'Tool';\n }\n}\n\n// =============================================================================\n// Formatting Helpers\n// =============================================================================\n\n/**\n * Format a file size in bytes to a human-readable string\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/**\n * Format a timestamp to a locale time string\n */\nexport function formatTimestamp(timestamp: string): string {\n try {\n const date = new Date(timestamp);\n return date.toLocaleTimeString('en-US', {\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n });\n } catch {\n return timestamp;\n }\n}\n\n/**\n * Truncate a string with ellipsis\n */\nexport function truncate(str: string, maxLength: number): string {\n if (str.length <= maxLength) return str;\n return str.substring(0, maxLength - 3) + '...';\n}\n\n/**\n * Join class names, filtering out falsy values\n */\nexport function cn(...classes: (string | undefined | null | false)[]): string {\n return classes.filter(Boolean).join(' ');\n}\n\n// =============================================================================\n// Entry Grouping for Compact Mode\n// =============================================================================\n\n/**\n * A grouped entry that contains either a single entry or a group of consecutive tool calls\n */\nexport type GroupedEntry =\n | { type: 'single'; entry: NormalizedEntry }\n | { type: 'tool_group'; entries: NormalizedEntry[]; id: string };\n\n/**\n * Group consecutive tool call entries together based on display config.\n *\n * This is used in compact mode to merge consecutive tool calls into\n * a single ToolExecutionGroup, while keeping text content separate.\n *\n * @param entries - Array of normalized entries\n * @param config - Display configuration (uses breakEveryNToolCalls)\n * @returns Array of grouped entries\n */\nexport function groupEntriesForCompactMode(\n entries: NormalizedEntry[],\n config: ToolDisplayConfig\n): GroupedEntry[] {\n const result: GroupedEntry[] = [];\n let currentToolGroup: NormalizedEntry[] = [];\n let toolGroupCounter = 0;\n\n const flushToolGroup = () => {\n if (currentToolGroup.length > 0) {\n result.push({\n type: 'tool_group',\n entries: [...currentToolGroup],\n id: `tool-group-${toolGroupCounter++}`,\n });\n currentToolGroup = [];\n }\n };\n\n for (const entry of entries) {\n if (entry.entryType.type === 'tool_call') {\n currentToolGroup.push(entry);\n\n // Check if we should break the group\n if (\n config.breakEveryNToolCalls &&\n config.breakEveryNToolCalls > 0 &&\n currentToolGroup.length >= config.breakEveryNToolCalls\n ) {\n flushToolGroup();\n }\n } else {\n // Non-tool-call entry - flush any pending tool group\n flushToolGroup();\n result.push({ type: 'single', entry });\n }\n }\n\n // Flush any remaining tool group\n flushToolGroup();\n\n return result;\n}\n\n/**\n * Extract tool calls from a group of entries\n */\nexport function extractToolCallsFromGroup(entries: NormalizedEntry[]): NormalizedToolCall[] {\n return entries\n .filter((e): e is NormalizedEntry & { entryType: { type: 'tool_call'; toolCall: NormalizedToolCall } } =>\n e.entryType.type === 'tool_call'\n )\n .map((e) => e.entryType.toolCall);\n}\n\n// =============================================================================\n// Option Parsing\n// =============================================================================\n\n/**\n * A parsed option from assistant message content\n */\nexport interface ParsedOption {\n /** Option identifier (e.g., \"1\", \"2\", \"A\", \"B\") */\n id: string;\n /** Option title/label */\n label: string;\n /** Optional description text */\n description?: string;\n}\n\n/**\n * Result of parsing options from content\n */\nexport interface ParsedOptionsResult {\n /** Text before the options */\n preamble: string;\n /** Parsed options */\n options: ParsedOption[];\n}\n\n/**\n * Parse options from assistant message content.\n * Detects patterns like:\n * - \"Option 1: Title\" / \"Option 2: Title\"\n * - \"**Option 1:** Title\" (bold markdown)\n * - Numbered lists with descriptions\n *\n * @returns ParsedOptionsResult if options found, null otherwise\n */\nexport function parseOptionsFromContent(content: string): ParsedOptionsResult | null {\n // Pattern 1: \"Option N:\" format (with or without bold markdown)\n // Matches: \"Option 1:\", \"**Option 1:**\", \"Option 1 -\", etc.\n const optionPattern = /(?:\\*\\*)?Option\\s+(\\d+)(?:\\*\\*)?[:\\-]\\s*([^\\n]+)(?:\\n((?:(?!\\n(?:\\*\\*)?Option\\s+\\d).)*?))?/gi;\n\n const options: ParsedOption[] = [];\n let firstMatchStart = -1;\n let match: RegExpExecArray | null;\n\n // Reset regex\n optionPattern.lastIndex = 0;\n\n while ((match = optionPattern.exec(content)) !== null) {\n if (firstMatchStart === -1) {\n firstMatchStart = match.index;\n }\n\n const id = match[1] ?? '';\n const labelRaw = match[2];\n const label = labelRaw ? labelRaw.trim() : '';\n // Description is everything after the label until the next option\n const descriptionRaw = match[3];\n\n // Clean up description - remove leading/trailing whitespace and normalize\n let description: string | undefined;\n if (descriptionRaw) {\n const cleaned = descriptionRaw\n .split('\\n')\n .map(line => line.trim())\n .filter(line => line.length > 0)\n .join(' ');\n if (cleaned) {\n description = cleaned;\n }\n }\n\n options.push({ id, label, description });\n }\n\n // Need at least 2 options to be considered a valid options list\n if (options.length >= 2) {\n const preamble = firstMatchStart > 0\n ? content.substring(0, firstMatchStart).trim()\n : '';\n\n return { preamble, options };\n }\n\n // Pattern 2: Simple numbered list at end of message\n // Look for \"What would you like to do?\" or similar followed by numbered items\n const questionPattern = /^(.*?(?:what would you like to do\\??|choose an option|select.*?:|here are your options.*?:))\\s*\\n+((?:\\d+\\.\\s+.+\\n?)+)/ims;\n const questionMatch = content.match(questionPattern);\n\n if (questionMatch && questionMatch[1] && questionMatch[2]) {\n const preamble = questionMatch[1].trim();\n const listSection = questionMatch[2];\n\n // Parse numbered list items\n const listPattern = /(\\d+)\\.\\s+([^\\n]+)/g;\n let listMatch: RegExpExecArray | null;\n\n while ((listMatch = listPattern.exec(listSection)) !== null) {\n const listId = listMatch[1] ?? '';\n const listLabelRaw = listMatch[2];\n const listLabel = listLabelRaw ? listLabelRaw.trim() : '';\n options.push({\n id: listId,\n label: listLabel,\n });\n }\n\n if (options.length >= 2) {\n return { preamble, options };\n }\n }\n\n return null;\n}\n"]}
|