agentic-fabriq-sdk 0.1.17__py3-none-any.whl → 0.1.19__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_cli/commands/tools.py +36 -54
- af_cli/core/client.py +29 -0
- af_cli/main.py +0 -2
- af_sdk/__init__.py +0 -4
- af_sdk/dx/__init__.py +1 -3
- af_sdk/dx/runtime.py +0 -106
- af_sdk/events.py +14 -50
- af_sdk/fabriq_client.py +66 -14
- af_sdk/models/__init__.py +0 -8
- af_sdk/models/types.py +0 -50
- {agentic_fabriq_sdk-0.1.17.dist-info → agentic_fabriq_sdk-0.1.19.dist-info}/METADATA +3 -3
- {agentic_fabriq_sdk-0.1.17.dist-info → agentic_fabriq_sdk-0.1.19.dist-info}/RECORD +14 -16
- af_cli/commands/agents.py +0 -238
- af_sdk/auth/application.py +0 -264
- {agentic_fabriq_sdk-0.1.17.dist-info → agentic_fabriq_sdk-0.1.19.dist-info}/WHEEL +0 -0
- {agentic_fabriq_sdk-0.1.17.dist-info → agentic_fabriq_sdk-0.1.19.dist-info}/entry_points.txt +0 -0
af_cli/commands/tools.py
CHANGED
|
@@ -121,17 +121,20 @@ def get(
|
|
|
121
121
|
|
|
122
122
|
@app.command()
|
|
123
123
|
def invoke(
|
|
124
|
-
|
|
124
|
+
connection_id: str = typer.Argument(..., help="Connection ID (e.g., 'slacker', 'gurt')"),
|
|
125
125
|
method: str = typer.Option(..., "--method", help="Tool method to invoke"),
|
|
126
126
|
params: str = typer.Option(None, "--params", help="JSON string of method parameters (e.g., '{\"channel\": \"test\", \"text\": \"Hello\"}')"),
|
|
127
127
|
format: str = typer.Option("json", "--format", help="Output format (json, table, yaml)"),
|
|
128
128
|
):
|
|
129
|
-
"""Invoke a tool
|
|
129
|
+
"""Invoke a tool using its connection ID.
|
|
130
|
+
|
|
131
|
+
The connection ID identifies which specific tool connection to use.
|
|
132
|
+
Run 'afctl tools list' to see your connection IDs.
|
|
130
133
|
|
|
131
134
|
Examples:
|
|
132
|
-
afctl tools invoke
|
|
133
|
-
afctl tools invoke
|
|
134
|
-
afctl tools invoke
|
|
135
|
+
afctl tools invoke slacker --method get_channels
|
|
136
|
+
afctl tools invoke slacker --method post_message --params '{"channel": "test", "text": "Hello!"}'
|
|
137
|
+
afctl tools invoke gurt --method list_files
|
|
135
138
|
"""
|
|
136
139
|
try:
|
|
137
140
|
# Parse parameters if provided
|
|
@@ -145,59 +148,29 @@ def invoke(
|
|
|
145
148
|
raise typer.Exit(1)
|
|
146
149
|
|
|
147
150
|
with get_client() as client:
|
|
148
|
-
|
|
149
|
-
tool_id = tool_identifier
|
|
150
|
-
try:
|
|
151
|
-
from uuid import UUID
|
|
152
|
-
UUID(tool_identifier)
|
|
153
|
-
# It's already a UUID, use it directly
|
|
154
|
-
except ValueError:
|
|
155
|
-
# Not a UUID, try to look up by name
|
|
156
|
-
info(f"Looking up tool '{tool_identifier}'...")
|
|
157
|
-
try:
|
|
158
|
-
tools_response = client.get("/api/v1/tools")
|
|
159
|
-
|
|
160
|
-
# Handle different response formats
|
|
161
|
-
if isinstance(tools_response, dict) and "tools" in tools_response:
|
|
162
|
-
tools = tools_response["tools"]
|
|
163
|
-
elif isinstance(tools_response, dict) and "items" in tools_response:
|
|
164
|
-
tools = tools_response["items"]
|
|
165
|
-
elif hasattr(tools_response, '__iter__') and not isinstance(tools_response, (str, dict)):
|
|
166
|
-
tools = list(tools_response)
|
|
167
|
-
else:
|
|
168
|
-
tools = []
|
|
169
|
-
|
|
170
|
-
# Find tool by name (case-insensitive)
|
|
171
|
-
matching_tools = [t for t in tools if isinstance(t, dict) and t.get("name", "").lower() == tool_identifier.lower()]
|
|
172
|
-
|
|
173
|
-
if not matching_tools:
|
|
174
|
-
error(f"Tool '{tool_identifier}' not found. Available tools:")
|
|
175
|
-
for t in tools:
|
|
176
|
-
if isinstance(t, dict):
|
|
177
|
-
print(f" - {t.get('name')} (ID: {t.get('id')})")
|
|
178
|
-
raise typer.Exit(1)
|
|
179
|
-
|
|
180
|
-
if len(matching_tools) > 1:
|
|
181
|
-
error(f"Multiple tools found with name '{tool_identifier}':")
|
|
182
|
-
for t in matching_tools:
|
|
183
|
-
print(f" - {t.get('name')} (ID: {t.get('id')})")
|
|
184
|
-
error("Please use the UUID instead.")
|
|
185
|
-
raise typer.Exit(1)
|
|
186
|
-
|
|
187
|
-
tool_id = matching_tools[0].get("id")
|
|
188
|
-
info(f"Resolved '{tool_identifier}' to tool ID: {tool_id}")
|
|
189
|
-
except Exception as lookup_error:
|
|
190
|
-
error(f"Failed to look up tool: {lookup_error}")
|
|
191
|
-
raise typer.Exit(1)
|
|
151
|
+
info(f"Invoking connection '{connection_id}' with method '{method}'...")
|
|
192
152
|
|
|
153
|
+
# Verify connection exists
|
|
154
|
+
connections = client.get("/api/v1/user-connections")
|
|
155
|
+
connection = next((c for c in connections if c.get("connection_id") == connection_id), None)
|
|
156
|
+
|
|
157
|
+
if not connection:
|
|
158
|
+
error(f"Connection '{connection_id}' not found")
|
|
159
|
+
info("Available connections:")
|
|
160
|
+
for conn in connections:
|
|
161
|
+
info(f" - {conn.get('connection_id')} ({conn.get('tool')})")
|
|
162
|
+
raise typer.Exit(1)
|
|
163
|
+
|
|
164
|
+
tool_name = connection.get("tool")
|
|
165
|
+
debug(f"Connection '{connection_id}' uses tool '{tool_name}'")
|
|
166
|
+
|
|
167
|
+
# Use the connection-based invoke endpoint (auto-creates tool if needed)
|
|
193
168
|
data = {
|
|
194
169
|
"method": method,
|
|
195
170
|
"parameters": parameters,
|
|
196
|
-
"context": {},
|
|
197
171
|
}
|
|
198
172
|
|
|
199
|
-
|
|
200
|
-
response = client.post(f"/api/v1/tools/{tool_id}/invoke", data)
|
|
173
|
+
response = client.post(f"/api/v1/tools/connections/{connection_id}/invoke", data)
|
|
201
174
|
|
|
202
175
|
success("Tool invoked successfully")
|
|
203
176
|
|
|
@@ -349,8 +322,11 @@ def add(
|
|
|
349
322
|
if redirect_uri:
|
|
350
323
|
config_payload["redirect_uri"] = redirect_uri
|
|
351
324
|
|
|
325
|
+
# For Google tools, pass tool_type parameter to prevent duplicates
|
|
326
|
+
tool_type_param = f"&tool_type={tool}" if api_tool_name == "google" else ""
|
|
327
|
+
|
|
352
328
|
client.post(
|
|
353
|
-
f"/api/v1/tools/{api_tool_name}/config?connection_id={connection_id}",
|
|
329
|
+
f"/api/v1/tools/{api_tool_name}/config?connection_id={connection_id}{tool_type_param}",
|
|
354
330
|
data=config_payload
|
|
355
331
|
)
|
|
356
332
|
success("✅ OAuth app credentials stored")
|
|
@@ -523,9 +499,15 @@ def disconnect(
|
|
|
523
499
|
info("Cancelled")
|
|
524
500
|
return
|
|
525
501
|
|
|
502
|
+
# Determine the API base tool name (Google tools all use "google")
|
|
503
|
+
api_tool_name = "google" if (tool.startswith("google_") or tool == "gmail") else tool
|
|
504
|
+
|
|
505
|
+
# For Google tools, pass tool_type parameter
|
|
506
|
+
tool_type_param = f"&tool_type={tool}" if api_tool_name == "google" else ""
|
|
507
|
+
|
|
526
508
|
# Delete connection credentials
|
|
527
509
|
client.delete(
|
|
528
|
-
f"/api/v1/tools/{
|
|
510
|
+
f"/api/v1/tools/{api_tool_name}/connection?connection_id={connection_id}{tool_type_param}"
|
|
529
511
|
)
|
|
530
512
|
|
|
531
513
|
success(f"✅ Disconnected: {connection_id}")
|
af_cli/core/client.py
CHANGED
|
@@ -86,6 +86,35 @@ class AFClient:
|
|
|
86
86
|
|
|
87
87
|
return self._handle_response(response)
|
|
88
88
|
|
|
89
|
+
def try_post(self, path: str, data: Optional[Dict] = None) -> tuple[bool, int, Optional[Dict[str, Any]]]:
|
|
90
|
+
"""Make POST request without exiting on error. Returns (success, status_code, response_data)."""
|
|
91
|
+
url = urljoin(self.config.gateway_url, path)
|
|
92
|
+
debug(f"POST {url}")
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
response = self.client.post(
|
|
96
|
+
path,
|
|
97
|
+
json=data,
|
|
98
|
+
headers=self._get_headers(),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
debug(f"Response: {response.status_code} {response.url}")
|
|
102
|
+
|
|
103
|
+
if response.status_code >= 400:
|
|
104
|
+
try:
|
|
105
|
+
error_data = response.json()
|
|
106
|
+
return False, response.status_code, error_data
|
|
107
|
+
except:
|
|
108
|
+
return False, response.status_code, {"detail": response.text}
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
return True, response.status_code, response.json()
|
|
112
|
+
except:
|
|
113
|
+
return True, response.status_code, {"message": "Success"}
|
|
114
|
+
|
|
115
|
+
except Exception as e:
|
|
116
|
+
return False, 0, {"detail": str(e)}
|
|
117
|
+
|
|
89
118
|
def put(self, path: str, data: Optional[Dict] = None) -> Dict[str, Any]:
|
|
90
119
|
"""Make PUT request."""
|
|
91
120
|
url = urljoin(self.config.gateway_url, path)
|
af_cli/main.py
CHANGED
|
@@ -9,7 +9,6 @@ import typer
|
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
from rich.table import Table
|
|
11
11
|
|
|
12
|
-
from af_cli.commands.agents import app as agents_app
|
|
13
12
|
from af_cli.commands.applications import app as applications_app
|
|
14
13
|
from af_cli.commands.auth import app as auth_app
|
|
15
14
|
from af_cli.commands.config import app as config_app
|
|
@@ -30,7 +29,6 @@ console = Console()
|
|
|
30
29
|
# Add subcommands
|
|
31
30
|
app.add_typer(auth_app, name="auth", help="Authentication commands")
|
|
32
31
|
app.add_typer(config_app, name="config", help="Configuration commands")
|
|
33
|
-
app.add_typer(agents_app, name="agents", help="Agent management commands")
|
|
34
32
|
app.add_typer(tools_app, name="tools", help="Tool management commands")
|
|
35
33
|
app.add_typer(applications_app, name="applications", help="Application management commands")
|
|
36
34
|
app.add_typer(mcp_servers_app, name="mcp-servers", help="MCP server management commands")
|
af_sdk/__init__.py
CHANGED
|
@@ -15,8 +15,6 @@ from .exceptions import (
|
|
|
15
15
|
ValidationError,
|
|
16
16
|
)
|
|
17
17
|
from .models.types import (
|
|
18
|
-
AgentInvokeRequest,
|
|
19
|
-
AgentInvokeResult,
|
|
20
18
|
ToolInvokeRequest,
|
|
21
19
|
ToolInvokeResult,
|
|
22
20
|
)
|
|
@@ -45,8 +43,6 @@ __all__ = [
|
|
|
45
43
|
"ConnectorError",
|
|
46
44
|
"NotFoundError",
|
|
47
45
|
"ValidationError",
|
|
48
|
-
"AgentInvokeRequest",
|
|
49
|
-
"AgentInvokeResult",
|
|
50
46
|
"ToolInvokeRequest",
|
|
51
47
|
"ToolInvokeResult",
|
|
52
48
|
"HTTPClient",
|
af_sdk/dx/__init__.py
CHANGED
af_sdk/dx/runtime.py
CHANGED
|
@@ -62,109 +62,3 @@ class MCPServer:
|
|
|
62
62
|
return r.json()
|
|
63
63
|
|
|
64
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
|
-
|
af_sdk/events.py
CHANGED
|
@@ -35,17 +35,6 @@ class EventMetadata(BaseModel):
|
|
|
35
35
|
trace_id: Optional[str] = None
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
class AgentEvent(BaseModel):
|
|
39
|
-
"""Agent lifecycle event"""
|
|
40
|
-
event_type: str # registered, updated, deleted, invoked
|
|
41
|
-
agent_id: str
|
|
42
|
-
agent_name: str
|
|
43
|
-
agent_type: str
|
|
44
|
-
tenant_id: str
|
|
45
|
-
metadata: EventMetadata
|
|
46
|
-
payload: Dict[str, Any] = Field(default_factory=dict)
|
|
47
|
-
|
|
48
|
-
|
|
49
38
|
class ToolEvent(BaseModel):
|
|
50
39
|
"""Tool lifecycle event"""
|
|
51
40
|
event_type: str # registered, updated, deleted, invoked
|
|
@@ -81,7 +70,7 @@ class SecretEvent(BaseModel):
|
|
|
81
70
|
payload: Dict[str, Any] = Field(default_factory=dict)
|
|
82
71
|
|
|
83
72
|
|
|
84
|
-
Event = Union[
|
|
73
|
+
Event = Union[ToolEvent, InvocationEvent, SecretEvent]
|
|
85
74
|
|
|
86
75
|
|
|
87
76
|
class EventStreamConfig(BaseModel):
|
|
@@ -127,25 +116,10 @@ class EventPublisher:
|
|
|
127
116
|
payload: Optional[Dict] = None,
|
|
128
117
|
correlation_id: Optional[str] = None
|
|
129
118
|
):
|
|
130
|
-
"""Publish agent lifecycle event"""
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
tenant_id=tenant_id
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
event = AgentEvent(
|
|
138
|
-
event_type=event_type,
|
|
139
|
-
agent_id=agent_id,
|
|
140
|
-
agent_name=agent_name,
|
|
141
|
-
agent_type=agent_type,
|
|
142
|
-
tenant_id=tenant_id,
|
|
143
|
-
metadata=metadata,
|
|
144
|
-
payload=payload or {}
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
subject = f"agents.{event_type}.{tenant_id}"
|
|
148
|
-
await self._publish_event(subject, event)
|
|
119
|
+
"""Publish agent lifecycle event - DEPRECATED"""
|
|
120
|
+
# Agent events have been removed from the SDK
|
|
121
|
+
# This method is kept for backward compatibility but does nothing
|
|
122
|
+
pass
|
|
149
123
|
|
|
150
124
|
async def publish_tool_event(
|
|
151
125
|
self,
|
|
@@ -284,19 +258,15 @@ class EventSubscriber:
|
|
|
284
258
|
|
|
285
259
|
async def subscribe_to_agent_events(
|
|
286
260
|
self,
|
|
287
|
-
handler: Callable
|
|
261
|
+
handler: Callable,
|
|
288
262
|
tenant_id: Optional[str] = None,
|
|
289
263
|
event_type: Optional[str] = None,
|
|
290
264
|
consumer_name: str = "agent-events-consumer"
|
|
291
265
|
):
|
|
292
|
-
"""Subscribe to agent events"""
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
if event_type:
|
|
297
|
-
subject = f"agents.{event_type}.*"
|
|
298
|
-
|
|
299
|
-
await self._subscribe(subject, handler, consumer_name, AgentEvent)
|
|
266
|
+
"""Subscribe to agent events - DEPRECATED"""
|
|
267
|
+
# Agent events have been removed from the SDK
|
|
268
|
+
# This method is kept for backward compatibility but does nothing
|
|
269
|
+
pass
|
|
300
270
|
|
|
301
271
|
async def subscribe_to_tool_events(
|
|
302
272
|
self,
|
|
@@ -641,16 +611,10 @@ async def publish_agent_registered(
|
|
|
641
611
|
tenant_id: str,
|
|
642
612
|
payload: Optional[Dict] = None
|
|
643
613
|
):
|
|
644
|
-
"""Convenience function to publish agent registered event"""
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
agent_name=agent_name,
|
|
649
|
-
agent_type=agent_type,
|
|
650
|
-
tenant_id=tenant_id,
|
|
651
|
-
source="af_gateway",
|
|
652
|
-
payload=payload
|
|
653
|
-
)
|
|
614
|
+
"""Convenience function to publish agent registered event - DEPRECATED"""
|
|
615
|
+
# Agent events have been removed from the SDK
|
|
616
|
+
# This function is kept for backward compatibility but does nothing
|
|
617
|
+
pass
|
|
654
618
|
|
|
655
619
|
|
|
656
620
|
async def publish_invocation_started(
|
af_sdk/fabriq_client.py
CHANGED
|
@@ -85,6 +85,7 @@ class FabriqClient:
|
|
|
85
85
|
method: str,
|
|
86
86
|
parameters: Optional[Dict[str, Any]] = None,
|
|
87
87
|
context: Optional[Dict[str, Any]] = None,
|
|
88
|
+
connection_id: Optional[str] = None,
|
|
88
89
|
) -> Dict[str, Any]:
|
|
89
90
|
"""Invoke a tool by name or UUID.
|
|
90
91
|
|
|
@@ -93,6 +94,7 @@ class FabriqClient:
|
|
|
93
94
|
method: Method name to invoke
|
|
94
95
|
parameters: Method parameters
|
|
95
96
|
context: Additional context
|
|
97
|
+
connection_id: Specific connection ID to use (for multi-connection tools)
|
|
96
98
|
|
|
97
99
|
Returns:
|
|
98
100
|
Tool invocation result
|
|
@@ -101,6 +103,8 @@ class FabriqClient:
|
|
|
101
103
|
result = await client.invoke_tool("slack", method="get_channels")
|
|
102
104
|
result = await client.invoke_tool("slack", method="post_message",
|
|
103
105
|
parameters={"channel": "test", "text": "Hello!"})
|
|
106
|
+
result = await client.invoke_tool("slack", method="get_channels",
|
|
107
|
+
connection_id="slacker")
|
|
104
108
|
"""
|
|
105
109
|
# Try to resolve tool name to UUID if not already a UUID
|
|
106
110
|
tool_id = tool_identifier
|
|
@@ -142,10 +146,70 @@ class FabriqClient:
|
|
|
142
146
|
body = {"method": method}
|
|
143
147
|
if parameters is not None:
|
|
144
148
|
body["parameters"] = parameters
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
|
|
150
|
+
# Add connection_id to context if provided
|
|
151
|
+
if context is not None or connection_id is not None:
|
|
152
|
+
ctx = (context or {}).copy()
|
|
153
|
+
if connection_id is not None:
|
|
154
|
+
ctx["connection_id"] = connection_id
|
|
155
|
+
body["context"] = ctx
|
|
156
|
+
|
|
147
157
|
r = await self._http.post(f"/tools/{tool_id}/invoke", json=body, headers=self._extra_headers)
|
|
148
158
|
return r.json()
|
|
159
|
+
|
|
160
|
+
async def invoke_connection(
|
|
161
|
+
self,
|
|
162
|
+
connection_id: str,
|
|
163
|
+
*,
|
|
164
|
+
method: str,
|
|
165
|
+
parameters: Optional[Dict[str, Any]] = None,
|
|
166
|
+
) -> Dict[str, Any]:
|
|
167
|
+
"""Invoke a tool using its connection ID (preferred method).
|
|
168
|
+
|
|
169
|
+
This uses the connection_id (like 'slacker', 'gurt') to automatically
|
|
170
|
+
find and invoke the correct tool with the right credentials.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
connection_id: Connection identifier (e.g., 'slacker', 'gurt')
|
|
174
|
+
method: Method name to invoke
|
|
175
|
+
parameters: Method parameters
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Tool invocation result
|
|
179
|
+
|
|
180
|
+
Examples:
|
|
181
|
+
result = await client.invoke_connection("slacker", method="get_channels")
|
|
182
|
+
result = await client.invoke_connection("slacker", method="post_message",
|
|
183
|
+
parameters={"channel": "test", "text": "Hello!"})
|
|
184
|
+
result = await client.invoke_connection("gurt", method="list_files")
|
|
185
|
+
"""
|
|
186
|
+
# This uses the tool invocation with connection_id in context
|
|
187
|
+
# which is supported by current production servers
|
|
188
|
+
body = {
|
|
189
|
+
"method": method,
|
|
190
|
+
"parameters": parameters or {},
|
|
191
|
+
"context": {"connection_id": connection_id}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# First, get the connection to find the tool name
|
|
195
|
+
connections_resp = await self._http.get("/user-connections", headers=self._extra_headers)
|
|
196
|
+
connections = connections_resp.json()
|
|
197
|
+
|
|
198
|
+
connection = next((c for c in connections if c.get("connection_id") == connection_id), None)
|
|
199
|
+
if not connection:
|
|
200
|
+
raise ValueError(f"Connection '{connection_id}' not found")
|
|
201
|
+
|
|
202
|
+
tool_name = connection.get("tool")
|
|
203
|
+
|
|
204
|
+
# Look up the tool by name to get its UUID
|
|
205
|
+
result = await self.invoke_tool(
|
|
206
|
+
tool_name,
|
|
207
|
+
method=method,
|
|
208
|
+
parameters=parameters,
|
|
209
|
+
connection_id=connection_id
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
return result
|
|
149
213
|
|
|
150
214
|
# -----------------
|
|
151
215
|
# MCP Servers
|
|
@@ -184,18 +248,6 @@ class FabriqClient:
|
|
|
184
248
|
r = await self._http.post(f"/proxy/mcp/{server_id}/invoke", json={"payload": payload, "raw": raw}, headers=self._extra_headers)
|
|
185
249
|
return r.json()
|
|
186
250
|
|
|
187
|
-
# -----------------
|
|
188
|
-
# Agents
|
|
189
|
-
# -----------------
|
|
190
|
-
async def list_agents(self, *, page: int = 1, page_size: int = 20, sort_by: str = "name", sort_order: str = "asc") -> Dict[str, Any]:
|
|
191
|
-
params: Dict[str, Any] = {"page": page, "page_size": page_size, "sort_by": sort_by, "sort_order": sort_order}
|
|
192
|
-
r = await self._http.get("/agents", params=params, headers=self._extra_headers)
|
|
193
|
-
return r.json()
|
|
194
|
-
|
|
195
|
-
async def invoke_agent(self, *, agent_id: str, params: Dict[str, Any], raw: bool = False) -> Dict[str, Any]:
|
|
196
|
-
r = await self._http.post(f"/proxy/agents/{agent_id}/invoke", json={"params": params, "raw": raw}, headers=self._extra_headers)
|
|
197
|
-
return r.json()
|
|
198
|
-
|
|
199
251
|
# -----------------
|
|
200
252
|
# Secrets (Gateway-backed Vault)
|
|
201
253
|
# -----------------
|
af_sdk/models/__init__.py
CHANGED
|
@@ -3,10 +3,6 @@ Data models for Agentic Fabric SDK.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from .types import (
|
|
6
|
-
Agent,
|
|
7
|
-
AgentCreate,
|
|
8
|
-
AgentInvokeRequest,
|
|
9
|
-
AgentInvokeResult,
|
|
10
6
|
ErrorResponse,
|
|
11
7
|
HealthResponse,
|
|
12
8
|
McpServer,
|
|
@@ -25,10 +21,6 @@ from .types import (
|
|
|
25
21
|
)
|
|
26
22
|
|
|
27
23
|
__all__ = [
|
|
28
|
-
"Agent",
|
|
29
|
-
"AgentCreate",
|
|
30
|
-
"AgentInvokeRequest",
|
|
31
|
-
"AgentInvokeResult",
|
|
32
24
|
"Tool",
|
|
33
25
|
"ToolInvokeRequest",
|
|
34
26
|
"ToolInvokeResult",
|
af_sdk/models/types.py
CHANGED
|
@@ -9,56 +9,6 @@ from uuid import UUID
|
|
|
9
9
|
from pydantic import BaseModel, Field
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class Agent(BaseModel):
|
|
13
|
-
"""Agent model."""
|
|
14
|
-
|
|
15
|
-
id: str
|
|
16
|
-
name: str
|
|
17
|
-
description: Optional[str] = None
|
|
18
|
-
owner: str
|
|
19
|
-
tenant_id: str
|
|
20
|
-
group_path: Optional[str] = None
|
|
21
|
-
endpoint_url: Optional[str] = None
|
|
22
|
-
auth_type: Optional[str] = None
|
|
23
|
-
scopes: List[str] = Field(default_factory=list)
|
|
24
|
-
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
25
|
-
created_at: datetime
|
|
26
|
-
updated_at: datetime
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class AgentCreate(BaseModel):
|
|
30
|
-
"""Request model for creating an agent."""
|
|
31
|
-
|
|
32
|
-
id: str
|
|
33
|
-
name: str
|
|
34
|
-
description: Optional[str] = None
|
|
35
|
-
endpoint_url: Optional[str] = None
|
|
36
|
-
auth_type: Optional[str] = None
|
|
37
|
-
scopes: List[str] = Field(default_factory=list)
|
|
38
|
-
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
39
|
-
group_path: Optional[str] = None
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class AgentInvokeRequest(BaseModel):
|
|
43
|
-
"""Request model for invoking an agent."""
|
|
44
|
-
|
|
45
|
-
input: str
|
|
46
|
-
params: Dict[str, Any] = Field(default_factory=dict)
|
|
47
|
-
max_tokens: Optional[int] = None
|
|
48
|
-
temperature: Optional[float] = None
|
|
49
|
-
stream: bool = False
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
class AgentInvokeResult(BaseModel):
|
|
53
|
-
"""Result model for agent invocation."""
|
|
54
|
-
|
|
55
|
-
output: str
|
|
56
|
-
logs: List[str] = Field(default_factory=list)
|
|
57
|
-
usage: Optional[Dict[str, Any]] = None
|
|
58
|
-
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
59
|
-
status: str = "success"
|
|
60
|
-
|
|
61
|
-
|
|
62
12
|
class Tool(BaseModel):
|
|
63
13
|
"""Tool model."""
|
|
64
14
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-fabriq-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.19
|
|
4
4
|
Summary: Agentic Fabriq SDK: high-level client, CLI tool, DX helpers, and auth for AI agents
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Keywords: fabriq,agentic-fabriq,sdk,ai,agents,agentic,fabric,cli
|
|
@@ -17,7 +17,7 @@ Classifier: Typing :: Typed
|
|
|
17
17
|
Requires-Dist: PyJWT (>=2.8.0)
|
|
18
18
|
Requires-Dist: PyYAML (>=6.0.0)
|
|
19
19
|
Requires-Dist: aiohttp (>=3.9.0)
|
|
20
|
-
Requires-Dist: click (>=8.
|
|
20
|
+
Requires-Dist: click (>=8.1.0)
|
|
21
21
|
Requires-Dist: cryptography (>=41.0.0)
|
|
22
22
|
Requires-Dist: httpx (>=0.25.0)
|
|
23
23
|
Requires-Dist: keyring (>=25.0.0)
|
|
@@ -27,7 +27,7 @@ Requires-Dist: opentelemetry-instrumentation-httpx (>=0.41b0)
|
|
|
27
27
|
Requires-Dist: pydantic (>=2.4.0)
|
|
28
28
|
Requires-Dist: rich (>=13.7.0)
|
|
29
29
|
Requires-Dist: stevedore (>=5.1.0)
|
|
30
|
-
Requires-Dist: typer[all] (>=0.
|
|
30
|
+
Requires-Dist: typer[all] (>=0.20.0)
|
|
31
31
|
Requires-Dist: typing-extensions (>=4.0.0)
|
|
32
32
|
Project-URL: Documentation, https://docs.agentic-fabric.org
|
|
33
33
|
Project-URL: Homepage, https://github.com/agentic-fabric/agentic-fabric
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
af_cli/__init__.py,sha256=F2T4x4H3VIdmTjHRyV5DkaRvbZcdHYzAWD2hxtTplE4,188
|
|
2
2
|
af_cli/commands/__init__.py,sha256=Qngm2Yks6oTKazlxCZA_tIAHXwHoU6Oc7Pw5BGkA7W4,49
|
|
3
|
-
af_cli/commands/agents.py,sha256=XkXL0eB6miWteK-EUet-kkEqVib3L4USncMrJmmnFjY,8325
|
|
4
3
|
af_cli/commands/applications.py,sha256=o6P-rJHO3tdqOQTBksntlVO7Y6hoDSPQp_9nB1azwzc,11958
|
|
5
4
|
af_cli/commands/auth.py,sha256=gz7DLiTTV0nv2DnKaa7_9nLHevCWslG54rRKRA1alx0,11967
|
|
6
5
|
af_cli/commands/config.py,sha256=_k4ixGZfa7Yl_yUL-W1jeMQzykFcPcbw20Yr1v6_ShM,2748
|
|
7
6
|
af_cli/commands/mcp_servers.py,sha256=1hEb4tX80dYb-j_2Z3gDud_v1FcMxl83syJ2N2eLWzI,2390
|
|
8
7
|
af_cli/commands/secrets.py,sha256=z_rHfn8-KEonTC5dioBgH7sc1ppMOltFVFz-58Nqwa0,3436
|
|
9
|
-
af_cli/commands/tools.py,sha256=
|
|
8
|
+
af_cli/commands/tools.py,sha256=2D8b6WwgKflk6JOoRxi-1j9k08mwLNeKlaUU8eOqPxs,24525
|
|
10
9
|
af_cli/core/__init__.py,sha256=cQ0H7rGfaMISQPhoNe4Xfu_EKU2TqRVt2OMI7tPea5U,51
|
|
11
|
-
af_cli/core/client.py,sha256=
|
|
10
|
+
af_cli/core/client.py,sha256=AYNWdCMNtDx7LOGXwoS7_rruss479T0lD6PZ1hG5dMA,5082
|
|
12
11
|
af_cli/core/config.py,sha256=fwLUF0blNII2RKdFlJ3Hat51vwwNyxpIkU_HvViz8To,8538
|
|
13
12
|
af_cli/core/oauth.py,sha256=sCzo97TZyx8sLmo0GYv53P3zoABK6htRCivHhRpPRdI,18162
|
|
14
13
|
af_cli/core/output.py,sha256=tL5z7M-tLu2M1Yl8O4M7OPP7iQivC1b_YUUB468Od5Y,4811
|
|
15
14
|
af_cli/core/token_storage.py,sha256=WhOQLx5_9pn2lAlFX01Y5DmWO7B9BJYfEkASCsBQwaI,7738
|
|
16
|
-
af_cli/main.py,sha256=
|
|
17
|
-
af_sdk/__init__.py,sha256=
|
|
15
|
+
af_cli/main.py,sha256=zFbbUhgmpGRp9VBGOyzSIK4NlguGsBI_SqZchyOu5Xc,5100
|
|
16
|
+
af_sdk/__init__.py,sha256=uKLl3Sqlg_TgmGZtI0Tt14JNXIhdHSb6dplw6acBDvU,1569
|
|
18
17
|
af_sdk/auth/__init__.py,sha256=laIiIwgxy0mEgRoXEakoEs7BuUYnyrlzHJbdf8Sqa5E,1137
|
|
19
|
-
af_sdk/auth/application.py,sha256=kg5wSbhkQj00vje8_f1mgKVD7ax4b8vKAn_npkI9Bms,7451
|
|
20
18
|
af_sdk/auth/applications.py,sha256=tJ4UMvCJF73i55_4uqsBM04GxDHk5X0SQRWdHVJDcNM,7452
|
|
21
19
|
af_sdk/auth/dpop.py,sha256=s0uiyxxuzsVQNtSexji1htJoxrALwlf1P9507xa-M3Y,1285
|
|
22
20
|
af_sdk/auth/oauth.py,sha256=WRTrrBzs9ieiNnWfxagP6Ag4oI9k0soYjEkjfS2y5Lg,8120
|
|
@@ -24,20 +22,20 @@ af_sdk/auth/token_cache.py,sha256=X36E6K0lWqMAqlJXC3i343y8oy-uFm1q-FEdVKXdL1Y,11
|
|
|
24
22
|
af_sdk/connectors/__init__.py,sha256=8SDknAjPH5Swk3fJZRh6Mi19zDZQO7vcej3BzOdHGCc,411
|
|
25
23
|
af_sdk/connectors/base.py,sha256=m3NtB4ozPtfjjs6t91OCLjCsj1xtzyK7jc7ox-HooPg,7319
|
|
26
24
|
af_sdk/connectors/registry.py,sha256=ZH0wYIZBqDnTWJ_IhfwZzifO5r3Rkb0VlEyXDhrGWIY,8799
|
|
27
|
-
af_sdk/dx/__init__.py,sha256=
|
|
25
|
+
af_sdk/dx/__init__.py,sha256=oiLKoDKu8I62hGj3AKzvZu8WlZbGHb_80qlmxkVWc-k,130
|
|
28
26
|
af_sdk/dx/decorators.py,sha256=o_EmvE_8pp2vTgMJMgfTy5SXG_24yabuKdoytah02Hk,1294
|
|
29
|
-
af_sdk/dx/runtime.py,sha256=
|
|
30
|
-
af_sdk/events.py,sha256=
|
|
27
|
+
af_sdk/dx/runtime.py,sha256=5oowtdWtTr12odBz6pXKexSVKf2smfQDw71-jBDKr8A,2471
|
|
28
|
+
af_sdk/events.py,sha256=onz1wWwATpm71HFfcEJLhvXBDSmE0M9RC92lMS-Sd3k,22609
|
|
31
29
|
af_sdk/exceptions.py,sha256=ZVjjBeq17CGK69N2OTkVTjPXqXSI_gA7cZk9rCvARcI,4381
|
|
32
|
-
af_sdk/fabriq_client.py,sha256=
|
|
33
|
-
af_sdk/models/__init__.py,sha256=
|
|
30
|
+
af_sdk/fabriq_client.py,sha256=jYimCc310_1kt3MbFdvHQOH0gja6jh8pniwu17okH4o,11567
|
|
31
|
+
af_sdk/models/__init__.py,sha256=nSjgNEyYILZ6WIQF-oG5IT0CWV7JGuE0xNLw8sHPshM,707
|
|
34
32
|
af_sdk/models/audit.py,sha256=_wRahNV7M7ftc2AHFf7J3WzIJ5cUyZhFn_lZX9NITp8,1476
|
|
35
|
-
af_sdk/models/types.py,sha256=
|
|
33
|
+
af_sdk/models/types.py,sha256=DPrl7XuFSYRrc8el8OX-ZNcZrIZwl8rTR9cH01uZFaU,5106
|
|
36
34
|
af_sdk/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
35
|
af_sdk/transport/__init__.py,sha256=HsOc6MmlxIS-PSYC_-6E36-dZYyT_auZeoXvGzVAqeg,104
|
|
38
36
|
af_sdk/transport/http.py,sha256=QB3eqQbwug95QHf5PG_714NKtlTjV9PzVTo8izJCylc,13203
|
|
39
37
|
af_sdk/vault.py,sha256=QVNGigIw8ND5sVXt05gvUY222b5-i9EbzLWNsDGdOU4,17926
|
|
40
|
-
agentic_fabriq_sdk-0.1.
|
|
41
|
-
agentic_fabriq_sdk-0.1.
|
|
42
|
-
agentic_fabriq_sdk-0.1.
|
|
43
|
-
agentic_fabriq_sdk-0.1.
|
|
38
|
+
agentic_fabriq_sdk-0.1.19.dist-info/METADATA,sha256=pyoPnROyDWvxMf9JbXScRIXSlLGl7xiAQfEtDYeUwTE,3658
|
|
39
|
+
agentic_fabriq_sdk-0.1.19.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
40
|
+
agentic_fabriq_sdk-0.1.19.dist-info/entry_points.txt,sha256=XUO2EaJhUtUS5pwVIkhemC-nEeFbKgXXLW97gQCCGm8,41
|
|
41
|
+
agentic_fabriq_sdk-0.1.19.dist-info/RECORD,,
|
af_cli/commands/agents.py
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Agent management commands for the Agentic Fabric CLI.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
import typer
|
|
8
|
-
|
|
9
|
-
from af_cli.core.client import get_client
|
|
10
|
-
from af_cli.core.output import error, info, print_output, success, warning, prompt_confirm
|
|
11
|
-
|
|
12
|
-
app = typer.Typer(help="Agent management commands")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@app.command()
|
|
16
|
-
def list(
|
|
17
|
-
page: int = typer.Option(1, "--page", help="Page number"),
|
|
18
|
-
page_size: int = typer.Option(20, "--page-size", help="Page size"),
|
|
19
|
-
search: Optional[str] = typer.Option(None, "--search", help="Search query"),
|
|
20
|
-
format: str = typer.Option("table", "--format", help="Output format"),
|
|
21
|
-
):
|
|
22
|
-
"""List agents."""
|
|
23
|
-
try:
|
|
24
|
-
with get_client() as client:
|
|
25
|
-
params = {
|
|
26
|
-
"page": page,
|
|
27
|
-
"page_size": page_size,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if search:
|
|
31
|
-
params["search"] = search
|
|
32
|
-
|
|
33
|
-
response = client.get("/api/v1/agents", params=params)
|
|
34
|
-
|
|
35
|
-
# Support both new (items/total) and legacy (agents/total) shapes
|
|
36
|
-
if isinstance(response, dict):
|
|
37
|
-
if "items" in response:
|
|
38
|
-
agents = response.get("items", [])
|
|
39
|
-
total = response.get("total", len(agents))
|
|
40
|
-
elif "agents" in response:
|
|
41
|
-
agents = response.get("agents", [])
|
|
42
|
-
total = response.get("total", len(agents))
|
|
43
|
-
else:
|
|
44
|
-
agents = []
|
|
45
|
-
total = 0
|
|
46
|
-
elif isinstance(response, list):
|
|
47
|
-
agents = response
|
|
48
|
-
total = len(agents)
|
|
49
|
-
else:
|
|
50
|
-
agents = []
|
|
51
|
-
total = 0
|
|
52
|
-
|
|
53
|
-
if not agents:
|
|
54
|
-
warning("No agents found")
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
# Format agent data for display
|
|
58
|
-
display_data = []
|
|
59
|
-
for agent in agents:
|
|
60
|
-
display_data.append({
|
|
61
|
-
"id": agent.get("id"),
|
|
62
|
-
"name": agent.get("name"),
|
|
63
|
-
"version": agent.get("version"),
|
|
64
|
-
"protocol": agent.get("protocol"),
|
|
65
|
-
"endpoint_url": agent.get("endpoint_url"),
|
|
66
|
-
"auth_method": agent.get("auth_method"),
|
|
67
|
-
"created_at": (agent.get("created_at") or "")[:19], # Trim microseconds if present
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
print_output(
|
|
71
|
-
display_data,
|
|
72
|
-
format_type=format,
|
|
73
|
-
columns=["id", "name", "version", "protocol", "endpoint_url", "auth_method", "created_at"],
|
|
74
|
-
title=f"Agents ({len(agents)}/{total})"
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
except Exception as e:
|
|
78
|
-
error(f"Failed to list agents: {e}")
|
|
79
|
-
raise typer.Exit(1)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@app.command()
|
|
83
|
-
def get(
|
|
84
|
-
agent_id: str = typer.Argument(..., help="Agent ID"),
|
|
85
|
-
format: str = typer.Option("table", "--format", help="Output format"),
|
|
86
|
-
):
|
|
87
|
-
"""Get agent details."""
|
|
88
|
-
try:
|
|
89
|
-
with get_client() as client:
|
|
90
|
-
agent = client.get(f"/api/v1/agents/{agent_id}")
|
|
91
|
-
|
|
92
|
-
print_output(
|
|
93
|
-
agent,
|
|
94
|
-
format_type=format,
|
|
95
|
-
title=f"Agent {agent_id}"
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
except Exception as e:
|
|
99
|
-
error(f"Failed to get agent: {e}")
|
|
100
|
-
raise typer.Exit(1)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@app.command()
|
|
104
|
-
def create(
|
|
105
|
-
name: str = typer.Option(..., "--name", help="Agent name"),
|
|
106
|
-
description: Optional[str] = typer.Option(None, "--description", help="Agent description"),
|
|
107
|
-
version: str = typer.Option("1.0.0", "--version", help="Agent version"),
|
|
108
|
-
protocol: str = typer.Option("HTTP", "--protocol", help="Agent protocol"),
|
|
109
|
-
endpoint_url: str = typer.Option(..., "--endpoint-url", help="Agent endpoint URL"),
|
|
110
|
-
auth_method: str = typer.Option("OAUTH2", "--auth-method", help="Authentication method"),
|
|
111
|
-
):
|
|
112
|
-
"""Create a new agent."""
|
|
113
|
-
try:
|
|
114
|
-
with get_client() as client:
|
|
115
|
-
data = {
|
|
116
|
-
"name": name,
|
|
117
|
-
"description": description,
|
|
118
|
-
"version": version,
|
|
119
|
-
"protocol": protocol,
|
|
120
|
-
"endpoint_url": endpoint_url,
|
|
121
|
-
"auth_method": auth_method,
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
agent = client.post("/api/v1/agents", data)
|
|
125
|
-
|
|
126
|
-
success(f"Agent created: {agent['id']}")
|
|
127
|
-
info(f"Name: {agent['name']}")
|
|
128
|
-
info(f"Endpoint: {agent['endpoint_url']}")
|
|
129
|
-
|
|
130
|
-
except Exception as e:
|
|
131
|
-
error(f"Failed to create agent: {e}")
|
|
132
|
-
raise typer.Exit(1)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
@app.command()
|
|
136
|
-
def update(
|
|
137
|
-
agent_id: str = typer.Argument(..., help="Agent ID"),
|
|
138
|
-
name: Optional[str] = typer.Option(None, "--name", help="Agent name"),
|
|
139
|
-
description: Optional[str] = typer.Option(None, "--description", help="Agent description"),
|
|
140
|
-
version: Optional[str] = typer.Option(None, "--version", help="Agent version"),
|
|
141
|
-
protocol: Optional[str] = typer.Option(None, "--protocol", help="Agent protocol"),
|
|
142
|
-
endpoint_url: Optional[str] = typer.Option(None, "--endpoint-url", help="Agent endpoint URL"),
|
|
143
|
-
auth_method: Optional[str] = typer.Option(None, "--auth-method", help="Authentication method"),
|
|
144
|
-
):
|
|
145
|
-
"""Update an agent."""
|
|
146
|
-
try:
|
|
147
|
-
with get_client() as client:
|
|
148
|
-
data = {}
|
|
149
|
-
|
|
150
|
-
if name is not None:
|
|
151
|
-
data["name"] = name
|
|
152
|
-
if description is not None:
|
|
153
|
-
data["description"] = description
|
|
154
|
-
if version is not None:
|
|
155
|
-
data["version"] = version
|
|
156
|
-
if protocol is not None:
|
|
157
|
-
data["protocol"] = protocol
|
|
158
|
-
if endpoint_url is not None:
|
|
159
|
-
data["endpoint_url"] = endpoint_url
|
|
160
|
-
if auth_method is not None:
|
|
161
|
-
data["auth_method"] = auth_method
|
|
162
|
-
|
|
163
|
-
if not data:
|
|
164
|
-
error("No update data provided")
|
|
165
|
-
raise typer.Exit(1)
|
|
166
|
-
|
|
167
|
-
agent = client.put(f"/api/v1/agents/{agent_id}", data)
|
|
168
|
-
|
|
169
|
-
success(f"Agent updated: {agent['id']}")
|
|
170
|
-
info(f"Name: {agent['name']}")
|
|
171
|
-
info(f"Endpoint: {agent['endpoint_url']}")
|
|
172
|
-
|
|
173
|
-
except Exception as e:
|
|
174
|
-
error(f"Failed to update agent: {e}")
|
|
175
|
-
raise typer.Exit(1)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
@app.command()
|
|
179
|
-
def delete(
|
|
180
|
-
agent_id: str = typer.Argument(..., help="Agent ID"),
|
|
181
|
-
force: bool = typer.Option(False, "--force", help="Force deletion without confirmation"),
|
|
182
|
-
):
|
|
183
|
-
"""Delete an agent."""
|
|
184
|
-
try:
|
|
185
|
-
if not force:
|
|
186
|
-
if not prompt_confirm(f"Are you sure you want to delete agent {agent_id}?"):
|
|
187
|
-
info("Deletion cancelled")
|
|
188
|
-
return
|
|
189
|
-
|
|
190
|
-
with get_client() as client:
|
|
191
|
-
client.delete(f"/api/v1/agents/{agent_id}")
|
|
192
|
-
|
|
193
|
-
success(f"Agent deleted: {agent_id}")
|
|
194
|
-
|
|
195
|
-
except Exception as e:
|
|
196
|
-
error(f"Failed to delete agent: {e}")
|
|
197
|
-
raise typer.Exit(1)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
@app.command()
|
|
201
|
-
def invoke(
|
|
202
|
-
agent_id: str = typer.Argument(..., help="Agent ID"),
|
|
203
|
-
input_text: str = typer.Option(..., "--input", help="Input message for the agent"),
|
|
204
|
-
format: str = typer.Option("table", "--format", help="Output format"),
|
|
205
|
-
):
|
|
206
|
-
"""Invoke an agent."""
|
|
207
|
-
try:
|
|
208
|
-
with get_client() as client:
|
|
209
|
-
data = {
|
|
210
|
-
"input": input_text,
|
|
211
|
-
"parameters": {},
|
|
212
|
-
"context": {},
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
info(f"Invoking agent {agent_id}...")
|
|
216
|
-
response = client.post(f"/api/v1/agents/{agent_id}/invoke", data)
|
|
217
|
-
|
|
218
|
-
success("Agent invoked successfully")
|
|
219
|
-
|
|
220
|
-
# Display response
|
|
221
|
-
if format == "table":
|
|
222
|
-
info("Response:")
|
|
223
|
-
print(response["output"])
|
|
224
|
-
|
|
225
|
-
if response.get("metadata"):
|
|
226
|
-
info("\nMetadata:")
|
|
227
|
-
print_output(response["metadata"], format_type="yaml")
|
|
228
|
-
|
|
229
|
-
if response.get("logs"):
|
|
230
|
-
info("\nLogs:")
|
|
231
|
-
for log in response["logs"]:
|
|
232
|
-
print(f" {log}")
|
|
233
|
-
else:
|
|
234
|
-
print_output(response, format_type=format)
|
|
235
|
-
|
|
236
|
-
except Exception as e:
|
|
237
|
-
error(f"Failed to invoke agent: {e}")
|
|
238
|
-
raise typer.Exit(1)
|
af_sdk/auth/application.py
DELETED
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Authentication helpers for Agentic Fabric SDK.
|
|
3
|
-
|
|
4
|
-
Provides utilities for loading application credentials and creating
|
|
5
|
-
authenticated clients.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
import json
|
|
10
|
-
import httpx
|
|
11
|
-
from typing import Optional, List, Dict
|
|
12
|
-
import logging
|
|
13
|
-
|
|
14
|
-
from .fabriq_client import FabriqClient
|
|
15
|
-
|
|
16
|
-
logger = logging.getLogger(__name__)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class ApplicationNotFoundError(Exception):
|
|
20
|
-
"""Raised when an application configuration is not found."""
|
|
21
|
-
pass
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class AuthenticationError(Exception):
|
|
25
|
-
"""Raised when authentication fails."""
|
|
26
|
-
pass
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
async def get_application_client(
|
|
30
|
-
app_id: str,
|
|
31
|
-
config_dir: Optional[Path] = None,
|
|
32
|
-
gateway_url: Optional[str] = None,
|
|
33
|
-
) -> FabriqClient:
|
|
34
|
-
"""
|
|
35
|
-
Get authenticated FabriqClient for an application.
|
|
36
|
-
|
|
37
|
-
Automatically loads credentials from ~/.af/applications/{app_id}.json
|
|
38
|
-
and exchanges them for a JWT token.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
app_id: Application identifier (e.g., "my-slack-bot")
|
|
42
|
-
config_dir: Optional custom config directory (default: ~/.af)
|
|
43
|
-
gateway_url: Optional gateway URL override (default: from app config)
|
|
44
|
-
|
|
45
|
-
Returns:
|
|
46
|
-
Authenticated FabriqClient instance
|
|
47
|
-
|
|
48
|
-
Raises:
|
|
49
|
-
ApplicationNotFoundError: If application config doesn't exist
|
|
50
|
-
AuthenticationError: If authentication fails
|
|
51
|
-
|
|
52
|
-
Example:
|
|
53
|
-
>>> client = await get_application_client("my-slack-bot")
|
|
54
|
-
>>> result = await client.invoke_tool("slack-uuid", "post_message", {...})
|
|
55
|
-
"""
|
|
56
|
-
# 1. Load application config
|
|
57
|
-
try:
|
|
58
|
-
app_config = load_application_config(app_id, config_dir)
|
|
59
|
-
except FileNotFoundError as e:
|
|
60
|
-
raise ApplicationNotFoundError(
|
|
61
|
-
f"Application '{app_id}' not found. "
|
|
62
|
-
f"Register it first with: afctl applications create --app-id {app_id} ..."
|
|
63
|
-
) from e
|
|
64
|
-
|
|
65
|
-
# Use provided gateway_url or fall back to config
|
|
66
|
-
base_url = gateway_url or app_config.get("gateway_url", "https://dashboard.agenticfabriq.com")
|
|
67
|
-
|
|
68
|
-
# 2. Exchange credentials for JWT token
|
|
69
|
-
try:
|
|
70
|
-
async with httpx.AsyncClient() as http:
|
|
71
|
-
response = await http.post(
|
|
72
|
-
f"{base_url}/api/v1/applications/token",
|
|
73
|
-
json={
|
|
74
|
-
"app_id": app_config["app_id"],
|
|
75
|
-
"secret_key": app_config["secret_key"]
|
|
76
|
-
},
|
|
77
|
-
timeout=30.0
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
if response.status_code != 200:
|
|
81
|
-
error_detail = response.text
|
|
82
|
-
try:
|
|
83
|
-
error_json = response.json()
|
|
84
|
-
error_detail = error_json.get("detail", response.text)
|
|
85
|
-
except:
|
|
86
|
-
pass
|
|
87
|
-
|
|
88
|
-
raise AuthenticationError(
|
|
89
|
-
f"Failed to authenticate application '{app_id}': {error_detail}"
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
token_data = response.json()
|
|
93
|
-
except httpx.HTTPError as e:
|
|
94
|
-
raise AuthenticationError(
|
|
95
|
-
f"Network error while authenticating application '{app_id}': {e}"
|
|
96
|
-
) from e
|
|
97
|
-
|
|
98
|
-
# 3. Create and return authenticated client
|
|
99
|
-
client = FabriqClient(
|
|
100
|
-
base_url=base_url,
|
|
101
|
-
auth_token=token_data["access_token"]
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
# Store metadata for potential refresh
|
|
105
|
-
client._app_id = app_id
|
|
106
|
-
client._expires_in = token_data.get("expires_in", 86400)
|
|
107
|
-
|
|
108
|
-
logger.info(
|
|
109
|
-
f"Authenticated as application '{app_id}' "
|
|
110
|
-
f"(user_id={token_data.get('user_id')}, tenant_id={token_data.get('tenant_id')})"
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
return client
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def load_application_config(
|
|
117
|
-
app_id: str,
|
|
118
|
-
config_dir: Optional[Path] = None
|
|
119
|
-
) -> Dict:
|
|
120
|
-
"""
|
|
121
|
-
Load application config from disk.
|
|
122
|
-
|
|
123
|
-
Args:
|
|
124
|
-
app_id: Application identifier
|
|
125
|
-
config_dir: Optional custom config directory (default: ~/.af)
|
|
126
|
-
|
|
127
|
-
Returns:
|
|
128
|
-
Application configuration dictionary
|
|
129
|
-
|
|
130
|
-
Raises:
|
|
131
|
-
FileNotFoundError: If application config doesn't exist
|
|
132
|
-
|
|
133
|
-
Example:
|
|
134
|
-
>>> config = load_application_config("my-slack-bot")
|
|
135
|
-
>>> print(config["app_id"], config["created_at"])
|
|
136
|
-
"""
|
|
137
|
-
if config_dir is None:
|
|
138
|
-
config_dir = Path.home() / ".af"
|
|
139
|
-
|
|
140
|
-
app_file = config_dir / "applications" / f"{app_id}.json"
|
|
141
|
-
|
|
142
|
-
if not app_file.exists():
|
|
143
|
-
raise FileNotFoundError(
|
|
144
|
-
f"Application '{app_id}' not found at {app_file}. "
|
|
145
|
-
f"Register it with: afctl applications create --app-id {app_id}"
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
with open(app_file, "r") as f:
|
|
149
|
-
return json.load(f)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def save_application_config(
|
|
153
|
-
app_id: str,
|
|
154
|
-
config: Dict,
|
|
155
|
-
config_dir: Optional[Path] = None
|
|
156
|
-
) -> Path:
|
|
157
|
-
"""
|
|
158
|
-
Save application config to disk.
|
|
159
|
-
|
|
160
|
-
Args:
|
|
161
|
-
app_id: Application identifier
|
|
162
|
-
config: Application configuration dictionary
|
|
163
|
-
config_dir: Optional custom config directory (default: ~/.af)
|
|
164
|
-
|
|
165
|
-
Returns:
|
|
166
|
-
Path to saved config file
|
|
167
|
-
|
|
168
|
-
Example:
|
|
169
|
-
>>> config = {
|
|
170
|
-
... "app_id": "my-bot",
|
|
171
|
-
... "secret_key": "sk_...",
|
|
172
|
-
... "gateway_url": "https://dashboard.agenticfabriq.com"
|
|
173
|
-
... }
|
|
174
|
-
>>> path = save_application_config("my-bot", config)
|
|
175
|
-
"""
|
|
176
|
-
if config_dir is None:
|
|
177
|
-
config_dir = Path.home() / ".af"
|
|
178
|
-
|
|
179
|
-
# Create applications directory if it doesn't exist
|
|
180
|
-
app_dir = config_dir / "applications"
|
|
181
|
-
app_dir.mkdir(parents=True, exist_ok=True)
|
|
182
|
-
|
|
183
|
-
# Write config file
|
|
184
|
-
app_file = app_dir / f"{app_id}.json"
|
|
185
|
-
with open(app_file, "w") as f:
|
|
186
|
-
json.dump(config, f, indent=2)
|
|
187
|
-
|
|
188
|
-
# Secure the file (user read/write only)
|
|
189
|
-
app_file.chmod(0o600)
|
|
190
|
-
|
|
191
|
-
logger.info(f"Saved application config to {app_file}")
|
|
192
|
-
|
|
193
|
-
return app_file
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def list_applications(
|
|
197
|
-
config_dir: Optional[Path] = None
|
|
198
|
-
) -> List[Dict]:
|
|
199
|
-
"""
|
|
200
|
-
List all registered applications.
|
|
201
|
-
|
|
202
|
-
Args:
|
|
203
|
-
config_dir: Optional custom config directory (default: ~/.af)
|
|
204
|
-
|
|
205
|
-
Returns:
|
|
206
|
-
List of application configuration dictionaries
|
|
207
|
-
|
|
208
|
-
Example:
|
|
209
|
-
>>> apps = list_applications()
|
|
210
|
-
>>> for app in apps:
|
|
211
|
-
... print(f"{app['app_id']}: {app.get('name', 'N/A')}")
|
|
212
|
-
"""
|
|
213
|
-
if config_dir is None:
|
|
214
|
-
config_dir = Path.home() / ".af"
|
|
215
|
-
|
|
216
|
-
app_dir = config_dir / "applications"
|
|
217
|
-
|
|
218
|
-
if not app_dir.exists():
|
|
219
|
-
return []
|
|
220
|
-
|
|
221
|
-
apps = []
|
|
222
|
-
for app_file in sorted(app_dir.glob("*.json")):
|
|
223
|
-
try:
|
|
224
|
-
with open(app_file, "r") as f:
|
|
225
|
-
app_config = json.load(f)
|
|
226
|
-
apps.append(app_config)
|
|
227
|
-
except Exception as e:
|
|
228
|
-
logger.warning(f"Failed to load application config from {app_file}: {e}")
|
|
229
|
-
|
|
230
|
-
return apps
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def delete_application_config(
|
|
234
|
-
app_id: str,
|
|
235
|
-
config_dir: Optional[Path] = None
|
|
236
|
-
) -> bool:
|
|
237
|
-
"""
|
|
238
|
-
Delete application config from disk.
|
|
239
|
-
|
|
240
|
-
Args:
|
|
241
|
-
app_id: Application identifier
|
|
242
|
-
config_dir: Optional custom config directory (default: ~/.af)
|
|
243
|
-
|
|
244
|
-
Returns:
|
|
245
|
-
True if deleted, False if not found
|
|
246
|
-
|
|
247
|
-
Example:
|
|
248
|
-
>>> deleted = delete_application_config("my-old-bot")
|
|
249
|
-
>>> if deleted:
|
|
250
|
-
... print("Deleted successfully")
|
|
251
|
-
"""
|
|
252
|
-
if config_dir is None:
|
|
253
|
-
config_dir = Path.home() / ".af"
|
|
254
|
-
|
|
255
|
-
app_file = config_dir / "applications" / f"{app_id}.json"
|
|
256
|
-
|
|
257
|
-
if not app_file.exists():
|
|
258
|
-
return False
|
|
259
|
-
|
|
260
|
-
app_file.unlink()
|
|
261
|
-
logger.info(f"Deleted application config: {app_file}")
|
|
262
|
-
|
|
263
|
-
return True
|
|
264
|
-
|
|
File without changes
|
{agentic_fabriq_sdk-0.1.17.dist-info → agentic_fabriq_sdk-0.1.19.dist-info}/entry_points.txt
RENAMED
|
File without changes
|