hostel-protocol-python 0.3.2__tar.gz → 0.4.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 (20) hide show
  1. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/PKG-INFO +2 -2
  2. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/pyproject.toml +2 -2
  3. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/src/hostel/protocol/converter.py +33 -1
  4. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/src/hostel/protocol/models.py +66 -0
  5. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/.github/workflows/ci.yml +0 -0
  6. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/.github/workflows/publish.yml +0 -0
  7. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/.gitignore +0 -0
  8. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/Makefile +0 -0
  9. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/src/hostel/client/__init__.py +0 -0
  10. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/src/hostel/client/client.py +0 -0
  11. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/src/hostel/protocol/__init__.py +0 -0
  12. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/src/hostel/py.typed +0 -0
  13. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/src/hostel/transport/__init__.py +0 -0
  14. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/src/hostel/transport/transport.py +0 -0
  15. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/src/hostel/transport/zeromq.py +0 -0
  16. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/tests/__init__.py +0 -0
  17. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/tests/test_client.py +0 -0
  18. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/tests/test_converter.py +0 -0
  19. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/tests/test_models.py +0 -0
  20. {hostel_protocol_python-0.3.2 → hostel_protocol_python-0.4.0}/tests/test_transport.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hostel-protocol-python
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: Pydantic models, transport and client for the Hostel protocol
5
5
  Requires-Python: >=3.12
6
- Requires-Dist: hostel-protocol>=0.3.0
6
+ Requires-Dist: hostel-protocol>=0.4.0
7
7
  Requires-Dist: protobuf<8,>=7.35.0
8
8
  Requires-Dist: pydantic>=2.5.0
9
9
  Provides-Extra: client
@@ -4,11 +4,11 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hostel-protocol-python"
7
- version = "0.3.2"
7
+ version = "0.4.0"
8
8
  description = "Pydantic models, transport and client for the Hostel protocol"
9
9
  requires-python = ">=3.12"
10
10
  dependencies = [
11
- "hostel-protocol>=0.3.0",
11
+ "hostel-protocol>=0.4.0",
12
12
  "pydantic>=2.5.0",
13
13
  "protobuf>=7.35.0,<8",
14
14
  ]
