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.
Files changed (34) hide show
  1. beeos_cloud_harness_sdk-1.0.0/LICENSE +21 -0
  2. beeos_cloud_harness_sdk-1.0.0/PKG-INFO +49 -0
  3. beeos_cloud_harness_sdk-1.0.0/README.md +34 -0
  4. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/__init__.py +283 -0
  5. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/_generated/__init__.py +1 -0
  6. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/_generated/models.py +97 -0
  7. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/_generated/operations.py +70 -0
  8. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/canvas_relay.py +112 -0
  9. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/claims.py +74 -0
  10. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/gateway.py +411 -0
  11. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/identity.py +137 -0
  12. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/jcs.py +74 -0
  13. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/lease_http.py +170 -0
  14. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/realtime.py +54 -0
  15. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/registration.py +668 -0
  16. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/retry.py +113 -0
  17. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/runtime_delivery_port.py +359 -0
  18. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/runtime_plane.py +175 -0
  19. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk/terminal.py +160 -0
  20. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk.egg-info/PKG-INFO +49 -0
  21. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk.egg-info/SOURCES.txt +32 -0
  22. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk.egg-info/dependency_links.txt +1 -0
  23. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk.egg-info/requires.txt +2 -0
  24. beeos_cloud_harness_sdk-1.0.0/beeos_cloud_harness_sdk.egg-info/top_level.txt +1 -0
  25. beeos_cloud_harness_sdk-1.0.0/pyproject.toml +22 -0
  26. beeos_cloud_harness_sdk-1.0.0/setup.cfg +4 -0
  27. beeos_cloud_harness_sdk-1.0.0/tests/test_agent.py +108 -0
  28. beeos_cloud_harness_sdk-1.0.0/tests/test_agent_auth.py +56 -0
  29. beeos_cloud_harness_sdk-1.0.0/tests/test_gateway.py +180 -0
  30. beeos_cloud_harness_sdk-1.0.0/tests/test_harness_helpers.py +226 -0
  31. beeos_cloud_harness_sdk-1.0.0/tests/test_lease_http.py +50 -0
  32. beeos_cloud_harness_sdk-1.0.0/tests/test_retry.py +97 -0
  33. beeos_cloud_harness_sdk-1.0.0/tests/test_runtime_registration.py +454 -0
  34. 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)