haive-hap 1.0.0__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.
haive/hap/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # HAP (Haive Agent Protocol) Module
2
+
3
+ This module implements the Haive Agent Protocol - a JSON-RPC 2.0 based protocol for agent communication and orchestration.
4
+
5
+ ## Overview
6
+
7
+ HAP is the protocol layer that enables:
8
+ - Agent discovery and registration
9
+ - Resource management
10
+ - Progress tracking
11
+ - Authentication and authorization
12
+ - Tool and prompt exposure
13
+
14
+ ## Components
15
+
16
+ ### Core Classes
17
+
18
+ - **HAPContext**: Execution context with logging, progress, and resource access
19
+ - **HAPServer**: JSON-RPC server for agent exposure
20
+ - **HAPClient**: Client for interacting with HAP servers
21
+ - **Resource Providers**: Pluggable resource access
22
+ - **Auth Providers**: Authentication and authorization
23
+
24
+ ### Protocol Structure
25
+
26
+ HAP follows JSON-RPC 2.0 with extensions for:
27
+ - Progress notifications
28
+ - Streaming responses
29
+ - Resource URIs
30
+ - Authentication tokens
31
+
32
+ ```python
33
+ # Example HAP request
34
+ {
35
+ "jsonrpc": "2.0",
36
+ "method": "agent/execute",
37
+ "params": {
38
+ "agent": "analyzer",
39
+ "input": {"text": "..."}
40
+ },
41
+ "id": "req-123"
42
+ }
43
+ ```
44
+
45
+ ## Key Features
46
+
47
+ ### Context Management
48
+
49
+ ```python
50
+ from haive.hap.hap import HAPContext
51
+
52
+ context = HAPContext(request_id="req-123")
53
+ await context.info("Starting agent execution")
54
+ await context.report_progress(0, 100, "Initializing")
55
+ ```
56
+
57
+ ### Resource Access
58
+
59
+ ```python
60
+ # Read resources through context
61
+ data = await context.read_resource("file://data.json")
62
+ config = await context.read_resource("config://agent/settings")
63
+ ```
64
+
65
+ ### Authentication
66
+
67
+ ```python
68
+ from haive.hap.hap import SimpleAuthProvider
69
+
70
+ auth = SimpleAuthProvider(user="alice", scopes=["execute", "read"])
71
+ context = HAPContext(auth_provider=auth)
72
+
73
+ if context.has_scope("execute"):
74
+ # Allowed to execute agents
75
+ pass
76
+ ```
77
+
78
+ ## Integration with AGP
79
+
80
+ HAP provides the protocol layer for AGP:
81
+
82
+ ```python
83
+ # Future implementation
84
+ server = HAPServer()
85
+
86
+ # Register HAP runtime
87
+ server.register_runtime(agp_runtime)
88
+
89
+ # Expose agents
90
+ server.expose_agent("analyzer", analyzer_agent)
91
+ server.expose_agent("writer", writer_agent)
92
+
93
+ # Start server
94
+ await server.start(port=8080)
95
+ ```
96
+
97
+ ## Protocol Methods
98
+
99
+ ### Standard Methods
100
+
101
+ - `agent/list` - List available agents
102
+ - `agent/execute` - Execute an agent
103
+ - `agent/describe` - Get agent metadata
104
+ - `resource/read` - Read a resource
105
+ - `resource/list` - List available resources
106
+
107
+ ### Notifications
108
+
109
+ - `progress` - Progress updates
110
+ - `log` - Log messages
111
+ - `state` - State changes
112
+
113
+ ## Future Development
114
+
115
+ 1. **Full Protocol Implementation**: Complete JSON-RPC server/client
116
+ 2. **MCP Compatibility**: Bridge between HAP and MCP
117
+ 3. **Discovery Service**: Agent discovery and registration
118
+ 4. **Distributed Execution**: Multi-server agent orchestration
haive/hap/__init__.py ADDED
@@ -0,0 +1,66 @@
1
+ """
2
+ Haive Agent Protocol (HAP) - MCP-like server for agents and graphs.
3
+
4
+ This module provides a standardized protocol for exposing Haive agents,
5
+ graphs, and state schemas as servers with tools, resources, and prompts.
6
+ """
7
+
8
+ # Core models
9
+ from haive.hap.models.graph import HAPGraph, HAPNode
10
+ from haive.hap.models.context import HAPContext
11
+
12
+ # Protocol types from types package
13
+ from haive.hap.types import (
14
+ HAPRequest,
15
+ HAPResponse,
16
+ HAPError,
17
+ ErrorCode,
18
+ ToolInfo,
19
+ ResourceInfo,
20
+ PromptInfo,
21
+ PromptArgument,
22
+ AgentInfo,
23
+ GraphInfo,
24
+ NodeInfo,
25
+ EdgeInfo,
26
+ ServerRegistration,
27
+ ServerCapability,
28
+ AgentExecutionRequest,
29
+ AgentExecutionResult,
30
+ GraphExecutionRequest,
31
+ GraphExecutionResult,
32
+ BaseInfo,
33
+ # Aliases
34
+ AgentId,
35
+ Entrypoint,
36
+ JSONMap
37
+ )
38
+
39
+ __version__ = "0.1.0"
40
+
41
+ __all__ = [
42
+ # Core models
43
+ "HAPGraph",
44
+ "HAPNode",
45
+ "HAPContext",
46
+ # Protocol types
47
+ "HAPRequest",
48
+ "HAPResponse",
49
+ "HAPError",
50
+ "ErrorCode",
51
+ "ToolInfo",
52
+ "ResourceInfo",
53
+ "PromptInfo",
54
+ "PromptArgument",
55
+ "AgentInfo",
56
+ "GraphInfo",
57
+ "NodeInfo",
58
+ "EdgeInfo",
59
+ "ServerRegistration",
60
+ "ServerCapability",
61
+ "AgentExecutionRequest",
62
+ "AgentExecutionResult",
63
+ "GraphExecutionRequest",
64
+ "GraphExecutionResult",
65
+ "BaseInfo"
66
+ ]
File without changes
@@ -0,0 +1,5 @@
1
+ from .base import HAPClientProtocol
2
+ from .local import LocalClient
3
+ from .http import HTTPClient
4
+
5
+ __all__ = ["HAPClientProtocol", "LocalClient", "HTTPClient"]
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+ from typing import Protocol, Mapping, Any
3
+ from ..models.context import HAPContext
4
+ from ..models.graph import AgentGraph
5
+
6
+ class HAPClientProtocol(Protocol):
7
+ def run_agent(self, entrypoint: str, *, inputs: Mapping[str, Any] | None = None) -> HAPContext: ...
8
+ def run_graph(self, graph: AgentGraph, *, inputs: Mapping[str, Any] | None = None) -> HAPContext: ...
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+ from typing import Mapping, Any
3
+ import json
4
+ import urllib.request
5
+ from ..models.context import HAPContext
6
+ from ..models.graph import AgentGraph
7
+
8
+ class HTTPClient:
9
+ """Very small HTTP client for a future HAP server.
10
+
11
+ Expects a server that exposes /run-agent and /run-graph endpoints.
12
+
13
+ This is a placeholder you can wire to your FastAPI app later.
14
+ """
15
+ def __init__(self, base_url: str, *, timeout: float = 30.0):
16
+ self.base_url = base_url.rstrip("/")
17
+ self.timeout = timeout
18
+
19
+ def _post_json(self, path: str, payload: dict) -> dict:
20
+ data = json.dumps(payload).encode("utf-8")
21
+ req = urllib.request.Request(self.base_url + path, data=data, headers={"Content-Type": "application/json"})
22
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
23
+ return json.loads(resp.read().decode("utf-8"))
24
+
25
+ def run_agent(self, entrypoint: str, *, inputs: Mapping[str, Any] | None = None) -> HAPContext:
26
+ res = self._post_json("/run-agent", {"entrypoint": entrypoint, "inputs": dict(inputs or {})})
27
+ return HAPContext.model_validate(res)
28
+
29
+ def run_graph(self, graph: AgentGraph, *, inputs: Mapping[str, Any] | None = None) -> HAPContext:
30
+ res = self._post_json("/run-graph", {"graph": graph.model_dump(), "inputs": dict(inputs or {})})
31
+ return HAPContext.model_validate(res)
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+ from typing import Mapping, Any
3
+ from ..models.context import HAPContext
4
+ from ..models.graph import AgentGraph
5
+ from ..server.runtime import HAPRuntime
6
+ import importlib
7
+ from haive.agents.base.agent import Agent
8
+
9
+ class LocalClient:
10
+ """Runs agents/graphs in-process using the local runtime."""
11
+
12
+ def run_agent(self, entrypoint: str, *, inputs: Mapping[str, Any] | None = None) -> HAPContext:
13
+ ctx = HAPContext(inputs=dict(inputs or {}))
14
+ # Build a trivial one-node graph for the single agent
15
+ from ..models.graph import AgentGraph, AgentNode
16
+ g = AgentGraph(nodes={"__single__": AgentNode(id="__single__", agent=entrypoint)}, entry="__single__")
17
+ return HAPRuntime(g).run(ctx)
18
+
19
+ def run_graph(self, graph: AgentGraph, *, inputs: Mapping[str, Any] | None = None) -> HAPContext:
20
+ ctx = HAPContext(inputs=dict(inputs or {}))
21
+ return HAPRuntime(graph).run(ctx)
haive/hap/context.py ADDED
@@ -0,0 +1,295 @@
1
+ """
2
+ Context system for HAP, providing execution context for tools and resources.
3
+
4
+ Similar to MCP's context, this allows tools to:
5
+ - Log messages at different levels
6
+ - Report progress
7
+ - Access resources
8
+ - Get authentication info
9
+ """
10
+
11
+ from typing import Optional, Any, Dict, List, Callable, Protocol
12
+ from datetime import datetime
13
+ import asyncio
14
+ from enum import Enum
15
+
16
+
17
+ class LogLevel(str, Enum):
18
+ """Log levels for context messages."""
19
+ DEBUG = "debug"
20
+ INFO = "info"
21
+ WARNING = "warning"
22
+ ERROR = "error"
23
+
24
+
25
+ class ProgressState(Enum):
26
+ """Progress reporting state."""
27
+ STARTED = "started"
28
+ IN_PROGRESS = "in_progress"
29
+ COMPLETED = "completed"
30
+ FAILED = "failed"
31
+
32
+
33
+ class IResourceProvider(Protocol):
34
+ """Interface for resource providers."""
35
+
36
+ async def read_resource(self, uri: str) -> Any:
37
+ """Read a resource by URI."""
38
+ ...
39
+
40
+
41
+ class IAuthProvider(Protocol):
42
+ """Interface for authentication providers."""
43
+
44
+ def get_current_user(self) -> Optional[str]:
45
+ """Get the current authenticated user."""
46
+ ...
47
+
48
+ def get_user_scopes(self) -> List[str]:
49
+ """Get the current user's scopes."""
50
+ ...
51
+
52
+
53
+ class HAPContext:
54
+ """
55
+ Execution context for HAP tools and resources.
56
+
57
+ Provides utilities for logging, progress reporting, resource access,
58
+ and authentication info during execution.
59
+ """
60
+
61
+ def __init__(
62
+ self,
63
+ request_id: str | int,
64
+ resource_provider: Optional[IResourceProvider] = None,
65
+ auth_provider: Optional[IAuthProvider] = None,
66
+ log_handler: Optional[Callable[[str, LogLevel, dict], None]] = None,
67
+ progress_handler: Optional[Callable[[int, int, str, ProgressState], None]] = None,
68
+ ):
69
+ self.request_id = request_id
70
+ self._resource_provider = resource_provider
71
+ self._auth_provider = auth_provider
72
+ self._log_handler = log_handler
73
+ self._progress_handler = progress_handler
74
+
75
+ # Track progress for nested operations
76
+ self._progress_stack: List[tuple[int, str]] = []
77
+
78
+ # Store context data
79
+ self._data: Dict[str, Any] = {}
80
+
81
+ # Logging Methods
82
+
83
+ async def debug(self, message: str, **kwargs):
84
+ """Log debug message."""
85
+ await self._log(LogLevel.DEBUG, message, kwargs)
86
+
87
+ async def info(self, message: str, **kwargs):
88
+ """Log info message."""
89
+ await self._log(LogLevel.INFO, message, kwargs)
90
+
91
+ async def warning(self, message: str, **kwargs):
92
+ """Log warning message."""
93
+ await self._log(LogLevel.WARNING, message, kwargs)
94
+
95
+ async def error(self, message: str, **kwargs):
96
+ """Log error message."""
97
+ await self._log(LogLevel.ERROR, message, kwargs)
98
+
99
+ async def _log(self, level: LogLevel, message: str, data: dict):
100
+ """Internal logging method."""
101
+ if self._log_handler:
102
+ log_entry = {
103
+ "timestamp": datetime.now().isoformat(),
104
+ "level": level.value,
105
+ "message": message,
106
+ "request_id": self.request_id,
107
+ **data
108
+ }
109
+ # Run in background to not block
110
+ asyncio.create_task(
111
+ asyncio.to_thread(self._log_handler, message, level, log_entry)
112
+ )
113
+
114
+ # Progress Reporting
115
+
116
+ async def report_progress(
117
+ self,
118
+ current: int,
119
+ total: int,
120
+ message: str = "",
121
+ state: Optional[ProgressState] = None
122
+ ):
123
+ """
124
+ Report progress for the current operation.
125
+
126
+ Args:
127
+ current: Current progress value
128
+ total: Total expected value
129
+ message: Optional progress message
130
+ state: Optional progress state
131
+ """
132
+ if not state:
133
+ if current == 0:
134
+ state = ProgressState.STARTED
135
+ elif current >= total:
136
+ state = ProgressState.COMPLETED
137
+ else:
138
+ state = ProgressState.IN_PROGRESS
139
+
140
+ if self._progress_handler:
141
+ asyncio.create_task(
142
+ asyncio.to_thread(
143
+ self._progress_handler,
144
+ current,
145
+ total,
146
+ message,
147
+ state
148
+ )
149
+ )
150
+
151
+ def start_nested_progress(self, total: int, description: str = ""):
152
+ """Start a nested progress operation."""
153
+ self._progress_stack.append((total, description))
154
+
155
+ async def update_nested_progress(self, current: int, message: str = ""):
156
+ """Update the current nested progress."""
157
+ if self._progress_stack:
158
+ total, description = self._progress_stack[-1]
159
+ full_message = f"{description}: {message}" if description else message
160
+ await self.report_progress(current, total, full_message)
161
+
162
+ def end_nested_progress(self):
163
+ """End the current nested progress operation."""
164
+ if self._progress_stack:
165
+ total, description = self._progress_stack.pop()
166
+ asyncio.create_task(
167
+ self.report_progress(total, total, f"{description} completed")
168
+ )
169
+
170
+ # Resource Access
171
+
172
+ async def read_resource(self, uri: str) -> Any:
173
+ """
174
+ Read a resource by URI.
175
+
176
+ Args:
177
+ uri: Resource URI (e.g., "agent://my-agent/state")
178
+
179
+ Returns:
180
+ Resource content
181
+
182
+ Raises:
183
+ ResourceNotFoundError: If resource doesn't exist
184
+ PermissionError: If access is denied
185
+ """
186
+ if not self._resource_provider:
187
+ raise RuntimeError("No resource provider configured")
188
+
189
+ await self.debug(f"Reading resource: {uri}")
190
+ try:
191
+ result = await self._resource_provider.read_resource(uri)
192
+ await self.debug(f"Successfully read resource: {uri}")
193
+ return result
194
+ except Exception as e:
195
+ await self.error(f"Failed to read resource {uri}: {str(e)}")
196
+ raise
197
+
198
+ # Authentication
199
+
200
+ def get_auth_user(self) -> Optional[str]:
201
+ """Get the current authenticated user."""
202
+ if not self._auth_provider:
203
+ return None
204
+ return self._auth_provider.get_current_user()
205
+
206
+ def get_auth_scopes(self) -> List[str]:
207
+ """Get the current user's scopes."""
208
+ if not self._auth_provider:
209
+ return []
210
+ return self._auth_provider.get_user_scopes()
211
+
212
+ def has_scope(self, scope: str) -> bool:
213
+ """Check if the current user has a specific scope."""
214
+ return scope in self.get_auth_scopes()
215
+
216
+ # Context Data
217
+
218
+ def set_data(self, key: str, value: Any):
219
+ """Store data in the context."""
220
+ self._data[key] = value
221
+
222
+ def get_data(self, key: str, default: Any = None) -> Any:
223
+ """Retrieve data from the context."""
224
+ return self._data.get(key, default)
225
+
226
+ def update_data(self, data: dict):
227
+ """Update context data with a dictionary."""
228
+ self._data.update(data)
229
+
230
+ # Utilities
231
+
232
+ async def with_progress(
233
+ self,
234
+ operation: Callable,
235
+ total: int,
236
+ description: str = "Processing"
237
+ ):
238
+ """
239
+ Execute an operation with automatic progress tracking.
240
+
241
+ Args:
242
+ operation: Async callable that accepts (current, context) args
243
+ total: Total number of items
244
+ description: Progress description
245
+ """
246
+ self.start_nested_progress(total, description)
247
+ try:
248
+ for i in range(total):
249
+ await self.update_nested_progress(i, f"Item {i+1}/{total}")
250
+ await operation(i, self)
251
+ finally:
252
+ self.end_nested_progress()
253
+
254
+ def create_child_context(self, **overrides) -> "HAPContext":
255
+ """Create a child context with optional overrides."""
256
+ return HAPContext(
257
+ request_id=overrides.get("request_id", f"{self.request_id}.child"),
258
+ resource_provider=overrides.get("resource_provider", self._resource_provider),
259
+ auth_provider=overrides.get("auth_provider", self._auth_provider),
260
+ log_handler=overrides.get("log_handler", self._log_handler),
261
+ progress_handler=overrides.get("progress_handler", self._progress_handler),
262
+ )
263
+
264
+
265
+ class SimpleResourceProvider:
266
+ """Simple in-memory resource provider for testing."""
267
+
268
+ def __init__(self):
269
+ self.resources: Dict[str, Any] = {}
270
+
271
+ def add_resource(self, uri: str, content: Any):
272
+ """Add a resource."""
273
+ self.resources[uri] = content
274
+
275
+ async def read_resource(self, uri: str) -> Any:
276
+ """Read a resource."""
277
+ if uri not in self.resources:
278
+ raise KeyError(f"Resource not found: {uri}")
279
+ return self.resources[uri]
280
+
281
+
282
+ class SimpleAuthProvider:
283
+ """Simple auth provider for testing."""
284
+
285
+ def __init__(self, user: Optional[str] = None, scopes: Optional[List[str]] = None):
286
+ self.user = user
287
+ self.scopes = scopes or []
288
+
289
+ def get_current_user(self) -> Optional[str]:
290
+ """Get current user."""
291
+ return self.user
292
+
293
+ def get_user_scopes(self) -> List[str]:
294
+ """Get user scopes."""
295
+ return self.scopes
@@ -0,0 +1,86 @@
1
+ # AGP Models Module
2
+
3
+ This module contains the core data models for the Haive Agent Protocol (AGP).
4
+
5
+ ## Overview
6
+
7
+ The models module provides the fundamental building blocks for AGP:
8
+ - **HAPContext**: State management with execution tracking
9
+ - **HAPGraph**: Graph structure for agent workflows
10
+ - **HAPNode**: Individual nodes in the graph
11
+
12
+ ## Components
13
+
14
+ ### HAPContext
15
+
16
+ The `HAPContext` class extends Haive's `StateSchema` to provide:
17
+ - Execution path tracking
18
+ - Agent metadata storage
19
+ - Graph context management
20
+ - Backward compatibility with AGP v1 properties
21
+
22
+ ```python
23
+ from haive.hap.models.context import HAPContext
24
+
25
+ context = HAPContext()
26
+ context.execution_path.append("node1")
27
+ context.agent_metadata["node1"] = {"status": "complete"}
28
+ ```
29
+
30
+ ### HAPGraph
31
+
32
+ The `HAPGraph` class manages the workflow structure:
33
+ - Node management (add, remove, connect)
34
+ - Topological ordering for execution
35
+ - Entry point configuration
36
+
37
+ ```python
38
+ from haive.hap.models.graph import HAPGraph
39
+
40
+ graph = HAPGraph()
41
+ graph.add_agent_node("step1", agent1, next_nodes=["step2"])
42
+ graph.add_agent_node("step2", agent2)
43
+ graph.entry_node = "step1"
44
+ ```
45
+
46
+ ### HAPNode
47
+
48
+ Individual nodes in the graph that can contain:
49
+ - Agent instances
50
+ - Agent entrypoints (module:class format)
51
+ - Connections to other nodes
52
+
53
+ ## Backward Compatibility
54
+
55
+ The module maintains backward compatibility with AGP v1:
56
+ - `HAPContext` supports `inputs`, `outputs`, `state`, `meta` properties
57
+ - `AgentGraph` and `AgentNode` are aliases for the AGP classes
58
+
59
+ ## Usage Example
60
+
61
+ ```python
62
+ from haive.hap.models import HAPGraph, HAPContext
63
+ from haive.agents.simple.agent import SimpleAgent
64
+
65
+ # Create context
66
+ context = HAPContext()
67
+
68
+ # Build graph
69
+ graph = HAPGraph()
70
+ agent = SimpleAgent(name="worker", engine=config)
71
+ graph.add_agent_node("worker_node", agent)
72
+
73
+ # Track execution
74
+ context.execution_path.append("worker_node")
75
+ context.agent_metadata["worker_node"] = {
76
+ "start_time": time.time(),
77
+ "status": "running"
78
+ }
79
+ ```
80
+
81
+ ## Design Principles
82
+
83
+ 1. **StateSchema Integration**: HAPContext inherits from StateSchema for proper Haive integration
84
+ 2. **Simplicity**: Uses BaseModel instead of complex inheritance chains
85
+ 3. **Flexibility**: Supports both agent instances and entrypoint strings
86
+ 4. **Compatibility**: Maintains all AGP v1 interfaces
@@ -0,0 +1,12 @@
1
+ """HAP models for agent graphs and contexts."""
2
+
3
+ from .context import HAPContext
4
+ from .graph import HAPGraph, HAPNode, AgentGraph, AgentNode
5
+
6
+ __all__ = [
7
+ "HAPContext",
8
+ "HAPGraph",
9
+ "HAPNode",
10
+ "AgentGraph", # Backward compatibility
11
+ "AgentNode", # Backward compatibility
12
+ ]