@agxnte/reidx 2.0.5 → 2.0.6
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/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/reidx/__init__.py +1 -1
- package/src/reidx/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/reidx/provider/__pycache__/_http.cpython-312.pyc +0 -0
- package/src/reidx/provider/__pycache__/store.cpython-312.pyc +0 -0
- package/src/reidx/provider/_http.py +75 -42
- package/src/reidx/provider/store.py +15 -5
- package/src/reidx/provider_manager/palette.py +37 -5
- package/src/reidx/ui/__pycache__/commands.cpython-312.pyc +0 -0
- package/src/reidx/ui/commands.py +11 -4
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/reidx/__init__.py
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,42 +1,75 @@
|
|
|
1
|
-
"""Shared HTTP helper for provider clients — stdlib urllib, no extra deps."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
1
|
+
"""Shared HTTP helper for provider clients — stdlib urllib, no extra deps."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import ssl
|
|
7
|
+
import urllib.error
|
|
8
|
+
import urllib.request
|
|
9
|
+
|
|
10
|
+
TIMEOUT_SECONDS = 120
|
|
11
|
+
|
|
12
|
+
_SSL_CONTEXT: ssl.SSLContext | None = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _build_ssl_context() -> ssl.SSLContext:
|
|
16
|
+
insecure = os.environ.get("REIDX_INSECURE", "").strip().lower() in ("1", "true", "yes", "on")
|
|
17
|
+
if insecure:
|
|
18
|
+
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
19
|
+
ctx.check_hostname = False
|
|
20
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
21
|
+
return ctx
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import certifi
|
|
25
|
+
return ssl.create_default_context(cafile=certifi.where())
|
|
26
|
+
except ImportError:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
ctx = ssl.create_default_context()
|
|
30
|
+
try:
|
|
31
|
+
ctx.load_default_certs(ssl.Purpose.SERVER_AUTH)
|
|
32
|
+
except ssl.SSLError:
|
|
33
|
+
pass
|
|
34
|
+
return ctx
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _ssl_ctx() -> ssl.SSLContext:
|
|
38
|
+
global _SSL_CONTEXT
|
|
39
|
+
if _SSL_CONTEXT is None:
|
|
40
|
+
_SSL_CONTEXT = _build_ssl_context()
|
|
41
|
+
return _SSL_CONTEXT
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def post_json(url: str, payload: dict, headers: dict[str, str], timeout: int = TIMEOUT_SECONDS) -> dict:
|
|
45
|
+
"""POST a JSON payload, return the parsed JSON response.
|
|
46
|
+
|
|
47
|
+
Raises RuntimeError on HTTP or network errors — providers surface those
|
|
48
|
+
to the agent loop, which turns them into tool-result errors rather than
|
|
49
|
+
crashing the turn.
|
|
50
|
+
"""
|
|
51
|
+
body = json.dumps(payload).encode("utf-8")
|
|
52
|
+
hdrs = {"content-type": "application/json", **headers}
|
|
53
|
+
req = urllib.request.Request(url, data=body, headers=hdrs, method="POST")
|
|
54
|
+
try:
|
|
55
|
+
with urllib.request.urlopen(req, timeout=timeout, context=_ssl_ctx()) as resp:
|
|
56
|
+
raw = resp.read().decode("utf-8")
|
|
57
|
+
except urllib.error.HTTPError as exc:
|
|
58
|
+
err_body = exc.read().decode("utf-8", errors="replace")[:500]
|
|
59
|
+
raise RuntimeError(f"HTTP {exc.code}: {err_body}") from exc
|
|
60
|
+
except urllib.error.URLError as exc:
|
|
61
|
+
raise RuntimeError(f"connection error: {exc}") from exc
|
|
62
|
+
return json.loads(raw)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_json(url: str, headers: dict[str, str], timeout: int = TIMEOUT_SECONDS) -> dict:
|
|
66
|
+
req = urllib.request.Request(url, headers=headers, method="GET")
|
|
67
|
+
try:
|
|
68
|
+
with urllib.request.urlopen(req, timeout=timeout, context=_ssl_ctx()) as resp:
|
|
69
|
+
raw = resp.read().decode("utf-8")
|
|
70
|
+
except urllib.error.HTTPError as exc:
|
|
71
|
+
err_body = exc.read().decode("utf-8", errors="replace")[:500]
|
|
72
|
+
raise RuntimeError(f"HTTP {exc.code}: {err_body}") from exc
|
|
73
|
+
except urllib.error.URLError as exc:
|
|
74
|
+
raise RuntimeError(f"connection error: {exc}") from exc
|
|
75
|
+
return json.loads(raw)
|
|
@@ -127,7 +127,10 @@ class ProviderStore:
|
|
|
127
127
|
return True
|
|
128
128
|
|
|
129
129
|
|
|
130
|
-
def validate_provider(
|
|
130
|
+
def validate_provider(
|
|
131
|
+
record: ProviderRecord,
|
|
132
|
+
skip_verify: bool = False,
|
|
133
|
+
) -> tuple[bool, str]:
|
|
131
134
|
try:
|
|
132
135
|
provider = build_provider(record)
|
|
133
136
|
except (ValueError, TypeError) as exc:
|
|
@@ -137,15 +140,22 @@ def validate_provider(record: ProviderRecord) -> tuple[bool, str]:
|
|
|
137
140
|
return False, "API key required for this provider kind"
|
|
138
141
|
if not record.api_key:
|
|
139
142
|
return True, "no key required"
|
|
143
|
+
if skip_verify:
|
|
144
|
+
return True, "verification skipped"
|
|
140
145
|
try:
|
|
141
146
|
models = provider.fetch_models()
|
|
147
|
+
except RuntimeError as exc:
|
|
148
|
+
msg = str(exc)
|
|
149
|
+
if msg.startswith("HTTP 401") or msg.startswith("HTTP 403"):
|
|
150
|
+
return False, f"authentication failed ({msg})"
|
|
151
|
+
if msg.startswith("HTTP "):
|
|
152
|
+
return False, f"provider error: {msg}"
|
|
153
|
+
return False, f"{msg} (use --skip-verify to save anyway)"
|
|
142
154
|
except Exception as exc:
|
|
143
|
-
return False, f"
|
|
155
|
+
return False, f"unexpected error: {exc} (use --skip-verify to save anyway)"
|
|
144
156
|
if models:
|
|
145
157
|
return True, f"ok ({len(models)} models available)"
|
|
146
|
-
|
|
147
|
-
return True, "connected"
|
|
148
|
-
return False, "key rejected or no models returned"
|
|
158
|
+
return True, "connected (no models endpoint or empty list)"
|
|
149
159
|
|
|
150
160
|
|
|
151
161
|
def load_into(registry: ProviderRegistry, storage_root: Path) -> list[str]:
|
|
@@ -610,8 +610,14 @@ class ProviderPalette:
|
|
|
610
610
|
)
|
|
611
611
|
ok, msg = validate_provider(record)
|
|
612
612
|
if not ok:
|
|
613
|
-
self.
|
|
614
|
-
self.
|
|
613
|
+
self._confirm_text = f"Key validation failed: {msg}\nSave anyway?"
|
|
614
|
+
self._pending_action = lambda: self._commit_wizard_provider(
|
|
615
|
+
name, kind, base_url, model, auth, api_key, forced_msg=msg,
|
|
616
|
+
)
|
|
617
|
+
self.screen = self.CONFIRM
|
|
618
|
+
self.selected_index = 0
|
|
619
|
+
self._scroll_offset = 0
|
|
620
|
+
self._invalidate()
|
|
615
621
|
return
|
|
616
622
|
if not model:
|
|
617
623
|
try:
|
|
@@ -622,6 +628,18 @@ class ProviderPalette:
|
|
|
622
628
|
if models:
|
|
623
629
|
model = models[0]
|
|
624
630
|
|
|
631
|
+
self._commit_wizard_provider(name, kind, base_url, model, auth, api_key)
|
|
632
|
+
|
|
633
|
+
def _commit_wizard_provider(
|
|
634
|
+
self,
|
|
635
|
+
name: str,
|
|
636
|
+
kind: str,
|
|
637
|
+
base_url: str,
|
|
638
|
+
model: str,
|
|
639
|
+
auth: str,
|
|
640
|
+
api_key: str,
|
|
641
|
+
forced_msg: str = "",
|
|
642
|
+
) -> str:
|
|
625
643
|
existing = self.db.get_provider(name)
|
|
626
644
|
if existing:
|
|
627
645
|
existing.kind = kind
|
|
@@ -660,7 +678,9 @@ class ProviderPalette:
|
|
|
660
678
|
self.wizard_data = {}
|
|
661
679
|
self.wizard_step_idx = 0
|
|
662
680
|
self.input_is_password = False
|
|
663
|
-
|
|
681
|
+
note = " (unverified)" if forced_msg else ""
|
|
682
|
+
self._close(f"Saved provider '{name}' ({kind}){note}")
|
|
683
|
+
return f"Saved provider '{name}'"
|
|
664
684
|
|
|
665
685
|
def _do_add_key(self, label: str, api_key: str) -> None:
|
|
666
686
|
if not self.current_provider:
|
|
@@ -673,13 +693,25 @@ class ProviderPalette:
|
|
|
673
693
|
auth_method=sp.auth_method,
|
|
674
694
|
)
|
|
675
695
|
ok, msg = validate_provider(record)
|
|
676
|
-
if
|
|
677
|
-
self.
|
|
696
|
+
if ok:
|
|
697
|
+
self._commit_key(label, api_key, msg)
|
|
678
698
|
return
|
|
699
|
+
self._confirm_text = f"Key validation failed: {msg}\nSave anyway?"
|
|
700
|
+
self._pending_action = lambda: self._commit_key(label, api_key, "saved (unverified)")
|
|
701
|
+
self.screen = self.CONFIRM
|
|
702
|
+
self.selected_index = 0
|
|
703
|
+
self._scroll_offset = 0
|
|
704
|
+
self._invalidate()
|
|
705
|
+
|
|
706
|
+
def _commit_key(self, label: str, api_key: str, msg: str) -> str:
|
|
707
|
+
if not self.current_provider:
|
|
708
|
+
return "Error: no provider"
|
|
709
|
+
name = self.current_provider.name
|
|
679
710
|
self.db.add_key(name, label, api_key)
|
|
680
711
|
self.current_provider = self.db.get_provider(name)
|
|
681
712
|
self._register_current()
|
|
682
713
|
self._show_message(f"Added key '{label}' ({msg})", self.MANAGE)
|
|
714
|
+
return f"Added key '{label}'"
|
|
683
715
|
|
|
684
716
|
def _do_delete_key(self, key_id: str, label: str) -> str:
|
|
685
717
|
if not self.current_provider:
|
|
Binary file
|
package/src/reidx/ui/commands.py
CHANGED
|
@@ -536,10 +536,17 @@ def _handle_providers(orchestrator: Orchestrator) -> None:
|
|
|
536
536
|
|
|
537
537
|
|
|
538
538
|
def _handle_connect(orchestrator: Orchestrator, arg: str) -> None:
|
|
539
|
-
|
|
539
|
+
skip_verify = False
|
|
540
|
+
tokens = arg.split()
|
|
541
|
+
parts: list[str] = []
|
|
542
|
+
for tok in tokens:
|
|
543
|
+
if tok in ("--skip-verify", "--skip", "--no-verify"):
|
|
544
|
+
skip_verify = True
|
|
545
|
+
else:
|
|
546
|
+
parts.append(tok)
|
|
540
547
|
if len(parts) < 3:
|
|
541
548
|
render.print_error(
|
|
542
|
-
"usage: /connect <name> <kind> <base_url> [api_key] [model] "
|
|
549
|
+
"usage: /connect <name> <kind> <base_url> [api_key] [model] [--skip-verify] "
|
|
543
550
|
f"(kind: {'|'.join(SUPPORTED_KINDS)})"
|
|
544
551
|
)
|
|
545
552
|
return
|
|
@@ -554,7 +561,7 @@ def _handle_connect(orchestrator: Orchestrator, arg: str) -> None:
|
|
|
554
561
|
model = parts[4] if len(parts) > 4 else ""
|
|
555
562
|
record = ProviderRecord(name=name, kind=kind, base_url=base_url, api_key=api_key, default_model=model)
|
|
556
563
|
if api_key:
|
|
557
|
-
ok, msg = validate_provider(record)
|
|
564
|
+
ok, msg = validate_provider(record, skip_verify=skip_verify)
|
|
558
565
|
if not ok:
|
|
559
566
|
render.print_error(f"key validation failed: {msg}")
|
|
560
567
|
return
|
|
@@ -564,7 +571,7 @@ def _handle_connect(orchestrator: Orchestrator, arg: str) -> None:
|
|
|
564
571
|
except ValueError as exc:
|
|
565
572
|
render.print_error(f"failed to build provider: {exc}")
|
|
566
573
|
return
|
|
567
|
-
if not model and api_key:
|
|
574
|
+
if not model and api_key and not skip_verify:
|
|
568
575
|
try:
|
|
569
576
|
models = provider.fetch_models()
|
|
570
577
|
except Exception:
|