trustgraph-base 2.2.2__tar.gz → 2.2.4__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 (160) hide show
  1. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/PKG-INFO +1 -1
  2. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/async_socket_client.py +138 -69
  3. trustgraph_base-2.2.4/trustgraph/api/socket_client.py +892 -0
  4. trustgraph_base-2.2.4/trustgraph/base_version.py +1 -0
  5. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph_base.egg-info/PKG-INFO +1 -1
  6. trustgraph_base-2.2.2/trustgraph/api/socket_client.py +0 -1478
  7. trustgraph_base-2.2.2/trustgraph/base_version.py +0 -1
  8. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/README.md +0 -0
  9. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/pyproject.toml +0 -0
  10. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/setup.cfg +0 -0
  11. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/__init__.py +0 -0
  12. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/api.py +0 -0
  13. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/async_bulk_client.py +0 -0
  14. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/async_flow.py +0 -0
  15. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/async_metrics.py +0 -0
  16. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/bulk_client.py +0 -0
  17. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/collection.py +0 -0
  18. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/config.py +0 -0
  19. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/exceptions.py +0 -0
  20. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/explainability.py +0 -0
  21. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/flow.py +0 -0
  22. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/knowledge.py +0 -0
  23. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/library.py +0 -0
  24. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/metrics.py +0 -0
  25. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/api/types.py +0 -0
  26. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/__init__.py +0 -0
  27. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/agent_client.py +0 -0
  28. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/agent_service.py +0 -0
  29. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/async_processor.py +0 -0
  30. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/backend.py +0 -0
  31. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/cassandra_config.py +0 -0
  32. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/chunking_service.py +0 -0
  33. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/collection_config_handler.py +0 -0
  34. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/consumer.py +0 -0
  35. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/consumer_spec.py +0 -0
  36. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/document_embeddings_client.py +0 -0
  37. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/document_embeddings_query_service.py +0 -0
  38. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/document_embeddings_store_service.py +0 -0
  39. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/dynamic_tool_service.py +0 -0
  40. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/embeddings_client.py +0 -0
  41. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/embeddings_service.py +0 -0
  42. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/flow.py +0 -0
  43. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/flow_processor.py +0 -0
  44. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/graph_embeddings_client.py +0 -0
  45. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/graph_embeddings_query_service.py +0 -0
  46. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/graph_embeddings_store_service.py +0 -0
  47. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/graph_rag_client.py +0 -0
  48. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/llm_service.py +0 -0
  49. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/logging.py +0 -0
  50. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/metrics.py +0 -0
  51. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/parameter_spec.py +0 -0
  52. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/producer.py +0 -0
  53. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/producer_spec.py +0 -0
  54. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/prompt_client.py +0 -0
  55. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/publisher.py +0 -0
  56. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/pubsub.py +0 -0
  57. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/pulsar_backend.py +0 -0
  58. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/request_response_spec.py +0 -0
  59. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/row_embeddings_query_client.py +0 -0
  60. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/spec.py +0 -0
  61. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/structured_query_client.py +0 -0
  62. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/subscriber.py +0 -0
  63. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/subscriber_spec.py +0 -0
  64. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/text_completion_client.py +0 -0
  65. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/tool_client.py +0 -0
  66. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/tool_service.py +0 -0
  67. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/tool_service_client.py +0 -0
  68. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/triples_client.py +0 -0
  69. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/triples_query_service.py +0 -0
  70. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/base/triples_store_service.py +0 -0
  71. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/__init__.py +0 -0
  72. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/agent_client.py +0 -0
  73. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/base.py +0 -0
  74. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/config_client.py +0 -0
  75. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/document_embeddings_client.py +0 -0
  76. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/document_rag_client.py +0 -0
  77. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/embeddings_client.py +0 -0
  78. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/graph_embeddings_client.py +0 -0
  79. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/graph_rag_client.py +0 -0
  80. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/llm_client.py +0 -0
  81. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/prompt_client.py +0 -0
  82. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/row_embeddings_client.py +0 -0
  83. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/clients/triples_query_client.py +0 -0
  84. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/exceptions.py +0 -0
  85. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/knowledge/__init__.py +0 -0
  86. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/knowledge/defs.py +0 -0
  87. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/knowledge/document.py +0 -0
  88. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/knowledge/identifier.py +0 -0
  89. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/knowledge/organization.py +0 -0
  90. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/knowledge/publication.py +0 -0
  91. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/log_level.py +0 -0
  92. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/__init__.py +0 -0
  93. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/registry.py +0 -0
  94. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/__init__.py +0 -0
  95. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/agent.py +0 -0
  96. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/base.py +0 -0
  97. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/collection.py +0 -0
  98. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/config.py +0 -0
  99. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/diagnosis.py +0 -0
  100. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/document_loading.py +0 -0
  101. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/embeddings.py +0 -0
  102. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/embeddings_query.py +0 -0
  103. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/flow.py +0 -0
  104. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/knowledge.py +0 -0
  105. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/library.py +0 -0
  106. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/metadata.py +0 -0
  107. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/nlp_query.py +0 -0
  108. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/primitives.py +0 -0
  109. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/prompt.py +0 -0
  110. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/retrieval.py +0 -0
  111. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/rows_query.py +0 -0
  112. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/structured_query.py +0 -0
  113. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/text_completion.py +0 -0
  114. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/tool.py +0 -0
  115. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/messaging/translators/triples.py +0 -0
  116. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/objects/__init__.py +0 -0
  117. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/objects/field.py +0 -0
  118. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/objects/object.py +0 -0
  119. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/provenance/__init__.py +0 -0
  120. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/provenance/agent.py +0 -0
  121. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/provenance/namespaces.py +0 -0
  122. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/provenance/triples.py +0 -0
  123. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/provenance/uris.py +0 -0
  124. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/provenance/vocabulary.py +0 -0
  125. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/rdf.py +0 -0
  126. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/__init__.py +0 -0
  127. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/core/__init__.py +0 -0
  128. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/core/metadata.py +0 -0
  129. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/core/primitives.py +0 -0
  130. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/core/topic.py +0 -0
  131. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/knowledge/__init__.py +0 -0
  132. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/knowledge/document.py +0 -0
  133. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/knowledge/embeddings.py +0 -0
  134. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/knowledge/graph.py +0 -0
  135. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/knowledge/knowledge.py +0 -0
  136. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/knowledge/nlp.py +0 -0
  137. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/knowledge/object.py +0 -0
  138. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/knowledge/rows.py +0 -0
  139. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/knowledge/structured.py +0 -0
  140. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/__init__.py +0 -0
  141. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/agent.py +0 -0
  142. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/collection.py +0 -0
  143. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/config.py +0 -0
  144. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/diagnosis.py +0 -0
  145. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/flow.py +0 -0
  146. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/library.py +0 -0
  147. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/llm.py +0 -0
  148. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/lookup.py +0 -0
  149. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/nlp_query.py +0 -0
  150. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/prompt.py +0 -0
  151. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/query.py +0 -0
  152. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/retrieval.py +0 -0
  153. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/rows_query.py +0 -0
  154. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/storage.py +0 -0
  155. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/structured_query.py +0 -0
  156. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph/schema/services/tool_service.py +0 -0
  157. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph_base.egg-info/SOURCES.txt +0 -0
  158. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph_base.egg-info/dependency_links.txt +0 -0
  159. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph_base.egg-info/requires.txt +0 -0
  160. {trustgraph_base-2.2.2 → trustgraph_base-2.2.4}/trustgraph_base.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trustgraph-base
