@vincemakes/kiso-core 0.1.32 → 0.1.33

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.
@@ -85,6 +85,7 @@ export function projectMessages(events) {
85
85
  assistantSource = undefined;
86
86
  return;
87
87
  }
88
+ const callIds = blocks.filter((b) => b.type === "tool_use").map((b) => b.callId);
88
89
  const reasoning = pendingReasoning;
89
90
  pendingReasoning = null;
90
91
  out.push({
@@ -95,6 +96,15 @@ export function projectMessages(events) {
95
96
  });
96
97
  blocks = [];
97
98
  assistantSource = undefined;
99
+ // P1: the closed assistant's calls whose results are NOT already
100
+ // buffered are pending — their results land later (post-stop late
101
+ // results, the straddle's shape). The mid-execution inputs hold
102
+ // until those results flush.
103
+ // the guard keeps a still-pending pair holding when a call-less
104
+ // assistant closes mid-hold (the empty set would release the input)
105
+ const buffered = new Set(resultBuf.map((r) => r.callId));
106
+ if (callIds.length > 0)
107
+ pendingCalls = new Set(callIds.filter((c) => !buffered.has(c)));
98
108
  };
99
109
  // C group/round 6: vetoed/rewritten user inputs. Collect the replacement map
100
110
  // first — the FINAL replacement for each input wins (later replacements
