Skip to content

Commit c99f531

Browse files
committed
add aichat cli support
1 parent c629454 commit c99f531

3 files changed

Lines changed: 170 additions & 1 deletion

File tree

code_assistant_manager/tools.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,3 +405,34 @@ tools:
405405
filesystem:
406406
touched:
407407
- "~/.kimi/config.toml (Kimi CLI configuration file)"
408+
409+
aichat:
410+
enabled: true
411+
install_cmd: cargo install aichat
412+
cli_command: aichat
413+
description: "AIChat - AI-powered chat CLI with multiple LLM support"
414+
env:
415+
exported:
416+
AICHAT_BASE_URL: "Populated from selected endpoint.endpoint."
417+
AICHAT_API_KEY: "Resolved from endpoint configuration and environment (masked in output)."
418+
AICHAT_MODEL: "Model ID selected via code-assistant-manager prompt."
419+
NODE_TLS_REJECT_UNAUTHORIZED: "0"
420+
configuration:
421+
required:
422+
endpoint: "Base URL for OpenAI-compatible API."
423+
list_models_cmd: "Shell command returning available models for the endpoint."
424+
optional:
425+
api_key_env: "Environment variable name containing the API key."
426+
supported_client: "Comma-separated list used to filter endpoints; must include aichat."
427+
keep_proxy_config: "When true, preserve proxy variables during model discovery."
428+
use_proxy: "When true, apply proxies from common config to runtime requests."
429+
description: "Display label shown during endpoint selection."
430+
model_listing_env:
431+
endpoint: "Provided to list_models_cmd as env var endpoint."
432+
api_key: "Provided to list_models_cmd as env var api_key (resolved API key)."
433+
proxies: "Proxy variables removed unless keep_proxy_config is true."
434+
cli_parameters:
435+
injected: []
436+
filesystem:
437+
touched:
438+
- "~/.config/aichat/config.yaml (AIChat configuration file)"

code_assistant_manager/tools/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def _ensure_tools_loaded() -> None:
4646