3
- Version: 2.2.2
3
+ Version: 2.2.4
4
4
  Summary: TrustGraph provides a means to run a pipeline of flexible AI processing components in a flexible means to achieve a processing pipeline.
5
5
  Author-email: "trustgraph.ai" <security@trustgraph.ai>
6
6
  Project-URL: Homepage, https://github.com/trustgraph-ai/trustgraph
@@ -1,5 +1,6 @@
1
1
 
2
2
  import json
3
+ import asyncio
3
4
  import websockets
4
5
  from typing import Optional, Dict, Any, AsyncIterator, Union
5
6
 
@@ -8,13 +9,29 @@ from . exceptions import ProtocolException, ApplicationException
8
9
 
9
10
 
10
11
  class AsyncSocketClient:
11
- """Asynchronous WebSocket client"""
12
+ """Asynchronous WebSocket client with persistent connection.
13
+
14
+ Maintains a single websocket connection and multiplexes requests
15
+ by ID, routing responses via a background reader task.
16
+
17
+ Use as an async context manager for proper lifecycle management:
18
+
19
+ async with AsyncSocketClient(url, timeout, token) as client:
20
+ result = await client._send_request(...)
21
+
22
+ Or call connect()/aclose() manually.
23
+ """
12
24
 
13
25
  def __init__(self, url: str, timeout: int, token: Optional[str]):
