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.
@@ -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,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: blade-agent-kit
3
+ Version: 0.4.1
4
+ Summary: REST client for Blade Agent (AgentKit Python, v1 without Socket.IO)
5
+ Requires-Python: <3.13,>=3.12
6
+ Requires-Dist: httpx>=0.28.1
@@ -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,3 @@
1
+ from blade_agent_kit.client import BladeAgentClient
2
+
3
+ __all__ = ["BladeAgentClient"]
@@ -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")