4747
# Import tool modules so their subclasses are registered
4848
from . import ( # noqa: F401
49+
aichat,
4950
ampcode,
5051
blackbox,
5152
claude,
@@ -123,6 +124,7 @@ def get_registered_tools() -> Dict[str, Type[CLITool]]:
123124
def __getattr__(name: str):
124125
"""Lazy-load tool classes when explicitly imported."""
125126
tool_map = {
127+
"AIChatTool": "aichat",
126128
"AmpcodeTool": "ampcode",
127129
"BlackboxTool": "blackbox",
128130
"ClaudeTool": "claude",
@@ -136,6 +138,7 @@ def __getattr__(name: str):
136138
"GeminiTool": "gemini",
137139
"GooseTool": "goose",
138140
"IfLowTool": "iflow",
141+
"KimiTool": "kimi",
139142
"NeovateTool": "neovate",
140143
"OpenCodeTool": "opencode",
141144
"QoderCLITool": "qodercli",
@@ -145,7 +148,9 @@ def __getattr__(name: str):
145148

146149
if name in tool_map:
147150
module_name = tool_map[name]
148-
module = __import__(f"code_assistant_manager.tools.{module_name}", fromlist=[name])
151+
module = __import__(
152+
f"code_assistant_manager.tools.{module_name}", fromlist=[name]
153+
)
149154
return getattr(module, name)
150155

151156
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import logging
2+
import os
3+
from pathlib import Path
4+
from typing import List, Optional
5+
6+
from .base import CLITool
7+
8+
logger = logging.getLogger(__name__)
9+
10+
11+
class AIChatTool(CLITool):
12+
"""AIChat CLI wrapper."""
13+
14+
command_name = "aichat"
15+
tool_key = "aichat"
16+
install_description = "AIChat CLI"
17+
18+
def _write_config(
19+
self,
20+
*,
21+
endpoint_config: dict,
22+
model: str,
23+
) -> Path:
24+
"""Write aichat configuration to ~/.config/aichat/config.yaml."""
25+
config_path = Path.home() / ".config" / "aichat" / "config.yaml"
26+
config_path.parent.mkdir(parents=True, exist_ok=True)
27+
28+
# Get the base URL and API key
29+
base_url = endpoint_config["endpoint"]
30+
api_key = endpoint_config["actual_api_key"]
31+
32+
# Create YAML content based on the user's specified configuration
33+
# AIChat model format: 'client_name:model_name'
34+
client_name = "custom"
35+
formatted_model = f"{client_name}:{model}"
36+
37+
yaml_content = f"""model: {formatted_model}
38+
use_tools: fs,web_search,code_interpreter,python,terminal,git
39+
temperature: 1
40+
agent_prelude: default
41+
42+
instructions:
43+
default: |
44+
You are a helpful AI assistant. You can answer questions, provide explanations, and assist with various tasks.
45+
If you don't know the answer, you can search the web or use tools to find the information.
46+
Always be polite and concise in your responses.
47+
48+
clients:
49+
- type: openai-compatible
50+
name: {client_name}
51+
api_base: {base_url}
52+
api_key: {api_key}
53+
models:
54+
- name: {model}
55+
"""
56+
57+
# Write to config file
58+
with open(config_path, "w", encoding="utf-8") as f:
59+
f.write(yaml_content)
60+
61+
return config_path
62+
63+
def run(self, args: Optional[List[str]] = None) -> int:
64+
"""
65+
Run the AIChat CLI tool with the specified arguments.
66+
67+
Args:
68+
args: List of arguments to pass to the AIChat CLI
69+
70+
Returns:
71+
Exit code of the AIChat CLI process
72+
"""
73+
args = args or []
74+
75+
# Load environment variables first
76+
self._load_environment()
77+
78+
# Check if the tool is installed and prompt for upgrade if needed
79+
if not self._ensure_tool_installed(
80+
self.command_name, self.tool_key, self.install_description
81+
):
82+
return 1
83+
84+
# Set up endpoint and model using the endpoint manager
85+
success, result = self._setup_endpoint_and_models(
86+
"aichat", select_multiple=False
87+
)
88+
if not success or result is None:
89+
return 1
90+
91+
# Extract endpoint configuration and selected model
92+
endpoint_config, _, selected_models = result
93+
94+
# Handle the case where selected_models might be a string or tuple
95+
if isinstance(selected_models, str):
96+
model = selected_models
97+
elif isinstance(selected_models, tuple) and len(selected_models) > 0:
98+
model = selected_models[0] # Take the first model if multiple
99+
else:
100+
return self._handle_error("No model selected")
101+
102+
if not endpoint_config or not model:
103+
return 1
104+
105+
# Write configuration to ~/.config/aichat/config.yaml
106+
config_path = None
107+
try:
108+
config_path = self._write_config(
109+
endpoint_config=endpoint_config,
110+
model=model,
111+
)
112+
print(f"[code-assistant-manager] Updated aichat config: {config_path}")
113+
except Exception as e:
114+
error_path = config_path or "~/.config/aichat/config.yaml"
115+
return self._handle_error(f"Failed to write {error_path}", e)
116+
117+
# Set up environment variables for AIChat
118+
env = os.environ.copy()
119+
# Set TLS environment for Node.js (in case aichat uses it)
120+
self._set_node_tls_env(env)
121+
122+
# Execute the AIChat CLI - it will read from the config file
123+
command = ["aichat"] + args
124+
125+
# Display the complete command
126+
args_str = " ".join(args) if args else ""
127+
command_str = f"aichat {args_str}".strip()
128+
print("")
129+
print("Complete command to execute:")
130+
print(command_str)
131+
print("")
132+
133+
return self._run_tool_with_env(command, env, "aichat", interactive=True)

0 commit comments

Comments
 (0)