Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b5c6352
Don't yap for the LLM
Jun 23, 2026
857cf69
Update repo links in docs
Jun 23, 2026
0bbb611
Improve stdin propagation to background commands
Jun 24, 2026
a85fa60
Only give content hashes for contentful lines
Jun 26, 2026
9e6e45f
Update py-cymbal
Jun 26, 2026
b6a9f0c
Let py-cymbal rock in case I push this version before the timeout exp…
Jun 26, 2026
35c9ac0
#587: Strip openai prefix from LLM proxies for proper costs and token…
Jun 26, 2026
931c220
Fix cache hit token cost calculator
Jun 26, 2026
adb7218
#584: Remove ambient refresh interval from non-TUI mode
Jun 26, 2026
c1d167b
Add documentation for hot-reload, in aget mode make lint errors in ou…
Jun 27, 2026
4fb5313
Specyify mime type in images messages, update file content soft compa…
Jun 27, 2026
ee623ca
Add script to view diff size of conversation snapshots
Jun 27, 2026
48f3aa9
Update agentic linting to not be cache destructive
Jun 28, 2026
5f79375
JSON File Representation
Jun 28, 2026
a4db0bd
Linting errors should be prompted by shorter ephemeral bump message
Jun 28, 2026
58dec4b
Separate concatenated user messages more explicitly
Jun 28, 2026
c96f93b
Clear ranges on edit so program can re-read changes immediately
Jun 28, 2026
d2934aa
Update EditText message when model doesn't use content ids
Jun 28, 2026
0351b1b
Only clear latest quarter of messages when total tokens gets high
Jun 28, 2026
929b347
Don't duplicate whole file contents unnecessarily
Jun 28, 2026
6355ecb
Only clear a third of diffs in files manager
Jun 28, 2026
14e1000
Improve cache hit frequency (final try) BECAUSE LLM PROVIDERS POPULAT…
Jun 28, 2026
cc0fe92
Update Ls and ResourceManager tools to use emojiis
Jun 28, 2026
09f3589
Update tests for hashline_formatted
Jun 28, 2026
d1d8689
Update cost analyzer script
Jun 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions cecli/coders/agent_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,11 @@ def _get_agent_config(self):
config["large_file_token_threshold"] = nested.getter(
config, "large_file_token_threshold", 8192
)
config["show_lint_errors"] = nested.getter(config, "show_lint_errors", False)
config["skip_cli_confirmations"] = nested.getter(
config, "skip_cli_confirmations", nested.getter(config, "yolo", [])
)

config["command_timeout"] = nested.getter(config, "command_timeout", 30)
config["allowed_commands"] = nested.getter(config, "allowed_commands", [])
config["hot_reload"] = nested.getter(config, "hot_reload", False)
Expand Down Expand Up @@ -816,6 +818,11 @@ async def gather_and_await():
if interrupted:
raise KeyboardInterrupt("Interrupted during linting")

has_errors = False

if self.lint_outcome is False:
has_errors = True

self.lint_outcome = not lint_errors

if lint_errors:
Expand All @@ -824,21 +831,35 @@ async def gather_and_await():
"# Fix any linting errors below, if possible and then continue with your task.",
1,
)
ConversationService.get_manager(self).remove_message_by_hash_key(
("lint_errors", "agent")
)
ConversationService.get_manager(self).add_message(
message_dict=dict(role="user", content=lint_errors),
tag=MessageTag.CUR,
hash_key=("lint_errors", "agent"),
tag=MessageTag.LINT,
hash_key=("lint_errors", "agent", lint_errors),
)
ConversationService.get_manager(self).add_message(
message_dict=dict(
role="user", content="Please address the latest linting errors."
),
tag=MessageTag.LINT,
hash_key=("lint_errors", "agent", lint_errors, "cta"),
promotion=ConversationService.get_manager(self).DEFAULT_TAG_PROMOTION_VALUE,
mark_for_demotion=1,
force=True,
mark_for_delete=0,
)
else:
ConversationService.get_manager(self).remove_message_by_hash_key(
("lint_errors", "agent")
)
if has_errors:
ConversationService.get_manager(self).add_message(
message_dict=dict(
role="user",
content=(
'<context name="linting_confirmation" from="agent">'
"All linting errors resolved."
"</context>"
),
),
tag=MessageTag.LINT,
hash_key=("lint_errors", "agent", str(time.monotonic_ns())),
)

return tool_responses

