@ashx-j/lunr-ai 0.2.22 → 0.2.24
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/dist/api/anthropic-claude-code-bridge.d.ts +6 -0
- package/dist/api/anthropic-claude-code-bridge.d.ts.map +1 -0
- package/dist/api/anthropic-claude-code-bridge.js +582 -0
- package/dist/api/anthropic-claude-code-bridge.js.map +1 -0
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +7 -19
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/auth/anthropic-route.d.ts +7 -0
- package/dist/auth/anthropic-route.d.ts.map +1 -0
- package/dist/auth/anthropic-route.js +25 -0
- package/dist/auth/anthropic-route.js.map +1 -0
- package/dist/auth/oauth/anthropic.d.ts +1 -6
- package/dist/auth/oauth/anthropic.d.ts.map +1 -1
- package/dist/auth/oauth/anthropic.js +214 -277
- package/dist/auth/oauth/anthropic.js.map +1 -1
- package/dist/auth/resolve.d.ts.map +1 -1
- package/dist/auth/resolve.js +27 -0
- package/dist/auth/resolve.js.map +1 -1
- package/dist/auth/types.d.ts +15 -2
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/types.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +1 -2
- package/dist/env-api-keys.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +19 -3
- package/dist/models.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +32 -3
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/radius.d.ts.map +1 -1
- package/dist/providers/radius.js +5 -1
- package/dist/providers/radius.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +15 -2
- package/vendor/hermes-claude-subscription-directsdk/ATTRIBUTION.md +9 -0
- package/vendor/hermes-claude-subscription-directsdk/LICENSE +21 -0
- package/vendor/hermes-claude-subscription-directsdk/README.md +179 -0
- package/vendor/hermes-claude-subscription-directsdk/admission.py +201 -0
- package/vendor/hermes-claude-subscription-directsdk/agent/reasoning_effort.py +17 -0
- package/vendor/hermes-claude-subscription-directsdk/directsdk.py +629 -0
- package/vendor/hermes-claude-subscription-directsdk/directsdk_setup.py +116 -0
- package/vendor/hermes-claude-subscription-directsdk/inert_mcp.py +24 -0
- package/vendor/hermes-claude-subscription-directsdk/lunr_bridge.py +89 -0
- package/vendor/hermes-claude-subscription-directsdk/lunr_setup_bridge.py +49 -0
- package/vendor/hermes-claude-subscription-directsdk/manifest.json +32 -0
- package/vendor/hermes-claude-subscription-directsdk/model_catalog.py +44 -0
- package/vendor/hermes-claude-subscription-directsdk/patches/0001-lunr-lifecycle-hooks.patch +14 -0
- package/vendor/hermes-claude-subscription-directsdk/tools/schema_sanitizer.py +23 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Setup-time probes of the user's Claude CLI: login state and its own model picker.
|
|
2
|
+
|
|
3
|
+
Both are offline with respect to Anthropic: ``auth status`` reads the local credential store, and
|
|
4
|
+
the ``initialize`` handshake enumerates the picker without a Messages request (the admission relay
|
|
5
|
+
proves it by counting upstream calls). Anything unexpected returns ``None``/``False`` so callers
|
|
6
|
+
fall back to the pinned catalog rather than failing setup.
|
|
7
|
+
"""
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import shutil
|
|
11
|
+
import subprocess
|
|
12
|
+
import tempfile
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from .admission import Admission
|
|
16
|
+
from .model_catalog import CONTEXT_WINDOWS, native_model
|
|
17
|
+
except ImportError:
|
|
18
|
+
from admission import Admission
|
|
19
|
+
from model_catalog import CONTEXT_WINDOWS, native_model
|
|
20
|
+
|
|
21
|
+
INSTALL_HINT = ("Claude Code is not installed (no `claude` on PATH). Install it with "
|
|
22
|
+
"`npm install -g @anthropic-ai/claude-code` or set CLAUDE_SUBSCRIPTION_DIRECTSDK_COMMAND to the binary.")
|
|
23
|
+
LOGIN_HINT = "Claude Code is installed but not logged in. Run `claude auth login`, then select this provider again."
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _resolve(command, env):
|
|
27
|
+
command = list(command) if command else [env.get("CLAUDE_SUBSCRIPTION_DIRECTSDK_COMMAND") or "claude"]
|
|
28
|
+
head = command[0]
|
|
29
|
+
exe = head if os.path.isabs(head) and os.access(head, os.X_OK) else shutil.which(head, path=env.get("PATH") or os.defpath)
|
|
30
|
+
return ([exe] + command[1:]) if exe else None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _child_env(env):
|
|
34
|
+
child = dict(env)
|
|
35
|
+
config = child.pop("CLAUDE_SUBSCRIPTION_DIRECTSDK_CONFIG_DIR", None)
|
|
36
|
+
if config:
|
|
37
|
+
child["CLAUDE_CONFIG_DIR"] = config
|
|
38
|
+
child.update(CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC="1", DISABLE_TELEMETRY="1", DISABLE_ERROR_REPORTING="1")
|
|
39
|
+
return child
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _plan_label(raw):
|
|
43
|
+
"""``"pro"`` (auth status) and ``"Claude Pro"`` (handshake) name the same plan."""
|
|
44
|
+
raw = str(raw or "").strip()
|
|
45
|
+
if not raw:
|
|
46
|
+
return ""
|
|
47
|
+
return raw if raw.lower().startswith("claude") else "Claude " + raw.replace("_", " ").title()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def setup_status(command=None, env=None, timeout=20):
|
|
51
|
+
"""``{available, logged_in, plan, detail, login_command}`` from the CLI's own ``auth status``."""
|
|
52
|
+
env = dict(env if env is not None else os.environ)
|
|
53
|
+
resolved = _resolve(command, env)
|
|
54
|
+
if resolved is None:
|
|
55
|
+
return {"available": False, "logged_in": False, "plan": "", "detail": INSTALL_HINT, "login_command": None}
|
|
56
|
+
login_command = resolved + ["auth", "login"]
|
|
57
|
+
try:
|
|
58
|
+
run = subprocess.run(resolved + ["auth", "status"], env=_child_env(env), stdin=subprocess.DEVNULL,
|
|
59
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=timeout)
|
|
60
|
+
auth = json.loads(run.stdout) if run.stdout.strip().startswith("{") else {}
|
|
61
|
+
except (OSError, ValueError, subprocess.SubprocessError):
|
|
62
|
+
auth = {}
|
|
63
|
+
logged_in = auth.get("loggedIn") is True
|
|
64
|
+
plan = str(auth.get("subscriptionType") or "")
|
|
65
|
+
detail = "" if logged_in else LOGIN_HINT
|
|
66
|
+
return {"available": True, "logged_in": logged_in, "plan": _plan_label(plan), "detail": detail,
|
|
67
|
+
"login_command": login_command}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def discover_models(command=None, env=None, timeout=40):
|
|
71
|
+
"""The account's live picker as ``[{id, label, note, upstream_requests}]`` in Hermes route ids,
|
|
72
|
+
or ``None`` when the CLI is missing, logged out, or the handshake fails."""
|
|
73
|
+
env = dict(env if env is not None else os.environ)
|
|
74
|
+
resolved = _resolve(command, env)
|
|
75
|
+
# Logged out, the handshake still answers with a generic default list; only a signed-in
|
|
76
|
+
# account's picker reflects its entitlements.
|
|
77
|
+
if resolved is None or not setup_status(command=resolved, env=env, timeout=timeout)["logged_in"]:
|
|
78
|
+
return None
|
|
79
|
+
child = _child_env(env)
|
|
80
|
+
gate = Admission("https://api.anthropic.com", timeout)
|
|
81
|
+
try:
|
|
82
|
+
child["ANTHROPIC_BASE_URL"] = gate.url
|
|
83
|
+
argv = resolved + ["-p", "--model", "sonnet", "--input-format", "stream-json", "--output-format", "stream-json",
|
|
84
|
+
"--verbose", "--tools", "", "--setting-sources", "", "--strict-mcp-config",
|
|
85
|
+
"--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]
|
|
86
|
+
handshake = json.dumps({"type": "control_request", "request_id": "hermes-picker",
|
|
87
|
+
"request": {"subtype": "initialize"}}) + "\n"
|
|
88
|
+
with tempfile.TemporaryDirectory(prefix="claude-directsdk-picker-") as cwd:
|
|
89
|
+
run = subprocess.run(argv, input=handshake, env=child, cwd=cwd, capture_output=True,
|
|
90
|
+
text=True, encoding="utf-8", errors="replace", timeout=timeout)
|
|
91
|
+
rows = [json.loads(line) for line in run.stdout.splitlines() if line.startswith("{")]
|
|
92
|
+
response = next(r["response"] for r in rows if r.get("type") == "control_response")
|
|
93
|
+
native = response.get("response", {}).get("models") or []
|
|
94
|
+
upstream = int(bool(gate.used))
|
|
95
|
+
except (OSError, ValueError, StopIteration, KeyError, subprocess.SubprocessError):
|
|
96
|
+
return None
|
|
97
|
+
finally:
|
|
98
|
+
gate.close()
|
|
99
|
+
if upstream or not native:
|
|
100
|
+
return None
|
|
101
|
+
# Pro / Team-standard seats bill Fable to usage credits from the first request; the CLI only
|
|
102
|
+
# says so at request time, so apply the documented plan rule here.
|
|
103
|
+
plan = str((response.get("response", {}).get("account") or {}).get("subscriptionType") or "").lower()
|
|
104
|
+
credit_billed_on_plan = {"claude-fable-5-1"} if plan and "max" not in plan else set()
|
|
105
|
+
routes = {}
|
|
106
|
+
for row in native:
|
|
107
|
+
base = str(row.get("resolvedModel") or row.get("value") or "").removesuffix("[1m]")
|
|
108
|
+
if base not in CONTEXT_WINDOWS:
|
|
109
|
+
continue
|
|
110
|
+
route = native_model(base)
|
|
111
|
+
# Model names, not the native "Default (recommended)" alias row.
|
|
112
|
+
label = str(row.get("description") or "").split("·")[0].strip() or route
|
|
113
|
+
entry = routes.setdefault(route, {"id": route, "label": label, "note": "", "upstream_requests": upstream})
|
|
114
|
+
if "usage credit" in str(row.get("description") or "").lower() or base in credit_billed_on_plan:
|
|
115
|
+
entry["note"] = "usage credits"
|
|
116
|
+
return list(routes.values()) or None
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Inventory only. No host imports, effects or executable tool implementations."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
manifest = json.loads(Path(sys.argv[1]).read_text(encoding='utf-8'))
|
|
9
|
+
for line in sys.stdin:
|
|
10
|
+
row = json.loads(line)
|
|
11
|
+
method = row.get('method')
|
|
12
|
+
result = {}
|
|
13
|
+
if method == 'initialize':
|
|
14
|
+
result = {'protocolVersion': '2024-11-05', 'capabilities': {'tools': {}}, 'serverInfo': {'name': 'hermes-inert-inventory', 'version': '1'}}
|
|
15
|
+
elif method == 'tools/list':
|
|
16
|
+
result = {'tools': manifest}
|
|
17
|
+
elif method == 'tools/call':
|
|
18
|
+
result = {'isError': True, 'content': [{'type': 'text', 'text': 'Denied: native tools are inert; only Hermes executes tools.'}]}
|
|
19
|
+
if 'id' in row:
|
|
20
|
+
print(json.dumps({'jsonrpc': '2.0', 'id': row['id'], 'result': result}), flush=True)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if __name__ == '__main__':
|
|
24
|
+
main()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Per-request JSON-lines bridge to the pinned, request-scoped native transport."""
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
import subprocess
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from directsdk import Client
|
|
9
|
+
from directsdk_setup import _resolve, _child_env
|
|
10
|
+
|
|
11
|
+
MAX_RECORD = 64 * 1024 * 1024
|
|
12
|
+
_write_lock = threading.Lock()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def emit(request_id, kind, **fields):
|
|
16
|
+
with _write_lock:
|
|
17
|
+
sys.stdout.write(json.dumps({'v': 1, 'requestId': request_id, 'type': kind, **fields}, separators=(',', ':')) + '\n')
|
|
18
|
+
sys.stdout.flush()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
line = sys.stdin.buffer.readline(MAX_RECORD + 1)
|
|
23
|
+
if not line or len(line) > MAX_RECORD:
|
|
24
|
+
return 2
|
|
25
|
+
try:
|
|
26
|
+
start = json.loads(line)
|
|
27
|
+
if start.get('v') != 1 or start.get('type') != 'start' or not isinstance(start.get('requestId'), str):
|
|
28
|
+
return 2
|
|
29
|
+
request_id = start['requestId']
|
|
30
|
+
command = start['command']
|
|
31
|
+
if not isinstance(command, str) or not command or not isinstance(start.get('messages'), list):
|
|
32
|
+
raise ValueError('Invalid bridge request')
|
|
33
|
+
resolved = _resolve([command], start['env'])
|
|
34
|
+
if resolved is None:
|
|
35
|
+
raise ValueError('Missing Claude Code CLI')
|
|
36
|
+
version = subprocess.run(resolved + ['--version'], env=_child_env(start['env']), stdin=subprocess.DEVNULL,
|
|
37
|
+
capture_output=True, text=True, timeout=20, check=True).stdout.strip()
|
|
38
|
+
if not re.match(r'^2\.1\.263(?:\s|$)', version):
|
|
39
|
+
raise ValueError('Unqualified Claude Code version')
|
|
40
|
+
client = Client(command=command, env=start['env'], timeout=start.get('timeout', 180),
|
|
41
|
+
on_spawn=lambda pid: emit(request_id, 'native_started', pid=pid),
|
|
42
|
+
on_replay=lambda: emit(request_id, 'replay_progress'))
|
|
43
|
+
except Exception:
|
|
44
|
+
return 2
|
|
45
|
+
|
|
46
|
+
cancelled = threading.Event()
|
|
47
|
+
|
|
48
|
+
def read_control():
|
|
49
|
+
while True:
|
|
50
|
+
control = sys.stdin.buffer.readline(MAX_RECORD + 1)
|
|
51
|
+
if not control:
|
|
52
|
+
return
|
|
53
|
+
try:
|
|
54
|
+
record = json.loads(control)
|
|
55
|
+
if record == {'v': 1, 'requestId': request_id, 'type': 'cancel'}:
|
|
56
|
+
cancelled.set()
|
|
57
|
+
client.close()
|
|
58
|
+
return
|
|
59
|
+
except (ValueError, UnicodeError):
|
|
60
|
+
cancelled.set()
|
|
61
|
+
client.close()
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
threading.Thread(target=read_control, daemon=True).start()
|
|
65
|
+
emit(request_id, 'ready')
|
|
66
|
+
try:
|
|
67
|
+
with client.create(model=start['model'], messages=start['messages'], tools=start['tools'],
|
|
68
|
+
extra_body=start.get('extraBody', {}), stream=True) as stream:
|
|
69
|
+
emit(request_id, 'start')
|
|
70
|
+
for chunk in stream:
|
|
71
|
+
delta = chunk.choices[0].delta
|
|
72
|
+
if delta.content:
|
|
73
|
+
emit(request_id, 'text_delta', text=delta.content)
|
|
74
|
+
if getattr(delta, 'reasoning_content', None):
|
|
75
|
+
emit(request_id, 'thinking_delta', text=delta.reasoning_content)
|
|
76
|
+
if hasattr(chunk, '_response'):
|
|
77
|
+
response = chunk._response.model_dump()
|
|
78
|
+
emit(request_id, 'complete', response=response)
|
|
79
|
+
return 0
|
|
80
|
+
except Exception:
|
|
81
|
+
emit(request_id, 'cancelled' if cancelled.is_set() else 'error',
|
|
82
|
+
message='Claude Code request failed. Check subscription setup and native CLI compatibility.')
|
|
83
|
+
return 1
|
|
84
|
+
finally:
|
|
85
|
+
client.close()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == '__main__':
|
|
89
|
+
sys.exit(main())
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Explicit setup probes. Never imported during normal model requests."""
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import subprocess
|
|
5
|
+
import hashlib
|
|
6
|
+
import os
|
|
7
|
+
from directsdk_setup import setup_status, discover_models, _resolve, _child_env
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
if len(sys.argv) == 3 and sys.argv[1] == 'auth-login':
|
|
12
|
+
try:
|
|
13
|
+
resolved = _resolve([sys.argv[2]], dict(os.environ))
|
|
14
|
+
if resolved is None:
|
|
15
|
+
return 1
|
|
16
|
+
return subprocess.run(resolved + ['auth', 'login'], timeout=300, check=False).returncode
|
|
17
|
+
except (OSError, subprocess.SubprocessError):
|
|
18
|
+
return 1
|
|
19
|
+
try:
|
|
20
|
+
request = json.loads(sys.stdin.readline(65537))
|
|
21
|
+
if request.get('v') != 1 or request.get('type') not in ('status', 'discover', 'version') or not isinstance(request.get('command'), str):
|
|
22
|
+
return 2
|
|
23
|
+
command = [request['command']]
|
|
24
|
+
env = request['env']
|
|
25
|
+
if request['type'] == 'version':
|
|
26
|
+
resolved = _resolve(command, env)
|
|
27
|
+
if resolved is None:
|
|
28
|
+
return 1
|
|
29
|
+
result = subprocess.run(resolved + ['--version'], env=_child_env(env), stdin=subprocess.DEVNULL,
|
|
30
|
+
capture_output=True, text=True, timeout=20, check=True).stdout.strip()
|
|
31
|
+
elif request['type'] == 'status':
|
|
32
|
+
result = setup_status(command=command, env=env)
|
|
33
|
+
if result['logged_in']:
|
|
34
|
+
auth = subprocess.run(_resolve(command, env) + ['auth', 'status'], env=_child_env(env),
|
|
35
|
+
stdin=subprocess.DEVNULL, capture_output=True, text=True, timeout=20, check=True)
|
|
36
|
+
account = json.loads(auth.stdout)
|
|
37
|
+
email = account.get('email', '')
|
|
38
|
+
if isinstance(email, str) and email:
|
|
39
|
+
result['accountFingerprint'] = hashlib.sha256(email.strip().lower().encode()).hexdigest()
|
|
40
|
+
else:
|
|
41
|
+
result = discover_models(command=command, env=env)
|
|
42
|
+
sys.stdout.write(json.dumps({'v': 1, 'requestId': request['requestId'], 'type': 'result', 'result': result}) + '\n')
|
|
43
|
+
return 0
|
|
44
|
+
except Exception:
|
|
45
|
+
return 1
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == '__main__':
|
|
49
|
+
sys.exit(main())
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"upstream": "https://github.com/NousResearch/hermes-plugin-claude-subscription-directsdk",
|
|
3
|
+
"commit": "f1c1220778c7864fe4c1494baf9b1566e7c95bd2",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"upstreamFiles": {
|
|
6
|
+
"LICENSE": "d1410a18033e51061f8a81279632cfe5d7e8041af30be9810a750f96edf1e0d5",
|
|
7
|
+
"README.md": "4c2057d1e6cb19fa078f439933b8f73d85a0f183c6651f66440e4e9d4d58e511",
|
|
8
|
+
"__init__.py": "01654271c9d9802122c7577ec3933c64330148e91aadd12fd711eff34cd9f7a4",
|
|
9
|
+
"admission.py": "a6841b870887eecc84501ab358891356ee2bb39431de68291ffd890028d82ac3",
|
|
10
|
+
"conftest.py": "64515322aea459826b85d0255c85779a2b11ef31a42a213b4602469d386627d4",
|
|
11
|
+
"directsdk.py": "d5d3d60ca59418697eaf08640eb445c25dc41ac64ee827b79008e06e8da2df54",
|
|
12
|
+
"directsdk_setup.py": "7b3499aea9405ff3f98a7fa25aad22053b1a89ce286d4e0e2841a880fc0dbe8c",
|
|
13
|
+
"evals/directsdk_admission.py": "58a7bde324a41c26889c910170f8766479b6c98e1a2c4b088b8bb69bd9cddb9a",
|
|
14
|
+
"evals/directsdk_cache_wire.py": "089cb380386cfe28fadd7c720e7ba609a6cb1ad2cf5a320a617b61a6ad7cfc94",
|
|
15
|
+
"inert_mcp.py": "6c5a0f64e4470ae90c6171bd83e2b537f641ff6d06cd76b3d49edba227274cb6",
|
|
16
|
+
"model_catalog.py": "5e4fa7ed95eedf243b46524abbd9771f8c4ba756cc0208f1fa9a5768a1281077",
|
|
17
|
+
"plugin.yaml": "6e2edc160c8a928e3393bc4e479589dfddee5291814ae930317086f2b0275067",
|
|
18
|
+
"pyproject.toml": "e041f415bf92d7a3ed71c40f5a1732be9e629000c6b46479f9036ebde3071453",
|
|
19
|
+
"tests/test_directsdk.py": "884c960c1b88c8727bbd1cc70598d509dd435ba02296bad2a805b23f717bdc91",
|
|
20
|
+
"tests/test_directsdk_admission.py": "a52d64907973d32e91dbc890f42e67d191a7b7d283b042f4d278968130385251",
|
|
21
|
+
"tests/test_directsdk_discovery.py": "279a5a0723f3327439238d7a682602627d1aceccc2e45ee9122159cfeb292f16",
|
|
22
|
+
"tests/test_directsdk_models.py": "b13ccf8b53d9748e41490940ce9fd092a8bf5069f2fa2e44f09cbf0ad49af044",
|
|
23
|
+
"tests/test_directsdk_replay.py": "9894ab096289142f84785533d728dcac0b721720fc03b1c5f4c5e509533adee3",
|
|
24
|
+
"tests/test_directsdk_setup.py": "82552e84aa4a0cf0a16c3a70e4418c9edfa85a27a89821b79cf8fb957e8f91f3"
|
|
25
|
+
},
|
|
26
|
+
"patchedFiles": {
|
|
27
|
+
"directsdk.py": "42b8eef95cc313ad0314083a25fd534603adb56cb86c57987ed5e67d31141e73"
|
|
28
|
+
},
|
|
29
|
+
"patches": {
|
|
30
|
+
"patches/0001-lunr-lifecycle-hooks.patch": "0c19a60c69f15745e7076a1791988408feb32611ae29156ab28a2670a50fedb5"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Pinned native routes; a loopback gateway needs explicit long-context selection."""
|
|
2
|
+
CONTEXT_WINDOWS = {
|
|
3
|
+
'claude-sonnet-5': 1_000_000,
|
|
4
|
+
'claude-haiku-4-5-20251001': 200_000,
|
|
5
|
+
'claude-opus-5': 1_000_000,
|
|
6
|
+
'claude-opus-4-8': 1_000_000,
|
|
7
|
+
'claude-fable-5-1': 1_000_000,
|
|
8
|
+
}
|
|
9
|
+
ALIASES = {
|
|
10
|
+
'sonnet': 'claude-sonnet-5',
|
|
11
|
+
'haiku': 'claude-haiku-4-5-20251001',
|
|
12
|
+
'claude-haiku-4-5': 'claude-haiku-4-5-20251001',
|
|
13
|
+
'opus': 'claude-opus-5',
|
|
14
|
+
'fable': 'claude-fable-5-1',
|
|
15
|
+
}
|
|
16
|
+
# Haiku 4.5 rejects `thinking: {'type': 'adaptive'}` with a 400 upstream; its effort signal
|
|
17
|
+
# still applies. Unknown routes keep adaptive so future models are not silently downgraded.
|
|
18
|
+
NO_ADAPTIVE_THINKING = frozenset({'claude-haiku-4-5-20251001'})
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def native_model(model):
|
|
22
|
+
base = model.removesuffix('[1m]')
|
|
23
|
+
canonical = ALIASES.get(base, base)
|
|
24
|
+
window = CONTEXT_WINDOWS.get(canonical)
|
|
25
|
+
if window == 1_000_000:
|
|
26
|
+
return canonical + '[1m]'
|
|
27
|
+
if window == 200_000:
|
|
28
|
+
if model.endswith('[1m]'):
|
|
29
|
+
raise ValueError('Haiku 4.5 does not support a 1M context window')
|
|
30
|
+
return canonical
|
|
31
|
+
return model
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def supports_adaptive_thinking(model):
|
|
35
|
+
# Body assembly precedes model validation, so an absent or invalid route answers True
|
|
36
|
+
# and still reaches the existing `model is required` error.
|
|
37
|
+
base = model.removesuffix('[1m]') if isinstance(model, str) else ''
|
|
38
|
+
return ALIASES.get(base, base) not in NO_ADAPTIVE_THINKING
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
MODEL_METADATA = {
|
|
42
|
+
native_model(model): {'canonical_model': model, 'context_window': window}
|
|
43
|
+
for model, window in CONTEXT_WINDOWS.items()
|
|
44
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
--- a/directsdk.py
|
|
2
|
+
+++ b/directsdk.py
|
|
3
|
+
@@ -269,0 +270,2 @@
|
|
4
|
+
+ if self.client.on_spawn is not None:
|
|
5
|
+
+ self.client.on_spawn(self.process.pid)
|
|
6
|
+
@@ -368 +370 @@
|
|
7
|
+
- def __init__(self, command=None, args=None, env=None, timeout=180, **_):
|
|
8
|
+
+ def __init__(self, command=None, args=None, env=None, timeout=180, on_spawn=None, on_replay=None, **_):
|
|
9
|
+
@@ -376,0 +379,2 @@
|
|
10
|
+
+ self.on_spawn = on_spawn
|
|
11
|
+
+ self.on_replay = on_replay
|
|
12
|
+
@@ -525,0 +530,2 @@
|
|
13
|
+
+ if self.on_replay is not None:
|
|
14
|
+
+ self.on_replay()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
def strip_nullable_unions(schema, *, keep_nullable_hint=True):
|
|
2
|
+
def rewrite(value):
|
|
3
|
+
if isinstance(value, list):
|
|
4
|
+
return [rewrite(item) for item in value]
|
|
5
|
+
if not isinstance(value, dict):
|
|
6
|
+
return value
|
|
7
|
+
out = {key: rewrite(item) for key, item in value.items()}
|
|
8
|
+
for key in ('anyOf', 'oneOf'):
|
|
9
|
+
variants = out.get(key)
|
|
10
|
+
if not isinstance(variants, list):
|
|
11
|
+
continue
|
|
12
|
+
non_null = [item for item in variants if not isinstance(item, dict) or item.get('type') != 'null']
|
|
13
|
+
if len(non_null) != 1 or len(non_null) == len(variants):
|
|
14
|
+
continue
|
|
15
|
+
replacement = dict(non_null[0]) if isinstance(non_null[0], dict) else {}
|
|
16
|
+
if keep_nullable_hint:
|
|
17
|
+
replacement.setdefault('nullable', True)
|
|
18
|
+
for meta in ('title', 'description', 'default', 'examples'):
|
|
19
|
+
if meta in out and meta not in replacement and not (meta == 'default' and '$ref' in replacement):
|
|
20
|
+
replacement[meta] = out[meta]
|
|
21
|
+
return rewrite(replacement)
|
|
22
|
+
return out
|
|
23
|
+
return rewrite(schema)
|