cecli-dev 1.2.2rc2__py3-none-any.whl → 1.2.3rc2__py3-none-any.whl
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.
- cecli/_version.py +3 -3
- cecli/helpers/conversation/manager.py +0 -12
- cecli/helpers/llms/config.py +8 -0
- cecli/helpers/llms/domains/messages.py +75 -1
- cecli/models.py +0 -25
- cecli/resources/model-metadata.ext.json +38884 -0
- cecli/resources/model-metadata.json +1874 -246
- cecli/resources/model-settings.yml +235 -1
- cecli/resources/providers.json +14 -0
- cecli/website/docmd.config.json +4 -0
- cecli/website/docs/llms/opencode-go.md +35 -0
- cecli_dev-1.2.3rc2.dist-info/METADATA +552 -0
- {cecli_dev-1.2.2rc2.dist-info → cecli_dev-1.2.3rc2.dist-info}/RECORD +17 -15
- cecli_dev-1.2.2rc2.dist-info/METADATA +0 -1071
- {cecli_dev-1.2.2rc2.dist-info → cecli_dev-1.2.3rc2.dist-info}/WHEEL +0 -0
- {cecli_dev-1.2.2rc2.dist-info → cecli_dev-1.2.3rc2.dist-info}/entry_points.txt +0 -0
- {cecli_dev-1.2.2rc2.dist-info → cecli_dev-1.2.3rc2.dist-info}/licenses/LICENSE.txt +0 -0
- {cecli_dev-1.2.2rc2.dist-info → cecli_dev-1.2.3rc2.dist-info}/top_level.txt +0 -0
cecli/_version.py
CHANGED
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 2,
|
|
21
|
+
__version__ = version = '1.2.3rc2'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 2, 3, 'rc2')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'ga5ff62c22'
|
|
@@ -358,18 +358,6 @@ class ConversationManager:
|
|
|
358
358
|
with safe_open(".cecli/logs/conversation.log", "w") as f:
|
|
359
359
|
json.dump(messages_dict, f, indent=4, default=lambda o: "<not serializable>")
|
|
360
360
|
|
|
361
|
-
# Add cache control headers when getting all messages (for LLM consumption)
|
|
362
|
-
# Only add cache control if the coder has add_cache_headers = True
|
|
363
|
-
if tag is None:
|
|
364
|
-
if (
|
|
365
|
-
coder
|
|
366
|
-
and hasattr(coder, "add_cache_headers")
|
|
367
|
-
and coder.add_cache_headers
|
|
368
|
-
and hasattr(coder, "main_model")
|
|
369
|
-
and not coder.main_model.caches_by_default
|
|
370
|
-
and not getattr(coder.main_model, "uses_messages_api", False)
|
|
371
|
-
):
|
|
372
|
-
messages_dict = self._add_cache_control(messages_dict)
|
|
373
361
|
return messages_dict
|
|
374
362
|
|
|
375
363
|
def clear_tag(self, tag: str, ratio: float = 0) -> None:
|
cecli/helpers/llms/config.py
CHANGED
|
@@ -28,6 +28,14 @@ PROVIDER_DEFAULTS: Dict[str, Dict[str, Any]] = {
|
|
|
28
28
|
"github_copilot": {"api_base": "https://api.githubcopilot.com", "api_key_env": None},
|
|
29
29
|
"meta": {"api_base": "https://api.meta.ai/v1", "api_key_env": "META_API_KEY"},
|
|
30
30
|
"chutes": {"api_base": "https://llm.chutes.ai/v1/", "api_key_env": "CHUTES_API_KEY"},
|
|
31
|
+
"opencode-go": {
|
|
32
|
+
"api_base": "https://opencode.ai/zen/go/v1",
|
|
33
|
+
"api_key_env": "OPENCODE_GO_API_KEY",
|
|
34
|
+
},
|
|
35
|
+
"opencode-zen": {
|
|
36
|
+
"api_base": "https://opencode.ai/zen/zen/v1",
|
|
37
|
+
"api_key_env": "OPENCODE_ZEN_API_KEY",
|
|
38
|
+
},
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
|
|
@@ -45,9 +45,12 @@ def anthropic_payload(
|
|
|
45
45
|
kwargs: Dict[str, Any],
|
|
46
46
|
) -> Dict[str, Any]:
|
|
47
47
|
system = system_prompt(messages)
|
|
48
|
+
wire_messages = _coalesce_anthropic_messages(
|
|
49
|
+
[anthropic_message(m) for m in messages if m.get("role") != "system"]
|
|
50
|
+
)
|
|
48
51
|
payload: Dict[str, Any] = {
|
|
49
52
|
"model": resolved["route"],
|
|
50
|
-
"messages":
|
|
53
|
+
"messages": wire_messages,
|
|
51
54
|
"max_tokens": (
|
|
52
55
|
kwargs.get("max_tokens") or resolved.get("llm_block", {}).get("max_tokens") or 4096
|
|
53
56
|
),
|
|
@@ -274,6 +277,7 @@ async def anthropic_stream(
|
|
|
274
277
|
"id": block.get("id", ""),
|
|
275
278
|
"name": block.get("name", ""),
|
|
276
279
|
"input": block.get("input") or {},
|
|
280
|
+
"_input_raw": "",
|
|
277
281
|
}
|
|
278
282
|
|
|
279
283
|
elif evt == "content_block_delta":
|
|
@@ -291,7 +295,21 @@ async def anthropic_stream(
|
|
|
291
295
|
elif dtype == "signature_delta":
|
|
292
296
|
entry["signature"] = delta.get("signature")
|
|
293
297
|
|
|
298
|
+
elif dtype == "input_json_delta":
|
|
299
|
+
entry["_input_raw"] += delta.get("partial_json") or ""
|
|
300
|
+
|
|
294
301
|
elif evt == "content_block_stop":
|
|
302
|
+
entry = blocks.get(current) if current is not None else None
|
|
303
|
+
|
|
304
|
+
if entry is not None:
|
|
305
|
+
raw = entry.pop("_input_raw", None)
|
|
306
|
+
|
|
307
|
+
if raw is not None:
|
|
308
|
+
try:
|
|
309
|
+
entry["input"] = json.loads(raw)
|
|
310
|
+
except json.JSONDecodeError:
|
|
311
|
+
pass
|
|
312
|
+
|
|
295
313
|
current = None
|
|
296
314
|
|
|
297
315
|
chunk = parse_anthropic_chunk(json_obj)
|
|
@@ -743,6 +761,62 @@ def _is_tool_turn(msg: Dict[str, Any]) -> bool:
|
|
|
743
761
|
)
|
|
744
762
|
|
|
745
763
|
|
|
764
|
+
def _coalesce_anthropic_messages(wire_messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
765
|
+
"""Merge consecutive user turns into a single user message.
|
|
766
|
+
|
|
767
|
+
The conversation manager emits one user message per tool result and can
|
|
768
|
+
inject file-context text as its own user message directly after the
|
|
769
|
+
results, so the raw wire list can contain several ``user`` messages in a
|
|
770
|
+
row. The Messages API requires alternating roles and, per the tool-use
|
|
771
|
+
spec, ``tool_result`` blocks must come first in the user message that
|
|
772
|
+
follows an assistant ``tool_use`` turn. Text runs (across messages) are
|
|
773
|
+
concatenated with ``"\n---\n"`` separators.
|
|
774
|
+
"""
|
|
775
|
+
result: List[Dict[str, Any]] = []
|
|
776
|
+
pending: List[Dict[str, Any]] = []
|
|
777
|
+
|
|
778
|
+
def flush() -> None:
|
|
779
|
+
if not pending:
|
|
780
|
+
return
|
|
781
|
+
|
|
782
|
+
tool_results = [b for b in pending if b.get("type") == "tool_result"]
|
|
783
|
+
others = [b for b in pending if b.get("type") != "tool_result"]
|
|
784
|
+
content: List[Dict[str, Any]] = list(tool_results)
|
|
785
|
+
text_parts: List[str] = []
|
|
786
|
+
|
|
787
|
+
for block in others:
|
|
788
|
+
if block.get("type") == "text":
|
|
789
|
+
text_parts.append(block.get("text") or "")
|
|
790
|
+
|
|
791
|
+
continue
|
|
792
|
+
|
|
793
|
+
if text_parts:
|
|
794
|
+
content.append({"type": "text", "text": "\n---\n".join(text_parts)})
|
|
795
|
+
text_parts = []
|
|
796
|
+
|
|
797
|
+
content.append(block)
|
|
798
|
+
|
|
799
|
+
if text_parts:
|
|
800
|
+
content.append({"type": "text", "text": "\n---\n".join(text_parts)})
|
|
801
|
+
|
|
802
|
+
result.append({"role": "user", "content": content})
|
|
803
|
+
pending.clear()
|
|
804
|
+
|
|
805
|
+
for msg in wire_messages:
|
|
806
|
+
if msg.get("role") != "user":
|
|
807
|
+
flush()
|
|
808
|
+
result.append(msg)
|
|
809
|
+
continue
|
|
810
|
+
|
|
811
|
+
content = msg.get("content")
|
|
812
|
+
blocks = content if isinstance(content, list) else [{"type": "text", "text": content}]
|
|
813
|
+
pending.extend(blocks)
|
|
814
|
+
|
|
815
|
+
flush()
|
|
816
|
+
|
|
817
|
+
return result
|
|
818
|
+
|
|
819
|
+
|
|
746
820
|
__all__ = [
|
|
747
821
|
"anthropic_payload",
|
|
748
822
|
"anthropic_message",
|
cecli/models.py
CHANGED
|
@@ -142,7 +142,6 @@ with importlib.resources.open_text("cecli.resources", "model-settings.yml") as f
|
|
|
142
142
|
|
|
143
143
|
|
|
144
144
|
class ModelInfoManager:
|
|
145
|
-
MODEL_INFO_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
|
|
146
145
|
CACHE_TTL = 60 * 60 * 24
|
|
147
146
|
|
|
148
147
|
def __init__(self):
|
|
@@ -177,28 +176,6 @@ class ModelInfoManager:
|
|
|
177
176
|
pass
|
|
178
177
|
self._cache_loaded = True
|
|
179
178
|
|
|
180
|
-
def _update_cache(self):
|
|
181
|
-
try:
|
|
182
|
-
import requests
|
|
183
|
-
|
|
184
|
-
response = requests.get(self.MODEL_INFO_URL, timeout=5, verify=self.verify_ssl)
|
|
185
|
-
if response.status_code == 200:
|
|
186
|
-
# Use json.dumps(response.json()) instead of response.text for
|
|
187
|
-
# compatibility with mocked responses in tests
|
|
188
|
-
parsed = response.json()
|
|
189
|
-
self._raw_content = json.dumps(parsed)
|
|
190
|
-
try:
|
|
191
|
-
parsed = response.json()
|
|
192
|
-
self.cache_file.write_text(json.dumps(parsed, indent=4))
|
|
193
|
-
except OSError:
|
|
194
|
-
pass
|
|
195
|
-
except Exception as ex:
|
|
196
|
-
print(str(ex))
|
|
197
|
-
try:
|
|
198
|
-
self.cache_file.write_text("{}")
|
|
199
|
-
except OSError:
|
|
200
|
-
pass
|
|
201
|
-
|
|
202
179
|
def _get_entry_from_raw(self, key):
|
|
203
180
|
"""Parse a single model entry from raw JSON string without loading the entire dict."""
|
|
204
181
|
return get_entry_from_raw(self._raw_content, key)
|
|
@@ -208,8 +185,6 @@ class ModelInfoManager:
|
|
|
208
185
|
if data:
|
|
209
186
|
return data
|
|
210
187
|
self._load_cache()
|
|
211
|
-
if not self._raw_content:
|
|
212
|
-
self._update_cache()
|
|
213
188
|
if not self._raw_content:
|
|
214
189
|
return dict()
|
|
215
190
|
info = self._get_entry_from_raw(model)
|