dsh-lcx-codex 0.4.2 → 0.4.3-pre.13
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 +75 -224
- package/THIRD_PARTY_NOTICES.md +64 -0
- package/cordis.patch.yml +3 -20
- package/lib/auxiliary-usage.js +63 -0
- package/lib/client.js +1398 -167
- package/lib/compact-v2.js +218 -199
- package/lib/dsh-compat.js +294 -100
- package/lib/dsh-responses.js +512 -277
- package/lib/grok-native-search.js +391 -0
- package/lib/index.js +1066 -758
- package/lib/invocation-policy-scope.js +261 -0
- package/lib/json-store.js +57 -31
- package/lib/native-checkpoint.js +520 -194
- package/lib/pi-responses-runtime.js +1571 -0
- package/lib/responses-request.js +109 -121
- package/lib/responses-stream.js +1280 -447
- package/lib/route.js +425 -369
- package/lib/search-accounting.js +86 -0
- package/lib/search-usage.js +86 -0
- package/lib/service-mutex.js +73 -64
- package/lib/token-budget.js +176 -108
- package/lib/transport.js +308 -68
- package/lib/types/client/index.d.ts +18 -0
- package/lib/types/client/search-media.d.ts +16 -0
- package/lib/types/index.d.ts +83 -0
- package/lib/web-run-output.js +189 -18
- package/lib/web-search-alpha.js +1067 -163
- package/lib/web-search-capability.js +80 -65
- package/lib/web-search-hosted.js +321 -33
- package/lib/web-search-ref-store.js +145 -60
- package/package.json +112 -32
- package/ARCHITECTURE.md +0 -117
- package/CHANGELOG.md +0 -224
- package/README_EN.md +0 -277
- package/assets/dsh-lcx-codex-banner.jpg +0 -0
- package/lib/legacy-v3.js +0 -20
- package/lib/responses-replay.js +0 -68
- package/scripts/probe-alpha.mjs +0 -43
- package/scripts/validate-dsh-schema.mjs +0 -31
package/lib/native-checkpoint.js
CHANGED
|
@@ -1,33 +1,9 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
/** @typedef {Record<string, unknown>} UnknownRecord */
|
|
8
|
-
/** @typedef {{ type: 'compaction', encrypted_content: string }} CompactionItem */
|
|
9
|
-
/** @typedef {{ role: 'developer' | 'user' | 'system', content?: string | unknown[] }} RetainedClientItem */
|
|
10
|
-
/** @typedef {{ type: string } | RetainedClientItem} NativeOutputItem */
|
|
11
|
-
/** @typedef {{ provider: string, model: string, baseURL: string, sessionId: string }} RouteIdentity */
|
|
12
|
-
/** @typedef {{ type?: string, text?: string, id?: unknown, name?: unknown, toolCallId?: unknown, content?: DshContentBlock[] }} DshContentBlock */
|
|
13
|
-
/** @typedef {{ kind?: string, plugin?: string, compactionId?: string }} DshMessageSource */
|
|
14
|
-
/** @typedef {{ role?: string, content?: DshContentBlock[], source?: DshMessageSource }} DshMessage */
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {DshMessage & {
|
|
17
|
-
* compactionId?: string,
|
|
18
|
-
* rawOutput?: unknown[],
|
|
19
|
-
* shadowedSeqs?: number[],
|
|
20
|
-
* message?: DshMessage
|
|
21
|
-
* }} SessionEventData
|
|
22
|
-
*/
|
|
23
|
-
/** @typedef {{ type: string, seq?: number, data?: SessionEventData }} SessionEvent */
|
|
24
|
-
/** @typedef {{ type: 'compaction/summary', seq?: number, data: SessionEventData & { compactionId: string, rawOutput?: unknown[], shadowedSeqs?: number[] } }} CompactionSummaryEvent */
|
|
25
|
-
/** @typedef {{ id?: string, events?: SessionEvent[], deriveEventMessage?: (event: SessionEvent) => DshMessage | null | undefined }} NativeSession */
|
|
26
|
-
/** @typedef {{ tokenBudget?: number, assistantTokenReserve?: number, assistantPerMessageTokenCap?: number }} RetentionOptions */
|
|
27
|
-
/** @typedef {{ item: unknown, index: number }} IndexedItem */
|
|
28
|
-
/** @typedef {{ index: number, item: unknown, tokens: number }} SelectedItem */
|
|
29
|
-
/** @typedef {{ selected: SelectedItem[], used: number }} Selection */
|
|
30
|
-
/** @typedef {{ items: unknown[], clientCount: number, assistantCount: number, estimatedTokens: number, clientEstimatedTokens: number, assistantEstimatedTokens: number }} RetentionPlan */
|
|
2
|
+
import { isCompactCheckpointSource } from "@deepseek-ai/dsh-compaction";
|
|
3
|
+
import { SessionSeq, } from "@deepseek-ai/dsh-session";
|
|
4
|
+
import { baseURLFingerprint, routeCompatible } from "./route.js";
|
|
5
|
+
import { persistNativeImageReferences, responseInputItems } from "./dsh-responses.js";
|
|
6
|
+
import { estimateBudgetItem, modelVisibleBudgetView, portableBudgetError, portableTokenCeiling, } from "./token-budget.js";
|
|
31
7
|
/**
|
|
32
8
|
* @typedef {object} NativeCheckpointBase
|
|
33
9
|
* @property {string} compactionId
|
|
@@ -44,14 +20,13 @@ import { estimateBudgetItem, portableBudgetError, portableTokenCeiling } from '.
|
|
|
44
20
|
* @property {number} [createdAt]
|
|
45
21
|
*/
|
|
46
22
|
/** @typedef {NativeCheckpointBase & { type: 'lcx-native-compaction-v5', version: 5, retentionPolicy?: 'conversation-fidelity-v1' }} NativeCheckpointV5 */
|
|
47
|
-
/** @typedef {
|
|
48
|
-
/** @typedef {NativeCheckpointV5 | NativeCheckpointV4} NativeCheckpointBlock */
|
|
23
|
+
/** @typedef {NativeCheckpointV5} NativeCheckpointBlock */
|
|
49
24
|
/**
|
|
50
25
|
* Minimal validated candidate shape. DSH rawOutput is unknown until the existing
|
|
51
26
|
* version, field, count, and compaction validation below has completed.
|
|
52
27
|
* @typedef {object} NativeCheckpointCandidate
|
|
53
|
-
* @property {typeof NATIVE_BLOCK_TYPE
|
|
54
|
-
* @property {typeof NATIVE_BLOCK_VERSION
|
|
28
|
+
* @property {typeof NATIVE_BLOCK_TYPE} type
|
|
29
|
+
* @property {typeof NATIVE_BLOCK_VERSION} version
|
|
55
30
|
* @property {unknown} [compactionId]
|
|
56
31
|
* @property {unknown} [nativeOutput]
|
|
57
32
|
* @property {unknown} [provider]
|
|
@@ -63,261 +38,612 @@ import { estimateBudgetItem, portableBudgetError, portableTokenCeiling } from '.
|
|
|
63
38
|
* @property {unknown} [retainedAssistantCount]
|
|
64
39
|
*/
|
|
65
40
|
/** @typedef {{ compaction: CompactionItem }} NativeCompactionResult */
|
|
66
|
-
/** @typedef {{ session:
|
|
41
|
+
/** @typedef {{ session: Session, route: RouteIdentity, result: NativeCompactionResult, input?: unknown[], ephemeralPreludeItemCount?: number, imageMap?: unknown, retentionOptions?: RetentionOptions }} CreateCheckpointOptions */
|
|
67
42
|
/** @typedef {Error & { code?: string }} LcxError */
|
|
68
|
-
|
|
69
|
-
export const
|
|
70
|
-
export const
|
|
71
|
-
export const
|
|
72
|
-
export const
|
|
73
|
-
const LEGACY_V3_PATTERN = /\[dsh-lcx-codex-v3-checkpoint:([0-9a-f-]{36})\]/iu
|
|
74
|
-
|
|
75
|
-
export const RETAINED_MESSAGE_TOKEN_BUDGET = 64_000
|
|
76
|
-
export const ASSISTANT_RETENTION_TOKEN_RESERVE = 24_000
|
|
77
|
-
export const ASSISTANT_RETENTION_PER_MESSAGE_TOKEN_CAP = 3_000
|
|
78
|
-
|
|
43
|
+
export const NATIVE_BLOCK_TYPE = "lcx-native-compaction-v5";
|
|
44
|
+
export const NATIVE_BLOCK_VERSION = 5;
|
|
45
|
+
export const RETAINED_MESSAGE_TOKEN_BUDGET = 64_000;
|
|
46
|
+
export const ASSISTANT_RETENTION_TOKEN_RESERVE = 24_000;
|
|
47
|
+
export const ASSISTANT_RETENTION_PER_MESSAGE_TOKEN_CAP = 3_000;
|
|
79
48
|
/**
|
|
80
49
|
* @param {unknown} value
|
|
81
50
|
* @returns {value is UnknownRecord}
|
|
82
51
|
*/
|
|
83
|
-
function isObject(value) {
|
|
84
|
-
|
|
85
|
-
|
|
52
|
+
function isObject(value) {
|
|
53
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
54
|
+
}
|
|
55
|
+
/** @param {Message | null | undefined} message */
|
|
56
|
+
function textOf(message) {
|
|
57
|
+
return (message?.content ?? [])
|
|
58
|
+
.filter((block) => block.type === "text")
|
|
59
|
+
.map((block) => block.text)
|
|
60
|
+
.join("");
|
|
61
|
+
}
|
|
86
62
|
/** @param {unknown} item */
|
|
87
|
-
function estimatedItemTokens(item) {
|
|
63
|
+
function estimatedItemTokens(item) {
|
|
64
|
+
return estimateBudgetItem(item);
|
|
65
|
+
}
|
|
88
66
|
/**
|
|
89
67
|
* @param {unknown} item
|
|
90
68
|
* @returns {item is UnknownRecord}
|
|
91
69
|
*/
|
|
92
|
-
function isRetainedClientItem(item) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
function assistantTextParts(item) {
|
|
70
|
+
function isRetainedClientItem(item) {
|
|
71
|
+
if (!isObject(item) || (item.type !== undefined && item.type !== "message"))
|
|
72
|
+
return false;
|
|
73
|
+
return (item.role === "user" || item.role === "developer" || item.role === "system");
|
|
74
|
+
}
|
|
75
|
+
function assistantTextParts(item) {
|
|
76
|
+
if (!isObject(item) ||
|
|
77
|
+
item.type !== "message" ||
|
|
78
|
+
item.role !== "assistant" ||
|
|
79
|
+
!Array.isArray(item.content))
|
|
80
|
+
return [];
|
|
81
|
+
return item.content.filter((part) => isObject(part) &&
|
|
82
|
+
part.type === "output_text" &&
|
|
83
|
+
typeof part.text === "string" &&
|
|
84
|
+
part.text.length > 0);
|
|
85
|
+
}
|
|
98
86
|
/** @param {unknown} item */
|
|
99
|
-
function isRetainedAssistantItem(item) {
|
|
87
|
+
function isRetainedAssistantItem(item) {
|
|
88
|
+
return assistantTextParts(item).length > 0;
|
|
89
|
+
}
|
|
100
90
|
/** @param {unknown} item */
|
|
101
|
-
function assistantText(item) {
|
|
91
|
+
function assistantText(item) {
|
|
92
|
+
return assistantTextParts(item)
|
|
93
|
+
.map((part) => part.text)
|
|
94
|
+
.join("");
|
|
95
|
+
}
|
|
102
96
|
/** @param {unknown} item */
|
|
103
|
-
function retainedAssistantPhase(item) {
|
|
104
|
-
|
|
97
|
+
function retainedAssistantPhase(item) {
|
|
98
|
+
const phase = isObject(item) ? item.phase : undefined;
|
|
99
|
+
return phase === "commentary" || phase === "final_answer" ? phase : undefined;
|
|
100
|
+
}
|
|
101
|
+
/** @param {unknown} value */
|
|
102
|
+
function containsBudgetImage(value) {
|
|
103
|
+
if (Array.isArray(value))
|
|
104
|
+
return value.some(containsBudgetImage);
|
|
105
|
+
if (!isObject(value))
|
|
106
|
+
return false;
|
|
107
|
+
return value.image === true || Object.values(value).some(containsBudgetImage);
|
|
108
|
+
}
|
|
109
|
+
/** @param {unknown} item */
|
|
110
|
+
function isImageBudgetItem(item) {
|
|
111
|
+
return containsBudgetImage(modelVisibleBudgetView(item));
|
|
112
|
+
}
|
|
105
113
|
/**
|
|
106
114
|
* @param {unknown} item
|
|
107
115
|
* @param {number} [maxTokens]
|
|
108
116
|
* @returns {UnknownRecord | undefined}
|
|
109
117
|
*/
|
|
110
118
|
function truncateVisibleAssistantItem(item, maxTokens = ASSISTANT_RETENTION_PER_MESSAGE_TOKEN_CAP) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
|
|
119
|
+
const text = assistantText(item);
|
|
120
|
+
if (!text)
|
|
121
|
+
return undefined;
|
|
122
|
+
const phase = retainedAssistantPhase(item);
|
|
123
|
+
const providerId = isObject(item) && typeof item.id === "string" && item.id
|
|
124
|
+
? item.id
|
|
125
|
+
: undefined;
|
|
126
|
+
/** @param {string} retained @param {boolean} preserveProviderId */
|
|
127
|
+
const candidate = (retained, preserveProviderId) => ({
|
|
128
|
+
type: "message",
|
|
129
|
+
role: "assistant",
|
|
130
|
+
content: [{ type: "output_text", text: retained }],
|
|
131
|
+
...(phase ? { phase } : {}),
|
|
132
|
+
...(preserveProviderId && providerId ? { id: providerId } : {}),
|
|
133
|
+
});
|
|
134
|
+
const full = candidate(text, true);
|
|
135
|
+
if ((estimatedItemTokens(full) ?? Infinity) <= maxTokens)
|
|
136
|
+
return full;
|
|
137
|
+
const marker = "\n…[LCX retained answer truncated]…\n";
|
|
138
|
+
/** @param {number} count */
|
|
139
|
+
const shortened = (count) => {
|
|
140
|
+
const available = Math.max(0, count - marker.length);
|
|
141
|
+
const head = Math.floor(available * 0.72);
|
|
142
|
+
const tail = available - head;
|
|
143
|
+
return candidate(`${text.slice(0, head)}${marker}${text.slice(Math.max(head, text.length - tail))}`, false);
|
|
144
|
+
};
|
|
145
|
+
let low = 0;
|
|
146
|
+
let high = text.length;
|
|
147
|
+
let best;
|
|
148
|
+
while (low <= high) {
|
|
149
|
+
const count = Math.floor((low + high) / 2);
|
|
150
|
+
const next = shortened(count);
|
|
151
|
+
if ((estimatedItemTokens(next) ?? Infinity) <= maxTokens) {
|
|
152
|
+
best = next;
|
|
153
|
+
low = count + 1;
|
|
154
|
+
}
|
|
155
|
+
else
|
|
156
|
+
high = count - 1;
|
|
157
|
+
}
|
|
158
|
+
return best;
|
|
159
|
+
}
|
|
137
160
|
/**
|
|
138
161
|
* @param {IndexedItem[]} candidates
|
|
139
162
|
* @param {number} budget
|
|
140
163
|
* @param {(value: IndexedItem) => unknown} [transform]
|
|
164
|
+
* @param {boolean} [stopOnImageOverflow]
|
|
141
165
|
* @returns {Selection}
|
|
142
166
|
*/
|
|
143
|
-
function selectNewest(candidates, budget, transform = (value) => structuredClone(value.item)) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
167
|
+
function selectNewest(candidates, budget, transform = (value) => structuredClone(value.item), stopOnImageOverflow = false) {
|
|
168
|
+
/** @type {SelectedItem[]} */
|
|
169
|
+
const selected = [];
|
|
170
|
+
let used = 0;
|
|
171
|
+
for (let index = candidates.length - 1; index >= 0; index -= 1) {
|
|
172
|
+
const candidate = candidates[index];
|
|
173
|
+
const item = transform(candidate);
|
|
174
|
+
if (!item)
|
|
175
|
+
continue;
|
|
176
|
+
const tokens = estimatedItemTokens(item);
|
|
177
|
+
if (tokens === undefined || tokens > budget || used + tokens > budget) {
|
|
178
|
+
if (stopOnImageOverflow &&
|
|
179
|
+
tokens !== undefined &&
|
|
180
|
+
isImageBudgetItem(item))
|
|
181
|
+
break;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
selected.push({ index: candidate.index, item, tokens });
|
|
185
|
+
used += tokens;
|
|
186
|
+
}
|
|
187
|
+
return { selected, used };
|
|
148
188
|
}
|
|
149
|
-
|
|
150
189
|
/**
|
|
151
190
|
* @param {unknown[] | null | undefined} input
|
|
152
191
|
* @param {RetentionOptions} [options]
|
|
153
192
|
* @returns {unknown[]}
|
|
154
193
|
*/
|
|
155
194
|
export function retainedCompactionInput(input, options = {}) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
195
|
+
const configuredBudget = options.tokenBudget;
|
|
196
|
+
const budget = typeof configuredBudget === "number" &&
|
|
197
|
+
Number.isSafeInteger(configuredBudget) &&
|
|
198
|
+
configuredBudget > 0
|
|
199
|
+
? configuredBudget
|
|
200
|
+
: RETAINED_MESSAGE_TOKEN_BUDGET;
|
|
201
|
+
const candidates = (input ?? [])
|
|
202
|
+
.map((item, index) => ({ item, index }))
|
|
203
|
+
.filter(({ item }) => isRetainedClientItem(item));
|
|
204
|
+
return selectNewest(candidates, budget)
|
|
205
|
+
.selected.sort((a, b) => a.index - b.index)
|
|
206
|
+
.map(({ item }) => item);
|
|
159
207
|
}
|
|
160
|
-
|
|
161
208
|
/**
|
|
162
209
|
* @param {unknown[] | null | undefined} input
|
|
163
210
|
* @param {RetentionOptions} [options]
|
|
164
211
|
* @returns {RetentionPlan}
|
|
165
212
|
*/
|
|
166
213
|
export function retainedConversationPlan(input, options = {}) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
214
|
+
const configuredBudget = options.tokenBudget;
|
|
215
|
+
const totalBudget = typeof configuredBudget === "number" &&
|
|
216
|
+
Number.isSafeInteger(configuredBudget) &&
|
|
217
|
+
configuredBudget > 0
|
|
218
|
+
? configuredBudget
|
|
219
|
+
: RETAINED_MESSAGE_TOKEN_BUDGET;
|
|
220
|
+
const configuredReserve = options.assistantTokenReserve;
|
|
221
|
+
const assistantReserve = Math.min(totalBudget, typeof configuredReserve === "number" &&
|
|
222
|
+
Number.isSafeInteger(configuredReserve) &&
|
|
223
|
+
configuredReserve >= 0
|
|
224
|
+
? configuredReserve
|
|
225
|
+
: ASSISTANT_RETENTION_TOKEN_RESERVE);
|
|
226
|
+
const configuredPerMessageCap = options.assistantPerMessageTokenCap;
|
|
227
|
+
const perMessageCap = typeof configuredPerMessageCap === "number" &&
|
|
228
|
+
Number.isSafeInteger(configuredPerMessageCap) &&
|
|
229
|
+
configuredPerMessageCap > 0
|
|
230
|
+
? configuredPerMessageCap
|
|
231
|
+
: ASSISTANT_RETENTION_PER_MESSAGE_TOKEN_CAP;
|
|
232
|
+
const indexed = (input ?? []).map((item, index) => ({ item, index }));
|
|
233
|
+
const assistant = selectNewest(indexed.filter(({ item }) => isRetainedAssistantItem(item)), assistantReserve, ({ item }) => truncateVisibleAssistantItem(item, perMessageCap));
|
|
234
|
+
const remaining = Math.max(0, totalBudget - assistant.used);
|
|
235
|
+
const client = selectNewest(indexed.filter(({ item }) => isRetainedClientItem(item)), remaining, undefined, true);
|
|
236
|
+
const selected = [...client.selected, ...assistant.selected].sort((a, b) => a.index - b.index);
|
|
237
|
+
return {
|
|
238
|
+
items: selected.map(({ item }) => item),
|
|
239
|
+
clientCount: client.selected.length,
|
|
240
|
+
assistantCount: assistant.selected.length,
|
|
241
|
+
estimatedTokens: client.used + assistant.used,
|
|
242
|
+
clientEstimatedTokens: client.used,
|
|
243
|
+
assistantEstimatedTokens: assistant.used,
|
|
244
|
+
};
|
|
176
245
|
}
|
|
177
246
|
/**
|
|
178
247
|
* @param {unknown[] | null | undefined} input
|
|
179
248
|
* @param {RetentionOptions} [options]
|
|
180
249
|
*/
|
|
181
|
-
export function retainedConversationInput(input, options = {}) {
|
|
250
|
+
export function retainedConversationInput(input, options = {}) {
|
|
251
|
+
return retainedConversationPlan(input, options).items;
|
|
252
|
+
}
|
|
182
253
|
/** @param {unknown[] | null | undefined} items */
|
|
183
|
-
export function hasRetainedCompactionInput(items) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
/** @param {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
254
|
+
export function hasRetainedCompactionInput(items) {
|
|
255
|
+
return (items ?? []).some((item) => isRetainedClientItem(item) || isRetainedAssistantItem(item));
|
|
256
|
+
}
|
|
257
|
+
/** @param {Message | null | undefined} message */
|
|
258
|
+
function hasCompactionId(source) {
|
|
259
|
+
return (isCompactCheckpointSource(source) &&
|
|
260
|
+
"compactionId" in source &&
|
|
261
|
+
typeof source.compactionId === "string" &&
|
|
262
|
+
source.compactionId.length > 0);
|
|
263
|
+
}
|
|
264
|
+
export function compactCheckpointId(message) {
|
|
265
|
+
const source = message?.source;
|
|
266
|
+
return source && hasCompactionId(source) ? source.compactionId : undefined;
|
|
267
|
+
}
|
|
268
|
+
/** Reject reserved checkpoint markers without interpreting unsupported formats or opening sidecars. */
|
|
269
|
+
export function assertSupportedCheckpointMessage(message) {
|
|
270
|
+
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))
|
|
273
|
+
throw Object.assign(new Error("Unsupported LCX checkpoint format; start a new session"), {
|
|
274
|
+
code: "LCX_CHECKPOINT_UNSUPPORTED",
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** @param {Session | null | undefined} session */
|
|
279
|
+
export function activeCompactionId(session) {
|
|
280
|
+
if (!session)
|
|
281
|
+
return undefined;
|
|
282
|
+
/** @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);
|
|
293
|
+
}
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
191
296
|
/**
|
|
192
297
|
* @param {CreateCheckpointOptions} options
|
|
193
298
|
* @returns {NativeCheckpointV5}
|
|
194
299
|
*/
|
|
195
|
-
export function createNativeCheckpointBlock({ session, route, result, input = [], ephemeralPreludeItemCount = 0, imageMap, retentionOptions = {} }) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
300
|
+
export function createNativeCheckpointBlock({ session, route, result, input = [], ephemeralPreludeItemCount = 0, imageMap, retentionOptions = {}, }) {
|
|
301
|
+
const compactionId = activeCompactionId(session);
|
|
302
|
+
if (!compactionId) {
|
|
303
|
+
const error = new Error("Native compaction could not correlate the active DSH compaction transaction");
|
|
304
|
+
error.code = "LCX_COMPACTION_ID_UNAVAILABLE";
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
if (!Number.isSafeInteger(ephemeralPreludeItemCount) ||
|
|
308
|
+
ephemeralPreludeItemCount < 0 ||
|
|
309
|
+
ephemeralPreludeItemCount > input.length) {
|
|
310
|
+
throw Object.assign(new Error("Native compaction prelude provenance is invalid"), { code: "LCX_COMPACTION_PRELUDE_PROVENANCE_INVALID" });
|
|
311
|
+
}
|
|
312
|
+
const retention = retainedConversationPlan(input.slice(ephemeralPreludeItemCount), retentionOptions);
|
|
313
|
+
/** @type {NativeOutputItem[]} */
|
|
314
|
+
const nativeOutput = persistNativeImageReferences([...retention.items, structuredClone(result.compaction)], imageMap ?? new Map());
|
|
315
|
+
return {
|
|
316
|
+
type: NATIVE_BLOCK_TYPE,
|
|
317
|
+
version: NATIVE_BLOCK_VERSION,
|
|
318
|
+
retentionPolicy: "conversation-fidelity-v1",
|
|
319
|
+
compactionId,
|
|
320
|
+
provider: route.provider,
|
|
321
|
+
model: route.model,
|
|
322
|
+
baseURLFingerprint: baseURLFingerprint(route.baseURL),
|
|
323
|
+
sourceSessionId: route.sessionId,
|
|
324
|
+
nativeOutput,
|
|
325
|
+
nativeCompaction: structuredClone(result.compaction),
|
|
326
|
+
retainedInputCount: retention.items.length,
|
|
327
|
+
retainedClientCount: retention.clientCount,
|
|
328
|
+
retainedAssistantCount: retention.assistantCount,
|
|
329
|
+
retainedEstimatedTokens: retention.estimatedTokens,
|
|
330
|
+
createdAt: Date.now(),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
212
333
|
/**
|
|
213
334
|
* @param {NativeCheckpointBlock} block
|
|
214
335
|
* @param {unknown} usage
|
|
215
336
|
* @returns {UnknownRecord[]}
|
|
216
337
|
*/
|
|
217
338
|
export function nativeCheckpointChunks(block, usage) {
|
|
218
|
-
|
|
219
|
-
|
|
339
|
+
const text = "LCX Native V2 checkpoint saved in the DSH session log.";
|
|
340
|
+
return [
|
|
341
|
+
{ type: "block-start", index: 0, blockType: "text" },
|
|
342
|
+
{ type: "text-delta", index: 0, text },
|
|
343
|
+
{ type: "block-end", index: 0, block: { type: "text", text } },
|
|
344
|
+
{ type: "block-start", index: 1, blockType: block.type },
|
|
345
|
+
{ type: "block-end", index: 1, block: structuredClone(block) },
|
|
346
|
+
...(usage === undefined ? [] : [{ type: "usage", usage }]),
|
|
347
|
+
{ type: "finish", reason: { kind: "stop" } },
|
|
348
|
+
];
|
|
220
349
|
}
|
|
221
|
-
|
|
222
350
|
/**
|
|
223
|
-
* @param {
|
|
351
|
+
* @param {Session | null | undefined} session
|
|
224
352
|
* @param {string | null | undefined} compactionId
|
|
225
353
|
* @returns {CompactionSummaryEvent | undefined}
|
|
226
354
|
*/
|
|
227
|
-
export function compactionSummaryEvent(session, compactionId) {
|
|
355
|
+
export function compactionSummaryEvent(session, compactionId) {
|
|
356
|
+
if (!session || !compactionId)
|
|
357
|
+
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;
|
|
364
|
+
}
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
228
367
|
/**
|
|
229
368
|
* rawOutput elements remain untrusted until stateFromSummaryEvent validates them.
|
|
230
369
|
* @param {CompactionSummaryEvent | null | undefined} event
|
|
231
370
|
* @returns {NativeCheckpointCandidate | undefined}
|
|
232
371
|
*/
|
|
233
|
-
function
|
|
372
|
+
function isNativeCheckpointCandidate(value) {
|
|
373
|
+
if (!isObject(value))
|
|
374
|
+
return false;
|
|
375
|
+
return (value.type === NATIVE_BLOCK_TYPE && value.version === NATIVE_BLOCK_VERSION);
|
|
376
|
+
}
|
|
377
|
+
function nativeBlockFromSummary(event) {
|
|
378
|
+
const raw = event?.data.rawOutput;
|
|
379
|
+
if (!raw)
|
|
380
|
+
return undefined;
|
|
381
|
+
for (const value of raw)
|
|
382
|
+
if (isNativeCheckpointCandidate(value))
|
|
383
|
+
return value;
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
234
386
|
/** @param {unknown} value */
|
|
235
|
-
function validateCount(value) {
|
|
387
|
+
function validateCount(value) {
|
|
388
|
+
return (value === undefined ||
|
|
389
|
+
(typeof value === "number" && Number.isSafeInteger(value) && value >= 0));
|
|
390
|
+
}
|
|
236
391
|
/**
|
|
237
392
|
* @param {CompactionSummaryEvent | null | undefined} event
|
|
238
393
|
* @returns {NativeCheckpointBlock | undefined}
|
|
239
394
|
*/
|
|
240
395
|
export function stateFromSummaryEvent(event) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
396
|
+
if (!event)
|
|
397
|
+
return undefined;
|
|
398
|
+
const block = nativeBlockFromSummary(event);
|
|
399
|
+
if (!block)
|
|
400
|
+
return undefined;
|
|
401
|
+
if (block.compactionId !== event.data.compactionId ||
|
|
402
|
+
!Array.isArray(block.nativeOutput) ||
|
|
403
|
+
block.nativeOutput.length === 0)
|
|
404
|
+
return undefined;
|
|
405
|
+
if (typeof block.provider !== "string" ||
|
|
406
|
+
typeof block.model !== "string" ||
|
|
407
|
+
typeof block.baseURLFingerprint !== "string" ||
|
|
408
|
+
typeof block.sourceSessionId !== "string")
|
|
409
|
+
return undefined;
|
|
410
|
+
if (!validateCount(block.retainedInputCount) ||
|
|
411
|
+
!validateCount(block.retainedClientCount) ||
|
|
412
|
+
!validateCount(block.retainedAssistantCount))
|
|
413
|
+
return undefined;
|
|
414
|
+
try {
|
|
415
|
+
responseInputItems(block.nativeOutput);
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
const compactions = block.nativeOutput.filter((item) => isObject(item) &&
|
|
421
|
+
item.type === "compaction" &&
|
|
422
|
+
typeof item.encrypted_content === "string");
|
|
423
|
+
if (compactions.length !== 1 || compactions[0].encrypted_content.trim().length === 0)
|
|
424
|
+
return undefined;
|
|
425
|
+
const compactionIndex = block.nativeOutput.findIndex((item) => isObject(item) && item.type === "compaction");
|
|
426
|
+
const prefix = compactionIndex >= 0 ? block.nativeOutput.slice(0, compactionIndex) : [];
|
|
427
|
+
{
|
|
428
|
+
const clients = prefix.filter(isRetainedClientItem).length;
|
|
429
|
+
const assistants = prefix.filter(isRetainedAssistantItem).length;
|
|
430
|
+
const total = clients + assistants;
|
|
431
|
+
if (block.retainedInputCount !== undefined &&
|
|
432
|
+
block.retainedInputCount !== total)
|
|
433
|
+
return undefined;
|
|
434
|
+
if (block.retainedClientCount !== undefined &&
|
|
435
|
+
block.retainedClientCount !== clients)
|
|
436
|
+
return undefined;
|
|
437
|
+
if (block.retainedAssistantCount !== undefined &&
|
|
438
|
+
block.retainedAssistantCount !== assistants)
|
|
439
|
+
return undefined;
|
|
440
|
+
}
|
|
441
|
+
const nativeCompaction = compactions[0];
|
|
442
|
+
if (!nativeCompaction)
|
|
443
|
+
return undefined;
|
|
444
|
+
const base = {
|
|
445
|
+
compactionId: String(block.compactionId),
|
|
446
|
+
provider: block.provider,
|
|
447
|
+
model: block.model,
|
|
448
|
+
baseURLFingerprint: block.baseURLFingerprint,
|
|
449
|
+
sourceSessionId: block.sourceSessionId,
|
|
450
|
+
nativeOutput: structuredClone(block.nativeOutput),
|
|
451
|
+
nativeCompaction: structuredClone(nativeCompaction),
|
|
452
|
+
...(block.retainedInputCount === undefined
|
|
453
|
+
? {}
|
|
454
|
+
: { retainedInputCount: block.retainedInputCount }),
|
|
455
|
+
...(block.retainedClientCount === undefined
|
|
456
|
+
? {}
|
|
457
|
+
: { retainedClientCount: block.retainedClientCount }),
|
|
458
|
+
...(block.retainedAssistantCount === undefined
|
|
459
|
+
? {}
|
|
460
|
+
: { retainedAssistantCount: block.retainedAssistantCount }),
|
|
461
|
+
};
|
|
462
|
+
return { ...base, type: NATIVE_BLOCK_TYPE, version: NATIVE_BLOCK_VERSION };
|
|
250
463
|
}
|
|
251
464
|
/**
|
|
252
|
-
* @param {
|
|
253
|
-
* @param {
|
|
465
|
+
* @param {Session} session
|
|
466
|
+
* @param {Message} message
|
|
254
467
|
*/
|
|
255
|
-
export function checkpointStateForMessage(session, message) {
|
|
468
|
+
export function checkpointStateForMessage(session, message) {
|
|
469
|
+
assertSupportedCheckpointMessage(message);
|
|
470
|
+
const id = compactCheckpointId(message);
|
|
471
|
+
if (!id)
|
|
472
|
+
return undefined;
|
|
473
|
+
const event = compactionSummaryEvent(session, id);
|
|
474
|
+
const state = stateFromSummaryEvent(event);
|
|
475
|
+
const hasNativeBlock = event?.data.rawOutput?.some(value => isObject(value) && typeof value.type === "string" && value.type.startsWith("lcx-native-compaction-"));
|
|
476
|
+
if (hasNativeBlock && !state)
|
|
477
|
+
throw Object.assign(new Error("Unsupported or invalid LCX checkpoint; start a new session"), { code: "LCX_CHECKPOINT_UNSUPPORTED" });
|
|
478
|
+
return state;
|
|
479
|
+
}
|
|
256
480
|
/**
|
|
257
481
|
* @param {NativeCheckpointBlock} state
|
|
258
482
|
* @param {RouteIdentity} route
|
|
259
483
|
* @param {unknown} ctx
|
|
260
484
|
*/
|
|
261
|
-
export function stateRouteCompatible(state, route, ctx) {
|
|
485
|
+
export function stateRouteCompatible(state, route, ctx) {
|
|
486
|
+
return routeCompatible(state, route, ctx);
|
|
487
|
+
}
|
|
262
488
|
/**
|
|
263
|
-
* @param {
|
|
489
|
+
* @param {Session} session
|
|
264
490
|
* @param {number} seq
|
|
265
|
-
* @returns {
|
|
491
|
+
* @returns {Message | undefined}
|
|
266
492
|
*/
|
|
267
|
-
function eventMessage(session, seq) {
|
|
268
|
-
|
|
269
|
-
|
|
493
|
+
function eventMessage(session, seq) {
|
|
494
|
+
if (!Number.isSafeInteger(seq) || seq < 0)
|
|
495
|
+
return undefined;
|
|
496
|
+
const event = session.eventAt(SessionSeq(seq));
|
|
497
|
+
return event ? (session.deriveEventMessage(event) ?? undefined) : undefined;
|
|
498
|
+
}
|
|
499
|
+
/** @param {Message} message */
|
|
500
|
+
function estimateChars(message) {
|
|
501
|
+
try {
|
|
502
|
+
const encoded = JSON.stringify(message);
|
|
503
|
+
return typeof encoded === "string" ? encoded.length : undefined;
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
270
509
|
/**
|
|
271
|
-
* @param {
|
|
510
|
+
* @param {Session} session
|
|
272
511
|
* @param {string} compactionId
|
|
273
|
-
* @
|
|
274
|
-
* @param {number} depth
|
|
275
|
-
* @returns {DshMessage[]}
|
|
512
|
+
* @returns {Message[]}
|
|
276
513
|
*/
|
|
277
|
-
function expandedCheckpointMessages(session, compactionId
|
|
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;
|
|
537
|
+
}
|
|
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));
|
|
546
|
+
}
|
|
547
|
+
return result;
|
|
548
|
+
}
|
|
278
549
|
/**
|
|
279
|
-
* @param {
|
|
550
|
+
* @param {Session} session
|
|
280
551
|
* @param {string} compactionId
|
|
281
|
-
* @returns {
|
|
552
|
+
* @returns {Message[]}
|
|
282
553
|
*/
|
|
283
|
-
export function shadowedMessagesForCheckpoint(session, compactionId) {
|
|
554
|
+
export function shadowedMessagesForCheckpoint(session, compactionId) {
|
|
555
|
+
return expandedCheckpointMessages(session, compactionId);
|
|
556
|
+
}
|
|
284
557
|
/**
|
|
285
|
-
* @param {
|
|
286
|
-
* @returns {
|
|
558
|
+
* @param {readonly Message[]} messages
|
|
559
|
+
* @returns {Message[][]}
|
|
287
560
|
*/
|
|
288
|
-
function groupMessages(messages) {
|
|
561
|
+
function groupMessages(messages) {
|
|
562
|
+
/** @type {Message[][]} */
|
|
563
|
+
const groups = [];
|
|
564
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
565
|
+
const message = messages[index];
|
|
566
|
+
const toolCalls = message.role === "assistant"
|
|
567
|
+
? message.content
|
|
568
|
+
.filter((block) => block.type === "tool-call")
|
|
569
|
+
.map((block) => String(block.id))
|
|
570
|
+
: [];
|
|
571
|
+
if (toolCalls.length === 0) {
|
|
572
|
+
groups.push([message]);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
const group = [message];
|
|
576
|
+
const pending = new Set(toolCalls);
|
|
577
|
+
let cursor = index + 1;
|
|
578
|
+
while (cursor < messages.length && pending.size > 0) {
|
|
579
|
+
const next = messages[cursor];
|
|
580
|
+
if (!next)
|
|
581
|
+
break;
|
|
582
|
+
const results = next.role === "user"
|
|
583
|
+
? next.content
|
|
584
|
+
.filter((block) => block.type === "tool-result")
|
|
585
|
+
.map((block) => String(block.toolCallId))
|
|
586
|
+
: [];
|
|
587
|
+
if (results.length === 0)
|
|
588
|
+
break;
|
|
589
|
+
group.push(next);
|
|
590
|
+
for (const id of results)
|
|
591
|
+
pending.delete(id);
|
|
592
|
+
cursor += 1;
|
|
593
|
+
}
|
|
594
|
+
if (pending.size === 0)
|
|
595
|
+
index = cursor - 1;
|
|
596
|
+
groups.push(group);
|
|
597
|
+
}
|
|
598
|
+
return groups;
|
|
599
|
+
}
|
|
289
600
|
/**
|
|
290
|
-
* @param {
|
|
601
|
+
* @param {Session} session
|
|
291
602
|
* @param {string} compactionId
|
|
292
603
|
* @param {{ maxChars?: number }} [options]
|
|
293
|
-
* @returns {
|
|
604
|
+
* @returns {Message[]}
|
|
294
605
|
*/
|
|
295
606
|
export function portableMessagesForCheckpoint(session, compactionId, options = {}) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
607
|
+
const configuredMaxChars = options.maxChars;
|
|
608
|
+
const maxChars = typeof configuredMaxChars === "number" &&
|
|
609
|
+
Number.isSafeInteger(configuredMaxChars) &&
|
|
610
|
+
configuredMaxChars > 0
|
|
611
|
+
? configuredMaxChars
|
|
612
|
+
: 80_000;
|
|
613
|
+
const maxTokens = portableTokenCeiling(maxChars) ?? 1;
|
|
614
|
+
const expanded = shadowedMessagesForCheckpoint(session, compactionId);
|
|
615
|
+
if (expanded.length === 0)
|
|
616
|
+
return [];
|
|
617
|
+
const groups = groupMessages(expanded);
|
|
618
|
+
const kept = [];
|
|
619
|
+
let chars = 0;
|
|
620
|
+
let tokens = 0;
|
|
621
|
+
for (let index = groups.length - 1; index >= 0; index -= 1) {
|
|
622
|
+
const group = groups[index];
|
|
623
|
+
let groupChars = 0;
|
|
624
|
+
let groupTokens = 0;
|
|
625
|
+
let budgetable = true;
|
|
626
|
+
for (const message of group) {
|
|
627
|
+
const messageChars = estimateChars(message);
|
|
628
|
+
const messageTokens = estimatedItemTokens(message);
|
|
629
|
+
if (messageChars === undefined || messageTokens === undefined) {
|
|
630
|
+
budgetable = false;
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
groupChars += messageChars;
|
|
634
|
+
groupTokens += messageTokens;
|
|
635
|
+
}
|
|
636
|
+
const exceeds = !budgetable ||
|
|
637
|
+
chars + groupChars > maxChars ||
|
|
638
|
+
tokens + groupTokens > maxTokens;
|
|
639
|
+
if (exceeds) {
|
|
640
|
+
if (kept.length === 0)
|
|
641
|
+
throw portableBudgetError("Portable checkpoint newest message group exceeds the configured budget");
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
kept.unshift(...group);
|
|
645
|
+
chars += groupChars;
|
|
646
|
+
tokens += groupTokens;
|
|
312
647
|
}
|
|
313
|
-
kept
|
|
314
|
-
}
|
|
315
|
-
return kept
|
|
648
|
+
return kept;
|
|
316
649
|
}
|
|
317
|
-
/**
|
|
318
|
-
* @param {DshMessage[]} messages
|
|
319
|
-
* @param {NativeSession} session
|
|
320
|
-
* @param {{ maxChars?: number }} [options]
|
|
321
|
-
* @returns {DshMessage[]}
|
|
322
|
-
*/
|
|
323
|
-
export function rewriteCheckpointsPortable(messages, session, options = {}) { const rewritten = []; let changed = false; for (const message of messages ?? []) { const id = compactCheckpointId(message); if (!id || !checkpointStateForMessage(session, message)) { rewritten.push(message); continue } const portable = portableMessagesForCheckpoint(session, id, options); if (portable.length === 0) { rewritten.push(message); continue } rewritten.push(...portable); changed = true } return changed ? rewritten : messages }
|