@liveblocks/core 3.5.1 → 3.5.3

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.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ var __export = (target, all) => {
6
6
 
7
7
  // src/version.ts
8
8
  var PKG_NAME = "@liveblocks/core";
9
- var PKG_VERSION = "3.5.1";
9
+ var PKG_VERSION = "3.5.3";
10
10
  var PKG_FORMAT = "esm";
11
11
 
12
12
  // src/dupe-detection.ts
@@ -3817,108 +3817,204 @@ function parseAuthToken(rawTokenString) {
3817
3817
  };
3818
3818
  }
3819
3819
 
3820
- // src/lib/parsePartialJsonObject.ts
3821
- function parsePartialJsonObject(partial) {
3822
- partial = partial.trimStart();
3823
- if (partial.charAt(0) !== "{") {
3824
- return {};
3825
- }
3826
- if (partial.trimEnd().endsWith("}")) {
3827
- const quickCheck = tryParseJson(partial);
3828
- if (quickCheck) {
3829
- return quickCheck;
3830
- }
3831
- }
3832
- let result = partial;
3833
- let quoteCount = 0;
3834
- let escaped = false;
3835
- for (let i = 0; i < result.length; i++) {
3836
- const char = result[i];
3837
- if (escaped) {
3838
- escaped = false;
3839
- } else if (char === "\\") {
3840
- escaped = true;
3841
- } else if (char === '"') {
3842
- quoteCount++;
3843
- }
3844
- }
3845
- if (quoteCount % 2 === 1) {
3846
- result += '"';
3847
- }
3848
- result = result.trimEnd();
3849
- if (result.endsWith(",")) {
3850
- result = result.slice(0, -1);
3851
- }
3852
- if (result.endsWith(".")) {
3853
- result = result.slice(0, -1);
3854
- }
3855
- const stack = [];
3856
- let inString = false;
3857
- escaped = false;
3858
- for (let i = 0; i < result.length; i++) {
3859
- const char = result[i];
3860
- if (inString) {
3861
- if (escaped) {
3862
- escaped = false;
3863
- } else if (char === "\\") {
3864
- escaped = true;
3865
- } else if (char === '"') {
3866
- inString = false;
3867
- }
3868
- } else {
3869
- if (char === '"') {
3870
- inString = true;
3871
- } else if (char === "{") {
3872
- stack.push("}");
3873
- } else if (char === "[") {
3874
- stack.push("]");
3875
- } else if (char === "}" && stack.length > 0 && stack[stack.length - 1] === "}") {
3876
- stack.pop();
3877
- } else if (char === "]" && stack.length > 0 && stack[stack.length - 1] === "]") {
3878
- stack.pop();
3820
+ // src/lib/IncrementalJsonParser.ts
3821
+ var EMPTY_OBJECT = Object.freeze({});
3822
+ var NULL_KEYWORD_CHARS = Array.from(new Set("null"));
3823
+ var TRUE_KEYWORD_CHARS = Array.from(new Set("true"));
3824
+ var FALSE_KEYWORD_CHARS = Array.from(new Set("false"));
3825
+ var ALL_KEYWORD_CHARS = Array.from(new Set("nulltruefalse"));
3826
+ function stripChar(str, chars) {
3827
+ const lastChar = str[str.length - 1];
3828
+ if (chars.includes(lastChar)) {
3829
+ return str.slice(0, -1);
3830
+ }
3831
+ return str;
3832
+ }
3833
+ var IncrementalJsonParser = class {
3834
+ // Input
3835
+ #sourceText = "";
3836
+ // Output
3837
+ #cachedJson;
3838
+ /** How much we've already parsed */
3839
+ #scanIndex = 0;
3840
+ /** Whether the last char processed was a backslash */
3841
+ #escaped = false;
3842
+ /**
3843
+ * Start position of the last unterminated string, -1 if we're not inside
3844
+ * a string currently.
3845
+ *
3846
+ * Example: '{"a": "foo'
3847
+ * ^
3848
+ */
3849
+ #lastUnterminatedString = -1;
3850
+ /**
3851
+ * Start position of the last fully terminated string we've seen.
3852
+ *
3853
+ * Example: '{"a": "foo'
3854
+ * ^
3855
+ */
3856
+ #lastTerminatedString = -1;
3857
+ /** The bracket stack of expected closing chars. For input '{"a": ["foo', the stack would be ['}', ']']. */
3858
+ #stack = [];
3859
+ constructor(text = "") {
3860
+ this.append(text);
3861
+ }
3862
+ get source() {
3863
+ return this.#sourceText;
3864
+ }
3865
+ get json() {
3866
+ if (this.#cachedJson === void 0) {
3867
+ this.#cachedJson = this.#parse();
3868
+ }
3869
+ return this.#cachedJson;
3870
+ }
3871
+ /** Whether we're currently inside an unterminated string, e.g. '{"hello' */
3872
+ get #inString() {
3873
+ return this.#lastUnterminatedString >= 0;
3874
+ }
3875
+ append(delta) {
3876
+ if (delta) {
3877
+ if (this.#sourceText === "") {
3878
+ delta = delta.trimStart();
3879
3879
  }
3880
+ this.#sourceText += delta;
3881
+ this.#cachedJson = void 0;
3880
3882
  }
3881
3883
  }
3882
- const suffix = stack.reverse().join("");
3883
- {
3884
- const attempt = tryParseJson(result + suffix);
3885
- if (attempt) {
3886
- return attempt;
3887
- }
3888
- }
3889
- if (result.endsWith(":")) {
3890
- result = result.slice(0, -1);
3891
- }
3892
- if (result.endsWith('"')) {
3893
- let pos = result.length - 2;
3894
- escaped = false;
3895
- while (pos >= 0) {
3896
- if (escaped) {
3897
- escaped = false;
3898
- } else if (result[pos] === "\\") {
3899
- escaped = true;
3900
- } else if (result[pos] === '"') {
3901
- result = result.slice(0, pos);
3902
- break;
3884
+ #autocompleteTail(output) {
3885
+ if (this.#inString) {
3886
+ return "";
3887
+ }
3888
+ const lastChar = output.charAt(output.length - 1);
3889
+ if (lastChar === "") return "";
3890
+ if (lastChar === "-") {
3891
+ return "0";
3892
+ }
3893
+ if (!ALL_KEYWORD_CHARS.includes(lastChar)) return "";
3894
+ if (NULL_KEYWORD_CHARS.includes(lastChar)) {
3895
+ if (output.endsWith("nul")) return "l";
3896
+ if (output.endsWith("nu")) return "ll";
3897
+ if (output.endsWith("n")) return "ull";
3898
+ }
3899
+ if (TRUE_KEYWORD_CHARS.includes(lastChar)) {
3900
+ if (output.endsWith("tru")) return "e";
3901
+ if (output.endsWith("tr")) return "ue";
3902
+ if (output.endsWith("t")) return "rue";
3903
+ }
3904
+ if (FALSE_KEYWORD_CHARS.includes(lastChar)) {
3905
+ if (output.endsWith("fals")) return "e";
3906
+ if (output.endsWith("fal")) return "se";
3907
+ if (output.endsWith("fa")) return "lse";
3908
+ if (output.endsWith("f")) return "alse";
3909
+ }
3910
+ return "";
3911
+ }
3912
+ /**
3913
+ * Updates the internal parsing state by processing any new content
3914
+ * that has been appended since the last parse. This updates the state with
3915
+ * facts only. Any interpretation is left to the #parse() method.
3916
+ */
3917
+ #catchup() {
3918
+ const newContent = this.#sourceText.slice(this.#scanIndex);
3919
+ for (let i = 0; i < newContent.length; i++) {
3920
+ const ch = newContent[i];
3921
+ const absolutePos = this.#scanIndex + i;
3922
+ if (this.#inString) {
3923
+ if (this.#escaped) {
3924
+ this.#escaped = false;
3925
+ } else if (ch === "\\") {
3926
+ this.#escaped = true;
3927
+ } else if (ch === '"') {
3928
+ this.#lastTerminatedString = this.#lastUnterminatedString;
3929
+ this.#lastUnterminatedString = -1;
3930
+ }
3931
+ } else {
3932
+ if (ch === '"') {
3933
+ this.#lastUnterminatedString = absolutePos;
3934
+ } else if (ch === "{") {
3935
+ this.#stack.push("}");
3936
+ } else if (ch === "[") {
3937
+ this.#stack.push("]");
3938
+ } else if (ch === "}" && this.#stack.length > 0 && this.#stack[this.#stack.length - 1] === "}") {
3939
+ this.#stack.pop();
3940
+ } else if (ch === "]" && this.#stack.length > 0 && this.#stack[this.#stack.length - 1] === "]") {
3941
+ this.#stack.pop();
3942
+ }
3903
3943
  }
3904
- pos--;
3905
3944
  }
3945
+ this.#scanIndex = this.#sourceText.length;
3906
3946
  }
3907
- if (result.endsWith(",")) {
3908
- result = result.slice(0, -1);
3947
+ #parse() {
3948
+ this.#catchup();
3949
+ let result = this.#sourceText;
3950
+ if (result.charAt(0) !== "{") {
3951
+ return EMPTY_OBJECT;
3952
+ }
3953
+ if (result.endsWith("}")) {
3954
+ const quickCheck = tryParseJson(result);
3955
+ if (quickCheck) {
3956
+ return quickCheck;
3957
+ }
3958
+ }
3959
+ if (this.#inString) {
3960
+ if (this.#escaped) {
3961
+ result = result.slice(0, -1);
3962
+ }
3963
+ result += '"';
3964
+ }
3965
+ result = result.trimEnd();
3966
+ result = stripChar(result, ",.");
3967
+ result = result + this.#autocompleteTail(result);
3968
+ const suffix = this.#stack.reduceRight((acc, ch) => acc + ch, "");
3969
+ {
3970
+ const attempt = tryParseJson(result + suffix);
3971
+ if (attempt) {
3972
+ return attempt;
3973
+ }
3974
+ }
3975
+ if (this.#inString) {
3976
+ result = result.slice(0, this.#lastUnterminatedString);
3977
+ } else {
3978
+ result = stripChar(result, ":");
3979
+ if (result.endsWith('"')) {
3980
+ result = result.slice(0, this.#lastTerminatedString);
3981
+ }
3982
+ }
3983
+ result = stripChar(result, ",");
3984
+ result += suffix;
3985
+ return tryParseJson(result) ?? EMPTY_OBJECT;
3909
3986
  }
3910
- result += suffix;
3911
- return tryParseJson(result) ?? {};
3912
- }
3987
+ };
3913
3988
 
3914
3989
  // src/types/ai.ts
3990
+ function replaceOrAppend(content, newItem, keyFn, now2) {
3991
+ const existingIndex = findLastIndex(
3992
+ content,
3993
+ (item) => item.type === newItem.type && keyFn(item) === keyFn(newItem)
3994
+ );
3995
+ if (existingIndex > -1) {
3996
+ content[existingIndex] = newItem;
3997
+ } else {
3998
+ closePart(content[content.length - 1], now2);
3999
+ content.push(newItem);
4000
+ }
4001
+ }
4002
+ function closePart(prevPart, endedAt) {
4003
+ if (prevPart?.type === "reasoning") {
4004
+ prevPart.endedAt ??= endedAt;
4005
+ }
4006
+ }
3915
4007
  function patchContentWithDelta(content, delta) {
4008
+ if (delta === null)
4009
+ return;
4010
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
3916
4011
  const lastPart = content[content.length - 1];
3917
4012
  switch (delta.type) {
3918
4013
  case "text-delta":
3919
4014
  if (lastPart?.type === "text") {
3920
4015
  lastPart.text += delta.textDelta;
3921
4016
  } else {
4017
+ closePart(lastPart, now2);
3922
4018
  content.push({ type: "text", text: delta.textDelta });
3923
4019
  }
3924
4020
  break;
@@ -3926,54 +4022,61 @@ function patchContentWithDelta(content, delta) {
3926
4022
  if (lastPart?.type === "reasoning") {
3927
4023
  lastPart.text += delta.textDelta;
3928
4024
  } else {
4025
+ closePart(lastPart, now2);
3929
4026
  content.push({
3930
4027
  type: "reasoning",
3931
- text: delta.textDelta ?? ""
4028
+ text: delta.textDelta ?? "",
4029
+ startedAt: now2
3932
4030
  });
3933
4031
  }
3934
4032
  break;
3935
4033
  case "tool-stream": {
3936
- let _cacheKey = "";
3937
- let _cachedArgs = {};
3938
- const toolInvocation = {
3939
- type: "tool-invocation",
3940
- stage: "receiving",
3941
- invocationId: delta.invocationId,
3942
- name: delta.name,
3943
- partialArgsText: "",
3944
- get partialArgs() {
3945
- if (this.partialArgsText !== _cacheKey) {
3946
- _cachedArgs = parsePartialJsonObject(this.partialArgsText);
3947
- _cacheKey = this.partialArgsText;
3948
- }
3949
- return _cachedArgs;
3950
- }
3951
- };
4034
+ const toolInvocation = createReceivingToolInvocation(
4035
+ delta.invocationId,
4036
+ delta.name
4037
+ );
3952
4038
  content.push(toolInvocation);
3953
4039
  break;
3954
4040
  }
3955
4041
  case "tool-delta": {
3956
4042
  if (lastPart?.type === "tool-invocation" && lastPart.stage === "receiving") {
3957
- lastPart.partialArgsText += delta.delta;
4043
+ lastPart.__appendDelta?.(delta.delta);
3958
4044
  }
3959
4045
  break;
3960
4046
  }
3961
- case "tool-invocation": {
3962
- const existingIndex = findLastIndex(
3963
- content,
3964
- (part) => part.type === "tool-invocation" && part.invocationId === delta.invocationId
3965
- );
3966
- if (existingIndex > -1) {
3967
- content[existingIndex] = delta;
3968
- } else {
3969
- content.push(delta);
3970
- }
4047
+ case "tool-invocation":
4048
+ replaceOrAppend(content, delta, (x) => x.invocationId, now2);
4049
+ break;
4050
+ case "retrieval":
4051
+ replaceOrAppend(content, delta, (x) => x.id, now2);
3971
4052
  break;
3972
- }
3973
4053
  default:
3974
4054
  return assertNever(delta, "Unhandled case");
3975
4055
  }
3976
4056
  }
4057
+ function createReceivingToolInvocation(invocationId, name, partialArgsText = "") {
4058
+ const parser = new IncrementalJsonParser(partialArgsText);
4059
+ return {
4060
+ type: "tool-invocation",
4061
+ stage: "receiving",
4062
+ invocationId,
4063
+ name,
4064
+ // --- Alternative implementation for FRONTEND only ------------------------
4065
+ get partialArgsText() {
4066
+ return parser.source;
4067
+ },
4068
+ // prettier-ignore
4069
+ get partialArgs() {
4070
+ return parser.json;
4071
+ },
4072
+ // prettier-ignore
4073
+ __appendDelta(delta) {
4074
+ parser.append(delta);
4075
+ }
4076
+ // prettier-ignore
4077
+ // ------------------------------------------------------------------------
4078
+ };
4079
+ }
3977
4080
 
3978
4081
  // src/ai.ts
3979
4082
  var DEFAULT_REQUEST_TIMEOUT = 4e3;
@@ -4373,6 +4476,33 @@ function createStore_forChatMessages(toolsStore, setToolResultFn) {
4373
4476
  failAllPending,
4374
4477
  markMine(messageId) {
4375
4478
  myMessages.add(messageId);
4479
+ },
4480
+ /**
4481
+ * Iterates over all my auto-executing messages.
4482
+ *
4483
+ * These are messages that match all these conditions:
4484
+ * - The message is an assistant message
4485
+ * - The message is owned by this client ("mine")
4486
+ * - The message is currently in "awaiting-tool" status
4487
+ * - The message has at least one tool invocation in "executing" stage
4488
+ * - The tool invocation has an execute() function defined
4489
+ */
4490
+ *getAutoExecutingMessageIds() {
4491
+ for (const messageId of myMessages) {
4492
+ const message = getMessageById(messageId);
4493
+ if (message?.role === "assistant" && message.status === "awaiting-tool") {
4494
+ const isAutoExecuting = message.contentSoFar.some((part) => {
4495
+ if (part.type === "tool-invocation" && part.stage === "executing") {
4496
+ const tool = toolsStore.getTool\u03A3(part.name, message.chatId).get();
4497
+ return typeof tool?.execute === "function";
4498
+ }
4499
+ return false;
4500
+ });
4501
+ if (isAutoExecuting) {
4502
+ yield message.id;
4503
+ }
4504
+ }
4505
+ }
4376
4506
  }
4377
4507
  };
4378
4508
  }
@@ -4425,6 +4555,28 @@ function createAi(config) {
4425
4555
  toolsStore,
4426
4556
  knowledge: new KnowledgeStack()
4427
4557
  };
4558
+ const DELTA_THROTTLE = 25;
4559
+ let pendingDeltas = [];
4560
+ let deltaBatchTimer = null;
4561
+ function flushPendingDeltas() {
4562
+ const currentQueue = pendingDeltas;
4563
+ pendingDeltas = [];
4564
+ if (deltaBatchTimer !== null) {
4565
+ clearTimeout(deltaBatchTimer);
4566
+ deltaBatchTimer = null;
4567
+ }
4568
+ batch(() => {
4569
+ for (const { id, delta } of currentQueue) {
4570
+ context.messagesStore.addDelta(id, delta);
4571
+ }
4572
+ });
4573
+ }
4574
+ function enqueueDelta(id, delta) {
4575
+ pendingDeltas.push({ id, delta });
4576
+ if (deltaBatchTimer === null) {
4577
+ deltaBatchTimer = setTimeout(flushPendingDeltas, DELTA_THROTTLE);
4578
+ }
4579
+ }
4428
4580
  let lastTokenKey;
4429
4581
  function onStatusDidChange(_newStatus) {
4430
4582
  const authValue = managedSocket.authValue;
@@ -4464,6 +4616,7 @@ function createAi(config) {
4464
4616
  function onDidConnect() {
4465
4617
  }
4466
4618
  function onDidDisconnect() {
4619
+ flushPendingDeltas();
4467
4620
  }
4468
4621
  function handleServerMessage(event) {
4469
4622
  if (typeof event.data !== "string")
@@ -4478,50 +4631,51 @@ function createAi(config) {
4478
4631
  return;
4479
4632
  }
4480
4633
  if ("event" in msg) {
4481
- switch (msg.event) {
4482
- case "cmd-failed":
4483
- pendingCmd?.reject(new Error(msg.error));
4484
- break;
4485
- case "delta": {
4486
- const { id, delta } = msg;
4487
- context.messagesStore.addDelta(id, delta);
4488
- break;
4489
- }
4490
- case "settle": {
4491
- context.messagesStore.upsert(msg.message);
4492
- break;
4493
- }
4494
- case "warning":
4495
- warn(msg.message);
4496
- break;
4497
- case "error":
4498
- error2(msg.error);
4499
- break;
4500
- case "rebooted":
4501
- context.messagesStore.failAllPending();
4502
- break;
4503
- case "sync":
4504
- batch(() => {
4505
- for (const m of msg["-messages"] ?? []) {
4506
- context.messagesStore.remove(m.chatId, m.id);
4507
- }
4508
- for (const chatId of msg["-chats"] ?? []) {
4509
- context.chatsStore.markDeleted(chatId);
4510
- context.messagesStore.removeByChatId(chatId);
4511
- }
4512
- for (const chatId of msg.clear ?? []) {
4513
- context.messagesStore.removeByChatId(chatId);
4514
- }
4515
- if (msg.chats) {
4516
- context.chatsStore.upsertMany(msg.chats);
4517
- }
4518
- if (msg.messages) {
4519
- context.messagesStore.upsertMany(msg.messages);
4634
+ if (msg.event === "delta") {
4635
+ const { id, delta } = msg;
4636
+ enqueueDelta(id, delta);
4637
+ } else {
4638
+ batch(() => {
4639
+ flushPendingDeltas();
4640
+ switch (msg.event) {
4641
+ case "cmd-failed":
4642
+ pendingCmd?.reject(new Error(msg.error));
4643
+ break;
4644
+ case "settle": {
4645
+ context.messagesStore.upsert(msg.message);
4646
+ break;
4520
4647
  }
4521
- });
4522
- break;
4523
- default:
4524
- return assertNever(msg, "Unhandled case");
4648
+ case "warning":
4649
+ warn(msg.message);
4650
+ break;
4651
+ case "error":
4652
+ error2(msg.error);
4653
+ break;
4654
+ case "rebooted":
4655
+ context.messagesStore.failAllPending();
4656
+ break;
4657
+ case "sync":
4658
+ for (const m of msg["-messages"] ?? []) {
4659
+ context.messagesStore.remove(m.chatId, m.id);
4660
+ }
4661
+ for (const chatId of msg["-chats"] ?? []) {
4662
+ context.chatsStore.markDeleted(chatId);
4663
+ context.messagesStore.removeByChatId(chatId);
4664
+ }
4665
+ for (const chatId of msg.clear ?? []) {
4666
+ context.messagesStore.removeByChatId(chatId);
4667
+ }
4668
+ if (msg.chats) {
4669
+ context.chatsStore.upsertMany(msg.chats);
4670
+ }
4671
+ if (msg.messages) {
4672
+ context.messagesStore.upsertMany(msg.messages);
4673
+ }
4674
+ break;
4675
+ default:
4676
+ return assertNever(msg, "Unhandled case");
4677
+ }
4678
+ });
4525
4679
  }
4526
4680
  } else {
4527
4681
  switch (msg.cmd) {
@@ -4660,6 +4814,14 @@ function createAi(config) {
4660
4814
  messagesStore.markMine(resp.message.id);
4661
4815
  }
4662
4816
  }
4817
+ function handleBeforeUnload() {
4818
+ for (const messageId of context.messagesStore.getAutoExecutingMessageIds()) {
4819
+ sendClientMsgWithResponse({ cmd: "abort-ai", messageId }).catch(() => {
4820
+ });
4821
+ }
4822
+ }
4823
+ const win = typeof window !== "undefined" ? window : void 0;
4824
+ win?.addEventListener("beforeunload", handleBeforeUnload, { once: true });
4663
4825
  return Object.defineProperty(
4664
4826
  {
4665
4827
  [kInternal]: {
@@ -4727,7 +4889,7 @@ function makeCreateSocketDelegateForAi(baseUrl, WebSocketPolyfill) {
4727
4889
  }
4728
4890
  const url2 = new URL(baseUrl);
4729
4891
  url2.protocol = url2.protocol === "http:" ? "ws" : "wss";
4730
- url2.pathname = "/ai/v5";
4892
+ url2.pathname = "/ai/v6";
4731
4893
  if (authValue.type === "secret") {
4732
4894
  url2.searchParams.set("tok", authValue.token.raw);
4733
4895
  } else if (authValue.type === "public") {