@kosuke-ai/cli 0.0.43 → 0.0.45

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 (86) hide show
  1. package/README.md +94 -1
  2. package/claude.costs.json +34 -0
  3. package/dist/index.js +55 -9
  4. package/dist/index.js.map +1 -1
  5. package/dist/kosuke/commands/build.d.ts +5 -1
  6. package/dist/kosuke/commands/build.d.ts.map +1 -1
  7. package/dist/kosuke/commands/build.js +300 -4
  8. package/dist/kosuke/commands/build.js.map +1 -1
  9. package/dist/kosuke/commands/migrate.d.ts +6 -1
  10. package/dist/kosuke/commands/migrate.d.ts.map +1 -1
  11. package/dist/kosuke/commands/migrate.js +124 -37
  12. package/dist/kosuke/commands/migrate.js.map +1 -1
  13. package/dist/kosuke/commands/plan.d.ts +4 -20
  14. package/dist/kosuke/commands/plan.d.ts.map +1 -1
  15. package/dist/kosuke/commands/plan.js +471 -679
  16. package/dist/kosuke/commands/plan.js.map +1 -1
  17. package/dist/kosuke/commands/requirements.js +2 -2
  18. package/dist/kosuke/commands/requirements.js.map +1 -1
  19. package/dist/kosuke/commands/review.d.ts +6 -1
  20. package/dist/kosuke/commands/review.d.ts.map +1 -1
  21. package/dist/kosuke/commands/review.js +168 -68
  22. package/dist/kosuke/commands/review.js.map +1 -1
  23. package/dist/kosuke/commands/serve.d.ts +9 -0
  24. package/dist/kosuke/commands/serve.d.ts.map +1 -0
  25. package/dist/kosuke/commands/serve.js +17 -0
  26. package/dist/kosuke/commands/serve.js.map +1 -0
  27. package/dist/kosuke/commands/ship.d.ts +6 -1
  28. package/dist/kosuke/commands/ship.d.ts.map +1 -1
  29. package/dist/kosuke/commands/ship.js +226 -67
  30. package/dist/kosuke/commands/ship.js.map +1 -1
  31. package/dist/kosuke/commands/test.d.ts +11 -6
  32. package/dist/kosuke/commands/test.d.ts.map +1 -1
  33. package/dist/kosuke/commands/test.js +438 -105
  34. package/dist/kosuke/commands/test.js.map +1 -1
  35. package/dist/kosuke/commands/tickets.d.ts +7 -15
  36. package/dist/kosuke/commands/tickets.d.ts.map +1 -1
  37. package/dist/kosuke/commands/tickets.js +17 -61
  38. package/dist/kosuke/commands/tickets.js.map +1 -1
  39. package/dist/kosuke/serve/middleware/error-handler.d.ts +13 -0
  40. package/dist/kosuke/serve/middleware/error-handler.d.ts.map +1 -0
  41. package/dist/kosuke/serve/middleware/error-handler.js +34 -0
  42. package/dist/kosuke/serve/middleware/error-handler.js.map +1 -0
  43. package/dist/kosuke/serve/middleware/validation.d.ts +13 -0
  44. package/dist/kosuke/serve/middleware/validation.d.ts.map +1 -0
  45. package/dist/kosuke/serve/middleware/validation.js +39 -0
  46. package/dist/kosuke/serve/middleware/validation.js.map +1 -0
  47. package/dist/kosuke/serve/routes/build.d.ts +6 -0
  48. package/dist/kosuke/serve/routes/build.d.ts.map +1 -0
  49. package/dist/kosuke/serve/routes/build.js +224 -0
  50. package/dist/kosuke/serve/routes/build.js.map +1 -0
  51. package/dist/kosuke/serve/routes/health.d.ts +6 -0
  52. package/dist/kosuke/serve/routes/health.d.ts.map +1 -0
  53. package/dist/kosuke/serve/routes/health.js +11 -0
  54. package/dist/kosuke/serve/routes/health.js.map +1 -0
  55. package/dist/kosuke/serve/routes/plan.d.ts +6 -0
  56. package/dist/kosuke/serve/routes/plan.d.ts.map +1 -0
  57. package/dist/kosuke/serve/routes/plan.js +104 -0
  58. package/dist/kosuke/serve/routes/plan.js.map +1 -0
  59. package/dist/kosuke/serve/server.d.ts +16 -0
  60. package/dist/kosuke/serve/server.d.ts.map +1 -0
  61. package/dist/kosuke/serve/server.js +63 -0
  62. package/dist/kosuke/serve/server.js.map +1 -0
  63. package/dist/kosuke/serve/validation/build.d.ts +41 -0
  64. package/dist/kosuke/serve/validation/build.d.ts.map +1 -0
  65. package/dist/kosuke/serve/validation/build.js +20 -0
  66. package/dist/kosuke/serve/validation/build.js.map +1 -0
  67. package/dist/kosuke/serve/validation/plan.d.ts +26 -0
  68. package/dist/kosuke/serve/validation/plan.d.ts.map +1 -0
  69. package/dist/kosuke/serve/validation/plan.js +15 -0
  70. package/dist/kosuke/serve/validation/plan.js.map +1 -0
  71. package/dist/kosuke/types.d.ts +217 -1
  72. package/dist/kosuke/types.d.ts.map +1 -1
  73. package/dist/kosuke/utils/claude-agent.d.ts +82 -6
  74. package/dist/kosuke/utils/claude-agent.d.ts.map +1 -1
  75. package/dist/kosuke/utils/claude-agent.js +381 -180
  76. package/dist/kosuke/utils/claude-agent.js.map +1 -1
  77. package/dist/kosuke/utils/playwright-mcp-client.d.ts +45 -0
  78. package/dist/kosuke/utils/playwright-mcp-client.d.ts.map +1 -0
  79. package/dist/kosuke/utils/playwright-mcp-client.js +131 -0
  80. package/dist/kosuke/utils/playwright-mcp-client.js.map +1 -0
  81. package/dist/lib.d.ts +4 -4
  82. package/dist/lib.d.ts.map +1 -1
  83. package/dist/lib.js +2 -1
  84. package/dist/lib.js.map +1 -1
  85. package/dist/package.json +7 -3
  86. package/package.json +7 -3