14
26
  self.url = self._convert_to_ws_url(url)
15
27
  self.timeout = timeout
16
28
  self.token = token
17
29
  self._request_counter = 0
30
+ self._socket = None
31
+ self._connect_cm = None
32
+ self._reader_task = None
33
+ self._pending = {} # request_id -> asyncio.Queue
34
+ self._connected = False
18
35
 
19
36
  def _convert_to_ws_url(self, url: str) -> str:
20
37
  """Convert HTTP URL to WebSocket URL"""
@@ -25,82 +42,123 @@ class AsyncSocketClient:
25
42
  elif url.startswith("ws://") or url.startswith("wss://"):
26
43
  return url
27
44
  else:
28
- # Assume ws://
29
45
  return f"ws://{url}"
30
46
 
47
+ def _build_ws_url(self):
48
+ ws_url = f"{self.url.rstrip('/')}/api/v1/socket"
49
+ if self.token:
50
+ ws_url = f"{ws_url}?token={self.token}"
51
+ return ws_url
52
+
53
+ async def connect(self):
54
+ """Establish the persistent websocket connection."""
55
+ if self._connected:
56
+ return
57
+
58
+ ws_url = self._build_ws_url()
59
+ self._connect_cm = websockets.connect(
60
+ ws_url, ping_interval=20, ping_timeout=self.timeout
61
+ )
62
+ self._socket = await self._connect_cm.__aenter__()
63
+ self._connected = True
64
+ self._reader_task = asyncio.create_task(self._reader())
65
+
66
+ async def __aenter__(self):
67
+ await self.connect()
68
+ return self
69
+
70
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
71
+ await self.aclose()
72
+
73
+ async def _ensure_connected(self):
74
+ """Lazily connect if not already connected."""
75
+ if not self._connected:
76
+ await self.connect()
77
+
78
+ async def _reader(self):
79
+ """Background task to read responses and route by request ID."""
80
+ try:
81
+ async for raw_message in self._socket:
82
+ response = json.loads(raw_message)
83
+ request_id = response.get("id")
84
+
85
+ if request_id and request_id in self._pending:
86
+ await self._pending[request_id].put(response)
87
+ # Ignore messages for unknown request IDs
88
+
89
+ except websockets.exceptions.ConnectionClosed:
90
+ pass
91
+ except Exception as e:
92
+ # Signal error to all pending requests
93
+ for queue in self._pending.values():
94
+ try:
95
+ await queue.put({"error": str(e)})
96
+ except:
97
+ pass
98
+ finally:
99
+ self._connected = False
100
+
101
+ def _next_request_id(self):
102
+ self._request_counter += 1
103
+ return f"req-{self._request_counter}"
104
+
31
105
  def flow(self, flow_id: str):
32
106
  """Get async flow instance for WebSocket operations"""
33
107
  return AsyncSocketFlowInstance(self, flow_id)
34
108
 
