blade-agent-kit 0.4.1__tar.gz
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.
- blade_agent_kit-0.4.1/.gitignore +55 -0
- blade_agent_kit-0.4.1/PKG-INFO +6 -0
- blade_agent_kit-0.4.1/pyproject.toml +13 -0
- blade_agent_kit-0.4.1/src/blade_agent_kit/__init__.py +3 -0
- blade_agent_kit-0.4.1/src/blade_agent_kit/client.py +138 -0
- blade_agent_kit-0.4.1/tests/test_list_sessions.py +35 -0
- blade_agent_kit-0.4.1/tests/test_ls_path.py +16 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# === 环境与密钥 ===
|
|
2
|
+
.env
|
|
3
|
+
.envrc
|
|
4
|
+
configs/oauth_config.yaml
|
|
5
|
+
|
|
6
|
+
# === IDE / 编辑器 ===
|
|
7
|
+
.vscode/
|
|
8
|
+
.idea/
|
|
9
|
+
.cursorignore
|
|
10
|
+
.cursorindexingignore
|
|
11
|
+
|
|
12
|
+
# === OS ===
|
|
13
|
+
.DS_Store
|
|
14
|
+
Thumbs.db
|
|
15
|
+
|
|
16
|
+
# === 日志 ===
|
|
17
|
+
*.log
|
|
18
|
+
|
|
19
|
+
# === 本地数据 ===
|
|
20
|
+
/workspace
|
|
21
|
+
tmp_e2e_debug*/
|
|
22
|
+
web/workspace/
|
|
23
|
+
web/pnpm-*.tgz
|
|
24
|
+
examples/react-sdk-example/vendor/
|
|
25
|
+
examples/react-sdk-example/node_modules/
|
|
26
|
+
.context/
|
|
27
|
+
progress.md
|
|
28
|
+
task_plan.md
|
|
29
|
+
findings.md
|
|
30
|
+
|
|
31
|
+
# === Python ===
|
|
32
|
+
__pycache__/
|
|
33
|
+
*.py[codz]
|
|
34
|
+
*$py.class
|
|
35
|
+
*.so
|
|
36
|
+
*.egg-info/
|
|
37
|
+
*.egg
|
|
38
|
+
dist/
|
|
39
|
+
build/
|
|
40
|
+
.venv
|
|
41
|
+
.pytest_cache/
|
|
42
|
+
.coverage
|
|
43
|
+
.coverage.*
|
|
44
|
+
htmlcov/
|
|
45
|
+
.ruff_cache/
|
|
46
|
+
.mypy_cache/
|
|
47
|
+
.pytype/
|
|
48
|
+
|
|
49
|
+
**/node_modules/*
|
|
50
|
+
**/.pnpm-store/
|
|
51
|
+
.claude/settings.local.json
|
|
52
|
+
|
|
53
|
+
# === Tokenizer 本地缓存 ===
|
|
54
|
+
host/src/blade_agent/host/tokenize/data/**/tokenizer.json
|
|
55
|
+
.claude/scheduled_tasks.lock
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "blade-agent-kit"
|
|
3
|
+
version = "0.4.1"
|
|
4
|
+
description = "REST client for Blade Agent (AgentKit Python, v1 without Socket.IO)"
|
|
5
|
+
requires-python = ">=3.12,<3.13"
|
|
6
|
+
dependencies = ["httpx>=0.28.1"]
|
|
7
|
+
|
|
8
|
+
[build-system]
|
|
9
|
+
requires = ["hatchling"]
|
|
10
|
+
build-backend = "hatchling.build"
|
|
11
|
+
|
|
12
|
+
[tool.hatch.build.targets.wheel]
|
|
13
|
+
packages = ["src/blade_agent_kit"]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Minimal REST client for Blade Agent (v1: no Socket.IO)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BladeAgentClient:
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
base_url: str,
|
|
14
|
+
*,
|
|
15
|
+
token: str | None = None,
|
|
16
|
+
timeout: float = 60.0,
|
|
17
|
+
) -> None:
|
|
18
|
+
self._base = base_url.rstrip("/")
|
|
19
|
+
self._token = token
|
|
20
|
+
self._timeout = timeout
|
|
21
|
+
|
|
22
|
+
def _headers(self) -> dict[str, str]:
|
|
23
|
+
h: dict[str, str] = {"Accept": "application/json"}
|
|
24
|
+
if self._token:
|
|
25
|
+
h["Authorization"] = f"Bearer {self._token}"
|
|
26
|
+
return h
|
|
27
|
+
|
|
28
|
+
def _client(self) -> httpx.Client:
|
|
29
|
+
return httpx.Client(
|
|
30
|
+
base_url=self._base,
|
|
31
|
+
timeout=self._timeout,
|
|
32
|
+
headers=self._headers(),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def health(self) -> dict[str, Any]:
|
|
36
|
+
with self._client() as c:
|
|
37
|
+
r = c.get("/api/health")
|
|
38
|
+
r.raise_for_status()
|
|
39
|
+
return r.json()
|
|
40
|
+
|
|
41
|
+
def list_sessions(
|
|
42
|
+
self,
|
|
43
|
+
*,
|
|
44
|
+
limit: int = 100,
|
|
45
|
+
offset: int = 0,
|
|
46
|
+
template_id_prefix: str | None = None,
|
|
47
|
+
q: str | None = None,
|
|
48
|
+
) -> list[dict[str, Any]]:
|
|
49
|
+
"""Return session items from paginated GET /api/sessions."""
|
|
50
|
+
params: dict[str, Any] = {"limit": limit, "offset": offset}
|
|
51
|
+
if template_id_prefix:
|
|
52
|
+
params["template_id_prefix"] = template_id_prefix
|
|
53
|
+
if q:
|
|
54
|
+
params["q"] = q.strip()
|
|
55
|
+
with self._client() as c:
|
|
56
|
+
r = c.get("/api/sessions", params=params)
|
|
57
|
+
r.raise_for_status()
|
|
58
|
+
payload = r.json()
|
|
59
|
+
if isinstance(payload, list):
|
|
60
|
+
return payload
|
|
61
|
+
if isinstance(payload, dict):
|
|
62
|
+
items = payload.get("items")
|
|
63
|
+
if isinstance(items, list):
|
|
64
|
+
return items
|
|
65
|
+
raise TypeError(f"unexpected /api/sessions response: {type(payload).__name__}")
|
|
66
|
+
|
|
67
|
+
def list_sessions_page(
|
|
68
|
+
self,
|
|
69
|
+
*,
|
|
70
|
+
limit: int = 20,
|
|
71
|
+
offset: int = 0,
|
|
72
|
+
template_id_prefix: str | None = None,
|
|
73
|
+
q: str | None = None,
|
|
74
|
+
) -> dict[str, Any]:
|
|
75
|
+
"""Return full paginated payload: items, total, limit, offset."""
|
|
76
|
+
params: dict[str, Any] = {"limit": limit, "offset": offset}
|
|
77
|
+
if template_id_prefix:
|
|
78
|
+
params["template_id_prefix"] = template_id_prefix
|
|
79
|
+
if q:
|
|
80
|
+
params["q"] = q.strip()
|
|
81
|
+
with self._client() as c:
|
|
82
|
+
r = c.get("/api/sessions", params=params)
|
|
83
|
+
r.raise_for_status()
|
|
84
|
+
payload = r.json()
|
|
85
|
+
if not isinstance(payload, dict):
|
|
86
|
+
raise TypeError(f"unexpected /api/sessions response: {type(payload).__name__}")
|
|
87
|
+
return payload
|
|
88
|
+
|
|
89
|
+
def get_session(self, session_id: str) -> dict[str, Any]:
|
|
90
|
+
with self._client() as c:
|
|
91
|
+
r = c.get(f"/api/sessions/{session_id}")
|
|
92
|
+
r.raise_for_status()
|
|
93
|
+
return r.json()
|
|
94
|
+
|
|
95
|
+
def get_history(self, session_id: str) -> dict[str, Any]:
|
|
96
|
+
with self._client() as c:
|
|
97
|
+
r = c.get(f"/api/sessions/{session_id}/history")
|
|
98
|
+
r.raise_for_status()
|
|
99
|
+
return r.json()
|
|
100
|
+
|
|
101
|
+
def create_session(self, intent: str = "") -> dict[str, Any]:
|
|
102
|
+
with self._client() as c:
|
|
103
|
+
r = c.post("/api/sessions", json={"intent": intent})
|
|
104
|
+
r.raise_for_status()
|
|
105
|
+
return r.json()
|
|
106
|
+
|
|
107
|
+
def delete_session(self, session_id: str) -> dict[str, Any]:
|
|
108
|
+
with self._client() as c:
|
|
109
|
+
r = c.delete(f"/api/sessions/{session_id}")
|
|
110
|
+
r.raise_for_status()
|
|
111
|
+
return r.json()
|
|
112
|
+
|
|
113
|
+
def list_dir(self, session_id: str, dir_path: str = ".") -> list[dict[str, Any]]:
|
|
114
|
+
with self._client() as c:
|
|
115
|
+
r = c.get(f"/api/sessions/{session_id}/ls/{_encode_ls_path(dir_path)}")
|
|
116
|
+
r.raise_for_status()
|
|
117
|
+
return r.json()
|
|
118
|
+
|
|
119
|
+
def get_checkpoints(self, session_id: str) -> dict[str, Any]:
|
|
120
|
+
with self._client() as c:
|
|
121
|
+
r = c.get(f"/api/sessions/{session_id}/checkpoints")
|
|
122
|
+
r.raise_for_status()
|
|
123
|
+
return r.json()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _encode_ls_path(dir_path: str) -> str:
|
|
127
|
+
"""Encode path for GET /api/sessions/{id}/ls/{dir_path}.
|
|
128
|
+
|
|
129
|
+
Root ``.`` must be sent as ``%2E``: ``/ls/.`` is normalized to ``/ls`` by HTTP
|
|
130
|
+
clients and does not match the server route (404). Aligns with browser
|
|
131
|
+
``encodeURIComponent`` for nested paths (``/`` → ``%2F``).
|
|
132
|
+
"""
|
|
133
|
+
from urllib.parse import quote
|
|
134
|
+
|
|
135
|
+
normalized = (dir_path or ".").strip()
|
|
136
|
+
if normalized == ".":
|
|
137
|
+
return "%2E"
|
|
138
|
+
return quote(normalized, safe="")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""list_sessions pagination parsing."""
|
|
2
|
+
|
|
3
|
+
from blade_agent_kit.client import BladeAgentClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_list_sessions_extracts_items(monkeypatch) -> None:
|
|
7
|
+
class FakeResponse:
|
|
8
|
+
def raise_for_status(self) -> None:
|
|
9
|
+
return None
|
|
10
|
+
|
|
11
|
+
def json(self) -> dict:
|
|
12
|
+
return {
|
|
13
|
+
"items": [{"id": "s1", "intent": "a"}],
|
|
14
|
+
"total": 1,
|
|
15
|
+
"limit": 20,
|
|
16
|
+
"offset": 0,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class FakeClient:
|
|
20
|
+
def get(self, path: str, params: dict | None = None) -> FakeResponse:
|
|
21
|
+
assert path == "/api/sessions"
|
|
22
|
+
assert params == {"limit": 100, "offset": 0}
|
|
23
|
+
return FakeResponse()
|
|
24
|
+
|
|
25
|
+
def __enter__(self):
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
def __exit__(self, *args: object) -> None:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
client = BladeAgentClient("http://test")
|
|
32
|
+
monkeypatch.setattr(client, "_client", lambda: FakeClient())
|
|
33
|
+
|
|
34
|
+
sessions = client.list_sessions()
|
|
35
|
+
assert sessions == [{"id": "s1", "intent": "a"}]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""blade-agent-kit client helpers."""
|
|
2
|
+
|
|
3
|
+
from blade_agent_kit.client import BladeAgentClient, _encode_ls_path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_encode_ls_path_root_dot() -> None:
|
|
7
|
+
assert _encode_ls_path(".") == "%2E"
|
|
8
|
+
assert _encode_ls_path("") == "%2E"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_encode_ls_path_nested() -> None:
|
|
12
|
+
assert _encode_ls_path("workspace/foo") == "workspace%2Ffoo"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_client_has_get_checkpoints() -> None:
|
|
16
|
+
assert hasattr(BladeAgentClient, "get_checkpoints")
|