Skip to content

Commit 33493d1

Browse files
authored
Merge pull request #632 from cecli-dev/v1.1.0
V1.1.0
2 parents 7fb4509 + 43df6e7 commit 33493d1

43 files changed

Lines changed: 4607 additions & 541 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cecli/coders/agent_coder.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
)
2828
from cecli.helpers.skills import SkillsManager
2929
from cecli.hooks import HookIntegration
30-
from cecli.llm import litellm
3130
from cecli.mcp import LocalServer, McpServerManager
3231
from cecli.tools.utils.base_tool import BaseTool
3332
from cecli.tools.utils.registry import ToolRegistry
@@ -364,16 +363,12 @@ async def _exec_async():
364363
}
365364
try:
366365
session = await server.connect()
367-
call_result = await litellm.experimental_mcp_client.call_openai_tool(
368-
session=session, openai_tool=tool_call_dict
369-
)
366+
call_result = await self.call_mcp_tool_from_session(session, tool_call_dict)
370367
except Exception as e:
371368
if server.is_session_expired_error(e):
372369
try:
373370
session = await server.reconnect()
374-
call_result = await litellm.experimental_mcp_client.call_openai_tool(
375-
session=session, openai_tool=tool_call_dict
376-
)
371+
call_result = await self.call_mcp_tool_from_session(session, tool_call_dict)
377372
except Exception as retry_exc:
378373
self.io.tool_warning(
379374
f"Executing {tool_name} on {server.name} failed after reconnect:\n"

cecli/coders/base_coder.py

Lines changed: 178 additions & 80 deletions
Large diffs are not rendered by default.

cecli/commands/add.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,24 @@ async def execute(cls, io, coder, args, **kwargs):
132132
f" {active_model.name} does not support images."
133133
)
134134
continue
135-
content = io.read_text(abs_file_path)
135+
try:
136+
content = io.read_text(abs_file_path)
137+
except (ValueError, UnicodeError, UnicodeDecodeError, OSError) as exc:
138+
# Binary or undecodable files (e.g. .git/objects/pack/*.rev)
139+
# raise ValueError("Could not determine text encoding ...")
140+
# from decoding.safe_open. Skip them gracefully instead of
141+
# crashing the session.
142+
msg = str(exc)
143+
if "Could not determine text encoding" in msg or isinstance(
144+
exc, (UnicodeError, UnicodeDecodeError)
145+
):
146+
io.tool_warning(
147+
f"Skipping {matched_file}: not decodable as text "
148+
"(binary or unknown encoding)"
149+
)
150+
else:
151+
io.tool_error(f"Skipping {matched_file}: {exc}")
152+
continue
136153
if content is None:
137154
io.tool_error(f"Unable to read {matched_file}")
138155
else:

cecli/commands/reasoning_effort.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class ReasoningEffortCommand(BaseCommand):
1414
@classmethod
1515
async def execute(cls, io, coder, args, **kwargs):
1616
"""Execute the reasoning-effort command with given parameters."""
17-
model = coder.main_model
17+
model = coder.get_active_model()
1818

1919
if not args.strip():
2020
# Display current value if no args are provided

cecli/commands/think_tokens.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class ThinkTokensCommand(BaseCommand):
1111
@classmethod
1212
async def execute(cls, io, coder, args, **kwargs):
1313
"""Execute the think-tokens command with given parameters."""
14-
model = coder.main_model
14+
model = coder.get_active_model()
1515

1616
if not args.strip():
1717
# Display current value if no args are provided

cecli/helpers/io_proxy.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ def __init__(self, target: T, coder: Any) -> None:
5151
super().__setattr__("_coder", weakref.ref(coder))
5252
# Per-coder task storage: {coder_uuid: {attr_name: asyncio.Task}}
5353
super().__setattr__("_per_coder", {coder_uuid: {}})
54+
# Last tool `type` emitted via tool_output — lives on the proxy,
55+
# never on the shared target (like coder_uuid)
56+
super().__setattr__("_last_type", None)
5457

5558
# Register a per-coder input queue (TUI mode only)
5659
# Allows the TUI to push input directly to this coder's queue,
@@ -76,6 +79,7 @@ def tool_output(self, *messages: Any, **kwargs: Any) -> Any:
7679
"""Forward tool_output with coder_uuid injected."""
7780
if "coder_uuid" not in kwargs:
7881
kwargs["coder_uuid"] = self._coder_uuid
82+
self._last_type = kwargs.get("type")
7983
return self._target.tool_output(*messages, **kwargs)
8084

8185
def tool_error(self, message: str = "", strip: bool = True, **kwargs: Any) -> Any:
@@ -265,7 +269,7 @@ def __getattr__(self, name: str) -> Any:
265269

