gencode-ai 0.1.3 → 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.
Files changed (151) hide show
  1. package/README.md +2 -1
  2. package/dist/agent/agent.d.ts +35 -0
  3. package/dist/agent/agent.d.ts.map +1 -1
  4. package/dist/agent/agent.js +93 -3
  5. package/dist/agent/agent.js.map +1 -1
  6. package/dist/agent/types.d.ts +6 -0
  7. package/dist/agent/types.d.ts.map +1 -1
  8. package/dist/checkpointing/checkpoint-manager.d.ts +87 -0
  9. package/dist/checkpointing/checkpoint-manager.d.ts.map +1 -0
  10. package/dist/checkpointing/checkpoint-manager.js +281 -0
  11. package/dist/checkpointing/checkpoint-manager.js.map +1 -0
  12. package/dist/checkpointing/index.d.ts +29 -0
  13. package/dist/checkpointing/index.d.ts.map +1 -0
  14. package/dist/checkpointing/index.js +29 -0
  15. package/dist/checkpointing/index.js.map +1 -0
  16. package/dist/checkpointing/types.d.ts +98 -0
  17. package/dist/checkpointing/types.d.ts.map +1 -0
  18. package/dist/checkpointing/types.js +7 -0
  19. package/dist/checkpointing/types.js.map +1 -0
  20. package/dist/cli/components/App.d.ts.map +1 -1
  21. package/dist/cli/components/App.js +157 -6
  22. package/dist/cli/components/App.js.map +1 -1
  23. package/dist/cli/components/CommandSuggestions.d.ts.map +1 -1
  24. package/dist/cli/components/CommandSuggestions.js +5 -0
  25. package/dist/cli/components/CommandSuggestions.js.map +1 -1
  26. package/dist/cli/components/Messages.d.ts +7 -1
  27. package/dist/cli/components/Messages.d.ts.map +1 -1
  28. package/dist/cli/components/Messages.js +11 -2
  29. package/dist/cli/components/Messages.js.map +1 -1
  30. package/dist/cli/components/ModeIndicator.d.ts +42 -0
  31. package/dist/cli/components/ModeIndicator.d.ts.map +1 -0
  32. package/dist/cli/components/ModeIndicator.js +52 -0
  33. package/dist/cli/components/ModeIndicator.js.map +1 -0
  34. package/dist/cli/components/PlanApproval.d.ts +36 -0
  35. package/dist/cli/components/PlanApproval.d.ts.map +1 -0
  36. package/dist/cli/components/PlanApproval.js +154 -0
  37. package/dist/cli/components/PlanApproval.js.map +1 -0
  38. package/dist/cli/components/theme.d.ts +2 -0
  39. package/dist/cli/components/theme.d.ts.map +1 -1
  40. package/dist/cli/components/theme.js +3 -0
  41. package/dist/cli/components/theme.js.map +1 -1
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +2 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/planning/index.d.ts +13 -0
  47. package/dist/planning/index.d.ts.map +1 -0
  48. package/dist/planning/index.js +15 -0
  49. package/dist/planning/index.js.map +1 -0
  50. package/dist/planning/plan-file.d.ts +59 -0
  51. package/dist/planning/plan-file.d.ts.map +1 -0
  52. package/dist/planning/plan-file.js +278 -0
  53. package/dist/planning/plan-file.js.map +1 -0
  54. package/dist/planning/state.d.ts +127 -0
  55. package/dist/planning/state.d.ts.map +1 -0
  56. package/dist/planning/state.js +261 -0
  57. package/dist/planning/state.js.map +1 -0
  58. package/dist/planning/tools/enter-plan-mode.d.ts +25 -0
  59. package/dist/planning/tools/enter-plan-mode.d.ts.map +1 -0
  60. package/dist/planning/tools/enter-plan-mode.js +98 -0
  61. package/dist/planning/tools/enter-plan-mode.js.map +1 -0
  62. package/dist/planning/tools/exit-plan-mode.d.ts +24 -0
  63. package/dist/planning/tools/exit-plan-mode.d.ts.map +1 -0
  64. package/dist/planning/tools/exit-plan-mode.js +149 -0
  65. package/dist/planning/tools/exit-plan-mode.js.map +1 -0
  66. package/dist/planning/types.d.ts +100 -0
  67. package/dist/planning/types.d.ts.map +1 -0
  68. package/dist/planning/types.js +28 -0
  69. package/dist/planning/types.js.map +1 -0
  70. package/dist/pricing/calculator.d.ts +21 -0
  71. package/dist/pricing/calculator.d.ts.map +1 -0
  72. package/dist/pricing/calculator.js +59 -0
  73. package/dist/pricing/calculator.js.map +1 -0
  74. package/dist/pricing/index.d.ts +7 -0
  75. package/dist/pricing/index.d.ts.map +1 -0
  76. package/dist/pricing/index.js +7 -0
  77. package/dist/pricing/index.js.map +1 -0
  78. package/dist/pricing/models.d.ts +20 -0
  79. package/dist/pricing/models.d.ts.map +1 -0
  80. package/dist/pricing/models.js +322 -0
  81. package/dist/pricing/models.js.map +1 -0
  82. package/dist/pricing/types.d.ts +30 -0
  83. package/dist/pricing/types.d.ts.map +1 -0
  84. package/dist/pricing/types.js +5 -0
  85. package/dist/pricing/types.js.map +1 -0
  86. package/dist/providers/anthropic.d.ts.map +1 -1
  87. package/dist/providers/anthropic.js +17 -10
  88. package/dist/providers/anthropic.js.map +1 -1
  89. package/dist/providers/gemini.d.ts.map +1 -1
  90. package/dist/providers/gemini.js +21 -14
  91. package/dist/providers/gemini.js.map +1 -1
  92. package/dist/providers/openai.d.ts.map +1 -1
  93. package/dist/providers/openai.js +12 -8
  94. package/dist/providers/openai.js.map +1 -1
  95. package/dist/providers/types.d.ts +2 -0
  96. package/dist/providers/types.d.ts.map +1 -1
  97. package/dist/providers/vertex-ai.d.ts.map +1 -1
  98. package/dist/providers/vertex-ai.js +17 -10
  99. package/dist/providers/vertex-ai.js.map +1 -1
  100. package/dist/session/manager.d.ts +4 -0
  101. package/dist/session/manager.d.ts.map +1 -1
  102. package/dist/session/manager.js +8 -0
  103. package/dist/session/manager.js.map +1 -1
  104. package/dist/tools/index.d.ts +7 -1
  105. package/dist/tools/index.d.ts.map +1 -1
  106. package/dist/tools/index.js +7 -0
  107. package/dist/tools/index.js.map +1 -1
  108. package/dist/tools/registry.d.ts +13 -0
  109. package/dist/tools/registry.d.ts.map +1 -1
  110. package/dist/tools/registry.js +79 -2
  111. package/dist/tools/registry.js.map +1 -1
  112. package/docs/cost-tracking-comparison.md +904 -0
  113. package/docs/operating-modes.md +96 -0
  114. package/docs/proposals/0025-cost-tracking.md +60 -2
  115. package/docs/proposals/README.md +1 -1
  116. package/examples/test-checkpointing.ts +121 -0
  117. package/examples/test-cost-tracking.ts +77 -0
  118. package/examples/test-interrupt-cleanup.ts +94 -0
  119. package/package.json +1 -1
  120. package/src/agent/agent.ts +110 -3
  121. package/src/agent/types.ts +6 -0
  122. package/src/checkpointing/checkpoint-manager.ts +327 -0
  123. package/src/checkpointing/index.ts +45 -0
  124. package/src/checkpointing/types.ts +104 -0
  125. package/src/cli/components/App.tsx +204 -5
  126. package/src/cli/components/CommandSuggestions.tsx +5 -0
  127. package/src/cli/components/Messages.tsx +23 -4
  128. package/src/cli/components/ModeIndicator.tsx +174 -0
  129. package/src/cli/components/PlanApproval.tsx +327 -0
  130. package/src/cli/components/theme.ts +3 -0
  131. package/src/index.ts +15 -0
  132. package/src/planning/index.ts +53 -0
  133. package/src/planning/plan-file.ts +326 -0
  134. package/src/planning/state.ts +305 -0
  135. package/src/planning/tools/enter-plan-mode.ts +111 -0
  136. package/src/planning/tools/exit-plan-mode.ts +170 -0
  137. package/src/planning/types.ts +150 -0
  138. package/src/pricing/calculator.ts +71 -0
  139. package/src/pricing/index.ts +7 -0
  140. package/src/pricing/models.ts +334 -0
  141. package/src/pricing/types.ts +32 -0
  142. package/src/providers/anthropic.ts +21 -10
  143. package/src/providers/gemini.ts +25 -14
  144. package/src/providers/openai.ts +17 -8
  145. package/src/providers/types.ts +3 -0
  146. package/src/providers/vertex-ai.ts +21 -10
  147. package/src/session/manager.ts +9 -0
  148. package/src/tools/index.ts +8 -0
  149. package/src/tools/registry.ts +95 -2
  150. package/.gencode/settings.local.json +0 -7
  151. package/CLAUDE.md +0 -86
