agentic-fabriq-sdk 0.1.3__py3-none-any.whl
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.
Potentially problematic release.
This version of agentic-fabriq-sdk might be problematic. Click here for more details.
- af_sdk/__init__.py +55 -0
- af_sdk/auth/__init__.py +31 -0
- af_sdk/auth/dpop.py +43 -0
- af_sdk/auth/oauth.py +247 -0
- af_sdk/auth/token_cache.py +318 -0
- af_sdk/connectors/__init__.py +23 -0
- af_sdk/connectors/base.py +231 -0
- af_sdk/connectors/registry.py +262 -0
- af_sdk/dx/__init__.py +12 -0
- af_sdk/dx/decorators.py +40 -0
- af_sdk/dx/runtime.py +170 -0
- af_sdk/events.py +699 -0
- af_sdk/exceptions.py +140 -0
- af_sdk/fabriq_client.py +198 -0
- af_sdk/models/__init__.py +47 -0
- af_sdk/models/audit.py +44 -0
- af_sdk/models/types.py +242 -0
- af_sdk/py.typed +0 -0
- af_sdk/transport/__init__.py +7 -0
- af_sdk/transport/http.py +366 -0
- af_sdk/vault.py +500 -0
- agentic_fabriq_sdk-0.1.3.dist-info/METADATA +81 -0
- agentic_fabriq_sdk-0.1.3.dist-info/RECORD +24 -0
- agentic_fabriq_sdk-0.1.3.dist-info/WHEEL +4 -0
af_sdk/dx/runtime.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
import os
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _base_headers(token: str, tenant_id: Optional[str]) -> Dict[str, str]:
|
|
9
|
+
headers = {"Authorization": f"Bearer {token}"}
|
|
10
|
+
if tenant_id:
|
|
11
|
+
headers["X-Tenant-Id"] = tenant_id
|
|
12
|
+
# Dev helper: allow overriding scopes from env for local testing
|
|
13
|
+
debug_scopes = os.getenv("FABRIQ_DEBUG_SCOPES")
|
|
14
|
+
if debug_scopes is not None:
|
|
15
|
+
headers["X-Debug-Scopes"] = debug_scopes
|
|
16
|
+
return headers
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ToolFabric:
|
|
20
|
+
"""Thin facade over Fabriq provider proxy endpoints (e.g., Slack).
|
|
21
|
+
|
|
22
|
+
This class lets developers think in terms of a "fabric" of tools provided
|
|
23
|
+
by a vendor, while under the hood we call the Gateway proxy endpoints.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, *, provider: str, base_url: str, access_token: str, tenant_id: Optional[str] = None):
|
|
27
|
+
self.provider = provider
|
|
28
|
+
self.base_url = base_url.rstrip("/")
|
|
29
|
+
self.token = access_token
|
|
30
|
+
self.tenant_id = tenant_id
|
|
31
|
+
|
|
32
|
+
def get_tools(self, names: List[str]) -> List[str]:
|
|
33
|
+
# Placeholder: simply returns opaque method identifiers as strings the Agent understands
|
|
34
|
+
return [f"{self.provider}:{name}" for name in names]
|
|
35
|
+
|
|
36
|
+
def invoke(self, action: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
37
|
+
url = f"{self.base_url}/api/v1/proxy/{self.provider}/{action}"
|
|
38
|
+
with httpx.Client(timeout=30.0) as c:
|
|
39
|
+
r = c.post(url, json=params, headers=_base_headers(self.token, self.tenant_id))
|
|
40
|
+
r.raise_for_status()
|
|
41
|
+
return r.json()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class MCPServer:
|
|
45
|
+
"""Facade for an MCP server registered in Fabriq (proxy layer)."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, *, server_id: str, base_url: str, access_token: str, tenant_id: Optional[str] = None):
|
|
48
|
+
self.server_id = server_id
|
|
49
|
+
self.base_url = base_url.rstrip("/")
|
|
50
|
+
self.token = access_token
|
|
51
|
+
self.tenant_id = tenant_id
|
|
52
|
+
|
|
53
|
+
def get_tools(self, names: List[str]) -> List[str]:
|
|
54
|
+
return [f"mcp:{self.server_id}:{name}" for name in names]
|
|
55
|
+
|
|
56
|
+
def invoke(self, tool: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
57
|
+
url = f"{self.base_url}/api/v1/proxy/mcp/{self.server_id}/invoke"
|
|
58
|
+
payload = {"payload": {"tool": tool, "args": args}}
|
|
59
|
+
with httpx.Client(timeout=60.0) as c:
|
|
60
|
+
r = c.post(url, json=payload, headers=_base_headers(self.token, self.tenant_id))
|
|
61
|
+
r.raise_for_status()
|
|
62
|
+
return r.json()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class AgentFabric:
|
|
66
|
+
"""Future-facing A2A discovery placeholder.
|
|
67
|
+
|
|
68
|
+
Today it simply stores identifiers of other agents by id.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, *, base_url: str, access_token: str, tenant_id: Optional[str] = None):
|
|
72
|
+
self.base_url = base_url.rstrip("/")
|
|
73
|
+
self.token = access_token
|
|
74
|
+
self.tenant_id = tenant_id
|
|
75
|
+
|
|
76
|
+
def get_agents(self, ids: List[str]) -> List[str]:
|
|
77
|
+
return ids
|
|
78
|
+
|
|
79
|
+
def invoke_agent(self, agent_id: str, input: Dict[str, Any]) -> Dict[str, Any]:
|
|
80
|
+
url = f"{self.base_url}/api/v1/agents/{agent_id}/invoke"
|
|
81
|
+
with httpx.Client(timeout=60.0) as c:
|
|
82
|
+
r = c.post(url, json={"input": input, "context": {}}, headers=_base_headers(self.token, self.tenant_id))
|
|
83
|
+
r.raise_for_status()
|
|
84
|
+
return r.json()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Agent:
|
|
88
|
+
"""Minimal orchestrator that can call tools and agents.
|
|
89
|
+
|
|
90
|
+
tools: a mixed list of
|
|
91
|
+
- wrapped local functions decorated with @tool (call directly), or
|
|
92
|
+
- string references produced by ToolFabric/MCPServer (we route accordingly).
|
|
93
|
+
agents: a list of agent ids discoverable via AgentFabric.get_agents().
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(
|
|
97
|
+
self,
|
|
98
|
+
*,
|
|
99
|
+
system_prompt: str,
|
|
100
|
+
tools: List[Any],
|
|
101
|
+
agents: List[str],
|
|
102
|
+
base_url: str,
|
|
103
|
+
access_token: str,
|
|
104
|
+
tenant_id: Optional[str] = None,
|
|
105
|
+
provider_fabrics: Optional[Dict[str, ToolFabric]] = None,
|
|
106
|
+
mcp_servers: Optional[Dict[str, MCPServer]] = None,
|
|
107
|
+
agent_fabric: Optional[AgentFabric] = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
self.system_prompt = system_prompt
|
|
110
|
+
self.tools = tools
|
|
111
|
+
self.agents = agents
|
|
112
|
+
self.base_url = base_url.rstrip("/")
|
|
113
|
+
self.token = access_token
|
|
114
|
+
self.tenant_id = tenant_id
|
|
115
|
+
self.provider_fabrics = provider_fabrics or {}
|
|
116
|
+
self.mcp_servers = mcp_servers or {}
|
|
117
|
+
self.agent_fabric = agent_fabric or AgentFabric(base_url=base_url, access_token=access_token, tenant_id=tenant_id)
|
|
118
|
+
|
|
119
|
+
def _is_wrapped_tool(self, obj: Any) -> bool:
|
|
120
|
+
return hasattr(obj, "_af_tool")
|
|
121
|
+
|
|
122
|
+
def run(self, instruction: str) -> str:
|
|
123
|
+
# Super-minimal router: if instruction mentions "slack" and "summary",
|
|
124
|
+
# fetch recent messages via a slack ToolFabric and send to a summarizer agent.
|
|
125
|
+
inst = instruction.lower()
|
|
126
|
+
if "slack" in inst and "summary" in inst:
|
|
127
|
+
slack: Optional[ToolFabric] = self.provider_fabrics.get("slack")
|
|
128
|
+
if not slack:
|
|
129
|
+
return "No slack ToolFabric configured."
|
|
130
|
+
# Find a channel id (prefer env, else pick first from channels.list)
|
|
131
|
+
channel_id = os.getenv("SLACK_DEFAULT_CHANNEL")
|
|
132
|
+
if not channel_id:
|
|
133
|
+
try:
|
|
134
|
+
ch_resp = slack.invoke("channels.list", {})
|
|
135
|
+
channels = (ch_resp.get("channels") or ch_resp.get("items") or [])
|
|
136
|
+
if channels:
|
|
137
|
+
channel_id = channels[0].get("id")
|
|
138
|
+
except Exception:
|
|
139
|
+
channel_id = None
|
|
140
|
+
if not channel_id:
|
|
141
|
+
return "Could not determine a Slack channel id; set SLACK_DEFAULT_CHANNEL or ensure channels.list works."
|
|
142
|
+
|
|
143
|
+
# Fetch messages
|
|
144
|
+
hist = slack.invoke("conversations.history", {"channel": channel_id, "limit": 50})
|
|
145
|
+
# Gateway proxy normalizes to { items: [...] }, but also returns provider-shape under raw
|
|
146
|
+
messages = hist.get("items") or hist.get("messages") or []
|
|
147
|
+
if not messages:
|
|
148
|
+
return "No Slack messages available or Slack is not connected. Connect Slack in the UI (Tools tab) for the active tenant or set SLACK_DEFAULT_CHANNEL."
|
|
149
|
+
lines = [m.get("text", "").strip() for m in messages if m.get("text")]
|
|
150
|
+
corpus = "\n".join(lines)
|
|
151
|
+
# Call first collaborator agent named 'summarizer' if present
|
|
152
|
+
target = next((a for a in self.agents if a == "summarizer"), None)
|
|
153
|
+
if not target:
|
|
154
|
+
return "No summarizer agent configured."
|
|
155
|
+
result = self.agent_fabric.invoke_agent(target, {"text": corpus})
|
|
156
|
+
# result shape: { output, metadata, logs, status }
|
|
157
|
+
return result.get("output") or str(result)
|
|
158
|
+
|
|
159
|
+
# Fallback: try local tools if any
|
|
160
|
+
for t in self.tools:
|
|
161
|
+
if self._is_wrapped_tool(t):
|
|
162
|
+
try:
|
|
163
|
+
out = t(instruction) # type: ignore[misc]
|
|
164
|
+
if isinstance(out, str) and out:
|
|
165
|
+
return out
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
return "No handler matched the instruction."
|
|
169
|
+
|
|
170
|
+
|