pi-sidecar-client 0.1.0.dev2__tar.gz → 0.1.0.dev4__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.
- {pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/PKG-INFO +1 -1
- {pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/pi_sidecar_client/__init__.py +28 -12
- {pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/pi_sidecar_client.egg-info/PKG-INFO +1 -1
- {pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/pyproject.toml +1 -1
- {pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/LICENSE +0 -0
- {pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/README.md +0 -0
- {pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/pi_sidecar_client.egg-info/SOURCES.txt +0 -0
- {pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/pi_sidecar_client.egg-info/dependency_links.txt +0 -0
- {pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/pi_sidecar_client.egg-info/requires.txt +0 -0
- {pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/pi_sidecar_client.egg-info/top_level.txt +0 -0
- {pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/setup.cfg +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import inspect
|
|
3
3
|
import os
|
|
4
|
+
import tempfile
|
|
4
5
|
from collections.abc import Callable
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from typing import Any
|
|
@@ -11,6 +12,7 @@ from simple_logger.logger import get_logger
|
|
|
11
12
|
logger = get_logger(name=__name__, level=os.environ.get("LOG_LEVEL", "INFO"))
|
|
12
13
|
|
|
13
14
|
SIDECAR_URL = os.environ.get("SIDECAR_URL", "http://127.0.0.1:9100")
|
|
15
|
+
DEFAULT_CWD = tempfile.gettempdir()
|
|
14
16
|
|
|
15
17
|
__all__ = [
|
|
16
18
|
"AIResult",
|
|
@@ -48,7 +50,8 @@ _usage_recorder: Callable | None = None
|
|
|
48
50
|
def set_usage_recorder(callback: Callable) -> None:
|
|
49
51
|
"""Register a callback for recording AI token usage.
|
|
50
52
|
|
|
51
|
-
The callback
|
|
53
|
+
The callback may be sync or async:
|
|
54
|
+
def recorder(*, request_id, result, call_type, prompt_chars, ai_provider, ai_model)
|
|
52
55
|
async def recorder(*, request_id, result, call_type, prompt_chars, ai_provider, ai_model)
|
|
53
56
|
"""
|
|
54
57
|
global _usage_recorder
|
|
@@ -115,6 +118,7 @@ class SidecarClient:
|
|
|
115
118
|
def __init__(self, base_url: str = SIDECAR_URL):
|
|
116
119
|
self._base_url = base_url.rstrip("/")
|
|
117
120
|
self._client = httpx.AsyncClient(base_url=self._base_url, timeout=600.0)
|
|
121
|
+
self._closed = False
|
|
118
122
|
|
|
119
123
|
async def health(self) -> dict:
|
|
120
124
|
"""Check sidecar health."""
|
|
@@ -140,7 +144,7 @@ class SidecarClient:
|
|
|
140
144
|
provider: str,
|
|
141
145
|
model: str,
|
|
142
146
|
system_prompt: str,
|
|
143
|
-
cwd: str =
|
|
147
|
+
cwd: str = DEFAULT_CWD,
|
|
144
148
|
custom_tools: list | None = None,
|
|
145
149
|
) -> str:
|
|
146
150
|
"""Create a new AI session. Returns session_id."""
|
|
@@ -202,6 +206,7 @@ class SidecarClient:
|
|
|
202
206
|
async def close(self) -> None:
|
|
203
207
|
"""Close the HTTP client."""
|
|
204
208
|
await self._client.aclose()
|
|
209
|
+
self._closed = True
|
|
205
210
|
|
|
206
211
|
|
|
207
212
|
# Singleton client
|
|
@@ -211,7 +216,7 @@ _client: SidecarClient | None = None
|
|
|
211
216
|
def get_sidecar_client() -> SidecarClient:
|
|
212
217
|
"""Get the singleton sidecar client."""
|
|
213
218
|
global _client
|
|
214
|
-
if _client is None:
|
|
219
|
+
if _client is None or _client._closed:
|
|
215
220
|
_client = SidecarClient()
|
|
216
221
|
return _client
|
|
217
222
|
|
|
@@ -250,7 +255,7 @@ async def call_ai(
|
|
|
250
255
|
provider=ai_provider,
|
|
251
256
|
model=ai_model,
|
|
252
257
|
system_prompt=system_prompt or "You are a helpful assistant.",
|
|
253
|
-
cwd=cwd or
|
|
258
|
+
cwd=cwd or DEFAULT_CWD,
|
|
254
259
|
custom_tools=custom_tools,
|
|
255
260
|
)
|
|
256
261
|
created_session = True
|
|
@@ -289,9 +294,10 @@ async def call_ai_once(
|
|
|
289
294
|
) -> AIResult:
|
|
290
295
|
"""Single-shot AI call with automatic session cleanup.
|
|
291
296
|
|
|
292
|
-
Creates a session, sends the prompt, and
|
|
293
|
-
|
|
294
|
-
|
|
297
|
+
Creates a session, sends the prompt, and deletes the session.
|
|
298
|
+
Cleanup is best-effort — if deletion fails, result.session_id is
|
|
299
|
+
preserved so the caller can retry cleanup.
|
|
300
|
+
Use ``call_ai`` directly for multi-turn conversations.
|
|
295
301
|
"""
|
|
296
302
|
result = await call_ai(
|
|
297
303
|
prompt,
|
|
@@ -327,13 +333,20 @@ async def check_sidecar_available() -> tuple[bool, str]:
|
|
|
327
333
|
"""Check if the sidecar service is available and ready."""
|
|
328
334
|
try:
|
|
329
335
|
client = get_sidecar_client()
|
|
330
|
-
|
|
331
|
-
data
|
|
332
|
-
if resp.status_code == 200 and data.get("status") == "ok":
|
|
336
|
+
data = await client.health()
|
|
337
|
+
if data.get("status") == "ok":
|
|
333
338
|
return True, "Sidecar is ready"
|
|
334
|
-
if
|
|
339
|
+
if data.get("status") == "starting":
|
|
335
340
|
return False, f"Sidecar starting: {data.get('message', 'model discovery in progress')}"
|
|
336
|
-
return False, f"Sidecar unhealthy
|
|
341
|
+
return False, f"Sidecar unhealthy: {data}"
|
|
342
|
+
except httpx.HTTPStatusError as e:
|
|
343
|
+
if e.response.status_code == 503:
|
|
344
|
+
try:
|
|
345
|
+
data = e.response.json()
|
|
346
|
+
return False, f"Sidecar starting: {data.get('message', 'model discovery in progress')}"
|
|
347
|
+
except ValueError:
|
|
348
|
+
pass
|
|
349
|
+
return False, f"Sidecar unhealthy (HTTP {e.response.status_code})"
|
|
337
350
|
except Exception as e:
|
|
338
351
|
return False, f"Sidecar unavailable: {e}"
|
|
339
352
|
|
|
@@ -343,6 +356,9 @@ async def run_parallel_with_limit(
|
|
|
343
356
|
max_concurrency: int = 5,
|
|
344
357
|
) -> list:
|
|
345
358
|
"""Run async tasks in parallel with concurrency limit."""
|
|
359
|
+
if max_concurrency < 1:
|
|
360
|
+
raise ValueError("max_concurrency must be >= 1")
|
|
361
|
+
|
|
346
362
|
semaphore = asyncio.Semaphore(max_concurrency)
|
|
347
363
|
|
|
348
364
|
async def limited(coro):
|
|
File without changes
|
|
File without changes
|
{pi_sidecar_client-0.1.0.dev2 → pi_sidecar_client-0.1.0.dev4}/pi_sidecar_client.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|