@@ -5,24 +5,232 @@
5
5
  * for all commands that use Claude Code Agent SDK.
6
6
  */
7
7
  import { query, } from '@anthropic-ai/claude-agent-sdk';
8
- import { existsSync } from 'fs';
9
- import { join } from 'path';
8
+ import { existsSync, readFileSync } from 'fs';
9
+ import { dirname, join } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
10
13
  /**
11
- * Calculate cost based on Claude Sonnet 4.5 pricing
12
- * - $3 per million input tokens
13
- * - $15 per million output tokens
14
- * - $3.75 per million cache creation tokens (input + 25% overhead)
15
- * - $0.30 per million cache read tokens (90% discount from input)
14
+ * Load costs configuration from claude.costs.json
16
15
  */
17
- export function calculateCost(inputTokens, outputTokens, cacheCreationTokens = 0, cacheReadTokens = 0) {
18
- const INPUT_COST_PER_MILLION = 3.0;
19
- const OUTPUT_COST_PER_MILLION = 15.0;
20
- const CACHE_CREATION_COST_PER_MILLION = 3.75;
21
- const CACHE_READ_COST_PER_MILLION = 0.3;
22
- const inputCost = (inputTokens / 1000000) * INPUT_COST_PER_MILLION;
23
- const outputCost = (outputTokens / 1000000) * OUTPUT_COST_PER_MILLION;
24
- const cacheCreationCost = (cacheCreationTokens / 1000000) * CACHE_CREATION_COST_PER_MILLION;
25
- const cacheReadCost = (cacheReadTokens / 1000000) * CACHE_READ_COST_PER_MILLION;
16
+ let costsCache = null;
17
+ function loadCosts() {
18
+ if (costsCache)
19
+ return costsCache;
20
+ try {
21
+ // Look for claude.costs.json in the package root (three levels up from dist/kosuke/utils)
22
+ const costsPath = join(__dirname, '..', '..', '..', 'claude.costs.json');
23
+ const costsContent = readFileSync(costsPath, 'utf-8');
24
+ costsCache = JSON.parse(costsContent);
25
+ return costsCache;
26
+ }
27
+ catch {
28
+ // Fallback to default Haiku costs if file not found
29
+ return {
30
+ models: {},
31
+ default: {
32
+ name: 'Claude Haiku 4.5',
33
+ input: 1.0,
34
+ output: 5.0,
35
+ cacheCreation: 1.25,
36
+ cacheRead: 0.1,
37
+ },
38
+ };
39
+ }
40
+ }
41
+ /**
42
+ * Get cost configuration for a specific model
43
+ */
44
+ function getModelCosts(model) {
45
+ const costs = loadCosts();
46
+ return costs.models[model] || costs.default;
47
+ }
48
+ /**
49
+ * Parse tool usage into structured action and params for streaming/display
50
+ *
51
+ * @param toolName - Name of the tool being used
52
+ * @param toolInput - Input parameters for the tool
53
+ * @returns Structured action and params object
54
+ *
55
+ * @example
56
+ * parseToolAction('Read', { file_path: '/path/to/file.ts' })
57
+ * // Returns: { action: 'Read', params: { path: '/path/to/file.ts' } }
58
+ */
59
+ export function parseToolAction(toolName, toolInput) {
60
+ const params = {};
61
+ switch (toolName) {
62
+ case 'Read':
63
+ params.path = toolInput.file_path || toolInput.target_file;
64
+ break;
65
+ case 'Grep':
66
+ params.pattern = toolInput.pattern;
67
+ if (toolInput.type)
68
+ params.type = toolInput.type;
69
+ if (toolInput.glob)
70
+ params.glob = toolInput.glob;
71
+ if (toolInput.path)
72
+ params.path = toolInput.path;
73
+ break;
74
+ case 'Glob':
75
+ params.pattern = toolInput.glob_pattern || toolInput.pattern;
76
+ if (toolInput.target_directory)
77
+ params.directory = toolInput.target_directory;
78
+ break;
79
+ case 'LS':
80
+ params.path = toolInput.target_directory;
81
+ break;
82
+ case 'Task':
83
+ {
84
+ const description = toolInput.description;
85
+ params.description = description;
86
+ if (toolInput.subagent_type)
87
+ params.type = toolInput.subagent_type;
88
+ }
89
+ break;
90
+ case 'WebSearch':
91
+ params.query = toolInput.search_term;
92
+ break;
93
+ case 'WebFetch':
94
+ {
95
+ const url = toolInput.url;
96
+ params.url = url;
97
+ if (url) {
98
+ try {
99
+ params.domain = new URL(url).hostname;
100
+ }
101
+ catch {
102
+ params.domain = undefined;
103
+ }
104
+ }
105
+ }
106
+ break;
107
+ case 'Edit':
108
+ case 'StrReplace':
109
+ case 'Write':
110
+ case 'Delete':
111
+ params.path = toolInput.file_path || toolInput.path;
112
+ break;
113
+ case 'Bash':
114
+ case 'Shell':
115
+ {
116
+ const command = toolInput.command;
117
+ params.command = command;
118
+ }
119
+ break;
120
+ case 'CodebaseSearch':
121
+ params.query = toolInput.query;
122
+ if (toolInput.target_directories) {
123
+ params.directories = toolInput.target_directories;
124
+ }
125
+ break;
126
+ default:
127
+ // For unknown tools, include all input params as fallback
128
+ Object.assign(params, toolInput);
129
+ break;
130
+ }
131
+ return { action: toolName, params };
132
+ }
133
+ /**
134
+ * Check if a tool is a file modification tool
135
+ * Based on actual tool names used in the codebase
136
+ */
137
+ function isFileModificationTool(toolName) {
138
+ return (toolName === 'Write' ||
139
+ toolName === 'StrReplace' ||
140
+ toolName === 'Edit' ||
141
+ toolName === 'Delete');
142
+ }
143
+ /**
144
+ * Log tool usage
145
+ */
146
+ export function logToolUsage(toolName, toolInput, filesReferenced) {
147
+ // Parse tool action using shared utility (handles normalization and params)
148
+ const input = toolInput && typeof toolInput === 'object' ? toolInput : {};
149
+ const parsed = parseToolAction(toolName, input);
150
+ // Track file references for Read operations
151
+ if (parsed.action === 'Read' && parsed.params.path) {
152
+ filesReferenced.add(parsed.params.path);
153
+ }
154
+ // Format log output based on tool type
155
+ let logMsg = '';
156
+ switch (parsed.action) {
157
+ case 'Write':
158
+ logMsg = ` ✍️ Writing ${parsed.params.path || 'file'}`;
159
+ break;
160
+ case 'StrReplace':
161
+ case 'Edit':
162
+ logMsg = ` ✏️ Editing ${parsed.params.path || 'file'}`;
163
+ break;
164
+ case 'Delete':
165
+ logMsg = ` 🗑️ Deleting ${parsed.params.path || 'file'}`;
166
+ break;
167
+ case 'Read':
168
+ logMsg = ` 📄 Reading ${parsed.params.path || 'file'}`;
169
+ break;
170
+ case 'Grep':
171
+ {
172
+ const pathInfo = parsed.params.path ? ` in ${parsed.params.path}` : '';
173
+ logMsg = ` 🔍 Searching: ${parsed.params.pattern}${pathInfo}`;
174
+ }
175
+ break;
176
+ case 'Glob':
177
+ {
178
+ const dirInfo = parsed.params.directory ? ` in ${parsed.params.directory}` : '';
179
+ logMsg = ` 📁 Finding: ${parsed.params.pattern}${dirInfo}`;
180
+ }
181
+ break;
182
+ case 'LS':
183
+ logMsg = ` 📂 Listing: ${parsed.params.path || 'directory'}`;
184
+ break;
185
+ case 'Task':
186
+ {
187
+ const taskType = parsed.params.type || 'Task';
188
+ logMsg = ` 🤖 ${taskType}: ${parsed.params.description || ''}`;
189
+ }
190
+ break;
191
+ case 'WebSearch':
192
+ logMsg = ` 🌐 Search: ${parsed.params.query}`;
193
+ break;
194
+ case 'WebFetch':
195
+ logMsg = ` 🌐 Fetching: ${parsed.params.domain || parsed.params.url}`;
196
+ break;
197
+ case 'Bash':
198
+ case 'Shell':
199
+ {
200
+ const cmd = parsed.params.command;
201
+ logMsg = ` 💻 Running: ${cmd}`;
202
+ }
203
+ break;
204
+ case 'CodebaseSearch':
205
+ {
206
+ const dirInfo = parsed.params.directories && Array.isArray(parsed.params.directories)
207
+ ? ` in ${parsed.params.directories.join(', ')}`
208
+ : '';
209
+ logMsg = ` 🔎 Codebase search: ${parsed.params.query || 'query'}${dirInfo}`;
210
+ }
211
+ break;
212
+ default:
213
+ // Fallback: show full tool info for unknown tools
214
+ const args = formatToolArgs(toolInput);
215
+ logMsg = ` 🔧 ${parsed.action}(${args})`;
216
+ break;
217
+ }
218
+ console.log(logMsg);
219
+ }
220
+ /**
221
+ * Calculate cost based on Claude pricing from claude.costs.json
222
+ *
223
+ * Costs are loaded from:
224
+ * 1. claude.costs.json model-specific costs (if model provided)
225
+ * 2. claude.costs.json default costs (fallback)
226
+ */
227
+ export function calculateCost(inputTokens, outputTokens, cacheCreationTokens = 0, cacheReadTokens = 0, model) {
228
+ // Get costs from JSON (uses model if provided, otherwise default)
229
+ const costs = model ? getModelCosts(model) : loadCosts().default;
230
+ const inputCost = (inputTokens / 1000000) * costs.input;
231
+ const outputCost = (outputTokens / 1000000) * costs.output;
232
+ const cacheCreationCost = (cacheCreationTokens / 1000000) * costs.cacheCreation;
233
+ const cacheReadCost = (cacheReadTokens / 1000000) * costs.cacheRead;
26
234
  return inputCost + outputCost + cacheCreationCost + cacheReadCost;
27
235
  }
28
236
  /**
@@ -72,7 +280,7 @@ function shouldLogText(text, verbosity) {
72
280
  /**
73
281
  * Log assistant message based on verbosity
74
282
  */
75
- function logAssistantMessage(text, verbosity) {
283
+ export function logAssistantMessage(text, verbosity) {
76
284
  const trimmed = text.trim();
77
285
  if (!trimmed || !shouldLogText(trimmed, verbosity)) {
78
286
  return;
@@ -90,72 +298,17 @@ function formatToolArgs(input) {
90
298
  return JSON.stringify(input);
91
299
  }
92
300
  /**
93
- * Log tool usage
94
- */
95
- function logToolUsage(toolName, toolInput, filesReferenced) {
96
- if (toolName === 'read_file' && toolInput && typeof toolInput === 'object') {
97
- const input = toolInput;
98
- if (input.target_file) {
99
- console.log(` 🔧 Reading ${input.target_file}`);
100
- filesReferenced.add(input.target_file);
101
- }
102
- }
103
- else if (toolName === 'grep' && toolInput && typeof toolInput === 'object') {
104
- const input = toolInput;
105
- const pathInfo = input.path ? ` in ${input.path}` : '';
106
- console.log(` 🔍 Searching for: ${input.pattern || 'pattern'}${pathInfo}`);
107
- }
108
- else if (toolName === 'glob_file_search' && toolInput && typeof toolInput === 'object') {
109
- const input = toolInput;
110
- const dirInfo = input.target_directory ? ` in ${input.target_directory}` : '';
111
- console.log(` 📁 Finding files: ${input.glob_pattern || '*'}${dirInfo}`);
112
- }
113
- else if (toolName === 'codebase_search' && toolInput && typeof toolInput === 'object') {
114
- const input = toolInput;
115
- const dirInfo = input.target_directories && input.target_directories.length > 0
116
- ? ` in ${input.target_directories.join(', ')}`
117
- : '';
118
- console.log(` 🔎 Searching codebase: ${input.query || 'query'}${dirInfo}`);
119
- }
120
- else if (toolName === 'write' ||
121
- toolName === 'search_replace' ||
122
- toolName === 'edit' ||
123
- toolName === 'edit_notebook' ||
124
- toolName === 'delete_file') {
125
- // Don't log here - fixCount will handle it
126
- }
127
- else if (toolName === 'run_terminal_cmd' && toolInput && typeof toolInput === 'object') {
128
- const input = toolInput;
129
- const cmd = input.command || 'command';
130
- console.log(` 🔧 Running: ${cmd}`);
131
- }
132
- else if (toolName === 'list_dir' && toolInput && typeof toolInput === 'object') {
133
- const input = toolInput;
134
- console.log(` 📂 Listing directory: ${input.target_directory || '.'}`);
135
- }
136
- else {
137
- // Generic tool logging with arguments
138
- const args = formatToolArgs(toolInput);
139
- console.log(` 🔧 ${toolName}(${args})`);
140
- }
141
- }
142
- /**
143
- * Run Claude agent with unified configuration and logging
301
+ * Run agent in streaming mode with typed events
302
+ * Yields Claude messages + final AgentResult with metrics
303
+ * For SSE integration and programmatic consumption
304
+ *
305
+ * @param prompt - User prompt/instruction
306
+ * @param config - Agent configuration
307
+ * @returns AsyncGenerator yielding AgentStreamEvent (messages + final result)
144
308
  */
145
- export async function runAgent(prompt, config) {
146
- const { systemPrompt, maxTurns = 20, model = 'claude-sonnet-4-5', cwd = process.cwd(), verbosity = 'normal', permissionMode = 'bypassPermissions', captureConversation = false, settingSources = ['project'], // Default: Load CLAUDE.md from project
147
- } = config;
148
- // Log the model being used
149
- console.log(`🤖 Using model: ${model}\n`);
150
- // Check if CLAUDE.md exists and warn if not (when loading from project)
151
- if (settingSources.includes('project')) {
152
- const claudePath = join(cwd, 'CLAUDE.md');
153
- if (!existsSync(claudePath)) {
154
- console.warn('⚠️ CLAUDE.md not found in project directory. Agent will use general coding best practices.\n');
155
- }
156
- }
157
- // Create AbortController for cleanup - the SDK spawns child processes
158
- // that need to be terminated after each query to prevent memory accumulation
309
+ export async function* runAgentStream(prompt, config) {
310
+ const { systemPrompt, maxTurns = 20, model = process.env.ANTHROPIC_MODEL || 'claude-haiku-4-5-20251001', cwd = process.cwd(), permissionMode = 'bypassPermissions', settingSources = ['project'], resume, allowedTools, disallowedTools, } = config;
311
+ // Create AbortController for SDK cleanup
159
312
  const abortController = new AbortController();
160
313
  const options = {
161
314
  model,
@@ -164,7 +317,10 @@ export async function runAgent(prompt, config) {
164
317
  cwd,
165
318
  permissionMode,
166
319
  settingSources,
167
- abortController, // Pass to SDK for process cleanup
320
+ abortController,
321
+ ...(resume && { resume }),
322
+ ...(allowedTools && { allowedTools }),
323
+ ...(disallowedTools && { disallowedTools }),
168
324
  };
169
325
  const responseStream = query({ prompt, options });
170
326
  let response = '';
@@ -174,6 +330,79 @@ export async function runAgent(prompt, config) {
174
330
  let cacheReadTokens = 0;
175
331
  let fixCount = 0;
176
332
  const filesReferenced = new Set();
333
+ let sessionId;
334
+ // Process the response stream
335
+ for await (const message of responseStream) {
336
+ const claudeMessage = message;
337
+ // Yield the message
338
+ yield { type: 'message', data: claudeMessage };
339
+ // Capture session ID
340
+ if (!sessionId && claudeMessage.session_id) {
341
+ sessionId = claudeMessage.session_id;
342
+ }
343
+ // Track metrics from assistant messages
344
+ if (claudeMessage.type === 'assistant' && claudeMessage.message?.content) {
345
+ for (const block of claudeMessage.message.content) {
346
+ if (block.type === 'text' && block.text) {
347
+ response += block.text;
348
+ }
349
+ else if (block.type === 'tool_use' && block.name) {
350
+ // Track fixes - count all file modification tools
351
+ if (isFileModificationTool(block.name)) {
352
+ fixCount++;
353
+ }
354
+ }
355
+ }
356
+ }
357
+ // Track token usage
358
+ if (claudeMessage.type === 'result' &&
359
+ claudeMessage.subtype === 'success' &&
360
+ claudeMessage.usage) {
361
+ inputTokens += claudeMessage.usage.input_tokens || 0;
362
+ outputTokens += claudeMessage.usage.output_tokens || 0;
363
+ cacheCreationTokens += claudeMessage.usage.cache_creation_input_tokens || 0;
364
+ cacheReadTokens += claudeMessage.usage.cache_read_input_tokens || 0;
365
+ }
366
+ }
367
+ // Calculate cost
368
+ const cost = calculateCost(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens);
369
+ // Yield final result
370
+ yield {
371
+ type: 'agent_result',
372
+ data: {
373
+ response: response.trim(),
374
+ tokensUsed: {
375
+ input: inputTokens,
376
+ output: outputTokens,
377
+ cacheCreation: cacheCreationTokens,
378
+ cacheRead: cacheReadTokens,
379
+ },
380
+ cost,
381
+ fixCount,
382
+ filesReferenced,
383
+ sessionId,
384
+ },
385
+ };
386
+ }
387
+ /**
388
+ * Run Claude agent with unified configuration and logging
389
+ * Processes stream and returns final result
390
+ */
391
+ export async function runAgent(prompt, config) {
392
+ const { verbosity = 'normal', captureConversation = false, settingSources = ['project'], cwd = process.cwd(), model = process.env.ANTHROPIC_MODEL || 'claude-haiku-4-5-20251001', } = config;
393
+ // Log the model being used
394
+ console.log(`🤖 Using model: ${model}\n`);
395
+ // Check if CLAUDE.md exists and warn if not (when loading from project)
396
+ if (settingSources.includes('project')) {
397
+ const claudePath = join(cwd, 'CLAUDE.md');
398
+ if (!existsSync(claudePath)) {
399
+ console.warn('⚠️ CLAUDE.md not found in project directory. Agent will use general coding best practices.\n');
400
+ }
401
+ }
402
+ // Get the stream
403
+ const stream = runAgentStream(prompt, config);
404
+ const filesReferenced = new Set();
405
+ let fixCount = 0;
177
406
  // Conversation capture (if enabled)
178
407
  const conversationMessages = [];
179
408
  // Track current assistant message and tool calls
@@ -186,117 +415,89 @@ export async function runAgent(prompt, config) {
186
415
  timestamp: new Date().toISOString(),
187
416
  });
188
417
  }
189
- // Process the response stream
190
- for await (const message of responseStream) {
191
- if (message.type === 'assistant') {
192
- const content = message.message.content;
193
- // Start new assistant message if capturing
194
- if (captureConversation && !currentMessage) {
195
- currentMessage = {
196
- role: 'assistant',
197
- content: '',
198
- timestamp: new Date().toISOString(),
199
- toolCalls: [],
200
- };
201
- }
202
- for (const block of content) {
203
- if (block.type === 'text' && block.text) {
204
- response += block.text;
205
- logAssistantMessage(block.text, verbosity);
206
- // Add to current message content
207
- if (captureConversation && currentMessage) {
208
- currentMessage.content += block.text;
209
- }
418
+ let result = null;
419
+ // Process the stream and add logging
420
+ for await (const event of stream) {
421
+ if (event.type === 'message') {
422
+ const message = event.data;
423
+ if (message.type === 'assistant' && message.message?.content) {
424
+ // Start new assistant message if capturing
425
+ if (captureConversation && !currentMessage) {
426
+ currentMessage = {
427
+ role: 'assistant',
428
+ content: '',
429
+ timestamp: new Date().toISOString(),
430
+ toolCalls: [],
431
+ };
210
432
  }
211
- else if (block.type === 'tool_use') {
212
- // Track fixes - count all file modification tools (case-insensitive)
213
- const toolNameLower = block.name.toLowerCase();
214
- const isFileModification = toolNameLower === 'write' ||
215
- toolNameLower === 'search_replace' ||
216
- toolNameLower === 'edit' ||
217
- toolNameLower === 'edit_notebook' ||
218
- toolNameLower === 'delete_file';
219
- if (isFileModification) {
220
- fixCount++;
221
- console.log(` 🔧 Applying fix ${fixCount}... (${block.name})`);
222
- }
223
- else {
224
- logToolUsage(block.name, block.input, filesReferenced);
433
+ for (const block of message.message.content) {
434
+ if (block.type === 'text' && block.text) {
435
+ logAssistantMessage(block.text, verbosity);
436
+ // Add to current message content
437
+ if (captureConversation && currentMessage) {
438
+ currentMessage.content += block.text;
439
+ }
225
440
  }
226
- // Capture tool call
227
- if (captureConversation && currentMessage) {
228
- currentMessage.toolCalls.push({
229
- name: block.name,
230
- input: block.input,
231
- // Output will be added when we receive tool_result
232
- });
441
+ else if (block.type === 'tool_use' && block.name) {
442
+ // Track fixes - count all file modification tools
443
+ if (isFileModificationTool(block.name)) {
444
+ fixCount++;
445
+ console.log(` 🔧 Applying fix ${fixCount}... (${block.name})`);
446
+ }
447
+ else {
448
+ logToolUsage(block.name, block.input, filesReferenced);
449
+ }
450
+ // Capture tool call
451
+ if (captureConversation && currentMessage) {
452
+ currentMessage.toolCalls.push({
453
+ name: block.name,
454
+ input: block.input,
455
+ });
456
+ }
233
457
  }
234
458
  }
235
459
  }
236
- }
237
- // Capture tool results
238
- // Note: tool_result is not part of the message stream type, so we skip this for now
239
- // Tool outputs are captured through the SDK's internal handling
240
- // Track token usage
241
- if (message.type === 'result' && message.subtype === 'success') {
242
- const input = message.usage.input_tokens || 0;
243
- const output = message.usage.output_tokens || 0;
244
- const cacheCreation = message.usage.cache_creation_input_tokens || 0;
245
- const cacheRead = message.usage.cache_read_input_tokens || 0;
246
- inputTokens += input;
247
- outputTokens += output;
248
- cacheCreationTokens += cacheCreation;
249
- cacheReadTokens += cacheRead;
250
- // Debug logging for token tracking (only if verbose)
251
- if (verbosity === 'verbose' && (input > 0 || output > 0)) {
252
- console.log(` 📊 Turn tokens: ${input} in, ${output} out, ${cacheCreation} cache write, ${cacheRead} cache read`);
460
+ // Track token usage for verbose logging
461
+ if (message.type === 'result' && message.subtype === 'success' && message.usage) {
462
+ const input = message.usage.input_tokens || 0;
463
+ const output = message.usage.output_tokens || 0;
464
+ const cacheCreation = message.usage.cache_creation_input_tokens || 0;
465
+ const cacheRead = message.usage.cache_read_input_tokens || 0;
466
+ // Debug logging for token tracking (only if verbose)
467
+ if (verbosity === 'verbose' && (input > 0 || output > 0)) {
468
+ console.log(` 📊 Turn tokens: ${input} in, ${output} out, ${cacheCreation} cache write, ${cacheRead} cache read`);
469
+ }
470
+ // Save current message when turn completes
471
+ if (captureConversation && currentMessage) {
472
+ conversationMessages.push({
473
+ role: currentMessage.role,
474
+ content: currentMessage.content,
475
+ timestamp: currentMessage.timestamp,
476
+ toolCalls: currentMessage.toolCalls.length > 0 ? currentMessage.toolCalls : undefined,
477
+ });
478
+ currentMessage = null; // Reset for next turn
479
+ }
253
480
  }
254
- // Save current message when turn completes
255
- if (captureConversation && currentMessage) {
256
- conversationMessages.push({
257
- role: currentMessage.role,
258
- content: currentMessage.content,
259
- timestamp: currentMessage.timestamp,
260
- toolCalls: currentMessage.toolCalls.length > 0 ? currentMessage.toolCalls : undefined,
261
- });
262
- currentMessage = null; // Reset for next turn
481
+ }
482
+ else if (event.type === 'agent_result') {
483
+ result = event.data;
484
+ // Debug logging for final totals
485
+ if (verbosity === 'verbose') {
486
+ console.log(`\n 📊 Final totals:`);
487
+ console.log(` • Fixes applied: ${result.fixCount}`);
488
+ console.log(` • Total input tokens: ${result.tokensUsed.input.toLocaleString()}`);
489
+ console.log(` • Total output tokens: ${result.tokensUsed.output.toLocaleString()}`);
490
+ console.log(` • Total cost: $${result.cost.toFixed(4)}`);
263
491
  }
264
492
  }
265
493
  }
266
- // Calculate cost
267
- const cost = calculateCost(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens);
268
- // Debug logging for final totals
269
- if (verbosity === 'verbose') {
270
- console.log(`\n 📊 Final totals:`);
271
- console.log(` • Fixes applied: ${fixCount}`);
272
- console.log(` • Total input tokens: ${inputTokens.toLocaleString()}`);
273
- console.log(` • Total output tokens: ${outputTokens.toLocaleString()}`);
274
- console.log(` • Total cost: $${cost.toFixed(4)}`);
494
+ if (!result) {
495
+ throw new Error('Agent stream ended without result');
275
496
  }
276
- const result = {
277
- response: response.trim(),
278
- tokensUsed: {
279
- input: inputTokens,
280
- output: outputTokens,
281
- cacheCreation: cacheCreationTokens,
282
- cacheRead: cacheReadTokens,
283
- },
284
- cost,
285
- fixCount,
286
- filesReferenced,
287
- };
288
497
  // Include conversation if captured
289
498
  if (captureConversation && conversationMessages.length > 0) {
290
499
  result.conversationMessages = conversationMessages;
291
500
  }
292
- // Cleanup: Abort the controller to terminate any child processes spawned by the SDK
293
- // This prevents memory accumulation when running multiple agents sequentially (e.g., kosuke build)
294
- try {
295
- abortController.abort();
296
- }
297
- catch {
298
- // Ignore abort errors - cleanup is best-effort
299
- }
300
501
  return result;
301
502
  }
302
503
  //# sourceMappingURL=claude-agent.js.map