35
109
  async def _send_request(self, service: str, flow: Optional[str], request: Dict[str, Any]):
36
- """Async WebSocket request implementation (non-streaming)"""
37
- # Generate unique request ID
38
- self._request_counter += 1
39
- request_id = f"req-{self._request_counter}"
40
-
41
- # Build WebSocket URL with optional token
42
- ws_url = f"{self.url}/api/v1/socket"
43
- if self.token:
44
- ws_url = f"{ws_url}?token={self.token}"
110
+ """Send a request and wait for a single response."""
111
+ await self._ensure_connected()
45
112
 
46
- # Build request message
47
- message = {
48
- "id": request_id,
49
- "service": service,
50
- "request": request
51
- }
52
- if flow:
53
- message["flow"] = flow
113
+ request_id = self._next_request_id()
114
+ queue = asyncio.Queue()
115
+ self._pending[request_id] = queue
54
116
 
55
- # Connect and send request
56
- async with websockets.connect(ws_url, ping_interval=20, ping_timeout=self.timeout) as websocket:
57
- await websocket.send(json.dumps(message))
117
+ try:
118
+ message = {
119
+ "id": request_id,
120
+ "service": service,
121
+ "request": request
122
+ }
123
+ if flow:
124
+ message["flow"] = flow
58
125
 
59
- # Wait for single response
60
- raw_message = await websocket.recv()
61
- response = json.loads(raw_message)
126
+ await self._socket.send(json.dumps(message))
62
127
 
63
- if response.get("id") != request_id:
64
- raise ProtocolException(f"Response ID mismatch")
128
+ response = await queue.get()
65
129
 
66
130
  if "error" in response:
67
131
  raise ApplicationException(response["error"])
68
132
 
69
133
  if "response" not in response:
70
- raise ProtocolException(f"Missing response in message")
134
+ raise ProtocolException("Missing response in message")
71
135
 
72
136
  return response["response"]
73
137
 
74
- async def _send_request_streaming(self, service: str, flow: Optional[str], request: Dict[str, Any]):
75
- """Async WebSocket request implementation (streaming)"""
76
- # Generate unique request ID
77
- self._request_counter += 1
78
- request_id = f"req-{self._request_counter}"
138
+ finally:
139
+ self._pending.pop(request_id, None)
79
140
 
80
- # Build WebSocket URL with optional token
81
- ws_url = f"{self.url}/api/v1/socket"
82
- if self.token:
83
- ws_url = f"{ws_url}?token={self.token}"
141
+ async def _send_request_streaming(self, service: str, flow: Optional[str], request: Dict[str, Any]):
142
+ """Send a request and yield streaming response chunks."""
143
+ await self._ensure_connected()
84
144
 
85
- # Build request message
86
- message = {
87
- "id": request_id,
88
- "service": service,
89
- "request": request
90
- }
91
- if flow:
92
- message["flow"] = flow
145
+ request_id = self._next_request_id()
146
+ queue = asyncio.Queue()
147
+ self._pending[request_id] = queue
93
148
 
94
- # Connect and send request
95
- async with websockets.connect(ws_url, ping_interval=20, ping_timeout=self.timeout) as websocket:
96
- await websocket.send(json.dumps(message))
149
+ try:
150
+ message = {
151
+ "id": request_id,
152
+ "service": service,
153
+ "request": request
154
+ }
155
+ if flow:
156
+ message["flow"] = flow
97
157
 
98
- # Yield chunks as they arrive
99
- async for raw_message in websocket:
100
- response = json.loads(raw_message)
158
+ await self._socket.send(json.dumps(message))
101
159
 
102
- if response.get("id") != request_id:
103
- continue # Ignore messages for other requests
160
+ while True:
161
+ response = await queue.get()
104
162
 
105
163
  if "error" in response:
106
164
  raise ApplicationException(response["error"])
@@ -108,18 +166,16 @@ class AsyncSocketClient:
108
166
  if "response" in response:
