ripperdoc 0.2.6__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.
- ripperdoc/__init__.py +3 -0
- ripperdoc/__main__.py +20 -0
- ripperdoc/cli/__init__.py +1 -0
- ripperdoc/cli/cli.py +405 -0
- ripperdoc/cli/commands/__init__.py +82 -0
- ripperdoc/cli/commands/agents_cmd.py +263 -0
- ripperdoc/cli/commands/base.py +19 -0
- ripperdoc/cli/commands/clear_cmd.py +18 -0
- ripperdoc/cli/commands/compact_cmd.py +23 -0
- ripperdoc/cli/commands/config_cmd.py +31 -0
- ripperdoc/cli/commands/context_cmd.py +144 -0
- ripperdoc/cli/commands/cost_cmd.py +82 -0
- ripperdoc/cli/commands/doctor_cmd.py +221 -0
- ripperdoc/cli/commands/exit_cmd.py +19 -0
- ripperdoc/cli/commands/help_cmd.py +20 -0
- ripperdoc/cli/commands/mcp_cmd.py +70 -0
- ripperdoc/cli/commands/memory_cmd.py +202 -0
- ripperdoc/cli/commands/models_cmd.py +413 -0
- ripperdoc/cli/commands/permissions_cmd.py +302 -0
- ripperdoc/cli/commands/resume_cmd.py +98 -0
- ripperdoc/cli/commands/status_cmd.py +167 -0
- ripperdoc/cli/commands/tasks_cmd.py +278 -0
- ripperdoc/cli/commands/todos_cmd.py +69 -0
- ripperdoc/cli/commands/tools_cmd.py +19 -0
- ripperdoc/cli/ui/__init__.py +1 -0
- ripperdoc/cli/ui/context_display.py +298 -0
- ripperdoc/cli/ui/helpers.py +22 -0
- ripperdoc/cli/ui/rich_ui.py +1557 -0
- ripperdoc/cli/ui/spinner.py +49 -0
- ripperdoc/cli/ui/thinking_spinner.py +128 -0
- ripperdoc/cli/ui/tool_renderers.py +298 -0
- ripperdoc/core/__init__.py +1 -0
- ripperdoc/core/agents.py +486 -0
- ripperdoc/core/commands.py +33 -0
- ripperdoc/core/config.py +559 -0
- ripperdoc/core/default_tools.py +88 -0
- ripperdoc/core/permissions.py +252 -0
- ripperdoc/core/providers/__init__.py +47 -0
- ripperdoc/core/providers/anthropic.py +250 -0
- ripperdoc/core/providers/base.py +265 -0
- ripperdoc/core/providers/gemini.py +615 -0
- ripperdoc/core/providers/openai.py +487 -0
- ripperdoc/core/query.py +1058 -0
- ripperdoc/core/query_utils.py +622 -0
- ripperdoc/core/skills.py +295 -0
- ripperdoc/core/system_prompt.py +431 -0
- ripperdoc/core/tool.py +240 -0
- ripperdoc/sdk/__init__.py +9 -0
- ripperdoc/sdk/client.py +333 -0
- ripperdoc/tools/__init__.py +1 -0
- ripperdoc/tools/ask_user_question_tool.py +431 -0
- ripperdoc/tools/background_shell.py +389 -0
- ripperdoc/tools/bash_output_tool.py +98 -0
- ripperdoc/tools/bash_tool.py +1016 -0
- ripperdoc/tools/dynamic_mcp_tool.py +428 -0
- ripperdoc/tools/enter_plan_mode_tool.py +226 -0
- ripperdoc/tools/exit_plan_mode_tool.py +153 -0
- ripperdoc/tools/file_edit_tool.py +346 -0
- ripperdoc/tools/file_read_tool.py +203 -0
- ripperdoc/tools/file_write_tool.py +205 -0
- ripperdoc/tools/glob_tool.py +179 -0
- ripperdoc/tools/grep_tool.py +370 -0
- ripperdoc/tools/kill_bash_tool.py +136 -0
- ripperdoc/tools/ls_tool.py +471 -0
- ripperdoc/tools/mcp_tools.py +591 -0
- ripperdoc/tools/multi_edit_tool.py +456 -0
- ripperdoc/tools/notebook_edit_tool.py +386 -0
- ripperdoc/tools/skill_tool.py +205 -0
- ripperdoc/tools/task_tool.py +379 -0
- ripperdoc/tools/todo_tool.py +494 -0
- ripperdoc/tools/tool_search_tool.py +380 -0
- ripperdoc/utils/__init__.py +1 -0
- ripperdoc/utils/bash_constants.py +51 -0
- ripperdoc/utils/bash_output_utils.py +43 -0
- ripperdoc/utils/coerce.py +34 -0
- ripperdoc/utils/context_length_errors.py +252 -0
- ripperdoc/utils/exit_code_handlers.py +241 -0
- ripperdoc/utils/file_watch.py +135 -0
- ripperdoc/utils/git_utils.py +274 -0
- ripperdoc/utils/json_utils.py +27 -0
- ripperdoc/utils/log.py +176 -0
- ripperdoc/utils/mcp.py +560 -0
- ripperdoc/utils/memory.py +253 -0
- ripperdoc/utils/message_compaction.py +676 -0
- ripperdoc/utils/messages.py +519 -0
- ripperdoc/utils/output_utils.py +258 -0
- ripperdoc/utils/path_ignore.py +677 -0
- ripperdoc/utils/path_utils.py +46 -0
- ripperdoc/utils/permissions/__init__.py +27 -0
- ripperdoc/utils/permissions/path_validation_utils.py +174 -0
- ripperdoc/utils/permissions/shell_command_validation.py +552 -0
- ripperdoc/utils/permissions/tool_permission_utils.py +279 -0
- ripperdoc/utils/prompt.py +17 -0
- ripperdoc/utils/safe_get_cwd.py +31 -0
- ripperdoc/utils/sandbox_utils.py +38 -0
- ripperdoc/utils/session_history.py +260 -0
- ripperdoc/utils/session_usage.py +117 -0
- ripperdoc/utils/shell_token_utils.py +95 -0
- ripperdoc/utils/shell_utils.py +159 -0
- ripperdoc/utils/todo.py +203 -0
- ripperdoc/utils/token_estimation.py +34 -0
- ripperdoc-0.2.6.dist-info/METADATA +193 -0
- ripperdoc-0.2.6.dist-info/RECORD +107 -0
- ripperdoc-0.2.6.dist-info/WHEEL +5 -0
- ripperdoc-0.2.6.dist-info/entry_points.txt +3 -0
- ripperdoc-0.2.6.dist-info/licenses/LICENSE +53 -0
- ripperdoc-0.2.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
from rich.markup import escape
|
|
4
|
+
|
|
5
|
+
from ripperdoc.cli.ui.helpers import get_profile_for_pointer
|
|
6
|
+
from ripperdoc.core.config import (
|
|
7
|
+
ModelProfile,
|
|
8
|
+
ProviderType,
|
|
9
|
+
add_model_profile,
|
|
10
|
+
delete_model_profile,
|
|
11
|
+
get_global_config,
|
|
12
|
+
set_model_pointer,
|
|
13
|
+
)
|
|
14
|
+
from ripperdoc.utils.log import get_logger
|
|
15
|
+
from ripperdoc.utils.prompt import prompt_secret
|
|
16
|
+
|
|
17
|
+
from .base import SlashCommand
|
|
18
|
+
|
|
19
|
+
logger = get_logger()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _handle(ui: Any, trimmed_arg: str) -> bool:
|
|
23
|
+
console = ui.console
|
|
24
|
+
tokens = trimmed_arg.split()
|
|
25
|
+
subcmd = tokens[0].lower() if tokens else ""
|
|
26
|
+
config = get_global_config()
|
|
27
|
+
logger.info(
|
|
28
|
+
"[models_cmd] Handling /models command",
|
|
29
|
+
extra={"subcommand": subcmd or "list", "session_id": getattr(ui, "session_id", None)},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def print_models_usage() -> None:
|
|
33
|
+
console.print("[bold]/models[/bold] — list configured models")
|
|
34
|
+
console.print("[bold]/models add <name>[/bold] — add or update a model profile")
|
|
35
|
+
console.print("[bold]/models edit <name>[/bold] — edit an existing model profile")
|
|
36
|
+
console.print("[bold]/models delete <name>[/bold] — delete a model profile")
|
|
37
|
+
console.print("[bold]/models use <name>[/bold] — set the main model pointer")
|
|
38
|
+
console.print("[bold]/models use <pointer> <name>[/bold] — set a specific pointer (main/task/reasoning/quick)")
|
|
39
|
+
|
|
40
|
+
def parse_int(prompt_text: str, default_value: Optional[int]) -> Optional[int]:
|
|
41
|
+
raw = console.input(prompt_text).strip()
|
|
42
|
+
if not raw:
|
|
43
|
+
return default_value
|
|
44
|
+
try:
|
|
45
|
+
return int(raw)
|
|
46
|
+
except ValueError:
|
|
47
|
+
console.print("[yellow]Invalid number, keeping previous value.[/yellow]")
|
|
48
|
+
return default_value
|
|
49
|
+
|
|
50
|
+
def parse_float(prompt_text: str, default_value: float) -> float:
|
|
51
|
+
raw = console.input(prompt_text).strip()
|
|
52
|
+
if not raw:
|
|
53
|
+
return default_value
|
|
54
|
+
try:
|
|
55
|
+
return float(raw)
|
|
56
|
+
except ValueError:
|
|
57
|
+
console.print("[yellow]Invalid number, keeping previous value.[/yellow]")
|
|
58
|
+
return default_value
|
|
59
|
+
|
|
60
|
+
if subcmd in ("help", "-h", "--help"):
|
|
61
|
+
print_models_usage()
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
if subcmd in ("add", "create"):
|
|
65
|
+
profile_name = tokens[1] if len(tokens) > 1 else console.input("Profile name: ").strip()
|
|
66
|
+
if not profile_name:
|
|
67
|
+
console.print("[red]Model profile name is required.[/red]")
|
|
68
|
+
print_models_usage()
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
overwrite = False
|
|
72
|
+
existing_profile = config.model_profiles.get(profile_name)
|
|
73
|
+
if existing_profile:
|
|
74
|
+
confirm = (
|
|
75
|
+
console.input(f"Profile '{profile_name}' exists. Overwrite? [y/N]: ")
|
|
76
|
+
.strip()
|
|
77
|
+
.lower()
|
|
78
|
+
)
|
|
79
|
+
if confirm not in ("y", "yes"):
|
|
80
|
+
return True
|
|
81
|
+
overwrite = True
|
|
82
|
+
|
|
83
|
+
current_profile = get_profile_for_pointer("main")
|
|
84
|
+
default_provider = (
|
|
85
|
+
(current_profile.provider.value) if current_profile else ProviderType.ANTHROPIC.value
|
|
86
|
+
)
|
|
87
|
+
provider_input = (
|
|
88
|
+
console.input(
|
|
89
|
+
f"Protocol ({', '.join(p.value for p in ProviderType)}) [{default_provider}]: "
|
|
90
|
+
)
|
|
91
|
+
.strip()
|
|
92
|
+
.lower()
|
|
93
|
+
or default_provider
|
|
94
|
+
)
|
|
95
|
+
try:
|
|
96
|
+
provider = ProviderType(provider_input)
|
|
97
|
+
except ValueError:
|
|
98
|
+
console.print(f"[red]Invalid provider: {escape(provider_input)}[/red]")
|
|
99
|
+
print_models_usage()
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
default_model = (
|
|
103
|
+
existing_profile.model
|
|
104
|
+
if existing_profile
|
|
105
|
+
else (current_profile.model if current_profile else "")
|
|
106
|
+
)
|
|
107
|
+
model_prompt = f"Model name to send{f' [{default_model}]' if default_model else ''}: "
|
|
108
|
+
model_name = console.input(model_prompt).strip() or default_model
|
|
109
|
+
if not model_name:
|
|
110
|
+
console.print("[red]Model name is required.[/red]")
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
api_key_input = prompt_secret("API key (leave blank to keep unset)").strip()
|
|
114
|
+
api_key = api_key_input or (existing_profile.api_key if existing_profile else None)
|
|
115
|
+
|
|
116
|
+
auth_token = existing_profile.auth_token if existing_profile else None
|
|
117
|
+
if provider == ProviderType.ANTHROPIC:
|
|
118
|
+
auth_token_input = prompt_secret(
|
|
119
|
+
"Auth token (Anthropic only, leave blank to keep unset)"
|
|
120
|
+
).strip()
|
|
121
|
+
auth_token = auth_token_input or auth_token
|
|
122
|
+
else:
|
|
123
|
+
auth_token = None
|
|
124
|
+
|
|
125
|
+
api_base_default = existing_profile.api_base if existing_profile else ""
|
|
126
|
+
api_base = (
|
|
127
|
+
console.input(
|
|
128
|
+
f"API base (optional){f' [{api_base_default}]' if api_base_default else ''}: "
|
|
129
|
+
).strip()
|
|
130
|
+
or api_base_default
|
|
131
|
+
or None
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
max_tokens_default = existing_profile.max_tokens if existing_profile else 4096
|
|
135
|
+
max_tokens = (
|
|
136
|
+
parse_int(
|
|
137
|
+
f"Max output tokens [{max_tokens_default}]: ",
|
|
138
|
+
max_tokens_default,
|
|
139
|
+
)
|
|
140
|
+
or max_tokens_default
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
temp_default = existing_profile.temperature if existing_profile else 0.7
|
|
144
|
+
temperature = parse_float(
|
|
145
|
+
f"Temperature [{temp_default}]: ",
|
|
146
|
+
temp_default,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
context_window_default = existing_profile.context_window if existing_profile else None
|
|
150
|
+
context_prompt = "Context window tokens (optional"
|
|
151
|
+
if context_window_default:
|
|
152
|
+
context_prompt += f", current {context_window_default}"
|
|
153
|
+
context_prompt += "): "
|
|
154
|
+
context_window = parse_int(context_prompt, context_window_default)
|
|
155
|
+
|
|
156
|
+
default_set_main = (
|
|
157
|
+
not config.model_profiles
|
|
158
|
+
or getattr(config.model_pointers, "main", "") not in config.model_profiles
|
|
159
|
+
)
|
|
160
|
+
set_main_input = (
|
|
161
|
+
console.input(f"Set as main model? [{'Y' if default_set_main else 'y'}/N]: ")
|
|
162
|
+
.strip()
|
|
163
|
+
.lower()
|
|
164
|
+
)
|
|
165
|
+
set_as_main = set_main_input in ("y", "yes") if set_main_input else default_set_main
|
|
166
|
+
|
|
167
|
+
profile = ModelProfile(
|
|
168
|
+
provider=provider,
|
|
169
|
+
model=model_name,
|
|
170
|
+
api_key=api_key,
|
|
171
|
+
api_base=api_base,
|
|
172
|
+
max_tokens=max_tokens,
|
|
173
|
+
temperature=temperature,
|
|
174
|
+
context_window=context_window,
|
|
175
|
+
auth_token=auth_token,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
add_model_profile(
|
|
180
|
+
profile_name,
|
|
181
|
+
profile,
|
|
182
|
+
overwrite=overwrite,
|
|
183
|
+
set_as_main=set_as_main,
|
|
184
|
+
)
|
|
185
|
+
except (OSError, IOError, ValueError, TypeError, PermissionError) as exc:
|
|
186
|
+
console.print(f"[red]Failed to save model: {escape(str(exc))}[/red]")
|
|
187
|
+
logger.warning(
|
|
188
|
+
"[models_cmd] Failed to save model profile: %s: %s",
|
|
189
|
+
type(exc).__name__, exc,
|
|
190
|
+
extra={"profile": profile_name, "session_id": getattr(ui, "session_id", None)},
|
|
191
|
+
)
|
|
192
|
+
return True
|
|
193
|
+
|
|
194
|
+
marker = " (main)" if set_as_main else ""
|
|
195
|
+
console.print(f"[green]✓ Model '{escape(profile_name)}' saved{marker}[/green]")
|
|
196
|
+
return True
|
|
197
|
+
|
|
198
|
+
if subcmd in ("edit", "update"):
|
|
199
|
+
profile_name = tokens[1] if len(tokens) > 1 else console.input("Profile to edit: ").strip()
|
|
200
|
+
existing_profile = config.model_profiles.get(profile_name or "")
|
|
201
|
+
if not profile_name or not existing_profile:
|
|
202
|
+
console.print("[red]Model profile not found.[/red]")
|
|
203
|
+
print_models_usage()
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
provider_default = existing_profile.provider.value
|
|
207
|
+
provider_input = (
|
|
208
|
+
console.input(
|
|
209
|
+
f"Protocol ({', '.join(p.value for p in ProviderType)}) [{provider_default}]: "
|
|
210
|
+
)
|
|
211
|
+
.strip()
|
|
212
|
+
.lower()
|
|
213
|
+
or provider_default
|
|
214
|
+
)
|
|
215
|
+
try:
|
|
216
|
+
provider = ProviderType(provider_input)
|
|
217
|
+
except ValueError:
|
|
218
|
+
console.print(f"[red]Invalid provider: {escape(provider_input)}[/red]")
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
model_name = (
|
|
222
|
+
console.input(f"Model name to send [{existing_profile.model}]: ").strip()
|
|
223
|
+
or existing_profile.model
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
api_key_label = "[set]" if existing_profile.api_key else "[not set]"
|
|
227
|
+
api_key_prompt = f"API key {api_key_label} (Enter=keep, '-'=clear)"
|
|
228
|
+
api_key_input = prompt_secret(api_key_prompt).strip()
|
|
229
|
+
if api_key_input == "-":
|
|
230
|
+
api_key = None
|
|
231
|
+
elif api_key_input:
|
|
232
|
+
api_key = api_key_input
|
|
233
|
+
else:
|
|
234
|
+
api_key = existing_profile.api_key
|
|
235
|
+
|
|
236
|
+
auth_token = existing_profile.auth_token
|
|
237
|
+
if (
|
|
238
|
+
provider == ProviderType.ANTHROPIC
|
|
239
|
+
or existing_profile.provider == ProviderType.ANTHROPIC
|
|
240
|
+
):
|
|
241
|
+
auth_label = "[set]" if auth_token else "[not set]"
|
|
242
|
+
auth_prompt = f"Auth token (Anthropic only) {auth_label} (Enter=keep, '-'=clear)"
|
|
243
|
+
auth_token_input = prompt_secret(auth_prompt).strip()
|
|
244
|
+
if auth_token_input == "-":
|
|
245
|
+
auth_token = None
|
|
246
|
+
elif auth_token_input:
|
|
247
|
+
auth_token = auth_token_input
|
|
248
|
+
else:
|
|
249
|
+
auth_token = None
|
|
250
|
+
|
|
251
|
+
api_base = (
|
|
252
|
+
console.input(f"API base (optional) [{existing_profile.api_base or ''}]: ").strip()
|
|
253
|
+
or existing_profile.api_base
|
|
254
|
+
)
|
|
255
|
+
if api_base == "":
|
|
256
|
+
api_base = None
|
|
257
|
+
|
|
258
|
+
max_tokens = (
|
|
259
|
+
parse_int(
|
|
260
|
+
f"Max output tokens [{existing_profile.max_tokens}]: ",
|
|
261
|
+
existing_profile.max_tokens,
|
|
262
|
+
)
|
|
263
|
+
or existing_profile.max_tokens
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
temperature = parse_float(
|
|
267
|
+
f"Temperature [{existing_profile.temperature}]: ",
|
|
268
|
+
existing_profile.temperature,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
context_window = parse_int(
|
|
272
|
+
f"Context window tokens [{existing_profile.context_window or 'unset'}]: ",
|
|
273
|
+
existing_profile.context_window,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
updated_profile = ModelProfile(
|
|
277
|
+
provider=provider,
|
|
278
|
+
model=model_name,
|
|
279
|
+
api_key=api_key,
|
|
280
|
+
api_base=api_base,
|
|
281
|
+
max_tokens=max_tokens,
|
|
282
|
+
temperature=temperature,
|
|
283
|
+
context_window=context_window,
|
|
284
|
+
auth_token=auth_token,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
add_model_profile(
|
|
289
|
+
profile_name,
|
|
290
|
+
updated_profile,
|
|
291
|
+
overwrite=True,
|
|
292
|
+
set_as_main=False,
|
|
293
|
+
)
|
|
294
|
+
except (OSError, IOError, ValueError, TypeError, PermissionError) as exc:
|
|
295
|
+
console.print(f"[red]Failed to update model: {escape(str(exc))}[/red]")
|
|
296
|
+
logger.warning(
|
|
297
|
+
"[models_cmd] Failed to update model profile: %s: %s",
|
|
298
|
+
type(exc).__name__, exc,
|
|
299
|
+
extra={"profile": profile_name, "session_id": getattr(ui, "session_id", None)},
|
|
300
|
+
)
|
|
301
|
+
return True
|
|
302
|
+
|
|
303
|
+
console.print(f"[green]✓ Model '{escape(profile_name)}' updated[/green]")
|
|
304
|
+
return True
|
|
305
|
+
|
|
306
|
+
if subcmd in ("delete", "del", "remove"):
|
|
307
|
+
target = tokens[1] if len(tokens) > 1 else console.input("Model to delete: ").strip()
|
|
308
|
+
if not target:
|
|
309
|
+
console.print("[red]Model name is required.[/red]")
|
|
310
|
+
print_models_usage()
|
|
311
|
+
return True
|
|
312
|
+
try:
|
|
313
|
+
delete_model_profile(target)
|
|
314
|
+
console.print(f"[green]✓ Deleted model '{escape(target)}'[/green]")
|
|
315
|
+
except KeyError as exc:
|
|
316
|
+
console.print(f"[yellow]{escape(str(exc))}[/yellow]")
|
|
317
|
+
except (OSError, IOError, PermissionError) as exc:
|
|
318
|
+
console.print(f"[red]Failed to delete model: {escape(str(exc))}[/red]")
|
|
319
|
+
print_models_usage()
|
|
320
|
+
logger.warning(
|
|
321
|
+
"[models_cmd] Failed to delete model profile: %s: %s",
|
|
322
|
+
type(exc).__name__, exc,
|
|
323
|
+
extra={"profile": target, "session_id": getattr(ui, "session_id", None)},
|
|
324
|
+
)
|
|
325
|
+
return True
|
|
326
|
+
|
|
327
|
+
if subcmd in ("use", "main", "set-main"):
|
|
328
|
+
# Support both "/models use <profile>" and "/models use <pointer> <profile>"
|
|
329
|
+
valid_pointers = {"main", "task", "reasoning", "quick"}
|
|
330
|
+
|
|
331
|
+
if len(tokens) >= 3:
|
|
332
|
+
# /models use <pointer> <profile>
|
|
333
|
+
pointer = tokens[1].lower()
|
|
334
|
+
target = tokens[2]
|
|
335
|
+
if pointer not in valid_pointers:
|
|
336
|
+
console.print(f"[red]Invalid pointer '{escape(pointer)}'. Valid pointers: {', '.join(valid_pointers)}[/red]")
|
|
337
|
+
print_models_usage()
|
|
338
|
+
return True
|
|
339
|
+
elif len(tokens) >= 2:
|
|
340
|
+
# Check if second token is a pointer or a profile
|
|
341
|
+
if tokens[1].lower() in valid_pointers:
|
|
342
|
+
pointer = tokens[1].lower()
|
|
343
|
+
target = console.input(f"Model to use for '{pointer}': ").strip()
|
|
344
|
+
else:
|
|
345
|
+
# /models use <profile> (defaults to main)
|
|
346
|
+
pointer = "main"
|
|
347
|
+
target = tokens[1]
|
|
348
|
+
else:
|
|
349
|
+
pointer = console.input("Pointer (main/task/reasoning/quick) [main]: ").strip().lower() or "main"
|
|
350
|
+
if pointer not in valid_pointers:
|
|
351
|
+
console.print(f"[red]Invalid pointer '{escape(pointer)}'. Valid pointers: {', '.join(valid_pointers)}[/red]")
|
|
352
|
+
return True
|
|
353
|
+
target = console.input(f"Model to use for '{pointer}': ").strip()
|
|
354
|
+
|
|
355
|
+
if not target:
|
|
356
|
+
console.print("[red]Model name is required.[/red]")
|
|
357
|
+
print_models_usage()
|
|
358
|
+
return True
|
|
359
|
+
try:
|
|
360
|
+
set_model_pointer(pointer, target)
|
|
361
|
+
console.print(f"[green]✓ Pointer '{escape(pointer)}' set to '{escape(target)}'[/green]")
|
|
362
|
+
except (ValueError, KeyError, OSError, IOError, PermissionError) as exc:
|
|
363
|
+
console.print(f"[red]{escape(str(exc))}[/red]")
|
|
364
|
+
print_models_usage()
|
|
365
|
+
logger.warning(
|
|
366
|
+
"[models_cmd] Failed to set model pointer: %s: %s",
|
|
367
|
+
type(exc).__name__, exc,
|
|
368
|
+
extra={"pointer": pointer, "profile": target, "session_id": getattr(ui, "session_id", None)},
|
|
369
|
+
)
|
|
370
|
+
return True
|
|
371
|
+
|
|
372
|
+
print_models_usage()
|
|
373
|
+
pointer_map = config.model_pointers.model_dump()
|
|
374
|
+
if not config.model_profiles:
|
|
375
|
+
console.print(" • No models configured")
|
|
376
|
+
return True
|
|
377
|
+
|
|
378
|
+
console.print("\n[bold]Configured Models:[/bold]")
|
|
379
|
+
for name, profile in config.model_profiles.items():
|
|
380
|
+
markers = [ptr for ptr, value in pointer_map.items() if value == name]
|
|
381
|
+
marker_text = f" ({', '.join(markers)})" if markers else ""
|
|
382
|
+
console.print(f" • {escape(name)}{marker_text}", markup=False)
|
|
383
|
+
console.print(f" protocol: {profile.provider.value}", markup=False)
|
|
384
|
+
console.print(f" model: {profile.model}", markup=False)
|
|
385
|
+
if profile.api_base:
|
|
386
|
+
console.print(f" api_base: {profile.api_base}", markup=False)
|
|
387
|
+
if profile.context_window:
|
|
388
|
+
console.print(f" context: {profile.context_window} tokens", markup=False)
|
|
389
|
+
console.print(
|
|
390
|
+
f" max_tokens: {profile.max_tokens}, temperature: {profile.temperature}",
|
|
391
|
+
markup=False,
|
|
392
|
+
)
|
|
393
|
+
console.print(f" api_key: {'***' if profile.api_key else 'Not set'}", markup=False)
|
|
394
|
+
if profile.provider == ProviderType.ANTHROPIC:
|
|
395
|
+
console.print(
|
|
396
|
+
f" auth_token: {'***' if getattr(profile, 'auth_token', None) else 'Not set'}",
|
|
397
|
+
markup=False,
|
|
398
|
+
)
|
|
399
|
+
if profile.openai_tool_mode:
|
|
400
|
+
console.print(f" openai_tool_mode: {profile.openai_tool_mode}", markup=False)
|
|
401
|
+
pointer_labels = ", ".join(f"{p}->{v or '-'}" for p, v in pointer_map.items())
|
|
402
|
+
console.print(f"[dim]Pointers: {escape(pointer_labels)}[/dim]")
|
|
403
|
+
return True
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
command = SlashCommand(
|
|
407
|
+
name="models",
|
|
408
|
+
description="Manage models: list/create/delete/use",
|
|
409
|
+
handler=_handle,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
__all__ = ["command"]
|