flare-kernel-client-py 0.1.0__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,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: flare-kernel-client-py
3
+ Version: 0.1.0
4
+ Summary: FLARE kernel client package
5
+ License: Proprietary
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: httpx<1.0,>=0.27
12
+
13
+ # flare-kernel-client-py
14
+
15
+ FLARE Kernel 流式客户端与 SSE 解析包。
16
+
17
+ 职责:
18
+
19
+ 1. 连接 Kernel SSE 端点。
20
+ 2. 解析 `event:` / `data:` 行。
21
+ 3. 不承载业务字段、不承载 UI。
22
+
23
+ 导出:
24
+
25
+ 1. `KernelStreamAdapter`
26
+ 2. `SSELineParser`
@@ -0,0 +1,14 @@
1
+ # flare-kernel-client-py
2
+
3
+ FLARE Kernel 流式客户端与 SSE 解析包。
4
+
5
+ 职责:
6
+
7
+ 1. 连接 Kernel SSE 端点。
8
+ 2. 解析 `event:` / `data:` 行。
9
+ 3. 不承载业务字段、不承载 UI。
10
+
11
+ 导出:
12
+
13
+ 1. `KernelStreamAdapter`
14
+ 2. `SSELineParser`
@@ -0,0 +1,25 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "flare-kernel-client-py"
7
+ version = "0.1.0"
8
+ description = "FLARE kernel client package"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = [
12
+ "httpx>=0.27,<1.0",
13
+ ]
14
+ license = { text = "Proprietary" }
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.11",
19
+ ]
20
+
21
+ [tool.setuptools]
22
+ package-dir = {"" = "src"}
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from .kernel_stream_adapter import KernelStreamAdapter
2
+ from .sse_parser import SSELineParser
3
+
4
+ __all__ = ["KernelStreamAdapter", "SSELineParser"]
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import Any, AsyncGenerator
5
+
6
+ from .sse_parser import SSELineParser
7
+
8
+
9
+ def _read_env(name: str) -> str | None:
10
+ value = os.getenv(name)
11
+ if value is None:
12
+ return None
13
+ normalized = value.strip()
14
+ return normalized or None
15
+
16
+
17
+ def _resolve_required_value(explicit: str | None, env_name: str) -> str:
18
+ candidate = (explicit or _read_env(env_name) or "").strip()
19
+ if not candidate:
20
+ raise ValueError(f"{env_name} is required for KernelStreamAdapter")
21
+ return candidate
22
+
23
+
24
+ class KernelStreamAdapter:
25
+ """Generic adapter for streaming events from a Kernel SSE endpoint."""
26
+
27
+ def __init__(
28
+ self,
29
+ base_url: str | None = None,
30
+ path: str | None = None,
31
+ timeout: float = 60.0,
32
+ ) -> None:
33
+ self.base_url = _resolve_required_value(base_url, "KERNEL_BASE_URL").rstrip("/")
34
+ self.path = _resolve_required_value(path, "KERNEL_STREAM_PATH")
35
+ self.timeout = timeout
36
+
37
+ def build_stream_url(self) -> str:
38
+ return f"{self.base_url}/{self.path.lstrip('/')}"
39
+
40
+ async def stream_chat(
41
+ self,
42
+ session_id: str,
43
+ message: str,
44
+ enabled_tools: list[str] | None = None,
45
+ ) -> AsyncGenerator[dict[str, Any], None]:
46
+ import httpx
47
+
48
+ payload = {
49
+ "message": message,
50
+ "content": message,
51
+ "session_id": session_id,
52
+ "enabled_tools": enabled_tools or [],
53
+ "enabled_capabilities": enabled_tools or [],
54
+ }
55
+ parser = SSELineParser()
56
+
57
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
58
+ async with client.stream(
59
+ "POST",
60
+ self.build_stream_url(),
61
+ json=payload,
62
+ headers={"Accept": "text/event-stream"},
63
+ ) as response:
64
+ if response.status_code != 200:
65
+ error_text = await response.aread()
66
+ raise Exception(
67
+ f"Kernel returned error {response.status_code}: "
68
+ f"{error_text.decode()}"
69
+ )
70
+
71
+ async for line in response.aiter_lines():
72
+ event = parser.feed_line(line)
73
+ if event is not None:
74
+ yield event
75
+
76
+
77
+ __all__ = ["KernelStreamAdapter"]
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+
7
+ class SSELineParser:
8
+ """Minimal SSE text line parser.
9
+
10
+ It keeps only the current `event:` value and turns a matching `data:` line
11
+ into a plain dict: {"type": ..., "data": ...}.
12
+ """
13
+
14
+ def __init__(self) -> None:
15
+ self._current_event: str | None = None
16
+
17
+ def reset(self) -> None:
18
+ self._current_event = None
19
+
20
+ def feed_line(self, line: str) -> dict[str, Any] | None:
21
+ if line is None:
22
+ return None
23
+
24
+ normalized = line.rstrip("\r")
25
+ if not normalized:
26
+ return None
27
+
28
+ if normalized.startswith("event:"):
29
+ self._current_event = normalized[6:].strip() or None
30
+ return None
31
+
32
+ if not normalized.startswith("data:") or not self._current_event:
33
+ return None
34
+
35
+ raw_data = normalized[5:].strip()
36
+ if not raw_data:
37
+ return None
38
+
39
+ try:
40
+ data = json.loads(raw_data)
41
+ except json.JSONDecodeError:
42
+ return None
43
+
44
+ return {"type": self._current_event, "data": data}
45
+
46
+
47
+ __all__ = ["SSELineParser"]
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: flare-kernel-client-py
3
+ Version: 0.1.0
4
+ Summary: FLARE kernel client package
5
+ License: Proprietary
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ Requires-Dist: httpx<1.0,>=0.27
12
+
13
+ # flare-kernel-client-py
14
+
15
+ FLARE Kernel 流式客户端与 SSE 解析包。
16
+
17
+ 职责:
18
+
19
+ 1. 连接 Kernel SSE 端点。
20
+ 2. 解析 `event:` / `data:` 行。
21
+ 3. 不承载业务字段、不承载 UI。
22
+
23
+ 导出:
24
+
25
+ 1. `KernelStreamAdapter`
26
+ 2. `SSELineParser`
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/flare_kernel_client/__init__.py
4
+ src/flare_kernel_client/kernel_stream_adapter.py
5
+ src/flare_kernel_client/sse_parser.py
6
+ src/flare_kernel_client_py.egg-info/PKG-INFO
7
+ src/flare_kernel_client_py.egg-info/SOURCES.txt
8
+ src/flare_kernel_client_py.egg-info/dependency_links.txt
9
+ src/flare_kernel_client_py.egg-info/requires.txt
10
+ src/flare_kernel_client_py.egg-info/top_level.txt
11
+ tests/test_basics.py
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ import flare_kernel_client
4
+
5
+ from flare_kernel_client import KernelStreamAdapter, SSELineParser
6
+
7
+
8
+ def test_package_smoke_imports_public_api() -> None:
9
+ assert hasattr(flare_kernel_client, "KernelStreamAdapter")
10
+ assert hasattr(flare_kernel_client, "SSELineParser")
11
+
12
+
13
+ def test_sse_line_parser_parses_event_data_and_resets() -> None:
14
+ parser = SSELineParser()
15
+
16
+ assert parser.feed_line('data: {"ignored": true}') is None
17
+ assert parser.feed_line("event: message") is None
18
+
19
+ event = parser.feed_line('data: {"ok": true, "count": 2}')
20
+ assert event == {"type": "message", "data": {"ok": True, "count": 2}}
21
+
22
+ parser.reset()
23
+ assert parser.feed_line('data: {"ok": true}') is None
24
+
25
+
26
+ def test_kernel_stream_adapter_build_stream_url_normalizes_slashes() -> None:
27
+ adapter = KernelStreamAdapter(
28
+ base_url="https://example.test/",
29
+ path="/kernel/stream",
30
+ timeout=12.5,
31
+ )
32
+
33
+ assert adapter.build_stream_url() == "https://example.test/kernel/stream"
34
+ assert adapter.timeout == 12.5