Skip to content

Commit 20eda97

Browse files
committed
feat: /model display, /diff, cost estimation, multiline input
- /model without args now shows current model (inspired by PR #4) - /diff lists files modified by edit_file/write_file this session - /tokens shows estimated cost for known models (GPT-4o, DeepSeek, etc.) - Multiline input via Esc+Enter, Enter still submits - Fix __version__ 0.1.0 -> 0.2.0 mismatch with pyproject.toml
1 parent 5180986 commit 20eda97

8 files changed

Lines changed: 89 additions & 9 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,11 @@ class HttpTool(Tool):
139139
## Commands
140140

141141
```
142+
/model Show current model
142143
/model <name> Switch model mid-conversation
143144
/compact Compress context (like Claude Code's /compact)
144-
/tokens Token usage
145+
/tokens Token usage + cost estimate
146+
/diff Show files modified this session
145147
/save Save session to disk
146148
/sessions List saved sessions
147149
/reset Clear history

README_CN.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,11 @@ class HttpTool(Tool):
139139
## 命令
140140

141141
```
142+
/model 查看当前模型
142143
/model <名称> 切换模型
143144
/compact 压缩上下文(对标 Claude Code 的 /compact)
144-
/tokens 查看 token 用量
145+
/tokens 查看 token 用量 + 费用估算
146+
/diff 查看本次会话修改的文件
145147
/save 保存会话
146148
/sessions 列出已保存的会话
147149
/reset 清空历史

corecoder/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""CoreCoder - Minimal AI coding agent inspired by Claude Code's architecture."""
22

3-
__version__ = "0.1.0"
3+
__version__ = "0.2.0"
44

55
from corecoder.agent import Agent
66
from corecoder.llm import LLM

corecoder/cli.py

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from rich.panel import Panel
1010
from prompt_toolkit import prompt as pt_prompt
1111
from prompt_toolkit.history import FileHistory
12+
from prompt_toolkit.key_binding import KeyBindings
1213

1314
from .agent import Agent
1415
from .llm import LLM
@@ -118,9 +119,26 @@ def _repl(agent: Agent, config: Config):
118119
hist_path = os.path.expanduser("~/.corecoder_history")
119120
history = FileHistory(hist_path)
120121

122+
# Enter submits, Escape+Enter inserts a newline (for pasting code blocks etc.)
123+
kb = KeyBindings()
124+
125+
@kb.add("enter")
126+
def _submit(event):
127+
event.current_buffer.validate_and_handle()
128+
129+
@kb.add("escape", "enter")
130+
def _newline(event):
131+
event.current_buffer.insert_text("\n")
132+
121133
while True:
122134
try:
123-
user_input = pt_prompt("You > ", history=history).strip()
135+
user_input = pt_prompt(
136+
"You > ",
137+
history=history,
138+
multiline=True,
139+
key_bindings=kb,
140+
prompt_continuation="... ",
141+
).strip()
124142
except (EOFError, KeyboardInterrupt):
125143
console.print("\nBye!")
126144
break
@@ -141,14 +159,20 @@ def _repl(agent: Agent, config: Config):
141159
if user_input == "/tokens":
142160
p = agent.llm.total_prompt_tokens
143161
c = agent.llm.total_completion_tokens
144-
console.print(f"Tokens used this session: [cyan]{p}[/cyan] prompt + [cyan]{c}[/cyan] completion = [bold]{p+c}[/bold] total")
162+
line = f"Tokens: [cyan]{p}[/cyan] prompt + [cyan]{c}[/cyan] completion = [bold]{p+c}[/bold] total"
163+
cost = agent.llm.estimated_cost
164+
if cost is not None:
165+
line += f" (~${cost:.4f})"
166+
console.print(line)
145167
continue
146-
if user_input.startswith("/model "):
147-
new_model = user_input[7:].strip()
168+
if user_input == "/model" or user_input.startswith("/model "):
169+
new_model = user_input[7:].strip() if user_input.startswith("/model ") else ""
148170
if new_model:
149171
agent.llm.model = new_model
150172
config.model = new_model
151173
console.print(f"Switched to [cyan]{new_model}[/cyan]")
174+
else:
175+
console.print(f"Current model: [cyan]{config.model}[/cyan]")
152176
continue
153177
if user_input == "/compact":
154178
from .context import estimate_tokens
@@ -165,6 +189,15 @@ def _repl(agent: Agent, config: Config):
165189
console.print(f"[green]Session saved: {sid}[/green]")
166190
console.print(f"Resume with: corecoder -r {sid}")
167191
continue
192+
if user_input == "/diff":
193+
from .tools.edit import _changed_files
194+
if not _changed_files:
195+
console.print("[dim]No files modified this session.[/dim]")
196+
else:
197+
console.print(f"[bold]Files modified this session ({len(_changed_files)}):[/bold]")
198+
for f in sorted(_changed_files):
199+
console.print(f" [cyan]{f}[/cyan]")
200+
continue
168201
if user_input == "/sessions":
169202
sessions = list_sessions()
170203
if not sessions:
@@ -202,12 +235,18 @@ def _show_help():
202235
"[bold]Commands:[/bold]\n"
203236
" /help Show this help\n"
204237
" /reset Clear conversation history\n"
238+
" /model Show current model\n"
205239
" /model <name> Switch model mid-conversation\n"
206240
" /tokens Show token usage\n"
207241
" /compact Compress conversation context\n"
242+
" /diff Show files modified this session\n"
208243
" /save Save session to disk\n"
209244
" /sessions List saved sessions\n"
210-
" quit Exit CoreCoder",
245+
" quit Exit CoreCoder\n"
246+
"\n"
247+
"[bold]Input:[/bold]\n"
248+
" Enter Submit message\n"
249+
" Esc+Enter Insert newline (for pasting code)",
211250
title="CoreCoder Help",
212251
border_style="dim",
213252
))

