dsh-lcx-codex 0.3.1 → 0.3.3
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/CHANGELOG.md +18 -0
- package/README.md +3 -1
- package/README_EN.md +3 -1
- package/lib/compact-v2.js +4 -2
- package/lib/compact.js +101 -2
- package/lib/index.js +38 -12
- package/package.json +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.3.3 - 2026-08-21
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Native V2 checkpoint 现在由客户端按 64K token 预算保留最近真实 user messages,再追加 exactly-one compaction item;不再错误假设上游 `response.output` 会返回 retained messages。旧的 compaction-only v3 checkpoint 会从 portable history 安全补全,运行时上下文注入不会进入 replacement history。
|
|
12
|
+
|
|
13
|
+
### Documentation
|
|
14
|
+
|
|
15
|
+
- 记录 installed `0.3.2` 的 compact + 三次 replay 缓存实测,并说明 Sub2API Codex OAuth 路径会删除上游不支持的 `prompt_cache_retention`;短缓存过期后的单次冷请求不等于插件改变了稳定前缀。
|
|
16
|
+
- 增加连续 replay 的前缀稳定性回归断言,区分缓存生命周期与 replacement-history 保真问题。
|
|
17
|
+
|
|
18
|
+
## 0.3.2 - 2026-08-21
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Native V2 compact 与同路由 replay 现在和 DSH/Pi 普通 Responses 请求复用相同的 session `prompt_cache_key`、缓存 retention 与会话 header,避免压缩边界额外切换 NewAPI/Sub2API 的缓存和账号粘性域;`cacheRetention: none` 时同时省略这些缓存信号。
|
|
23
|
+
- 移除未被插件直接导入或注入的 `@deepseek-ai/dsh-compaction-basic` peer 声明,避免 DSH/pnpm 安装时出现误导性的宿主依赖警告;Basic fallback 继续通过 DSH `llm/stream` 的 `next()` 链调用宿主实现。
|
|
24
|
+
|
|
7
25
|
## 0.3.1 - 2026-08-21
|
|
8
26
|
|
|
9
27
|
### Fixed
|
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ dsh plugin --profile web add dsh-lcx-codex
|
|
|
78
78
|
也可以下载 GitHub Release 中的 `.tgz` 安装指定版本:
|
|
79
79
|
|
|
80
80
|
```powershell
|
|
81
|
-
dsh plugin --profile web add .\dsh-lcx-codex-0.3.
|
|
81
|
+
dsh plugin --profile web add .\dsh-lcx-codex-0.3.2.tgz
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
安装后启动 DSH:
|
|
@@ -130,6 +130,8 @@ node (Join-Path $dshHome 'profiles\web\node_modules\dsh-lcx-codex\scripts\probe-
|
|
|
130
130
|
- Alpha capability:`$DSH_HOME/storages/lcx-codex/web-alpha-capabilities.json`
|
|
131
131
|
- Alpha refs:`$DSH_HOME/storages/lcx-codex/web-alpha-refs.json`
|
|
132
132
|
- 只支持 Native remote-compaction V2,不调用 `/responses/compact`
|
|
133
|
+
- 同路由 replay 会复用 DSH session 的 `prompt_cache_key` 并保持已有请求前缀稳定;短期缓存过期后仍可能出现单次冷请求,不能用会话累计命中率判断插件是否破坏缓存
|
|
134
|
+
- Sub2API 的 Codex OAuth 转换层会删除上游不支持的 `prompt_cache_retention`,因此经该路径设置 `24h` 不会延长缓存;以实际连续请求的 `cached_tokens` 为准
|
|
133
135
|
- Checkpoint 不保存图片原始字节或 data URL
|
|
134
136
|
- Opaque checkpoint 不跨不兼容 provider、model、base URL、session 或 lineage 回放
|
|
135
137
|
- 不包含图片生成功能
|
package/README_EN.md
CHANGED
|
@@ -78,7 +78,7 @@ dsh plugin --profile web add dsh-lcx-codex
|
|
|
78
78
|
You can also install a specific `.tgz` file from a GitHub Release:
|
|
79
79
|
|
|
80
80
|
```powershell
|
|
81
|
-
dsh plugin --profile web add .\dsh-lcx-codex-0.3.
|
|
81
|
+
dsh plugin --profile web add .\dsh-lcx-codex-0.3.2.tgz
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
Start DSH after installation:
|
|
@@ -130,6 +130,8 @@ The probe does not print the key or full response bodies. Restart DSH, or disabl
|
|
|
130
130
|
- Alpha capabilities: `$DSH_HOME/storages/lcx-codex/web-alpha-capabilities.json`
|
|
131
131
|
- Alpha references: `$DSH_HOME/storages/lcx-codex/web-alpha-refs.json`
|
|
132
132
|
- Only Native remote-compaction V2 is supported; `/responses/compact` is never called.
|
|
133
|
+
- Same-route replay reuses the DSH session `prompt_cache_key` and preserves the existing request prefix. A single cold request can still occur after the short-lived upstream cache expires, so the cumulative session hit rate is not evidence that the plugin changed the prefix.
|
|
134
|
+
- Sub2API strips the unsupported `prompt_cache_retention` field on its Codex OAuth path, so requesting `24h` does not extend cache lifetime on that route. Use the `cached_tokens` reported by consecutive real requests as the authoritative signal.
|
|
133
135
|
- Checkpoints never store raw image bytes or data URLs.
|
|
134
136
|
- Opaque checkpoints are not replayed across an incompatible provider, model, base URL, session, or lineage.
|
|
135
137
|
- Image generation is not included.
|
package/lib/compact-v2.js
CHANGED
|
@@ -156,7 +156,7 @@ export function mergeFeatureHeader(headers) {
|
|
|
156
156
|
return result
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
export function buildNativeCompactionBody({ model, input, instructions, promptCacheKey, tools }) {
|
|
159
|
+
export function buildNativeCompactionBody({ model, input, instructions, promptCacheKey, promptCacheRetention, tools }) {
|
|
160
160
|
if (!Array.isArray(input)) throw protocolError('Native compaction input must be an array', 'LCX_COMPACT_INVALID_INPUT')
|
|
161
161
|
if (input.some((item) => isObject(item) && item.type === 'compaction_trigger')) {
|
|
162
162
|
throw protocolError('Native compaction input already contains compaction_trigger', 'LCX_COMPACT_DUPLICATE_TRIGGER')
|
|
@@ -170,6 +170,7 @@ export function buildNativeCompactionBody({ model, input, instructions, promptCa
|
|
|
170
170
|
...(nativeTools !== undefined ? { tools: nativeTools } : {}),
|
|
171
171
|
...(instructions !== undefined ? { instructions: structuredClone(instructions) } : {}),
|
|
172
172
|
...(promptCacheKey !== undefined && promptCacheKey !== null ? { prompt_cache_key: String(promptCacheKey) } : {}),
|
|
173
|
+
...(promptCacheRetention !== undefined && promptCacheRetention !== null ? { prompt_cache_retention: String(promptCacheRetention) } : {}),
|
|
173
174
|
}
|
|
174
175
|
}
|
|
175
176
|
|
|
@@ -273,6 +274,7 @@ export async function requestNativeCompaction({
|
|
|
273
274
|
input,
|
|
274
275
|
instructions,
|
|
275
276
|
promptCacheKey,
|
|
277
|
+
promptCacheRetention,
|
|
276
278
|
idempotencyKey,
|
|
277
279
|
tools,
|
|
278
280
|
headers,
|
|
@@ -281,7 +283,7 @@ export async function requestNativeCompaction({
|
|
|
281
283
|
maxAttempts,
|
|
282
284
|
maxResponseBytes,
|
|
283
285
|
}) {
|
|
284
|
-
const body = buildNativeCompactionBody({ model, input, instructions, promptCacheKey, tools })
|
|
286
|
+
const body = buildNativeCompactionBody({ model, input, instructions, promptCacheKey, promptCacheRetention, tools })
|
|
285
287
|
const requestHeaders = { ...(headers ?? {}) }
|
|
286
288
|
if (idempotencyKey !== undefined && idempotencyKey !== null && !Object.keys(requestHeaders).some((name) => name.toLowerCase() === 'idempotency-key')) {
|
|
287
289
|
requestHeaders['idempotency-key'] = String(idempotencyKey)
|
package/lib/compact.js
CHANGED
|
@@ -4,8 +4,107 @@ import { offloadDshRequestImages, resolveDshImage, serializeDshResponsesInput }
|
|
|
4
4
|
const UNSUPPORTED_CHECKPOINT_PATTERN = /\[dsh-lcx-codex-checkpoint:[0-9a-f-]{36}\]/iu
|
|
5
5
|
const PORTABLE_CHECKPOINT_PATTERN = /\[dsh-lcx-codex-v3-checkpoint:([0-9a-f-]{36})\]/giu
|
|
6
6
|
const PORTABLE_HISTORY_TOKEN_BUDGET = 20_000
|
|
7
|
+
export const NATIVE_RETAINED_MESSAGE_TOKEN_BUDGET = 64_000
|
|
7
8
|
export const PORTABLE_HISTORY_BYTE_BUDGET = 2 * 1024 * 1024
|
|
8
9
|
const UNSUPPORTED_IMAGE_PLACEHOLDER = '[image omitted because the target model does not support image input]'
|
|
10
|
+
const NATIVE_IMAGE_TOKEN_ESTIMATE = 765
|
|
11
|
+
const CONTEXTUAL_USER_PREFIXES = [
|
|
12
|
+
'<environment_context>',
|
|
13
|
+
'<user_instructions>',
|
|
14
|
+
'<additional_context>',
|
|
15
|
+
'<skills',
|
|
16
|
+
'<token_budget>',
|
|
17
|
+
'<model_switch>',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
function nativeContentPartTokens(part) {
|
|
21
|
+
if (part?.type === 'input_image' || part?.type === 'dsh_image_attachment') return NATIVE_IMAGE_TOKEN_ESTIMATE
|
|
22
|
+
if (typeof part?.text === 'string') return Math.ceil(part.text.length / 4)
|
|
23
|
+
return Math.ceil(JSON.stringify(part ?? null).length / 4)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function nativeMessageTokens(item) {
|
|
27
|
+
return Math.max(1, (item?.content ?? []).reduce((total, part) => total + nativeContentPartTokens(part), 0))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function truncatedNativeText(text, maxTokens) {
|
|
31
|
+
const maxChars = maxTokens * 4
|
|
32
|
+
if (maxChars <= 0) return ''
|
|
33
|
+
if (text.length <= maxChars) return text
|
|
34
|
+
const omittedTokens = Math.max(1, Math.ceil((text.length - maxChars) / 4))
|
|
35
|
+
const marker = `[...${omittedTokens} tokens truncated...]`
|
|
36
|
+
if (maxChars <= marker.length + 2) return text.slice(-maxChars)
|
|
37
|
+
const sideChars = Math.max(1, Math.floor((maxChars - marker.length) / 2))
|
|
38
|
+
return `${text.slice(0, sideChars)}${marker}${text.slice(-sideChars)}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function truncateNativeUserMessage(item, maxTokens) {
|
|
42
|
+
let remaining = maxTokens
|
|
43
|
+
const content = []
|
|
44
|
+
for (const part of item?.content ?? []) {
|
|
45
|
+
if (remaining <= 0) break
|
|
46
|
+
const tokens = Math.max(1, nativeContentPartTokens(part))
|
|
47
|
+
if (tokens <= remaining) {
|
|
48
|
+
content.push(structuredClone(part))
|
|
49
|
+
remaining -= tokens
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
if (typeof part?.text === 'string') {
|
|
53
|
+
const text = truncatedNativeText(part.text, remaining)
|
|
54
|
+
if (text) content.push({ ...structuredClone(part), text })
|
|
55
|
+
}
|
|
56
|
+
remaining = 0
|
|
57
|
+
}
|
|
58
|
+
return content.length > 0 ? { ...structuredClone(item), content } : undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isNativeRetainedUserMessage(item) {
|
|
62
|
+
if (item?.type !== 'message' || item.role !== 'user' || !Array.isArray(item.content)) return false
|
|
63
|
+
return !item.content.some((part) => {
|
|
64
|
+
if (part?.type !== 'input_text' || typeof part.text !== 'string') return false
|
|
65
|
+
const text = part.text.trimStart().toLowerCase()
|
|
66
|
+
return CONTEXTUAL_USER_PREFIXES.some((prefix) => text.startsWith(prefix))
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function buildNativeReplacementHistory(input, compaction, options = {}) {
|
|
71
|
+
if (compaction?.type !== 'compaction' || typeof compaction.encrypted_content !== 'string' || compaction.encrypted_content.length === 0) {
|
|
72
|
+
const error = new Error('LCX Native replacement history requires one non-empty compaction item')
|
|
73
|
+
error.code = 'LCX_COMPACT_INVALID_RESPONSE'
|
|
74
|
+
throw error
|
|
75
|
+
}
|
|
76
|
+
let remaining = options.tokenBudget ?? NATIVE_RETAINED_MESSAGE_TOKEN_BUDGET
|
|
77
|
+
if (!Number.isSafeInteger(remaining) || remaining <= 0) {
|
|
78
|
+
const error = new Error('LCX Native retained message token budget must be a positive integer')
|
|
79
|
+
error.code = 'LCX_COMPACT_INVALID_INPUT'
|
|
80
|
+
throw error
|
|
81
|
+
}
|
|
82
|
+
const retained = []
|
|
83
|
+
const candidates = (input ?? []).filter(isNativeRetainedUserMessage)
|
|
84
|
+
for (let index = candidates.length - 1; index >= 0 && remaining > 0; index -= 1) {
|
|
85
|
+
const item = candidates[index]
|
|
86
|
+
const tokens = nativeMessageTokens(item)
|
|
87
|
+
if (tokens <= remaining) {
|
|
88
|
+
retained.push(structuredClone(item))
|
|
89
|
+
remaining -= tokens
|
|
90
|
+
continue
|
|
91
|
+
}
|
|
92
|
+
const truncated = truncateNativeUserMessage(item, remaining)
|
|
93
|
+
if (truncated) retained.push(truncated)
|
|
94
|
+
remaining = 0
|
|
95
|
+
}
|
|
96
|
+
retained.reverse()
|
|
97
|
+
retained.push(structuredClone(compaction))
|
|
98
|
+
return retained
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function checkpointNativeReplacementHistory(record) {
|
|
102
|
+
const nativeOutput = Array.isArray(record?.nativeOutput) ? record.nativeOutput : []
|
|
103
|
+
if (nativeOutput.some(isNativeRetainedUserMessage)) return structuredClone(nativeOutput)
|
|
104
|
+
const compaction = nativeOutput.find((item) => item?.type === 'compaction') ?? record?.nativeCompaction
|
|
105
|
+
if (!compaction) return structuredClone(nativeOutput)
|
|
106
|
+
return buildNativeReplacementHistory(record?.portableHistory ?? [], compaction)
|
|
107
|
+
}
|
|
9
108
|
|
|
10
109
|
export function portableCheckpointIds(text) {
|
|
11
110
|
return [...String(text ?? '').matchAll(PORTABLE_CHECKPOINT_PATTERN)].map((match) => match[1].toLowerCase())
|
|
@@ -461,7 +560,7 @@ export function buildPortableResponsesInput(messages, store, route) {
|
|
|
461
560
|
throw error
|
|
462
561
|
}
|
|
463
562
|
const prefix = state === 'native-compatible'
|
|
464
|
-
? record
|
|
563
|
+
? checkpointNativeReplacementHistory(record)
|
|
465
564
|
: [...portableSummaryItem(record.portableSummary), ...buildPortableHistory(record.portableHistory)]
|
|
466
565
|
const tail = normalInput((messages ?? []).slice(marker.index + 1), { strict: true })
|
|
467
566
|
return [...structuredClone(prefix), ...tail]
|
|
@@ -513,7 +612,7 @@ export async function buildPortableResponsesInputWithImages(messages, store, rou
|
|
|
513
612
|
}
|
|
514
613
|
}
|
|
515
614
|
const prefix = state === 'native-compatible'
|
|
516
|
-
? await hydrateNativeImageReferences(record
|
|
615
|
+
? await hydrateNativeImageReferences(checkpointNativeReplacementHistory(record), options)
|
|
517
616
|
: [...portableSummaryItem(record.portableSummary), ...portableHistory]
|
|
518
617
|
const tail = state === 'native-compatible'
|
|
519
618
|
? await serializeDshResponsesInput(requestMessages.slice(marker.index + 1), { ...options, imageSupport, route })
|
package/lib/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import z from '@deepseek-ai/schemastery'
|
|
2
2
|
import { attributionHeaders, resolveRetryPolicy } from '@deepseek-ai/dsh-llm'
|
|
3
3
|
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
4
|
+
import { clampOpenAIPromptCacheKey } from '@earendil-works/pi-ai/api/openai-prompt-cache'
|
|
4
5
|
import { randomUUID } from 'node:crypto'
|
|
5
6
|
import { homedir } from 'node:os'
|
|
6
7
|
import { join } from 'node:path'
|
|
7
8
|
import { CheckpointV3Store, CHECKPOINT_V3_VERSION } from './checkpoint-store-v3.js'
|
|
8
9
|
import {
|
|
9
10
|
baseURLFingerprint,
|
|
11
|
+
buildNativeReplacementHistory,
|
|
10
12
|
buildPortableHistory,
|
|
11
13
|
assertCheckpointRoute,
|
|
12
14
|
buildPortableResponsesInput,
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
14
16
|
hasPortableCheckpoint,
|
|
15
17
|
hydrateNativeImageReferences,
|
|
16
18
|
inputImageCount,
|
|
19
|
+
checkpointNativeReplacementHistory,
|
|
17
20
|
latestPortableMarker,
|
|
18
21
|
normalizeCompactionResponse,
|
|
19
22
|
persistNativeImageReferences,
|
|
@@ -135,6 +138,7 @@ function normalizeCompactTransport(value) {
|
|
|
135
138
|
}
|
|
136
139
|
|
|
137
140
|
function normalizeConfig(input = {}) {
|
|
141
|
+
const cacheRetention = ['none', 'short', 'long'].includes(input.cacheRetention) ? input.cacheRetention : 'short'
|
|
138
142
|
return {
|
|
139
143
|
provider: input.provider || 'lcx',
|
|
140
144
|
baseURL: String(input.baseURL || 'https://api.lcxbot.com/v1').replace(/\/+$/u, ''),
|
|
@@ -142,6 +146,8 @@ function normalizeConfig(input = {}) {
|
|
|
142
146
|
model: input.model || 'gpt-5.6-sol',
|
|
143
147
|
compactTransport: normalizeCompactTransport(input.compactTransport),
|
|
144
148
|
headers: input.headers && typeof input.headers === 'object' ? { ...input.headers } : {},
|
|
149
|
+
cacheRetention,
|
|
150
|
+
supportsLongCacheRetention: input.supportsLongCacheRetention !== false,
|
|
145
151
|
checkpointPath: input.checkpointPath || defaultCheckpointPath(),
|
|
146
152
|
portableCheckpointPath: input.portableCheckpointPath || portableCheckpointPath(input.checkpointPath || defaultCheckpointPath()),
|
|
147
153
|
alphaCapabilityPath: input.alphaCapabilityPath || defaultAlphaCapabilityPath(),
|
|
@@ -186,6 +192,8 @@ function resolveResponsesRouteConfig(ctx, options, fallbackConfig) {
|
|
|
186
192
|
baseURL,
|
|
187
193
|
apiKeyEnv,
|
|
188
194
|
headers: profile.headers ?? fallbackConfig.headers,
|
|
195
|
+
cacheRetention: profile.cacheRetention ?? fallbackConfig.cacheRetention,
|
|
196
|
+
supportsLongCacheRetention: profile.compat?.supportsLongCacheRetention ?? fallbackConfig.supportsLongCacheRetention,
|
|
189
197
|
model: options.model,
|
|
190
198
|
compactTransport: profile.compactTransport ?? fallbackConfig.compactTransport,
|
|
191
199
|
timeoutMs: profile.timeoutMs ?? fallbackConfig.timeoutMs,
|
|
@@ -301,7 +309,7 @@ async function authenticatedHeaders(ctx, config, sessionId, clientRequestId) {
|
|
|
301
309
|
...config.headers,
|
|
302
310
|
...attributionHeaders(),
|
|
303
311
|
authorization: `Bearer ${await resolveApiKey(ctx, config)}`,
|
|
304
|
-
'x-client-request-id': clientRequestId ?? randomUUID(),
|
|
312
|
+
...(clientRequestId === null ? {} : { 'x-client-request-id': clientRequestId ?? (sessionId ? String(sessionId) : randomUUID()) }),
|
|
305
313
|
...(sessionId ? { 'session-id': String(sessionId) } : {}),
|
|
306
314
|
}
|
|
307
315
|
}
|
|
@@ -361,19 +369,35 @@ function routeWithSessionAncestry(ctx, route) {
|
|
|
361
369
|
return { ...route, ancestorSessionIds: sessionAncestry(ctx, route.sessionId) }
|
|
362
370
|
}
|
|
363
371
|
|
|
364
|
-
function
|
|
365
|
-
|
|
372
|
+
function promptCacheSessionId(route, config) {
|
|
373
|
+
if (config?.cacheRetention === 'none') return undefined
|
|
374
|
+
return route?.sessionId ? String(route.sessionId) : undefined
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function promptCacheKey(route, config) {
|
|
378
|
+
const sessionId = promptCacheSessionId(route, config)
|
|
379
|
+
return sessionId ? clampOpenAIPromptCacheKey(sessionId) : undefined
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function promptCacheRetention(config) {
|
|
383
|
+
return config?.cacheRetention === 'long' && config.supportsLongCacheRetention !== false ? '24h' : undefined
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function promptCacheHeaders(ctx, config, route) {
|
|
387
|
+
const sessionId = promptCacheSessionId(route, config)
|
|
388
|
+
return authenticatedHeaders(ctx, config, sessionId, sessionId === undefined ? null : undefined)
|
|
366
389
|
}
|
|
367
390
|
|
|
368
391
|
async function requestRemoteCompaction(options, input, config, signal, ctx, route, preflightHeaders) {
|
|
369
392
|
try {
|
|
370
|
-
const headers = preflightHeaders ?? await
|
|
393
|
+
const headers = preflightHeaders ?? await promptCacheHeaders(ctx, config, route)
|
|
371
394
|
return await requestNativeCompaction({
|
|
372
395
|
baseURL: config.baseURL,
|
|
373
396
|
model: options.model,
|
|
374
397
|
input,
|
|
375
398
|
instructions: options.system,
|
|
376
|
-
promptCacheKey: promptCacheKey(route),
|
|
399
|
+
promptCacheKey: promptCacheKey(route, config),
|
|
400
|
+
promptCacheRetention: promptCacheRetention(config),
|
|
377
401
|
idempotencyKey: randomUUID(),
|
|
378
402
|
tools: options.tools,
|
|
379
403
|
headers,
|
|
@@ -731,7 +755,7 @@ function checkpointReplayRecord(messages, store) {
|
|
|
731
755
|
return { marker, record }
|
|
732
756
|
}
|
|
733
757
|
|
|
734
|
-
function nativeReplayBody(options, route, nativeOutput, tailInput) {
|
|
758
|
+
function nativeReplayBody(options, route, config, nativeOutput, tailInput) {
|
|
735
759
|
return {
|
|
736
760
|
model: options.model,
|
|
737
761
|
input: [...structuredClone(nativeOutput), ...structuredClone(tailInput)],
|
|
@@ -739,7 +763,8 @@ function nativeReplayBody(options, route, nativeOutput, tailInput) {
|
|
|
739
763
|
store: false,
|
|
740
764
|
...(options.tools !== undefined ? { tools: responsesTools(options.tools) } : {}),
|
|
741
765
|
...(options.system !== undefined ? { instructions: options.system } : {}),
|
|
742
|
-
...(promptCacheKey(route) ? { prompt_cache_key: promptCacheKey(route) } : {}),
|
|
766
|
+
...(promptCacheKey(route, config) ? { prompt_cache_key: promptCacheKey(route, config) } : {}),
|
|
767
|
+
...(promptCacheRetention(config) ? { prompt_cache_retention: promptCacheRetention(config) } : {}),
|
|
743
768
|
}
|
|
744
769
|
}
|
|
745
770
|
|
|
@@ -751,7 +776,7 @@ async function* nativeCheckpointReplayStream(options, config, portableStore, ctx
|
|
|
751
776
|
? await resolveModelImageSupport(llm, route, options.signal)
|
|
752
777
|
: 'supported'
|
|
753
778
|
const imageOptions = { resolveImage: attachmentImageResolver(ctx), imageSupport, signal: options.signal, maxRequestImageBytes: config.maxRequestImageBytes }
|
|
754
|
-
const nativeOutput = await hydrateNativeImageReferences(record
|
|
779
|
+
const nativeOutput = await hydrateNativeImageReferences(checkpointNativeReplacementHistory(record), imageOptions)
|
|
755
780
|
const tailInput = await buildPortableResponsesInputWithImages(
|
|
756
781
|
(options.messages ?? []).slice(marker.index + 1),
|
|
757
782
|
portableStore,
|
|
@@ -762,8 +787,8 @@ async function* nativeCheckpointReplayStream(options, config, portableStore, ctx
|
|
|
762
787
|
try {
|
|
763
788
|
const response = await fetchSse(
|
|
764
789
|
`${config.baseURL}/responses`,
|
|
765
|
-
nativeReplayBody(options, route, nativeOutput, tailInput),
|
|
766
|
-
mergeFeatureHeader(await
|
|
790
|
+
nativeReplayBody(options, route, config, nativeOutput, tailInput),
|
|
791
|
+
mergeFeatureHeader(await promptCacheHeaders(ctx, config, route)),
|
|
767
792
|
replaySignals.signal,
|
|
768
793
|
config.timeoutMs + 1000,
|
|
769
794
|
{ maxResponseBytes: config.maxResponseBytes },
|
|
@@ -822,7 +847,7 @@ async function prepareRemoteCompaction(options, config, portableStore, ctx, sess
|
|
|
822
847
|
const route = currentRoute(options, config)
|
|
823
848
|
const lease = sessionTracker?.capture(route.sessionId)
|
|
824
849
|
lease?.assert('request-start')
|
|
825
|
-
const headers = await
|
|
850
|
+
const headers = await promptCacheHeaders(ctx, config, route)
|
|
826
851
|
responsesTools(options.tools)
|
|
827
852
|
const llm = ctx?.llm ?? ctx?.get?.('llm')
|
|
828
853
|
const imageSupport = typeof llm?.resolveModelInfo === 'function'
|
|
@@ -864,7 +889,8 @@ function portableMarkerTextWithSummary(id, model, usage, summary) {
|
|
|
864
889
|
async function commitRemoteCompaction(prepared, result, portableStore, config, summary, usage) {
|
|
865
890
|
const { history, route, lease, input, imageReferences } = prepared
|
|
866
891
|
lease?.assert('commit')
|
|
867
|
-
const
|
|
892
|
+
const replacementHistory = buildNativeReplacementHistory(input, result.compaction)
|
|
893
|
+
const persistedNativeOutput = persistNativeImageReferences(replacementHistory, imageReferences)
|
|
868
894
|
const persistedPortableInput = persistNativeImageReferences(input, imageReferences)
|
|
869
895
|
const id = randomUUID()
|
|
870
896
|
const parent = latestPortableMarker(history)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-lcx-codex",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "DSH web search and Responses native V2 compaction for Sub2API-proxied or NewAPI-relayed GPT models",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -62,7 +62,6 @@
|
|
|
62
62
|
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.8",
|
|
63
63
|
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.8",
|
|
64
64
|
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.8",
|
|
65
|
-
"@deepseek-ai/dsh-compaction-basic": "^0.1.0-rc.8",
|
|
66
65
|
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.8",
|
|
67
66
|
"@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
|
|
68
67
|
"@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
|