wave-agent-sdk 0.0.4 → 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 +82 -6
  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 +3 -3
  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 +101 -7
  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 +28 -5
  45. package/src/services/memory.ts +3 -3
  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
package/src/agent.ts CHANGED
@@ -20,6 +20,7 @@ import type {
20
20
  McpServerStatus,
21
21
  GatewayConfig,
22
22
  ModelConfig,
23
+ Usage,
23
24
  } from "./types.js";
24
25
  import { HookManager } from "./hooks/index.js";
25
26
  import { configResolver } from "./utils/configResolver.js";
@@ -51,6 +52,14 @@ export interface AgentOptions {
51
52
  workdir?: string;
52
53
  /**Optional custom system prompt - if provided, replaces default system prompt */
53
54
  systemPrompt?: string;
55
+
56
+ // New: Session directory configuration
57
+ /**
58
+ * Optional custom directory for session file storage
59
+ * @default join(homedir(), ".wave", "sessions")
60
+ * @example "/path/to/custom/sessions"
61
+ */
62
+ sessionDir?: string;
54
63
  }
55
64
 
56
65
  export interface AgentCallbacks
@@ -72,6 +81,7 @@ export class Agent {
72
81
  private hookManager: HookManager; // Add hooks manager instance
73
82
  private workdir: string; // Working directory
74
83
  private systemPrompt?: string; // Custom system prompt
84
+ private _usages: Usage[] = []; // Usage tracking array
75
85
 
76
86
  // Configuration properties
77
87
  private gatewayConfig: GatewayConfig;
@@ -81,13 +91,21 @@ export class Agent {
81
91
  /**
82
92
  * Agent constructor - handles configuration resolution and validation
83
93
  *
84
- * IMPORTANT: Keep this constructor's signature exactly the same as Agent.create()
85
- * to maintain API consistency. Both methods should accept the same AgentOptions.
94
+ * IMPORTANT: This constructor is private. Use Agent.create() instead for proper
95
+ * async initialization. Keep this constructor's signature exactly the same as
96
+ * Agent.create() to maintain API consistency.
86
97
  *
87
98
  * @param options - Configuration options for the Agent instance
99
+ * @param options.sessionDir - Optional custom directory for session storage
88
100
  */
89
- constructor(options: AgentOptions) {
90
- const { callbacks = {}, logger, workdir, systemPrompt } = options;
101
+ private constructor(options: AgentOptions) {
102
+ const {
103
+ callbacks = {},
104
+ logger,
105
+ workdir,
106
+ systemPrompt,
107
+ sessionDir,
108
+ } = options;
91
109
 
92
110
  // Resolve configuration from constructor args and environment variables
93
111
  const gatewayConfig = configResolver.resolveGatewayConfig(
@@ -139,6 +157,7 @@ export class Agent {
139
157
  callbacks,
140
158
  workdir: this.workdir,
141
159
  logger: this.logger,
160
+ sessionDir,
142
161
  });
143
162
 
144
163
  // Initialize subagent manager with all dependencies in constructor
@@ -151,6 +170,7 @@ export class Agent {
151
170
  gatewayConfig,
152
171
  modelConfig,
153
172
  tokenLimit,
173
+ onUsageAdded: (usage) => this.addUsage(usage),
154
174
  });
155
175
 
156
176
  // Initialize AI manager with resolved configuration
@@ -160,7 +180,12 @@ export class Agent {
160
180
  logger: this.logger,
161
181
  backgroundBashManager: this.backgroundBashManager,
162
182
  hookManager: this.hookManager,
163
- callbacks,
183
+ callbacks: {
184
+ ...callbacks,
185
+ onUsageAdded: (usage: Usage) => {
186
+ this.addUsage(usage);
187
+ },
188
+ },
164
189
  workdir: this.workdir,
165
190
  systemPrompt: this.systemPrompt,
166
191
  gatewayConfig: this.gatewayConfig,
@@ -192,6 +217,34 @@ export class Agent {
192
217
  return this.messageManager.getMessages();
193
218
  }
194
219
 
220
+ public get usages(): Usage[] {
221
+ return [...this._usages]; // Return copy to prevent external modification
222
+ }
223
+
224
+ /**
225
+ * Rebuild usage array from messages containing usage metadata
226
+ * Called during session restoration to reconstruct usage tracking
227
+ */
228
+ private rebuildUsageFromMessages(): void {
229
+ this._usages = [];
230
+ this.messages.forEach((message) => {
231
+ if (message.role === "assistant" && message.usage) {
232
+ this._usages.push(message.usage);
233
+ }
234
+ });
235
+ // Trigger callback after rebuilding usage array
236
+ this.messageManager.triggerUsageChange();
237
+ }
238
+
239
+ /**
240
+ * Add usage data to the tracking array and trigger callbacks
241
+ * @param usage Usage data from AI operations
242
+ */
243
+ private addUsage(usage: Usage): void {
244
+ this._usages.push(usage);
245
+ this.messageManager.triggerUsageChange();
246
+ }
247
+
195
248
  public get latestTotalTokens(): number {
196
249
  return this.messageManager.getlatestTotalTokens();
197
250
  }
@@ -242,6 +295,43 @@ export class Agent {
242
295
  * @param options - Same AgentOptions interface used by constructor
243
296
  * @returns Promise<Agent> - Fully initialized Agent instance
244
297
  */
298
+ /**
299
+ * Create a new Agent instance with async initialization
300
+ *
301
+ * This is the recommended way to create Agent instances. The constructor is private
302
+ * to ensure proper async initialization of all components.
303
+ *
304
+ * @param options - Configuration options for the Agent instance
305
+ * @param options.apiKey - API key for the AI service (or set WAVE_API_KEY env var)
306
+ * @param options.baseURL - Base URL for the AI service (or set WAVE_BASE_URL env var)
307
+ * @param options.sessionDir - Optional custom directory for session file storage.
308
+ * If not provided, defaults to ~/.wave/sessions/. Can be relative or absolute path.
309
+ * Examples: "./app-sessions", "/var/myapp/sessions", "~/Documents/sessions"
310
+ * @param options.callbacks - Optional callbacks for various Agent events
311
+ * @param options.restoreSessionId - Optional session ID to restore from
312
+ * @param options.continueLastSession - Whether to continue the last session automatically
313
+ * @param options.logger - Optional custom logger implementation
314
+ * @param options.messages - Optional initial messages for testing convenience
315
+ * @param options.workdir - Working directory (defaults to process.cwd())
316
+ * @param options.systemPrompt - Optional custom system prompt
317
+ * @returns Promise that resolves to initialized Agent instance
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * // Basic usage with default session directory
322
+ * const agent = await Agent.create({
323
+ * apiKey: 'your-api-key',
324
+ * baseURL: 'https://api.example.com'
325
+ * });
326
+ *
327
+ * // Custom session directory
328
+ * const agent = await Agent.create({
329
+ * apiKey: 'your-api-key',
330
+ * baseURL: 'https://api.example.com',
331
+ * sessionDir: './app-sessions'
332
+ * });
333
+ * ```
334
+ */
245
335
  static async create(options: AgentOptions): Promise<Agent> {
246
336
  // Create Agent instance - configuration resolution and validation now happens in constructor
247
337
  const instance = new Agent(options);
@@ -289,7 +379,7 @@ export class Agent {
289
379
  // Initialize hooks configuration
290
380
  try {
291
381
  // Load hooks configuration from user and project settings
292
- this.logger?.info("Loading hooks configuration...");
382
+ this.logger?.debug("Loading hooks configuration...");
293
383
  this.hookManager.loadConfigurationFromSettings();
294
384
  this.logger?.debug("Hooks system initialized successfully");
295
385
  } catch (error) {
@@ -301,12 +391,16 @@ export class Agent {
301
391
  if (options?.messages) {
302
392
  // If messages are provided, use them directly (useful for testing)
303
393
  this.messageManager.setMessages(options.messages);
394
+ // Rebuild usage array from restored messages
395
+ this.rebuildUsageFromMessages();
304
396
  } else {
305
397
  // Otherwise, handle session restoration
306
398
  await this.messageManager.handleSessionRestoration(
307
399
  options?.restoreSessionId,
308
400
  options?.continueLastSession,
309
401
  );
402
+ // Rebuild usage array from restored messages
403
+ this.rebuildUsageFromMessages();
310
404
  }
311
405
  }
312
406
 
@@ -361,7 +455,7 @@ export class Agent {
361
455
 
362
456
  /** Destroy managers, clean up resources */
363
457
  public async destroy(): Promise<void> {
364
- this.messageManager.saveSession();
458
+ await this.messageManager.saveSession();
365
459
  this.abortAIMessage();
366
460
  this.abortBashCommand();
367
461
  this.abortSlashCommand();
@@ -174,7 +174,9 @@ export class HookManager implements IHookManager {
174
174
 
175
175
  const eventConfigs = this.configuration[event];
176
176
  if (!eventConfigs || eventConfigs.length === 0) {
177
- this.logger?.debug(`[HookManager] No hooks configured for ${event} event`);
177
+ this.logger?.debug(
178
+ `[HookManager] No hooks configured for ${event} event`,
179
+ );
178
180
  return [];
179
181
  }
180
182
 
@@ -261,7 +263,7 @@ export class HookManager implements IHookManager {
261
263
  results,
262
264
  totalDuration,
263
265
  );
264
- this.logger?.info(`[HookManager] ${event} execution summary: ${summary}`);
266
+ this.logger?.debug(`[HookManager] ${event} execution summary: ${summary}`);
265
267
 
266
268
  return results;
267
269
  }
@@ -2,7 +2,7 @@ import { callAgent, compressMessages } from "../services/aiService.js";
2
2
  import { getMessagesToCompress } from "../utils/messageOperations.js";
3
3
  import { convertMessagesForAPI } from "../utils/convertMessagesForAPI.js";
4
4
  import * as memory from "../services/memory.js";
5
- import type { Logger, GatewayConfig, ModelConfig } from "../types.js";
5
+ import type { Logger, GatewayConfig, ModelConfig, Usage } from "../types.js";
6
6
  import type { ToolManager } from "./toolManager.js";
7
7
  import type { ToolContext, ToolResult } from "../tools/types.js";
8
8
  import type { MessageManager } from "./messageManager.js";
@@ -13,6 +13,7 @@ import type { ExtendedHookExecutionContext } from "../hooks/types.js";
13
13
 
14
14
  export interface AIManagerCallbacks {
15
15
  onCompressionStateChange?: (isCompressing: boolean) => void;
16
+ onUsageAdded?: (usage: Usage) => void;
16
17
  }
17
18
 
18
19
  export interface AIManagerOptions {
@@ -140,7 +141,7 @@ export class AIManager {
140
141
 
141
142
  // Check if token limit exceeded - use injected configuration
142
143
  if (usage.total_tokens > this.tokenLimit) {
143
- this.logger?.info(
144
+ this.logger?.debug(
144
145
  `Token usage exceeded ${this.tokenLimit}, compressing messages...`,
145
146
  );
146
147
 
@@ -156,7 +157,7 @@ export class AIManager {
156
157
 
157
158
  this.setIsCompressing(true);
158
159
  try {
159
- const compressedContent = await compressMessages({
160
+ const compressionResult = await compressMessages({
160
161
  gatewayConfig: this.gatewayConfig,
161
162
  modelConfig: this.modelConfig,
162
163
  messages: recentChatMessages,
@@ -166,10 +167,26 @@ export class AIManager {
166
167
  // Execute message reconstruction and sessionId update after compression
167
168
  this.messageManager.compressMessagesAndUpdateSession(
168
169
  insertIndex,
169
- compressedContent,
170
+ compressionResult.content,
170
171
  );
171
172
 
172
- this.logger?.info(
173
+ // Handle usage tracking for compression operations
174
+ if (compressionResult.usage) {
175
+ const usage: Usage = {
176
+ prompt_tokens: compressionResult.usage.prompt_tokens,
177
+ completion_tokens: compressionResult.usage.completion_tokens,
178
+ total_tokens: compressionResult.usage.total_tokens,
179
+ model: this.modelConfig.fastModel,
180
+ operation_type: "compress",
181
+ };
182
+
183
+ // Notify Agent to add to usage tracking
184
+ if (this.callbacks?.onUsageAdded) {
185
+ this.callbacks.onUsageAdded(usage);
186
+ }
187
+ }
188
+
189
+ this.logger?.debug(
173
190
  `Successfully compressed ${messagesToCompress.length} messages and updated session`,
174
191
  );
175
192
  } catch (compressError) {
@@ -255,8 +272,27 @@ export class AIManager {
255
272
  }
256
273
  }
257
274
 
258
- // Add assistant message at once (including content and tool calls)
259
- this.messageManager.addAssistantMessage(content, toolCalls);
275
+ // Handle usage tracking for agent operations
276
+ let usage: Usage | undefined;
277
+ if (result.usage) {
278
+ usage = {
279
+ prompt_tokens: result.usage.prompt_tokens,
280
+ completion_tokens: result.usage.completion_tokens,
281
+ total_tokens: result.usage.total_tokens,
282
+ model: model || this.modelConfig.agentModel,
283
+ operation_type: "agent",
284
+ };
285
+ }
286
+
287
+ // Add assistant message at once (including content, tool calls, and usage)
288
+ this.messageManager.addAssistantMessage(content, toolCalls, usage);
289
+
290
+ // Notify Agent to add to usage tracking
291
+ if (usage) {
292
+ if (this.callbacks?.onUsageAdded) {
293
+ this.callbacks.onUsageAdded(usage);
294
+ }
295
+ }
260
296
 
261
297
  if (toolCalls.length > 0) {
262
298
  for (const functionToolCall of toolCalls) {
@@ -53,7 +53,7 @@ export class McpManager {
53
53
  this.workdir = workdir;
54
54
 
55
55
  if (autoConnect) {
56
- this.logger?.info("Initializing MCP servers...");
56
+ this.logger?.debug("Initializing MCP servers...");
57
57
 
58
58
  // Ensure MCP configuration is loaded
59
59
  const config = await this.ensureConfigLoaded();
@@ -63,10 +63,10 @@ export class McpManager {
63
63
  const connectionPromises = Object.keys(config.mcpServers).map(
64
64
  async (serverName) => {
65
65
  try {
66
- this.logger?.info(`Connecting to MCP server: ${serverName}`);
66
+ this.logger?.debug(`Connecting to MCP server: ${serverName}`);
67
67
  const success = await this.connectServer(serverName);
68
68
  if (success) {
69
- this.logger?.info(
69
+ this.logger?.debug(
70
70
  `Successfully connected to MCP server: ${serverName}`,
71
71
  );
72
72
  } else {
@@ -86,7 +86,7 @@ export class McpManager {
86
86
  await Promise.all(connectionPromises);
87
87
  }
88
88
 
89
- this.logger?.info("MCP servers initialization completed");
89
+ this.logger?.debug("MCP servers initialization completed");
90
90
  // Trigger state change callback after initialization
91
91
  this.callbacks.onServersChange?.(this.getAllServers());
92
92
  }
@@ -255,7 +255,7 @@ export class McpManager {
255
255
  };
256
256
 
257
257
  transport.onclose = () => {
258
- this.logger?.info(`MCP Server ${name} transport closed`);
258
+ this.logger?.debug(`MCP Server ${name} transport closed`);
259
259
  this.connections.delete(name);
260
260
  this.updateServerStatus(name, {
261
261
  status: "disconnected",
@@ -16,7 +16,7 @@ import {
16
16
  type UpdateSubagentBlockParams,
17
17
  type AgentToolBlockUpdateParams,
18
18
  } from "../utils/messageOperations.js";
19
- import type { Logger, Message } from "../types.js";
19
+ import type { Logger, Message, Usage } from "../types.js";
20
20
  import {
21
21
  cleanupExpiredSessions,
22
22
  getLatestSession,
@@ -32,6 +32,7 @@ export interface MessageManagerCallbacks {
32
32
  onSessionIdChange?: (sessionId: string) => void;
33
33
  onLatestTotalTokensChange?: (latestTotalTokens: number) => void;
34
34
  onUserInputHistoryChange?: (history: string[]) => void;
35
+ onUsagesChange?: (usages: Usage[]) => void;
35
36
  // Incremental callback
36
37
  onUserMessageAdded?: (
37
38
  content: string,
@@ -71,6 +72,13 @@ export interface MessageManagerOptions {
71
72
  callbacks: MessageManagerCallbacks;
72
73
  workdir: string;
73
74
  logger?: Logger;
75
+
76
+ // New: Optional session directory override
77
+ /**
78
+ * Custom session directory path
79
+ * @default join(homedir(), ".wave", "sessions")
80
+ */
81
+ sessionDir?: string;
74
82
  }
75
83
 
76
84
  export class MessageManager {
@@ -83,6 +91,7 @@ export class MessageManager {
83
91
  private workdir: string;
84
92
  private logger?: Logger; // Add optional logger property
85
93
  private callbacks: MessageManagerCallbacks;
94
+ private sessionDir?: string; // Add session directory property
86
95
 
87
96
  constructor(options: MessageManagerOptions) {
88
97
  this.sessionId = randomUUID();
@@ -93,6 +102,7 @@ export class MessageManager {
93
102
  this.workdir = options.workdir;
94
103
  this.callbacks = options.callbacks;
95
104
  this.logger = options.logger;
105
+ this.sessionDir = options.sessionDir;
96
106
  }
97
107
 
98
108
  // Getter methods
@@ -113,7 +123,7 @@ export class MessageManager {
113
123
  }
114
124
 
115
125
  public getTranscriptPath(): string {
116
- return getSessionFilePath(this.sessionId);
126
+ return getSessionFilePath(this.sessionId, this.sessionDir);
117
127
  }
118
128
 
119
129
  // Setter methods, will trigger callbacks
@@ -140,6 +150,7 @@ export class MessageManager {
140
150
  this.workdir,
141
151
  this.latestTotalTokens,
142
152
  this.sessionStartTime,
153
+ this.sessionDir,
143
154
  );
144
155
  } catch (error) {
145
156
  this.logger?.error("Failed to save session:", error);
@@ -155,7 +166,7 @@ export class MessageManager {
155
166
  ): Promise<void> {
156
167
  // Clean up expired sessions first
157
168
  try {
158
- await cleanupExpiredSessions(this.workdir);
169
+ await cleanupExpiredSessions(this.workdir, this.sessionDir);
159
170
  } catch (error) {
160
171
  console.warn("Failed to cleanup expired sessions:", error);
161
172
  }
@@ -168,13 +179,16 @@ export class MessageManager {
168
179
  let sessionToRestore: SessionData | null = null;
169
180
 
170
181
  if (restoreSessionId) {
171
- sessionToRestore = await loadSession(restoreSessionId);
182
+ sessionToRestore = await loadSession(restoreSessionId, this.sessionDir);
172
183
  if (!sessionToRestore) {
173
184
  console.error(`Session not found: ${restoreSessionId}`);
174
185
  process.exit(1);
175
186
  }
176
187
  } else if (continueLastSession) {
177
- sessionToRestore = await getLatestSession(this.workdir);
188
+ sessionToRestore = await getLatestSession(
189
+ this.workdir,
190
+ this.sessionDir,
191
+ );
178
192
  if (!sessionToRestore) {
179
193
  console.error(
180
194
  `No previous session found for workdir: ${this.workdir}`,
@@ -290,11 +304,13 @@ export class MessageManager {
290
304
  public addAssistantMessage(
291
305
  content?: string,
292
306
  toolCalls?: ChatCompletionMessageFunctionToolCall[],
307
+ usage?: Usage,
293
308
  ): void {
294
309
  const newMessages = addAssistantMessageToMessages(
295
310
  this.messages,
296
311
  content,
297
312
  toolCalls,
313
+ usage,
298
314
  );
299
315
  this.setMessages(newMessages);
300
316
  this.callbacks.onAssistantMessageAdded?.(content, toolCalls);
@@ -466,4 +482,17 @@ export class MessageManager {
466
482
  };
467
483
  this.callbacks.onSubAgentBlockUpdated?.(params.subagentId, params.messages);
468
484
  }
485
+
486
+ /**
487
+ * Trigger usage change callback with all usage data from assistant messages
488
+ */
489
+ public triggerUsageChange(): void {
490
+ const usages: Usage[] = [];
491
+ for (const message of this.messages) {
492
+ if (message.role === "assistant" && message.usage) {
493
+ usages.push(message.usage);
494
+ }
495
+ }
496
+ this.callbacks.onUsagesChange?.(usages);
497
+ }
469
498
  }
@@ -36,7 +36,7 @@ export class SkillManager {
36
36
  * Initialize the skill manager by discovering available skills
37
37
  */
38
38
  async initialize(): Promise<void> {
39
- this.logger?.info("Initializing SkillManager...");
39
+ this.logger?.debug("Initializing SkillManager...");
40
40
 
41
41
  try {
42
42
  // Clear existing data before discovery
@@ -66,7 +66,7 @@ export class SkillManager {
66
66
  }
67
67
 
68
68
  this.initialized = true;
69
- this.logger?.info(
69
+ this.logger?.debug(
70
70
  `SkillManager initialized with ${this.skillMetadata.size} skills`,
71
71
  );
72
72
  } catch (error) {
@@ -247,7 +247,7 @@ export class SkillManager {
247
247
  ): Promise<{ content: string; context?: SkillInvocationContext }> {
248
248
  const { skill_name } = args;
249
249
 
250
- this.logger?.info(`Invoking skill: ${skill_name}`);
250
+ this.logger?.debug(`Invoking skill: ${skill_name}`);
251
251
 
252
252
  try {
253
253
  // Load the skill
@@ -92,7 +92,7 @@ export class SlashCommandManager {
92
92
  });
93
93
  }
94
94
 
95
- this.logger?.info(`Loaded ${customCommands.length} custom commands`);
95
+ this.logger?.debug(`Loaded ${customCommands.length} custom commands`);
96
96
  } catch (error) {
97
97
  this.logger?.warn("Failed to load custom commands:", error);
98
98
  }
@@ -1,6 +1,12 @@
1
1
  import { randomUUID } from "crypto";
2
2
  import type { SubagentConfiguration } from "../utils/subagentParser.js";
3
- import type { Message, Logger, GatewayConfig, ModelConfig } from "../types.js";
3
+ import type {
4
+ Message,
5
+ Logger,
6
+ GatewayConfig,
7
+ ModelConfig,
8
+ Usage,
9
+ } from "../types.js";
4
10
  import { AIManager } from "./aiManager.js";
5
11
  import {
6
12
  MessageManager,
@@ -27,6 +33,7 @@ export interface SubagentManagerOptions {
27
33
  gatewayConfig: GatewayConfig;
28
34
  modelConfig: ModelConfig;
29
35
  tokenLimit: number;
36
+ onUsageAdded?: (usage: Usage) => void;
30
37
  }
31
38
 
32
39
  export class SubagentManager {
@@ -40,6 +47,7 @@ export class SubagentManager {
40
47
  private gatewayConfig: GatewayConfig;
41
48
  private modelConfig: ModelConfig;
42
49
  private tokenLimit: number;
50
+ private onUsageAdded?: (usage: Usage) => void;
43
51
 
44
52
  constructor(options: SubagentManagerOptions) {
45
53
  this.workdir = options.workdir;
@@ -49,6 +57,7 @@ export class SubagentManager {
49
57
  this.gatewayConfig = options.gatewayConfig;
50
58
  this.modelConfig = options.modelConfig;
51
59
  this.tokenLimit = options.tokenLimit;
60
+ this.onUsageAdded = options.onUsageAdded;
52
61
  }
53
62
 
54
63
  /**
@@ -156,6 +165,9 @@ export class SubagentManager {
156
165
  agentModel: modelToUse,
157
166
  },
158
167
  tokenLimit: this.tokenLimit,
168
+ callbacks: {
169
+ onUsageAdded: this.onUsageAdded,
170
+ },
159
171
  });
160
172
 
161
173
  const instance: SubagentInstance = {
@@ -311,7 +323,7 @@ export class SubagentManager {
311
323
  messages: instance.messages,
312
324
  });
313
325
 
314
- this.logger?.info(`Aborted subagent instance: ${subagentId}`);
326
+ this.logger?.debug(`Aborted subagent instance: ${subagentId}`);
315
327
  return true;
316
328
  } catch (error) {
317
329
  this.logger?.error(
@@ -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
  }
@@ -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}`);