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.
- package/README.md +2 -1
- package/dist/agent/agent.d.ts +35 -0
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/agent.js +93 -3
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/types.d.ts +6 -0
- 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 +157 -6
- 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 +11 -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/theme.d.ts +2 -0
- package/dist/cli/components/theme.d.ts.map +1 -1
- package/dist/cli/components/theme.js +3 -0
- 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/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/index.d.ts +7 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +7 -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/docs/cost-tracking-comparison.md +904 -0
- package/docs/operating-modes.md +96 -0
- package/docs/proposals/0025-cost-tracking.md +60 -2
- package/docs/proposals/README.md +1 -1
- 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 +110 -3
- package/src/agent/types.ts +6 -0
- 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 +204 -5
- package/src/cli/components/CommandSuggestions.tsx +5 -0
- package/src/cli/components/Messages.tsx +23 -4
- package/src/cli/components/ModeIndicator.tsx +174 -0
- package/src/cli/components/PlanApproval.tsx +327 -0
- package/src/cli/components/theme.ts +3 -0
- package/src/index.ts +15 -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/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/index.ts +8 -0
- package/src/tools/registry.ts +95 -2
- package/.gencode/settings.local.json +0 -7
- 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**:
|
|
5
|
+
- **Status**: Implemented
|
|
6
6
|
- **Created**: 2025-01-15
|
|
7
|
-
- **Updated**:
|
|
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)
|
package/docs/proposals/README.md
CHANGED
|
@@ -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 |
|
|
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
package/src/agent/agent.ts
CHANGED
|
@@ -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.
|
|
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
|
*/
|
package/src/agent/types.ts
CHANGED
|
@@ -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 {
|