266270
def __setattr__(self, name: str, value: Any) -> None:
267271
# Proxy-internal attributes — store on proxy instance only
268-
if name in ("_target", "_coder_uuid", "_coder", "_per_coder"):
272+
if name in ("_target", "_coder_uuid", "_coder", "_per_coder", "_last_type"):
269273
super().__setattr__(name, value)
270274
# Per-coder task attributes — isolate per-coder so coders don't
271275
# compete for the same promise on the shared InputOutput instance
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""Derive default per-model configuration from model metadata.
2+
3+
The model config package turns a flat litellm-style model metadata file into the
4+
``{api, llm, agent}`` override blocks that :mod:`cecli.models` consumes,
5+
mirroring the ``model-overrides`` section of ``.cecli.conf.yml``.
6+
"""
7+
8+
from .pipeline import ModelConfigPipeline, get_default_config
9+
10+
__all__ = ["ModelConfigPipeline", "get_default_config"]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Derive the ``agent`` override block for a model.
2+
3+
The agent block holds ModelSettings overrides. :mod:`cecli.models` applies each
4+
of these directly (``setattr``) the same way the ``agent`` section of a
5+
``model-overrides`` entry is applied.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from typing import Dict, Optional
11+
12+
from .identifiers import is_anthropic
13+
from .utils import supports_reasoning
14+
15+
16+
def derive_agent_config(provider: Optional[str], route: str, record: Optional[Dict]) -> Dict:
17+
"""Return the ``agent`` config block for a model.
18+
19+
Args:
20+
provider: Provider portion of the model name (may be ``None``).
21+
route: Model route (name after the provider prefix).
22+
record: The matched model metadata record, or ``None`` for unknown models.
23+
24+
Returns:
25+
A dict of ModelSettings overrides (caching, temperature handling).
26+
"""
27+
reasoning = supports_reasoning(record)
28+
record = record or {}
29+
agent: Dict = {
30+
"cache_control": is_anthropic(provider, route, record),
31+
# ``cache_read_input_token_cost`` in the metadata is the determinant for
32+
# whether a model supports prompt caching. Unknown models default to
33+
# assuming caching support.
34+
"caches_by_default": bool(record.get("cache_read_input_token_cost")) if record else True,
35+
}
36+
37+
if reasoning or record.get("supports_adaptive_thinking"):
38+
agent["use_temperature"] = False
39+
40+
return agent

cecli/helpers/model_config/api.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""Derive the ``api`` override block for a model.
2+
3+
The api block holds request-level parameters. :mod:`cecli.models` merges each
4+
of these keys into ``extra_params`` the same way the ``api`` section of a
5+
``model-overrides`` entry is applied.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from typing import Dict, Optional
11+
12+
from .identifiers import is_anthropic, is_claude_5_plus, is_gemini_2_5
13+
from .utils import supports_reasoning
14+
15+
_THINKING_BUDGET_TOKENS = 2048
16+
#: Default thinking budget for Gemini 2.5 models (Gemini 2.5 Pro's default).
17+
_GEMINI_THINKING_BUDGET_TOKENS = 8192
18+
19+
20+
def derive_api_config(provider: Optional[str], route: str, record: Optional[Dict]) -> Dict:
21+
"""Return the ``api`` config block for a model.
22+
23+
Args:
24+
provider: Provider portion of the model name (may be ``None``).
25+
route: Model route (name after the provider prefix).
26+
record: The matched model metadata record, or ``None`` for unknown models.
27+
28+
Returns:
29+
A dict of request-level params (reasoning format, thinking, tool calls).
30+
"""
31+
reasoning = supports_reasoning(record)
32+
record = record or {}
33+
gemini_2_5 = is_gemini_2_5(provider, route, record)
34+
api: Dict = {}
35+
36+
if reasoning and not gemini_2_5:
37+
effort = _default_reasoning_effort(record)
38+
39+
if effort:
40+
api["reasoning_effort"] = effort
41+
42+
if is_anthropic(provider, route, record) and not is_claude_5_plus(provider, route, record):
43+
# Claude 5+ uses adaptive thinking via ``reasoning_effort`` instead of
44+
# the ``thinking.type.enabled`` budget block.
45+
api["thinking"] = {"type": "enabled", "budget_tokens": _THINKING_BUDGET_TOKENS}
46+
elif gemini_2_5:
47+
# Gemini 2.5 configures thinking via a token budget; litellm maps the
48+
# generic ``thinking`` param onto ``thinkingBudget`` + ``includeThoughts``.
49+
api["thinking"] = {"type": "enabled", "budget_tokens": _GEMINI_THINKING_BUDGET_TOKENS}
50+
51+
if record.get("supports_parallel_function_calling", True):
52+
api["parallel_tool_calls"] = True
53+
54+
return api
55+
56+
57+
def _default_reasoning_effort(record):
58+
"""Default reasoning effort for a reasoning-capable model.
59+
60+
Always ``medium``; the metadata effort flags are intentionally not used.
61+
"""
62+
return "medium"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Provider-specific helper overrides for the model config pipeline."""
2+
3+
from .reasoning import anthropic_reasoning, format_reasoning, gemini_reasoning, noop
4+
from .thinking import (
5+
anthropic_5_thinking,
6+
anthropic_thinking,
7+
format_thinking,
8+
gemini_thinking,
9+
)
10+
11+
__all__ = [
12+
"format_reasoning",
13+
"anthropic_reasoning",
14+
"gemini_reasoning",
15+
"noop",
16+
"format_thinking",
17+
"anthropic_thinking",
18+
"anthropic_5_thinking",
19+
"gemini_thinking",
20+
]

0 commit comments

Comments
 (0)