agentic-fabriq-sdk 0.1.18__tar.gz → 0.1.20__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.

Potentially problematic release.


This version of agentic-fabriq-sdk might be problematic. Click here for more details.

Files changed (40) hide show
  1. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/PKG-INFO +3 -3
  2. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/commands/tools.py +36 -54
  3. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/core/client.py +29 -0
  4. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/fabriq_client.py +54 -2
  5. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/models/__init__.py +0 -8
  6. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/pyproject.toml +3 -3
  7. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/README.md +0 -0
  8. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/__init__.py +0 -0
  9. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/commands/__init__.py +0 -0
  10. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/commands/applications.py +0 -0
  11. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/commands/auth.py +0 -0
  12. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/commands/config.py +0 -0
  13. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/commands/mcp_servers.py +0 -0
  14. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/commands/secrets.py +0 -0
  15. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/core/__init__.py +0 -0
  16. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/core/config.py +0 -0
  17. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/core/oauth.py +0 -0
  18. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/core/output.py +0 -0
  19. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/core/token_storage.py +0 -0
  20. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_cli/main.py +0 -0
  21. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/__init__.py +0 -0
  22. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/auth/__init__.py +0 -0
  23. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/auth/applications.py +0 -0
  24. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/auth/dpop.py +0 -0
  25. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/auth/oauth.py +0 -0
  26. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/auth/token_cache.py +0 -0
  27. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/connectors/__init__.py +0 -0
  28. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/connectors/base.py +0 -0
  29. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/connectors/registry.py +0 -0
  30. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/dx/__init__.py +0 -0
  31. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/dx/decorators.py +0 -0
  32. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/dx/runtime.py +0 -0
  33. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/events.py +0 -0
  34. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/exceptions.py +0 -0
  35. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/models/audit.py +0 -0
  36. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/models/types.py +0 -0
  37. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/py.typed +0 -0
  38. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/transport/__init__.py +0 -0
  39. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/transport/http.py +0 -0
  40. {agentic_fabriq_sdk-0.1.18 → agentic_fabriq_sdk-0.1.20}/af_sdk/vault.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-fabriq-sdk
3
- Version: 0.1.18
3
+ Version: 0.1.20
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.0.0)
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.12.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
@@ -121,17 +121,20 @@ def get(
121
121
 
122
122
  @app.command()
123
123
  def invoke(
124
- tool_identifier: str = typer.Argument(..., help="Tool name (e.g., 'slack') or UUID"),
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 by name or UUID.
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 slack --method get_channels
133
- afctl tools invoke slack --method post_message --params '{"channel": "test", "text": "Hello!"}'
134
- afctl tools invoke c17b55af-18f8-49be-b9e9-44b1d1be429e --method get_channels
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
- # Try to resolve tool name to UUID if not already a UUID
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
- info(f"Invoking tool {tool_identifier} method {method}...")
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/{tool}/connection?connection_id={connection_id}"
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}")
@@ -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)
@@ -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,58 @@ class FabriqClient:
142
146
  body = {"method": method}
143
147
  if parameters is not None:
144
148
  body["parameters"] = parameters
145
- if context is not None:
146
- body["context"] = context
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 directly uses the connection_id (like 'slacker', 'gurt') to invoke
170
+ the tool without needing to look up UUIDs. This is the most efficient
171
+ and reliable way to invoke tools.
172
+
173
+ Args:
174
+ connection_id: Connection identifier (e.g., 'slacker', 'gurt')
175
+ method: Method name to invoke
176
+ parameters: Method parameters
177
+
178
+ Returns:
179
+ Tool invocation result
180
+
181
+ Examples:
182
+ result = await client.invoke_connection("slacker", method="get_channels")
183
+ result = await client.invoke_connection("slacker", method="post_message",
184
+ parameters={"channel": "test", "text": "Hello!"})
185
+ result = await client.invoke_connection("gurt", method="list_files")
186
+ """
187
+ # Use the direct connection-based invoke endpoint
188
+ # This matches what the CLI uses and is more efficient
189
+ body = {
190
+ "method": method,
191
+ "parameters": parameters or {},
192
+ }
193
+
194
+ # Call the connection-based invoke endpoint
195
+ r = await self._http.post(
196
+ f"/tools/connections/{connection_id}/invoke",
197
+ json=body,
198
+ headers=self._extra_headers
199
+ )
200
+ return r.json()
149
201
 
150
202
  # -----------------
151
203
  # MCP Servers
@@ -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",
@@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api"
5
5
 
6
6
  [tool.poetry]
7
7
  name = "agentic-fabriq-sdk"
8
- version = "0.1.18"
8
+ version = "0.1.20"
9
9
  description = "Agentic Fabriq SDK: high-level client, CLI tool, DX helpers, and auth for AI agents"
10
10
  readme = "README.md"
11
11
  license = "Apache-2.0"
@@ -43,11 +43,11 @@ aiohttp = ">=3.9.0"
43
43
  # Plugin/extension system
44
44
  stevedore = ">=5.1.0"
45
45
  # CLI dependencies
46
- typer = {extras = ["all"], version = ">=0.12.0"}
46
+ typer = {extras = ["all"], version = ">=0.20.0"}
47
47
  rich = ">=13.7.0"
48
48
  keyring = ">=25.0.0"
49
49
  cryptography = ">=41.0.0"
50
- click = ">=8.0.0"
50
+ click = ">=8.1.0"
51
51
  PyYAML = ">=6.0.0"
52
52
 
53
53
  [tool.poetry.scripts]