rippletide-python 0.1.0__tar.gz → 0.3.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.
- rippletide_python-0.3.0/PKG-INFO +186 -0
- rippletide_python-0.3.0/README.md +176 -0
- {rippletide_python-0.1.0 → rippletide_python-0.3.0}/pyproject.toml +2 -1
- {rippletide_python-0.1.0 → rippletide_python-0.3.0}/rippletide/client.py +361 -194
- rippletide_python-0.3.0/rippletide/telemetry.py +242 -0
- rippletide_python-0.3.0/rippletide_python.egg-info/PKG-INFO +186 -0
- {rippletide_python-0.1.0 → rippletide_python-0.3.0}/rippletide_python.egg-info/SOURCES.txt +2 -0
- rippletide_python-0.3.0/rippletide_python.egg-info/requires.txt +2 -0
- {rippletide_python-0.1.0 → rippletide_python-0.3.0}/tests/test_client.py +245 -5
- rippletide_python-0.1.0/PKG-INFO +0 -101
- rippletide_python-0.1.0/README.md +0 -93
- rippletide_python-0.1.0/rippletide_python.egg-info/PKG-INFO +0 -101
- {rippletide_python-0.1.0 → rippletide_python-0.3.0}/rippletide/__init__.py +0 -0
- {rippletide_python-0.1.0 → rippletide_python-0.3.0}/rippletide_python.egg-info/dependency_links.txt +0 -0
- {rippletide_python-0.1.0 → rippletide_python-0.3.0}/rippletide_python.egg-info/top_level.txt +0 -0
- {rippletide_python-0.1.0 → rippletide_python-0.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rippletide-python
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Monitor-first Python SDK for Rippletide-connected agents
|
|
5
|
+
License: UNLICENSED
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: opentelemetry-sdk==1.40.0
|
|
9
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-common==1.40.0
|
|
10
|
+
|
|
11
|
+
# Rippletide Python
|
|
12
|
+
|
|
13
|
+
`rippletide-python` connects a code-owned Python agent to Rippletide without
|
|
14
|
+
taking ownership of its model traffic. It has no provider dependencies: wrap a
|
|
15
|
+
hand-written provider or internal HTTP call at the existing model seam.
|
|
16
|
+
|
|
17
|
+
## Install and connect
|
|
18
|
+
|
|
19
|
+
The current release is `rippletide-python==0.3.0`. It requires
|
|
20
|
+
the platform's OTLP ingestion/readers and does not provide V1 trace compatibility.
|
|
21
|
+
Until it is published, validate it by installing a locally built wheel; do not
|
|
22
|
+
assume the unversioned registry install below contains candidate changes.
|
|
23
|
+
|
|
24
|
+
Requires Python 3.10 or later. From the existing agent repository, use its Python
|
|
25
|
+
environment, or create one if it has none:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python3 -m venv .venv
|
|
29
|
+
source .venv/bin/activate
|
|
30
|
+
python -m pip install rippletide-python
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
On Windows, activate with `.venv\Scripts\Activate.ps1` in PowerShell instead.
|
|
34
|
+
Keep the dependency in the project's requirements or package-manager manifest.
|
|
35
|
+
|
|
36
|
+
In Rippletide, choose **Connect an agent → Agent (Python)**. Save the generated
|
|
37
|
+
credentials in the repository's git-ignored `.env`, then apply the generated
|
|
38
|
+
prompt with your coding agent in that repository. The CLI also supports
|
|
39
|
+
`rippletide connect --kind agent --language python --print-prompt`.
|
|
40
|
+
|
|
41
|
+
Run a real turn that uses a tool and delivers a response. Confirm **Runtime
|
|
42
|
+
verified (5/5)** in Fleet and the run's tool calls and **Delivered** outcome in
|
|
43
|
+
Activity. Copying a prompt or storing credentials alone does not prove a
|
|
44
|
+
connection.
|
|
45
|
+
|
|
46
|
+
## Instrument existing call sites
|
|
47
|
+
|
|
48
|
+
The following fragment belongs inside the existing request handler; reuse the
|
|
49
|
+
agent's own provider, messages, tools, context, and response sink.
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from rippletide import Rippletide, load_rippletide_env
|
|
53
|
+
|
|
54
|
+
# Keep these values aligned with the agent's existing provider call.
|
|
55
|
+
PROVIDER_NAME = "existing-provider"
|
|
56
|
+
PROVIDER_API_KEY_ENV = "EXISTING_PROVIDER_API_KEY"
|
|
57
|
+
MODEL_OPERATION = "existing-provider.request"
|
|
58
|
+
|
|
59
|
+
load_rippletide_env("/path/to/connected-repo/.env")
|
|
60
|
+
rippletide = Rippletide(name="Support agent")
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
rippletide.register_prompts({"system": {"fallback": SYSTEM_PROMPT}}) # prompt text is withheld by default
|
|
64
|
+
rippletide.tools("core", TOOLS)
|
|
65
|
+
rippletide.connection(PROVIDER_NAME, env={"api_key": PROVIDER_API_KEY_ENV})
|
|
66
|
+
with rippletide.run(name="inbound_request", input=request, conversation_id=conversation_id):
|
|
67
|
+
with rippletide.agent(name="orchestrator"):
|
|
68
|
+
call_model = rippletide.traced(raw_model_call, name=MODEL_OPERATION, kind="model")
|
|
69
|
+
candidate = call_model(messages)
|
|
70
|
+
return rippletide.deliver_response(
|
|
71
|
+
request=request, response=candidate, context=trusted_context,
|
|
72
|
+
deliver=send_to_caller,
|
|
73
|
+
)
|
|
74
|
+
finally:
|
|
75
|
+
rippletide.flush()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The CLI writes an agent-scoped `RIPPLETIDE=rippletide://…` connection string
|
|
79
|
+
into the connected repository's `.env`. `load_rippletide_env()` loads it without
|
|
80
|
+
overriding the process environment. The SDK also accepts the legacy
|
|
81
|
+
`RIPPLETIDE_API_KEY`, `RIPPLETIDE_AGENT_ID` (or `RIPPLETIDE_APP`), and optional
|
|
82
|
+
`RIPPLETIDE_BASE_URL` generated by the Connection page. A valid `RIPPLETIDE`
|
|
83
|
+
connection takes precedence over those legacy values; explicit constructor
|
|
84
|
+
arguments take precedence over either format. Never use a Platform key in the
|
|
85
|
+
agent or commit the `.env`.
|
|
86
|
+
|
|
87
|
+
## Async and streaming agents
|
|
88
|
+
|
|
89
|
+
The same client serves synchronous and asyncio code. `guard_tool_call()`,
|
|
90
|
+
`guard_tool_result()` and `deliver_response()` accept `async def` callbacks and
|
|
91
|
+
then return a coroutine to await; the policy round trip and receipts run in a
|
|
92
|
+
worker thread, so the event loop is never blocked. `run()`, `agent()` and
|
|
93
|
+
`span()` are plain `with` blocks in both cases. `traced()` wraps sync
|
|
94
|
+
functions, coroutine functions, and calls that return a provider stream: the
|
|
95
|
+
stream is passed through untouched and its span ends once the stream is
|
|
96
|
+
consumed, closed, or abandoned, in which case the span is marked
|
|
97
|
+
`stream_not_consumed`. Policy decides on the complete response: pass the text
|
|
98
|
+
the agent has assembled from the stream to `deliver_response()` before the
|
|
99
|
+
existing sink delivers it. When the tokens must reach the caller as they arrive,
|
|
100
|
+
pass the stream itself as `response`: chunks flow through to `deliver` untouched
|
|
101
|
+
and the decision is recorded on the assembled text once the stream ends, with no
|
|
102
|
+
suppression possible, so a BLOCK is marked as decided after delivery.
|
|
103
|
+
Use `await rippletide.aflush()` and
|
|
104
|
+
`await rippletide.ashutdown()` from asyncio code.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
async def handle(request):
|
|
108
|
+
with rippletide.run(name="inbound_request", input=request):
|
|
109
|
+
stream = rippletide.traced(open_model_stream, name=MODEL_OPERATION, kind="model")(messages)
|
|
110
|
+
candidate = "".join([chunk.text async for chunk in stream])
|
|
111
|
+
await rippletide.guard_tool_call(toolset="core", tool="lookup", params=args, execute=lookup_async)
|
|
112
|
+
return await rippletide.deliver_response(request=request, response=candidate, deliver=send_async)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Policy failures and capture
|
|
116
|
+
|
|
117
|
+
The client starts disabled when no Connection key is present. For
|
|
118
|
+
`guard_tool_call()` and `deliver_response()`:
|
|
119
|
+
|
|
120
|
+
- Network failures, 5xx responses, rate limits, and malformed or interrupted HTTP
|
|
121
|
+
responses fail open: the protected callback can still run.
|
|
122
|
+
- An enforce-mode `BLOCK` raises `RippletidePolicyBlockedError` before the
|
|
123
|
+
protected callback runs. An observe-only `WOULD_BLOCK` permits execution.
|
|
124
|
+
- HTTP `400`, `401`, `403`, or `404` from the policy decision endpoint raises
|
|
125
|
+
`RippletidePolicyDecisionError` before the callback runs. These request,
|
|
126
|
+
authentication, authorization, or routing failures are not swallowed as
|
|
127
|
+
telemetry outages.
|
|
128
|
+
|
|
129
|
+
Result filtering has its own fallback rules, described below. Application input
|
|
130
|
+
validation and exceptions from the agent's own callbacks can also propagate.
|
|
131
|
+
|
|
132
|
+
Runtime instrumentation uses native OpenTelemetry spans and start logs, exported as
|
|
133
|
+
OTLP/HTTP JSON to `/v1/traces` and `/v1/logs`. Providers are local to the client;
|
|
134
|
+
the SDK does not replace application providers. The only profile is `full`, but
|
|
135
|
+
content is omitted unless `Rippletide(capture_content=True)` is explicit. Captured
|
|
136
|
+
content is bounded and credential-redacted, including nested Python mappings
|
|
137
|
+
such as `UserDict` and `MappingProxyType`, not generally PII-filtered. The old
|
|
138
|
+
capture modes are unsupported. `runtime_traces=False` disables evidence export
|
|
139
|
+
without disabling policy checks. Always call `flush()` before a short-lived
|
|
140
|
+
worker exits, and `shutdown()` when the client is no longer needed.
|
|
141
|
+
|
|
142
|
+
Tool and response policy spans include the invocation ID; once a decision arrives, their end
|
|
143
|
+
events and the protected action spans include its decision ID, including in
|
|
144
|
+
the default content-withheld posture. These IDs correlate the action with its policy receipt.
|
|
145
|
+
|
|
146
|
+
Prompt inventory is recorded with its content withheld by default. Pass
|
|
147
|
+
`allow_content_export=True` to `register_prompts(...)` only when the prompt
|
|
148
|
+
owner has explicitly approved storing the prompt text in Rippletide.
|
|
149
|
+
|
|
150
|
+
## Filter a completed tool result (explicit opt-in)
|
|
151
|
+
|
|
152
|
+
For an eligible collection returned by a tool, declare its result contract and
|
|
153
|
+
pass `allow_result_export=True`. This evaluates the complete result transiently
|
|
154
|
+
at Rippletide; do this only with the owner's explicit approval. It is false by
|
|
155
|
+
default. Transient policy failures and malformed or mismatched plans forward
|
|
156
|
+
every item unchanged. A missing result endpoint (`404` with no error code or
|
|
157
|
+
`NOT_FOUND`) also uses this fallback. Other deterministic request or permission
|
|
158
|
+
rejections raise `RippletidePolicyDecisionError` before `apply` runs.
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
rippletide.tools("catalog", TOOLS, results={
|
|
162
|
+
"list_products": {
|
|
163
|
+
"resultSchema": {"type": "object", "properties": {"products": {"type": "array"}}},
|
|
164
|
+
"resultCollection": {"itemsPath": ["products"], "itemIdPath": ["id"]},
|
|
165
|
+
},
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
result = list_products(params)
|
|
169
|
+
return rippletide.guard_tool_result(
|
|
170
|
+
toolset="catalog", tool="list_products", params=params,
|
|
171
|
+
result=result, result_items=result["products"], context=trusted_context,
|
|
172
|
+
allow_result_export=True,
|
|
173
|
+
apply=lambda guarded: {**guarded["result"], "products": guarded["deliveredItems"]},
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`ALLOW` and observe-only `WOULD_BLOCK` items are delivered; only enforce-mode
|
|
178
|
+
`BLOCK` items are excluded. The callback receives the original result snapshot,
|
|
179
|
+
the delivered and excluded items, and the decision. It is responsible for the
|
|
180
|
+
native response shape, and the SDK records the applied outcome as a receipt.
|
|
181
|
+
Result fingerprints use the server's ECMAScript JSON encoding, including its
|
|
182
|
+
number formatting and UTF-16 object-key ordering.
|
|
183
|
+
|
|
184
|
+
Python MCP servers remain unsupported. A response that is streamed to the
|
|
185
|
+
caller before it is complete cannot be protected by `deliver_response()`; the
|
|
186
|
+
SDK does not represent such a path as complete response-delivery protection.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Rippletide Python
|
|
2
|
+
|
|
3
|
+
`rippletide-python` connects a code-owned Python agent to Rippletide without
|
|
4
|
+
taking ownership of its model traffic. It has no provider dependencies: wrap a
|
|
5
|
+
hand-written provider or internal HTTP call at the existing model seam.
|
|
6
|
+
|
|
7
|
+
## Install and connect
|
|
8
|
+
|
|
9
|
+
The current release is `rippletide-python==0.3.0`. It requires
|
|
10
|
+
the platform's OTLP ingestion/readers and does not provide V1 trace compatibility.
|
|
11
|
+
Until it is published, validate it by installing a locally built wheel; do not
|
|
12
|
+
assume the unversioned registry install below contains candidate changes.
|
|
13
|
+
|
|
14
|
+
Requires Python 3.10 or later. From the existing agent repository, use its Python
|
|
15
|
+
environment, or create one if it has none:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
python3 -m venv .venv
|
|
19
|
+
source .venv/bin/activate
|
|
20
|
+
python -m pip install rippletide-python
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
On Windows, activate with `.venv\Scripts\Activate.ps1` in PowerShell instead.
|
|
24
|
+
Keep the dependency in the project's requirements or package-manager manifest.
|
|
25
|
+
|
|
26
|
+
In Rippletide, choose **Connect an agent → Agent (Python)**. Save the generated
|
|
27
|
+
credentials in the repository's git-ignored `.env`, then apply the generated
|
|
28
|
+
prompt with your coding agent in that repository. The CLI also supports
|
|
29
|
+
`rippletide connect --kind agent --language python --print-prompt`.
|
|
30
|
+
|
|
31
|
+
Run a real turn that uses a tool and delivers a response. Confirm **Runtime
|
|
32
|
+
verified (5/5)** in Fleet and the run's tool calls and **Delivered** outcome in
|
|
33
|
+
Activity. Copying a prompt or storing credentials alone does not prove a
|
|
34
|
+
connection.
|
|
35
|
+
|
|
36
|
+
## Instrument existing call sites
|
|
37
|
+
|
|
38
|
+
The following fragment belongs inside the existing request handler; reuse the
|
|
39
|
+
agent's own provider, messages, tools, context, and response sink.
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from rippletide import Rippletide, load_rippletide_env
|
|
43
|
+
|
|
44
|
+
# Keep these values aligned with the agent's existing provider call.
|
|
45
|
+
PROVIDER_NAME = "existing-provider"
|
|
46
|
+
PROVIDER_API_KEY_ENV = "EXISTING_PROVIDER_API_KEY"
|
|
47
|
+
MODEL_OPERATION = "existing-provider.request"
|
|
48
|
+
|
|
49
|
+
load_rippletide_env("/path/to/connected-repo/.env")
|
|
50
|
+
rippletide = Rippletide(name="Support agent")
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
rippletide.register_prompts({"system": {"fallback": SYSTEM_PROMPT}}) # prompt text is withheld by default
|
|
54
|
+
rippletide.tools("core", TOOLS)
|
|
55
|
+
rippletide.connection(PROVIDER_NAME, env={"api_key": PROVIDER_API_KEY_ENV})
|
|
56
|
+
with rippletide.run(name="inbound_request", input=request, conversation_id=conversation_id):
|
|
57
|
+
with rippletide.agent(name="orchestrator"):
|
|
58
|
+
call_model = rippletide.traced(raw_model_call, name=MODEL_OPERATION, kind="model")
|
|
59
|
+
candidate = call_model(messages)
|
|
60
|
+
return rippletide.deliver_response(
|
|
61
|
+
request=request, response=candidate, context=trusted_context,
|
|
62
|
+
deliver=send_to_caller,
|
|
63
|
+
)
|
|
64
|
+
finally:
|
|
65
|
+
rippletide.flush()
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The CLI writes an agent-scoped `RIPPLETIDE=rippletide://…` connection string
|
|
69
|
+
into the connected repository's `.env`. `load_rippletide_env()` loads it without
|
|
70
|
+
overriding the process environment. The SDK also accepts the legacy
|
|
71
|
+
`RIPPLETIDE_API_KEY`, `RIPPLETIDE_AGENT_ID` (or `RIPPLETIDE_APP`), and optional
|
|
72
|
+
`RIPPLETIDE_BASE_URL` generated by the Connection page. A valid `RIPPLETIDE`
|
|
73
|
+
connection takes precedence over those legacy values; explicit constructor
|
|
74
|
+
arguments take precedence over either format. Never use a Platform key in the
|
|
75
|
+
agent or commit the `.env`.
|
|
76
|
+
|
|
77
|
+
## Async and streaming agents
|
|
78
|
+
|
|
79
|
+
The same client serves synchronous and asyncio code. `guard_tool_call()`,
|
|
80
|
+
`guard_tool_result()` and `deliver_response()` accept `async def` callbacks and
|
|
81
|
+
then return a coroutine to await; the policy round trip and receipts run in a
|
|
82
|
+
worker thread, so the event loop is never blocked. `run()`, `agent()` and
|
|
83
|
+
`span()` are plain `with` blocks in both cases. `traced()` wraps sync
|
|
84
|
+
functions, coroutine functions, and calls that return a provider stream: the
|
|
85
|
+
stream is passed through untouched and its span ends once the stream is
|
|
86
|
+
consumed, closed, or abandoned, in which case the span is marked
|
|
87
|
+
`stream_not_consumed`. Policy decides on the complete response: pass the text
|
|
88
|
+
the agent has assembled from the stream to `deliver_response()` before the
|
|
89
|
+
existing sink delivers it. When the tokens must reach the caller as they arrive,
|
|
90
|
+
pass the stream itself as `response`: chunks flow through to `deliver` untouched
|
|
91
|
+
and the decision is recorded on the assembled text once the stream ends, with no
|
|
92
|
+
suppression possible, so a BLOCK is marked as decided after delivery.
|
|
93
|
+
Use `await rippletide.aflush()` and
|
|
94
|
+
`await rippletide.ashutdown()` from asyncio code.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
async def handle(request):
|
|
98
|
+
with rippletide.run(name="inbound_request", input=request):
|
|
99
|
+
stream = rippletide.traced(open_model_stream, name=MODEL_OPERATION, kind="model")(messages)
|
|
100
|
+
candidate = "".join([chunk.text async for chunk in stream])
|
|
101
|
+
await rippletide.guard_tool_call(toolset="core", tool="lookup", params=args, execute=lookup_async)
|
|
102
|
+
return await rippletide.deliver_response(request=request, response=candidate, deliver=send_async)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Policy failures and capture
|
|
106
|
+
|
|
107
|
+
The client starts disabled when no Connection key is present. For
|
|
108
|
+
`guard_tool_call()` and `deliver_response()`:
|
|
109
|
+
|
|
110
|
+
- Network failures, 5xx responses, rate limits, and malformed or interrupted HTTP
|
|
111
|
+
responses fail open: the protected callback can still run.
|
|
112
|
+
- An enforce-mode `BLOCK` raises `RippletidePolicyBlockedError` before the
|
|
113
|
+
protected callback runs. An observe-only `WOULD_BLOCK` permits execution.
|
|
114
|
+
- HTTP `400`, `401`, `403`, or `404` from the policy decision endpoint raises
|
|
115
|
+
`RippletidePolicyDecisionError` before the callback runs. These request,
|
|
116
|
+
authentication, authorization, or routing failures are not swallowed as
|
|
117
|
+
telemetry outages.
|
|
118
|
+
|
|
119
|
+
Result filtering has its own fallback rules, described below. Application input
|
|
120
|
+
validation and exceptions from the agent's own callbacks can also propagate.
|
|
121
|
+
|
|
122
|
+
Runtime instrumentation uses native OpenTelemetry spans and start logs, exported as
|
|
123
|
+
OTLP/HTTP JSON to `/v1/traces` and `/v1/logs`. Providers are local to the client;
|
|
124
|
+
the SDK does not replace application providers. The only profile is `full`, but
|
|
125
|
+
content is omitted unless `Rippletide(capture_content=True)` is explicit. Captured
|
|
126
|
+
content is bounded and credential-redacted, including nested Python mappings
|
|
127
|
+
such as `UserDict` and `MappingProxyType`, not generally PII-filtered. The old
|
|
128
|
+
capture modes are unsupported. `runtime_traces=False` disables evidence export
|
|
129
|
+
without disabling policy checks. Always call `flush()` before a short-lived
|
|
130
|
+
worker exits, and `shutdown()` when the client is no longer needed.
|
|
131
|
+
|
|
132
|
+
Tool and response policy spans include the invocation ID; once a decision arrives, their end
|
|
133
|
+
events and the protected action spans include its decision ID, including in
|
|
134
|
+
the default content-withheld posture. These IDs correlate the action with its policy receipt.
|
|
135
|
+
|
|
136
|
+
Prompt inventory is recorded with its content withheld by default. Pass
|
|
137
|
+
`allow_content_export=True` to `register_prompts(...)` only when the prompt
|
|
138
|
+
owner has explicitly approved storing the prompt text in Rippletide.
|
|
139
|
+
|
|
140
|
+
## Filter a completed tool result (explicit opt-in)
|
|
141
|
+
|
|
142
|
+
For an eligible collection returned by a tool, declare its result contract and
|
|
143
|
+
pass `allow_result_export=True`. This evaluates the complete result transiently
|
|
144
|
+
at Rippletide; do this only with the owner's explicit approval. It is false by
|
|
145
|
+
default. Transient policy failures and malformed or mismatched plans forward
|
|
146
|
+
every item unchanged. A missing result endpoint (`404` with no error code or
|
|
147
|
+
`NOT_FOUND`) also uses this fallback. Other deterministic request or permission
|
|
148
|
+
rejections raise `RippletidePolicyDecisionError` before `apply` runs.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
rippletide.tools("catalog", TOOLS, results={
|
|
152
|
+
"list_products": {
|
|
153
|
+
"resultSchema": {"type": "object", "properties": {"products": {"type": "array"}}},
|
|
154
|
+
"resultCollection": {"itemsPath": ["products"], "itemIdPath": ["id"]},
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
result = list_products(params)
|
|
159
|
+
return rippletide.guard_tool_result(
|
|
160
|
+
toolset="catalog", tool="list_products", params=params,
|
|
161
|
+
result=result, result_items=result["products"], context=trusted_context,
|
|
162
|
+
allow_result_export=True,
|
|
163
|
+
apply=lambda guarded: {**guarded["result"], "products": guarded["deliveredItems"]},
|
|
164
|
+
)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`ALLOW` and observe-only `WOULD_BLOCK` items are delivered; only enforce-mode
|
|
168
|
+
`BLOCK` items are excluded. The callback receives the original result snapshot,
|
|
169
|
+
the delivered and excluded items, and the decision. It is responsible for the
|
|
170
|
+
native response shape, and the SDK records the applied outcome as a receipt.
|
|
171
|
+
Result fingerprints use the server's ECMAScript JSON encoding, including its
|
|
172
|
+
number formatting and UTF-16 object-key ordering.
|
|
173
|
+
|
|
174
|
+
Python MCP servers remain unsupported. A response that is streamed to the
|
|
175
|
+
caller before it is complete cannot be protected by `deliver_response()`; the
|
|
176
|
+
SDK does not represent such a path as complete response-delivery protection.
|
|
@@ -4,10 +4,11 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rippletide-python"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Monitor-first Python SDK for Rippletide-connected agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
|
+
dependencies = ["opentelemetry-sdk==1.40.0", "opentelemetry-exporter-otlp-proto-common==1.40.0"]
|
|
11
12
|
license = { text = "UNLICENSED" }
|
|
12
13
|
|
|
13
14
|
[tool.setuptools]
|