getstack 0.14.0__tar.gz → 0.16.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.
- {getstack-0.14.0 → getstack-0.16.0}/CLAUDE.md +1 -1
- {getstack-0.14.0 → getstack-0.16.0}/PKG-INFO +1 -1
- {getstack-0.14.0 → getstack-0.16.0}/pyproject.toml +1 -1
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/__init__.py +4 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/agents.py +14 -1
- getstack-0.16.0/src/getstack/_async/usage.py +15 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/agents.py +24 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/types.py +7 -0
- getstack-0.16.0/src/getstack/usage.py +37 -0
- {getstack-0.14.0 → getstack-0.16.0}/tests/test_parity.py +74 -2
- {getstack-0.14.0 → getstack-0.16.0}/.gitignore +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/LICENSE +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/README.md +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/scripts/verify_sign_claim_crosslang.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/__init__.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/audit.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/credentials.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/delivery_methods.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/detector_config.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/dropoffs.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/evidence_packs.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/identity.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/inbound_webhooks.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/intents.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/llm.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/missions.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/notifications.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/partner_tenants.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/passport_session.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/passports.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/proxy.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/reviews.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/scan.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/security_events.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/services.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/skills.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/_async/team.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/agent_auth.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/audit.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/auth.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/browser_bootstrap.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/client.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/credentials.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/delivery_methods.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/detector_config.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/dropoffs.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/errors.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/evidence_packs.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/identity.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/inbound_webhooks.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/intents.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/llm.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/missions.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/notifications.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/partner_tenants.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/passport_header_verify.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/passport_session.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/passports.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/proxy.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/py.typed +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/reviews.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/scan.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/security_events.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/services.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/sign_claim.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/skills.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/team.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/src/getstack/verify_offline.py +0 -0
- {getstack-0.14.0 → getstack-0.16.0}/tests/test_sign_claim.py +0 -0
|
@@ -12,7 +12,7 @@ Published to PyPI as `getstack`. Not a workspace member of the main monorepo
|
|
|
12
12
|
`AsyncStack`): agents, passports (+ delegate + revoke-agent/session/all +
|
|
13
13
|
list_active), services (+ connect + grant/revoke/get_permissions),
|
|
14
14
|
credentials, drop-offs (+ expire), skills, identity, audit, notifications
|
|
15
|
-
(+ update), security-events, proxy, scan, intents, missions,
|
|
15
|
+
(+ update), security-events, proxy, scan, usage, intents, missions,
|
|
16
16
|
evidence-packs, llm gateway, inbound-webhooks, partner, **team**,
|
|
17
17
|
**delivery_methods**, **detector_config**.
|
|
18
18
|
- **Offline / customer-managed crypto** (the JS headline features):
|
|
@@ -49,6 +49,7 @@ from .notifications import NotificationService
|
|
|
49
49
|
from .audit import AuditService
|
|
50
50
|
from .proxy import ProxyService as ProxySvc, ProxyResponse
|
|
51
51
|
from .scan import ScanService
|
|
52
|
+
from .usage import UsageService
|
|
52
53
|
from .security_events import SecurityEventService as SecurityEventSvc
|
|
53
54
|
from .skills import SkillService
|
|
54
55
|
from .identity import IdentityService
|
|
@@ -233,6 +234,7 @@ class Stack:
|
|
|
233
234
|
self.audit = AuditService(self._client)
|
|
234
235
|
self.proxy = ProxySvc(self._client)
|
|
235
236
|
self.scan = ScanService(self._client)
|
|
237
|
+
self.usage = UsageService(self._client)
|
|
236
238
|
self.security_events = SecurityEventSvc(self._client)
|
|
237
239
|
self.skills = SkillService(self._client)
|
|
238
240
|
self.identity = IdentityService(self._client)
|
|
@@ -378,6 +380,7 @@ class AsyncStack:
|
|
|
378
380
|
from ._async.audit import AsyncAuditService
|
|
379
381
|
from ._async.proxy import AsyncProxyService
|
|
380
382
|
from ._async.scan import AsyncScanService
|
|
383
|
+
from ._async.usage import AsyncUsageService
|
|
381
384
|
from ._async.security_events import AsyncSecurityEventService
|
|
382
385
|
from ._async.skills import AsyncSkillService
|
|
383
386
|
from ._async.identity import AsyncIdentityService
|
|
@@ -402,6 +405,7 @@ class AsyncStack:
|
|
|
402
405
|
self.audit = AsyncAuditService(self._client)
|
|
403
406
|
self.proxy = AsyncProxyService(self._client)
|
|
404
407
|
self.scan = AsyncScanService(self._client)
|
|
408
|
+
self.usage = AsyncUsageService(self._client)
|
|
405
409
|
self.security_events = AsyncSecurityEventService(self._client)
|
|
406
410
|
self.skills = AsyncSkillService(self._client)
|
|
407
411
|
self.identity = AsyncIdentityService(self._client)
|
|
@@ -23,10 +23,15 @@ class AsyncAgentService:
|
|
|
23
23
|
key_mode: str | None = None,
|
|
24
24
|
on_warning: str = "notify",
|
|
25
25
|
on_critical: str = "notify",
|
|
26
|
+
llm_inject_system_prompt: bool | None = None,
|
|
27
|
+
llm_redact_pii: bool | None = None,
|
|
26
28
|
) -> Agent:
|
|
27
29
|
"""Register an agent. See the sync :meth:`AgentService.register`
|
|
28
30
|
docstring — pass ``profile="proxied-llm-bot"`` for a bot that just
|
|
29
|
-
runs. Unset fields are not sent, so they never clobber a preset.
|
|
31
|
+
runs. Unset fields are not sent, so they never clobber a preset.
|
|
32
|
+
The GET-245 LLM gateway toggles both default True server-side;
|
|
33
|
+
``llm_redact_pii=False`` sends user PII to the LLM provider
|
|
34
|
+
unredacted (explicit operator decision) and False IS sent."""
|
|
30
35
|
body: dict[str, Any] = {"name": name}
|
|
31
36
|
if description:
|
|
32
37
|
body["description"] = description
|
|
@@ -42,6 +47,11 @@ class AsyncAgentService:
|
|
|
42
47
|
body["on_warning"] = on_warning
|
|
43
48
|
if on_critical != "notify":
|
|
44
49
|
body["on_critical"] = on_critical
|
|
50
|
+
# is-not-None (not truthiness) so an explicit False reaches the wire.
|
|
51
|
+
if llm_inject_system_prompt is not None:
|
|
52
|
+
body["llm_inject_system_prompt"] = llm_inject_system_prompt
|
|
53
|
+
if llm_redact_pii is not None:
|
|
54
|
+
body["llm_redact_pii"] = llm_redact_pii
|
|
45
55
|
return _to_agent(await self._client.post("/v1/agents/register", json=body))
|
|
46
56
|
|
|
47
57
|
async def get(self, agent_id: str) -> Agent:
|
|
@@ -52,6 +62,9 @@ class AsyncAgentService:
|
|
|
52
62
|
return [_to_agent(a) for a in data]
|
|
53
63
|
|
|
54
64
|
async def update(self, agent_id: str, **kwargs: Any) -> Agent:
|
|
65
|
+
"""PATCH mutable agent fields, passed through as-is — including
|
|
66
|
+
the GET-245 LLM gateway toggles; see the sync
|
|
67
|
+
:meth:`AgentService.update`."""
|
|
55
68
|
return _to_agent(await self._client.patch(f"/v1/agents/{agent_id}", json=kwargs))
|
|
56
69
|
|
|
57
70
|
async def suspend(self, agent_id: str) -> Agent:
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Async mirror of UsageService."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ..client import AsyncHttpClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncUsageService:
|
|
11
|
+
def __init__(self, client: AsyncHttpClient):
|
|
12
|
+
self._client = client
|
|
13
|
+
|
|
14
|
+
async def get(self) -> dict[str, Any]:
|
|
15
|
+
return await self._client.request("GET", "/v1/usage")
|
|
@@ -28,6 +28,10 @@ def _to_agent(data: dict[str, Any]) -> Agent:
|
|
|
28
28
|
updated_at=data["updated_at"],
|
|
29
29
|
credential_access=data.get("credential_access", "direct"),
|
|
30
30
|
key_mode=data.get("key_mode", "stack_managed"),
|
|
31
|
+
# GET-245 — LLM gateway toggles; True mirrors the DB default
|
|
32
|
+
# (fail-closed) when an older API omits them.
|
|
33
|
+
llm_inject_system_prompt=data.get("llm_inject_system_prompt", True),
|
|
34
|
+
llm_redact_pii=data.get("llm_redact_pii", True),
|
|
31
35
|
)
|
|
32
36
|
|
|
33
37
|
|
|
@@ -45,6 +49,8 @@ class AgentService:
|
|
|
45
49
|
key_mode: str | None = None,
|
|
46
50
|
on_warning: str = "notify",
|
|
47
51
|
on_critical: str = "notify",
|
|
52
|
+
llm_inject_system_prompt: bool | None = None,
|
|
53
|
+
llm_redact_pii: bool | None = None,
|
|
48
54
|
) -> Agent:
|
|
49
55
|
"""Register an agent.
|
|
50
56
|
|
|
@@ -59,6 +65,14 @@ class AgentService:
|
|
|
59
65
|
server default (``enforced`` + ``direct``) OR the profile value —
|
|
60
66
|
they are only sent when provided, so an unset value never clobbers
|
|
61
67
|
a preset.
|
|
68
|
+
|
|
69
|
+
``llm_inject_system_prompt`` / ``llm_redact_pii`` tune the
|
|
70
|
+
``/v1/llm/*`` gateway per agent (both default True server-side):
|
|
71
|
+
inject prepends the STACK security context to every gateway call;
|
|
72
|
+
redact replaces PII in user messages before forwarding.
|
|
73
|
+
``llm_redact_pii=False`` means user PII flows to the LLM provider
|
|
74
|
+
unredacted — an explicit operator decision. ``False`` is sent on
|
|
75
|
+
the wire; only ``None`` (unset) is omitted.
|
|
62
76
|
"""
|
|
63
77
|
body: dict[str, Any] = {"name": name}
|
|
64
78
|
if description:
|
|
@@ -75,6 +89,11 @@ class AgentService:
|
|
|
75
89
|
body["on_warning"] = on_warning
|
|
76
90
|
if on_critical != "notify":
|
|
77
91
|
body["on_critical"] = on_critical
|
|
92
|
+
# is-not-None (not truthiness) so an explicit False reaches the wire.
|
|
93
|
+
if llm_inject_system_prompt is not None:
|
|
94
|
+
body["llm_inject_system_prompt"] = llm_inject_system_prompt
|
|
95
|
+
if llm_redact_pii is not None:
|
|
96
|
+
body["llm_redact_pii"] = llm_redact_pii
|
|
78
97
|
return _to_agent(self._client.post("/v1/agents/register", json=body))
|
|
79
98
|
|
|
80
99
|
def get(self, agent_id: str) -> Agent:
|
|
@@ -85,6 +104,11 @@ class AgentService:
|
|
|
85
104
|
return [_to_agent(a) for a in data]
|
|
86
105
|
|
|
87
106
|
def update(self, agent_id: str, **kwargs: Any) -> Agent:
|
|
107
|
+
"""PATCH mutable agent fields, passed through as-is. The GET-245
|
|
108
|
+
LLM gateway toggles (``llm_inject_system_prompt``,
|
|
109
|
+
``llm_redact_pii``) ride here too — see :meth:`register` for
|
|
110
|
+
semantics; ``llm_redact_pii=False`` sends user PII to the LLM
|
|
111
|
+
provider unredacted."""
|
|
88
112
|
return _to_agent(self._client.patch(f"/v1/agents/{agent_id}", json=kwargs))
|
|
89
113
|
|
|
90
114
|
def suspend(self, agent_id: str) -> Agent:
|
|
@@ -28,6 +28,13 @@ class Agent:
|
|
|
28
28
|
# see whether the box needs an agent keypair (customer_managed) or the
|
|
29
29
|
# operator credential (stack_managed). Defaulted for older responses.
|
|
30
30
|
key_mode: str = "stack_managed"
|
|
31
|
+
# GET-245 — per-agent LLM gateway toggles. Both default true at the DB
|
|
32
|
+
# (fail-closed): inject prepends the STACK security context to /v1/llm/*
|
|
33
|
+
# calls; redact replaces PII in user messages before forwarding. False on
|
|
34
|
+
# llm_redact_pii means user PII reaches the LLM provider unredacted.
|
|
35
|
+
# Defaulted for older API responses that predate the fields.
|
|
36
|
+
llm_inject_system_prompt: bool = True
|
|
37
|
+
llm_redact_pii: bool = True
|
|
31
38
|
|
|
32
39
|
|
|
33
40
|
@dataclass
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Usage service — `/v1/usage` (usage + spend + earnings rollup).
|
|
2
|
+
|
|
3
|
+
The one-call rollup of what your operator consumed against its plan,
|
|
4
|
+
spent, and earned this billing period. Superset of the per-domain meters
|
|
5
|
+
(`stack.scan.usage()`, `stack.llm.usage`, `stack.proxy` counts) — one
|
|
6
|
+
authoritative shape the CLI and dashboard read too. Money fields are in
|
|
7
|
+
cents.
|
|
8
|
+
|
|
9
|
+
Example::
|
|
10
|
+
|
|
11
|
+
u = stack.usage.get()
|
|
12
|
+
p = u["consumption"]["passports"]
|
|
13
|
+
print(f"Passports {p['used']}/{p['limit']}")
|
|
14
|
+
print(f"Spent this period: ${u['spend_cents']['total'] / 100:.2f}")
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from .client import HttpClient
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class UsageService:
|
|
25
|
+
def __init__(self, client: HttpClient):
|
|
26
|
+
self._client = client
|
|
27
|
+
|
|
28
|
+
def get(self) -> dict[str, Any]:
|
|
29
|
+
"""Usage, spend, and earnings for the current billing period.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Dict with keys: tier, period_start, period_end, consumption
|
|
33
|
+
(per-meter {used, limit}), spend_cents (skills / llm_compute /
|
|
34
|
+
identity / overage / total), wallet_cents ({balance}), and
|
|
35
|
+
earnings_cents ({unpaid, lifetime}). All money in cents.
|
|
36
|
+
"""
|
|
37
|
+
return self._client.request("GET", "/v1/usage")
|
|
@@ -32,8 +32,8 @@ from getstack import ( # noqa: E402
|
|
|
32
32
|
# Services that must exist on BOTH clients.
|
|
33
33
|
EXPECTED_SERVICES = [
|
|
34
34
|
"agents", "passports", "credentials", "services", "dropoffs", "reviews",
|
|
35
|
-
"notifications", "audit", "proxy", "scan", "
|
|
36
|
-
"identity", "intents", "missions", "evidence_packs", "llm",
|
|
35
|
+
"notifications", "audit", "proxy", "scan", "usage", "security_events",
|
|
36
|
+
"skills", "identity", "intents", "missions", "evidence_packs", "llm",
|
|
37
37
|
"inbound_webhooks", "partner_tenants", "team", "delivery_methods",
|
|
38
38
|
"detector_config",
|
|
39
39
|
]
|
|
@@ -58,6 +58,7 @@ EXPECTED_METHODS = [
|
|
|
58
58
|
("team", "invite"),
|
|
59
59
|
("delivery_methods", "add"),
|
|
60
60
|
("detector_config", "upsert"),
|
|
61
|
+
("usage", "get"),
|
|
61
62
|
]
|
|
62
63
|
|
|
63
64
|
|
|
@@ -88,6 +89,74 @@ def test_register_accepts_key_mode_on_both():
|
|
|
88
89
|
for client in (s, a):
|
|
89
90
|
params = inspect.signature(client.agents.register).parameters
|
|
90
91
|
assert "profile" in params and "credential_access" in params
|
|
92
|
+
# GET-245 LLM gateway toggles.
|
|
93
|
+
assert "llm_inject_system_prompt" in params and "llm_redact_pii" in params
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# Minimal valid agent response for _to_agent; llm_redact_pii=False proves
|
|
97
|
+
# the response field lands on the model instead of the True fallback.
|
|
98
|
+
_AGENT_RESPONSE = {
|
|
99
|
+
"id": "agent_x", "operator_id": "op_x", "name": "bot", "status": "active",
|
|
100
|
+
"created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z",
|
|
101
|
+
"llm_inject_system_prompt": True, "llm_redact_pii": False,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class _CaptureClient:
|
|
106
|
+
"""Stands in for HttpClient/AsyncHttpClient; records the last body."""
|
|
107
|
+
|
|
108
|
+
def __init__(self, is_async: bool = False):
|
|
109
|
+
self.is_async = is_async
|
|
110
|
+
self.json = None
|
|
111
|
+
|
|
112
|
+
def _record(self, json):
|
|
113
|
+
self.json = json
|
|
114
|
+
return dict(_AGENT_RESPONSE)
|
|
115
|
+
|
|
116
|
+
def post(self, path, json=None, **kw):
|
|
117
|
+
if self.is_async:
|
|
118
|
+
async def _coro():
|
|
119
|
+
return self._record(json)
|
|
120
|
+
return _coro()
|
|
121
|
+
return self._record(json)
|
|
122
|
+
|
|
123
|
+
patch = post
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_register_llm_toggles_round_trip_sync():
|
|
127
|
+
s, _ = _clients()
|
|
128
|
+
s.agents._client = stub = _CaptureClient()
|
|
129
|
+
agent = s.agents.register(
|
|
130
|
+
"bot", llm_inject_system_prompt=True, llm_redact_pii=False
|
|
131
|
+
)
|
|
132
|
+
# Both flags reach the body; explicit False is sent, not dropped.
|
|
133
|
+
assert stub.json["llm_inject_system_prompt"] is True
|
|
134
|
+
assert stub.json["llm_redact_pii"] is False
|
|
135
|
+
# And the response fields land on the Agent model.
|
|
136
|
+
assert agent.llm_inject_system_prompt is True and agent.llm_redact_pii is False
|
|
137
|
+
# Unset -> omitted, so the server/profile default is never clobbered.
|
|
138
|
+
s.agents.register("bot2")
|
|
139
|
+
assert "llm_inject_system_prompt" not in stub.json
|
|
140
|
+
assert "llm_redact_pii" not in stub.json
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_register_llm_toggles_round_trip_async():
|
|
144
|
+
import asyncio
|
|
145
|
+
|
|
146
|
+
_, a = _clients()
|
|
147
|
+
a.agents._client = stub = _CaptureClient(is_async=True)
|
|
148
|
+
agent = asyncio.run(a.agents.register("bot", llm_redact_pii=False))
|
|
149
|
+
assert stub.json["llm_redact_pii"] is False
|
|
150
|
+
assert "llm_inject_system_prompt" not in stub.json
|
|
151
|
+
assert agent.llm_redact_pii is False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def test_update_llm_toggles_pass_through():
|
|
155
|
+
s, _ = _clients()
|
|
156
|
+
s.agents._client = stub = _CaptureClient()
|
|
157
|
+
agent = s.agents.update("agent_x", llm_redact_pii=False)
|
|
158
|
+
assert stub.json == {"llm_redact_pii": False}
|
|
159
|
+
assert agent.llm_redact_pii is False
|
|
91
160
|
|
|
92
161
|
|
|
93
162
|
def test_offline_crypto_exported():
|
|
@@ -104,6 +173,9 @@ if __name__ == "__main__":
|
|
|
104
173
|
test_both_clients_have_all_services()
|
|
105
174
|
test_both_clients_have_all_methods()
|
|
106
175
|
test_register_accepts_key_mode_on_both()
|
|
176
|
+
test_register_llm_toggles_round_trip_sync()
|
|
177
|
+
test_register_llm_toggles_round_trip_async()
|
|
178
|
+
test_update_llm_toggles_pass_through()
|
|
107
179
|
test_offline_crypto_exported()
|
|
108
180
|
test_service_classes_exported()
|
|
109
181
|
print("all parity tests passed")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|