@@ -30,6 +30,15 @@ _PYDANTIC_TO_PROTO: dict[type[BaseModel], type[Message]] = {
30
30
  models.ChatMessage: chat_pb2.ChatMessage,
31
31
  models.ChatRequest: chat_pb2.ChatRequest,
32
32
  models.ChatResponse: chat_pb2.ChatResponse,
33
+ # Conversation
34
+ models.ConversationSummary: chat_pb2.ConversationSummary,
35
+ models.ConversationMessage: chat_pb2.ConversationMessage,
36
+ models.ListConversationsRequest: chat_pb2.ListConversationsRequest,
37
+ models.ListConversationsResponse: chat_pb2.ListConversationsResponse,
38
+ models.GetConversationRequest: chat_pb2.GetConversationRequest,
39
+ models.GetConversationResponse: chat_pb2.GetConversationResponse,
40
+ models.DeleteConversationRequest: chat_pb2.DeleteConversationRequest,
41
+ models.DeleteConversationResponse: chat_pb2.DeleteConversationResponse,
33
42
  # Agent
34
43
  models.ListAgentsRequest: agent_pb2.ListAgentsRequest,
35
44
  models.ListAgentsResponse: agent_pb2.ListAgentsResponse,
@@ -62,6 +71,14 @@ _PYDANTIC_TO_PROTO: dict[type[BaseModel], type[Message]] = {
62
71
 
63
72
  _PROTO_TO_PYDANTIC: dict[type[Message], type[BaseModel]] = {v: k for k, v in _PYDANTIC_TO_PROTO.items()}
64
73
 
74
+ # Fields in each proto that are plain strings but should be treated as optional
75
+ # (empty string on the wire maps to None in Pydantic)
76
+ _OPTIONAL_STRING_FIELDS: dict[type[Message], set[str]] = {
77
+ chat_pb2.ChatRequest: {"conversation_id"},
78
+ chat_pb2.ChatResponse: {"conversation_id"},
79
+ chat_pb2.ConversationMessage: {"tool_name", "tool_call_id", "parameters_json"},
80
+ }
81
+
65
82
  # Fields in each proto that use google.protobuf.StringValue (nullable strings)
66
83
  _STRING_VALUE_FIELDS: dict[type[Message], set[str]] = {
67
84
  task_pb2.TaskData: {"webhook_url", "status", "response", "created_at", "updated_at", "executed_at"},
@@ -87,12 +104,15 @@ _VALUE_FIELDS: dict[type[Message], set[str]] = {
87
104
  _REPEATED_MSG_FIELDS: dict[type[Message], dict[str, type[Message]]] = {
88
105
  agent_pb2.ListAgentsResponse: {"agents": struct_pb2.Struct},
89
106
  chat_pb2.ChatRequest: {"messages": chat_pb2.ChatMessage},
107
+ chat_pb2.ListConversationsResponse: {"conversations": chat_pb2.ConversationSummary},
108
+ chat_pb2.GetConversationResponse: {"messages": chat_pb2.ConversationMessage},
90
109
  component_pb2.ListComponentsResponse: {"components": struct_pb2.Struct},
91
110
  task_pb2.ListTasksResponse: {"tasks": task_pb2.TaskData},
92
111
  }
93
112
 
94
113
  # Fields that hold a single sub-message (non-oneof)
95
114
  _SUB_MSG_FIELDS: dict[type[Message], dict[str, type[Message]]] = {
115
+ chat_pb2.GetConversationResponse: {"conversation": chat_pb2.ConversationSummary},
96
116
  task_pb2.CreateTaskResponse: {"task": task_pb2.TaskData},
97
117
  task_pb2.GetTaskResponse: {"task": task_pb2.TaskData},
98
118
  task_pb2.UpdateTaskResponse: {"task": task_pb2.TaskData},
@@ -122,6 +142,12 @@ _HOSTEL_MESSAGE_PAYLOAD_FIELDS: dict[str, type[Message]] = {
122
142
  "system_payload": struct_pb2.Struct,
123
143
  "chat_request": chat_pb2.ChatRequest,
124
144
  "chat_response_chunk": chat_pb2.ChatResponse,
145
+ "conversation_list_request": chat_pb2.ListConversationsRequest,
146
+ "conversation_list_response": chat_pb2.ListConversationsResponse,
147
+ "conversation_get_request": chat_pb2.GetConversationRequest,
148
+ "conversation_get_response": chat_pb2.GetConversationResponse,
149
+ "conversation_delete_request": chat_pb2.DeleteConversationRequest,
150
+ "conversation_delete_response": chat_pb2.DeleteConversationResponse,
125
151
  "task_create": task_pb2.CreateTaskRequest,
126
152
  "task_create_response": task_pb2.CreateTaskResponse,
127
153
  "task_list": task_pb2.ListTasksRequest,
@@ -199,6 +225,7 @@ def pydantic_to_proto(model: BaseModel) -> Message:
199
225
  def _pydantic_to_proto_inner(model: BaseModel, proto_cls: type[Message]) -> Message:
200
226
  proto = proto_cls()
201
227
  string_value_fields = _STRING_VALUE_FIELDS.get(proto_cls, set())
228
+ optional_string_fields = _OPTIONAL_STRING_FIELDS.get(proto_cls, set())
202
229
  struct_fields = _STRUCT_FIELDS.get(proto_cls, set())
203
230
  value_fields = _VALUE_FIELDS.get(proto_cls, set())
204
231
  repeated_fields = _REPEATED_MSG_FIELDS.get(proto_cls, {})
@@ -289,6 +316,7 @@ def proto_to_pydantic(proto: Message) -> BaseModel:
289
316
  def _proto_to_pydantic_inner(proto: Message, pydantic_cls: type[BaseModel]) -> BaseModel:
290
317
  proto_cls = type(proto)
291
318
  string_value_fields = _STRING_VALUE_FIELDS.get(proto_cls, set())
319
+ optional_string_fields = _OPTIONAL_STRING_FIELDS.get(proto_cls, set())
292
320
  struct_fields = _STRUCT_FIELDS.get(proto_cls, set())
293
321
  value_fields = _VALUE_FIELDS.get(proto_cls, set())
294
322
  repeated_fields = _REPEATED_MSG_FIELDS.get(proto_cls, {})
@@ -382,6 +410,10 @@ def _proto_to_pydantic_inner(proto: Message, pydantic_cls: type[BaseModel]) -> B
382
410
 
383
411
  # Scalar fields
384
412
  else:
385
- kwargs[field_name] = getattr(proto, field_name)
413
+ raw = getattr(proto, field_name)
414
+ if field_name in optional_string_fields:
415
+ kwargs[field_name] = raw if raw != "" else None
416
+ else:
417
+ kwargs[field_name] = raw
386
418
 
387
419
  return pydantic_cls(**kwargs)
@@ -42,6 +42,7 @@ class ChatRequest(BaseModel):
42
42
  messages: list[ChatMessage] = []
43
43
  planning_mode: Literal["react", "plan_and_execute"] = "react"
44
44
  require_plan_approval: bool = True
45
+ conversation_id: str | None = None
45
46
 
46
47
 
47
48
  class ChatResponse(BaseModel):
@@ -49,6 +50,7 @@ class ChatResponse(BaseModel):
49
50
  role: str = ""
50
51
  scope: str = ""
51
52
  content: Any = None
53
+ conversation_id: str | None = None
52
54
 
53
55
 
54
56
  # ---------------------------------------------------------------------------
@@ -209,6 +211,62 @@ class DeleteTaskResponse(BaseModel):
209
211
  success: bool = False
210
212
 
211
213
 
214
+ # ---------------------------------------------------------------------------
215
+ # Conversation
216
+ # ---------------------------------------------------------------------------
217
+
218
+
219
+ class ConversationSummary(BaseModel):
220
+ model_config = ConfigDict(populate_by_name=True)
221
+ id: str = ""
222
+ agent_name: str = ""
223
+ created_at: str = ""
224
+ updated_at: str = ""
225
+
226
+
227
+ class ConversationMessage(BaseModel):
228
+ model_config = ConfigDict(populate_by_name=True)
229
+ id: str = ""
230
+ conversation_id: str = ""
231
+ role: Literal["human", "ai", "tool_call", "tool_response"] = "human"
232
+ content: str = ""
233
+ tool_name: str | None = None
234
+ tool_call_id: str | None = None
235
+ parameters_json: str | None = None
236
+ sequence: int = 0
237
+ is_complete: bool = True
238
+ created_at: str = ""
239
+
240
+
241
+ class ListConversationsRequest(BaseModel):
242
+ model_config = ConfigDict(populate_by_name=True)
243
+
244
+
245
+ class ListConversationsResponse(BaseModel):
246
+ model_config = ConfigDict(populate_by_name=True)
247
+ conversations: list[ConversationSummary] = []
248
+
249
+
250
+ class GetConversationRequest(BaseModel):
251
+ model_config = ConfigDict(populate_by_name=True)
252
+ conversation_id: str
253
+
254
+
255
+ class GetConversationResponse(BaseModel):
256
+ model_config = ConfigDict(populate_by_name=True)
257
+ conversation: ConversationSummary | None = None
258
+ messages: list[ConversationMessage] = []
259
+
260
+
261
+ class DeleteConversationRequest(BaseModel):
262
+ model_config = ConfigDict(populate_by_name=True)
263
+ conversation_id: str
264
+
265
+
266
+ class DeleteConversationResponse(BaseModel):
267
+ model_config = ConfigDict(populate_by_name=True)
268
+
269
+
212
270
  # ---------------------------------------------------------------------------
213
271
  # Envelope (HostelMessage)
214
272
  # ---------------------------------------------------------------------------
@@ -241,6 +299,14 @@ class HostelMessage(BaseModel):
241
299
  chat_request: ChatRequest | None = None
242
300
  chat_response_chunk: ChatResponse | None = None
243
301
 
302
+ # oneof payload – Conversation
303
+ conversation_list_request: ListConversationsRequest | None = None
304
+ conversation_list_response: ListConversationsResponse | None = None
305
+ conversation_get_request: GetConversationRequest | None = None
306
+ conversation_get_response: GetConversationResponse | None = None
307
+ conversation_delete_request: DeleteConversationRequest | None = None
308
+ conversation_delete_response: DeleteConversationResponse | None = None
309
+
244
310
  # oneof payload – Task
245
311
  task_create: CreateTaskRequest | None = None
246
312
  task_create_response: CreateTaskResponse | None = None