@agxnte/reidx 2.0.6 → 2.0.8
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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/reidx/__init__.py +1 -1
- package/src/reidx/app/__pycache__/commands.cpython-312.pyc +0 -0
- package/src/reidx/provider/__pycache__/models.cpython-312.pyc +0 -0
- package/src/reidx/provider/__pycache__/store.cpython-312.pyc +0 -0
- package/src/reidx/provider/models.py +228 -0
- package/src/reidx/provider/store.py +31 -3
- package/src/reidx/provider_manager/__pycache__/catalog.cpython-312.pyc +0 -0
- package/src/reidx/provider_manager/__pycache__/keychain.cpython-312.pyc +0 -0
- package/src/reidx/provider_manager/__pycache__/palette.cpython-312.pyc +0 -0
- package/src/reidx/provider_manager/palette.py +18 -4
- package/src/reidx/ui/__pycache__/app.cpython-312.pyc +0 -0
- package/src/reidx/ui/__pycache__/commands.cpython-312.pyc +0 -0
- package/src/reidx/ui/commands.py +61 -7
package/README.md
CHANGED
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/reidx/__init__.py
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Model normalization, validation, and discovery utilities."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from reidx.diagnostics.logger import get_logger
|
|
8
|
+
|
|
9
|
+
log = get_logger("reidx.provider.models")
|
|
10
|
+
|
|
11
|
+
KNOWN_PROVIDER_PREFIXES = {
|
|
12
|
+
"openrouter",
|
|
13
|
+
"anthropic",
|
|
14
|
+
"openai",
|
|
15
|
+
"google",
|
|
16
|
+
"cohere",
|
|
17
|
+
"mistral",
|
|
18
|
+
"meta",
|
|
19
|
+
"meta-llama",
|
|
20
|
+
"microsoft",
|
|
21
|
+
"nvidia",
|
|
22
|
+
"deepseek",
|
|
23
|
+
"qwen",
|
|
24
|
+
"yi",
|
|
25
|
+
"zai",
|
|
26
|
+
"xai",
|
|
27
|
+
"perplexity",
|
|
28
|
+
"together",
|
|
29
|
+
"fireworks",
|
|
30
|
+
"groq",
|
|
31
|
+
"cerebras",
|
|
32
|
+
"deepinfra",
|
|
33
|
+
"sambanova",
|
|
34
|
+
"novita",
|
|
35
|
+
"featherless",
|
|
36
|
+
"chutes",
|
|
37
|
+
"aimlapi",
|
|
38
|
+
"hyperbolic",
|
|
39
|
+
"moonshot",
|
|
40
|
+
"volcengine",
|
|
41
|
+
"hunyuan",
|
|
42
|
+
"upstage",
|
|
43
|
+
"predibase",
|
|
44
|
+
"gravity",
|
|
45
|
+
"infermatic",
|
|
46
|
+
"baseten",
|
|
47
|
+
"anyscale",
|
|
48
|
+
"lambda",
|
|
49
|
+
"kluster",
|
|
50
|
+
"monsterapi",
|
|
51
|
+
"cloudflare-ai",
|
|
52
|
+
"replicate",
|
|
53
|
+
"watsonx",
|
|
54
|
+
"aleph-alpha",
|
|
55
|
+
"ai21",
|
|
56
|
+
"databricks",
|
|
57
|
+
"siliconflow",
|
|
58
|
+
"nebius",
|
|
59
|
+
"lepton",
|
|
60
|
+
"voyage-ai",
|
|
61
|
+
"jan",
|
|
62
|
+
"llamacpp",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
VALID_VARIANTS = {":free", ":thinking", ":nitro", ":extended", ":online", ":alpha", ":beta"}
|
|
66
|
+
VALID_VARIANTS_LOWER = {v.lower() for v in VALID_VARIANTS}
|
|
67
|
+
|
|
68
|
+
UI_DISPLAY_PATTERN = re.compile(r"\s*\[via\s+([^\]]+)\]", re.IGNORECASE)
|
|
69
|
+
PROVIDER_DASH_PATTERN = re.compile(r"^([A-Za-z0-9\-]+)\s+-\s+(.+)$")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class NormalizedModel:
|
|
74
|
+
provider: str
|
|
75
|
+
model_id: str
|
|
76
|
+
full_id: str
|
|
77
|
+
variant: str | None
|
|
78
|
+
base_model: str
|
|
79
|
+
is_valid: bool
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def normalize_model_id(raw: str, provider_name: str | None = None) -> NormalizedModel:
|
|
83
|
+
original = raw.strip()
|
|
84
|
+
|
|
85
|
+
# Remove UI display text like "[via openrouter]"
|
|
86
|
+
cleaned = UI_DISPLAY_PATTERN.sub("", original).strip()
|
|
87
|
+
|
|
88
|
+
# Remove "ProviderName - " prefix
|
|
89
|
+
dash_match = PROVIDER_DASH_PATTERN.match(cleaned)
|
|
90
|
+
if dash_match:
|
|
91
|
+
cleaned = dash_match.group(2).strip()
|
|
92
|
+
|
|
93
|
+
parts = cleaned.split("/")
|
|
94
|
+
|
|
95
|
+
if not parts or not parts[0]:
|
|
96
|
+
return NormalizedModel(
|
|
97
|
+
provider=provider_name.lower() if provider_name else "",
|
|
98
|
+
model_id=cleaned,
|
|
99
|
+
full_id=cleaned,
|
|
100
|
+
variant=None,
|
|
101
|
+
base_model=cleaned,
|
|
102
|
+
is_valid=False,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
first_part_lower = parts[0].lower()
|
|
106
|
+
provider = ""
|
|
107
|
+
model_parts = parts
|
|
108
|
+
|
|
109
|
+
if provider_name and provider_name.lower() in KNOWN_PROVIDER_PREFIXES:
|
|
110
|
+
provider = provider_name.lower()
|
|
111
|
+
if first_part_lower == provider:
|
|
112
|
+
model_parts = parts[1:]
|
|
113
|
+
elif first_part_lower in KNOWN_PROVIDER_PREFIXES:
|
|
114
|
+
provider = first_part_lower
|
|
115
|
+
model_parts = parts[1:]
|
|
116
|
+
|
|
117
|
+
if not model_parts:
|
|
118
|
+
return NormalizedModel(
|
|
119
|
+
provider=provider,
|
|
120
|
+
model_id="",
|
|
121
|
+
full_id=cleaned,
|
|
122
|
+
variant=None,
|
|
123
|
+
base_model="",
|
|
124
|
+
is_valid=False,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
model_id = "/".join(model_parts)
|
|
128
|
+
|
|
129
|
+
variant = None
|
|
130
|
+
base_model = model_id
|
|
131
|
+
for v in VALID_VARIANTS:
|
|
132
|
+
if model_id.lower().endswith(v.lower()):
|
|
133
|
+
variant = v[1:]
|
|
134
|
+
base_model = model_id[: -len(v)]
|
|
135
|
+
break
|
|
136
|
+
|
|
137
|
+
if provider:
|
|
138
|
+
full_id = f"{provider}/{model_id}"
|
|
139
|
+
else:
|
|
140
|
+
full_id = model_id
|
|
141
|
+
|
|
142
|
+
is_valid = bool(provider and base_model and "/" in base_model)
|
|
143
|
+
|
|
144
|
+
return NormalizedModel(
|
|
145
|
+
provider=provider,
|
|
146
|
+
model_id=model_id,
|
|
147
|
+
full_id=full_id,
|
|
148
|
+
variant=variant,
|
|
149
|
+
base_model=base_model,
|
|
150
|
+
is_valid=is_valid,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def denormalize_model_id(normalized: NormalizedModel, include_provider: bool = True) -> str:
|
|
155
|
+
if include_provider and normalized.provider:
|
|
156
|
+
return normalized.full_id
|
|
157
|
+
return normalized.model_id
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
async def fetch_provider_models(provider) -> list[str]:
|
|
161
|
+
import asyncio
|
|
162
|
+
try:
|
|
163
|
+
loop = asyncio.get_event_loop()
|
|
164
|
+
models = await loop.run_in_executor(None, provider.fetch_models)
|
|
165
|
+
return sorted(models) if models else []
|
|
166
|
+
except Exception as exc:
|
|
167
|
+
log.warning("failed to fetch models from provider: %s", exc)
|
|
168
|
+
return []
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def validate_model_against_provider(
|
|
172
|
+
normalized: NormalizedModel,
|
|
173
|
+
available_models: list[str],
|
|
174
|
+
) -> tuple[bool, str]:
|
|
175
|
+
if not normalized.is_valid:
|
|
176
|
+
return False, f"Invalid model format: {normalized.full_id}"
|
|
177
|
+
|
|
178
|
+
if not available_models:
|
|
179
|
+
return True, "No model list available from provider (skipping validation)"
|
|
180
|
+
|
|
181
|
+
if normalized.full_id in available_models:
|
|
182
|
+
return True, "Model found in provider catalog"
|
|
183
|
+
|
|
184
|
+
if normalized.model_id in available_models:
|
|
185
|
+
return True, "Model found in provider catalog (without provider prefix)"
|
|
186
|
+
|
|
187
|
+
if normalized.base_model in available_models:
|
|
188
|
+
return True, f"Base model found (variant '{normalized.variant}' may not be listed separately)"
|
|
189
|
+
|
|
190
|
+
base_with_provider = f"{normalized.provider}/{normalized.base_model}"
|
|
191
|
+
if base_with_provider in available_models:
|
|
192
|
+
return True, f"Base model found with provider prefix (variant '{normalized.variant}' may not be listed separately)"
|
|
193
|
+
|
|
194
|
+
for avail in available_models:
|
|
195
|
+
if normalized.base_model in avail or avail in normalized.base_model:
|
|
196
|
+
return True, f"Similar model found: {avail}"
|
|
197
|
+
|
|
198
|
+
return False, f"Model '{normalized.full_id}' not found in provider catalog ({len(available_models)} models available)"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class ModelCache:
|
|
202
|
+
def __init__(self) -> None:
|
|
203
|
+
self._cache: dict[str, tuple[list[str], float]] = {}
|
|
204
|
+
self._ttl = 3600
|
|
205
|
+
|
|
206
|
+
def get(self, provider_name: str) -> list[str] | None:
|
|
207
|
+
import time
|
|
208
|
+
if provider_name in self._cache:
|
|
209
|
+
models, timestamp = self._cache[provider_name]
|
|
210
|
+
if time.time() - timestamp < self._ttl:
|
|
211
|
+
return models
|
|
212
|
+
else:
|
|
213
|
+
del self._cache[provider_name]
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
def set(self, provider_name: str, models: list[str]) -> None:
|
|
217
|
+
import time
|
|
218
|
+
self._cache[provider_name] = (models, time.time())
|
|
219
|
+
|
|
220
|
+
def invalidate(self, provider_name: str) -> None:
|
|
221
|
+
self._cache.pop(provider_name, None)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
_model_cache = ModelCache()
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def get_model_cache() -> ModelCache:
|
|
228
|
+
return _model_cache
|
|
@@ -23,6 +23,11 @@ from pathlib import Path
|
|
|
23
23
|
from reidx.diagnostics.logger import get_logger
|
|
24
24
|
from reidx.provider.anthropic import AnthropicProvider
|
|
25
25
|
from reidx.provider.base import BaseProvider
|
|
26
|
+
from reidx.provider.models import (
|
|
27
|
+
denormalize_model_id,
|
|
28
|
+
normalize_model_id,
|
|
29
|
+
validate_model_against_provider,
|
|
30
|
+
)
|
|
26
31
|
from reidx.provider.ollama import OllamaProvider
|
|
27
32
|
from reidx.provider.openai import OpenAICompatibleProvider, OpenAIProvider
|
|
28
33
|
from reidx.provider.registry import ProviderRegistry
|
|
@@ -153,6 +158,16 @@ def validate_provider(
|
|
|
153
158
|
return False, f"{msg} (use --skip-verify to save anyway)"
|
|
154
159
|
except Exception as exc:
|
|
155
160
|
return False, f"unexpected error: {exc} (use --skip-verify to save anyway)"
|
|
161
|
+
|
|
162
|
+
if models and record.default_model:
|
|
163
|
+
normalized = normalize_model_id(record.default_model, provider_name=record.name)
|
|
164
|
+
if normalized.is_valid:
|
|
165
|
+
is_valid, msg = validate_model_against_provider(normalized, models)
|
|
166
|
+
if not is_valid:
|
|
167
|
+
return False, f"model validation failed: {msg}"
|
|
168
|
+
# Update the record with the normalized model ID
|
|
169
|
+
record.default_model = denormalize_model_id(normalized)
|
|
170
|
+
|
|
156
171
|
if models:
|
|
157
172
|
return True, f"ok ({len(models)} models available)"
|
|
158
173
|
return True, "connected (no models endpoint or empty list)"
|
|
@@ -171,6 +186,7 @@ def load_into(registry: ProviderRegistry, storage_root: Path) -> list[str]:
|
|
|
171
186
|
|
|
172
187
|
|
|
173
188
|
def load_from_database(registry: ProviderRegistry, storage_root: Path) -> list[str]:
|
|
189
|
+
from reidx.provider.models import denormalize_model_id, normalize_model_id
|
|
174
190
|
from reidx.provider_manager.database import ProviderDatabase
|
|
175
191
|
|
|
176
192
|
added: list[str] = []
|
|
@@ -198,10 +214,22 @@ def load_from_database(registry: ProviderRegistry, storage_root: Path) -> list[s
|
|
|
198
214
|
except Exception:
|
|
199
215
|
models = []
|
|
200
216
|
if models:
|
|
201
|
-
|
|
202
|
-
|
|
217
|
+
normalized = normalize_model_id(models[0], provider_name=sp.name)
|
|
218
|
+
if normalized.is_valid:
|
|
219
|
+
model = denormalize_model_id(normalized)
|
|
220
|
+
provider.default_model = model
|
|
221
|
+
sp.default_model = model
|
|
222
|
+
db.save_provider(sp)
|
|
223
|
+
log.info("auto-fetched model for %s: %s", sp.name, model)
|
|
224
|
+
elif record.default_model:
|
|
225
|
+
# Normalize the existing model
|
|
226
|
+
normalized = normalize_model_id(record.default_model, provider_name=sp.name)
|
|
227
|
+
if normalized.is_valid:
|
|
228
|
+
model = denormalize_model_id(normalized)
|
|
229
|
+
record.default_model = model
|
|
230
|
+
provider.default_model = model
|
|
231
|
+
sp.default_model = model
|
|
203
232
|
db.save_provider(sp)
|
|
204
|
-
log.info("auto-fetched model for %s: %s", sp.name, models[0])
|
|
205
233
|
registry.register(sp.name, provider)
|
|
206
234
|
added.append(sp.name)
|
|
207
235
|
return added
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -9,6 +9,10 @@ from typing import Any
|
|
|
9
9
|
from prompt_toolkit.buffer import Buffer
|
|
10
10
|
|
|
11
11
|
from reidx.diagnostics.logger import get_logger
|
|
12
|
+
from reidx.provider.models import (
|
|
13
|
+
denormalize_model_id,
|
|
14
|
+
normalize_model_id,
|
|
15
|
+
)
|
|
12
16
|
from reidx.provider.store import ProviderRecord, build_provider, validate_provider
|
|
13
17
|
from reidx.provider_manager.catalog import (
|
|
14
18
|
ProviderDefinition,
|
|
@@ -267,6 +271,12 @@ class ProviderPalette:
|
|
|
267
271
|
self._handle_input_enter()
|
|
268
272
|
self._invalidate()
|
|
269
273
|
return
|
|
274
|
+
if self.screen == self.MESSAGE:
|
|
275
|
+
self.screen = self._message_next
|
|
276
|
+
self.selected_index = 0
|
|
277
|
+
self._scroll_offset = 0
|
|
278
|
+
self._invalidate()
|
|
279
|
+
return
|
|
270
280
|
items = self._build_items()
|
|
271
281
|
if not items:
|
|
272
282
|
return
|
|
@@ -626,7 +636,9 @@ class ProviderPalette:
|
|
|
626
636
|
except Exception:
|
|
627
637
|
models = []
|
|
628
638
|
if models:
|
|
629
|
-
|
|
639
|
+
normalized = normalize_model_id(models[0], provider_name=name)
|
|
640
|
+
if normalized.is_valid:
|
|
641
|
+
model = denormalize_model_id(normalized)
|
|
630
642
|
|
|
631
643
|
self._commit_wizard_provider(name, kind, base_url, model, auth, api_key)
|
|
632
644
|
|
|
@@ -750,9 +762,11 @@ class ProviderPalette:
|
|
|
750
762
|
except Exception:
|
|
751
763
|
models = []
|
|
752
764
|
if models:
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
765
|
+
normalized = normalize_model_id(models[0], provider_name=sp.name)
|
|
766
|
+
if normalized.is_valid:
|
|
767
|
+
provider.default_model = denormalize_model_id(normalized)
|
|
768
|
+
sp.default_model = denormalize_model_id(normalized)
|
|
769
|
+
self.db.save_provider(sp)
|
|
756
770
|
self.orchestrator.providers.register(sp.name, provider)
|
|
757
771
|
except (ValueError, TypeError):
|
|
758
772
|
log.exception("failed to register provider %s", sp.name)
|
|
Binary file
|
|
Binary file
|
package/src/reidx/ui/commands.py
CHANGED
|
@@ -19,6 +19,7 @@ from rich.text import Text
|
|
|
19
19
|
from reidx.config.storage import storage_root
|
|
20
20
|
from reidx.goals.models import Goal, GoalNodeKind, GoalStatus
|
|
21
21
|
from reidx.policy.models import PermissionMode
|
|
22
|
+
from reidx.provider.models import denormalize_model_id, normalize_model_id
|
|
22
23
|
from reidx.provider.store import (
|
|
23
24
|
SUPPORTED_KINDS,
|
|
24
25
|
ProviderRecord,
|
|
@@ -80,6 +81,7 @@ SLASH_COMMANDS: list[tuple[str, str, str, str]] = [
|
|
|
80
81
|
("/connect", "", "open the interactive provider connection palette", "Providers"),
|
|
81
82
|
("/disconnect", "<name>", "remove a saved provider", "Providers"),
|
|
82
83
|
("/use", "<name>", "switch this session to a registered provider", "Providers"),
|
|
84
|
+
("/models", "[provider]", "list available models from providers (optionally filter by provider)", "Providers"),
|
|
83
85
|
("/help", "", "show this help", "Meta"),
|
|
84
86
|
("/clear", "", "clear the screen", "Meta"),
|
|
85
87
|
("/exit", "", f"quit {APP_NAME}", "Meta"),
|
|
@@ -484,6 +486,7 @@ def _providers_store(orchestrator: Orchestrator) -> ProviderStore:
|
|
|
484
486
|
|
|
485
487
|
|
|
486
488
|
def _save_to_database(orchestrator: Orchestrator, record: ProviderRecord) -> None:
|
|
489
|
+
from reidx.provider.models import denormalize_model_id, normalize_model_id
|
|
487
490
|
from reidx.provider_manager import keychain
|
|
488
491
|
from reidx.provider_manager.database import ProviderDatabase, StoredKey, StoredProvider
|
|
489
492
|
|
|
@@ -504,7 +507,11 @@ def _save_to_database(orchestrator: Orchestrator, record: ProviderRecord) -> Non
|
|
|
504
507
|
existing.base_url = record.base_url
|
|
505
508
|
existing.auth_method = record.auth_method
|
|
506
509
|
if record.default_model:
|
|
507
|
-
|
|
510
|
+
normalized = normalize_model_id(record.default_model, provider_name=record.name)
|
|
511
|
+
if normalized.is_valid:
|
|
512
|
+
existing.default_model = denormalize_model_id(normalized)
|
|
513
|
+
else:
|
|
514
|
+
existing.default_model = record.default_model
|
|
508
515
|
db.save_provider(existing)
|
|
509
516
|
else:
|
|
510
517
|
sp = StoredProvider(
|
|
@@ -519,6 +526,12 @@ def _save_to_database(orchestrator: Orchestrator, record: ProviderRecord) -> Non
|
|
|
519
526
|
)
|
|
520
527
|
sp.keys.append(k)
|
|
521
528
|
sp.active_key_id = k.id
|
|
529
|
+
if record.default_model:
|
|
530
|
+
normalized = normalize_model_id(record.default_model, provider_name=record.name)
|
|
531
|
+
if normalized.is_valid:
|
|
532
|
+
sp.default_model = denormalize_model_id(normalized)
|
|
533
|
+
else:
|
|
534
|
+
sp.default_model = record.default_model
|
|
522
535
|
db.save_provider(sp)
|
|
523
536
|
|
|
524
537
|
|
|
@@ -577,15 +590,18 @@ def _handle_connect(orchestrator: Orchestrator, arg: str) -> None:
|
|
|
577
590
|
except Exception:
|
|
578
591
|
models = []
|
|
579
592
|
if models:
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
593
|
+
normalized = normalize_model_id(models[0], provider_name=name)
|
|
594
|
+
if normalized.is_valid:
|
|
595
|
+
model = denormalize_model_id(normalized)
|
|
596
|
+
provider.default_model = model
|
|
597
|
+
record.default_model = model
|
|
598
|
+
render.print_info(f"auto-detected model: {model}")
|
|
583
599
|
_providers_store(orchestrator).save(record)
|
|
584
600
|
_save_to_database(orchestrator, record)
|
|
585
601
|
if orchestrator.providers is not None:
|
|
586
602
|
orchestrator.providers.register(name, provider)
|
|
587
|
-
render.print_info(f"connected provider '{name}' ({kind})
|
|
588
|
-
render.print_info(f"switch with: /use {name}")
|
|
603
|
+
render.print_info(f"connected provider '{name}' ({kind}) {base_url or '(default)'}")
|
|
604
|
+
render.print_info(f"switch with: /use {name}")
|
|
589
605
|
|
|
590
606
|
|
|
591
607
|
def _handle_disconnect(orchestrator: Orchestrator, arg: str) -> None:
|
|
@@ -609,6 +625,42 @@ def _handle_disconnect(orchestrator: Orchestrator, arg: str) -> None:
|
|
|
609
625
|
render.print_error(f"no saved provider named '{name}'")
|
|
610
626
|
|
|
611
627
|
|
|
628
|
+
def _handle_models(orchestrator: Orchestrator, arg: str) -> None:
|
|
629
|
+
"""Handle /models command - list available models from providers."""
|
|
630
|
+
provider_filter = arg.strip() or None
|
|
631
|
+
|
|
632
|
+
if orchestrator.providers is None:
|
|
633
|
+
render.print_error("no provider registry available")
|
|
634
|
+
return
|
|
635
|
+
|
|
636
|
+
provider_names = orchestrator.providers.names()
|
|
637
|
+
if not provider_names:
|
|
638
|
+
render.print_info("no providers registered")
|
|
639
|
+
return
|
|
640
|
+
|
|
641
|
+
if provider_filter:
|
|
642
|
+
if provider_filter not in provider_names:
|
|
643
|
+
render.print_error(f"provider '{provider_filter}' not registered (see /providers)")
|
|
644
|
+
return
|
|
645
|
+
provider_names = [provider_filter]
|
|
646
|
+
|
|
647
|
+
for name in provider_names:
|
|
648
|
+
provider = orchestrator.providers.get(name)
|
|
649
|
+
try:
|
|
650
|
+
models = provider.fetch_models()
|
|
651
|
+
except Exception as exc:
|
|
652
|
+
render.print_error(f" {name}: failed to fetch models - {exc}")
|
|
653
|
+
continue
|
|
654
|
+
|
|
655
|
+
if not models:
|
|
656
|
+
render.print_info(f" {name}: (no models returned)")
|
|
657
|
+
continue
|
|
658
|
+
|
|
659
|
+
render.print_info(f" {name} ({len(models)} models):")
|
|
660
|
+
for model in models:
|
|
661
|
+
render.print_info(f" {model}")
|
|
662
|
+
|
|
663
|
+
|
|
612
664
|
def _handle_use(orchestrator: Orchestrator, arg: str) -> None:
|
|
613
665
|
name = arg.strip()
|
|
614
666
|
if not name:
|
|
@@ -723,10 +775,12 @@ def handle(orchestrator: Orchestrator, line: str) -> str:
|
|
|
723
775
|
_handle_disconnect(orchestrator, arg)
|
|
724
776
|
elif cmd == "use":
|
|
725
777
|
_handle_use(orchestrator, arg)
|
|
778
|
+
elif cmd == "models":
|
|
779
|
+
_handle_models(orchestrator, arg)
|
|
726
780
|
elif cmd == "clear":
|
|
727
781
|
render.console.clear()
|
|
728
782
|
elif cmd in ("exit", "quit", "q"):
|
|
729
783
|
return "exit"
|
|
730
784
|
else:
|
|
731
785
|
render.print_error(f"unknown command: /{cmd} (try /help)")
|
|
732
|
-
return "continue"
|
|
786
|
+
return "continue"
|