dsh-lcx-codex 0.4.3 → 0.5.0-pre.1

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.
@@ -1,9 +1,23 @@
1
1
  // @ts-check
2
2
  import { isCompactCheckpointSource } from "@deepseek-ai/dsh-compaction";
3
- import { SessionSeq, } from "@deepseek-ai/dsh-session";
3
+ import { projectToolUpdates, } from "@deepseek-ai/dsh-llm";
4
4
  import { baseURLFingerprint, routeCompatible } from "./route.js";
5
5
  import { persistNativeImageReferences, responseInputItems } from "./dsh-responses.js";
6
6
  import { estimateBudgetItem, modelVisibleBudgetView, portableBudgetError, portableTokenCeiling, } from "./token-budget.js";
7
+ function sessionQueryUnavailable(cause) {
8
+ return Object.assign(new Error("LCX checkpoint history requires the DSH session query service", { cause }), { code: "LCX_SESSION_QUERY_UNAVAILABLE" });
9
+ }
10
+ async function observeEvents(ctx, session) {
11
+ const query = ctx.get("sessionQuery");
12
+ if (!query)
13
+ throw sessionQueryUnavailable();
14
+ try {
15
+ return await query.observeSession(session.id, { projectionMode: "none" });
16
+ }
17
+ catch (cause) {
18
+ throw sessionQueryUnavailable(cause);
19
+ }
20
+ }
7
21
  /**
8
22
  * @typedef {object} NativeCheckpointBase
9
23
  * @property {string} compactionId
@@ -41,6 +55,7 @@ import { estimateBudgetItem, modelVisibleBudgetView, portableBudgetError, portab
41
55
  /** @typedef {{ session: Session, route: RouteIdentity, result: NativeCompactionResult, input?: unknown[], ephemeralPreludeItemCount?: number, imageMap?: unknown, retentionOptions?: RetentionOptions }} CreateCheckpointOptions */
42
56
  /** @typedef {Error & { code?: string }} LcxError */
43
57
  export const NATIVE_BLOCK_TYPE = "lcx-native-compaction-v5";
58
+ const MIGRATED_NATIVE_BLOCK_PREFIX = "plugin:lcx-native-compaction-";
44
59
  export const NATIVE_BLOCK_VERSION = 5;
45
60
  export const RETAINED_MESSAGE_TOKEN_BUDGET = 64_000;
46
61
  export const ASSISTANT_RETENTION_TOKEN_RESERVE = 24_000;
@@ -267,38 +282,51 @@ export function compactCheckpointId(message) {
267
282
  }
268
283
  /** Reject reserved checkpoint markers without interpreting unsupported formats or opening sidecars. */
269
284
  export function assertSupportedCheckpointMessage(message) {
285
+ // Legacy marker-only messages had no DSH checkpoint source. A marker quoted
286
+ // inside ordinary content (for example a tool's source-file excerpt) is data.
287
+ const legacyMarkerOnly = message.role === "user" && message.content.length === 1 &&
288
+ message.content[0]?.type === "text" &&
289
+ /^\[dsh-lcx-codex-[^\]\r\n]*checkpoint:[^\]\r\n]*\]$/iu.test(message.content[0].text.trim());
270
290
  for (const block of message.content) {
271
- if (block.type.startsWith("lcx-native-compaction-") && block.type !== NATIVE_BLOCK_TYPE ||
272
- block.type === "text" && /\[dsh-lcx-codex-[^\]\r\n]*checkpoint:/iu.test(block.text))
291
+ if ((block.type.startsWith("lcx-native-compaction-") && block.type !== NATIVE_BLOCK_TYPE) ||
292
+ block.type.startsWith(MIGRATED_NATIVE_BLOCK_PREFIX) ||
293
+ block.type === "text" && (legacyMarkerOnly ||
294
+ compactCheckpointId(message) !== undefined && /\[dsh-lcx-codex-[^\]\r\n]*checkpoint:/iu.test(block.text)))
273
295
  throw Object.assign(new Error("Unsupported LCX checkpoint format; start a new session"), {
274
296
  code: "LCX_CHECKPOINT_UNSUPPORTED",
275
297
  });
276
298
  }
