cozy-search 0.19.0 → 0.19.2

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.
@@ -202,7 +202,7 @@ var CozyAssistantRuntimeProviderInner = function CozyAssistantRuntimeProviderInn
202
202
 
203
203
  try {
204
204
  if (res.object === 'delta' && res.content !== undefined) {
205
- streamBridgeRef.current.onDelta(conversationId, res.content);
205
+ streamBridgeRef.current.onDelta(conversationId, res.content, res.position);
206
206
  }
207
207
 
208
208
  if (res.object === 'done') {
@@ -8,6 +8,8 @@
8
8
  export declare class StreamBridge {
9
9
  private streams;
10
10
  private cleanupCallback;
11
+ private positionBuffers;
12
+ private nextPositions;
11
13
  /**
12
14
  * Sets a callback to be invoked when cleanup is called.
13
15
  * This allows the provider to mark the current message as cancelled.
@@ -20,9 +22,10 @@ export declare class StreamBridge {
20
22
  createStream(conversationId: string): AsyncIterableIterator<string>;
21
23
  /**
22
24
  * Called when a delta event is received from WebSocket.
23
- * Pushes the content chunk to the appropriate stream.
25
+ * When a position is provided, chunks are buffered and flushed in order.
26
+ * Without a position, chunks are pushed directly in arrival order.
24
27
  */
25
- onDelta(conversationId: string, content: string): void;
28
+ onDelta(conversationId: string, content: string, position?: number): void;
26
29
  /**
27
30
  * Called when a 'done' event is received from WebSocket.
28
31
  * Completes the stream for the conversation.
@@ -25,6 +25,8 @@ var StreamBridge = /*#__PURE__*/function () {
25
25
  (0, _classCallCheck2.default)(this, StreamBridge);
26
26
  (0, _defineProperty2.default)(this, "streams", new Map());
27
27
  (0, _defineProperty2.default)(this, "cleanupCallback", null);
28
+ (0, _defineProperty2.default)(this, "positionBuffers", new Map());
29
+ (0, _defineProperty2.default)(this, "nextPositions", new Map());
28
30
  }
29
31
 
30
32
  (0, _createClass2.default)(StreamBridge, [{
@@ -128,17 +130,59 @@ var StreamBridge = /*#__PURE__*/function () {
128
130
  }
129
131
  /**
130
132
  * Called when a delta event is received from WebSocket.
131
- * Pushes the content chunk to the appropriate stream.
133
+ * When a position is provided, chunks are buffered and flushed in order.
134
+ * Without a position, chunks are pushed directly in arrival order.
132
135
  */
133
136
 
134
137
  }, {
135
138
  key: "onDelta",
136
- value: function onDelta(conversationId, content) {
139
+ value: function onDelta(conversationId, content, position) {
140
+ var _this$nextPositions$g;
141
+
137
142
  var stream = this.streams.get(conversationId);
143
+ if (!stream) return;
138
144
 
139
- if (stream) {
145
+ if (position === undefined) {
146
+ stream.push(content);
147
+ return;
148
+ } // Fast path: chunk arrived in order, no buffering needed
149
+
150
+
151
+ var nextExpected = (_this$nextPositions$g = this.nextPositions.get(conversationId)) !== null && _this$nextPositions$g !== void 0 ? _this$nextPositions$g : 0;
152
+
153
+ if (position === nextExpected) {
140
154
  stream.push(content);
155
+ var next = nextExpected + 1; // Flush any previously buffered chunks that are now contiguous
156
+
157
+ var _buffer = this.positionBuffers.get(conversationId);
158
+
159
+ if (_buffer && _buffer.size > 0) {
160
+ while (_buffer.has(next)) {
161
+ stream.push(_buffer.get(next));
162
+
163
+ _buffer.delete(next);
164
+
165
+ next++;
166
+ }
167
+
168
+ if (_buffer.size === 0) {
169
+ this.positionBuffers.delete(conversationId);
170
+ }
171
+ }
172
+
173
+ this.nextPositions.set(conversationId, next);
174
+ return;
175
+ } // Out-of-order: buffer until the gap is filled
176
+
177
+
178
+ var buffer = this.positionBuffers.get(conversationId);
179
+
180
+ if (!buffer) {
181
+ buffer = new Map();
182
+ this.positionBuffers.set(conversationId, buffer);
141
183
  }
184
+
185
+ buffer.set(position, content);
142
186
  }
143
187
  /**
144
188
  * Called when a 'done' event is received from WebSocket.
@@ -152,6 +196,8 @@ var StreamBridge = /*#__PURE__*/function () {
152
196
 
153
197
  if (stream) {
154
198
  stream.complete();
199
+ this.positionBuffers.delete(conversationId);
200
+ this.nextPositions.delete(conversationId);
155
201
  }
156
202
  }
157
203
  /**
@@ -166,6 +212,8 @@ var StreamBridge = /*#__PURE__*/function () {
166
212
 
167
213
  if (stream) {
168
214
  stream.error(error);
215
+ this.positionBuffers.delete(conversationId);
216
+ this.nextPositions.delete(conversationId);
169
217
  }
170
218
  }
171
219
  /**
@@ -185,6 +233,8 @@ var StreamBridge = /*#__PURE__*/function () {
185
233
 
186
234
  stream.complete();
187
235
  this.streams.delete(conversationId);
236
+ this.positionBuffers.delete(conversationId);
237
+ this.nextPositions.delete(conversationId);
188
238
  }
189
239
  }
190
240
  /**
@@ -63,6 +63,54 @@ describe('StreamBridge', () => {
63
63
  expect(result.done).toBe(true)
64
64
  })
65
65
 
66
+ it('should reorder out-of-order deltas when position is provided', async () => {
67
+ const iterator = bridge.createStream('convo_1')
68
+
69
+ // Arrive out of order: position 1 before position 0
70
+ bridge.onDelta('convo_1', 'world!', 1)
71
+ bridge.onDelta('convo_1', 'Hello ', 0)
72
+
73
+ const first = await iterator.next()
74
+ expect(first).toEqual({ value: 'Hello ', done: false })
75
+
76
+ const second = await iterator.next()
77
+ expect(second).toEqual({ value: 'world!', done: false })
78
+ })
79
+
80
+ it('should flush buffered positions in order when gap is filled', async () => {
81
+ const iterator = bridge.createStream('convo_1')
82
+
83
+ // Positions 2 and 1 arrive before 0
84
+ bridge.onDelta('convo_1', 'c', 2)
85
+ bridge.onDelta('convo_1', 'b', 1)
86
+ // Nothing yielded yet since position 0 is missing
87
+
88
+ bridge.onDelta('convo_1', 'a', 0)
89
+ // Now all three should flush in order
90
+
91
+ const first = await iterator.next()
92
+ expect(first).toEqual({ value: 'a', done: false })
93
+
94
+ const second = await iterator.next()
95
+ expect(second).toEqual({ value: 'b', done: false })
96
+
97
+ const third = await iterator.next()
98
+ expect(third).toEqual({ value: 'c', done: false })
99
+ })
100
+
101
+ it('should push directly when no position is provided', async () => {
102
+ const iterator = bridge.createStream('convo_1')
103
+
104
+ bridge.onDelta('convo_1', 'Hello ')
105
+ bridge.onDelta('convo_1', 'world!')
106
+
107
+ const first = await iterator.next()
108
+ expect(first).toEqual({ value: 'Hello ', done: false })
109
+
110
+ const second = await iterator.next()
111
+ expect(second).toEqual({ value: 'world!', done: false })
112
+ })
113
+
66
114
  it('multiple unresolved next calls should reject to prevent concurrency issues', async () => {
67
115
  const iterator = bridge.createStream('convo_1')
68
116
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cozy-search",
3
3
  "description": "UI components about search bar and IA assistant",
4
- "version": "0.19.0",
4
+ "version": "0.19.2",
5
5
  "author": {
6
6
  "name": "Cozy Cloud",
7
7
  "url": "https://github.com/cozy"
@@ -29,21 +29,21 @@
29
29
  "cozy-bar": "^29.0.0",
30
30
  "cozy-client": "^60.21.0",
31
31
  "cozy-device-helper": "^4.0.3",
32
- "cozy-flags": "^4.9.0",
33
- "cozy-intent": "^2.31.0",
34
- "cozy-logger": "^1.18.0",
32
+ "cozy-flags": "^4.9.1",
33
+ "cozy-intent": "^2.31.1",
34
+ "cozy-logger": "^1.18.1",
35
35
  "cozy-minilog": "^3.10.1",
36
36
  "cozy-pouch-link": "^54.0.0",
37
- "cozy-realtime": "^5.9.0",
37
+ "cozy-realtime": "^5.9.1",
38
38
  "cozy-ui": "^135.6.0",
39
- "cozy-ui-plus": "^5.3.0",
39
+ "cozy-ui-plus": "^5.3.1",
40
40
  "cross-fetch": "^4.0.0",
41
- "jest": "28.1.3",
41
+ "jest": "30.3.0",
42
42
  "react": "18.2.0",
43
43
  "react-dom": "18.2.0",
44
44
  "react-router-dom": "6.14.2",
45
45
  "stylus": "^0.64.0",
46
- "twake-i18n": "^0.4.0",
46
+ "twake-i18n": "^0.4.1",
47
47
  "typescript": "5.5.2"
48
48
  },
49
49
  "files": [
@@ -94,5 +94,5 @@
94
94
  "lint": "cd .. && yarn eslint --ext js,jsx,ts packages/cozy-search"
95
95
  },
96
96
  "types": "dist/index.d.ts",
97
- "gitHead": "a2816d422234922d845a1b6c01fae4c45fe81fc8"
97
+ "gitHead": "661b14497f4331618699916112fbdb1d5ed2d606"
98
98
  }