CORVO-AI commited on
Commit
91c7b38
·
verified ·
1 Parent(s): 7409b02

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +30 -19
app.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
  MindStudio WebSocket Streaming Client — Flask API Endpoint
3
- FIXED: 'ClientConnection' object has no attribute 'closed'
4
- Compatible with websockets >= 11.x and older versions
5
  """
6
 
7
  import asyncio
@@ -26,7 +26,6 @@ INITIAL_CONTINUATION_TOKEN = os.getenv(
26
  "155b2f71-9738-4260-8849-46e3eb0dd384::2b71a3fd-f4f0-4439-8163-60cd53d28218"
27
  )
28
  TARGET_MODEL = "claude-4-7-opus"
29
-
30
  WS_URL = "wss://api-socket.mindstudio.ai/"
31
 
32
  # ─────────────────────────────────────────────
@@ -38,7 +37,7 @@ _clients_lock = threading.Lock()
38
 
39
 
40
  # ─────────────────────────────────────────────
41
- # HELPER
42
  # ─────────────────────────────────────────────
43
 
44
  def safe_get_model(debug_info: dict):
@@ -49,24 +48,16 @@ def safe_get_model(debug_info: dict):
49
 
50
 
51
  def check_ws_open(ws) -> bool:
52
- """
53
- Universal check for WebSocket open state.
54
- Handles both old and new websockets library versions.
55
- """
56
  if ws is None:
57
  return False
58
  try:
59
- # websockets >= 11.x — ClientConnection
60
  if hasattr(ws, 'close_code'):
61
  return ws.close_code is None
62
- # websockets < 11.x — WebSocketClientProtocol
63
  if hasattr(ws, 'closed'):
64
  return not ws.closed
65
- # websockets >= 13.x — may use 'state'
66
  if hasattr(ws, 'state'):
67
  import websockets.connection
68
  return ws.state == websockets.connection.State.OPEN
69
- # fallback
70
  return True
71
  except Exception:
72
  return False
@@ -80,7 +71,11 @@ class MindStudioClient:
80
  def __init__(self, access_token: str, model: str = TARGET_MODEL):
81
  self.access_token = access_token
82
  self.model = model
83
- self.continuation_token = INITIAL_CONTINUATION_TOKEN
 
 
 
 
84
  self.ws = None
85
  self.loop = asyncio.new_event_loop()
86
 
@@ -111,7 +106,6 @@ class MindStudioClient:
111
  print(f"[WS] Connected! Type: {type(self.ws).__name__}")
112
  print(f"[WS] Subprotocol: {self.ws.subprotocol}")
113
 
114
- # Log which attributes are available for debugging
115
  has_closed = hasattr(self.ws, 'closed')
116
  has_closecode = hasattr(self.ws, 'close_code')
117
  has_state = hasattr(self.ws, 'state')
@@ -120,6 +114,17 @@ class MindStudioClient:
120
  def is_connected(self) -> bool:
121
  return check_ws_open(self.ws)
122
 
 
 
 
 
 
 
 
 
 
 
 
123
  def send_message_stream(self, message: str, thread_id: str):
124
  import queue
125
 
@@ -129,7 +134,7 @@ class MindStudioClient:
129
  async def _run():
130
  try:
131
  listen_task = asyncio.create_task(
132
- self._listen_for_response(chunk_queue, done_event)
133
  )
134
  await asyncio.sleep(0.1)
135
  await self._post_message(message, thread_id)
@@ -152,9 +157,14 @@ class MindStudioClient:
152
 
153
  async def _post_message(self, message: str, thread_id: str):
154
  post_url = f"https://v1.mindstudio-api.com/v1/apps/load/{APP_ID}/threads/post"
 
 
 
 
 
155
  payload = {
156
  "threadId": thread_id,
157
- "continuationToken": self.continuation_token,
158
  "payload": {
159
  "message": message,
160
  "imageUrl": "",
@@ -176,7 +186,8 @@ class MindStudioClient:
176
  if resp.status_code not in (200, 204):
177
  print(f"[POST] Body: {resp.text}")
178
 
179
- async def _listen_for_response(self, chunk_queue, done_event):
 
180
  full_response = ""
181
  previous_content_length = 0
182
  system_post_id = None
@@ -205,8 +216,8 @@ class MindStudioClient:
205
  data = event.get("appThreadContinuationActionUpdated", {})
206
  new_token = data.get("continuationAction", {}).get("token")
207
  if new_token:
208
- self.continuation_token = new_token
209
- print(f"[WS] Continuation token updated")
210
 
211
  underlying = (
212
  data.get("continuationAction", {})
 
1
  """