Expand Down Expand Up @@ -1666,6 +1687,11 @@ def get_background_command_output(self):
command_str = command_info.get(command_key, {}).get("command", command_key)
output += f"\n[bg: {command_str}]\n{cmd_output}\n"

# Clean up stale (finished) background commands after reading their output
for command_key, info in command_info.items():
if not info.get("running", False):
BackgroundCommandManager.stop_background_command(command_key)

return output

def get_git_status(self):
Expand Down
35 changes: 24 additions & 11 deletions cecli/coders/base_coder.py
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ def __init__(
self.mcp_manager = mcp_manager
self.enable_context_compaction = enable_context_compaction

self.context_compaction_current_ratio = 0
self.context_compaction_max_tokens = context_compaction_max_tokens
self.context_compaction_summary_tokens = context_compaction_summary_tokens
self.max_reflections = nested.getter(self.args, "max_reflections", 3)
Expand Down Expand Up @@ -876,9 +877,8 @@ def get_announcements(self):
env_items.append(f"{rel_repo_dir} ({num_files:,} files)")
if num_files > 1000:
env_items.append(
"Warning: For large repos, consider using --subtree-only and .cecli_ignore"
"Warning: For large repos, consider using --subtree-only and .cecli.ignore"
)
env_items.append(f"See: {urls.large_repos}")
else:
env_items.append("no git repo")

Expand Down Expand Up @@ -1440,7 +1440,10 @@ def get_images_message(self, fnames):
if mime_type.startswith("image/") and supports_images:
content = [
{"type": "text", "text": f"Image file: {rel_fname}"},
{"type": "image_url", "image_url": {"url": image_url, "detail": "high"}},
{
"type": "image_url",
"image_url": {"url": image_url, "detail": "high", "format": mime_type},
},
]
elif mime_type == "application/pdf" and supports_pdfs:
content = [
Expand Down Expand Up @@ -2001,12 +2004,15 @@ async def compact_context_if_needed(self, force=False, message=""):

combined_tokens = done_tokens + cur_tokens + diff_tokens

self.context_compaction_current_ratio = all_tokens / self.context_compaction_max_tokens

if force or (
all_tokens >= self.context_compaction_max_tokens * 0.9
and ConversationService.get_chunks(self).last_clear_count > 10
and ConversationService.get_chunks(self).last_clear_count > 20
):
manager.clear_tag(MessageTag.DIFFS)
manager.clear_tag(MessageTag.FILE_CONTEXTS)
manager.clear_tag(MessageTag.LINT, ratio=0.33)
manager.clear_tag(MessageTag.DIFFS, ratio=0.33)
manager.clear_tag(MessageTag.FILE_CONTEXTS, ratio=0.33)
ConversationService.get_files(self).clear_file_cache()
ConversationService.get_chunks(self).flush_removals()

Expand Down Expand Up @@ -3249,6 +3255,12 @@ async def lint_edited(self, fnames, show_output=True):
res += errors
res += "\n"

if self.edit_format in ("agent", "subagent"):
if self.agent_config.get("show_lint_errors"):
show_output = True
else:
show_output = False

if res and show_output:
self.io.tool_warning(res)

Expand Down Expand Up @@ -4024,7 +4036,9 @@ def compute_costs_from_tokens(
input_cost_per_token = self.get_active_model().info.get("input_cost_per_token") or 0
output_cost_per_token = self.get_active_model().info.get("output_cost_per_token") or 0
input_cost_per_token_cache_hit = (
self.get_active_model().info.get("input_cost_per_token_cache_hit") or 0
self.get_active_model().info.get("input_cost_per_token_cache_hit")
or self.get_active_model().info.get("cache_read_input_token_cost")
or 0
)

# deepseek
Expand All @@ -4036,14 +4050,13 @@ def compute_costs_from_tokens(
# == total tokens that were

if input_cost_per_token_cache_hit:
# must be deepseek
cost += input_cost_per_token_cache_hit * cache_hit_tokens
cost += (prompt_tokens - input_cost_per_token_cache_hit) * input_cost_per_token
cost += cache_hit_tokens * input_cost_per_token_cache_hit
cost += (prompt_tokens - cache_hit_tokens) * input_cost_per_token
else:
# hard code the anthropic adjustments, no-ops for other models since cache_x_tokens==0
cost += cache_write_tokens * input_cost_per_token * 1.25
cost += cache_hit_tokens * input_cost_per_token * 0.10
cost += prompt_tokens * input_cost_per_token
cost += (prompt_tokens - cache_hit_tokens) * input_cost_per_token

cost += completion_tokens * output_cost_per_token
return cost
Expand Down
Loading
Loading