Skip to content

Commit c8f1f83

Browse files
authored
Merge pull request #368 from dwash96/v0.95.7
v0.95.7
2 parents 020b440 + d6b9c29 commit c8f1f83

21 files changed

Lines changed: 907 additions & 318 deletions

.github/workflows/ubuntu-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,5 @@ jobs:
5959
- name: Run tests
6060
run: |
6161
pytest
62+
env:
63+
CECLI_TUI: "false"

.github/workflows/windows-tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ jobs:
4747
- name: Run tests
4848
run: |
4949
pytest
50+
env:
51+
CECLI_TUI: "false"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ LLMs are a part of our lives from here on out so join us in learning about and c
2222
* [Skills](https://github.com/dwash96/cecli/blob/main/aider/website/docs/config/skills.md)
2323
* [Session Management](https://github.com/dwash96/cecli/blob/main/aider/website/docs/sessions.md)
2424
* [Custom Commands](https://github.com/dwash96/cecli/blob/main/cecli/website/docs/config/custom-commands.md)
25+
* [Custom System Prompts](https://github.com/dwash96/cecli/blob/main/cecli/website/docs/config/custom-system-prompts.md)
2526
* [Custom Tools](https://github.com/dwash96/cecli/blob/main/cecli/website/docs/config/agent-mode.md#creating-custom-tools)
2627
* [Advanced Model Configuration](https://github.com/dwash96/cecli/blob/main/aider/website/docs/config/model-aliases.md#advanced-model-settings)
2728
* [Aider Original Documentation (still mostly applies)](https://aider.chat/)

cecli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from packaging import version
22

3-
__version__ = "0.93.0.dev"
3+
__version__ = "0.95.7.dev"
44
safe_version = __version__
55

66
try:

cecli/args.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -242,13 +242,34 @@ def get_parser(default_config_files, git_root):
242242
),
243243
)
244244

245+
#######
246+
group = parser.add_argument_group("Customization Settings")
247+
group.add_argument(
248+
"--custom",
249+
metavar="CUSTOM_JSON",
250+
help=(
251+
"Specify cecli customizations configurations (for prompts, commands, etc.) as a JSON"
252+
" string"
253+
),
254+
default=None,
255+
)
245256
########
246257
group = parser.add_argument_group("TUI Settings")
258+
259+
env_val = os.environ.get("CECLI_TUI")
260+
261+
if env_val is not None and env_val.lower() == "false":
262+
tui_default = False
263+
linear_output_default = True
264+
else:
265+
tui_default = None
266+
linear_output_default = None
267+
247268
group.add_argument(
248269
"--tui",
249270
action=argparse.BooleanOptionalAction,
250-
default=None,
251-
help="Launch Textual TUI interface (experimental)",
271+
default=tui_default,
272+
help="Launch Textual TUI interface",
252273
)
253274
group.add_argument(
254275
"--tui-config",
@@ -803,8 +824,8 @@ def get_parser(default_config_files, git_root):
803824
group.add_argument(
804825
"--linear-output",
805826
action=argparse.BooleanOptionalAction,
806-
help="Run input and output sequentially instead of us simultaneous streams (default: True)",
807-
default=True,
827+
help="Run input and output sequentially instead of us simultaneous streams (default: None)",
828+
default=linear_output_default,
808829
)
809830
group.add_argument(
810831
"--debug",
@@ -975,12 +996,6 @@ def get_parser(default_config_files, git_root):
975996
" specified, a default command for your OS may be used."
976997
),
977998
)
978-
group.add_argument(
979-
"--command-paths",
980-
help="JSON array of paths to custom commands files",
981-
action="append",
982-
default=None,
983-
)
984999
group.add_argument(
9851000
"--command-prefix",
9861001
default=None,

cecli/coders/agent_coder.py

Lines changed: 50 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from cecli import urls, utils
1818
from cecli.change_tracker import ChangeTracker
19+
from cecli.helpers import nested
1920
from cecli.helpers.similarity import (
2021
cosine_similarity,
2122
create_bigram_vector,
@@ -105,50 +106,61 @@ def _get_agent_config(self):
105106
self.io.tool_warning(f"Failed to parse agent-config JSON: {e}")
106107
return {}
107108

108-
if "large_file_token_threshold" not in config:
109-
config["large_file_token_threshold"] = 25000
110-
111-
if "tools_paths" not in config:
112-
config["tools_paths"] = []
113-
if "tools_includelist" not in config:
114-
config["tools_includelist"] = []
115-
if "tools_excludelist" not in config:
116-
config["tools_excludelist"] = []
109+
config["large_file_token_threshold"] = nested.getter(
110+
config, "large_file_token_threshold", 25000
111+
)
112+
config["skip_cli_confirmations"] = nested.getter(
113+
config, "skip_cli_confirmations", nested.getter(config, "yolo", [])
114+
)
117115

118-
if "include_context_blocks" in config:
119-
self.allowed_context_blocks = set(config["include_context_blocks"])
120-
else:
121-
self.allowed_context_blocks = {
122-
"context_summary",
123-
"directory_structure",
124-
"environment_info",
125-
"git_status",
126-
"symbol_outline",
127-
"todo_list",
128-
"skills",
129-
}
116+
config["tools_paths"] = nested.getter(config, "tools_paths", [])
117+
config["tools_includelist"] = nested.getter(
118+
config, ["tools_includelist", "tools_whitelist"], []
119+
)
120+
config["tools_excludelist"] = nested.getter(
121+
config, ["tools_excludelist", "tools_blacklist"], []
122+
)
130123

131-
if "exclude_context_blocks" in config:
132-
for context_block in config["exclude_context_blocks"]:
133-
try:
134-
self.allowed_context_blocks.remove(context_block)
135-
except KeyError:
136-
pass
124+
config["include_context_blocks"] = set(
125+
nested.getter(
126+
config,
127+
"include_context_blocks",
128+
{
129+
"context_summary",
130+
"directory_structure",
131+
"environment_info",
132+
"git_status",
133+
"symbol_outline",
134+
"todo_list",
135+
"skills",
136+
},
137+
)
138+
)
139+
config["exclude_context_blocks"] = set(nested.getter(config, "exclude_context_blocks", []))
137140

138141
self.large_file_token_threshold = config["large_file_token_threshold"]
139-
self.skip_cli_confirmations = config.get(
140-
"skip_cli_confirmations", config.get("yolo", False)
141-
)
142+
self.skip_cli_confirmations = config["skip_cli_confirmations"]
143+
144+
self.allowed_context_blocks = config["include_context_blocks"]
145+
146+
for context_block in config["exclude_context_blocks"]:
147+
try:
148+
self.allowed_context_blocks.remove(context_block)
149+
except KeyError:
150+
pass
142151

143152
if "skills" in self.allowed_context_blocks:
144-
if "skills_paths" not in config:
145-
config["skills_paths"] = []
146-
if "skills_includelist" not in config:
147-
config["skills_includelist"] = []
148-
if "skills_excludelist" not in config:
149-
config["skills_excludelist"] = []
150-
151-
if "skills" not in self.allowed_context_blocks or not config.get("skills_paths", []):
153+
config["skills_paths"] = nested.getter(config, "skills_paths", [])
154+
config["skills_includelist"] = nested.getter(
155+
config, ["skills_includelist", "skills_whitelist"], []
156+
)
157+
config["skills_excludelist"] = nested.getter(
158+
config, ["skills_excludelist", "skills_blacklist"], []
159+
)
160+
161+
if "skills" not in self.allowed_context_blocks or not nested.getter(
162+
config, "skills_paths", []
163+
):
152164
config["tools_excludelist"].append("loadskill")
153165
config["tools_excludelist"].append("removeskill")
154166

cecli/coders/base_coder.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from cecli import __version__, models, urls, utils
3939
from cecli.commands import Commands, SwitchCoderSignal
4040
from cecli.exceptions import LiteLLMExceptions
41-
from cecli.helpers import coroutines
41+
from cecli.helpers import coroutines, nested
4242
from cecli.helpers.profiler import TokenProfiler
4343
from cecli.history import ChatSummary
4444
from cecli.io import ConfirmGroup, InputOutput
@@ -61,7 +61,7 @@
6161
from cecli.utils import format_tokens, is_image_file
6262

6363
from ..dump import dump # noqa: F401
64-
from ..prompts.utils.prompt_registry import registry
64+
from ..prompts.utils.registry import PromptObject, PromptRegistry
6565
from .chat_chunks import ChatChunks
6666

6767

@@ -564,6 +564,29 @@ def __init__(
564564
except Exception as e:
565565
self.io.tool_warning(f"Could not remove todo list file {todo_file_path}: {e}")
566566

567+
customizations = dict()
568+
try:
569+
if self.args:
570+
customizations = nested.getter(self.args, "custom", "{}")
571+
customizations = json.loads(customizations)
572+
except (json.JSONDecodeError, TypeError):
573+
customizations = dict()
574+
pass
575+
576+
self.custom = customizations
577+
578+
if nested.getter(self.custom, "prompt_map.all", None):
579+
prompts = PromptRegistry.get_prompt(nested.getter(self.custom, "prompt_map.all"))
580+
prompt_obj = PromptObject(prompts)
581+
Coder._prompt_cache[self.prompt_format] = prompt_obj
582+
583+
if nested.getter(self.custom, f"prompt_map.{self.prompt_format}", None):
584+
prompts = PromptRegistry.get_prompt(
585+
nested.getter(self.custom, f"prompt_map.{self.prompt_format}")
586+
)
587+
prompt_obj = PromptObject(prompts)
588+
Coder._prompt_cache[self.prompt_format] = prompt_obj
589+
567590
# validate the functions jsonschema
568591
if self.functions:
569592
from jsonschema import Draft7Validator
@@ -600,14 +623,7 @@ def gpt_prompts(self):
600623
return Coder._prompt_cache[prompt_name]
601624

602625
# Get prompts from registry
603-
prompts = registry.get_prompt(prompt_name)
604-
605-
# Create a simple object that allows attribute access
606-
class PromptObject:
607-
def __init__(self, prompts_dict):
608-
for key, value in prompts_dict.items():
609-
setattr(self, key, value)
610-
626+
prompts = PromptRegistry.get_prompt(prompt_name)
611627
# Cache the prompt object
612628
prompt_obj = PromptObject(prompts)
613629
Coder._prompt_cache[prompt_name] = prompt_obj

cecli/commands/core.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pathlib import Path
66

77
from cecli.commands.utils.registry import CommandRegistry
8-
from cecli.helpers import plugin_manager
8+
from cecli.helpers import nested, plugin_manager
99
from cecli.helpers.file_searcher import handle_core_files
1010
from cecli.repo import ANY_GIT_ERROR
1111

@@ -80,13 +80,16 @@ def __init__(
8080
self.editor = editor
8181
self.original_read_only_fnames = set(original_read_only_fnames or [])
8282

83+
customizations = dict()
8384
try:
84-
self.custom_commands = json.loads(getattr(self.args, "command_paths", "[]"))
85-
except (json.JSONDecodeError, TypeError) as e:
86-
self.io.tool_warning(f"Failed to parse command paths JSON: {e}")
87-
self.custom_commands = []
88-
89-
# Load custom commands from plugin paths
85+
if self.args:
86+
customizations = nested.getter(self.args, "custom", "{}")
87+
customizations = json.loads(customizations)
88+
except (json.JSONDecodeError, TypeError):
89+
customizations = dict()
90+
pass
91+
92+
self.custom_commands = nested.getter(customizations, "command-paths", [])
9093
self._load_custom_commands(self.custom_commands)
9194

9295
self.cmd_running_event = asyncio.Event()

cecli/helpers/model_providers.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,40 @@ def _get_cache_file(self, provider: str) -> Path:
439439
fname = f"{provider}_models.json"
440440
return self.cache_dir / fname
441441

442+
def _normalize_models_payload(self, provider: str, payload: Dict) -> Dict:
443+
"""Normalize provider payloads into an OpenAI-style `{data: [{id: ...}]}`."""
444+
if not isinstance(payload, dict):
445+
return {}
446+
if "data" in payload and isinstance(payload.get("data"), list):
447+
return payload
448+
# Fireworks returns `{models: [...], nextPageToken: ..., totalSize: ...}`
449+
models = payload.get("models")
450+
if isinstance(models, list):
451+
normalized = []
452+
for item in models:
453+
if not isinstance(item, dict):
454+
continue
455+
model_id = item.get("name") or item.get("id")
456+
if not model_id:
457+
continue
458+
record = {"id": model_id}
459+
for key in (
460+
"max_input_tokens",
461+
"max_output_tokens",
462+
"max_tokens",
463+
"context_length",
464+
"context_window",
465+
"mode",
466+
"pricing",
467+
"input_cost_per_token",
468+
"output_cost_per_token",
469+
):
470+
if key in item and item[key] is not None:
471+
record[key] = item[key]
472+
normalized.append(record)
473+
return {"data": normalized}
474+
return {}
475+
442476
def _load_cache(self, provider: str) -> None:
443477
if self._cache_loaded.get(provider):
444478
return
@@ -460,9 +494,10 @@ def _update_cache(self, provider: str) -> None:
460494
payload = self._fetch_provider_models(provider)
461495
cache_file = self._get_cache_file(provider)
462496
if payload:
463-
self._provider_cache[provider] = payload
497+
normalized = self._normalize_models_payload(provider, payload)
498+
self._provider_cache[provider] = normalized
464499
try:
465-
cache_file.write_text(json.dumps(payload, indent=2))
500+
cache_file.write_text(json.dumps(normalized, indent=2))
466501
except OSError:
467502
pass
468503
return
@@ -479,6 +514,13 @@ def _fetch_provider_models(self, provider: str) -> Optional[Dict]:
479514
models_url = api_base.rstrip("/") + "/models"
480515
if not models_url:
481516
return None
517+
# Substitute {account_id} placeholder if present
518+
if "{account_id}" in models_url:
519+
account_id = self._get_account_id(provider)
520+
if not account_id:
521+
print(f"Failed to fetch {provider} model list: account_id_env not set")
522+
return None
523+
models_url = models_url.replace("{account_id}", account_id)
482524
headers = {}
483525
default_headers = config.get("default_headers") or {}
484526
headers.update(default_headers)
@@ -509,6 +551,13 @@ def _get_api_key(self, provider: str) -> Optional[str]:
509551
return value
510552
return None
511553

554+
def _get_account_id(self, provider: str) -> Optional[str]:
555+
config = self.provider_configs[provider]
556+
account_id_env = config.get("account_id_env")
557+
if account_id_env:
558+
return os.environ.get(account_id_env)
559+
return None
560+
512561

513562
def ensure_litellm_providers_registered() -> None:
514563
"""One-time registration guard for LiteLLM provider metadata."""

0 commit comments

Comments
 (0)