@dastageer_44/eco-code 1.1.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/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +111 -0
- package/dist/config.js.map +1 -0
- package/dist/harness/agent.d.ts +51 -0
- package/dist/harness/agent.js +168 -0
- package/dist/harness/agent.js.map +1 -0
- package/dist/harness/context.d.ts +17 -0
- package/dist/harness/context.js +77 -0
- package/dist/harness/context.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +414 -0
- package/dist/index.js.map +1 -0
- package/dist/services/openrouter.d.ts +21 -0
- package/dist/services/openrouter.js +106 -0
- package/dist/services/openrouter.js.map +1 -0
- package/dist/services/updater.d.ts +6 -0
- package/dist/services/updater.js +61 -0
- package/dist/services/updater.js.map +1 -0
- package/dist/taste/engine.d.ts +10 -0
- package/dist/taste/engine.js +80 -0
- package/dist/taste/engine.js.map +1 -0
- package/dist/tools/file-ops.d.ts +18 -0
- package/dist/tools/file-ops.js +89 -0
- package/dist/tools/file-ops.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +235 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/search-ops.d.ts +5 -0
- package/dist/tools/search-ops.js +55 -0
- package/dist/tools/search-ops.js.map +1 -0
- package/dist/tools/shell-ops.d.ts +18 -0
- package/dist/tools/shell-ops.js +140 -0
- package/dist/tools/shell-ops.js.map +1 -0
- package/dist/tools/types.d.ts +11 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/ui/banner.d.ts +2 -0
- package/dist/ui/banner.js +31 -0
- package/dist/ui/banner.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 eco-code contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# eco-code 🌿
|
|
2
|
+
|
|
3
|
+
> **Autonomous, Free-Tier AI Coding Agent for your Terminal**
|
|
4
|
+
> Powered by OpenRouter free models with dynamic discovery and a taste-adaptation harness.
|
|
5
|
+
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://npmjs.com/package/eco-code)
|
|
8
|
+
[](https://nodejs.org/)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ⚡ Quick Start
|
|
13
|
+
|
|
14
|
+
### 1. Instant Run (No installation required)
|
|
15
|
+
```bash
|
|
16
|
+
npx eco-code
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### 2. Global Installation
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g eco-code
|
|
22
|
+
eco-code
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 3. Update Anytime
|
|
26
|
+
```bash
|
|
27
|
+
npm update -g eco-code
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ✨ Features
|
|
33
|
+
|
|
34
|
+
- **Zero-Cost Coding**: Automatically queries OpenRouter's live API to discover and connect to available **free tier models** (Qwen 2.5 Coder, DeepSeek, Llama 3.3, Gemini Flash, etc.) at $0/token.
|
|
35
|
+
- **Autonomous Tool Harness**: Equipped with file inspection, line-level editing, creation, directory exploration, and shell command execution.
|
|
36
|
+
- **"Taste" Adaptation Engine**: Inspired by modern meta-agent research (such as Command Code), `eco-code` maintains a `.agent/taste.md` style profile that learns your repository conventions, testing frameworks, and formatting preferences.
|
|
37
|
+
- **Dynamic Model Switching**: Switch between available free models on-the-fly using `/model`.
|
|
38
|
+
- **Direct Shell Escape**: Prefix any command with `!` (e.g. `!git status`, `!npm test`) to run terminal commands without exiting.
|
|
39
|
+
- **Auto-Update Notifier**: Notifies you directly in your terminal whenever a new version is released.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🚀 Usage & Slash Commands
|
|
44
|
+
|
|
45
|
+
Inside the `eco-code` interactive REPL:
|
|
46
|
+
|
|
47
|
+
| Command | Description |
|
|
48
|
+
| :--- | :--- |
|
|
49
|
+
| `/help` | Display the interactive help menu |
|
|
50
|
+
| `/model` | Scan & switch between active OpenRouter free models |
|
|
51
|
+
| `/taste` | View or add directives to the project taste memory (`.agent/taste.md`) |
|
|
52
|
+
| `/key` | Update or rotate your OpenRouter API key |
|
|
53
|
+
| `/clear` | Reset conversation history & context |
|
|
54
|
+
| `!<command>` | Execute a direct terminal command (e.g. `!git diff`, `!npm run build`) |
|
|
55
|
+
| `/exit` | Exit the CLI agent |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 🛠️ Configuration & Credentials
|
|
60
|
+
|
|
61
|
+
On your first run, `eco-code` will ask for your [OpenRouter API Key](https://openrouter.ai/keys).
|
|
62
|
+
|
|
63
|
+
- Keys are stored securely in your user home directory (`~/.eco-code/config.json`).
|
|
64
|
+
- You can also set the environment variable:
|
|
65
|
+
```bash
|
|
66
|
+
export OPENROUTER_API_KEY="sk-or-v1-..."
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 📦 How to Publish & Maintain (For Maintainers)
|
|
72
|
+
|
|
73
|
+
1. **Clone the repository**:
|
|
74
|
+
```bash
|
|
75
|
+
git clone https://github.com/your-username/eco-code.git
|
|
76
|
+
cd eco-code
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
2. **Install dependencies**:
|
|
80
|
+
```bash
|
|
81
|
+
npm install
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
3. **Build TypeScript**:
|
|
85
|
+
```bash
|
|
86
|
+
npm run build
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
4. **Test locally with npm link**:
|
|
90
|
+
```bash
|
|
91
|
+
npm link
|
|
92
|
+
eco-code
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
5. **Publish to NPM**:
|
|
96
|
+
```bash
|
|
97
|
+
npm login
|
|
98
|
+
npm publish --access public
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 📄 License
|
|
104
|
+
|
|
105
|
+
MIT © 2025 eco-code contributors
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface FreeModelInfo {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
contextLength: number;
|
|
5
|
+
description: string;
|
|
6
|
+
score: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ModelCache {
|
|
9
|
+
timestamp: number;
|
|
10
|
+
dateStr: string;
|
|
11
|
+
models: FreeModelInfo[];
|
|
12
|
+
}
|
|
13
|
+
export interface EcoConfig {
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
defaultModel?: string;
|
|
16
|
+
systemPromptAddition?: string;
|
|
17
|
+
autoApproveTools?: boolean;
|
|
18
|
+
modelCache?: ModelCache;
|
|
19
|
+
}
|
|
20
|
+
export interface SavedSession {
|
|
21
|
+
id: string;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
model: string;
|
|
24
|
+
messages: any[];
|
|
25
|
+
}
|
|
26
|
+
export declare class ConfigManager {
|
|
27
|
+
private configDir;
|
|
28
|
+
private configFile;
|
|
29
|
+
private sessionsDir;
|
|
30
|
+
constructor();
|
|
31
|
+
private ensureDirectoryExists;
|
|
32
|
+
getConfigPath(): string;
|
|
33
|
+
load(): EcoConfig;
|
|
34
|
+
save(config: EcoConfig): void;
|
|
35
|
+
getApiKey(): string | undefined;
|
|
36
|
+
setApiKey(key: string): void;
|
|
37
|
+
getDefaultModel(): string | undefined;
|
|
38
|
+
setDefaultModel(model: string): void;
|
|
39
|
+
getCachedModels(): FreeModelInfo[] | null;
|
|
40
|
+
setCachedModels(models: FreeModelInfo[]): void;
|
|
41
|
+
saveSession(messages: any[], model: string): void;
|
|
42
|
+
loadLatestSession(): SavedSession | null;
|
|
43
|
+
clear(): void;
|
|
44
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
export class ConfigManager {
|
|
5
|
+
configDir;
|
|
6
|
+
configFile;
|
|
7
|
+
sessionsDir;
|
|
8
|
+
constructor() {
|
|
9
|
+
this.configDir = path.join(os.homedir(), '.eco-code');
|
|
10
|
+
this.configFile = path.join(this.configDir, 'config.json');
|
|
11
|
+
this.sessionsDir = path.join(this.configDir, 'sessions');
|
|
12
|
+
this.ensureDirectoryExists();
|
|
13
|
+
}
|
|
14
|
+
ensureDirectoryExists() {
|
|
15
|
+
if (!fs.existsSync(this.configDir)) {
|
|
16
|
+
fs.mkdirSync(this.configDir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
if (!fs.existsSync(this.sessionsDir)) {
|
|
19
|
+
fs.mkdirSync(this.sessionsDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
getConfigPath() {
|
|
23
|
+
return this.configFile;
|
|
24
|
+
}
|
|
25
|
+
load() {
|
|
26
|
+
try {
|
|
27
|
+
if (fs.existsSync(this.configFile)) {
|
|
28
|
+
const raw = fs.readFileSync(this.configFile, 'utf-8');
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
save(config) {
|
|
36
|
+
this.ensureDirectoryExists();
|
|
37
|
+
fs.writeFileSync(this.configFile, JSON.stringify(config, null, 2), 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
getApiKey() {
|
|
40
|
+
return process.env.OPENROUTER_API_KEY || this.load().apiKey;
|
|
41
|
+
}
|
|
42
|
+
setApiKey(key) {
|
|
43
|
+
const config = this.load();
|
|
44
|
+
config.apiKey = key.trim();
|
|
45
|
+
this.save(config);
|
|
46
|
+
}
|
|
47
|
+
getDefaultModel() {
|
|
48
|
+
return this.load().defaultModel;
|
|
49
|
+
}
|
|
50
|
+
setDefaultModel(model) {
|
|
51
|
+
const config = this.load();
|
|
52
|
+
config.defaultModel = model;
|
|
53
|
+
this.save(config);
|
|
54
|
+
}
|
|
55
|
+
// --- Model Cache (Daily / 24h invalidation) ---
|
|
56
|
+
getCachedModels() {
|
|
57
|
+
const config = this.load();
|
|
58
|
+
const cache = config.modelCache;
|
|
59
|
+
if (!cache || !Array.isArray(cache.models) || cache.models.length === 0) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const todayStr = new Date().toISOString().slice(0, 10);
|
|
63
|
+
const isWithin24Hours = Date.now() - cache.timestamp < 24 * 60 * 60 * 1000;
|
|
64
|
+
const isSameDay = cache.dateStr === todayStr;
|
|
65
|
+
if (isSameDay && isWithin24Hours) {
|
|
66
|
+
return cache.models;
|
|
67
|
+
}
|
|
68
|
+
return null; // Stale cache, needs daily refresh
|
|
69
|
+
}
|
|
70
|
+
setCachedModels(models) {
|
|
71
|
+
const config = this.load();
|
|
72
|
+
config.modelCache = {
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
dateStr: new Date().toISOString().slice(0, 10),
|
|
75
|
+
models,
|
|
76
|
+
};
|
|
77
|
+
this.save(config);
|
|
78
|
+
}
|
|
79
|
+
// --- Session Persistence & Resume ---
|
|
80
|
+
saveSession(messages, model) {
|
|
81
|
+
this.ensureDirectoryExists();
|
|
82
|
+
const latestPath = path.join(this.sessionsDir, 'latest.json');
|
|
83
|
+
const sessionData = {
|
|
84
|
+
id: 'latest',
|
|
85
|
+
timestamp: Date.now(),
|
|
86
|
+
model,
|
|
87
|
+
messages,
|
|
88
|
+
};
|
|
89
|
+
try {
|
|
90
|
+
fs.writeFileSync(latestPath, JSON.stringify(sessionData, null, 2), 'utf-8');
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
}
|
|
94
|
+
loadLatestSession() {
|
|
95
|
+
const latestPath = path.join(this.sessionsDir, 'latest.json');
|
|
96
|
+
try {
|
|
97
|
+
if (fs.existsSync(latestPath)) {
|
|
98
|
+
const raw = fs.readFileSync(latestPath, 'utf-8');
|
|
99
|
+
return JSON.parse(raw);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch { }
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
clear() {
|
|
106
|
+
if (fs.existsSync(this.configFile)) {
|
|
107
|
+
fs.unlinkSync(this.configFile);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { ContextManager } from './context.js';
|
|
3
|
+
import { ConfigManager } from '../config.js';
|
|
4
|
+
export interface TurnCallbacks {
|
|
5
|
+
onToolStart?: (toolName: string, args: any) => void;
|
|
6
|
+
onToolEnd?: (toolName: string, result: string) => void;
|
|
7
|
+
onToken?: (token: string) => void;
|
|
8
|
+
}
|
|
9
|
+
export interface ContextStats {
|
|
10
|
+
usedTokens: number;
|
|
11
|
+
maxTokens: number;
|
|
12
|
+
sessionTotalTokens: number;
|
|
13
|
+
percentage: number;
|
|
14
|
+
isWarning: boolean;
|
|
15
|
+
isCritical: boolean;
|
|
16
|
+
progressBar: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class AgentHarness {
|
|
19
|
+
private client;
|
|
20
|
+
private model;
|
|
21
|
+
private maxTokens;
|
|
22
|
+
private currentTokens;
|
|
23
|
+
private sessionTotalTokens;
|
|
24
|
+
private contextManager;
|
|
25
|
+
private configManager;
|
|
26
|
+
private history;
|
|
27
|
+
private workspaceDir;
|
|
28
|
+
constructor(client: OpenAI, model: string, maxTokens: number, configManager: ConfigManager, workspaceDir?: string);
|
|
29
|
+
resetContext(): void;
|
|
30
|
+
setModel(newModel: string, maxTokens?: number): void;
|
|
31
|
+
getModel(): string;
|
|
32
|
+
getMaxTokens(): number;
|
|
33
|
+
getSessionTotalTokens(): number;
|
|
34
|
+
getContextManager(): ContextManager;
|
|
35
|
+
getHistory(): OpenAI.Chat.Completions.ChatCompletionMessageParam[];
|
|
36
|
+
setHistory(newHistory: OpenAI.Chat.Completions.ChatCompletionMessageParam[]): void;
|
|
37
|
+
private recalculateEstimatedTokens;
|
|
38
|
+
/**
|
|
39
|
+
* Calculates real-time context token usage and percentage
|
|
40
|
+
*/
|
|
41
|
+
getContextStats(): ContextStats;
|
|
42
|
+
/**
|
|
43
|
+
* Manually compacts context and recalculates tokens
|
|
44
|
+
*/
|
|
45
|
+
compactContext(): {
|
|
46
|
+
before: number;
|
|
47
|
+
after: number;
|
|
48
|
+
savedPercentage: number;
|
|
49
|
+
};
|
|
50
|
+
runTurn(userInput: string, callbacks?: TurnCallbacks): Promise<string>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { TOOL_SCHEMAS, executeToolCall } from '../tools/index.js';
|
|
2
|
+
import { ContextManager } from './context.js';
|
|
3
|
+
export class AgentHarness {
|
|
4
|
+
client;
|
|
5
|
+
model;
|
|
6
|
+
maxTokens;
|
|
7
|
+
currentTokens = 0;
|
|
8
|
+
sessionTotalTokens = 0;
|
|
9
|
+
contextManager;
|
|
10
|
+
configManager;
|
|
11
|
+
history = [];
|
|
12
|
+
workspaceDir;
|
|
13
|
+
constructor(client, model, maxTokens, configManager, workspaceDir = process.cwd()) {
|
|
14
|
+
this.client = client;
|
|
15
|
+
this.model = model;
|
|
16
|
+
this.maxTokens = maxTokens || 8192;
|
|
17
|
+
this.configManager = configManager;
|
|
18
|
+
this.workspaceDir = workspaceDir;
|
|
19
|
+
this.contextManager = new ContextManager(workspaceDir);
|
|
20
|
+
this.resetContext();
|
|
21
|
+
}
|
|
22
|
+
resetContext() {
|
|
23
|
+
this.history = [
|
|
24
|
+
{
|
|
25
|
+
role: 'system',
|
|
26
|
+
content: this.contextManager.buildSystemPrompt(),
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
this.recalculateEstimatedTokens();
|
|
30
|
+
}
|
|
31
|
+
setModel(newModel, maxTokens) {
|
|
32
|
+
this.model = newModel;
|
|
33
|
+
if (maxTokens) {
|
|
34
|
+
this.maxTokens = maxTokens;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
getModel() {
|
|
38
|
+
return this.model;
|
|
39
|
+
}
|
|
40
|
+
getMaxTokens() {
|
|
41
|
+
return this.maxTokens;
|
|
42
|
+
}
|
|
43
|
+
getSessionTotalTokens() {
|
|
44
|
+
return this.sessionTotalTokens;
|
|
45
|
+
}
|
|
46
|
+
getContextManager() {
|
|
47
|
+
return this.contextManager;
|
|
48
|
+
}
|
|
49
|
+
getHistory() {
|
|
50
|
+
return this.history;
|
|
51
|
+
}
|
|
52
|
+
setHistory(newHistory) {
|
|
53
|
+
this.history = newHistory;
|
|
54
|
+
this.recalculateEstimatedTokens();
|
|
55
|
+
}
|
|
56
|
+
recalculateEstimatedTokens() {
|
|
57
|
+
const rawStr = JSON.stringify(this.history);
|
|
58
|
+
// Standard heuristic: ~3.8 characters per token for code/text
|
|
59
|
+
this.currentTokens = Math.ceil(rawStr.length / 3.8);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Calculates real-time context token usage and percentage
|
|
63
|
+
*/
|
|
64
|
+
getContextStats() {
|
|
65
|
+
if (this.currentTokens === 0) {
|
|
66
|
+
this.recalculateEstimatedTokens();
|
|
67
|
+
}
|
|
68
|
+
const percentage = Math.min(100, Math.max(1, Math.round((this.currentTokens / this.maxTokens) * 100)));
|
|
69
|
+
const isCritical = percentage >= 75;
|
|
70
|
+
const isWarning = percentage >= 55 && percentage < 75;
|
|
71
|
+
// Build visual progress bar: [██████░░░░░░░░░░]
|
|
72
|
+
const totalBars = 10;
|
|
73
|
+
const filledBars = Math.min(totalBars, Math.round((percentage / 100) * totalBars));
|
|
74
|
+
const emptyBars = totalBars - filledBars;
|
|
75
|
+
const progressBar = `[${'█'.repeat(filledBars)}${'░'.repeat(emptyBars)}]`;
|
|
76
|
+
return {
|
|
77
|
+
usedTokens: this.currentTokens,
|
|
78
|
+
maxTokens: this.maxTokens,
|
|
79
|
+
sessionTotalTokens: this.sessionTotalTokens,
|
|
80
|
+
percentage,
|
|
81
|
+
isWarning,
|
|
82
|
+
isCritical,
|
|
83
|
+
progressBar,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Manually compacts context and recalculates tokens
|
|
88
|
+
*/
|
|
89
|
+
compactContext() {
|
|
90
|
+
const beforeStats = this.getContextStats();
|
|
91
|
+
const before = this.history.length;
|
|
92
|
+
this.history = this.contextManager.manualCompact(this.history, 4);
|
|
93
|
+
this.recalculateEstimatedTokens();
|
|
94
|
+
const afterStats = this.getContextStats();
|
|
95
|
+
const after = this.history.length;
|
|
96
|
+
const savedPercentage = Math.max(0, beforeStats.percentage - afterStats.percentage);
|
|
97
|
+
return { before, after, savedPercentage };
|
|
98
|
+
}
|
|
99
|
+
async runTurn(userInput, callbacks = {}) {
|
|
100
|
+
this.history.push({ role: 'user', content: userInput });
|
|
101
|
+
this.history = this.contextManager.autoCompactHistory(this.history);
|
|
102
|
+
let maxSteps = 15;
|
|
103
|
+
let stepCount = 0;
|
|
104
|
+
while (stepCount < maxSteps) {
|
|
105
|
+
stepCount++;
|
|
106
|
+
let response;
|
|
107
|
+
try {
|
|
108
|
+
response = await this.client.chat.completions.create({
|
|
109
|
+
model: this.model,
|
|
110
|
+
messages: this.history,
|
|
111
|
+
tools: TOOL_SCHEMAS,
|
|
112
|
+
tool_choice: 'auto',
|
|
113
|
+
temperature: 0.2,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
throw new Error(`OpenRouter API error with model [${this.model}]: ${err.message}`);
|
|
118
|
+
}
|
|
119
|
+
// Record live usage from OpenRouter if reported
|
|
120
|
+
if (response.usage && response.usage.total_tokens) {
|
|
121
|
+
this.currentTokens = response.usage.total_tokens;
|
|
122
|
+
this.sessionTotalTokens += response.usage.total_tokens;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
this.recalculateEstimatedTokens();
|
|
126
|
+
// Fallback: estimate session tokens based on current context length
|
|
127
|
+
// This is a rough heuristic if API doesn't return usage
|
|
128
|
+
this.sessionTotalTokens += this.currentTokens;
|
|
129
|
+
}
|
|
130
|
+
const message = response.choices[0]?.message;
|
|
131
|
+
if (!message) {
|
|
132
|
+
throw new Error('Received empty response from model.');
|
|
133
|
+
}
|
|
134
|
+
this.history.push(message);
|
|
135
|
+
// Check if tool calls were requested
|
|
136
|
+
if (message.tool_calls && message.tool_calls.length > 0) {
|
|
137
|
+
for (const toolCall of message.tool_calls) {
|
|
138
|
+
if (toolCall.type === 'function') {
|
|
139
|
+
const funcName = toolCall.function.name;
|
|
140
|
+
let args = {};
|
|
141
|
+
try {
|
|
142
|
+
args = JSON.parse(toolCall.function.arguments);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
args = {};
|
|
146
|
+
}
|
|
147
|
+
callbacks.onToolStart?.(funcName, args);
|
|
148
|
+
const toolResult = await executeToolCall(funcName, args, this.workspaceDir);
|
|
149
|
+
callbacks.onToolEnd?.(funcName, toolResult);
|
|
150
|
+
this.history.push({
|
|
151
|
+
role: 'tool',
|
|
152
|
+
tool_call_id: toolCall.id,
|
|
153
|
+
content: toolResult,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// Save session state to disk automatically
|
|
160
|
+
this.configManager.saveSession(this.history, this.model);
|
|
161
|
+
return message.content || '(No response provided)';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
this.configManager.saveSession(this.history, this.model);
|
|
165
|
+
return 'Agent reached maximum reasoning steps for this turn.';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/harness/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { TasteEngine } from '../taste/engine.js';
|
|
3
|
+
export declare class ContextManager {
|
|
4
|
+
private tasteEngine;
|
|
5
|
+
private workspaceDir;
|
|
6
|
+
constructor(workspaceDir?: string);
|
|
7
|
+
buildSystemPrompt(): string;
|
|
8
|
+
getTasteEngine(): TasteEngine;
|
|
9
|
+
/**
|
|
10
|
+
* Automatically compacts history if it exceeds rolling limit
|
|
11
|
+
*/
|
|
12
|
+
autoCompactHistory(messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[], maxTurns?: number): OpenAI.Chat.Completions.ChatCompletionMessageParam[];
|
|
13
|
+
/**
|
|
14
|
+
* Explicitly compacts history, keeping system prompt + summary + last N messages
|
|
15
|
+
*/
|
|
16
|
+
manualCompact(messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[], keepRecent?: number): OpenAI.Chat.Completions.ChatCompletionMessageParam[];
|
|
17
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as os from 'os';
|
|
2
|
+
import { TasteEngine } from '../taste/engine.js';
|
|
3
|
+
export class ContextManager {
|
|
4
|
+
tasteEngine;
|
|
5
|
+
workspaceDir;
|
|
6
|
+
constructor(workspaceDir = process.cwd()) {
|
|
7
|
+
this.workspaceDir = workspaceDir;
|
|
8
|
+
this.tasteEngine = new TasteEngine(workspaceDir);
|
|
9
|
+
}
|
|
10
|
+
buildSystemPrompt() {
|
|
11
|
+
const taste = this.tasteEngine.getTastePrompt();
|
|
12
|
+
return `You are eco-code, an autonomous and intelligent CLI coding assistant.
|
|
13
|
+
You are running directly in the developer's terminal environment.
|
|
14
|
+
|
|
15
|
+
Current Workspace: ${this.workspaceDir}
|
|
16
|
+
Operating System: ${os.platform()} (${os.arch()})
|
|
17
|
+
|
|
18
|
+
## Tools — IMPORTANT CONTRACT
|
|
19
|
+
Always prefer these deterministic tools:
|
|
20
|
+
- Use \`read_file\` for all file reading. It is windowed and limits reads to 2000 lines. Do not re-read large files from the beginning.
|
|
21
|
+
- Use \`run_command\` for all shell commands.
|
|
22
|
+
- For ANY command that might take > 5 seconds, set \`run_in_background: true\`.
|
|
23
|
+
- Read background task output with \`shell_output\` and \`from_offset\` (start at 0, then pass the returned \`next_offset\` for subsequent reads).
|
|
24
|
+
- NEVER manually loop run->sleep->read. Use the \`sleep\` tool to pause, or \`monitor_command\` to wait for an exit or log pattern.
|
|
25
|
+
- Exit code 1 from \`grep\` means "no matches found" — this is NOT an error, do not retry.
|
|
26
|
+
|
|
27
|
+
## CORE CAPABILITIES & RULES:
|
|
28
|
+
1. Always inspect a file using read_file before attempting to edit it with replace_in_file.
|
|
29
|
+
2. Be precise with replace_in_file: target_content must match the file content verbatim.
|
|
30
|
+
3. When writing code, write complete, working code without placeholders or omissions.
|
|
31
|
+
4. Verify your changes when appropriate.
|
|
32
|
+
5. Keep conversational replies concise. Stop when the task is done. Do not pad responses.
|
|
33
|
+
|
|
34
|
+
TASTE & DIRECTIVES:
|
|
35
|
+
${taste}`;
|
|
36
|
+
}
|
|
37
|
+
getTasteEngine() {
|
|
38
|
+
return this.tasteEngine;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Automatically compacts history if it exceeds rolling limit
|
|
42
|
+
*/
|
|
43
|
+
autoCompactHistory(messages, maxTurns = 20) {
|
|
44
|
+
if (messages.length <= maxTurns) {
|
|
45
|
+
return messages;
|
|
46
|
+
}
|
|
47
|
+
return this.manualCompact(messages, 6);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Explicitly compacts history, keeping system prompt + summary + last N messages
|
|
51
|
+
*/
|
|
52
|
+
manualCompact(messages, keepRecent = 4) {
|
|
53
|
+
if (messages.length <= keepRecent + 1) {
|
|
54
|
+
return messages;
|
|
55
|
+
}
|
|
56
|
+
const systemMsg = messages[0];
|
|
57
|
+
const olderMessages = messages.slice(1, -keepRecent);
|
|
58
|
+
const recentMessages = messages.slice(-keepRecent);
|
|
59
|
+
// Extract quick overview of past actions
|
|
60
|
+
const summaryLines = [];
|
|
61
|
+
for (const msg of olderMessages) {
|
|
62
|
+
if (msg.role === 'user' && typeof msg.content === 'string') {
|
|
63
|
+
summaryLines.push(`User asked: "${msg.content.slice(0, 100)}..."`);
|
|
64
|
+
}
|
|
65
|
+
else if (msg.role === 'assistant' && msg.tool_calls) {
|
|
66
|
+
const toolNames = msg.tool_calls.map((t) => t.type === 'function' ? t.function.name : '').join(', ');
|
|
67
|
+
summaryLines.push(`Executed tools: [${toolNames}]`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const summaryMessage = {
|
|
71
|
+
role: 'system',
|
|
72
|
+
content: `[Previous Conversation Compaction Summary]\n${summaryLines.slice(-10).join('\n')}\n(Earlier context compacted to save tokens)`,
|
|
73
|
+
};
|
|
74
|
+
return [systemMsg, summaryMessage, ...recentMessages];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/harness/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAEzB,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,OAAO,cAAc;IACjB,WAAW,CAAc;IACzB,YAAY,CAAS;IAE7B,YAAY,eAAuB,OAAO,CAAC,GAAG,EAAE;QAC9C,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,YAAY,CAAC,CAAC;IACnD,CAAC;IAED,iBAAiB;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC;QAChD,OAAO;;;qBAGU,IAAI,CAAC,YAAY;oBAClB,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE;;;;;;;;;;;;;;;;;;;EAmB7C,KAAK,EAAE,CAAC;IACR,CAAC;IAED,cAAc;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED;;OAEG;IACH,kBAAkB,CAChB,QAA8D,EAC9D,WAAmB,EAAE;QAErB,IAAI,QAAQ,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;YAChC,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;IAED;;OAEG;IACH,aAAa,CACX,QAA8D,EAC9D,aAAqB,CAAC;QAEtB,IAAI,QAAQ,CAAC,MAAM,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;YACtC,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,aAAa,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC;QACrD,MAAM,cAAc,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC;QAEnD,yCAAyC;QACzC,MAAM,YAAY,GAAa,EAAE,CAAC;QAClC,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;YAChC,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAC3D,YAAY,CAAC,IAAI,CAAC,gBAAgB,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YACrE,CAAC;iBAAM,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;gBACtD,MAAM,SAAS,GAAG,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACrG,YAAY,CAAC,IAAI,CAAC,oBAAoB,SAAS,GAAG,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;QAED,MAAM,cAAc,GAAuD;YACzE,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,+CAA+C,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,8CAA8C;SACzI,CAAC;QAEF,OAAO,CAAC,SAAS,EAAE,cAAc,EAAE,GAAG,cAAc,CAAC,CAAC;IACxD,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED