gencode-ai 0.1.2 → 0.2.0
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 +15 -17
- package/dist/agent/agent.d.ts +43 -0
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +107 -4
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/types.d.ts +20 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/checkpointing/checkpoint-manager.d.ts +87 -0
- package/dist/checkpointing/checkpoint-manager.d.ts.map +1 -0
- package/dist/checkpointing/checkpoint-manager.js +281 -0
- package/dist/checkpointing/checkpoint-manager.js.map +1 -0
- package/dist/checkpointing/index.d.ts +29 -0
- package/dist/checkpointing/index.d.ts.map +1 -0
- package/dist/checkpointing/index.js +29 -0
- package/dist/checkpointing/index.js.map +1 -0
- package/dist/checkpointing/types.d.ts +98 -0
- package/dist/checkpointing/types.d.ts.map +1 -0
- package/dist/checkpointing/types.js +7 -0
- package/dist/checkpointing/types.js.map +1 -0
- package/dist/cli/components/App.d.ts.map +1 -1
- package/dist/cli/components/App.js +193 -7
- package/dist/cli/components/App.js.map +1 -1
- package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
- package/dist/cli/components/CommandSuggestions.js +5 -0
- package/dist/cli/components/CommandSuggestions.js.map +1 -1
- package/dist/cli/components/Messages.d.ts +7 -1
- package/dist/cli/components/Messages.d.ts.map +1 -1
- package/dist/cli/components/Messages.js +28 -2
- package/dist/cli/components/Messages.js.map +1 -1
- package/dist/cli/components/ModeIndicator.d.ts +42 -0
- package/dist/cli/components/ModeIndicator.d.ts.map +1 -0
- package/dist/cli/components/ModeIndicator.js +52 -0
- package/dist/cli/components/ModeIndicator.js.map +1 -0
- package/dist/cli/components/PlanApproval.d.ts +36 -0
- package/dist/cli/components/PlanApproval.d.ts.map +1 -0
- package/dist/cli/components/PlanApproval.js +154 -0
- package/dist/cli/components/PlanApproval.js.map +1 -0
- package/dist/cli/components/QuestionPrompt.d.ts +23 -0
- package/dist/cli/components/QuestionPrompt.d.ts.map +1 -0
- package/dist/cli/components/QuestionPrompt.js +231 -0
- package/dist/cli/components/QuestionPrompt.js.map +1 -0
- package/dist/cli/components/index.d.ts +1 -0
- package/dist/cli/components/index.d.ts.map +1 -1
- package/dist/cli/components/index.js +1 -0
- package/dist/cli/components/index.js.map +1 -1
- package/dist/cli/components/theme.d.ts +9 -0
- package/dist/cli/components/theme.d.ts.map +1 -1
- package/dist/cli/components/theme.js +14 -1
- package/dist/cli/components/theme.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/permissions/types.d.ts.map +1 -1
- package/dist/permissions/types.js +2 -0
- package/dist/permissions/types.js.map +1 -1
- package/dist/planning/index.d.ts +13 -0
- package/dist/planning/index.d.ts.map +1 -0
- package/dist/planning/index.js +15 -0
- package/dist/planning/index.js.map +1 -0
- package/dist/planning/plan-file.d.ts +59 -0
- package/dist/planning/plan-file.d.ts.map +1 -0
- package/dist/planning/plan-file.js +278 -0
- package/dist/planning/plan-file.js.map +1 -0
- package/dist/planning/state.d.ts +127 -0
- package/dist/planning/state.d.ts.map +1 -0
- package/dist/planning/state.js +261 -0
- package/dist/planning/state.js.map +1 -0
- package/dist/planning/tools/enter-plan-mode.d.ts +25 -0
- package/dist/planning/tools/enter-plan-mode.d.ts.map +1 -0
- package/dist/planning/tools/enter-plan-mode.js +98 -0
- package/dist/planning/tools/enter-plan-mode.js.map +1 -0
- package/dist/planning/tools/exit-plan-mode.d.ts +24 -0
- package/dist/planning/tools/exit-plan-mode.d.ts.map +1 -0
- package/dist/planning/tools/exit-plan-mode.js +149 -0
- package/dist/planning/tools/exit-plan-mode.js.map +1 -0
- package/dist/planning/types.d.ts +100 -0
- package/dist/planning/types.d.ts.map +1 -0
- package/dist/planning/types.js +28 -0
- package/dist/planning/types.js.map +1 -0
- package/dist/pricing/calculator.d.ts +21 -0
- package/dist/pricing/calculator.d.ts.map +1 -0
- package/dist/pricing/calculator.js +59 -0
- package/dist/pricing/calculator.js.map +1 -0
- package/dist/pricing/index.d.ts +7 -0
- package/dist/pricing/index.d.ts.map +1 -0
- package/dist/pricing/index.js +7 -0
- package/dist/pricing/index.js.map +1 -0
- package/dist/pricing/models.d.ts +20 -0
- package/dist/pricing/models.d.ts.map +1 -0
- package/dist/pricing/models.js +322 -0
- package/dist/pricing/models.js.map +1 -0
- package/dist/pricing/types.d.ts +30 -0
- package/dist/pricing/types.d.ts.map +1 -0
- package/dist/pricing/types.js +5 -0
- package/dist/pricing/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +17 -10
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +21 -14
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +12 -8
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/types.d.ts +2 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/vertex-ai.d.ts.map +1 -1
- package/dist/providers/vertex-ai.js +17 -10
- package/dist/providers/vertex-ai.js.map +1 -1
- package/dist/session/manager.d.ts +4 -0
- package/dist/session/manager.d.ts.map +1 -1
- package/dist/session/manager.js +8 -0
- package/dist/session/manager.js.map +1 -1
- package/dist/tools/builtin/ask-user.d.ts +64 -0
- package/dist/tools/builtin/ask-user.d.ts.map +1 -0
- package/dist/tools/builtin/ask-user.js +148 -0
- package/dist/tools/builtin/ask-user.js.map +1 -0
- package/dist/tools/index.d.ts +19 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +11 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/registry.d.ts +13 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +79 -2
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/types.d.ts +17 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js.map +1 -1
- package/docs/cost-tracking-comparison.md +904 -0
- package/docs/operating-modes.md +96 -0
- package/docs/proposals/0012-ask-user-question.md +66 -1
- package/docs/proposals/0025-cost-tracking.md +60 -2
- package/docs/proposals/README.md +2 -2
- package/examples/test-ask-user.ts +167 -0
- package/examples/test-checkpointing.ts +121 -0
- package/examples/test-cost-tracking.ts +77 -0
- package/examples/test-interrupt-cleanup.ts +94 -0
- package/package.json +1 -1
- package/src/agent/agent.ts +130 -4
- package/src/agent/index.ts +1 -0
- package/src/agent/types.ts +19 -1
- package/src/checkpointing/checkpoint-manager.ts +327 -0
- package/src/checkpointing/index.ts +45 -0
- package/src/checkpointing/types.ts +104 -0
- package/src/cli/components/App.tsx +259 -8
- package/src/cli/components/CommandSuggestions.tsx +5 -0
- package/src/cli/components/Messages.tsx +66 -4
- package/src/cli/components/ModeIndicator.tsx +174 -0
- package/src/cli/components/PlanApproval.tsx +327 -0
- package/src/cli/components/QuestionPrompt.tsx +462 -0
- package/src/cli/components/index.ts +1 -0
- package/src/cli/components/theme.ts +14 -1
- package/src/index.ts +15 -0
- package/src/permissions/types.ts +2 -0
- package/src/planning/index.ts +53 -0
- package/src/planning/plan-file.ts +326 -0
- package/src/planning/state.ts +305 -0
- package/src/planning/tools/enter-plan-mode.ts +111 -0
- package/src/planning/tools/exit-plan-mode.ts +170 -0
- package/src/planning/types.ts +150 -0
- package/src/pricing/calculator.ts +71 -0
- package/src/pricing/index.ts +7 -0
- package/src/pricing/models.ts +334 -0
- package/src/pricing/types.ts +32 -0
- package/src/prompts/system/base.txt +42 -0
- package/src/prompts/tools/ask-user.txt +110 -0
- package/src/providers/anthropic.ts +21 -10
- package/src/providers/gemini.ts +25 -14
- package/src/providers/openai.ts +17 -8
- package/src/providers/types.ts +3 -0
- package/src/providers/vertex-ai.ts +21 -10
- package/src/session/manager.ts +9 -0
- package/src/tools/builtin/ask-user.ts +185 -0
- package/src/tools/index.ts +23 -0
- package/src/tools/registry.ts +95 -2
- package/src/tools/types.ts +18 -0
- package/.gencode/settings.local.json +0 -7
package/src/providers/openai.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import OpenAI from 'openai';
|
|
7
|
+
import { calculateCost } from '../pricing/calculator.js';
|
|
7
8
|
import type {
|
|
8
9
|
LLMProvider,
|
|
9
10
|
CompletionOptions,
|
|
@@ -44,7 +45,7 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
44
45
|
temperature: options.temperature,
|
|
45
46
|
});
|
|
46
47
|
|
|
47
|
-
return this.convertResponse(response);
|
|
48
|
+
return this.convertResponse(response, options.model);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
async *stream(options: CompletionOptions): AsyncGenerator<StreamChunk, void, unknown> {
|
|
@@ -195,7 +196,10 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
195
196
|
}));
|
|
196
197
|
}
|
|
197
198
|
|
|
198
|
-
private convertResponse(
|
|
199
|
+
private convertResponse(
|
|
200
|
+
response: OpenAI.Chat.Completions.ChatCompletion,
|
|
201
|
+
model: string
|
|
202
|
+
): CompletionResponse {
|
|
199
203
|
const choice = response.choices[0];
|
|
200
204
|
const content: MessageContent[] = [];
|
|
201
205
|
|
|
@@ -216,15 +220,20 @@ export class OpenAIProvider implements LLMProvider {
|
|
|
216
220
|
}
|
|
217
221
|
}
|
|
218
222
|
|
|
223
|
+
const usage = response.usage
|
|
224
|
+
? {
|
|
225
|
+
inputTokens: response.usage.prompt_tokens,
|
|
226
|
+
outputTokens: response.usage.completion_tokens,
|
|
227
|
+
}
|
|
228
|
+
: undefined;
|
|
229
|
+
|
|
230
|
+
const cost = usage ? calculateCost(this.name, model, usage) : undefined;
|
|
231
|
+
|
|
219
232
|
return {
|
|
220
233
|
content,
|
|
221
234
|
stopReason: this.convertStopReason(choice.finish_reason),
|
|
222
|
-
usage
|
|
223
|
-
|
|
224
|
-
inputTokens: response.usage.prompt_tokens,
|
|
225
|
-
outputTokens: response.usage.completion_tokens,
|
|
226
|
-
}
|
|
227
|
-
: undefined,
|
|
235
|
+
usage,
|
|
236
|
+
cost,
|
|
228
237
|
};
|
|
229
238
|
}
|
|
230
239
|
|
package/src/providers/types.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Abstracts differences between OpenAI, Anthropic, and Gemini APIs
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type { CostEstimate } from '../pricing/types.js';
|
|
7
|
+
|
|
6
8
|
// ============================================================================
|
|
7
9
|
// Message Types
|
|
8
10
|
// ============================================================================
|
|
@@ -92,6 +94,7 @@ export interface CompletionResponse {
|
|
|
92
94
|
inputTokens: number;
|
|
93
95
|
outputTokens: number;
|
|
94
96
|
};
|
|
97
|
+
cost?: CostEstimate;
|
|
95
98
|
}
|
|
96
99
|
|
|
97
100
|
// ============================================================================
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { GoogleAuth } from 'google-auth-library';
|
|
12
|
+
import { calculateCost } from '../pricing/calculator.js';
|
|
12
13
|
import type {
|
|
13
14
|
LLMProvider,
|
|
14
15
|
CompletionOptions,
|
|
@@ -210,7 +211,7 @@ export class VertexAIProvider implements LLMProvider {
|
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
const data = (await response.json()) as VertexAIResponse;
|
|
213
|
-
return this.convertResponse(data);
|
|
214
|
+
return this.convertResponse(data, options.model);
|
|
214
215
|
}
|
|
215
216
|
|
|
216
217
|
async *stream(options: CompletionOptions): AsyncGenerator<StreamChunk, void, unknown> {
|
|
@@ -337,15 +338,20 @@ export class VertexAIProvider implements LLMProvider {
|
|
|
337
338
|
// Build final response
|
|
338
339
|
const content = this.buildFinalContent(contentBlocks, toolInputBuffers);
|
|
339
340
|
|
|
341
|
+
const usage = {
|
|
342
|
+
inputTokens,
|
|
343
|
+
outputTokens,
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const cost = calculateCost(this.name, options.model, usage);
|
|
347
|
+
|
|
340
348
|
yield {
|
|
341
349
|
type: 'done',
|
|
342
350
|
response: {
|
|
343
351
|
content,
|
|
344
352
|
stopReason,
|
|
345
|
-
usage
|
|
346
|
-
|
|
347
|
-
outputTokens,
|
|
348
|
-
},
|
|
353
|
+
usage,
|
|
354
|
+
cost,
|
|
349
355
|
},
|
|
350
356
|
};
|
|
351
357
|
}
|
|
@@ -449,14 +455,19 @@ export class VertexAIProvider implements LLMProvider {
|
|
|
449
455
|
}));
|
|
450
456
|
}
|
|
451
457
|
|
|
452
|
-
private convertResponse(response: VertexAIResponse): CompletionResponse {
|
|
458
|
+
private convertResponse(response: VertexAIResponse, model: string): CompletionResponse {
|
|
459
|
+
const usage = {
|
|
460
|
+
inputTokens: response.usage.input_tokens,
|
|
461
|
+
outputTokens: response.usage.output_tokens,
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const cost = calculateCost(this.name, model, usage);
|
|
465
|
+
|
|
453
466
|
return {
|
|
454
467
|
content: this.convertContent(response.content),
|
|
455
468
|
stopReason: this.convertStopReason(response.stop_reason),
|
|
456
|
-
usage
|
|
457
|
-
|
|
458
|
-
outputTokens: response.usage.output_tokens,
|
|
459
|
-
},
|
|
469
|
+
usage,
|
|
470
|
+
cost,
|
|
460
471
|
};
|
|
461
472
|
}
|
|
462
473
|
|
package/src/session/manager.ts
CHANGED
|
@@ -326,6 +326,15 @@ export class SessionManager {
|
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Remove the last message from current session
|
|
331
|
+
*/
|
|
332
|
+
removeLastMessage(): void {
|
|
333
|
+
if (this.currentSession && this.currentSession.messages.length > 0) {
|
|
334
|
+
this.currentSession.messages.pop();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
329
338
|
/**
|
|
330
339
|
* Export session to JSON
|
|
331
340
|
*/
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AskUserQuestion Tool - Structured user questioning
|
|
3
|
+
*
|
|
4
|
+
* Allows the agent to pause execution and present structured questions
|
|
5
|
+
* to the user with predefined options. Supports single-select, multi-select,
|
|
6
|
+
* and custom "Other" input.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import type { Tool, ToolResult, ToolContext } from '../types.js';
|
|
11
|
+
import { loadToolDescription } from '../../prompts/index.js';
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Zod Schemas
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
export const QuestionOptionSchema = z.object({
|
|
18
|
+
label: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.max(50)
|
|
22
|
+
.describe('Display text for this option (1-5 words, concise)'),
|
|
23
|
+
description: z
|
|
24
|
+
.string()
|
|
25
|
+
.min(1)
|
|
26
|
+
.max(200)
|
|
27
|
+
.describe('Explanation of what this option means or implications'),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const QuestionSchema = z.object({
|
|
31
|
+
question: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.describe('The complete question to ask the user, ending with a question mark'),
|
|
35
|
+
header: z
|
|
36
|
+
.string()
|
|
37
|
+
.min(1)
|
|
38
|
+
.max(12)
|
|
39
|
+
.describe('Very short label displayed as a chip/tag (max 12 chars)'),
|
|
40
|
+
options: z
|
|
41
|
+
.array(QuestionOptionSchema)
|
|
42
|
+
.min(2)
|
|
43
|
+
.max(4)
|
|
44
|
+
.describe('2-4 options for the user to choose from'),
|
|
45
|
+
multiSelect: z
|
|
46
|
+
.boolean()
|
|
47
|
+
.describe('Set to true to allow multiple selections, false for single choice'),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const AskUserQuestionInputSchema = z.object({
|
|
51
|
+
questions: z
|
|
52
|
+
.array(QuestionSchema)
|
|
53
|
+
.min(1)
|
|
54
|
+
.max(4)
|
|
55
|
+
.describe('1-4 questions to ask the user'),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// Types
|
|
60
|
+
// ============================================================================
|
|
61
|
+
|
|
62
|
+
export type QuestionOption = z.infer<typeof QuestionOptionSchema>;
|
|
63
|
+
export type Question = z.infer<typeof QuestionSchema>;
|
|
64
|
+
export type AskUserQuestionInput = z.infer<typeof AskUserQuestionInputSchema>;
|
|
65
|
+
|
|
66
|
+
export interface QuestionAnswer {
|
|
67
|
+
question: string;
|
|
68
|
+
header: string;
|
|
69
|
+
selectedOptions: string[];
|
|
70
|
+
customInput?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface AskUserQuestionResult {
|
|
74
|
+
answers: QuestionAnswer[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============================================================================
|
|
78
|
+
// Answer Formatting
|
|
79
|
+
// ============================================================================
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Format answers for display to the agent
|
|
83
|
+
*/
|
|
84
|
+
export function formatAnswersForAgent(answers: QuestionAnswer[]): string {
|
|
85
|
+
const lines: string[] = ['User answered the following questions:', ''];
|
|
86
|
+
|
|
87
|
+
answers.forEach((answer, index) => {
|
|
88
|
+
lines.push(`${index + 1}. ${answer.header} (${answer.question})`);
|
|
89
|
+
|
|
90
|
+
if (answer.selectedOptions.length > 0) {
|
|
91
|
+
lines.push(` Selected: ${answer.selectedOptions.join(', ')}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (answer.customInput) {
|
|
95
|
+
lines.push(` Custom input: ${answer.customInput}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lines.push('');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
lines.push('Proceeding with user selections.');
|
|
102
|
+
|
|
103
|
+
return lines.join('\n');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Format answers for CLI confirmation display
|
|
108
|
+
*/
|
|
109
|
+
export function formatAnswersForDisplay(answers: QuestionAnswer[]): string {
|
|
110
|
+
return answers
|
|
111
|
+
.map((answer) => {
|
|
112
|
+
const selections = answer.customInput
|
|
113
|
+
? [...answer.selectedOptions, answer.customInput].join(', ')
|
|
114
|
+
: answer.selectedOptions.join(', ');
|
|
115
|
+
return `✔ ${answer.header}: ${selections}`;
|
|
116
|
+
})
|
|
117
|
+
.join('\n');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ============================================================================
|
|
121
|
+
// Tool Implementation
|
|
122
|
+
// ============================================================================
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* AskUserQuestion tool
|
|
126
|
+
*
|
|
127
|
+
* This tool is special - it doesn't execute immediately but signals
|
|
128
|
+
* the agent loop to pause and wait for user input via the CLI.
|
|
129
|
+
*
|
|
130
|
+
* The actual questioning is handled by the CLI layer (QuestionPrompt component).
|
|
131
|
+
* This tool just validates the input and returns a marker for the agent loop.
|
|
132
|
+
*/
|
|
133
|
+
export const askUserQuestionTool: Tool<AskUserQuestionInput> = {
|
|
134
|
+
name: 'AskUserQuestion',
|
|
135
|
+
description: loadToolDescription('ask-user'),
|
|
136
|
+
parameters: AskUserQuestionInputSchema,
|
|
137
|
+
|
|
138
|
+
async execute(input: AskUserQuestionInput, context: ToolContext): Promise<ToolResult> {
|
|
139
|
+
// Validation is handled by Zod schema
|
|
140
|
+
// Additional validation for recommended options format
|
|
141
|
+
for (const question of input.questions) {
|
|
142
|
+
// Check if first option has (Recommended) - this is just a hint, not enforced
|
|
143
|
+
const firstOption = question.options[0];
|
|
144
|
+
if (firstOption && !firstOption.label.includes('(Recommended)')) {
|
|
145
|
+
// This is fine - recommended suffix is optional
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Check if context has askUser callback
|
|
150
|
+
if (context.askUser) {
|
|
151
|
+
try {
|
|
152
|
+
const answers = await context.askUser(input.questions);
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
output: formatAnswersForAgent(answers),
|
|
156
|
+
metadata: {
|
|
157
|
+
title: 'AskUserQuestion',
|
|
158
|
+
subtitle: `${answers.length} answer(s) received`,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
} catch (error) {
|
|
162
|
+
return {
|
|
163
|
+
success: false,
|
|
164
|
+
output: '',
|
|
165
|
+
error: `Failed to get user response: ${error instanceof Error ? error.message : String(error)}`,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// If no askUser callback, return a special marker
|
|
171
|
+
// The agent loop should detect this and handle it specially
|
|
172
|
+
return {
|
|
173
|
+
success: true,
|
|
174
|
+
output: JSON.stringify({
|
|
175
|
+
type: 'ask_user_question',
|
|
176
|
+
questions: input.questions,
|
|
177
|
+
requiresUserInput: true,
|
|
178
|
+
}),
|
|
179
|
+
metadata: {
|
|
180
|
+
title: 'AskUserQuestion',
|
|
181
|
+
subtitle: `${input.questions.length} question(s) pending`,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
};
|
package/src/tools/index.ts
CHANGED
|
@@ -15,6 +15,21 @@ export { grepTool } from './builtin/grep.js';
|
|
|
15
15
|
export { webfetchTool } from './builtin/webfetch.js';
|
|
16
16
|
export { websearchTool } from './builtin/websearch.js';
|
|
17
17
|
export { todowriteTool, getTodos, clearTodos } from './builtin/todowrite.js';
|
|
18
|
+
export {
|
|
19
|
+
askUserQuestionTool,
|
|
20
|
+
formatAnswersForAgent,
|
|
21
|
+
formatAnswersForDisplay,
|
|
22
|
+
} from './builtin/ask-user.js';
|
|
23
|
+
export type {
|
|
24
|
+
Question as AskUserQuestion,
|
|
25
|
+
QuestionOption as AskUserQuestionOption,
|
|
26
|
+
QuestionAnswer as AskUserQuestionAnswer,
|
|
27
|
+
AskUserQuestionInput,
|
|
28
|
+
AskUserQuestionResult,
|
|
29
|
+
} from './builtin/ask-user.js';
|
|
30
|
+
|
|
31
|
+
// Plan mode tools
|
|
32
|
+
export { enterPlanModeTool, exitPlanModeTool } from '../planning/index.js';
|
|
18
33
|
|
|
19
34
|
import { ToolRegistry } from './registry.js';
|
|
20
35
|
import { readTool } from './builtin/read.js';
|
|
@@ -26,6 +41,8 @@ import { grepTool } from './builtin/grep.js';
|
|
|
26
41
|
import { webfetchTool } from './builtin/webfetch.js';
|
|
27
42
|
import { websearchTool } from './builtin/websearch.js';
|
|
28
43
|
import { todowriteTool } from './builtin/todowrite.js';
|
|
44
|
+
import { askUserQuestionTool } from './builtin/ask-user.js';
|
|
45
|
+
import { enterPlanModeTool, exitPlanModeTool } from '../planning/index.js';
|
|
29
46
|
|
|
30
47
|
/**
|
|
31
48
|
* Create a registry with all built-in tools
|
|
@@ -42,6 +59,9 @@ export function createDefaultRegistry(): ToolRegistry {
|
|
|
42
59
|
webfetchTool,
|
|
43
60
|
websearchTool,
|
|
44
61
|
todowriteTool,
|
|
62
|
+
askUserQuestionTool,
|
|
63
|
+
enterPlanModeTool,
|
|
64
|
+
exitPlanModeTool,
|
|
45
65
|
]);
|
|
46
66
|
return registry;
|
|
47
67
|
}
|
|
@@ -59,4 +79,7 @@ export const builtinTools = [
|
|
|
59
79
|
webfetchTool,
|
|
60
80
|
websearchTool,
|
|
61
81
|
todowriteTool,
|
|
82
|
+
askUserQuestionTool,
|
|
83
|
+
enterPlanModeTool,
|
|
84
|
+
exitPlanModeTool,
|
|
62
85
|
];
|
package/src/tools/registry.ts
CHANGED
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
* Tool Registry - Manages available tools
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
5
6
|
import type { Tool, ToolContext, ToolResult } from './types.js';
|
|
6
|
-
import { zodToJsonSchema, getErrorMessage } from './types.js';
|
|
7
|
+
import { zodToJsonSchema, getErrorMessage, resolvePath } from './types.js';
|
|
7
8
|
import type { ToolDefinition } from '../providers/types.js';
|
|
9
|
+
import { getPlanModeManager } from '../planning/index.js';
|
|
10
|
+
import { getCheckpointManager } from '../checkpointing/index.js';
|
|
11
|
+
import type { ChangeType } from '../checkpointing/index.js';
|
|
12
|
+
|
|
13
|
+
// Tools that modify files and should be tracked for checkpointing
|
|
14
|
+
const CHECKPOINT_TOOLS = ['Write', 'Edit'];
|
|
8
15
|
|
|
9
16
|
export class ToolRegistry {
|
|
10
17
|
private tools: Map<string, Tool> = new Map();
|
|
@@ -50,6 +57,20 @@ export class ToolRegistry {
|
|
|
50
57
|
.filter((t): t is ToolDefinition => t !== null);
|
|
51
58
|
}
|
|
52
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Get tool definitions filtered by plan mode
|
|
62
|
+
* In plan mode, only read-only tools are returned
|
|
63
|
+
*/
|
|
64
|
+
getFilteredDefinitions(toolNames?: string[]): ToolDefinition[] {
|
|
65
|
+
const planManager = getPlanModeManager();
|
|
66
|
+
const names = toolNames ?? this.list();
|
|
67
|
+
|
|
68
|
+
// Filter tools based on plan mode state
|
|
69
|
+
const filteredNames = planManager.filterTools(names);
|
|
70
|
+
|
|
71
|
+
return this.getDefinitions(filteredNames);
|
|
72
|
+
}
|
|
73
|
+
|
|
53
74
|
/**
|
|
54
75
|
* Execute a tool by name
|
|
55
76
|
*/
|
|
@@ -75,9 +96,81 @@ export class ToolRegistry {
|
|
|
75
96
|
};
|
|
76
97
|
}
|
|
77
98
|
|
|
78
|
-
|
|
99
|
+
// Capture pre-execution state for checkpointing
|
|
100
|
+
let preState: { filePath: string; content: string | null; existed: boolean } | null = null;
|
|
101
|
+
if (CHECKPOINT_TOOLS.includes(name) && parsed.data && typeof parsed.data === 'object') {
|
|
102
|
+
const filePath = (parsed.data as { file_path?: string }).file_path;
|
|
103
|
+
if (filePath) {
|
|
104
|
+
preState = await this.captureFileState(filePath, context.cwd);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Execute the tool
|
|
109
|
+
const result = await tool.execute(parsed.data, context);
|
|
110
|
+
|
|
111
|
+
// Record checkpoint on successful file modification
|
|
112
|
+
if (result.success && preState) {
|
|
113
|
+
await this.recordCheckpoint(name, preState, context.cwd);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return result;
|
|
79
117
|
} catch (error) {
|
|
80
118
|
return { success: false, output: '', error: `Tool execution failed: ${getErrorMessage(error)}` };
|
|
81
119
|
}
|
|
82
120
|
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Capture file state before modification
|
|
124
|
+
*/
|
|
125
|
+
private async captureFileState(
|
|
126
|
+
filePath: string,
|
|
127
|
+
cwd: string
|
|
128
|
+
): Promise<{ filePath: string; content: string | null; existed: boolean }> {
|
|
129
|
+
const resolvedPath = resolvePath(filePath, cwd);
|
|
130
|
+
try {
|
|
131
|
+
const content = await fs.readFile(resolvedPath, 'utf-8');
|
|
132
|
+
return { filePath: resolvedPath, content, existed: true };
|
|
133
|
+
} catch {
|
|
134
|
+
// File doesn't exist
|
|
135
|
+
return { filePath: resolvedPath, content: null, existed: false };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Record a checkpoint after file modification
|
|
141
|
+
*/
|
|
142
|
+
private async recordCheckpoint(
|
|
143
|
+
toolName: string,
|
|
144
|
+
preState: { filePath: string; content: string | null; existed: boolean },
|
|
145
|
+
cwd: string
|
|
146
|
+
): Promise<void> {
|
|
147
|
+
const checkpointManager = getCheckpointManager();
|
|
148
|
+
|
|
149
|
+
// Read current file content
|
|
150
|
+
let newContent: string | null = null;
|
|
151
|
+
try {
|
|
152
|
+
newContent = await fs.readFile(preState.filePath, 'utf-8');
|
|
153
|
+
} catch {
|
|
154
|
+
// File was deleted or doesn't exist
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Determine change type
|
|
158
|
+
let changeType: ChangeType;
|
|
159
|
+
if (!preState.existed && newContent !== null) {
|
|
160
|
+
changeType = 'create';
|
|
161
|
+
} else if (preState.existed && newContent === null) {
|
|
162
|
+
changeType = 'delete';
|
|
163
|
+
} else {
|
|
164
|
+
changeType = 'modify';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Record the change
|
|
168
|
+
checkpointManager.recordChange({
|
|
169
|
+
path: preState.filePath,
|
|
170
|
+
changeType,
|
|
171
|
+
previousContent: preState.content,
|
|
172
|
+
newContent,
|
|
173
|
+
toolName,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
83
176
|
}
|
package/src/tools/types.ts
CHANGED
|
@@ -9,9 +9,27 @@ import { z } from 'zod';
|
|
|
9
9
|
// Tool Definition Types
|
|
10
10
|
// ============================================================================
|
|
11
11
|
|
|
12
|
+
// Forward declaration for Question type (used by AskUserQuestion tool)
|
|
13
|
+
export interface Question {
|
|
14
|
+
question: string;
|
|
15
|
+
header: string;
|
|
16
|
+
options: Array<{ label: string; description: string }>;
|
|
17
|
+
multiSelect: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Forward declaration for QuestionAnswer type (used by AskUserQuestion tool)
|
|
21
|
+
export interface QuestionAnswer {
|
|
22
|
+
question: string;
|
|
23
|
+
header: string;
|
|
24
|
+
selectedOptions: string[];
|
|
25
|
+
customInput?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
12
28
|
export interface ToolContext {
|
|
13
29
|
cwd: string;
|
|
14
30
|
abortSignal?: AbortSignal;
|
|
31
|
+
/** Callback for AskUserQuestion tool to interact with user */
|
|
32
|
+
askUser?: (questions: Question[]) => Promise<QuestionAnswer[]>;
|
|
15
33
|
}
|
|
16
34
|
|
|
17
35
|
export interface ToolResultMetadata {
|