2
  MindStudio WebSocket Streaming Client — Flask API Endpoint
3
+ FIXED: Per-thread continuation tokens for proper conversation memory
4
+ FIXED: WebSocket compatibility
5
  """
6
 
7
  import asyncio
 
26
  "155b2f71-9738-4260-8849-46e3eb0dd384::2b71a3fd-f4f0-4439-8163-60cd53d28218"
27
  )
28
  TARGET_MODEL = "claude-4-7-opus"
 
29
  WS_URL = "wss://api-socket.mindstudio.ai/"
30
 
31
  # ─────────────────────────────────────────────
 
37
 
38
 
39
  # ─────────────────────────────────────────────
40
+ # HELPERS
41
  # ─────────────────────────────────────────────
42
 
43
  def safe_get_model(debug_info: dict):
 
48
 
49
 
50
  def check_ws_open(ws) -> bool:
 
 
 
 
51
  if ws is None:
52
  return False
53
  try:
 
54
  if hasattr(ws, 'close_code'):
55
  return ws.close_code is None
 
56
  if hasattr(ws, 'closed'):
57
  return not ws.closed
 
58
  if hasattr(ws, 'state'):
59
  import websockets.connection
60
  return ws.state == websockets.connection.State.OPEN
 
61
  return True
62
  except Exception:
63
  return False
 
71
  def __init__(self, access_token: str, model: str = TARGET_MODEL):
72
  self.access_token = access_token
73
  self.model = model
74
+
75
+ # ✅ FIX: Per-thread continuation tokens instead of one global token
76
+ self._continuation_tokens: dict = {}
77
+ self._tokens_lock = threading.Lock()
78
+
79
  self.ws = None
80
  self.loop = asyncio.new_event_loop()
81
 
 
106
  print(f"[WS] Connected! Type: {type(self.ws).__name__}")
107
  print(f"[WS] Subprotocol: {self.ws.subprotocol}")
108
 
 
109
  has_closed = hasattr(self.ws, 'closed')
110
  has_closecode = hasattr(self.ws, 'close_code')
111
  has_state = hasattr(self.ws, 'state')
 
114
  def is_connected(self) -> bool:
115
  return check_ws_open(self.ws)
116
 
117
+ def get_continuation_token(self, thread_id: str) -> str:
118
+ """✅ Get the continuation token for a specific thread."""
119
+ with self._tokens_lock:
120
+ return self._continuation_tokens.get(thread_id, INITIAL_CONTINUATION_TOKEN)
121
+
122
+ def set_continuation_token(self, thread_id: str, token: str):
123
+ """✅ Save the continuation token for a specific thread."""
124
+ with self._tokens_lock:
125
+ self._continuation_tokens[thread_id] = token
126
+ print(f"[TOKEN] Thread {thread_id[:8]}... token updated")
127
+
128
  def send_message_stream(self, message: str, thread_id: str):
129
  import queue
130
 
 
134
  async def _run():
135
  try:
136
  listen_task = asyncio.create_task(
137
+ self._listen_for_response(chunk_queue, done_event, thread_id)
138
  )
139
  await asyncio.sleep(0.1)
140
  await self._post_message(message, thread_id)
 
157
 
158
  async def _post_message(self, message: str, thread_id: str):
159
  post_url = f"https://v1.mindstudio-api.com/v1/apps/load/{APP_ID}/threads/post"
160
+
161
+ # ✅ FIX: Use per-thread continuation token
162
+ continuation_token = self.get_continuation_token(thread_id)
163
+ print(f"[POST] Using token: {continuation_token[:30]}... for thread: {thread_id[:8]}...")
164
+
165
  payload = {
166
  "threadId": thread_id,
167
+ "continuationToken": continuation_token,
168
  "payload": {
169
  "message": message,
170
  "imageUrl": "",
 
186
  if resp.status_code not in (200, 204):
187
  print(f"[POST] Body: {resp.text}")
188
 
189
+ async def _listen_for_response(self, chunk_queue, done_event, thread_id: str):
190
+ """✅ FIX: Accept thread_id to update its specific continuation token."""
191
  full_response = ""
192
  previous_content_length = 0
193
  system_post_id = None
 
216
  data = event.get("appThreadContinuationActionUpdated", {})
217
  new_token = data.get("continuationAction", {}).get("token")
218
  if new_token:
219
+ # FIX: Save token per thread, not globally
220
+ self.set_continuation_token(thread_id, new_token)
221
 
222
  underlying = (
223
  data.get("continuationAction", {})