277
299
  }
278
- /** @param {Session | null | undefined} session */
279
- export function activeCompactionId(session) {
300
+ /** The active transaction is read from one immutable query observation. */
301
+ export async function activeCompactionId(ctx, session) {
280
302
  if (!session)
281
303
  return undefined;
282
304
  /** @type {Set<string>} */ const ended = new Set();
283
- const events = session.snapshotEvents();
284
- for (let index = events.length - 1; index >= 0; index -= 1) {
285
- const event = events[index];
286
- if (!event)
287
- continue;
288
- if (event.type === "compaction/end")
289
- ended.add(String(event.data.compactionId));
290
- if (event.type === "compaction/start" &&
291
- !ended.has(String(event.data.compactionId)))
292
- return String(event.data.compactionId);
305
+ const lease = await observeEvents(ctx, session);
306
+ try {
307
+ const events = lease.events;
308
+ for (let index = events.length - 1; index >= 0; index -= 1) {
309
+ const event = events[index];
310
+ if (!event)
311
+ continue;
312
+ if (event.type === "compaction/end")
313
+ ended.add(String(event.data.compactionId));
314
+ if (event.type === "compaction/start" &&
315
+ !ended.has(String(event.data.compactionId)))
316
+ return String(event.data.compactionId);
317
+ }
318
+ return undefined;
319
+ }
320
+ finally {
321
+ lease[Symbol.dispose]();
293
322
  }
294
- return undefined;
295
323
  }
296
324
  /**
297
325
  * @param {CreateCheckpointOptions} options
298
- * @returns {NativeCheckpointV5}
326
+ * @returns {Promise<NativeCheckpointV5>}
299
327
  */
300
- export function createNativeCheckpointBlock({ session, route, result, input = [], ephemeralPreludeItemCount = 0, imageMap, retentionOptions = {}, }) {
301
- const compactionId = activeCompactionId(session);
328
+ export async function createNativeCheckpointBlock({ ctx, session, route, result, input = [], ephemeralPreludeItemCount = 0, imageMap, retentionOptions = {}, }) {
329
+ const compactionId = await activeCompactionId(ctx, session);
302
330
  if (!compactionId) {
303
331
  const error = new Error("Native compaction could not correlate the active DSH compaction transaction");
304
332
  error.code = "LCX_COMPACTION_ID_UNAVAILABLE";
@@ -350,19 +378,25 @@ export function nativeCheckpointChunks(block, usage) {
350
378
  /**
351
379
  * @param {Session | null | undefined} session
352
380
  * @param {string | null | undefined} compactionId
353
- * @returns {CompactionSummaryEvent | undefined}
381
+ * @returns {Promise<CompactionSummaryEvent | undefined>}
354
382
  */
355
- export function compactionSummaryEvent(session, compactionId) {
383
+ export async function compactionSummaryEvent(ctx, session, compactionId) {
356
384
  if (!session || !compactionId)
357
385
  return undefined;
358
- const events = session.snapshotEvents();
359
- for (let index = events.length - 1; index >= 0; index -= 1) {
360
- const event = events[index];
361
- if (event?.type === "compaction/summary" &&
362
- String(event.data.compactionId) === compactionId)
363
- return event;
386
+ const lease = await observeEvents(ctx, session);
387
+ try {
388
+ const events = lease.events;
389
+ for (let index = events.length - 1; index >= 0; index -= 1) {
390
+ const event = events[index];
391
+ if (event?.type === "compaction/summary" &&
392
+ String(event.data.compactionId) === compactionId)
393
+ return event;
394
+ }
395
+ return undefined;
396
+ }
397
+ finally {
398
+ lease[Symbol.dispose]();
364
399
  }
365
- return undefined;
366
400
  }
367
401
  /**
368
402
  * rawOutput elements remain untrusted until stateFromSummaryEvent validates them.
@@ -378,11 +412,20 @@ function nativeBlockFromSummary(event) {
378
412
  const raw = event?.data.rawOutput;
379
413
  if (!raw)
380
414
  return undefined;
381
- for (const value of raw)
415
+ assertNoMigratedNativeBlock(raw);
416
+ for (const value of raw) {
382
417
  if (isNativeCheckpointCandidate(value))
383
418
  return value;
419
+ }
384
420
  return undefined;
385
421
  }
422
+ function assertNoMigratedNativeBlock(raw) {
423
+ if (raw?.some(value => isObject(value) && typeof value.type === "string" &&
424
+ value.type.startsWith(MIGRATED_NATIVE_BLOCK_PREFIX)))
425
+ throw Object.assign(new Error("Unsupported LCX checkpoint format; start a new session"), {
426
+ code: "LCX_CHECKPOINT_UNSUPPORTED",
427
+ });
428
+ }
386
429
  /** @param {unknown} value */
387
430
  function validateCount(value) {
388
431
  return (value === undefined ||
@@ -465,14 +508,18 @@ export function stateFromSummaryEvent(event) {
465
508
  * @param {Session} session
466
509
  * @param {Message} message
467
510
  */
468
- export function checkpointStateForMessage(session, message) {
511
+ export function checkpointStateForMessage(ctx, session, message) {
469
512
  assertSupportedCheckpointMessage(message);
470
513
  const id = compactCheckpointId(message);
471
514
  if (!id)
472
515
  return undefined;
473
- const event = compactionSummaryEvent(session, id);
516
+ return checkpointStateForMessageAsync(ctx, session, id);
517
+ }
518
+ async function checkpointStateForMessageAsync(ctx, session, id) {
519
+ const event = await compactionSummaryEvent(ctx, session, id);
474
520
  const state = stateFromSummaryEvent(event);
475
- const hasNativeBlock = event?.data.rawOutput?.some(value => isObject(value) && typeof value.type === "string" && value.type.startsWith("lcx-native-compaction-"));
521
+ const hasNativeBlock = event?.data.rawOutput?.some(value => isObject(value) && typeof value.type === "string" &&
522
+ (value.type.startsWith("lcx-native-compaction-") || value.type.startsWith(MIGRATED_NATIVE_BLOCK_PREFIX)));
476
523
  if (hasNativeBlock && !state)
477
524
  throw Object.assign(new Error("Unsupported or invalid LCX checkpoint; start a new session"), { code: "LCX_CHECKPOINT_UNSUPPORTED" });
478
525
  return state;
@@ -485,16 +532,55 @@ export function checkpointStateForMessage(session, message) {
485
532
  export function stateRouteCompatible(state, route, ctx) {
486
533
  return routeCompatible(state, route, ctx);
487
534
  }
535
+ const DSH_PROJECTION_UNAVAILABLE_PREFIX = 'session message projection "';
536
+ const DSH_PROJECTION_UNAVAILABLE_SUFFIX = '" was removed or replaced; restore the session with its owning plugin';
537
+ /**
538
+ * DSH 0.1.6 attaches no error code or subclass to this failure and exposes no
539
+ * public inventory of required or active projections, so message shape is the
540
+ * only available signal. Both classes fail closed, so a misclassification
541
+ * costs precision in the error code, not correctness.
542
+ * @param {unknown} cause
543
+ */
544
+ function isMissingOrReplacedProjectionError(cause) {
545
+ if (!(cause instanceof Error))
546
+ return false;
547
+ const { message } = cause;
548
+ if (!message.startsWith(DSH_PROJECTION_UNAVAILABLE_PREFIX) ||
549
+ !message.endsWith(DSH_PROJECTION_UNAVAILABLE_SUFFIX))
550
+ return false;
551
+ const projectionType = message.slice(DSH_PROJECTION_UNAVAILABLE_PREFIX.length, message.length - DSH_PROJECTION_UNAVAILABLE_SUFFIX.length);
552
+ return projectionType.length > 0;
553
+ }
554
+ function checkpointHistoryReadError(cause) {
555
+ return Object.assign(new Error("Unsupported or invalid LCX checkpoint history; start a new session", {
556
+ cause,
557
+ }), { code: "LCX_CHECKPOINT_UNSUPPORTED" });
558
+ }
488
559
  /**
560
+ * DSH 0.1.6 derives a message only while every `SessionMessageProjection` the
561
+ * durable log committed is still registered; an absent or replaced owner throws
562
+ * instead of yielding pre-projection content. Reconstructing the shadowed
563
+ * transcript without that owner would resurrect content a durable decision
564
+ * (for example `image/offload`) removed, so this is a checkpoint
565
+ * incompatibility rather than a recoverable read.
489
566
  * @param {Session} session
490
567
  * @param {number} seq
491
568
  * @returns {Message | undefined}
492
569
  */
493
- function eventMessage(session, seq) {
570
+ function eventMessage(session, events, seq) {
494
571
  if (!Number.isSafeInteger(seq) || seq < 0)
495
- return undefined;
496
- const event = session.eventAt(SessionSeq(seq));
497
- return event ? (session.deriveEventMessage(event) ?? undefined) : undefined;
572
+ throw checkpointHistoryReadError(new Error("Portable checkpoint history contains an invalid event sequence"));
573
+ try {
574
+ const event = events[seq];
575
+ if (!event)
576
+ throw new Error(`Portable checkpoint history is missing event at seq ${seq}`);
577
+ return session.deriveEventMessage(event) ?? undefined;
578
+ }
579
+ catch (cause) {
580
+ if (isMissingOrReplacedProjectionError(cause))
581
+ throw Object.assign(new Error("Session history requires a message projection this runtime does not provide; restore the owning plugin to reuse this checkpoint", { cause }), { code: "LCX_CHECKPOINT_PROJECTION_UNAVAILABLE" });
582
+ throw checkpointHistoryReadError(cause);
583
+ }
498
584
  }
499
585
  /** @param {Message} message */
500
586
  function estimateChars(message) {
@@ -509,50 +595,60 @@ function estimateChars(message) {
509
595
  /**
510
596
  * @param {Session} session
511
597
  * @param {string} compactionId
512
- * @returns {Message[]}
598
+ * @returns {Promise<Message[]>}
513
599
  */
514
- function expandedCheckpointMessages(session, compactionId) {
515
- const summaries = new Map();
516
- for (const event of session.snapshotEvents())
517
- if (event.type === "compaction/summary")
518
- summaries.set(String(event.data.compactionId), event);
519
- const active = new Set();
520
- const stack = [];
521
- const enter = (id) => {
522
- const summary = summaries.get(id);
523
- if (active.has(id) || !summary)
524
- throw Object.assign(new Error("Portable checkpoint history is cyclic or incomplete"), { code: "LCX_CHECKPOINT_UNSUPPORTED" });
525
- active.add(id);
526
- stack.push({ id, seqs: summary.data.shadowedSeqs, index: 0 });
527
- };
528
- // Walk the DSH event graph iteratively so repeated compaction has no depth cutoff.
529
- const result = [];
530
- enter(compactionId);
531
- while (stack.length) {
532
- const frame = stack[stack.length - 1];
533
- if (frame.index === frame.seqs.length) {
534
- active.delete(frame.id);
535
- stack.pop();
536
- continue;
600
+ async function expandedCheckpointMessages(ctx, session, compactionId) {
601
+ const lease = await observeEvents(ctx, session);
602
+ try {
603
+ const events = lease.events;
604
+ const summaries = new Map();
605
+ for (const event of events)
606
+ if (event.type === "compaction/summary")
607
+ summaries.set(String(event.data.compactionId), event);
608
+ const active = new Set();
609
+ const stack = [];
610
+ const enter = (id) => {
611
+ const summary = summaries.get(id);
612
+ if (active.has(id) || !summary)
613
+ throw Object.assign(new Error("Portable checkpoint history is cyclic or incomplete"), { code: "LCX_CHECKPOINT_UNSUPPORTED" });
614
+ assertNoMigratedNativeBlock(summary.data.rawOutput);
615
+ active.add(id);
616
+ stack.push({ id, seqs: summary.data.shadowedSeqs, index: 0 });
617
+ };
618
+ // Walk the DSH event graph iteratively so repeated compaction has no depth cutoff.
619
+ const result = [];
620
+ enter(compactionId);
621
+ while (stack.length) {
622
+ const frame = stack[stack.length - 1];
623
+ if (frame.index === frame.seqs.length) {
624
+ active.delete(frame.id);
625
+ stack.pop();
626
+ continue;
627
+ }
628
+ const message = eventMessage(session, events, frame.seqs[frame.index++]);
629
+ if (!message)
630
+ continue;
631
+ assertSupportedCheckpointMessage(message);
632
+ const nested = compactCheckpointId(message);
633
+ if (nested)
634
+ enter(nested);
635
+ else
636
+ result.push(structuredClone(message));
537
637
  }
538
- const message = eventMessage(session, frame.seqs[frame.index++]);
539
- if (!message)
540
- continue;
541
- const nested = compactCheckpointId(message);
542
- if (nested)
543
- enter(nested);
544
- else
545
- result.push(structuredClone(message));
638
+ // Same projection DSH applies for routes without toolUpdate (Decision 6).
639
+ return projectToolUpdates(result, undefined, undefined, undefined).messages;
640
+ }
641
+ finally {
642
+ lease[Symbol.dispose]();
546
643
  }
547
- return result;
548
644
  }
549
645
  /**
550
646
  * @param {Session} session
551
647
  * @param {string} compactionId
552
- * @returns {Message[]}
648
+ * @returns {Promise<Message[]>}
553
649
  */
554
- export function shadowedMessagesForCheckpoint(session, compactionId) {
555
- return expandedCheckpointMessages(session, compactionId);
650
+ export async function shadowedMessagesForCheckpoint(ctx, session, compactionId) {
651
+ return expandedCheckpointMessages(ctx, session, compactionId);
556
652
  }
557
653
  /**
558
654
  * @param {readonly Message[]} messages
@@ -579,11 +675,7 @@ function groupMessages(messages) {
579
675
  const next = messages[cursor];
580
676
  if (!next)
581
677
  break;
582
- const results = next.role === "user"
583
- ? next.content
584
- .filter((block) => block.type === "tool-result")
585
- .map((block) => String(block.toolCallId))
586
- : [];
678
+ const results = next.role === "tool" ? [String(next.toolCallId)] : [];
587
679
  if (results.length === 0)
588
680
  break;
589
681
  group.push(next);
@@ -601,9 +693,9 @@ function groupMessages(messages) {
601
693
  * @param {Session} session
602
694
  * @param {string} compactionId
603
695
  * @param {{ maxChars?: number }} [options]
604
- * @returns {Message[]}
696
+ * @returns {Promise<Message[]>}
605
697
  */
606
- export function portableMessagesForCheckpoint(session, compactionId, options = {}) {
698
+ export async function portableMessagesForCheckpoint(ctx, session, compactionId, options = {}) {
607
699
  const configuredMaxChars = options.maxChars;
608
700
  const maxChars = typeof configuredMaxChars === "number" &&
609
701
  Number.isSafeInteger(configuredMaxChars) &&
@@ -611,7 +703,7 @@ export function portableMessagesForCheckpoint(session, compactionId, options = {
611
703
  ? configuredMaxChars
612
704
  : 80_000;
613
705
  const maxTokens = portableTokenCeiling(maxChars) ?? 1;
614
- const expanded = shadowedMessagesForCheckpoint(session, compactionId);
706
+ const expanded = await shadowedMessagesForCheckpoint(ctx, session, compactionId);
615
707
  if (expanded.length === 0)
616
708
  return [];
617
709
  const groups = groupMessages(expanded);