corecoder/llm.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,25 @@ def message(self) -> dict:
4545
return msg
4646

4747

48+
# pricing per million tokens: (input, output)
49+
_PRICING = {
50+
"gpt-4o": (2.5, 10),
51+
"gpt-4o-mini": (0.15, 0.6),
52+
"gpt-4.1": (2, 8),
53+
"gpt-4.1-mini": (0.4, 1.6),
54+
"gpt-4.1-nano": (0.1, 0.4),
55+
"o3": (2, 8),
56+
"o4-mini": (1.1, 4.4),
57+
"deepseek-chat": (0.27, 1.10),
58+
"deepseek-reasoner": (0.55, 2.19),
59+
"claude-sonnet-4-6": (3, 15),
60+
"claude-opus-4-6": (15, 75),
61+
"claude-haiku-4-5": (0.8, 4),
62+
"qwen-max": (1.6, 6.4),
63+
"kimi-k2.5": (0.7, 2.8),
64+
}
65+
66+
4867
class LLM:
4968
def __init__(
5069
self,
@@ -59,6 +78,18 @@ def __init__(
5978
self.total_prompt_tokens = 0
6079
self.total_completion_tokens = 0
6180

81+
@property
82+
def estimated_cost(self) -> float | None:
83+
"""Rough cost estimate in USD. Returns None if model not in pricing table."""
84+
pricing = _PRICING.get(self.model)
85+
if not pricing:
86+
return None
87+
input_rate, output_rate = pricing
88+
return (
89+
self.total_prompt_tokens * input_rate / 1_000_000
90+
+ self.total_completion_tokens * output_rate / 1_000_000
91+
)
92+
6293
def chat(
6394
self,
6495
messages: list[dict],

corecoder/tools/edit.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111

1212
from .base import Tool
1313

14+
# track files changed this session for /diff
15+
_changed_files: set[str] = set()
16+
1417

1518
class EditFileTool(Tool):
1619
name = "edit_file"
@@ -61,6 +64,7 @@ def execute(self, file_path: str, old_string: str, new_string: str) -> str:
6164

6265
new_content = content.replace(old_string, new_string, 1)
6366
p.write_text(new_content)
67+
_changed_files.add(str(p))
6468

6569
# generate a unified diff so the user/LLM can see exactly what changed
6670
diff = _unified_diff(content, new_content, str(p))

corecoder/tools/write.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from pathlib import Path
44
from .base import Tool
5+
from .edit import _changed_files
56

67

78
class WriteFileTool(Tool):
@@ -30,6 +31,7 @@ def execute(self, file_path: str, content: str) -> str:
3031
p = Path(file_path).expanduser().resolve()
3132
p.parent.mkdir(parents=True, exist_ok=True)
3233
p.write_text(content)
34+
_changed_files.add(str(p))
3335
n_lines = content.count("\n") + (1 if content and not content.endswith("\n") else 0)
3436
return f"Wrote {n_lines} lines to {file_path}"
3537
except Exception as e:

tests/test_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
def test_version():
13-
assert __version__ == "0.1.0"
13+
assert __version__ == "0.2.0"
1414

1515

1616
def test_public_api_exports():

0 commit comments

Comments
 (0)