@@ -150,15 +160,32 @@ export function projectMessages(events) {
150
160
  let resultBuf = [];
151
161
  const callOrder = new Map();
152
162
  let callOrderNext = 0;
153
- const flushResults = () => {
154
- if (resultBuf.length === 0)
155
- return;
156
- resultBuf.sort((a, b) => (callOrder.get(a.callId) ?? 0) - (callOrder.get(b.callId) ?? 0));
157
- for (const r of resultBuf)
158
- out.push(r.message);
159
- resultBuf = [];
160
- callOrder.clear();
161
- callOrderNext = 0;
163
+ // P1 (0.1.42): the mid-execution input deferral. A user input arriving
164
+ // while the last closed assistant's tool_calls are UNANSWERED (the
165
+ // result lands later in the log — the boundary straddle's durable
166
+ // shape) must never separate the call from its result: the request
167
+ // [assistant, user, tool] is a real provider 400. Such inputs are
168
+ // HELD and released right after the results flush — the mid-execution
169
+ // input takes effect when the turn's execution completes.
170
+ let pendingCalls = new Set();
171
+ let heldUsers = [];
172
+ const flushResults = (force = false) => {
173
+ if (resultBuf.length > 0) {
174
+ resultBuf.sort((a, b) => (callOrder.get(a.callId) ?? 0) - (callOrder.get(b.callId) ?? 0));
175
+ for (const r of resultBuf)
176
+ out.push(r.message);
177
+ resultBuf = [];
178
+ callOrder.clear();
179
+ callOrderNext = 0;
180
+ }
181
+ // The held inputs release ONLY when the pair resolved (or the
182
+ // projection ends — force): a still-pending pair must keep holding,
183
+ // or the next user_input's leading flush would dump the inputs
184
+ // between the call and its late result (the 400 again).
185
+ if (force || pendingCalls.size === 0) {
186
+ out.push(...heldUsers);
187
+ heldUsers = [];
188
+ }
162
189
  };
163
190
  let explicitAssistant = false;
164
191
  for (const ev of events) {
@@ -186,30 +213,41 @@ export function projectMessages(events) {
186
213
  case "user_input": {
187
214
  // 0.1.26: the previous turn closes here — the assistant
188
215
  // first, then its results in call order (the turn boundary).
189
- flushAssistant();
190
- flushResults();
191
216
  // round 6: the final replacement renders HERE, at the input's own
192
217
  // position — the original is skipped, the replacement event
193
218
  // itself produces nothing (a later replacement for the same
194
- // input never becomes a second message).
219
+ // input never becomes a second message); a null content is a
220
+ // true veto (nothing renders at that position).
221
+ flushAssistant();
222
+ flushResults();
223
+ let content = ev.content;
224
+ let source = ev.source;
225
+ let veto = false;
195
226
  if ("seq" in ev && typeof ev.seq === "number" && replaced.has(ev.seq)) {
196
227
  const replacement = replaced.get(ev.seq);
197
- flushAssistant();
198
- if (replacement.content !== null) {
199
- out.push({
200
- role: "user",
201
- content: replacement.content,
202
- ...(replacement.source !== undefined ? { source: replacement.source } : {}),
203
- });
228
+ if (replacement.content === null) {
229
+ veto = true;
230
+ }
231
+ else {
232
+ content = replacement.content;
233
+ source = replacement.source ?? ev.source;
204
234
  }
205
- break;
206
235
  }
207
- flushAssistant();
208
- out.push({
236
+ if (veto)
237
+ break;
238
+ const message = {
209
239
  role: "user",
210
- content: ev.content,
211
- ...(ev.source !== undefined ? { source: ev.source } : {}),
212
- });
240
+ content,
241
+ ...(source !== undefined ? { source } : {}),
242
+ };
243
+ // P1: the mid-execution deferral — an input arriving while
244
+ // the turn's calls are unanswered is HELD and released right
245
+ // after the results flush (the pairing invariant: the
246
+ // results must follow their calls before any user message).
247
+ if (pendingCalls.size > 0)
248
+ heldUsers.push(message);
249
+ else
250
+ out.push(message);
213
251
  break;
214
252
  }
215
253
  case "user_input_replaced":
@@ -253,18 +291,16 @@ export function projectMessages(events) {
253
291
  case "text_delta":
254
292
  // 0.1.26: a text delta with BUFFERED RESULTS opens the NEXT
255
293
  // turn's stream — the previous turn closes HERE: its
256
- // assistant first (already closed at the stop, usually),
257
- // then its buffered results in call order (the turn
258
- // boundary; the API requires each tool_calls message to be
259
- // followed by its tool messages a real DeepSeek 400
260
- // without the boundary). The guard keys on the RESULT
261
- // buffer: a mid-message delta (the current turn's text, the
262
- // multi-block assistant) has nothing buffered and must not
263
- // flush the empty flush would clear the source and split
264
- // the message.
265
- if (resultBuf.length > 0)
266
- flushAssistant();
267
- flushResults();
294
+ // assistant (closed at the stop), then its buffered results
295
+ // in call order (the API requires each tool_calls message
296
+ // to be followed by its tool messages a real 400 without
297
+ // the boundary). P1 (0.1.42): the flush keys on OPEN
298
+ // assistant content an open block or text proves the
299
+ // model is still streaming THIS turn, and a same-turn
300
+ // buffered result must stay buffered (flushing it here
301
+ // split the turn's message the fresh2 400).
302
+ if (blocks.length === 0 && text === null && resultBuf.length > 0)
303
+ flushResults();
268
304
  text = (text ?? "") + ev.text;
269
305
  break;
270
306
  case "tool_call_start":
@@ -276,11 +312,14 @@ export function projectMessages(events) {
276
312
  case "tool_call_end":
277
313
  // 0.1.26: a tool_call_end with BUFFERED RESULTS opens the
278
314
  // NEXT turn's stream (the model called again) — the
279
- // previous turn closes first; a same-turn call has nothing
280
- // buffered.
281
- if (resultBuf.length > 0)
282
- flushAssistant();
283
- flushResults();
315
+ // previous turn's results close first; a same-turn call
316
+ // keeps the assistant open. P1 (0.1.42): open assistant
317
+ // content proves the same-turn call — the buffered results
318
+ // close at the stop, NEVER here (flushing a same-turn
319
+ // result at a later call_end split the turn's message —
320
+ // the fresh2 400).
321
+ if (blocks.length === 0 && text === null && resultBuf.length > 0)
322
+ flushResults();
284
323
  pushText();
285
324
  callOrder.set(ev.callId, callOrderNext++);
286
325
  blocks.push({
@@ -315,6 +354,9 @@ export function projectMessages(events) {
315
354
  }
316
355
  // 0.1.26: BUFFERED — the results flush in call order at the
317
356
  // turn boundary (flushResults), not in completion order.
357
+ // P1: a pending call's result resolves the pair — the held
358
+ // mid-execution inputs release with the flush.
359
+ pendingCalls.delete(ev.callId);
318
360
  resultBuf.push({ callId: ev.callId, message });
319
361
  break;
320
362
  }
@@ -374,9 +416,11 @@ export function projectMessages(events) {
374
416
  // the assistant message that follows carries it. 0.1.26: the
375
417
  // assistant flush is guarded on the buffered results (a
376
418
  // mid-turn reasoning must not split the current message).
377
- if (resultBuf.length > 0)
378
- flushAssistant();
379
- flushResults();
419
+ // P1 (0.1.42): the flush keys on OPEN assistant content —
420
+ // a mid-turn reasoning with a same-turn result buffered
421
+ // must NOT split the message (the fresh2 400 family).
422
+ if (blocks.length === 0 && text === null && resultBuf.length > 0)
423
+ flushResults();
380
424
  pendingReasoning = (pendingReasoning ?? "") + ev.text;
381
425
  break;
382
426
  case "summarized":
@@ -397,9 +441,14 @@ export function projectMessages(events) {
397
441
  break;
398
442
  case "stop":
399
443
  // The turn boundary: the assistant closes at the provider's
400
- // stop; the results are buffered and flush at the next
401
- // turn's first event.
444
+ // stop — and the turn's BUFFERED results close WITH it (P1
445
+ // 0.1.42). Parallel execution lands same-turn results
446
+ // mid-stream; flushing them here — the true boundary —
447
+ // projects [assistant, results…] in reading order, and the
448
+ // stream-open guards below only ever flush a CLOSED turn's
449
+ // late (post-stop) results.
402
450
  flushAssistant();
451
+ flushResults();
403
452
  break;
404
453
  case "terminal":
405
454
  case "tool_execution_started":
@@ -417,7 +466,7 @@ export function projectMessages(events) {
417
466
  }
418
467
  }
419
468
  flushAssistant();
420
- flushResults();
469
+ flushResults(true); // the log's end is the last resort: held inputs never drop
421
470
  return out;
422
471
  }
423
472
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-core",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "kiso (foundation) core — protocol, event log, loop, hooks, modes, permissions, compaction, delivery truth. The 2,000-line kernel at the bottom of the kiso framework.",
5
5
  "type": "module",
6
6
  "license": "MIT",