claritty-sdk 2.0.2__tar.gz → 2.0.4__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.
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/PKG-INFO +1 -1
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/executor_client.py +6 -1
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/platform_creds.py +8 -1
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/runtime/platform_client.py +6 -1
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/runtime/tool_loop.py +16 -7
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk.egg-info/PKG-INFO +1 -1
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk.egg-info/SOURCES.txt +1 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/pyproject.toml +1 -1
- claritty_sdk-2.0.4/tests/test_integration_broker_dispatch.py +60 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/README.md +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/__init__.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/cli_eval.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/cli_verify.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/context.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/decorators.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/enums.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/eval.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/executor.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/graph.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/__init__.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/__init__.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/airtable.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/anthropic.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/asana.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/calendly.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/catalog.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/clickup.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/client.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/discord.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/dropbox.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/github.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/gitlab.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/gmail.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/google_calendar.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/google_drive.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/hubspot.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/jira.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/linear.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/linkedin.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/mailchimp.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/mcp_client.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/microsoft_teams.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/notion.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/notion_mcp.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/openai.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/outlook.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/outlook_calendar.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/pipedrive.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/postgres.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/posthog.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/s3.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/salesforce.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/slack.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/stripe.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/trello.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/twilio.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/integrations/x_twitter.pyi +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/llm.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/manifest.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/registry.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/runtime/__init__.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/runtime/bootstrap.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/runtime/expressions.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/runtime/schema_validate.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/runtime/tracing.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/runtime/workflow_engine.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/trigger_manager.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk/validators.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk.egg-info/dependency_links.txt +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk.egg-info/entry_points.txt +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk.egg-info/requires.txt +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/claritty_sdk.egg-info/top_level.txt +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/setup.cfg +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/tests/test_graph_emitter.py +0 -0
- {claritty_sdk-2.0.2 → claritty_sdk-2.0.4}/tests/test_manifest_triggers.py +0 -0
|
@@ -55,11 +55,16 @@ def execute_tool(
|
|
|
55
55
|
os.environ.get("CLARITTY_CREDS_TIMEOUT_S", "30")
|
|
56
56
|
)
|
|
57
57
|
url = f"{base}/internal/integrations/tools/{integration_id}/{tool_id}/execute"
|
|
58
|
+
# Per-app scoping: the platform serves the connection bound to THIS app.
|
|
59
|
+
payload: Dict[str, Any] = {"userId": user_id, "arguments": arguments}
|
|
60
|
+
app_id = os.environ.get("CLARITY_APP_ID")
|
|
61
|
+
if app_id:
|
|
62
|
+
payload["appId"] = app_id
|
|
58
63
|
try:
|
|
59
64
|
resp = httpx.post(
|
|
60
65
|
url,
|
|
61
66
|
headers={"X-Claritty-Internal": secret},
|
|
62
|
-
json=
|
|
67
|
+
json=payload,
|
|
63
68
|
timeout=timeout,
|
|
64
69
|
)
|
|
65
70
|
except httpx.HTTPError as e:
|
|
@@ -110,11 +110,18 @@ def fetch_for_user(integration_id: str, user_id: str) -> Credentials:
|
|
|
110
110
|
import httpx
|
|
111
111
|
|
|
112
112
|
url = f"{base}/internal/integrations/credentials/fetch"
|
|
113
|
+
# Per-app scoping: the platform resolves the connection bound to THIS app.
|
|
114
|
+
# CLARITY_APP_ID is injected per deployed app; omit when absent (legacy →
|
|
115
|
+
# the platform falls back to the user-scoped connection).
|
|
116
|
+
payload = {"userId": user_id, "integrationCatalogId": integration_id}
|
|
117
|
+
app_id = os.environ.get("CLARITY_APP_ID")
|
|
118
|
+
if app_id:
|
|
119
|
+
payload["appId"] = app_id
|
|
113
120
|
try:
|
|
114
121
|
with httpx.Client(timeout=_FETCH_TIMEOUT_S) as client:
|
|
115
122
|
res = client.post(
|
|
116
123
|
url,
|
|
117
|
-
json=
|
|
124
|
+
json=payload,
|
|
118
125
|
headers={
|
|
119
126
|
"X-Claritty-Internal": secret,
|
|
120
127
|
"Content-Type": "application/json",
|
|
@@ -138,11 +138,16 @@ class PlatformClient:
|
|
|
138
138
|
f"{self.base_url}/internal/integrations/"
|
|
139
139
|
f"{integration_id}/{bare}/execute"
|
|
140
140
|
)
|
|
141
|
+
# Per-app scoping: platform serves the connection bound to THIS app.
|
|
142
|
+
payload: Dict[str, Any] = {"userId": user_id, "arguments": arguments}
|
|
143
|
+
app_id = os.environ.get("CLARITY_APP_ID")
|
|
144
|
+
if app_id:
|
|
145
|
+
payload["appId"] = app_id
|
|
141
146
|
try:
|
|
142
147
|
with httpx.Client(timeout=self._timeout) as http:
|
|
143
148
|
res = http.post(
|
|
144
149
|
url,
|
|
145
|
-
json=
|
|
150
|
+
json=payload,
|
|
146
151
|
headers={
|
|
147
152
|
"X-Claritty-Internal": self._secret,
|
|
148
153
|
"Content-Type": "application/json",
|
|
@@ -618,14 +618,23 @@ async def _dispatch_tool(
|
|
|
618
618
|
return {"error": str(exc)}
|
|
619
619
|
|
|
620
620
|
handler = _decorators.get_registered_tool(tool_id)
|
|
621
|
+
if handler is None and "." in tool_id and decl.source == "catalog":
|
|
622
|
+
# Integration-provided tool with no locally-registered handler → BROKER
|
|
623
|
+
# it through the platform executor: the platform holds the credential +
|
|
624
|
+
# provider REST code and runs the call server-side; the app only gets the
|
|
625
|
+
# result. This is the canonical secure path — the credential never
|
|
626
|
+
# reaches the app. (Phase 3.)
|
|
627
|
+
from ..integrations.catalog import (
|
|
628
|
+
get_provided_tool,
|
|
629
|
+
_make_platform_delegate,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
integration_id = tool_id.split(".", 1)[0]
|
|
633
|
+
handler = get_provided_tool(tool_id) or _make_platform_delegate(
|
|
634
|
+
integration_id, tool_id
|
|
635
|
+
)
|
|
621
636
|
if handler is None:
|
|
622
|
-
return {
|
|
623
|
-
"error": (
|
|
624
|
-
f"no registered handler for tool {tool_id!r}; the agent "
|
|
625
|
-
"may need to wait for Phase 3 (integration catalog) for "
|
|
626
|
-
"this to be dispatched"
|
|
627
|
-
)
|
|
628
|
-
}
|
|
637
|
+
return {"error": f"no registered handler for tool {tool_id!r}"}
|
|
629
638
|
|
|
630
639
|
tool_ctx = ToolCtx(
|
|
631
640
|
user_id=agent_ctx.user_id,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Phase 3: the agent tool-loop must BROKER integration-provided tools through
|
|
2
|
+
the platform executor — the credential never reaches the app. A catalog-source
|
|
3
|
+
tool with no locally-registered handler is dispatched via the platform delegate
|
|
4
|
+
(PlatformClient.execute_integration_tool), and only its result is returned.
|
|
5
|
+
"""
|
|
6
|
+
import asyncio
|
|
7
|
+
|
|
8
|
+
from claritty_sdk.runtime import tool_loop
|
|
9
|
+
from claritty_sdk.runtime import platform_client
|
|
10
|
+
from claritty_sdk.manifest import ToolDecl
|
|
11
|
+
from claritty_sdk.context import AgentContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _ctx():
|
|
15
|
+
return AgentContext(user_id="u1")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_integration_tool_is_brokered(monkeypatch):
|
|
19
|
+
seen = {}
|
|
20
|
+
|
|
21
|
+
def fake_exec(self, integration_id, tool_id, user_id, arguments):
|
|
22
|
+
seen.update(
|
|
23
|
+
integration_id=integration_id,
|
|
24
|
+
tool_id=tool_id,
|
|
25
|
+
user_id=user_id,
|
|
26
|
+
arguments=arguments,
|
|
27
|
+
)
|
|
28
|
+
return {"message_id": "m1"}
|
|
29
|
+
|
|
30
|
+
monkeypatch.setattr(
|
|
31
|
+
platform_client.PlatformClient, "execute_integration_tool", fake_exec
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
toolset = {
|
|
35
|
+
"gmail.send": ToolDecl(
|
|
36
|
+
id="gmail.send", source="catalog", handler="", input={}, output={}
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
out = asyncio.run(
|
|
40
|
+
tool_loop._dispatch_tool(
|
|
41
|
+
"gmail.send", {"to": "x@y.com"}, toolset, _ctx(), None
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
# Only the result comes back — no credential.
|
|
45
|
+
assert out == {"message_id": "m1"}
|
|
46
|
+
assert seen["integration_id"] == "gmail"
|
|
47
|
+
assert seen["user_id"] == "u1"
|
|
48
|
+
assert seen["arguments"] == {"to": "x@y.com"}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_unknown_custom_tool_still_errors():
|
|
52
|
+
toolset = {
|
|
53
|
+
"foo.bar": ToolDecl(
|
|
54
|
+
id="foo.bar", source="custom", handler="", input={}, output={}
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
out = asyncio.run(
|
|
58
|
+
tool_loop._dispatch_tool("foo.bar", {}, toolset, _ctx(), None)
|
|
59
|
+
)
|
|
60
|
+
assert "no registered handler" in out.get("error", "")
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|