109
167
  resp = response["response"]
110
168
 
111
- # Parse different chunk types
112
169
  chunk = self._parse_chunk(resp)
113
- if chunk is not None: # Skip provenance messages in streaming
170
+ if chunk is not None:
114
171
  yield chunk
115
172
 
116
- # Check if this is the final message
117
- # end_of_session indicates entire session is complete (including provenance)
118
- # end_of_dialog is for agent dialogs
119
- # complete is from the gateway envelope
120
173
  if resp.get("end_of_session") or resp.get("end_of_dialog") or response.get("complete"):
121
174
  break
122
175
 
176
+ finally:
177
+ self._pending.pop(request_id, None)
178
+
123
179
  def _parse_chunk(self, resp: Dict[str, Any]):
124
180
  """Parse response chunk into appropriate type. Returns None for non-content messages."""
125
181
  chunk_type = resp.get("chunk_type")
@@ -127,7 +183,6 @@ class AsyncSocketClient:
127
183
 
128
184
  # Handle new GraphRAG message format with message_type
129
185
  if message_type == "provenance":
130
- # Provenance messages are not yielded to user - they're metadata
131
186
  return None
132
187
 
133
188
  if chunk_type == "thought":
@@ -147,25 +202,41 @@ class AsyncSocketClient:
147
202
  end_of_dialog=resp.get("end_of_dialog", False)
148
203
  )
149
204
  elif chunk_type == "action":
150
- # Agent action chunks - treat as thoughts for display purposes
151
205
  return AgentThought(
152
206
  content=resp.get("content", ""),
153
207
  end_of_message=resp.get("end_of_message", False)
154
208
  )
155
209
  else:
156
- # RAG-style chunk (or generic chunk with message_type="chunk")
157
- # Text-completion uses "response" field, RAG uses "chunk" field, Prompt uses "text" field
158
210
  content = resp.get("response", resp.get("chunk", resp.get("text", "")))
159
211
  return RAGChunk(
160
212
  content=content,
161
213
  end_of_stream=resp.get("end_of_stream", False),
162
- error=None # Errors are always thrown, never stored
214
+ error=None
163
215
  )
164
216
 
165
217
  async def aclose(self):
166
- """Close WebSocket connection"""
167
- # Cleanup handled by context manager
168
- pass
218
+ """Close the persistent WebSocket connection cleanly."""
219
+ # Wait for reader to finish (socket close will cause it to exit)
220
+ if self._reader_task:
221
+ self._reader_task.cancel()
222
+ try:
223
+ await self._reader_task
224
+ except asyncio.CancelledError:
225
+ pass
226
+ self._reader_task = None
227
+
228
+ # Exit the websockets context manager — this cleanly shuts down
229
+ # the connection and its keepalive task
230
+ if self._connect_cm:
231
+ try:
232
+ await self._connect_cm.__aexit__(None, None, None)
233
+ except Exception:
234
+ pass
235
+ self._connect_cm = None
236
+
237
+ self._socket = None
238
+ self._connected = False
239
+ self._pending.clear()
169
240
 
170
241
 
171
242
  class AsyncSocketFlowInstance:
@@ -292,7 +363,6 @@ class AsyncSocketFlowInstance:
292
363
 
293
364
  async def graph_embeddings_query(self, text: str, user: str, collection: str, limit: int = 10, **kwargs):
294
365
  """Query graph embeddings for semantic search"""
295
- # First convert text to embedding vector
296
366
  emb_result = await self.embeddings(texts=[text])
297
367
  vector = emb_result.get("vectors", [[]])[0]
298
368
 
@@ -362,7 +432,6 @@ class AsyncSocketFlowInstance:
362
432
  limit: int = 10, **kwargs
363
433
  ):
364
434
  """Query row embeddings for semantic search on structured data"""
365
- # First convert text to embedding vector
366
435
  emb_result = await self.embeddings(texts=[text])
367
436
  vector = emb_result.get("vectors", [[]])[0]
368
437