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.
- package/README.md +95 -60
- package/lib/abort-error.js +47 -0
- package/lib/client.js +597 -505
- package/lib/dsh-compat.js +37 -0
- package/lib/dsh-responses.js +92 -48
- package/lib/index.js +174 -105
- package/lib/invocation-policy-scope.js +4 -8
- package/lib/native-checkpoint.js +172 -80
- package/lib/pi-responses-runtime.js +252 -98
- package/lib/pi086-responses-allowlist.js +131 -0
- package/lib/responses-request.js +2 -2
- package/lib/responses-stream.js +62 -11
- package/lib/route.js +23 -32
- package/lib/search-accounting.js +22 -6
- package/lib/search-usage.js +32 -18
- package/lib/service-mutex.js +4 -8
- package/lib/token-budget.js +15 -21
- package/lib/transport.js +6 -6
- package/lib/types/client/search-media.d.ts +11 -2
- package/lib/types/index.d.ts +71 -43
- package/lib/web-search-alpha-history.js +62 -0
- package/lib/web-search-alpha.js +141 -124
- package/lib/web-search-capability.js +1 -1
- package/lib/web-search-hosted.js +29 -17
- package/lib/web-search-ref-store.js +27 -5
- package/package.json +41 -27
package/lib/native-checkpoint.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { isCompactCheckpointSource } from "@deepseek-ai/dsh-compaction";
|
|
3
|
-
import {
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
284
|
-
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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" &&
|
|
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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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 === "
|
|
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);
|