beeos-cloud-harness-sdk 1.0.0__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.
- beeos_cloud_harness_sdk-1.0.0/LICENSE +21 -0
- beeos_cloud_harness_sdk-1.0.0/PKG-INFO +49 -0
- beeos_cloud_harness_sdk-1.0.0/README.md +34 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/__init__.py +283 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/_generated/__init__.py +1 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/_generated/models.py +97 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/_generated/operations.py +70 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/canvas_relay.py +112 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/claims.py +74 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/gateway.py +411 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/identity.py +137 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/jcs.py +74 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/lease_http.py +170 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/realtime.py +54 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/registration.py +668 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/retry.py +113 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/runtime_delivery_port.py +359 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/runtime_plane.py +175 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/terminal.py +160 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk.egg-info/PKG-INFO +49 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk.egg-info/SOURCES.txt +32 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk.egg-info/dependency_links.txt +1 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk.egg-info/requires.txt +2 -0
- beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk.egg-info/top_level.txt +1 -0
- beeos_cloud_harness_sdk-1.0.0/pyproject.toml +22 -0
- beeos_cloud_harness_sdk-1.0.0/setup.cfg +4 -0
- beeos_cloud_harness_sdk-1.0.0/tests/test_agent.py +108 -0
- beeos_cloud_harness_sdk-1.0.0/tests/test_agent_auth.py +56 -0
- beeos_cloud_harness_sdk-1.0.0/tests/test_gateway.py +180 -0
- beeos_cloud_harness_sdk-1.0.0/tests/test_harness_helpers.py +226 -0
- beeos_cloud_harness_sdk-1.0.0/tests/test_lease_http.py +50 -0
- beeos_cloud_harness_sdk-1.0.0/tests/test_retry.py +97 -0
- beeos_cloud_harness_sdk-1.0.0/tests/test_runtime_registration.py +454 -0
- beeos_cloud_harness_sdk-1.0.0/tests/test_runtime_signature_interop.py +87 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present BeeOS
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: beeos-cloud-harness-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: BeeOS Cloud Harness SDK
|
|
5
|
+
Author: BeeOS
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/beeos-ai/beeos-cloud-harness-sdk-python
|
|
8
|
+
Project-URL: Issues, https://github.com/beeos-ai/beeos-cloud-harness-sdk-python/issues
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: httpx>=0.27
|
|
13
|
+
Requires-Dist: cryptography>=42
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# BeeOS Cloud Harness SDK for Python
|
|
17
|
+
|
|
18
|
+
Python SDK for connecting custom agent harnesses to BeeOS Cloud. It provides
|
|
19
|
+
runtime registration and lease management, signed agent requests, durable
|
|
20
|
+
command delivery, and chat reply handling.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Requires Python 3.11 or later.
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
pip install beeos-cloud-harness-sdk
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from beeos_cloud_harness_sdk import RUNTIME_METHODS, operation_path
|
|
34
|
+
|
|
35
|
+
print(RUNTIME_METHODS)
|
|
36
|
+
print(operation_path("agentGet", agentId="your-agent-id"))
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`BeeOSAgentRuntime` connects a harness to the configured Agent Gateway and
|
|
40
|
+
Message Service using its runtime lease. The `identity`, `registration`,
|
|
41
|
+
`lease_http`, and `runtime_delivery_port` modules provide the building blocks
|
|
42
|
+
for managing identity, authority, and delivery. Keep private signing keys with
|
|
43
|
+
the harness; use the Cloud-issued lease credentials for runtime requests.
|
|
44
|
+
|
|
45
|
+
Source and issues: [beeos-cloud-harness-sdk-python](https://github.com/beeos-ai/beeos-cloud-harness-sdk-python).
|
|
46
|
+
|
|
47
|
+
## License
|
|
48
|
+
|
|
49
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# BeeOS Cloud Harness SDK for Python
|
|
2
|
+
|
|
3
|
+
Python SDK for connecting custom agent harnesses to BeeOS Cloud. It provides
|
|
4
|
+
runtime registration and lease management, signed agent requests, durable
|
|
5
|
+
command delivery, and chat reply handling.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Requires Python 3.11 or later.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pip install beeos-cloud-harness-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from beeos_cloud_harness_sdk import RUNTIME_METHODS, operation_path
|
|
19
|
+
|
|
20
|
+
print(RUNTIME_METHODS)
|
|
21
|
+
print(operation_path("agentGet", agentId="your-agent-id"))
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`BeeOSAgentRuntime` connects a harness to the configured Agent Gateway and
|
|
25
|
+
Message Service using its runtime lease. The `identity`, `registration`,
|
|
26
|
+
`lease_http`, and `runtime_delivery_port` modules provide the building blocks
|
|
27
|
+
for managing identity, authority, and delivery. Keep private signing keys with
|
|
28
|
+
the harness; use the Cloud-issued lease credentials for runtime requests.
|
|
29
|
+
|
|
30
|
+
Source and issues: [beeos-cloud-harness-sdk-python](https://github.com/beeos-ai/beeos-cloud-harness-sdk-python).
|
|
31
|
+
|
|
32
|
+
## License
|
|
33
|
+
|
|
34
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""Production BeeOS Harness SDK for custom agent harnesses (OpenClaw, Hermes, and others)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import json, urllib.error, urllib.parse, urllib.request
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import Any, get_args
|
|
6
|
+
|
|
7
|
+
from ._generated.models import RuntimeMessagePatch, RuntimeMethod
|
|
8
|
+
from ._generated.operations import OPERATIONS, operation_path
|
|
9
|
+
|
|
10
|
+
RUNTIME_METHODS: list[str] = list(get_args(RuntimeMethod))
|
|
11
|
+
|
|
12
|
+
# Cloud chat delivers every user prompt as this runtime method. The bound reply
|
|
13
|
+
# row is pre-created by Cloud in `streaming`; the agent converges it.
|
|
14
|
+
CHAT_PROMPT_METHOD = "session/prompt"
|
|
15
|
+
|
|
16
|
+
class BeeOSAgentError(RuntimeError):
|
|
17
|
+
def __init__(self, status: int, code: str, message: str):
|
|
18
|
+
super().__init__(message)
|
|
19
|
+
self.status, self.code = status, code
|
|
20
|
+
|
|
21
|
+
class BeeOSAgentRuntime:
|
|
22
|
+
def __init__(self, agent_gateway_url: str, message_service_url: str, instance_id: str, agent_id: str, token: str | Callable[[], str], framework: str = "custom", transport=None):
|
|
23
|
+
if not all([agent_gateway_url, message_service_url, instance_id, agent_id, token]):
|
|
24
|
+
raise ValueError("agent runtime requires gateway, message service, instance, agent and token")
|
|
25
|
+
self.agent_gateway_url = agent_gateway_url.rstrip("/") + "/"
|
|
26
|
+
self.message_service_url = message_service_url.rstrip("/") + "/"
|
|
27
|
+
self.instance_id = instance_id
|
|
28
|
+
self.agent_id = agent_id
|
|
29
|
+
self._token = token
|
|
30
|
+
self.framework = framework
|
|
31
|
+
self._transport = transport
|
|
32
|
+
self._closed = False
|
|
33
|
+
self.on_chat_message = None
|
|
34
|
+
self.on_chat_cancel = None
|
|
35
|
+
self.on_runtime_method = None
|
|
36
|
+
self.agent = _Agent(self)
|
|
37
|
+
self.messages = _Messages(self)
|
|
38
|
+
self.chat = _Chat(self)
|
|
39
|
+
self.files = _Files(self)
|
|
40
|
+
self.canvas = _Canvas(self)
|
|
41
|
+
self.a2a = _A2a(self)
|
|
42
|
+
self.messaging = _Messaging(self)
|
|
43
|
+
|
|
44
|
+
def token(self) -> str:
|
|
45
|
+
return self._token() if callable(self._token) else self._token
|
|
46
|
+
|
|
47
|
+
def register(self, *, on_chat_message=None, on_chat_cancel=None, on_runtime_method=None):
|
|
48
|
+
self.on_chat_message = on_chat_message
|
|
49
|
+
self.on_chat_cancel = on_chat_cancel
|
|
50
|
+
self.on_runtime_method = on_runtime_method
|
|
51
|
+
|
|
52
|
+
def request(self, base: str, method: str, path: str, json_body=None, headers=None):
|
|
53
|
+
if self._closed:
|
|
54
|
+
raise BeeOSAgentError(409, "runtime_closed", "BeeOSAgentRuntime is closed")
|
|
55
|
+
root = self.agent_gateway_url if base == "gateway" else self.message_service_url
|
|
56
|
+
url = urllib.parse.urljoin(root, path.lstrip("/"))
|
|
57
|
+
key = self.token()
|
|
58
|
+
req_headers = {
|
|
59
|
+
"Authorization": key if " " in key else f"Bearer {key}",
|
|
60
|
+
"Accept": "application/json",
|
|
61
|
+
"X-BeeOS-Instance-ID": self.instance_id,
|
|
62
|
+
"X-BeeOS-Agent-ID": self.agent_id,
|
|
63
|
+
}
|
|
64
|
+
if headers:
|
|
65
|
+
req_headers.update(headers)
|
|
66
|
+
data = None
|
|
67
|
+
if json_body is not None:
|
|
68
|
+
req_headers["Content-Type"] = "application/json"
|
|
69
|
+
data = json.dumps(json_body).encode()
|
|
70
|
+
if self._transport:
|
|
71
|
+
status, raw, _resp = self._transport(method, url, req_headers, data)
|
|
72
|
+
if status >= 400:
|
|
73
|
+
raise BeeOSAgentError(status, "agent_backend_unavailable", raw.decode() if isinstance(raw, (bytes, bytearray)) else str(raw))
|
|
74
|
+
if status == 204 or not raw:
|
|
75
|
+
return None
|
|
76
|
+
return json.loads(raw)
|
|
77
|
+
req = urllib.request.Request(url, data=data, headers=req_headers, method=method)
|
|
78
|
+
try:
|
|
79
|
+
with urllib.request.urlopen(req) as resp:
|
|
80
|
+
raw = resp.read()
|
|
81
|
+
if resp.status == 204 or not raw:
|
|
82
|
+
return None
|
|
83
|
+
return json.loads(raw.decode())
|
|
84
|
+
except urllib.error.HTTPError as err:
|
|
85
|
+
payload = err.read().decode()
|
|
86
|
+
code, message = "agent_backend_unavailable", err.reason
|
|
87
|
+
try:
|
|
88
|
+
parsed = json.loads(payload)
|
|
89
|
+
code, message = parsed.get("code", code), parsed.get("message", message)
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
raise BeeOSAgentError(err.code, code, message) from err
|
|
93
|
+
|
|
94
|
+
def _call(self, operation_id: str, json_body=None, headers=None, **path_params):
|
|
95
|
+
method, _path, service = OPERATIONS[operation_id]
|
|
96
|
+
base = "gateway" if service == "agent-gateway" else "message"
|
|
97
|
+
return self.request(base, method, operation_path(operation_id, **path_params), json_body, headers)
|
|
98
|
+
|
|
99
|
+
def dispatch_chat_message(self, event: dict):
|
|
100
|
+
if self.on_chat_message:
|
|
101
|
+
return self.on_chat_message(event)
|
|
102
|
+
|
|
103
|
+
def dispatch_chat_cancel(self, event: dict):
|
|
104
|
+
if self.on_chat_cancel:
|
|
105
|
+
return self.on_chat_cancel(event)
|
|
106
|
+
|
|
107
|
+
def dispatch_runtime_method(self, method: str, params: dict | None = None):
|
|
108
|
+
if method not in RUNTIME_METHODS:
|
|
109
|
+
raise BeeOSAgentError(400, "unsupported_runtime_method", method)
|
|
110
|
+
if method == CHAT_PROMPT_METHOD and not self.on_runtime_method:
|
|
111
|
+
return self.dispatch_session_prompt(params or {})
|
|
112
|
+
if not self.on_runtime_method:
|
|
113
|
+
raise BeeOSAgentError(501, "runtime_method_unhandled", method)
|
|
114
|
+
return self.on_runtime_method(method, params or {})
|
|
115
|
+
|
|
116
|
+
def dispatch_session_prompt(self, params: dict):
|
|
117
|
+
"""Cloud chat prompt -> the agent's chat handler (same event shape)."""
|
|
118
|
+
prompt = extract_session_prompt(params, self.agent_id)
|
|
119
|
+
if prompt is None:
|
|
120
|
+
raise BeeOSAgentError(400, "invalid_session_prompt", CHAT_PROMPT_METHOD)
|
|
121
|
+
return self.dispatch_chat_message(prompt)
|
|
122
|
+
|
|
123
|
+
def consume_envelope(self, envelope: dict):
|
|
124
|
+
kind = envelope.get("type") or (envelope.get("message") or {}).get("type") or ""
|
|
125
|
+
if kind == "chat_cancel":
|
|
126
|
+
content = envelope.get("content") or {}
|
|
127
|
+
return self.dispatch_chat_cancel({
|
|
128
|
+
"conversationId": envelope.get("conversationId") or envelope.get("conversation_id") or "",
|
|
129
|
+
"targetMessageId": envelope.get("replyTo") or envelope.get("reply_to") or content.get("target_message_id") or "",
|
|
130
|
+
"agentId": self.agent_id,
|
|
131
|
+
})
|
|
132
|
+
prompt = extract_chat_prompt(envelope, self.agent_id)
|
|
133
|
+
if not prompt or not prompt.get("messageId") or not prompt.get("channelId"):
|
|
134
|
+
return None
|
|
135
|
+
return self.dispatch_chat_message({
|
|
136
|
+
"conversationId": prompt["channelId"],
|
|
137
|
+
"messageId": prompt["messageId"],
|
|
138
|
+
"agentId": self.agent_id,
|
|
139
|
+
"text": prompt["message"],
|
|
140
|
+
"channelId": prompt["channelId"],
|
|
141
|
+
"sessionKey": prompt.get("sessionKey"),
|
|
142
|
+
"files": prompt.get("files") or [],
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
def close(self):
|
|
146
|
+
self._closed = True
|
|
147
|
+
self.on_chat_message = self.on_chat_cancel = self.on_runtime_method = None
|
|
148
|
+
|
|
149
|
+
def extract_chat_prompt(envelope: dict, local_agent_id: str = "") -> dict | None:
|
|
150
|
+
inner = envelope.get("message") or envelope
|
|
151
|
+
content = inner.get("content") or {}
|
|
152
|
+
if not isinstance(content, dict):
|
|
153
|
+
content = {}
|
|
154
|
+
text = content.get("message") or envelope.get("body") or ""
|
|
155
|
+
if not text and isinstance(content.get("parts"), list):
|
|
156
|
+
text = "\n".join(part.get("text", "") for part in content["parts"] if isinstance(part, dict) and part.get("type") == "text")
|
|
157
|
+
if not text:
|
|
158
|
+
return None
|
|
159
|
+
channel_id = inner.get("conversationId") or envelope.get("conversationId") or envelope.get("conversation_id") or content.get("channel_id")
|
|
160
|
+
context_id = content.get("context_id") or (content.get("metadata") or {}).get("context_id")
|
|
161
|
+
message_id = inner.get("id") or envelope.get("id")
|
|
162
|
+
source = "a2a" if inner.get("type") == "agent_request" or envelope.get("type") == "agent_request" else "invoke"
|
|
163
|
+
session_key = content.get("session_key") or f"agent:{local_agent_id}:{source}:{'ctx-' + context_id if context_id else 'ch-' + channel_id if channel_id else 'anon'}"
|
|
164
|
+
files = [item for item in (content.get("files") or []) if isinstance(item, str)]
|
|
165
|
+
if isinstance(content.get("parts"), list):
|
|
166
|
+
files.extend(part.get("file_url") for part in content["parts"] if isinstance(part, dict) and part.get("file_url"))
|
|
167
|
+
return {"message": text, "files": [item for item in files if item], "channelId": channel_id, "contextId": context_id, "sessionKey": session_key, "messageId": message_id}
|
|
168
|
+
|
|
169
|
+
def extract_session_prompt(params: dict, local_agent_id: str = "") -> dict | None:
|
|
170
|
+
"""Normalize Cloud chat ``session/prompt`` params into a chat event.
|
|
171
|
+
|
|
172
|
+
Returns None when the task binding Cloud guarantees is incomplete, so the
|
|
173
|
+
caller fails the operation instead of writing to an unbound reply row.
|
|
174
|
+
"""
|
|
175
|
+
if not isinstance(params, dict):
|
|
176
|
+
return None
|
|
177
|
+
conversation_id = str(params.get("conversationId") or "")
|
|
178
|
+
reply_message_id = str(params.get("replyMessageId") or "")
|
|
179
|
+
message_id = str(params.get("taskRequestMessageId") or "")
|
|
180
|
+
run_id = str(params.get("runtimeRunId") or params.get("taskId") or "")
|
|
181
|
+
if not conversation_id or not reply_message_id or not message_id or not run_id:
|
|
182
|
+
return None
|
|
183
|
+
raw_parts = params.get("parts") if isinstance(params.get("parts"), list) else []
|
|
184
|
+
parts = [part for part in raw_parts if isinstance(part, dict)]
|
|
185
|
+
text = "\n".join(str(part.get("text") or "") for part in parts if part.get("type") == "text").strip()
|
|
186
|
+
files = [str(part["blobRef"]) for part in parts if part.get("blobRef")]
|
|
187
|
+
agent_id = str(params.get("platformAgentId") or local_agent_id)
|
|
188
|
+
return {
|
|
189
|
+
"conversationId": conversation_id,
|
|
190
|
+
"channelId": conversation_id,
|
|
191
|
+
"messageId": message_id,
|
|
192
|
+
"replyMessageId": reply_message_id,
|
|
193
|
+
"runtimeRunId": run_id,
|
|
194
|
+
"agentId": agent_id,
|
|
195
|
+
"text": text,
|
|
196
|
+
"files": files,
|
|
197
|
+
"modelOverrideId": str(params.get("modelOverrideId") or "") or None,
|
|
198
|
+
"sessionKey": f"agent:{agent_id}:cloud-chat:ch-{conversation_id}",
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class _Chat:
|
|
203
|
+
"""Cloud chat reply convergence over the current runtime lease."""
|
|
204
|
+
|
|
205
|
+
def __init__(self, runtime: BeeOSAgentRuntime):
|
|
206
|
+
self.runtime = runtime
|
|
207
|
+
|
|
208
|
+
def patch_reply(self, conversation_id: str, reply_message_id: str, body: RuntimeMessagePatch, operation_key: str = ""):
|
|
209
|
+
if not conversation_id or not reply_message_id:
|
|
210
|
+
raise BeeOSAgentError(400, "invalid_chat_reply", "chat reply requires conversationId and replyMessageId")
|
|
211
|
+
headers = {"Idempotency-Key": operation_key} if operation_key else None
|
|
212
|
+
return self.runtime._call("runtimeConversationMessagePatch", body, headers, conversationId=conversation_id, messageId=reply_message_id)
|
|
213
|
+
|
|
214
|
+
def complete_reply(self, conversation_id: str, reply_message_id: str, text: str, operation_key: str = ""):
|
|
215
|
+
return self.patch_reply(
|
|
216
|
+
conversation_id, reply_message_id,
|
|
217
|
+
{"body": text, "parts": [{"type": "text", "text": text}], "state": "completed", "stop_reason": "end_turn"},
|
|
218
|
+
operation_key)
|
|
219
|
+
|
|
220
|
+
def fail_reply(self, conversation_id: str, reply_message_id: str, text: str = "", operation_key: str = ""):
|
|
221
|
+
return self.patch_reply(
|
|
222
|
+
conversation_id, reply_message_id,
|
|
223
|
+
{"body": text, "state": "failed", "stop_reason": "error"},
|
|
224
|
+
operation_key)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class _Agent:
|
|
228
|
+
def __init__(self, runtime: BeeOSAgentRuntime):
|
|
229
|
+
self.runtime = runtime
|
|
230
|
+
def get(self):
|
|
231
|
+
return self.runtime._call("agentGet", agentId=self.runtime.agent_id)
|
|
232
|
+
def update_exposure(self, patch, resource_version):
|
|
233
|
+
return self.runtime._call("agentUpdate", patch, {"If-Match": f'"{resource_version}"'}, agentId=self.runtime.agent_id)
|
|
234
|
+
def sync(self, input):
|
|
235
|
+
return self.runtime._call("agentSync", input)
|
|
236
|
+
|
|
237
|
+
class _Messages:
|
|
238
|
+
def __init__(self, runtime: BeeOSAgentRuntime):
|
|
239
|
+
self.runtime = runtime
|
|
240
|
+
def send(self, conversation_id, input):
|
|
241
|
+
return self.runtime._call("conversationMessageSend", input, conversationId=conversation_id)
|
|
242
|
+
def reply(self, conversation_id: str, inbound_message_id: str, text: str):
|
|
243
|
+
if not conversation_id or not inbound_message_id:
|
|
244
|
+
raise BeeOSAgentError(400, "invalid_agent_reply", "agent_reply requires conversationId and inbound messageId")
|
|
245
|
+
return self.send(conversation_id, {"type": "agent_reply", "replyTo": inbound_message_id, "reply_to": inbound_message_id, "content": {"text": text}, "sender": self.runtime.instance_id})
|
|
246
|
+
def list(self, conversation_id):
|
|
247
|
+
return self.runtime._call("conversationMessagesList", conversationId=conversation_id)
|
|
248
|
+
def consume_envelope(self, envelope: dict):
|
|
249
|
+
return self.runtime.consume_envelope(envelope)
|
|
250
|
+
|
|
251
|
+
class _Files:
|
|
252
|
+
def __init__(self, runtime: BeeOSAgentRuntime):
|
|
253
|
+
self.runtime = runtime
|
|
254
|
+
def list(self):
|
|
255
|
+
return self.runtime._call("agentFilesList")
|
|
256
|
+
def presign(self, input):
|
|
257
|
+
return self.runtime._call("agentFilePresign", input)
|
|
258
|
+
def confirm(self, input):
|
|
259
|
+
return self.runtime._call("agentFileConfirm", input)
|
|
260
|
+
def resolve(self, file_id):
|
|
261
|
+
return self.runtime._call("agentFileResolve", fileId=file_id)
|
|
262
|
+
|
|
263
|
+
class _Canvas:
|
|
264
|
+
def __init__(self, runtime: BeeOSAgentRuntime):
|
|
265
|
+
self.runtime = runtime
|
|
266
|
+
def token(self, input=None):
|
|
267
|
+
return self.runtime._call("canvasTokenCreate", input if input is not None else {})
|
|
268
|
+
|
|
269
|
+
class _A2a:
|
|
270
|
+
def __init__(self, runtime: BeeOSAgentRuntime):
|
|
271
|
+
self.runtime = runtime
|
|
272
|
+
def discover(self):
|
|
273
|
+
return self.runtime._call("a2aDiscover")
|
|
274
|
+
def call(self, agent_id, input):
|
|
275
|
+
return self.runtime._call("a2aJsonRpc", input, agentId=agent_id)
|
|
276
|
+
def get_task(self, task_id):
|
|
277
|
+
return self.runtime._call("a2aTaskGet", taskId=task_id)
|
|
278
|
+
|
|
279
|
+
class _Messaging:
|
|
280
|
+
def __init__(self, runtime: BeeOSAgentRuntime):
|
|
281
|
+
self.runtime = runtime
|
|
282
|
+
def token(self, input=None):
|
|
283
|
+
return self.runtime._call("messagingTokenCreate", input if input is not None else {})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Generated by sdks/codegen/gen.mjs from sdks/products/harness/spec/harness.openapi.yaml. Do not edit."""
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# generated by datamodel-codegen:
|
|
2
|
+
# filename: harness.openapi.yaml
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, Literal, NotRequired, TypeAlias, TypedDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JsonObject(TypedDict):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
RuntimeMethod: TypeAlias = Literal[
|
|
14
|
+
'agent/create',
|
|
15
|
+
'agent/update',
|
|
16
|
+
'agent/delete',
|
|
17
|
+
'agent/applyTemplate',
|
|
18
|
+
'skills/list',
|
|
19
|
+
'skills/install',
|
|
20
|
+
'skills/update',
|
|
21
|
+
'skills/uninstall',
|
|
22
|
+
'models/list',
|
|
23
|
+
'cron/list',
|
|
24
|
+
'cron/status',
|
|
25
|
+
'cron/add',
|
|
26
|
+
'cron/update',
|
|
27
|
+
'cron/remove',
|
|
28
|
+
'cron/run',
|
|
29
|
+
'cron/runs',
|
|
30
|
+
'mcp/list',
|
|
31
|
+
'mcp/prepare',
|
|
32
|
+
'mcp/set',
|
|
33
|
+
'mcp/unset',
|
|
34
|
+
'session/set_mode',
|
|
35
|
+
'session/set_model',
|
|
36
|
+
'session/clear',
|
|
37
|
+
'session/cancel',
|
|
38
|
+
'session/prompt',
|
|
39
|
+
'canvas/toggle',
|
|
40
|
+
'canvas/clear',
|
|
41
|
+
'canvas/reference',
|
|
42
|
+
'canvas/dimensions',
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class RuntimeDeliveriesReadRequest(TypedDict):
|
|
47
|
+
schemaVersion: Literal[1]
|
|
48
|
+
maxCount: int
|
|
49
|
+
blockMs: int
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RuntimeDeliveryIdsRequest(TypedDict):
|
|
53
|
+
schemaVersion: Literal[1]
|
|
54
|
+
deliveryIds: list[str]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RuntimeOperationAppendRequest(TypedDict):
|
|
58
|
+
schemaVersion: Literal[1]
|
|
59
|
+
type: str
|
|
60
|
+
payload: Any
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RuntimeConversationSyncRequest(TypedDict):
|
|
64
|
+
schemaVersion: Literal[1]
|
|
65
|
+
operationId: str
|
|
66
|
+
runtimeSessionId: str
|
|
67
|
+
runtimeSessionKey: NotRequired[str]
|
|
68
|
+
title: NotRequired[str]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class RuntimeMessagePatch(TypedDict):
|
|
72
|
+
body: NotRequired[str]
|
|
73
|
+
body_append: NotRequired[str]
|
|
74
|
+
body_from: NotRequired[int]
|
|
75
|
+
parts: NotRequired[list[JsonObject]]
|
|
76
|
+
state: NotRequired[str]
|
|
77
|
+
stop_reason: NotRequired[str]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TerminalAgentAuthFrame(TypedDict):
|
|
81
|
+
type: Literal['agent_auth']
|
|
82
|
+
instance_id: str
|
|
83
|
+
service: Literal['terminal']
|
|
84
|
+
public_key: str
|
|
85
|
+
timestamp: int
|
|
86
|
+
nonce: str
|
|
87
|
+
signature: str
|
|
88
|
+
runtimeLeaseCredential: str
|
|
89
|
+
handlerIdentity: str
|
|
90
|
+
runtimeEpoch: str
|
|
91
|
+
leaseId: str
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class CanvasRelayConnect(TypedDict):
|
|
95
|
+
path: Literal['/ws/agent']
|
|
96
|
+
agentIdQuery: NotRequired[str]
|
|
97
|
+
tokenQuery: NotRequired[str]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Generated by sdks/codegen/gen.mjs from sdks/products/harness/spec/harness.openapi.yaml. Do not edit.
|
|
2
|
+
import re
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
|
|
5
|
+
OPERATIONS: dict[str, tuple[str, str, str]] = {
|
|
6
|
+
"runtimeRegister": ("POST", "/api/v1/runtime/registrations", "agent-gateway"),
|
|
7
|
+
"runtimeHeartbeat": ("POST", "/api/v1/runtime/registrations/{registrationId}/heartbeat", "agent-gateway"),
|
|
8
|
+
"runtimeRealtimeTokenCreate": ("POST", "/api/v1/runtime/realtime/token", "message-service"),
|
|
9
|
+
"runtimeConversationSync": ("POST", "/api/v1/runtime/conversations", "message-service"),
|
|
10
|
+
"runtimeConversationMessagesList": ("GET", "/api/v1/runtime/conversations/{conversationId}/messages", "message-service"),
|
|
11
|
+
"runtimeConversationMessageCreate": ("POST", "/api/v1/runtime/conversations/{conversationId}/messages", "message-service"),
|
|
12
|
+
"runtimeConversationMessageGet": ("GET", "/api/v1/runtime/conversations/{conversationId}/messages/{messageId}", "message-service"),
|
|
13
|
+
"runtimeConversationMessagePatch": ("PATCH", "/api/v1/runtime/conversations/{conversationId}/messages/{messageId}", "message-service"),
|
|
14
|
+
"runtimeConversationRunStart": ("POST", "/api/v1/runtime/conversations/{conversationId}/runs/start", "message-service"),
|
|
15
|
+
"runtimeConversationRunFinish": ("POST", "/api/v1/runtime/conversations/{conversationId}/runs/finish", "message-service"),
|
|
16
|
+
"runtimeDeliveriesRead": ("POST", "/api/v1/runtime/deliveries/read", "message-service"),
|
|
17
|
+
"runtimeDeliveriesRenew": ("POST", "/api/v1/runtime/deliveries/renew", "message-service"),
|
|
18
|
+
"runtimeDeliveriesAck": ("POST", "/api/v1/runtime/deliveries/ack", "message-service"),
|
|
19
|
+
"runtimeOperationHistoryGet": ("GET", "/api/v1/runtime/operations/{operationId}/history", "message-service"),
|
|
20
|
+
"runtimeOperationMessageAppend": ("POST", "/api/v1/runtime/operations/{operationId}/messages", "message-service"),
|
|
21
|
+
"conversationMessagesList": ("GET", "/api/v2/conversations/{conversationId}/messages", "message-service"),
|
|
22
|
+
"conversationMessageSend": ("POST", "/api/v2/conversations/{conversationId}/messages", "message-service"),
|
|
23
|
+
"agentGet": ("GET", "/api/v1/agents/{agentId}", "agent-gateway"),
|
|
24
|
+
"agentUpdate": ("PATCH", "/api/v1/agents/{agentId}", "agent-gateway"),
|
|
25
|
+
"agentSync": ("POST", "/api/v1/agents/sync", "agent-gateway"),
|
|
26
|
+
"agentFilesList": ("GET", "/api/v1/agent/files", "agent-gateway"),
|
|
27
|
+
"agentFilePresign": ("POST", "/api/v1/agent/files/presign", "agent-gateway"),
|
|
28
|
+
"agentFileConfirm": ("POST", "/api/v1/agent/files/confirm", "agent-gateway"),
|
|
29
|
+
"agentFileResolve": ("GET", "/api/v1/agent/files/{fileId}/resolve", "agent-gateway"),
|
|
30
|
+
"canvasTokenCreate": ("POST", "/api/v1/canvas/token", "agent-gateway"),
|
|
31
|
+
"a2aDiscover": ("GET", "/api/v1/a2a/discover", "agent-gateway"),
|
|
32
|
+
"a2aJsonRpc": ("POST", "/api/v1/a2a/{agentId}/jsonrpc", "agent-gateway"),
|
|
33
|
+
"a2aTaskGet": ("GET", "/api/v1/a2a/tasks/{taskId}", "agent-gateway"),
|
|
34
|
+
"messagingTokenCreate": ("POST", "/api/v1/messaging/token", "agent-gateway"),
|
|
35
|
+
"runtimeHistoryBoundaryPrepare": ("POST", "/api/v1/runtime/conversations/{conversationId}/history-boundary/prepare", "message-service"),
|
|
36
|
+
"runtimeHistoryBoundaryResetting": ("POST", "/api/v1/runtime/conversations/{conversationId}/history-boundary/resetting", "message-service"),
|
|
37
|
+
"runtimeHistoryBoundaryResetDone": ("POST", "/api/v1/runtime/conversations/{conversationId}/history-boundary/reset_done", "message-service"),
|
|
38
|
+
"runtimeHistoryBoundaryCommit": ("POST", "/api/v1/runtime/conversations/{conversationId}/history-boundary/commit", "message-service"),
|
|
39
|
+
"runtimeHistoryBoundaryAbort": ("POST", "/api/v1/runtime/conversations/{conversationId}/history-boundary/abort", "message-service"),
|
|
40
|
+
"runtimeHistoryBoundaryReconcile": ("POST", "/api/v1/runtime/conversations/{conversationId}/history-boundary/reconcile", "message-service"),
|
|
41
|
+
"runtimeConversationMetadataModel": ("POST", "/api/v1/runtime/conversations/{conversationId}/metadata/model", "message-service"),
|
|
42
|
+
"runtimeConversationCronDelivery": ("POST", "/api/v1/runtime/conversations/{conversationId}/deliveries/cron", "message-service"),
|
|
43
|
+
"runtimeClaimNext": ("POST", "/api/v1/runtime/claims/next", "message-service"),
|
|
44
|
+
"runtimeClaimExact": ("POST", "/api/v1/runtime/operations/{operationId}/claims", "message-service"),
|
|
45
|
+
"runtimeClaimRenew": ("POST", "/api/v1/runtime/operations/{operationId}/claims/renew", "message-service"),
|
|
46
|
+
"runtimeClaimRelease": ("POST", "/api/v1/runtime/operations/{operationId}/claims/release", "message-service"),
|
|
47
|
+
"runtimeResourceRedeem": ("POST", "/api/v1/internal/runtime/operations/{operationId}/resources/{kind}/{resourceRef}/redeem", "agent-gateway"),
|
|
48
|
+
"runtimeOutputFilePresign": ("POST", "/api/v1/internal/runtime/operations/{operationId}/output-files/presign", "agent-gateway"),
|
|
49
|
+
"runtimeOutputFileConfirm": ("POST", "/api/v1/internal/runtime/operations/{operationId}/output-files/confirm", "agent-gateway"),
|
|
50
|
+
"runtimeInputFilesResolve": ("POST", "/api/v1/internal/runtime/operations/{operationId}/input-files/resolve", "agent-gateway"),
|
|
51
|
+
"agentTemplateOriginReport": ("POST", "/api/v1/agents/{agentId}/template-origin", "agent-gateway"),
|
|
52
|
+
"channelCreate": ("POST", "/api/v1/channels", "agent-gateway"),
|
|
53
|
+
"a2aTaskComplete": ("POST", "/api/v1/a2a/tasks/{taskId}/complete", "agent-gateway"),
|
|
54
|
+
"agentEventPublish": ("POST", "/api/v1/agent/events", "agent-gateway"),
|
|
55
|
+
"bridgeConfigGet": ("GET", "/api/v1/bridge/config", "agent-gateway"),
|
|
56
|
+
"agentConnectorMcp": ("POST", "/api/v1/agent/connectors/mcp", "agent-gateway"),
|
|
57
|
+
"automationList": ("GET", "/api/v1/automations", "agent-gateway"),
|
|
58
|
+
"automationCreate": ("POST", "/api/v1/automations", "agent-gateway"),
|
|
59
|
+
"automationWebhookCreate": ("POST", "/api/v1/automations/webhooks", "agent-gateway"),
|
|
60
|
+
"automationGet": ("GET", "/api/v1/automations/{automationId}", "agent-gateway"),
|
|
61
|
+
"automationRunsList": ("GET", "/api/v1/automations/{automationId}/runs", "agent-gateway"),
|
|
62
|
+
"automationRunCreate": ("POST", "/api/v1/automations/{automationId}/runs", "agent-gateway"),
|
|
63
|
+
"automationRunGet": ("GET", "/api/v1/automations/{automationId}/runs/{runId}", "agent-gateway"),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def operation_path(operation_id: str, **params: object) -> str:
|
|
68
|
+
"""Expand an operation's path template; every value is percent-encoded."""
|
|
69
|
+
template = OPERATIONS[operation_id][1]
|
|
70
|
+
return re.sub(r"\{(\w+)\}", lambda match: quote(str(params[match.group(1)]), safe=""), template)
|