wave-code 0.0.4 → 0.0.6
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/README.md +2 -2
- package/dist/components/ChatInterface.d.ts.map +1 -1
- package/dist/components/ChatInterface.js +4 -24
- package/dist/components/CommandSelector.js +4 -4
- package/dist/components/DiffViewer.d.ts +1 -1
- package/dist/components/DiffViewer.d.ts.map +1 -1
- package/dist/components/DiffViewer.js +15 -15
- package/dist/components/FileSelector.js +2 -2
- package/dist/components/InputBox.d.ts.map +1 -1
- package/dist/components/InputBox.js +46 -101
- package/dist/components/Markdown.d.ts +6 -0
- package/dist/components/Markdown.d.ts.map +1 -0
- package/dist/components/Markdown.js +22 -0
- package/dist/components/MessageItem.d.ts +9 -0
- package/dist/components/MessageItem.d.ts.map +1 -0
- package/dist/components/MessageItem.js +15 -0
- package/dist/components/MessageList.d.ts +1 -1
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +33 -32
- package/dist/components/SubagentBlock.d.ts +1 -2
- package/dist/components/SubagentBlock.d.ts.map +1 -1
- package/dist/components/SubagentBlock.js +29 -20
- package/dist/components/ToolResultDisplay.js +5 -5
- package/dist/contexts/useChat.d.ts +1 -0
- package/dist/contexts/useChat.d.ts.map +1 -1
- package/dist/contexts/useChat.js +29 -2
- package/dist/hooks/useInputManager.d.ts +93 -0
- package/dist/hooks/useInputManager.d.ts.map +1 -0
- package/dist/hooks/useInputManager.js +332 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -10
- package/dist/managers/InputManager.d.ts +171 -0
- package/dist/managers/InputManager.d.ts.map +1 -0
- package/dist/managers/InputManager.js +826 -0
- package/dist/print-cli.d.ts +8 -0
- package/dist/print-cli.d.ts.map +1 -0
- package/dist/print-cli.js +128 -0
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/fileSearch.d.ts +20 -0
- package/dist/utils/fileSearch.d.ts.map +1 -0
- package/dist/utils/fileSearch.js +102 -0
- package/dist/utils/logger.js +3 -3
- package/dist/utils/usageSummary.d.ts +33 -0
- package/dist/utils/usageSummary.d.ts.map +1 -0
- package/dist/utils/usageSummary.js +154 -0
- package/package.json +10 -6
- package/src/components/ChatInterface.tsx +13 -43
- package/src/components/CommandSelector.tsx +5 -5
- package/src/components/DiffViewer.tsx +18 -16
- package/src/components/FileSelector.tsx +2 -2
- package/src/components/InputBox.tsx +78 -169
- package/src/components/Markdown.tsx +29 -0
- package/src/components/MessageItem.tsx +104 -0
- package/src/components/MessageList.tsx +142 -198
- package/src/components/SubagentBlock.tsx +56 -73
- package/src/components/ToolResultDisplay.tsx +6 -6
- package/src/contexts/useChat.tsx +34 -2
- package/src/hooks/useInputManager.ts +461 -0
- package/src/index.ts +20 -10
- package/src/managers/InputManager.ts +1132 -0
- package/src/print-cli.ts +160 -0
- package/src/utils/constants.ts +1 -1
- package/src/utils/fileSearch.ts +133 -0
- package/src/utils/logger.ts +3 -3
- package/src/utils/usageSummary.ts +234 -0
- package/dist/hooks/useBashHistorySelector.d.ts +0 -15
- package/dist/hooks/useBashHistorySelector.d.ts.map +0 -1
- package/dist/hooks/useBashHistorySelector.js +0 -61
- package/dist/hooks/useCommandSelector.d.ts +0 -24
- package/dist/hooks/useCommandSelector.d.ts.map +0 -1
- package/dist/hooks/useCommandSelector.js +0 -98
- package/dist/hooks/useFileSelector.d.ts +0 -16
- package/dist/hooks/useFileSelector.d.ts.map +0 -1
- package/dist/hooks/useFileSelector.js +0 -174
- package/dist/hooks/useImageManager.d.ts +0 -13
- package/dist/hooks/useImageManager.d.ts.map +0 -1
- package/dist/hooks/useImageManager.js +0 -46
- package/dist/hooks/useInputHistory.d.ts +0 -11
- package/dist/hooks/useInputHistory.d.ts.map +0 -1
- package/dist/hooks/useInputHistory.js +0 -64
- package/dist/hooks/useInputKeyboardHandler.d.ts +0 -83
- package/dist/hooks/useInputKeyboardHandler.d.ts.map +0 -1
- package/dist/hooks/useInputKeyboardHandler.js +0 -507
- package/dist/hooks/useInputState.d.ts +0 -14
- package/dist/hooks/useInputState.d.ts.map +0 -1
- package/dist/hooks/useInputState.js +0 -57
- package/dist/hooks/useMemoryTypeSelector.d.ts +0 -9
- package/dist/hooks/useMemoryTypeSelector.d.ts.map +0 -1
- package/dist/hooks/useMemoryTypeSelector.js +0 -27
- package/dist/plain-cli.d.ts +0 -7
- package/dist/plain-cli.d.ts.map +0 -1
- package/dist/plain-cli.js +0 -44
- package/src/hooks/useBashHistorySelector.ts +0 -77
- package/src/hooks/useCommandSelector.ts +0 -131
- package/src/hooks/useFileSelector.ts +0 -227
- package/src/hooks/useImageManager.ts +0 -64
- package/src/hooks/useInputHistory.ts +0 -74
- package/src/hooks/useInputKeyboardHandler.ts +0 -778
- package/src/hooks/useInputState.ts +0 -66
- package/src/hooks/useMemoryTypeSelector.ts +0 -40
- package/src/plain-cli.ts +0 -60
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface PrintCliOptions {
|
|
2
|
+
restoreSessionId?: string;
|
|
3
|
+
continueLastSession?: boolean;
|
|
4
|
+
message?: string;
|
|
5
|
+
showStats?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function startPrintCli(options: PrintCliOptions): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=print-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"print-cli.d.ts","sourceRoot":"","sources":["../src/print-cli.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAgBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAuI3E"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Agent } from "wave-agent-sdk";
|
|
2
|
+
import { displayUsageSummary } from "./utils/usageSummary.js";
|
|
3
|
+
function displayTimingInfo(startTime, showStats) {
|
|
4
|
+
// Skip timing info in test environment or if stats are disabled
|
|
5
|
+
if (process.env.NODE_ENV === "test" || process.env.VITEST || !showStats) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const endTime = new Date();
|
|
9
|
+
const duration = endTime.getTime() - startTime.getTime();
|
|
10
|
+
process.stdout.write(`\n\n📅 Start time: ${startTime.toISOString()}\n`);
|
|
11
|
+
process.stdout.write(`📅 End time: ${endTime.toISOString()}\n`);
|
|
12
|
+
process.stdout.write(`⏱️ Duration: ${duration}ms\n`);
|
|
13
|
+
}
|
|
14
|
+
export async function startPrintCli(options) {
|
|
15
|
+
const startTime = new Date();
|
|
16
|
+
const { restoreSessionId, continueLastSession, message, showStats = false, } = options;
|
|
17
|
+
if ((!message || message.trim() === "") &&
|
|
18
|
+
!continueLastSession &&
|
|
19
|
+
!restoreSessionId) {
|
|
20
|
+
console.error("Print mode requires a message: use --print 'your message' or -p 'your message'");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
let agent;
|
|
24
|
+
// Setup callbacks for agent
|
|
25
|
+
const callbacks = {
|
|
26
|
+
onAssistantMessageAdded: () => {
|
|
27
|
+
// Assistant message started - no content to output yet
|
|
28
|
+
process.stdout.write("\n");
|
|
29
|
+
},
|
|
30
|
+
onAssistantContentUpdated: (chunk) => {
|
|
31
|
+
// FR-001: Stream content updates for real-time display - output only the new chunk
|
|
32
|
+
process.stdout.write(chunk);
|
|
33
|
+
},
|
|
34
|
+
// Tool block callback - display tool name when tool starts
|
|
35
|
+
onToolBlockUpdated: (params) => {
|
|
36
|
+
// Print tool name only during 'running' stage (happens once per tool call)
|
|
37
|
+
if (params.stage === "running" && params.name) {
|
|
38
|
+
process.stdout.write(`\n🔧 ${params.name}`);
|
|
39
|
+
if (params.compactParams) {
|
|
40
|
+
process.stdout.write(` ${params.compactParams}`);
|
|
41
|
+
}
|
|
42
|
+
process.stdout.write(`\n`);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
// Subagent block callbacks
|
|
46
|
+
onSubAgentBlockAdded: (subagentId, parameters) => {
|
|
47
|
+
// Display subagent creation with indentation
|
|
48
|
+
process.stdout.write(`\n🤖 Subagent [${parameters.subagent_type}]: ${parameters.description}\n`);
|
|
49
|
+
},
|
|
50
|
+
onSubAgentBlockUpdated: (subagentId, status) => {
|
|
51
|
+
// Display subagent status updates
|
|
52
|
+
const statusIconMap = {
|
|
53
|
+
active: "🔄",
|
|
54
|
+
completed: "✅",
|
|
55
|
+
error: "❌",
|
|
56
|
+
aborted: "⚠️",
|
|
57
|
+
};
|
|
58
|
+
const statusIcon = statusIconMap[status] ?? "🔄";
|
|
59
|
+
process.stdout.write(` ${statusIcon} Subagent status: ${status}\n`);
|
|
60
|
+
},
|
|
61
|
+
// Subagent message callbacks
|
|
62
|
+
onSubagentAssistantMessageAdded: () => {
|
|
63
|
+
// Subagent assistant message started - add indentation
|
|
64
|
+
process.stdout.write("\n ");
|
|
65
|
+
},
|
|
66
|
+
onSubagentAssistantContentUpdated: (_subagentId, chunk) => {
|
|
67
|
+
// Stream subagent content with indentation - output only the new chunk
|
|
68
|
+
process.stdout.write(chunk);
|
|
69
|
+
},
|
|
70
|
+
onSubagentUserMessageAdded: (_subagentId, params) => {
|
|
71
|
+
// Display subagent user messages with indentation
|
|
72
|
+
process.stdout.write(`\n 👤 User: ${params.content}\n`);
|
|
73
|
+
},
|
|
74
|
+
// Error block callback
|
|
75
|
+
onErrorBlockAdded: (error) => {
|
|
76
|
+
// Display error blocks with distinct formatting
|
|
77
|
+
process.stdout.write(`\n❌ Error: ${error}\n`);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
// Initialize agent
|
|
82
|
+
agent = await Agent.create({
|
|
83
|
+
callbacks,
|
|
84
|
+
restoreSessionId,
|
|
85
|
+
continueLastSession,
|
|
86
|
+
});
|
|
87
|
+
// Send message if provided and not empty
|
|
88
|
+
if (message && message.trim() !== "") {
|
|
89
|
+
await agent.sendMessage(message);
|
|
90
|
+
}
|
|
91
|
+
// Display usage summary before exit
|
|
92
|
+
if (showStats) {
|
|
93
|
+
try {
|
|
94
|
+
const usages = agent.usages;
|
|
95
|
+
const sessionFilePath = agent.sessionFilePath;
|
|
96
|
+
displayUsageSummary(usages, sessionFilePath);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Silently ignore usage summary errors
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Display timing information
|
|
103
|
+
displayTimingInfo(startTime, showStats);
|
|
104
|
+
// Destroy agent and exit after sendMessage completes
|
|
105
|
+
await agent.destroy();
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error("Failed to send message:", error);
|
|
110
|
+
if (agent) {
|
|
111
|
+
// Display usage summary even on error
|
|
112
|
+
if (showStats) {
|
|
113
|
+
try {
|
|
114
|
+
const usages = agent.usages;
|
|
115
|
+
const sessionFilePath = agent.sessionFilePath;
|
|
116
|
+
displayUsageSummary(usages, sessionFilePath);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Silently ignore usage summary errors
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Display timing information even on error
|
|
123
|
+
displayTimingInfo(startTime, showStats);
|
|
124
|
+
await agent.destroy();
|
|
125
|
+
}
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
}
|
package/dist/utils/constants.js
CHANGED
|
@@ -15,4 +15,4 @@ export const LOG_FILE = path.join(DATA_DIRECTORY, "app.log");
|
|
|
15
15
|
/**
|
|
16
16
|
* Pagination related constants
|
|
17
17
|
*/
|
|
18
|
-
export const MESSAGES_PER_PAGE =
|
|
18
|
+
export const MESSAGES_PER_PAGE = 15; // Number of messages displayed per page
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface FileItem {
|
|
2
|
+
path: string;
|
|
3
|
+
type: "file" | "directory";
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Check if path is a directory
|
|
7
|
+
*/
|
|
8
|
+
export declare const isDirectory: (filePath: string) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Convert string paths to FileItem objects
|
|
11
|
+
*/
|
|
12
|
+
export declare const convertToFileItems: (paths: string[]) => FileItem[];
|
|
13
|
+
/**
|
|
14
|
+
* Search files and directories using glob patterns
|
|
15
|
+
*/
|
|
16
|
+
export declare const searchFiles: (query: string, options?: {
|
|
17
|
+
maxResults?: number;
|
|
18
|
+
workingDirectory?: string;
|
|
19
|
+
}) => Promise<FileItem[]>;
|
|
20
|
+
//# sourceMappingURL=fileSearch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileSearch.d.ts","sourceRoot":"","sources":["../../src/utils/fileSearch.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;CAC5B;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,KAAG,OAS9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,EAAE,KAAG,QAAQ,EAK5D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GACtB,OAAO,MAAM,EACb,UAAU;IACR,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,KACA,OAAO,CAAC,QAAQ,EAAE,CAyFpB,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import { getGlobIgnorePatterns } from "wave-agent-sdk";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
/**
|
|
6
|
+
* Check if path is a directory
|
|
7
|
+
*/
|
|
8
|
+
export const isDirectory = (filePath) => {
|
|
9
|
+
try {
|
|
10
|
+
const fullPath = path.isAbsolute(filePath)
|
|
11
|
+
? filePath
|
|
12
|
+
: path.join(process.cwd(), filePath);
|
|
13
|
+
return fs.statSync(fullPath).isDirectory();
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Convert string paths to FileItem objects
|
|
21
|
+
*/
|
|
22
|
+
export const convertToFileItems = (paths) => {
|
|
23
|
+
return paths.map((filePath) => ({
|
|
24
|
+
path: filePath,
|
|
25
|
+
type: isDirectory(filePath) ? "directory" : "file",
|
|
26
|
+
}));
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Search files and directories using glob patterns
|
|
30
|
+
*/
|
|
31
|
+
export const searchFiles = async (query, options) => {
|
|
32
|
+
const { maxResults = 10, workingDirectory = process.cwd() } = options || {};
|
|
33
|
+
try {
|
|
34
|
+
let files = [];
|
|
35
|
+
let directories = [];
|
|
36
|
+
const globOptions = {
|
|
37
|
+
ignore: getGlobIgnorePatterns(workingDirectory),
|
|
38
|
+
maxDepth: 10,
|
|
39
|
+
nocase: true, // Case insensitive
|
|
40
|
+
dot: true, // Include hidden files and directories
|
|
41
|
+
cwd: workingDirectory, // Specify search root directory
|
|
42
|
+
};
|
|
43
|
+
if (!query.trim()) {
|
|
44
|
+
// When query is empty, show some common file types and directories
|
|
45
|
+
const commonPatterns = [
|
|
46
|
+
"**/*.ts",
|
|
47
|
+
"**/*.tsx",
|
|
48
|
+
"**/*.js",
|
|
49
|
+
"**/*.jsx",
|
|
50
|
+
"**/*.json",
|
|
51
|
+
];
|
|
52
|
+
// Search files
|
|
53
|
+
const filePromises = commonPatterns.map((pattern) => glob(pattern, { ...globOptions, nodir: true }));
|
|
54
|
+
// Search directories (only search first level to avoid too many results)
|
|
55
|
+
const dirPromises = [glob("*/", { ...globOptions, maxDepth: 1 })];
|
|
56
|
+
const fileResults = await Promise.all(filePromises);
|
|
57
|
+
const dirResults = await Promise.all(dirPromises);
|
|
58
|
+
files = fileResults.flat();
|
|
59
|
+
directories = dirResults.flat().map((dir) => {
|
|
60
|
+
// glob returns string type paths, remove trailing slash
|
|
61
|
+
return String(dir).replace(/\/$/, "");
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Build multiple glob patterns to support more flexible search
|
|
66
|
+
const filePatterns = [
|
|
67
|
+
// Match files with filenames containing query
|
|
68
|
+
`**/*${query}*`,
|
|
69
|
+
// Match files with query in path (match directory names)
|
|
70
|
+
`**/${query}*/**/*`,
|
|
71
|
+
];
|
|
72
|
+
const dirPatterns = [
|
|
73
|
+
// Match directory names containing query
|
|
74
|
+
`**/*${query}*/`,
|
|
75
|
+
// Match directories containing query in path
|
|
76
|
+
`**/${query}*/`,
|
|
77
|
+
];
|
|
78
|
+
// Search files
|
|
79
|
+
const filePromises = filePatterns.map((pattern) => glob(pattern, { ...globOptions, nodir: true }));
|
|
80
|
+
// Search directories
|
|
81
|
+
const dirPromises = dirPatterns.map((pattern) => glob(pattern, { ...globOptions, nodir: false }));
|
|
82
|
+
const fileResults = await Promise.all(filePromises);
|
|
83
|
+
const dirResults = await Promise.all(dirPromises);
|
|
84
|
+
files = fileResults.flat();
|
|
85
|
+
directories = dirResults.flat().map((dir) => {
|
|
86
|
+
// glob returns string type paths, remove trailing slash
|
|
87
|
+
return String(dir).replace(/\/$/, "");
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Deduplicate and merge files and directories
|
|
91
|
+
const uniqueFiles = Array.from(new Set(files));
|
|
92
|
+
const uniqueDirectories = Array.from(new Set(directories));
|
|
93
|
+
const allPaths = [...uniqueDirectories, ...uniqueFiles]; // Directories first
|
|
94
|
+
// Limit to maximum results and convert to FileItem
|
|
95
|
+
const fileItems = convertToFileItems(allPaths.slice(0, maxResults));
|
|
96
|
+
return fileItems;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error("Glob search error:", error);
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
};
|
package/dist/utils/logger.js
CHANGED
|
@@ -216,7 +216,7 @@ const truncateLogFileIfNeeded = (config) => {
|
|
|
216
216
|
fs.writeFileSync(logFile, truncatedContent);
|
|
217
217
|
// Record truncation operation
|
|
218
218
|
const removedLines = lines.length - keepLines;
|
|
219
|
-
logger.
|
|
219
|
+
logger.debug(`Log file truncated: removed ${removedLines} lines, kept last ${keepLines} lines`);
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
catch (error) {
|
|
@@ -235,11 +235,11 @@ export const cleanupLogs = async (customConfig) => {
|
|
|
235
235
|
return;
|
|
236
236
|
}
|
|
237
237
|
const config = { ...getCleanupConfig(), ...customConfig };
|
|
238
|
-
logger.
|
|
238
|
+
logger.debug("Starting log cleanup...", {
|
|
239
239
|
maxFileSize: config.maxFileSize,
|
|
240
240
|
keepLines: config.keepLines,
|
|
241
241
|
});
|
|
242
242
|
// Truncate current log file (if needed)
|
|
243
243
|
truncateLogFileIfNeeded(config);
|
|
244
|
-
logger.
|
|
244
|
+
logger.debug("Log cleanup completed");
|
|
245
245
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Usage } from "wave-agent-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Token summary by model
|
|
4
|
+
*/
|
|
5
|
+
export interface TokenSummary {
|
|
6
|
+
model: string;
|
|
7
|
+
prompt_tokens: number;
|
|
8
|
+
completion_tokens: number;
|
|
9
|
+
total_tokens: number;
|
|
10
|
+
operations: {
|
|
11
|
+
agent_calls: number;
|
|
12
|
+
compressions: number;
|
|
13
|
+
};
|
|
14
|
+
cache_read_input_tokens?: number;
|
|
15
|
+
cache_creation_input_tokens?: number;
|
|
16
|
+
cache_creation?: {
|
|
17
|
+
ephemeral_5m_input_tokens: number;
|
|
18
|
+
ephemeral_1h_input_tokens: number;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Calculate token usage summary by model from usage array
|
|
23
|
+
* @param usages Array of usage data from agent operations
|
|
24
|
+
* @returns Array of token summaries grouped by model
|
|
25
|
+
*/
|
|
26
|
+
export declare function calculateTokenSummary(usages: Usage[]): Record<string, TokenSummary>;
|
|
27
|
+
/**
|
|
28
|
+
* Display usage summary in a formatted way
|
|
29
|
+
* @param usages Array of usage data from agent operations
|
|
30
|
+
* @param sessionFilePath Optional session file path to display
|
|
31
|
+
*/
|
|
32
|
+
export declare function displayUsageSummary(usages: Usage[], sessionFilePath?: string): void;
|
|
33
|
+
//# sourceMappingURL=usageSummary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usageSummary.d.ts","sourceRoot":"","sources":["../../src/utils/usageSummary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE;QACV,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IAEF,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,cAAc,CAAC,EAAE;QACf,yBAAyB,EAAE,MAAM,CAAC;QAClC,yBAAyB,EAAE,MAAM,CAAC;KACnC,CAAC;CACH;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,KAAK,EAAE,GACd,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAyE9B;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,KAAK,EAAE,EACf,eAAe,CAAC,EAAE,MAAM,GACvB,IAAI,CAwHN"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate token usage summary by model from usage array
|
|
3
|
+
* @param usages Array of usage data from agent operations
|
|
4
|
+
* @returns Array of token summaries grouped by model
|
|
5
|
+
*/
|
|
6
|
+
export function calculateTokenSummary(usages) {
|
|
7
|
+
const summaryMap = new Map();
|
|
8
|
+
for (const usage of usages) {
|
|
9
|
+
const model = usage.model || "unknown";
|
|
10
|
+
if (!summaryMap.has(model)) {
|
|
11
|
+
summaryMap.set(model, {
|
|
12
|
+
model,
|
|
13
|
+
prompt_tokens: 0,
|
|
14
|
+
completion_tokens: 0,
|
|
15
|
+
total_tokens: 0,
|
|
16
|
+
operations: {
|
|
17
|
+
agent_calls: 0,
|
|
18
|
+
compressions: 0,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const summary = summaryMap.get(model);
|
|
23
|
+
summary.prompt_tokens += usage.prompt_tokens;
|
|
24
|
+
summary.completion_tokens += usage.completion_tokens;
|
|
25
|
+
summary.total_tokens += usage.total_tokens;
|
|
26
|
+
// Handle cache tokens if present and non-zero
|
|
27
|
+
if (usage.cache_read_input_tokens && usage.cache_read_input_tokens > 0) {
|
|
28
|
+
summary.cache_read_input_tokens =
|
|
29
|
+
(summary.cache_read_input_tokens || 0) + usage.cache_read_input_tokens;
|
|
30
|
+
}
|
|
31
|
+
if (usage.cache_creation_input_tokens &&
|
|
32
|
+
usage.cache_creation_input_tokens > 0) {
|
|
33
|
+
summary.cache_creation_input_tokens =
|
|
34
|
+
(summary.cache_creation_input_tokens || 0) +
|
|
35
|
+
usage.cache_creation_input_tokens;
|
|
36
|
+
}
|
|
37
|
+
if (usage.cache_creation &&
|
|
38
|
+
(usage.cache_creation.ephemeral_5m_input_tokens > 0 ||
|
|
39
|
+
usage.cache_creation.ephemeral_1h_input_tokens > 0)) {
|
|
40
|
+
if (!summary.cache_creation) {
|
|
41
|
+
summary.cache_creation = {
|
|
42
|
+
ephemeral_5m_input_tokens: 0,
|
|
43
|
+
ephemeral_1h_input_tokens: 0,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
summary.cache_creation.ephemeral_5m_input_tokens +=
|
|
47
|
+
usage.cache_creation.ephemeral_5m_input_tokens || 0;
|
|
48
|
+
summary.cache_creation.ephemeral_1h_input_tokens +=
|
|
49
|
+
usage.cache_creation.ephemeral_1h_input_tokens || 0;
|
|
50
|
+
}
|
|
51
|
+
// Track operation types
|
|
52
|
+
if (usage.operation_type === "agent") {
|
|
53
|
+
summary.operations.agent_calls += 1;
|
|
54
|
+
}
|
|
55
|
+
else if (usage.operation_type === "compress") {
|
|
56
|
+
summary.operations.compressions += 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Convert Map to Record and sort by total tokens
|
|
60
|
+
const result = {};
|
|
61
|
+
const sortedEntries = Array.from(summaryMap.entries()).sort((a, b) => b[1].total_tokens - a[1].total_tokens);
|
|
62
|
+
for (const [model, summary] of sortedEntries) {
|
|
63
|
+
result[model] = summary;
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Display usage summary in a formatted way
|
|
69
|
+
* @param usages Array of usage data from agent operations
|
|
70
|
+
* @param sessionFilePath Optional session file path to display
|
|
71
|
+
*/
|
|
72
|
+
export function displayUsageSummary(usages, sessionFilePath) {
|
|
73
|
+
if (usages.length === 0) {
|
|
74
|
+
return; // No usage data to display
|
|
75
|
+
}
|
|
76
|
+
const summaries = calculateTokenSummary(usages);
|
|
77
|
+
console.log("\nToken Usage Summary:");
|
|
78
|
+
console.log("==================");
|
|
79
|
+
if (sessionFilePath) {
|
|
80
|
+
console.log(`Session: ${sessionFilePath}`);
|
|
81
|
+
}
|
|
82
|
+
let totalPrompt = 0;
|
|
83
|
+
let totalCompletion = 0;
|
|
84
|
+
let totalTokens = 0;
|
|
85
|
+
let totalAgentCalls = 0;
|
|
86
|
+
let totalCompressions = 0;
|
|
87
|
+
let totalCacheRead = 0;
|
|
88
|
+
let totalCacheCreation = 0;
|
|
89
|
+
let totalCache5m = 0;
|
|
90
|
+
let totalCache1h = 0;
|
|
91
|
+
let hasCacheData = false;
|
|
92
|
+
for (const [, summary] of Object.entries(summaries)) {
|
|
93
|
+
console.log(`Model: ${summary.model}`);
|
|
94
|
+
console.log(` Prompt tokens: ${summary.prompt_tokens.toLocaleString()}`);
|
|
95
|
+
console.log(` Completion tokens: ${summary.completion_tokens.toLocaleString()}`);
|
|
96
|
+
console.log(` Total tokens: ${summary.total_tokens.toLocaleString()}`);
|
|
97
|
+
// Display cache information if available
|
|
98
|
+
if (summary.cache_read_input_tokens ||
|
|
99
|
+
summary.cache_creation_input_tokens ||
|
|
100
|
+
summary.cache_creation) {
|
|
101
|
+
hasCacheData = true;
|
|
102
|
+
console.log(" Cache Usage:");
|
|
103
|
+
if (summary.cache_read_input_tokens &&
|
|
104
|
+
summary.cache_read_input_tokens > 0) {
|
|
105
|
+
console.log(` Read from cache: ${summary.cache_read_input_tokens.toLocaleString()} tokens`);
|
|
106
|
+
totalCacheRead += summary.cache_read_input_tokens;
|
|
107
|
+
}
|
|
108
|
+
if (summary.cache_creation_input_tokens &&
|
|
109
|
+
summary.cache_creation_input_tokens > 0) {
|
|
110
|
+
console.log(` Created cache: ${summary.cache_creation_input_tokens.toLocaleString()} tokens`);
|
|
111
|
+
totalCacheCreation += summary.cache_creation_input_tokens;
|
|
112
|
+
}
|
|
113
|
+
if (summary.cache_creation) {
|
|
114
|
+
if (summary.cache_creation.ephemeral_5m_input_tokens > 0) {
|
|
115
|
+
console.log(` 5m cache: ${summary.cache_creation.ephemeral_5m_input_tokens.toLocaleString()} tokens`);
|
|
116
|
+
totalCache5m += summary.cache_creation.ephemeral_5m_input_tokens;
|
|
117
|
+
}
|
|
118
|
+
if (summary.cache_creation.ephemeral_1h_input_tokens > 0) {
|
|
119
|
+
console.log(` 1h cache: ${summary.cache_creation.ephemeral_1h_input_tokens.toLocaleString()} tokens`);
|
|
120
|
+
totalCache1h += summary.cache_creation.ephemeral_1h_input_tokens;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
console.log(` Operations: ${summary.operations.agent_calls} agent calls, ${summary.operations.compressions} compressions`);
|
|
125
|
+
console.log();
|
|
126
|
+
totalPrompt += summary.prompt_tokens;
|
|
127
|
+
totalCompletion += summary.completion_tokens;
|
|
128
|
+
totalTokens += summary.total_tokens;
|
|
129
|
+
totalAgentCalls += summary.operations.agent_calls;
|
|
130
|
+
totalCompressions += summary.operations.compressions;
|
|
131
|
+
}
|
|
132
|
+
if (Object.keys(summaries).length > 1) {
|
|
133
|
+
console.log("Overall Total:");
|
|
134
|
+
console.log(` Prompt tokens: ${totalPrompt.toLocaleString()}`);
|
|
135
|
+
console.log(` Completion tokens: ${totalCompletion.toLocaleString()}`);
|
|
136
|
+
console.log(` Total tokens: ${totalTokens.toLocaleString()}`);
|
|
137
|
+
if (hasCacheData) {
|
|
138
|
+
console.log(" Cache Usage:");
|
|
139
|
+
if (totalCacheRead > 0) {
|
|
140
|
+
console.log(` Read from cache: ${totalCacheRead.toLocaleString()} tokens`);
|
|
141
|
+
}
|
|
142
|
+
if (totalCacheCreation > 0) {
|
|
143
|
+
console.log(` Created cache: ${totalCacheCreation.toLocaleString()} tokens`);
|
|
144
|
+
}
|
|
145
|
+
if (totalCache5m > 0) {
|
|
146
|
+
console.log(` 5m cache: ${totalCache5m.toLocaleString()} tokens`);
|
|
147
|
+
}
|
|
148
|
+
if (totalCache1h > 0) {
|
|
149
|
+
console.log(` 1h cache: ${totalCache1h.toLocaleString()} tokens`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
console.log(` Operations: ${totalAgentCalls} agent calls, ${totalCompressions} compressions`);
|
|
153
|
+
}
|
|
154
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-code",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "CLI-based code assistant powered by AI, built with React and Ink",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -24,14 +24,18 @@
|
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"
|
|
28
|
-
"react": "^19.1.0",
|
|
29
|
-
"yargs": "^17.7.2",
|
|
27
|
+
"chalk": "^5.6.2",
|
|
30
28
|
"diff": "^8.0.2",
|
|
31
29
|
"glob": "^11.0.3",
|
|
32
|
-
"
|
|
30
|
+
"ink": "^6.5.1",
|
|
31
|
+
"marked": "^11.2.0",
|
|
32
|
+
"marked-terminal": "^7.3.0",
|
|
33
|
+
"react": "^19.1.0",
|
|
34
|
+
"yargs": "^17.7.2",
|
|
35
|
+
"wave-agent-sdk": "0.0.8"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
38
|
+
"@types/marked-terminal": "^6.1.1",
|
|
35
39
|
"@types/react": "^19.1.8",
|
|
36
40
|
"@types/yargs": "^17.0.0",
|
|
37
41
|
"eslint-plugin-react": "^7.37.5",
|
|
@@ -52,7 +56,7 @@
|
|
|
52
56
|
"scripts": {
|
|
53
57
|
"build": "rimraf dist && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
|
|
54
58
|
"type-check": "tsc --noEmit --incremental",
|
|
55
|
-
"
|
|
59
|
+
"watch": "tsc -p tsconfig.build.json --watch & tsc-alias -p tsconfig.build.json --watch",
|
|
56
60
|
"test": "vitest run",
|
|
57
61
|
"lint": "eslint --cache",
|
|
58
62
|
"format": "prettier --write ."
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import { Box } from "ink";
|
|
3
3
|
import { MessageList } from "./MessageList.js";
|
|
4
4
|
import { InputBox } from "./InputBox.js";
|
|
5
5
|
import { useChat } from "../contexts/useChat.js";
|
|
6
|
-
import type { Message } from "wave-agent-sdk";
|
|
7
6
|
|
|
8
7
|
export const ChatInterface: React.FC = () => {
|
|
9
8
|
const {
|
|
@@ -19,54 +18,25 @@ export const ChatInterface: React.FC = () => {
|
|
|
19
18
|
connectMcpServer,
|
|
20
19
|
disconnectMcpServer,
|
|
21
20
|
isExpanded,
|
|
21
|
+
sessionId,
|
|
22
22
|
latestTotalTokens,
|
|
23
23
|
slashCommands,
|
|
24
24
|
hasSlashCommand,
|
|
25
25
|
} = useChat();
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
const expandedMessagesRef = useRef<Message[]>([]);
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
// Only sync when collapsed
|
|
32
|
-
if (!isExpanded) {
|
|
33
|
-
expandedMessagesRef.current = messages.map((message, index) => {
|
|
34
|
-
// If it's the last message, deep copy its blocks
|
|
35
|
-
if (index === messages.length - 1) {
|
|
36
|
-
return {
|
|
37
|
-
...message,
|
|
38
|
-
blocks: message.blocks.map((block) => ({ ...block })),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return message;
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}, [isExpanded, messages]);
|
|
27
|
+
if (!sessionId) return null;
|
|
45
28
|
|
|
46
29
|
return (
|
|
47
|
-
<Box flexDirection="column" height="100%">
|
|
48
|
-
<
|
|
49
|
-
{
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
/>
|
|
58
|
-
) : (
|
|
59
|
-
// Normal mode uses real-time state
|
|
60
|
-
<MessageList
|
|
61
|
-
messages={messages}
|
|
62
|
-
isLoading={isLoading}
|
|
63
|
-
isCommandRunning={isCommandRunning}
|
|
64
|
-
isCompressing={isCompressing}
|
|
65
|
-
latestTotalTokens={latestTotalTokens}
|
|
66
|
-
isExpanded={false}
|
|
67
|
-
/>
|
|
68
|
-
)}
|
|
69
|
-
</Box>
|
|
30
|
+
<Box flexDirection="column" height="100%" paddingY={1}>
|
|
31
|
+
<MessageList
|
|
32
|
+
messages={messages}
|
|
33
|
+
isLoading={isLoading}
|
|
34
|
+
isCommandRunning={isCommandRunning}
|
|
35
|
+
isCompressing={isCompressing}
|
|
36
|
+
latestTotalTokens={latestTotalTokens}
|
|
37
|
+
isExpanded={isExpanded}
|
|
38
|
+
key={String(isExpanded) + sessionId}
|
|
39
|
+
/>
|
|
70
40
|
|
|
71
41
|
{!isExpanded && (
|
|
72
42
|
<InputBox
|
|
@@ -41,7 +41,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
|
|
|
41
41
|
const filteredCommands = allCommands.filter(
|
|
42
42
|
(command) =>
|
|
43
43
|
!searchQuery ||
|
|
44
|
-
command.
|
|
44
|
+
command.id.toLowerCase().includes(searchQuery.toLowerCase()),
|
|
45
45
|
);
|
|
46
46
|
|
|
47
47
|
useInput((input, key) => {
|
|
@@ -50,7 +50,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
|
|
|
50
50
|
filteredCommands.length > 0 &&
|
|
51
51
|
selectedIndex < filteredCommands.length
|
|
52
52
|
) {
|
|
53
|
-
const selectedCommand = filteredCommands[selectedIndex].
|
|
53
|
+
const selectedCommand = filteredCommands[selectedIndex].id;
|
|
54
54
|
onSelect(selectedCommand);
|
|
55
55
|
}
|
|
56
56
|
return;
|
|
@@ -61,7 +61,7 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
|
|
|
61
61
|
filteredCommands.length > 0 &&
|
|
62
62
|
selectedIndex < filteredCommands.length
|
|
63
63
|
) {
|
|
64
|
-
const selectedCommand = filteredCommands[selectedIndex].
|
|
64
|
+
const selectedCommand = filteredCommands[selectedIndex].id;
|
|
65
65
|
onInsert(selectedCommand);
|
|
66
66
|
}
|
|
67
67
|
return;
|
|
@@ -116,12 +116,12 @@ export const CommandSelector: React.FC<CommandSelectorProps> = ({
|
|
|
116
116
|
</Box>
|
|
117
117
|
|
|
118
118
|
{filteredCommands.map((command, index) => (
|
|
119
|
-
<Box key={command.
|
|
119
|
+
<Box key={command.id} flexDirection="column">
|
|
120
120
|
<Text
|
|
121
121
|
color={index === selectedIndex ? "black" : "white"}
|
|
122
122
|
backgroundColor={index === selectedIndex ? "magenta" : undefined}
|
|
123
123
|
>
|
|
124
|
-
{index === selectedIndex ? "▶ " : " "}/{command.
|
|
124
|
+
{index === selectedIndex ? "▶ " : " "}/{command.id}
|
|
125
125
|
</Text>
|
|
126
126
|
{index === selectedIndex && (
|
|
127
127
|
<Box marginLeft={4}>
|