@kosuke-ai/cli 0.0.44 → 0.0.46
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 +94 -1
- package/claude.costs.json +34 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -1
- package/dist/kosuke/commands/build.d.ts +5 -1
- package/dist/kosuke/commands/build.d.ts.map +1 -1
- package/dist/kosuke/commands/build.js +297 -3
- package/dist/kosuke/commands/build.js.map +1 -1
- package/dist/kosuke/commands/migrate.d.ts +6 -1
- package/dist/kosuke/commands/migrate.d.ts.map +1 -1
- package/dist/kosuke/commands/migrate.js +124 -37
- package/dist/kosuke/commands/migrate.js.map +1 -1
- package/dist/kosuke/commands/plan.d.ts +4 -20
- package/dist/kosuke/commands/plan.d.ts.map +1 -1
- package/dist/kosuke/commands/plan.js +471 -679
- package/dist/kosuke/commands/plan.js.map +1 -1
- package/dist/kosuke/commands/requirements.js +2 -2
- package/dist/kosuke/commands/requirements.js.map +1 -1
- package/dist/kosuke/commands/review.d.ts +6 -1
- package/dist/kosuke/commands/review.d.ts.map +1 -1
- package/dist/kosuke/commands/review.js +168 -68
- package/dist/kosuke/commands/review.js.map +1 -1
- package/dist/kosuke/commands/serve.d.ts +9 -0
- package/dist/kosuke/commands/serve.d.ts.map +1 -0
- package/dist/kosuke/commands/serve.js +17 -0
- package/dist/kosuke/commands/serve.js.map +1 -0
- package/dist/kosuke/commands/ship.d.ts +6 -1
- package/dist/kosuke/commands/ship.d.ts.map +1 -1
- package/dist/kosuke/commands/ship.js +226 -67
- package/dist/kosuke/commands/ship.js.map +1 -1
- package/dist/kosuke/commands/test.d.ts +6 -1
- package/dist/kosuke/commands/test.d.ts.map +1 -1
- package/dist/kosuke/commands/test.js +171 -73
- package/dist/kosuke/commands/test.js.map +1 -1
- package/dist/kosuke/commands/tickets.d.ts +7 -15
- package/dist/kosuke/commands/tickets.d.ts.map +1 -1
- package/dist/kosuke/commands/tickets.js +14 -58
- package/dist/kosuke/commands/tickets.js.map +1 -1
- package/dist/kosuke/serve/middleware/error-handler.d.ts +13 -0
- package/dist/kosuke/serve/middleware/error-handler.d.ts.map +1 -0
- package/dist/kosuke/serve/middleware/error-handler.js +34 -0
- package/dist/kosuke/serve/middleware/error-handler.js.map +1 -0
- package/dist/kosuke/serve/middleware/validation.d.ts +13 -0
- package/dist/kosuke/serve/middleware/validation.d.ts.map +1 -0
- package/dist/kosuke/serve/middleware/validation.js +39 -0
- package/dist/kosuke/serve/middleware/validation.js.map +1 -0
- package/dist/kosuke/serve/routes/build.d.ts +6 -0
- package/dist/kosuke/serve/routes/build.d.ts.map +1 -0
- package/dist/kosuke/serve/routes/build.js +224 -0
- package/dist/kosuke/serve/routes/build.js.map +1 -0
- package/dist/kosuke/serve/routes/health.d.ts +6 -0
- package/dist/kosuke/serve/routes/health.d.ts.map +1 -0
- package/dist/kosuke/serve/routes/health.js +11 -0
- package/dist/kosuke/serve/routes/health.js.map +1 -0
- package/dist/kosuke/serve/routes/plan.d.ts +6 -0
- package/dist/kosuke/serve/routes/plan.d.ts.map +1 -0
- package/dist/kosuke/serve/routes/plan.js +104 -0
- package/dist/kosuke/serve/routes/plan.js.map +1 -0
- package/dist/kosuke/serve/server.d.ts +16 -0
- package/dist/kosuke/serve/server.d.ts.map +1 -0
- package/dist/kosuke/serve/server.js +63 -0
- package/dist/kosuke/serve/server.js.map +1 -0
- package/dist/kosuke/serve/validation/build.d.ts +41 -0
- package/dist/kosuke/serve/validation/build.d.ts.map +1 -0
- package/dist/kosuke/serve/validation/build.js +20 -0
- package/dist/kosuke/serve/validation/build.js.map +1 -0
- package/dist/kosuke/serve/validation/plan.d.ts +26 -0
- package/dist/kosuke/serve/validation/plan.d.ts.map +1 -0
- package/dist/kosuke/serve/validation/plan.js +15 -0
- package/dist/kosuke/serve/validation/plan.js.map +1 -0
- package/dist/kosuke/types.d.ts +205 -0
- package/dist/kosuke/types.d.ts.map +1 -1
- package/dist/kosuke/utils/claude-agent.d.ts +82 -6
- package/dist/kosuke/utils/claude-agent.d.ts.map +1 -1
- package/dist/kosuke/utils/claude-agent.js +381 -180
- package/dist/kosuke/utils/claude-agent.js.map +1 -1
- package/dist/lib.d.ts +4 -4
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +2 -1
- package/dist/lib.js.map +1 -1
- package/dist/package.json +6 -2
- package/package.json +6 -2
|
@@ -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
|
-
*
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
*
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
146
|
-
const { systemPrompt, maxTurns = 20, model = 'claude-
|
|
147
|
-
|
|
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,
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
});
|
|
262
|
-
|
|
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
|
-
|
|
267
|
-
|
|
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
|