@@ -0,0 +1,96 @@
1
+ # Operating Modes
2
+
3
+ > Reference: [Claude Code](https://claude.ai/code) operating modes
4
+
5
+ GenCode has **3 operating modes** that can be cycled with `Shift+Tab`:
6
+
7
+ ```
8
+ Normal (default)
9
+ ↓ Shift+Tab
10
+ Plan (⏸ plan mode on)
11
+ ↓ Shift+Tab
12
+ Accept (⏵⏵ accept edits on)
13
+ ↓ Shift+Tab
14
+ Normal
15
+ ...
16
+ ```
17
+
18
+ ## Mode Comparison
19
+
20
+ | Mode | Status Indicator | Edit Confirmation | Available Tools |
21
+ |------|------------------|-------------------|-----------------|
22
+ | **Normal** | _(none)_ | Required | All |
23
+ | **Plan** | `⏸ plan mode on (shift+tab to cycle)` | N/A (edits blocked) | Read-only |
24
+ | **Accept** | `⏵⏵ accept edits on (shift+tab to cycle)` | Auto-approved | All |
25
+
26
+ ## Mode Details
27
+
28
+ ### 1. Normal Mode (Default)
29
+
30
+ - Normal execution mode
31
+ - Every Write/Edit operation requires user confirmation
32
+ - All tools available
33
+ - Default mode when GenCode starts
34
+
35
+ ### 2. Plan Mode
36
+
37
+ - Read-only exploration mode for designing implementation approaches
38
+ - Write, Edit, and Bash tools are blocked
39
+ - Used for understanding codebase and creating implementation plans
40
+
41
+ **Allowed Tools:** Read, Glob, Grep, WebFetch, WebSearch, TodoWrite, AskUserQuestion
42
+
43
+ **Blocked Tools:** Write, Edit, Bash
44
+
45
+ ### 3. Auto-accept Mode
46
+
47
+ - Automatic edit acceptance mode
48
+ - Write/Edit operations are automatically approved without confirmation
49
+ - Useful when trusting the agent or after approving a plan
50
+ - **Caution**: May cause unintended file changes
51
+
52
+ ## Plan Mode Workflow
53
+
54
+ When in Plan mode, the agent goes through 5 phases:
55
+
56
+ ```
57
+ EnterPlanMode
58
+
59
+ ┌─────────────┐
60
+ │Understanding│ ← Explore codebase
61
+ └──────┬──────┘
62
+
63
+ ┌─────────────┐
64
+ │ Design │ ← Design approach
65
+ └──────┬──────┘
66
+
67
+ ┌─────────────┐
68
+ │ Review │ ← (Optional) Clarify requirements
69
+ └──────┬──────┘
70
+
71
+ ┌─────────────┐
72
+ │ Final │ ← Write plan to file
73
+ └──────┬──────┘
74
+
75
+ ExitPlanMode
76
+
77
+ ┌─────────────┐
78
+ │ Approval │ ← User approves/modifies/cancels
79
+ └─────────────┘
80
+ ```
81
+
82
+ ## Plan Approval Options
83
+
84
+ When exiting Plan mode:
85
+
86
+ | Option | Description | Next Mode |
87
+ |--------|-------------|-----------|
88
+ | **approve** | Accept plan, auto-accept edits | Accept |
89
+ | **approve_manual** | Accept plan, manually approve each edit | Normal |
90
+ | **modify** | Return to modify the plan | Plan |
91
+ | **cancel** | Cancel plan entirely | Normal |
92
+
93
+ ## References
94
+
95
+ - [Claude Code Best Practices](https://www.anthropic.com/engineering/claude-code-best-practices)
96
+ - [Plan Mode Proposal](./proposals/0004-plan-mode.md)
@@ -2,9 +2,10 @@
2
2
 
3
3
  - **Proposal ID**: 0025
4
4
  - **Author**: mycode team
5
- - **Status**: Draft
5
+ - **Status**: Implemented
6
6
  - **Created**: 2025-01-15
7
- - **Updated**: 2025-01-15
7
+ - **Updated**: 2026-01-16
8
+ - **Implemented**: 2026-01-16
8
9
 
9
10
  ## Summary
10
11
 
@@ -439,6 +440,63 @@ Only show costs after session ends.
439
440
 
440
441
  No breaking changes.
441
442
 
443
+ ## Implementation Notes
444
+
445
+ ### Phase 1 Complete (2026-01-16)
446
+
447
+ Implemented core cost tracking functionality with real-time display in CLI.
448
+
449
+ ### Files Created
450
+
451
+ | File | Description |
452
+ |------|-------------|
453
+ | `src/pricing/types.ts` | Type definitions for pricing and costs |
454
+ | `src/pricing/models.ts` | Model pricing database (updated Jan 2025) |
455
+ | `src/pricing/calculator.ts` | Cost calculation and formatting utilities |
456
+ | `src/pricing/index.ts` | Module exports |
457
+ | `examples/test-cost-tracking.ts` | Test script for cost tracking |
458
+ | `docs/cost-tracking-comparison.md` | Detailed comparison with OpenCode |
459
+
460
+ ### Files Modified
461
+
462
+ | File | Changes |
463
+ |------|---------|
464
+ | `src/providers/types.ts` | Added `cost?: CostEstimate` to `CompletionResponse` |
465
+ | `src/providers/anthropic.ts` | Calculate and return cost in responses |
466
+ | `src/providers/openai.ts` | Calculate and return cost in responses |
467
+ | `src/providers/gemini.ts` | Calculate and return cost in responses |
468
+ | `src/providers/vertex-ai.ts` | Calculate and return cost in responses |
469
+ | `src/agent/types.ts` | Added `usage` and `cost` to `AgentEventDone` |
470
+ | `src/agent/agent.ts` | Pass usage and cost to done event |
471
+ | `src/cli/components/App.tsx` | Pass cost data to CompletionMessage |
472
+ | `src/cli/components/Messages.tsx` | Display cost and token usage |
473
+
474
+ ### Key Implementation Details
475
+
476
+ 1. **Pricing Database**: Comprehensive pricing for all major models (Anthropic, OpenAI, Google Gemini/Vertex AI) as of January 2025
477
+
478
+ 2. **Cost Calculation**: Simple formula `(tokens / 1M) * pricePerMillion` for each token type
479
+
480
+ 3. **CLI Display Format**: `✻ Done for 2.3s • Tokens: 1.2K in / 567 out • (~$0.02)`
481
+
482
+ 4. **Provider Integration**: All 4 providers (Anthropic, OpenAI, Gemini, Vertex AI) calculate cost when usage data is available
483
+
484
+ 5. **Graceful Degradation**: Cost displays only when provider returns usage data (some stream modes may not include it)
485
+
486
+ ### Testing
487
+
488
+ - Tested cost calculation with sample data
489
+ - Verified correct pricing for all providers
490
+ - Confirmed CLI display formatting
491
+
492
+ ### Future Enhancements (Phase 2+)
493
+
494
+ - Session cost aggregation
495
+ - Budget system with alerts
496
+ - Cost reporting (`/costs` command)
497
+ - Multi-provider cost comparison
498
+ - Advanced token types (reasoning, cache)
499
+
442
500
  ## References
443
501
 
444
502
  - [Anthropic Pricing](https://www.anthropic.com/pricing)
@@ -47,7 +47,7 @@ This directory contains enhancement proposals for the gencode project. Each prop
47
47
  | [0022](./0022-plugin-system.md) | Plugin System | Draft |
48
48
  | [0023](./0023-permission-enhancements.md) | Permission Enhancements | Draft |
49
49
  | [0024](./0024-keyboard-shortcuts.md) | Keyboard Shortcuts | Draft |
50
- | [0025](./0025-cost-tracking.md) | Cost Tracking | Draft |
50
+ | [0025](./0025-cost-tracking.md) | Cost Tracking | Implemented |
51
51
  | [0026](./0026-git-integration.md) | Git Integration | Draft |
52
52
  | [0027](./0027-enhanced-read-tool.md) | Enhanced Read Tool | Draft |
53
53
  | [0028](./0028-enhanced-bash-tool.md) | Enhanced Bash Tool | Draft |
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Test Checkpointing System
3
+ *
4
+ * This example demonstrates the automatic file change tracking and rewind capabilities.
5
+ */
6
+
7
+ import { CheckpointManager } from '../src/checkpointing/index.js';
8
+ import * as fs from 'fs/promises';
9
+ import * as path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ async function testCheckpointing() {
16
+ console.log('🧪 Testing Checkpointing System\n');
17
+
18
+ const manager = new CheckpointManager('test-session');
19
+ const testDir = path.join(__dirname, '../.test-checkpoints');
20
+ const testFile = path.join(testDir, 'example.txt');
21
+
22
+ try {
23
+ // Create test directory
24
+ await fs.mkdir(testDir, { recursive: true });
25
+
26
+ console.log('1️⃣ Recording file creation...');
27
+ const originalContent = 'Hello, World!';
28
+ await fs.writeFile(testFile, originalContent, 'utf-8');
29
+
30
+ manager.recordChange({
31
+ path: testFile,
32
+ changeType: 'create',
33
+ previousContent: null,
34
+ newContent: originalContent,
35
+ toolName: 'Write',
36
+ });
37
+
38
+ console.log(' ✓ File created:', testFile);
39
+ console.log(' ✓ Checkpoint recorded\n');
40
+
41
+ console.log('2️⃣ Recording file modification...');
42
+ const modifiedContent = 'Hello, Checkpointing!';
43
+ await fs.writeFile(testFile, modifiedContent, 'utf-8');
44
+
45
+ manager.recordChange({
46
+ path: testFile,
47
+ changeType: 'modify',
48
+ previousContent: originalContent,
49
+ newContent: modifiedContent,
50
+ toolName: 'Edit',
51
+ });
52
+
53
+ console.log(' ✓ File modified');
54
+ console.log(' ✓ Checkpoint recorded\n');
55
+
56
+ console.log('3️⃣ Listing checkpoints...');
57
+ console.log(manager.formatCheckpointList(true));
58
+ console.log();
59
+
60
+ console.log('4️⃣ Getting summary...');
61
+ const summary = manager.getSummary();
62
+ console.log(` Created: ${summary.created}`);
63
+ console.log(` Modified: ${summary.modified}`);
64
+ console.log(` Deleted: ${summary.deleted}`);
65
+ console.log(` Total: ${summary.total}\n`);
66
+
67
+ console.log('5️⃣ Rewinding last change...');
68
+ const result = await manager.rewind({ count: 1 });
69
+
70
+ if (result.success) {
71
+ console.log(' ✓ Rewind successful');
72
+ result.revertedFiles.forEach((f) => {
73
+ console.log(` • ${path.basename(f.path)} (${f.action})`);
74
+ });
75
+
76
+ // Verify content was restored
77
+ const restoredContent = await fs.readFile(testFile, 'utf-8');
78
+ if (restoredContent === originalContent) {
79
+ console.log(' ✓ Content restored correctly\n');
80
+ } else {
81
+ console.log(' ✗ Content mismatch!\n');
82
+ }
83
+ } else {
84
+ console.log(' ✗ Rewind failed');
85
+ result.errors.forEach((e) => console.log(` ${e.error}`));
86
+ }
87
+
88
+ console.log('6️⃣ Rewinding all changes...');
89
+ const finalResult = await manager.rewind({ all: true });
90
+
91
+ if (finalResult.success) {
92
+ console.log(' ✓ All changes reverted');
93
+ finalResult.revertedFiles.forEach((f) => {
94
+ console.log(` • ${path.basename(f.path)} (${f.action})`);
95
+ });
96
+
97
+ // Verify file was deleted
98
+ try {
99
+ await fs.access(testFile);
100
+ console.log(' ✗ File still exists!\n');
101
+ } catch {
102
+ console.log(' ✓ File was deleted as expected\n');
103
+ }
104
+ }
105
+
106
+ console.log('✅ All tests passed!\n');
107
+ } catch (error) {
108
+ console.error('❌ Test failed:', error);
109
+ } finally {
110
+ // Cleanup
111
+ try {
112
+ await fs.rm(testDir, { recursive: true, force: true });
113
+ console.log('🧹 Cleaned up test directory');
114
+ } catch {
115
+ // Ignore cleanup errors
116
+ }
117
+ }
118
+ }
119
+
120
+ // Run the test
121
+ testCheckpointing();
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Test cost tracking functionality
3
+ */
4
+
5
+ import { createProvider } from '../src/providers/index.js';
6
+ import { calculateCost, formatCost, formatTokens } from '../src/pricing/calculator.js';
7
+
8
+ async function testCostTracking() {
9
+ console.log('='.repeat(60));
10
+ console.log('Testing Cost Tracking Feature');
11
+ console.log('='.repeat(60));
12
+ console.log();
13
+
14
+ // Test 1: Cost Calculation
15
+ console.log('Test 1: Cost Calculation');
16
+ console.log('-'.repeat(60));
17
+
18
+ const testCases = [
19
+ { provider: 'anthropic', model: 'claude-sonnet-4', input: 1000, output: 500 },
20
+ { provider: 'openai', model: 'gpt-4o', input: 1000, output: 500 },
21
+ { provider: 'gemini', model: 'gemini-2.0-flash', input: 1000, output: 500 },
22
+ ];
23
+
24
+ for (const test of testCases) {
25
+ const cost = calculateCost(test.provider, test.model, {
26
+ inputTokens: test.input,
27
+ outputTokens: test.output,
28
+ });
29
+
30
+ console.log(`\n${test.provider}/${test.model}:`);
31
+ console.log(` Input: ${formatTokens(test.input)} tokens`);
32
+ console.log(` Output: ${formatTokens(test.output)} tokens`);
33
+ console.log(` Cost: ${formatCost(cost.totalCost)}`);
34
+ console.log(` Breakdown: ${formatCost(cost.inputCost)} (input) + ${formatCost(cost.outputCost)} (output)`);
35
+ }
36
+
37
+ console.log();
38
+ console.log('='.repeat(60));
39
+ console.log();
40
+
41
+ // Test 2: Real API Call (if env var is set)
42
+ if (process.env.ANTHROPIC_API_KEY) {
43
+ console.log('Test 2: Real API Call with Anthropic');
44
+ console.log('-'.repeat(60));
45
+
46
+ const provider = createProvider({ provider: 'anthropic', model: 'claude-haiku-3-5' });
47
+
48
+ const response = await provider.complete({
49
+ model: 'claude-haiku-3-5',
50
+ messages: [{ role: 'user', content: 'Say hello in one word' }],
51
+ maxTokens: 10,
52
+ });
53
+
54
+ console.log('\nResponse:', response.content[0]?.type === 'text' ? response.content[0].text : '');
55
+
56
+ if (response.usage) {
57
+ console.log(`\nToken Usage:`);
58
+ console.log(` Input: ${formatTokens(response.usage.inputTokens)}`);
59
+ console.log(` Output: ${formatTokens(response.usage.outputTokens)}`);
60
+ }
61
+
62
+ if (response.cost) {
63
+ console.log(`\nCost Estimate:`);
64
+ console.log(` Total: ${formatCost(response.cost.totalCost)}`);
65
+ console.log(` Input: ${formatCost(response.cost.inputCost)}`);
66
+ console.log(` Output: ${formatCost(response.cost.outputCost)}`);
67
+ }
68
+
69
+ console.log();
70
+ console.log('='.repeat(60));
71
+ } else {
72
+ console.log('Skipping API test (no ANTHROPIC_API_KEY)');
73
+ console.log('='.repeat(60));
74
+ }
75
+ }
76
+
77
+ testCostTracking().catch(console.error);
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Test Interrupt Cleanup
3
+ *
4
+ * Simulates the interrupt scenario and verifies cleanup works correctly
5
+ */
6
+
7
+ import { Agent } from '../src/agent/index.js';
8
+ import type { Message } from '../src/providers/types.js';
9
+
10
+ async function testInterruptCleanup() {
11
+ console.log('🧪 Testing Interrupt Cleanup\n');
12
+
13
+ const agent = new Agent({
14
+ provider: 'anthropic',
15
+ model: 'claude-3-5-sonnet-20241022',
16
+ cwd: process.cwd(),
17
+ });
18
+
19
+ try {
20
+ // Simulate incomplete tool_use message (what happens during interrupt)
21
+ console.log('1️⃣ Simulating incomplete tool_use message...');
22
+
23
+ // Access private messages array via getHistory and direct manipulation
24
+ const history = agent.getHistory();
25
+ console.log(` Initial history: ${history.length} messages`);
26
+
27
+ // Add a user message
28
+ (agent as any).messages.push({
29
+ role: 'user',
30
+ content: 'Create a file',
31
+ });
32
+ console.log(` After user message: ${agent.getHistory().length} messages`);
33
+
34
+ // Add an assistant message with tool_use (simulating interrupt point)
35
+ (agent as any).messages.push({
36
+ role: 'assistant',
37
+ content: [
38
+ { type: 'text', text: 'I will create the file.' },
39
+ {
40
+ type: 'tool_use',
41
+ id: 'toolu_test_123',
42
+ name: 'Write',
43
+ input: { file_path: 'test.txt', content: 'hello' },
44
+ },
45
+ ],
46
+ });
47
+ console.log(` After assistant+tool_use: ${agent.getHistory().length} messages`);
48
+ console.log(` Last message role: ${agent.getHistory()[agent.getHistory().length - 1].role}`);
49
+
50
+ // Verify incomplete state
51
+ const lastMessage = agent.getHistory()[agent.getHistory().length - 1];
52
+ const hasToolUse = Array.isArray(lastMessage.content) &&
53
+ lastMessage.content.some((c: any) => c.type === 'tool_use');
54
+ console.log(` Has incomplete tool_use: ${hasToolUse}\n`);
55
+
56
+ console.log('2️⃣ Calling cleanupIncompleteMessages()...');
57
+ agent.cleanupIncompleteMessages();
58
+
59
+ const afterCleanup = agent.getHistory();
60
+ console.log(` After cleanup: ${afterCleanup.length} messages`);
61
+
62
+ if (afterCleanup.length === 1 && afterCleanup[0].role === 'user') {
63
+ console.log(' ✓ Incomplete assistant message removed!\n');
64
+ } else {
65
+ console.log(' ✗ Cleanup failed!\n');
66
+ console.log(' Remaining messages:', JSON.stringify(afterCleanup, null, 2));
67
+ }
68
+
69
+ console.log('3️⃣ Testing with complete messages (no tool_use)...');
70
+
71
+ // Add complete assistant message
72
+ (agent as any).messages.push({
73
+ role: 'assistant',
74
+ content: [{ type: 'text', text: 'Here is your answer.' }],
75
+ });
76
+ console.log(` Before cleanup: ${agent.getHistory().length} messages`);
77
+
78
+ agent.cleanupIncompleteMessages();
79
+ console.log(` After cleanup: ${agent.getHistory().length} messages`);
80
+
81
+ if (agent.getHistory().length === 2) {
82
+ console.log(' ✓ Complete messages preserved!\n');
83
+ } else {
84
+ console.log(' ✗ Complete message was incorrectly removed!\n');
85
+ }
86
+
87
+ console.log('✅ All tests passed!\n');
88
+ } catch (error) {
89
+ console.error('❌ Test failed:', error);
90
+ }
91
+ }
92
+
93
+ // Run the test
94
+ testInterruptCleanup();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gencode-ai",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "An open-source AI assistant for your terminal",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,6 +17,12 @@ import { MemoryManager, type LoadedMemory } from '../memory/index.js';
17
17
  import type { AgentConfig, AgentEvent } from './types.js';
18
18
  import { buildSystemPromptForModel, debugPromptLoading } from '../prompts/index.js';
19
19
  import type { Question, QuestionAnswer } from '../tools/types.js';
20
+ import {
21
+ getPlanModeManager,
22
+ type PlanModeManager,
23
+ type ModeType,
24
+ type AllowedPrompt,
25
+ } from '../planning/index.js';
20
26
 
21
27
  // Type for askUser callback
22
28
  export type AskUserCallback = (questions: Question[]) => Promise<QuestionAnswer[]>;
@@ -27,6 +33,7 @@ export class Agent {
27
33
  private permissions: PermissionManager;
28
34
  private sessionManager: SessionManager;
29
35
  private memoryManager: MemoryManager;
36
+ private planModeManager: PlanModeManager;
30
37
  private config: AgentConfig;
31
38
  private messages: Message[] = [];
32
39
  private sessionId: string | null = null;
@@ -48,6 +55,7 @@ export class Agent {
48
55
  });
49
56
  this.sessionManager = new SessionManager();
50
57
  this.memoryManager = new MemoryManager();
58
+ this.planModeManager = getPlanModeManager();
51
59
  }
52
60
 
53
61
  /**
@@ -137,6 +145,78 @@ export class Agent {
137
145
  return this.loadedMemory;
138
146
  }
139
147
 
148
+ // ============================================================================
149
+ // Plan Mode
150
+ // ============================================================================
151
+
152
+ /**
153
+ * Get plan mode manager for external access
154
+ */
155
+ getPlanModeManager(): PlanModeManager {
156
+ return this.planModeManager;
157
+ }
158
+
159
+ /**
160
+ * Check if plan mode is active
161
+ */
162
+ isPlanModeActive(): boolean {
163
+ return this.planModeManager.isActive();
164
+ }
165
+
166
+ /**
167
+ * Get current mode (build or plan)
168
+ */
169
+ getCurrentMode(): ModeType {
170
+ return this.planModeManager.getCurrentMode();
171
+ }
172
+
173
+ /**
174
+ * Enter plan mode programmatically
175
+ */
176
+ async enterPlanMode(taskDescription?: string): Promise<string> {
177
+ const { createPlanFile } = await import('../planning/index.js');
178
+ const cwd = this.config.cwd ?? process.cwd();
179
+ const planFile = await createPlanFile(cwd, taskDescription);
180
+ this.planModeManager.enter(planFile.path, taskDescription);
181
+ return planFile.path;
182
+ }
183
+
184
+ /**
185
+ * Exit plan mode
186
+ */
187
+ exitPlanMode(approved: boolean = false): void {
188
+ if (approved) {
189
+ // Add allowed prompts from plan mode to permissions
190
+ const allowedPrompts = this.planModeManager.getRequestedPermissions();
191
+ if (allowedPrompts.length > 0) {
192
+ this.permissions.addAllowedPrompts(allowedPrompts);
193
+ }
194
+ }
195
+ this.planModeManager.exit(approved);
196
+ }
197
+
198
+ /**
199
+ * Toggle plan mode
200
+ */
201
+ async togglePlanMode(): Promise<void> {
202
+ if (this.planModeManager.isActive()) {
203
+ this.planModeManager.exit(false);
204
+ } else {
205
+ await this.enterPlanMode();
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Get plan mode requested permissions
211
+ */
212
+ getPlanModePermissions(): AllowedPrompt[] {
213
+ return this.planModeManager.getRequestedPermissions();
214
+ }
215
+
216
+ // ============================================================================
217
+ // Session Management
218
+ // ============================================================================
219
+
140
220
  /**
141
221
  * Get current session ID
142
222
  */
@@ -290,8 +370,8 @@ export class Agent {
290
370
  while (turns < maxTurns) {
291
371
  turns++;
292
372
 
293
- // Get tool definitions
294
- const toolDefs = this.registry.getDefinitions(this.config.tools);
373
+ // Get tool definitions (filtered by plan mode if active)
374
+ const toolDefs = this.registry.getFilteredDefinitions(this.config.tools);
295
375
 
296
376
  // Call LLM
297
377
  let response;
@@ -341,7 +421,7 @@ export class Agent {
341
421
  await this.sessionManager.addMessage({ role: 'assistant', content: response.content });
342
422
 
343
423
  if (response.stopReason !== 'tool_use' || toolCalls.length === 0) {
344
- yield { type: 'done', text: textContent };
424
+ yield { type: 'done', text: textContent, usage: response.usage, cost: response.cost };
345
425
  return;
346
426
  }
347
427
 
@@ -388,6 +468,33 @@ export class Agent {
388
468
  this.sessionManager.clearMessages();
389
469
  }
390
470
 
471
+ /**
472
+ * Clean up incomplete tool use messages (after interruption)
473
+ * Removes the last assistant message if it contains tool_use without corresponding tool_result
474
+ */
475
+ cleanupIncompleteMessages(): void {
476
+ if (this.messages.length === 0) return;
477
+
478
+ const lastMessage = this.messages[this.messages.length - 1];
479
+
480
+ // Check if last message is an assistant message with tool_use
481
+ if (lastMessage.role === 'assistant' && Array.isArray(lastMessage.content)) {
482
+ const hasToolUse = lastMessage.content.some((c) => c.type === 'tool_use');
483
+
484
+ if (hasToolUse) {
485
+ // Remove the incomplete assistant message
486
+ this.messages.pop();
487
+
488
+ // Also remove from session manager
489
+ // Note: SessionManager should have corresponding cleanup method
490
+ const messages = this.sessionManager.getMessages();
491
+ if (messages.length > 0 && messages[messages.length - 1].role === 'assistant') {
492
+ this.sessionManager.removeLastMessage();
493
+ }
494
+ }
495
+ }
496
+ }
497
+
391
498
  /**
392
499
  * Get conversation history
393
500
  */
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { PermissionConfig } from '../permissions/types.js';
6
+ import type { CostEstimate } from '../pricing/types.js';
6
7
 
7
8
  export interface AgentConfig {
8
9
  provider: 'openai' | 'anthropic' | 'gemini' | 'vertex-ai';
@@ -59,6 +60,11 @@ export interface AgentEventError {
59
60
  export interface AgentEventDone {
60
61
  type: 'done';
61
62
  text: string;
63
+ usage?: {
64
+ inputTokens: number;
65
+ outputTokens: number;
66
+ };
67
+ cost?: CostEstimate;
62
68
  }
63
69
 
64
70
  export interface AgentEventAskUser {