@el4cteo/rbx-studio-mcp 0.7.6 → 0.7.8

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.
@@ -173,33 +173,67 @@ local MAX_PENDING = 48 * 1024 * 1024
173
173
  trailing blank line.
174
174
  ]]
175
175
  local function frameReader(): (chunk: string) -> { { [string]: any } }
176
- local pending = ""
176
+ --[[
177
+ Held as a list of reads rather than one growing string, and only the new
178
+ read is searched for a break. Re-joining and re-scanning everything held
179
+ on every read made a big frame cost quadratic time in how many pieces it
180
+ arrived in.
181
+ ]]
182
+ local held: { string } = {}
183
+ local heldBytes = 0
184
+ local lastChar = ""
185
+
186
+ local function reset()
187
+ table.clear(held)
188
+ heldBytes = 0
189
+ lastChar = ""
190
+ end
191
+
192
+ local function hold(piece: string)
193
+ if piece == "" then
194
+ return
195
+ end
196
+ table.insert(held, piece)
197
+ heldBytes += #piece
198
+ lastChar = string.sub(piece, -1)
199
+ end
177
200
 
178
201
  return function(chunk: string): { { [string]: any } }
179
- -- Normalised after joining, so a CRLF split across two reads still folds.
180
- pending = (string.gsub(pending .. chunk, "\r\n", "\n"))
202
+ -- A CR ending the previous read pairs with an LF starting this one; the
203
+ -- stray CR is harmless, because parseFrames normalises again.
204
+ chunk = (string.gsub(chunk, "\r\n", "\n"))
181
205
 
182
206
  local frames: { { [string]: any } } = {}
183
- local lastBreak = string.match(pending, "^.*()\n\n")
207
+ local lastBreak = string.match(chunk, "^.*()\n\n")
208
+ if lastBreak == nil and lastChar == "\n" and string.sub(chunk, 1, 1) == "\n" then
209
+ -- The terminator's two newlines landed either side of the read.
210
+ lastBreak = 0
211
+ end
212
+
184
213
  if lastBreak ~= nil then
185
- frames = parseFrames(string.sub(pending, 1, lastBreak - 1))
186
- pending = string.sub(pending, lastBreak + 2)
214
+ local before = if lastBreak > 0 then string.sub(chunk, 1, lastBreak - 1) else ""
215
+ frames = parseFrames(table.concat(held) .. before)
216
+ reset()
217
+ hold(string.sub(chunk, lastBreak + 2))
218
+ else
219
+ hold(chunk)
187
220
  end
188
221
 
189
222
  -- A partial JSON document never ends in "}", so the decode is only
190
223
  -- attempted when it could succeed rather than on every read of a big one.
191
- if pending ~= "" and string.match(pending, "}%s*$") then
192
- local tail = parseFrames(pending)
224
+ local last = held[#held]
225
+ if last ~= nil and string.match(last, "}%s*$", math.max(1, #last - 64)) then
226
+ local tail = parseFrames(table.concat(held))
193
227
  if #tail > 0 then
194
228
  for _, frame in tail do
195
229
  table.insert(frames, frame)
196
230
  end
197
- pending = ""
231
+ reset()
198
232
  end
199
233
  end
200
234
 
201
- if #pending > MAX_PENDING then
202
- pending = ""
235
+ if heldBytes > MAX_PENDING then
236
+ reset()
203
237
  end
204
238
  return frames
205
239
  end
@@ -366,7 +400,26 @@ end
366
400
  ]]
367
401
  function Transport.sendResult(result: { [string]: any })
368
402
  task.spawn(function()
369
- Net.postJson("/result?studioId=" .. studioId, result)
403
+ --[[
404
+ Encoded under pcall. A handler that returns something JSON cannot hold
405
+ -- an Instance, a function, a mixed table -- made JSONEncode throw inside
406
+ this task, so no answer was ever posted and the agent waited out the
407
+ whole deadline for a TIMEOUT that named the wrong cause.
408
+ ]]
409
+ local ok, body = pcall(HttpService.JSONEncode, HttpService, result)
410
+ if not ok then
411
+ body = HttpService:JSONEncode({
412
+ id = result.id,
413
+ ok = false,
414
+ error = {
415
+ code = "ENCODE_ERROR",
416
+ message = "The result could not be sent back as JSON: " .. tostring(body),
417
+ hint = "The command ran, but returned a value JSON cannot represent "
418
+ .. "(an Instance, a function, or a mixed table). Return plain data instead.",
419
+ },
420
+ })
421
+ end
422
+ Net.request("POST", "/result?studioId=" .. studioId, body)
370
423
  end)
371
424
  end
372
425