wave-agent-sdk 0.0.3 → 0.0.5

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.
Files changed (53) hide show
  1. package/dist/agent.d.ts +61 -4
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +84 -8
  4. package/dist/hooks/manager.d.ts.map +1 -1
  5. package/dist/hooks/manager.js +1 -1
  6. package/dist/managers/aiManager.d.ts +2 -1
  7. package/dist/managers/aiManager.d.ts.map +1 -1
  8. package/dist/managers/aiManager.js +37 -6
  9. package/dist/managers/mcpManager.js +5 -5
  10. package/dist/managers/messageManager.d.ts +13 -2
  11. package/dist/managers/messageManager.d.ts.map +1 -1
  12. package/dist/managers/messageManager.js +20 -7
  13. package/dist/managers/skillManager.js +3 -3
  14. package/dist/managers/slashCommandManager.js +1 -1
  15. package/dist/managers/subagentManager.d.ts +3 -1
  16. package/dist/managers/subagentManager.d.ts.map +1 -1
  17. package/dist/managers/subagentManager.js +5 -1
  18. package/dist/services/aiService.d.ts +9 -1
  19. package/dist/services/aiService.d.ts.map +1 -1
  20. package/dist/services/aiService.js +17 -3
  21. package/dist/services/memory.js +5 -5
  22. package/dist/services/session.d.ts +64 -15
  23. package/dist/services/session.d.ts.map +1 -1
  24. package/dist/services/session.js +80 -30
  25. package/dist/tools/bashTool.js +2 -2
  26. package/dist/tools/deleteFileTool.js +1 -1
  27. package/dist/tools/editTool.js +1 -1
  28. package/dist/tools/multiEditTool.js +2 -2
  29. package/dist/tools/writeTool.js +1 -1
  30. package/dist/types.d.ts +12 -0
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/utils/messageOperations.d.ts +2 -2
  33. package/dist/utils/messageOperations.d.ts.map +1 -1
  34. package/dist/utils/messageOperations.js +2 -1
  35. package/package.json +1 -1
  36. package/src/agent.ts +103 -9
  37. package/src/hooks/manager.ts +4 -2
  38. package/src/managers/aiManager.ts +43 -7
  39. package/src/managers/mcpManager.ts +5 -5
  40. package/src/managers/messageManager.ts +34 -5
  41. package/src/managers/skillManager.ts +3 -3
  42. package/src/managers/slashCommandManager.ts +1 -1
  43. package/src/managers/subagentManager.ts +14 -2
  44. package/src/services/aiService.ts +29 -6
  45. package/src/services/memory.ts +5 -5
  46. package/src/services/session.ts +93 -26
  47. package/src/tools/bashTool.ts +2 -2
  48. package/src/tools/deleteFileTool.ts +1 -1
  49. package/src/tools/editTool.ts +1 -1
  50. package/src/tools/multiEditTool.ts +2 -2
  51. package/src/tools/writeTool.ts +1 -1
  52. package/src/types.ts +13 -0
  53. package/src/utils/messageOperations.ts +3 -1
@@ -74,7 +74,7 @@ export interface CallAgentOptions {
74
74
  messages: ChatCompletionMessageParam[];
75
75
  sessionId?: string;
76
76
  abortSignal?: AbortSignal;
77
- memory?: string; // Memory content parameter, content read from WAVE.md
77
+ memory?: string; // Memory content parameter, content read from AGENTS.md
78
78
  workdir: string; // Current working directory
79
79
  tools?: ChatCompletionFunctionTool[]; // Tool configuration
80
80
  model?: string; // Custom model
@@ -214,9 +214,18 @@ export interface CompressMessagesOptions {
214
214
  abortSignal?: AbortSignal;
215
215
  }
216
216
 
217
+ export interface CompressMessagesResult {
218
+ content: string;
219
+ usage?: {
220
+ prompt_tokens: number;
221
+ completion_tokens: number;
222
+ total_tokens: number;
223
+ };
224
+ }
225
+
217
226
  export async function compressMessages(
218
227
  options: CompressMessagesOptions,
219
- ): Promise<string> {
228
+ ): Promise<CompressMessagesResult> {
220
229
  const { gatewayConfig, modelConfig, messages, abortSignal } = options;
221
230
 
222
231
  // Create OpenAI client with injected configuration
@@ -294,15 +303,29 @@ For technical conversations, structure as:
294
303
  },
295
304
  );
