hostel-protocol-python 0.1.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.
@@ -0,0 +1,76 @@
1
+ """hostel_protocol — Pydantic convenience layer for the Hostel protocol."""
2
+
3
+ from hostel_protocol.converter import proto_to_pydantic, pydantic_to_proto
4
+ from hostel_protocol.models import (
5
+ AIChatMessage,
6
+ ChatMessage,
7
+ ChatRequest,
8
+ ChatResponse,
9
+ CreateComponentRequest,
10
+ CreateComponentResponse,
11
+ CreateTaskRequest,
12
+ CreateTaskResponse,
13
+ DeleteComponentRequest,
14
+ DeleteComponentResponse,
15
+ DeleteTaskRequest,
16
+ DeleteTaskResponse,
17
+ GetComponentRequest,
18
+ GetComponentResponse,
19
+ GetTaskRequest,
20
+ GetTaskResponse,
21
+ HostelMessage,
22
+ HumanChatMessage,
23
+ ListAgentsRequest,
24
+ ListAgentsResponse,
25
+ ListComponentsRequest,
26
+ ListComponentsResponse,
27
+ ListTasksRequest,
28
+ ListTasksResponse,
29
+ TaskData,
30
+ ToolChatMessage,
31
+ UpdateComponentRequest,
32
+ UpdateComponentResponse,
33
+ UpdateTaskRequest,
34
+ UpdateTaskResponse,
35
+ )
36
+
37
+ __all__ = [
38
+ # Converter
39
+ "pydantic_to_proto",
40
+ "proto_to_pydantic",
41
+ # Chat
42
+ "HumanChatMessage",
43
+ "AIChatMessage",
44
+ "ToolChatMessage",
45
+ "ChatMessage",
46
+ "ChatRequest",
47
+ "ChatResponse",
48
+ # Agent
49
+ "ListAgentsRequest",
50
+ "ListAgentsResponse",
51
+ # Component
52
+ "CreateComponentRequest",
53
+ "CreateComponentResponse",
54
+ "GetComponentRequest",
55
+ "GetComponentResponse",
56
+ "ListComponentsRequest",
57
+ "ListComponentsResponse",
58
+ "UpdateComponentRequest",
59
+ "UpdateComponentResponse",
60
+ "DeleteComponentRequest",
61
+ "DeleteComponentResponse",
62
+ # Task
63
+ "TaskData",
64
+ "CreateTaskRequest",
65
+ "CreateTaskResponse",
66
+ "ListTasksRequest",
67
+ "ListTasksResponse",
68
+ "GetTaskRequest",
69
+ "GetTaskResponse",
70
+ "UpdateTaskRequest",
71
+ "UpdateTaskResponse",
72
+ "DeleteTaskRequest",
73
+ "DeleteTaskResponse",
74
+ # Envelope
75
+ "HostelMessage",
76
+ ]
@@ -0,0 +1,368 @@
1
+ """Bidirectional converter between Pydantic models and Protobuf messages."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from google.protobuf import struct_pb2, wrappers_pb2
8
+ from google.protobuf.json_format import MessageToDict, ParseDict
9
+ from google.protobuf.message import Message
10
+ from hostel.protocol.v1 import (
11
+ agent_pb2,
12
+ chat_pb2,
13
+ component_pb2,
14
+ message_pb2,
15
+ task_pb2,
16
+ )
17
+ from pydantic import BaseModel
18
+
19
+ from hostel_protocol import models
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Registry: Pydantic model class <-> Protobuf message class
23
+ # ---------------------------------------------------------------------------
24
+
25
+ _PYDANTIC_TO_PROTO: dict[type[BaseModel], type[Message]] = {
26
+ # Chat
27
+ models.HumanChatMessage: chat_pb2.HumanChatMessage,
28
+ models.AIChatMessage: chat_pb2.AIChatMessage,
29
+ models.ToolChatMessage: chat_pb2.ToolChatMessage,
30
+ models.ChatMessage: chat_pb2.ChatMessage,
31
+ models.ChatRequest: chat_pb2.ChatRequest,
32
+ models.ChatResponse: chat_pb2.ChatResponse,
33
+ # Agent
34
+ models.ListAgentsRequest: agent_pb2.ListAgentsRequest,
35
+ models.ListAgentsResponse: agent_pb2.ListAgentsResponse,
36
+ # Component
37
+ models.CreateComponentRequest: component_pb2.CreateComponentRequest,
38
+ models.CreateComponentResponse: component_pb2.CreateComponentResponse,
39
+ models.GetComponentRequest: component_pb2.GetComponentRequest,
40
+ models.GetComponentResponse: component_pb2.GetComponentResponse,
41
+ models.ListComponentsRequest: component_pb2.ListComponentsRequest,
42
+ models.ListComponentsResponse: component_pb2.ListComponentsResponse,
43
+ models.UpdateComponentRequest: component_pb2.UpdateComponentRequest,
44
+ models.UpdateComponentResponse: component_pb2.UpdateComponentResponse,
45
+ models.DeleteComponentRequest: component_pb2.DeleteComponentRequest,
46
+ models.DeleteComponentResponse: component_pb2.DeleteComponentResponse,
47
+ # Task
48
+ models.TaskData: task_pb2.TaskData,
49
+ models.CreateTaskRequest: task_pb2.CreateTaskRequest,
50
+ models.CreateTaskResponse: task_pb2.CreateTaskResponse,
51
+ models.ListTasksRequest: task_pb2.ListTasksRequest,
52
+ models.ListTasksResponse: task_pb2.ListTasksResponse,
53
+ models.GetTaskRequest: task_pb2.GetTaskRequest,
54
+ models.GetTaskResponse: task_pb2.GetTaskResponse,
55
+ models.UpdateTaskRequest: task_pb2.UpdateTaskRequest,
56
+ models.UpdateTaskResponse: task_pb2.UpdateTaskResponse,
57
+ models.DeleteTaskRequest: task_pb2.DeleteTaskRequest,
58
+ models.DeleteTaskResponse: task_pb2.DeleteTaskResponse,
59
+ # Envelope
60
+ models.HostelMessage: message_pb2.HostelMessage,
61
+ }
62
+
63
+ _PROTO_TO_PYDANTIC: dict[type[Message], type[BaseModel]] = {v: k for k, v in _PYDANTIC_TO_PROTO.items()}
64
+
65
+ # Fields in each proto that use google.protobuf.StringValue (nullable strings)
66
+ _STRING_VALUE_FIELDS: dict[type[Message], set[str]] = {
67
+ task_pb2.TaskData: {"webhook_url", "status", "response", "created_at", "updated_at", "executed_at"},
68
+ task_pb2.CreateTaskRequest: {"webhook_url"},
69
+ task_pb2.UpdateTaskRequest: {"agent_name", "prompt", "start_datetime", "webhook_url", "status"},
70
+ }
71
+
72
+ # Fields that hold google.protobuf.Struct (dict[str, Any])
73
+ _STRUCT_FIELDS: dict[type[Message], set[str]] = {
74
+ chat_pb2.ChatResponse: {"content"},
75
+ component_pb2.CreateComponentRequest: {"data"},
76
+ component_pb2.GetComponentResponse: {"data"},
77
+ component_pb2.UpdateComponentRequest: {"data"},
78
+ message_pb2.HostelMessage: {"meta", "system_payload", "generic_payload"},
79
+ }
80
+
81
+ # Fields that hold google.protobuf.Value
82
+ _VALUE_FIELDS: dict[type[Message], set[str]] = {
83
+ chat_pb2.ChatResponse: {"content"},
84
+ }
85
+
86
+ # Fields that hold repeated sub-messages
87
+ _REPEATED_MSG_FIELDS: dict[type[Message], dict[str, type[Message]]] = {
88
+ agent_pb2.ListAgentsResponse: {"agents": struct_pb2.Struct},
89
+ chat_pb2.ChatRequest: {"messages": chat_pb2.ChatMessage},
90
+ component_pb2.ListComponentsResponse: {"components": struct_pb2.Struct},
91
+ task_pb2.ListTasksResponse: {"tasks": task_pb2.TaskData},
92
+ }
93
+
94
+ # Fields that hold a single sub-message (non-oneof)
95
+ _SUB_MSG_FIELDS: dict[type[Message], dict[str, type[Message]]] = {
96
+ task_pb2.CreateTaskResponse: {"task": task_pb2.TaskData},
97
+ task_pb2.GetTaskResponse: {"task": task_pb2.TaskData},
98
+ task_pb2.UpdateTaskResponse: {"task": task_pb2.TaskData},
99
+ }
100
+
101
+ # oneof groups: proto class -> {field_name: proto sub-message class}
102
+ _ONEOF_FIELDS: dict[type[Message], dict[str, type[Message]]] = {
103
+ chat_pb2.ChatMessage: {
104
+ "human": chat_pb2.HumanChatMessage,
105
+ "ai": chat_pb2.AIChatMessage,
106
+ "tool": chat_pb2.ToolChatMessage,
107
+ },
108
+ }
109
+
110
+ # HostelMessage payload oneof (special handling due to breadth)
111
+ _HOSTEL_MESSAGE_PAYLOAD_FIELDS: dict[str, type[Message]] = {
112
+ "agent_list_request": agent_pb2.ListAgentsRequest,
113
+ "agent_list_response": agent_pb2.ListAgentsResponse,
114
+ "system_payload": struct_pb2.Struct,
115
+ "chat_request": chat_pb2.ChatRequest,
116
+ "chat_response_chunk": chat_pb2.ChatResponse,
117
+ "task_create": task_pb2.CreateTaskRequest,
118
+ "task_create_response": task_pb2.CreateTaskResponse,
119
+ "task_list": task_pb2.ListTasksRequest,
120
+ "task_list_response": task_pb2.ListTasksResponse,
121
+ "task_get": task_pb2.GetTaskRequest,
122
+ "task_get_response": task_pb2.GetTaskResponse,
123
+ "task_update": task_pb2.UpdateTaskRequest,
124
+ "task_update_response": task_pb2.UpdateTaskResponse,
125
+ "task_delete": task_pb2.DeleteTaskRequest,
126
+ "task_delete_response": task_pb2.DeleteTaskResponse,
127
+ "component_create": component_pb2.CreateComponentRequest,
128
+ "component_create_response": component_pb2.CreateComponentResponse,
129
+ "component_get": component_pb2.GetComponentRequest,
130
+ "component_get_response": component_pb2.GetComponentResponse,
131
+ "component_list": component_pb2.ListComponentsRequest,
132
+ "component_list_response": component_pb2.ListComponentsResponse,
133
+ "component_update": component_pb2.UpdateComponentRequest,
134
+ "component_update_response": component_pb2.UpdateComponentResponse,
135
+ "component_delete": component_pb2.DeleteComponentRequest,
136
+ "component_delete_response": component_pb2.DeleteComponentResponse,
137
+ "generic_payload": struct_pb2.Struct,
138
+ }
139
+
140
+
141
+ # ---------------------------------------------------------------------------
142
+ # Helpers
143
+ # ---------------------------------------------------------------------------
144
+
145
+
146
+ def _struct_to_dict(s: struct_pb2.Struct) -> dict[str, Any]:
147
+ return dict(MessageToDict(s))
148
+
149
+
150
+ def _dict_to_struct(d: dict[str, Any]) -> struct_pb2.Struct:
151
+ s = struct_pb2.Struct()
152
+ s.update(d)
153
+ return s
154
+
155
+
156
+ def _value_to_python(v: struct_pb2.Value) -> Any:
157
+ return MessageToDict(v)
158
+
159
+
160
+ def _python_to_value(v: Any) -> struct_pb2.Value:
161
+ val = struct_pb2.Value()
162
+ if v is None:
163
+ val.null_value = 0 # type: ignore[assignment]
164
+ elif isinstance(v, bool):
165
+ val.bool_value = v
166
+ elif isinstance(v, (int, float)):
167
+ val.number_value = float(v)
168
+ elif isinstance(v, str):
169
+ val.string_value = v
170
+ elif isinstance(v, dict):
171
+ ParseDict(v, val.struct_value)
172
+ elif isinstance(v, list):
173
+ for item in v:
174
+ val.list_value.values.append(_python_to_value(item))
175
+ return val
176
+
177
+
178
+ # ---------------------------------------------------------------------------
179
+ # Pydantic → Protobuf
180
+ # ---------------------------------------------------------------------------
181
+
182
+
183
+ def pydantic_to_proto(model: BaseModel) -> Message:
184
+ """Convert a Pydantic model instance to the corresponding Protobuf message."""
185
+ proto_cls = _PYDANTIC_TO_PROTO.get(type(model))
186
+ if proto_cls is None:
187
+ raise TypeError(f"No protobuf mapping registered for {type(model).__name__}")
188
+ return _pydantic_to_proto_inner(model, proto_cls)
189
+
190
+
191
+ def _pydantic_to_proto_inner(model: BaseModel, proto_cls: type[Message]) -> Message:
192
+ proto = proto_cls()
193
+ string_value_fields = _STRING_VALUE_FIELDS.get(proto_cls, set())
194
+ struct_fields = _STRUCT_FIELDS.get(proto_cls, set())
195
+ value_fields = _VALUE_FIELDS.get(proto_cls, set())
196
+ repeated_fields = _REPEATED_MSG_FIELDS.get(proto_cls, {})
197
+ sub_msg_fields = _SUB_MSG_FIELDS.get(proto_cls, {})
198
+ oneof_fields = _ONEOF_FIELDS.get(proto_cls, {})
199
+
200
+ # Special handling for HostelMessage payload oneof
201
+ is_envelope = proto_cls is message_pb2.HostelMessage
202
+
203
+ for field_name, value in model:
204
+ if value is None:
205
+ continue
206
+
207
+ # StringValue wrapper fields
208
+ if field_name in string_value_fields:
209
+ wrapper = wrappers_pb2.StringValue(value=value)
210
+ getattr(proto, field_name).CopyFrom(wrapper)
211
+
212
+ # google.protobuf.Value fields
213
+ elif field_name in value_fields:
214
+ getattr(proto, field_name).CopyFrom(_python_to_value(value))
215
+
216
+ # Struct fields (dict)
217
+ elif field_name in struct_fields:
218
+ if isinstance(value, dict):
219
+ getattr(proto, field_name).CopyFrom(_dict_to_struct(value))
220
+ # repeated Struct handled below
221
+
222
+ # Repeated Struct (list[dict])
223
+ elif field_name in repeated_fields and repeated_fields[field_name] is struct_pb2.Struct:
224
+ for item in value:
225
+ getattr(proto, field_name).append(_dict_to_struct(item))
226
+
227
+ # Repeated sub-messages
228
+ elif field_name in repeated_fields:
229
+ child_proto_cls = repeated_fields[field_name]
230
+ child_pydantic_cls = _PROTO_TO_PYDANTIC.get(child_proto_cls)
231
+ for item in value:
232
+ if child_pydantic_cls and isinstance(item, child_pydantic_cls):
233
+ getattr(proto, field_name).append(_pydantic_to_proto_inner(item, child_proto_cls))
234
+
235
+ # Single sub-message fields
236
+ elif field_name in sub_msg_fields:
237
+ child_proto_cls = sub_msg_fields[field_name]
238
+ getattr(proto, field_name).CopyFrom(_pydantic_to_proto_inner(value, child_proto_cls))
239
+
240
+ # Oneof sub-message fields
241
+ elif field_name in oneof_fields:
242
+ child_proto_cls = oneof_fields[field_name]
243
+ getattr(proto, field_name).CopyFrom(_pydantic_to_proto_inner(value, child_proto_cls))
244
+
245
+ # HostelMessage payload oneof
246
+ elif is_envelope and field_name in _HOSTEL_MESSAGE_PAYLOAD_FIELDS:
247
+ child_proto_cls = _HOSTEL_MESSAGE_PAYLOAD_FIELDS[field_name]
248
+ if child_proto_cls is struct_pb2.Struct:
249
+ getattr(proto, field_name).CopyFrom(_dict_to_struct(value))
250
+ else:
251
+ child_pydantic_cls = _PROTO_TO_PYDANTIC.get(child_proto_cls)
252
+ if child_pydantic_cls and isinstance(value, child_pydantic_cls):
253
+ getattr(proto, field_name).CopyFrom(_pydantic_to_proto_inner(value, child_proto_cls))
254
+
255
+ # Scalar fields
256
+ else:
257
+ setattr(proto, field_name, value)
258
+
259
+ return proto
260
+
261
+
262
+ # ---------------------------------------------------------------------------
263
+ # Protobuf → Pydantic
264
+ # ---------------------------------------------------------------------------
265
+
266
+
267
+ def proto_to_pydantic(proto: Message) -> BaseModel:
268
+ """Convert a Protobuf message to the corresponding Pydantic model."""
269
+ pydantic_cls = _PROTO_TO_PYDANTIC.get(type(proto))
270
+ if pydantic_cls is None:
271
+ raise TypeError(f"No pydantic mapping registered for {type(proto).__name__}")
272
+ return _proto_to_pydantic_inner(proto, pydantic_cls)
273
+
274
+
275
+ def _proto_to_pydantic_inner(proto: Message, pydantic_cls: type[BaseModel]) -> BaseModel:
276
+ proto_cls = type(proto)
277
+ string_value_fields = _STRING_VALUE_FIELDS.get(proto_cls, set())
278
+ struct_fields = _STRUCT_FIELDS.get(proto_cls, set())
279
+ value_fields = _VALUE_FIELDS.get(proto_cls, set())
280
+ repeated_fields = _REPEATED_MSG_FIELDS.get(proto_cls, {})
281
+ sub_msg_fields = _SUB_MSG_FIELDS.get(proto_cls, {})
282
+ oneof_fields = _ONEOF_FIELDS.get(proto_cls, {})
283
+
284
+ is_envelope = proto_cls is message_pb2.HostelMessage
285
+
286
+ kwargs: dict[str, Any] = {}
287
+
288
+ for field_desc in proto.DESCRIPTOR.fields:
289
+ field_name = field_desc.name
290
+
291
+ # StringValue wrapper fields
292
+ if field_name in string_value_fields:
293
+ if proto.HasField(field_name):
294
+ kwargs[field_name] = getattr(proto, field_name).value
295
+ else:
296
+ kwargs[field_name] = None
297
+
298
+ # google.protobuf.Value fields
299
+ elif field_name in value_fields:
300
+ if proto.HasField(field_name):
301
+ kwargs[field_name] = _value_to_python(getattr(proto, field_name))
302
+ else:
303
+ kwargs[field_name] = None
304
+
305
+ # Struct fields (dict)
306
+ elif field_name in struct_fields and field_name not in repeated_fields:
307
+ if is_envelope and field_name in {"system_payload", "generic_payload"}:
308
+ # These are oneof payload members
309
+ if proto.HasField(field_name):
310
+ kwargs[field_name] = _struct_to_dict(getattr(proto, field_name))
311
+ else:
312
+ kwargs[field_name] = None
313
+ elif proto.HasField(field_name):
314
+ kwargs[field_name] = _struct_to_dict(getattr(proto, field_name))
315
+ else:
316
+ kwargs[field_name] = {}
317
+
318
+ # Repeated Struct (list[dict])
319
+ elif field_name in repeated_fields and repeated_fields[field_name] is struct_pb2.Struct:
320
+ kwargs[field_name] = [_struct_to_dict(item) for item in getattr(proto, field_name)]
321
+
322
+ # Repeated sub-messages
323
+ elif field_name in repeated_fields:
324
+ child_proto_cls = repeated_fields[field_name]
325
+ child_pydantic_cls = _PROTO_TO_PYDANTIC.get(child_proto_cls)
326
+ if child_pydantic_cls:
327
+ kwargs[field_name] = [
328
+ _proto_to_pydantic_inner(item, child_pydantic_cls) for item in getattr(proto, field_name)
329
+ ]
330
+
331
+ # Single sub-message fields
332
+ elif field_name in sub_msg_fields:
333
+ if proto.HasField(field_name):
334
+ child_proto_cls = sub_msg_fields[field_name]
335
+ child_pydantic_cls = _PROTO_TO_PYDANTIC.get(child_proto_cls)
336
+ if child_pydantic_cls:
337
+ kwargs[field_name] = _proto_to_pydantic_inner(getattr(proto, field_name), child_pydantic_cls)
338
+ else:
339
+ kwargs[field_name] = None
340
+
341
+ # Oneof sub-message fields
342
+ elif field_name in oneof_fields:
343
+ if proto.HasField(field_name):
344
+ child_proto_cls = oneof_fields[field_name]
345
+ child_pydantic_cls = _PROTO_TO_PYDANTIC.get(child_proto_cls)
346
+ if child_pydantic_cls:
347
+ kwargs[field_name] = _proto_to_pydantic_inner(getattr(proto, field_name), child_pydantic_cls)
348
+ else:
349
+ kwargs[field_name] = None
350
+
351
+ # HostelMessage payload oneof
352
+ elif is_envelope and field_name in _HOSTEL_MESSAGE_PAYLOAD_FIELDS:
353
+ if proto.HasField(field_name):
354
+ child_proto_cls = _HOSTEL_MESSAGE_PAYLOAD_FIELDS[field_name]
355
+ if child_proto_cls is struct_pb2.Struct:
356
+ kwargs[field_name] = _struct_to_dict(getattr(proto, field_name))
357
+ else:
358
+ child_pydantic_cls = _PROTO_TO_PYDANTIC.get(child_proto_cls)
359
+ if child_pydantic_cls:
360
+ kwargs[field_name] = _proto_to_pydantic_inner(getattr(proto, field_name), child_pydantic_cls)
361
+ else:
362
+ kwargs[field_name] = None
363
+
364
+ # Scalar fields
365
+ else:
366
+ kwargs[field_name] = getattr(proto, field_name)
367
+
368
+ return pydantic_cls(**kwargs)
@@ -0,0 +1,267 @@
1
+ """Pydantic models mirroring the Hostel protocol protobuf definitions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+ # ---------------------------------------------------------------------------
10
+ # Chat
11
+ # ---------------------------------------------------------------------------
12
+
13
+
14
+ class HumanChatMessage(BaseModel):
15
+ model_config = ConfigDict(populate_by_name=True)
16
+ content: str
17
+
18
+
19
+ class AIChatMessage(BaseModel):
20
+ model_config = ConfigDict(populate_by_name=True)
21
+ content: str
22
+
23
+
24
+ class ToolChatMessage(BaseModel):
25
+ model_config = ConfigDict(populate_by_name=True)
26
+ content: str
27
+ tool_name: str
28
+
29
+
30
+ class ChatMessage(BaseModel):
31
+ """Wrapper matching the protobuf ``oneof message`` in ``ChatMessage``."""
32
+
33
+ model_config = ConfigDict(populate_by_name=True)
34
+ human: HumanChatMessage | None = None
35
+ ai: AIChatMessage | None = None
36
+ tool: ToolChatMessage | None = None
37
+
38
+
39
+ class ChatRequest(BaseModel):
40
+ model_config = ConfigDict(populate_by_name=True)
41
+ agent_name: str
42
+ messages: list[ChatMessage] = []
43
+
44
+
45
+ class ChatResponse(BaseModel):
46
+ model_config = ConfigDict(populate_by_name=True)
47
+ role: str = ""
48
+ scope: str = ""
49
+ content: Any = None
50
+
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Agent
54
+ # ---------------------------------------------------------------------------
55
+
56
+
57
+ class ListAgentsRequest(BaseModel):
58
+ model_config = ConfigDict(populate_by_name=True)
59
+
60
+
61
+ class ListAgentsResponse(BaseModel):
62
+ model_config = ConfigDict(populate_by_name=True)
63
+ agents: list[dict[str, Any]] = []
64
+
65
+
66
+ # ---------------------------------------------------------------------------
67
+ # Component
68
+ # ---------------------------------------------------------------------------
69
+
70
+
71
+ class CreateComponentRequest(BaseModel):
72
+ model_config = ConfigDict(populate_by_name=True)
73
+ component_type: str
74
+ data: dict[str, Any] = {}
75
+
76
+
77
+ class CreateComponentResponse(BaseModel):
78
+ model_config = ConfigDict(populate_by_name=True)
79
+ component_type: str = ""
80
+ name: str = ""
81
+ success: bool = False
82
+
83
+
84
+ class GetComponentRequest(BaseModel):
85
+ model_config = ConfigDict(populate_by_name=True)
86
+ component_type: str
87
+ name: str
88
+
89
+
90
+ class GetComponentResponse(BaseModel):
91
+ model_config = ConfigDict(populate_by_name=True)
92
+ component_type: str = ""
93
+ data: dict[str, Any] = {}
94
+
95
+
96
+ class ListComponentsRequest(BaseModel):
97
+ model_config = ConfigDict(populate_by_name=True)
98
+ component_type: str
99
+
100
+
101
+ class ListComponentsResponse(BaseModel):
102
+ model_config = ConfigDict(populate_by_name=True)
103
+ component_type: str = ""
104
+ components: list[dict[str, Any]] = []
105
+
106
+
107
+ class UpdateComponentRequest(BaseModel):
108
+ model_config = ConfigDict(populate_by_name=True)
109
+ component_type: str
110
+ name: str
111
+ data: dict[str, Any] = {}
112
+
113
+
114
+ class UpdateComponentResponse(BaseModel):
115
+ model_config = ConfigDict(populate_by_name=True)
116
+ component_type: str = ""
117
+ name: str = ""
118
+ success: bool = False
119
+
120
+
121
+ class DeleteComponentRequest(BaseModel):
122
+ model_config = ConfigDict(populate_by_name=True)
123
+ component_type: str
124
+ name: str
125
+
126
+
127
+ class DeleteComponentResponse(BaseModel):
128
+ model_config = ConfigDict(populate_by_name=True)
129
+ component_type: str = ""
130
+ name: str = ""
131
+ success: bool = False
132
+
133
+
134
+ # ---------------------------------------------------------------------------
135
+ # Task
136
+ # ---------------------------------------------------------------------------
137
+
138
+
139
+ class TaskData(BaseModel):
140
+ model_config = ConfigDict(populate_by_name=True)
141
+ id: str = ""
142
+ agent_name: str = ""
143
+ prompt: str = ""
144
+ start_datetime: str = ""
145
+ webhook_url: str | None = None
146
+ status: str | None = None
147
+ response: str | None = None
148
+ created_at: str | None = None
149
+ updated_at: str | None = None
150
+ executed_at: str | None = None
151
+
152
+
153
+ class CreateTaskRequest(BaseModel):
154
+ model_config = ConfigDict(populate_by_name=True)
155
+ agent_name: str
156
+ prompt: str
157
+ start_datetime: str
158
+ webhook_url: str | None = None
159
+
160
+
161
+ class CreateTaskResponse(BaseModel):
162
+ model_config = ConfigDict(populate_by_name=True)
163
+ task: TaskData | None = None
164
+
165
+
166
+ class ListTasksRequest(BaseModel):
167
+ model_config = ConfigDict(populate_by_name=True)
168
+
169
+
170
+ class ListTasksResponse(BaseModel):
171
+ model_config = ConfigDict(populate_by_name=True)
172
+ tasks: list[TaskData] = []
173
+
174
+
175
+ class GetTaskRequest(BaseModel):
176
+ model_config = ConfigDict(populate_by_name=True)
177
+ task_id: str
178
+
179
+
180
+ class GetTaskResponse(BaseModel):
181
+ model_config = ConfigDict(populate_by_name=True)
182
+ task: TaskData | None = None
183
+
184
+
185
+ class UpdateTaskRequest(BaseModel):
186
+ model_config = ConfigDict(populate_by_name=True)
187
+ task_id: str
188
+ agent_name: str | None = None
189
+ prompt: str | None = None
190
+ start_datetime: str | None = None
191
+ webhook_url: str | None = None
192
+ status: str | None = None
193
+
194
+
195
+ class UpdateTaskResponse(BaseModel):
196
+ model_config = ConfigDict(populate_by_name=True)
197
+ task: TaskData | None = None
198
+
199
+
200
+ class DeleteTaskRequest(BaseModel):
201
+ model_config = ConfigDict(populate_by_name=True)
202
+ task_id: str
203
+
204
+
205
+ class DeleteTaskResponse(BaseModel):
206
+ model_config = ConfigDict(populate_by_name=True)
207
+ success: bool = False
208
+
209
+
210
+ # ---------------------------------------------------------------------------
211
+ # Envelope (HostelMessage)
212
+ # ---------------------------------------------------------------------------
213
+
214
+
215
+ class HostelMessage(BaseModel):
216
+ """Unified envelope mirroring ``hostel.protocol.v1.HostelMessage``.
217
+
218
+ The ``oneof payload`` is modelled as a set of optional fields – exactly one
219
+ should be set at a time, matching the protobuf semantics.
220
+ """
221
+
222
+ model_config = ConfigDict(populate_by_name=True)
223
+
224
+ # Header
225
+ version: str = ""
226
+ id: str = ""
227
+ type: str = ""
228
+ action: str = ""
229
+ meta: dict[str, Any] = {}
230
+
231
+ # oneof payload – Agent
232
+ agent_list_request: ListAgentsRequest | None = None
233
+ agent_list_response: ListAgentsResponse | None = None
234
+
235
+ # oneof payload – System
236
+ system_payload: dict[str, Any] | None = None
237
+
238
+ # oneof payload – Chat
239
+ chat_request: ChatRequest | None = None
240
+ chat_response_chunk: ChatResponse | None = None
241
+
242
+ # oneof payload – Task
243
+ task_create: CreateTaskRequest | None = None
244
+ task_create_response: CreateTaskResponse | None = None
245
+ task_list: ListTasksRequest | None = None
246
+ task_list_response: ListTasksResponse | None = None
247
+ task_get: GetTaskRequest | None = None
248
+ task_get_response: GetTaskResponse | None = None
249
+ task_update: UpdateTaskRequest | None = None
250
+ task_update_response: UpdateTaskResponse | None = None
251
+ task_delete: DeleteTaskRequest | None = None
252
+ task_delete_response: DeleteTaskResponse | None = None
253
+
254
+ # oneof payload – Component
255
+ component_create: CreateComponentRequest | None = None
256
+ component_create_response: CreateComponentResponse | None = None
257
+ component_get: GetComponentRequest | None = None
258
+ component_get_response: GetComponentResponse | None = None
259
+ component_list: ListComponentsRequest | None = None
260
+ component_list_response: ListComponentsResponse | None = None
261
+ component_update: UpdateComponentRequest | None = None
262
+ component_update_response: UpdateComponentResponse | None = None
263
+ component_delete: DeleteComponentRequest | None = None
264
+ component_delete_response: DeleteComponentResponse | None = None
265
+
266
+ # oneof payload – Generic
267
+ generic_payload: dict[str, Any] | None = None
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: hostel-protocol-python
3
+ Version: 0.1.0
4
+ Summary: Pydantic convenience layer for the Hostel protocol
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: hostel-protocol>=0.1.0
7
+ Requires-Dist: protobuf>=7.0
8
+ Requires-Dist: pydantic>=2.5.0
9
+ Provides-Extra: dev
10
+ Requires-Dist: black; extra == 'dev'
11
+ Requires-Dist: mypy; extra == 'dev'
12
+ Requires-Dist: pytest; extra == 'dev'
13
+ Requires-Dist: pytest-asyncio; extra == 'dev'
14
+ Requires-Dist: pytest-cov; extra == 'dev'
15
+ Requires-Dist: ruff; extra == 'dev'
16
+ Requires-Dist: types-protobuf; extra == 'dev'
@@ -0,0 +1,6 @@
1
+ hostel_protocol/__init__.py,sha256=ctRJ5Y8Yf1hZlm1QXLViBpc_kKaoPCnETb2T2bSHpyM,1795
2
+ hostel_protocol/converter.py,sha256=qwoJE6NFrpcefm8UQdVIFCdvcF99f4lc5aghdIDy4sc,15593
3
+ hostel_protocol/models.py,sha256=AIOuY7Vmz0YVQeZmQNBw9gD3ZFn-3Uc0ZS1dlmbWcmc,7520
4
+ hostel_protocol_python-0.1.0.dist-info/METADATA,sha256=LCwCwkTkp-DScHHMSbX6EXi36KOJ0xUIFvGA9wA8Ym8,549
5
+ hostel_protocol_python-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
6
+ hostel_protocol_python-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any