agentic-fabriq-sdk 0.1.17__tar.gz → 0.1.18__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 (44) hide show
  1. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/PKG-INFO +1 -1
  2. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/main.py +0 -2
  3. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/__init__.py +0 -4
  4. agentic_fabriq_sdk-0.1.18/af_sdk/dx/__init__.py +10 -0
  5. agentic_fabriq_sdk-0.1.18/af_sdk/dx/runtime.py +64 -0
  6. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/events.py +14 -50
  7. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/fabriq_client.py +0 -12
  8. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/models/types.py +0 -50
  9. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/pyproject.toml +1 -1
  10. agentic_fabriq_sdk-0.1.17/af_cli/commands/agents.py +0 -238
  11. agentic_fabriq_sdk-0.1.17/af_sdk/auth/application.py +0 -264
  12. agentic_fabriq_sdk-0.1.17/af_sdk/dx/__init__.py +0 -12
  13. agentic_fabriq_sdk-0.1.17/af_sdk/dx/runtime.py +0 -170
  14. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/README.md +0 -0
  15. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/__init__.py +0 -0
  16. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/commands/__init__.py +0 -0
  17. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/commands/applications.py +0 -0
  18. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/commands/auth.py +0 -0
  19. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/commands/config.py +0 -0
  20. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/commands/mcp_servers.py +0 -0
  21. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/commands/secrets.py +0 -0
  22. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/commands/tools.py +0 -0
  23. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/core/__init__.py +0 -0
  24. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/core/client.py +0 -0
  25. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/core/config.py +0 -0
  26. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/core/oauth.py +0 -0
  27. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/core/output.py +0 -0
  28. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_cli/core/token_storage.py +0 -0
  29. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/auth/__init__.py +0 -0
  30. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/auth/applications.py +0 -0
  31. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/auth/dpop.py +0 -0
  32. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/auth/oauth.py +0 -0
  33. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/auth/token_cache.py +0 -0
  34. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/connectors/__init__.py +0 -0
  35. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/connectors/base.py +0 -0
  36. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/connectors/registry.py +0 -0
  37. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/dx/decorators.py +0 -0
  38. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/exceptions.py +0 -0
  39. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/models/__init__.py +0 -0
  40. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/models/audit.py +0 -0
  41. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/py.typed +0 -0
  42. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/transport/__init__.py +0 -0
  43. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/af_sdk/transport/http.py +0 -0
  44. {agentic_fabriq_sdk-0.1.17 → agentic_fabriq_sdk-0.1.18}/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.17
3
+ Version: 0.1.18
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
@@ -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")
@@ -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",
@@ -0,0 +1,10 @@
1
+ from .decorators import tool
2
+ from .runtime import ToolFabric, MCPServer
3
+
4
+ __all__ = [
5
+ "tool",
6
+ "ToolFabric",
7
+ "MCPServer",
8
+ ]
9
+
10
+
@@ -0,0 +1,64 @@
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
+
@@ -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[AgentEvent, ToolEvent, InvocationEvent, SecretEvent]
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
- metadata = EventMetadata(
132
- source=source,
133
- correlation_id=correlation_id,
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[[AgentEvent], Awaitable[None]],
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
- subject = "agents.*"
294
- if tenant_id:
295
- subject += f".{tenant_id}"
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
- await publisher.publish_agent_event(
646
- event_type="registered",
647
- agent_id=agent_id,
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(
@@ -184,18 +184,6 @@ class FabriqClient:
184
184
  r = await self._http.post(f"/proxy/mcp/{server_id}/invoke", json={"payload": payload, "raw": raw}, headers=self._extra_headers)
185
185
  return r.json()
186
186
 
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
187
  # -----------------
200
188
  # Secrets (Gateway-backed Vault)
201
189
  # -----------------
@@ -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
 
@@ -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.17"
8
+ version = "0.1.18"
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"
@@ -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)
@@ -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
-
@@ -1,12 +0,0 @@
1
- from .decorators import tool
2
- from .runtime import ToolFabric, AgentFabric, MCPServer, Agent
3
-
4
- __all__ = [
5
- "tool",
6
- "ToolFabric",
7
- "AgentFabric",
8
- "MCPServer",
9
- "Agent",
10
- ]
11
-
12
-
@@ -1,170 +0,0 @@
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
-