296
305
 
297
- return (
306
+ const content =
298
307
  response.choices[0]?.message?.content?.trim() ||
299
- "Failed to compress conversation history"
300
- );
308
+ "Failed to compress conversation history";
309
+ const usage = response.usage
310
+ ? {
311
+ prompt_tokens: response.usage.prompt_tokens,
312
+ completion_tokens: response.usage.completion_tokens,
313
+ total_tokens: response.usage.total_tokens,
314
+ }
315
+ : undefined;
316
+
317
+ return {
318
+ content,
319
+ usage,
320
+ };
301
321
  } catch (error) {
302
322
  if ((error as Error).name === "AbortError") {
303
323
  throw new Error("Compression request was aborted");
304
324
  }
305
325
  // // logger.error("Failed to compress messages:", error);
306
- return "Failed to compress conversation history";
326
+ return {
327
+ content: "Failed to compress conversation history",
328
+ usage: undefined,
329
+ };
307
330
  }
308
331
  }
@@ -16,7 +16,7 @@ export const addMemory = async (
16
16
  }
17
17
 
18
18
  try {
19
- const memoryFilePath = path.join(workdir, "WAVE.md");
19
+ const memoryFilePath = path.join(workdir, "AGENTS.md");
20
20
 
21
21
  // Format memory entry, starting with -, no timestamp
22
22
  const memoryEntry = `- ${message.substring(1).trim()}\n`;
@@ -41,7 +41,7 @@ export const addMemory = async (
41
41
  // Write file
42
42
  await fs.writeFile(memoryFilePath, updatedContent, "utf-8");
43
43
 
44
- // logger.info(`Memory added to ${memoryFilePath}:`, message);
44
+ // logger.debug(`Memory added to ${memoryFilePath}:`, message);
45
45
  } catch (error) {
46
46
  // logger.error("Failed to add memory:", error);
47
47
  throw new Error(`Failed to add memory: ${(error as Error).message}`);
@@ -63,7 +63,7 @@ export const ensureUserMemoryFile = async (): Promise<void> => {
63
63
  const initialContent =
64
64
  "# User Memory\n\nThis is the user-level memory file, recording important information and context across projects.\n\n";
65
65
  await fs.writeFile(USER_MEMORY_FILE, initialContent, "utf-8");
66
- // logger.info(`Created user memory file: ${USER_MEMORY_FILE}`);
66
+ // logger.debug(`Created user memory file: ${USER_MEMORY_FILE}`);
67
67
  } else {
68
68
  throw error;
69
69
  }
@@ -93,7 +93,7 @@ export const addUserMemory = async (message: string): Promise<void> => {
93
93
  // Write file
94
94
  await fs.writeFile(USER_MEMORY_FILE, updatedContent, "utf-8");
95
95
 
96
- // logger.info(`User memory added to ${USER_MEMORY_FILE}:`, message);
96
+ // logger.debug(`User memory added to ${USER_MEMORY_FILE}:`, message);
97
97
  } catch (error) {
98
98
  // logger.error("Failed to add user memory:", error);
99
99
  throw new Error(`Failed to add user memory: ${(error as Error).message}`);
@@ -113,7 +113,7 @@ export const getUserMemoryContent = async (): Promise<string> => {
113
113
  // Read project memory file content
114
114
  export const readMemoryFile = async (workdir: string): Promise<string> => {
115
115
  try {
116
- const memoryFilePath = path.join(workdir, "WAVE.md");
116
+ const memoryFilePath = path.join(workdir, "AGENTS.md");
117
117
  return await fs.readFile(memoryFilePath, "utf-8");
118
118
  } catch (error) {
119
119
  if ((error as NodeJS.ErrnoException).code === "ENOENT") {
@@ -30,12 +30,23 @@ const SESSION_DIR = join(homedir(), ".wave", "sessions");
30
30
  const VERSION = "1.0.0";
31
31
  const MAX_SESSION_AGE_DAYS = 30;
32
32
 
33
+ /**
34
+ * Resolve session directory path with fallback to default
35
+ * @param sessionDir Optional custom session directory
36
+ * @returns Resolved session directory path
37
+ */
38
+ export function resolveSessionDir(sessionDir?: string): string {
39
+ return sessionDir || SESSION_DIR;
40
+ }
41
+
33
42
  /**
34
43
  * Ensure session directory exists
44
+ * @param sessionDir Optional custom session directory
35
45
  */
36
- async function ensureSessionDir(): Promise<void> {
46
+ export async function ensureSessionDir(sessionDir?: string): Promise<void> {
47
+ const resolvedDir = resolveSessionDir(sessionDir);
37
48
  try {
38
- await fs.mkdir(SESSION_DIR, { recursive: true });
49
+ await fs.mkdir(resolvedDir, { recursive: true });
39
50
  } catch (error) {
40
51
  throw new Error(`Failed to create session directory: ${error}`);
41
52
  }
@@ -44,9 +55,13 @@ async function ensureSessionDir(): Promise<void> {
44
55
  /**
45
56
  * Generate session file path
46
57
  */
47
- export function getSessionFilePath(sessionId: string): string {
58
+ export function getSessionFilePath(
59
+ sessionId: string,
60
+ sessionDir?: string,
61
+ ): string {
48
62
  const shortId = sessionId.split("_")[2] || sessionId.slice(-8);
49
- return join(SESSION_DIR, `session_${shortId}.json`);
63
+ const resolvedDir = resolveSessionDir(sessionDir);
64
+ return join(resolvedDir, `session_${shortId}.json`);
50
65
  }
51
66
 
52
67
  /**
@@ -62,7 +77,15 @@ function filterDiffBlocks(messages: Message[]): Message[] {
62
77
  }
63
78
 
64
79
  /**
65
- * Save session data
80
+ * Save session data to storage
81
+ *
82
+ * @param sessionId - Unique identifier for the session
83
+ * @param messages - Array of messages to save
84
+ * @param workdir - Working directory for the session
85
+ * @param latestTotalTokens - Total tokens used in the session
86
+ * @param startedAt - ISO timestamp when session started (defaults to current time)
87
+ * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
88
+ * @throws {Error} When session cannot be saved due to permission or disk space issues
66
89
  */
67
90
  export async function saveSession(
68
91
  sessionId: string,
@@ -70,6 +93,7 @@ export async function saveSession(
70
93
  workdir: string,
71
94
  latestTotalTokens: number = 0,
72
95
  startedAt?: string,
96
+ sessionDir?: string,
73
97
  ): Promise<void> {
74
98
  // Do not save session files in test environment
75
99
  if (process.env.NODE_ENV === "test") {
@@ -81,7 +105,7 @@ export async function saveSession(
81
105
  return;
82
106
  }
83
107
 
84
- await ensureSessionDir();
108
+ await ensureSessionDir(sessionDir);
85
109
 
86
110
  // Filter out diff blocks before saving
87
111
  const filteredMessages = filterDiffBlocks(messages);
@@ -100,7 +124,7 @@ export async function saveSession(
100
124
  },
101
125
  };
102
126
 
103
- const filePath = getSessionFilePath(sessionId);
127
+ const filePath = getSessionFilePath(sessionId, sessionDir);
104
128
  try {
105
129
  await fs.writeFile(filePath, JSON.stringify(sessionData, null, 2), "utf-8");
106
130
  } catch (error) {
@@ -109,12 +133,18 @@ export async function saveSession(
109
133
  }
110
134
 
111
135
  /**
112
- * Load session data
136
+ * Load session data from storage
137
+ *
138
+ * @param sessionId - Unique identifier for the session to load
139
+ * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
140
+ * @returns Promise that resolves to session data or null if session doesn't exist
141
+ * @throws {Error} When session exists but cannot be read or contains invalid data
113
142
  */
114
143
  export async function loadSession(
115
144
  sessionId: string,
145
+ sessionDir?: string,
116
146
  ): Promise<SessionData | null> {
117
- const filePath = getSessionFilePath(sessionId);
147
+ const filePath = getSessionFilePath(sessionId, sessionDir);
118
148
 
119
149
  try {
120
150
  const content = await fs.readFile(filePath, "utf-8");
@@ -135,12 +165,18 @@ export async function loadSession(
135
165
  }
136
166
 
137
167
  /**
138
- * Get most recent session
168
+ * Get the most recent session for a specific working directory
169
+ *
170
+ * @param workdir - Working directory to find the most recent session for
171
+ * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
172
+ * @returns Promise that resolves to the most recent session data or null if no sessions exist
173
+ * @throws {Error} When session directory cannot be accessed or session data is corrupted
139
174
  */
140
175
  export async function getLatestSession(
141
176
  workdir: string,
177
+ sessionDir?: string,
142
178
  ): Promise<SessionData | null> {
143
- const sessions = await listSessions(workdir);
179
+ const sessions = await listSessions(workdir, false, sessionDir);
144
180
  if (sessions.length === 0) {
145
181
  return null;
146
182
  }
@@ -151,19 +187,27 @@ export async function getLatestSession(
151
187
  new Date(b.lastActiveAt).getTime() - new Date(a.lastActiveAt).getTime(),
152
188
  )[0];
153
189
 
154
- return loadSession(latestSession.id);
190
+ return loadSession(latestSession.id, sessionDir);
155
191
  }
156
192
 
157
193
  /**
158
- * List all sessions
194
+ * List all sessions for a specific working directory or across all working directories
195
+ *
196
+ * @param workdir - Working directory to filter sessions by
197
+ * @param includeAllWorkdirs - If true, returns sessions from all working directories
198
+ * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
199
+ * @returns Promise that resolves to array of session metadata objects
200
+ * @throws {Error} When session directory cannot be accessed or read
159
201
  */
160
202
  export async function listSessions(
161
203
  workdir: string,
162
204
  includeAllWorkdirs = false,
205
+ sessionDir?: string,
163
206
  ): Promise<SessionMetadata[]> {
164
207
  try {
165
- await ensureSessionDir();
166
- const files = await fs.readdir(SESSION_DIR);
208
+ await ensureSessionDir(sessionDir);
209
+ const resolvedDir = resolveSessionDir(sessionDir);
210
+ const files = await fs.readdir(resolvedDir);
167
211
 
168
212
  const sessions: SessionMetadata[] = [];
169
213
 
@@ -173,7 +217,7 @@ export async function listSessions(
173
217
  }
174
218
 
175
219
  try {
176
- const filePath = join(SESSION_DIR, file);
220
+ const filePath = join(resolvedDir, file);
177
221
  const content = await fs.readFile(filePath, "utf-8");
178
222
  const sessionData = JSON.parse(content) as SessionData;
179
223
 
@@ -206,10 +250,18 @@ export async function listSessions(
206
250
  }
207
251
 
208
252
  /**
209
- * Delete session
253
+ * Delete a session from storage
254
+ *
255
+ * @param sessionId - Unique identifier for the session to delete
256
+ * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
257
+ * @returns Promise that resolves to true if session was deleted, false if it didn't exist
258
+ * @throws {Error} When session exists but cannot be deleted due to permission issues
210
259
  */
211
- export async function deleteSession(sessionId: string): Promise<boolean> {
212
- const filePath = getSessionFilePath(sessionId);
260
+ export async function deleteSession(
261
+ sessionId: string,
262
+ sessionDir?: string,
263
+ ): Promise<boolean> {
264
+ const filePath = getSessionFilePath(sessionId, sessionDir);
213
265
 
214
266
  try {
215
267
  await fs.unlink(filePath);
@@ -223,15 +275,23 @@ export async function deleteSession(sessionId: string): Promise<boolean> {
223
275
  }
224
276
 
225
277
  /**
226
- * Clean up expired sessions
278
+ * Clean up expired sessions older than the configured maximum age
279
+ *
280
+ * @param workdir - Working directory to clean up sessions for
281
+ * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
282
+ * @returns Promise that resolves to the number of sessions that were deleted
283
+ * @throws {Error} When session directory cannot be accessed or sessions cannot be deleted
227
284
  */
228
- export async function cleanupExpiredSessions(workdir: string): Promise<number> {
285
+ export async function cleanupExpiredSessions(
286
+ workdir: string,
287
+ sessionDir?: string,
288
+ ): Promise<number> {
229
289
  // Do not perform cleanup operations in test environment
230
290
  if (process.env.NODE_ENV === "test") {
231
291
  return 0;
232
292
  }
233
293
 
234
- const sessions = await listSessions(workdir, true);
294
+ const sessions = await listSessions(workdir, true, sessionDir);
235
295
  const now = new Date();
236
296
  const maxAge = MAX_SESSION_AGE_DAYS * 24 * 60 * 60 * 1000; // Convert to milliseconds
237
297
 
@@ -242,7 +302,7 @@ export async function cleanupExpiredSessions(workdir: string): Promise<number> {
242
302
 
243
303
  if (sessionAge > maxAge) {
244
304
  try {
245
- await deleteSession(session.id);
305
+ await deleteSession(session.id, sessionDir);
246
306
  deletedCount++;
247
307
  } catch (error) {
248
308
  console.warn(
@@ -256,10 +316,17 @@ export async function cleanupExpiredSessions(workdir: string): Promise<number> {
256
316
  }
257
317
 
258
318
  /**
259
- * Check if session exists
319
+ * Check if a session exists in storage
320
+ *
321
+ * @param sessionId - Unique identifier for the session to check
322
+ * @param sessionDir - Optional custom directory for session storage (defaults to ~/.wave/sessions/)
323
+ * @returns Promise that resolves to true if session exists, false otherwise
260
324
  */
261
- export async function sessionExists(sessionId: string): Promise<boolean> {
262
- const filePath = getSessionFilePath(sessionId);
325
+ export async function sessionExists(
326
+ sessionId: string,
327
+ sessionDir?: string,
328
+ ): Promise<boolean> {
329
+ const filePath = getSessionFilePath(sessionId, sessionDir);
263
330
 
264
331
  try {
265
332
  await fs.access(filePath);
@@ -44,10 +44,10 @@ export const bashTool: ToolPlugin = {
44
44
  ): Promise<ToolResult> => {
45
45
  const command = args.command as string;
46
46
  const runInBackground = args.run_in_background as boolean | undefined;
47
- // Set default timeout: 30s for foreground, no timeout for background
47
+ // Set default timeout: 60s for foreground, no timeout for background
48
48
  const timeout =
49
49
  (args.timeout as number | undefined) ??
50
- (runInBackground ? undefined : 30000);
50
+ (runInBackground ? undefined : 60000);
51
51
 
52
52
  if (!command || typeof command !== "string") {
53
53
  return {
@@ -48,7 +48,7 @@ export const deleteFileTool: ToolPlugin = {
48
48
  // Delete file
49
49
  await unlink(filePath);
50
50
 
51
- // logger.info(`Successfully deleted file: ${filePath}`);
51
+ // logger.debug(`Successfully deleted file: ${filePath}`);
52
52
 
53
53
  return {
54
54
  success: true,
@@ -160,7 +160,7 @@ export const editTool: ToolPlugin = {
160
160
  ? `Replaced ${replacementCount} instances`
161
161
  : "Text replaced successfully";
162
162
 
163
- // logger.info(`Edit tool: ${shortResult}`);
163
+ // logger.debug(`Edit tool: ${shortResult}`);
164
164
 
165
165
  return {
166
166
  success: true,
@@ -149,7 +149,7 @@ export const multiEditTool: ToolPlugin = {
149
149
  if (edits[0] && edits[0].old_string === "") {
150
150
  originalContent = "";
151
151
  isNewFile = true;
152
- // logger.info(`Creating new file: ${resolvedPath}`);
152
+ // logger.debug(`Creating new file: ${resolvedPath}`);
153
153
  } else {
154
154
  return {
155
155
  success: false,
@@ -236,7 +236,7 @@ export const multiEditTool: ToolPlugin = {
236
236
 
237
237
  const detailedContent = `${shortResult}\n\nOperations performed:\n${appliedEdits.map((edit, i) => `${i + 1}. ${edit}`).join("\n")}`;
238
238
 
239
- // logger.info(`MultiEdit tool: ${shortResult}`);
239
+ // logger.debug(`MultiEdit tool: ${shortResult}`);
240
240
 
241
241
  return {
242
242
  success: true,
@@ -121,7 +121,7 @@ export const writeTool: ToolPlugin = {
121
121
  const chars = content.length;
122
122
  const detailedContent = `${shortResult} (${lines} lines, ${chars} characters)`;
123
123
 
124
- // logger.info(`Write tool: ${shortResult}`);
124
+ // logger.debug(`Write tool: ${shortResult}`);
125
125
 
126
126
  return {
127
127
  success: true,
package/src/types.ts CHANGED
@@ -14,6 +14,7 @@ export interface Logger {
14
14
  export interface Message {
15
15
  role: "user" | "assistant";
16
16
  blocks: MessageBlock[];
17
+ usage?: Usage; // Usage data for this message's AI operation (assistant messages only)
17
18
  }
18
19
 
19
20
  export type MessageBlock =
@@ -332,6 +333,18 @@ export class ConfigurationError extends Error {
332
333
  }
333
334
  }
334
335
 
336
+ /**
337
+ * Usage statistics for AI operations
338
+ * Extends OpenAI's Usage format with additional tracking fields
339
+ */
340
+ export interface Usage {
341
+ prompt_tokens: number; // Tokens used in prompts
342
+ completion_tokens: number; // Tokens generated in completions
343
+ total_tokens: number; // Sum of prompt + completion tokens
344
+ model?: string; // Model used for the operation (e.g., "gpt-4", "gpt-3.5-turbo")
345
+ operation_type?: "agent" | "compress"; // Type of operation that generated usage
346
+ }
347
+
335
348
  // Standard error messages
336
349
  export const CONFIG_ERRORS = {
337
350
  MISSING_API_KEY:
@@ -1,4 +1,4 @@
1
- import type { Message } from "../types.js";
1
+ import type { Message, Usage } from "../types.js";
2
2
  import { readFileSync } from "fs";
3
3
  import { extname } from "path";
4
4
  import { ChatCompletionMessageFunctionToolCall } from "openai/resources.js";
@@ -177,6 +177,7 @@ export const addAssistantMessageToMessages = (
177
177
  messages: Message[],
178
178
  content?: string,
179
179
  toolCalls?: ChatCompletionMessageFunctionToolCall[],
180
+ usage?: Usage,
180
181
  ): Message[] => {
181
182
  const blocks: Message["blocks"] = [];
182
183
 
@@ -202,6 +203,7 @@ export const addAssistantMessageToMessages = (
202
203
  const initialAssistantMessage: Message = {
203
204
  role: "assistant",
204
205
  blocks,
206
+ usage, // Include usage data if provided
205
207
  };
206
208
 
207
209
  return [...messages, initialAssistantMessage];