dsh-lcx-codex 0.3.0
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 +42 -0
- package/LICENSE +21 -0
- package/README.md +120 -0
- package/cordis.patch.yml +18 -0
- package/lib/checkpoint-store-v3.js +459 -0
- package/lib/client.js +248 -0
- package/lib/compact-v2.js +305 -0
- package/lib/compact.js +653 -0
- package/lib/dsh-pi-responses.js +339 -0
- package/lib/index.js +1757 -0
- package/lib/private-file.js +18 -0
- package/lib/session-lease.js +70 -0
- package/lib/transport.js +396 -0
- package/lib/web-run-output.js +157 -0
- package/lib/web-search-alpha.js +451 -0
- package/lib/web-search-capability.js +78 -0
- package/lib/web-search-hosted.js +383 -0
- package/lib/web-search-ref-store.js +70 -0
- package/lib/web-search-store.js +181 -0
- package/package.json +99 -0
- package/scripts/probe-alpha.mjs +91 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { convertResponsesMessages } from '@earendil-works/pi-ai/api/openai-responses-shared'
|
|
2
|
+
import { offloadRequestImages } from '@deepseek-ai/dsh-llm'
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024
|
|
5
|
+
const SUPPORTED_IMAGE_MEDIA_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp', 'image/gif'])
|
|
6
|
+
const RESPONSES_TOOL_CALL_PROVIDERS = new Set(['openai', 'openai-codex', 'opencode'])
|
|
7
|
+
|
|
8
|
+
function compactError(message, code, cause) {
|
|
9
|
+
const error = new Error(message, cause === undefined ? undefined : { cause })
|
|
10
|
+
error.code = code
|
|
11
|
+
return error
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function invalidReplay(message) {
|
|
15
|
+
return compactError(`invalid pi-ai replay state: ${message}`, 'LCX_COMPACT_INVALID_REPLAY_STATE')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function unsupportedContent(type) {
|
|
19
|
+
return compactError(
|
|
20
|
+
`LCX Compact cannot safely serialize DSH message content type: ${String(type)}`,
|
|
21
|
+
'LCX_CHECKPOINT_PORTABLE_UNSUPPORTED_CONTENT',
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseArguments(raw) {
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(raw)
|
|
28
|
+
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) return parsed
|
|
29
|
+
} catch {
|
|
30
|
+
// Match the DSH pi-ai adapter: malformed model arguments replay as an empty object.
|
|
31
|
+
}
|
|
32
|
+
return {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function emptyUsage() {
|
|
36
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function replayBlockType(type) {
|
|
40
|
+
if (type === 'text') return 'text'
|
|
41
|
+
if (type === 'reasoning') return 'reasoning'
|
|
42
|
+
if (type === 'tool-call') return 'tool-call'
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function readDshPiReplayState(value) {
|
|
47
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) throw invalidReplay('expected a replay envelope')
|
|
48
|
+
const response = value.response
|
|
49
|
+
if (response === null || typeof response !== 'object' || Array.isArray(response)) throw invalidReplay('expected a response object')
|
|
50
|
+
if (response.kind !== 'pi-ai') throw invalidReplay('unknown state kind')
|
|
51
|
+
if (response.version !== 2) throw invalidReplay(`unsupported version ${String(response.version)}`)
|
|
52
|
+
for (const key of ['api', 'provider', 'model']) {
|
|
53
|
+
if (typeof response[key] !== 'string' || response[key].length === 0) throw invalidReplay(`${key} must be a non-empty string`)
|
|
54
|
+
}
|
|
55
|
+
if (!['stop', 'length', 'toolUse', 'error', 'aborted'].includes(response.stopReason)) throw invalidReplay('unknown stopReason')
|
|
56
|
+
if (response.responseModel !== undefined && typeof response.responseModel !== 'string') throw invalidReplay('responseModel must be a string')
|
|
57
|
+
if (response.responseId !== undefined && typeof response.responseId !== 'string') throw invalidReplay('responseId must be a string')
|
|
58
|
+
if (!Array.isArray(value.blocks)) throw invalidReplay('blocks must be an array')
|
|
59
|
+
for (const [index, block] of value.blocks.entries()) {
|
|
60
|
+
if (block === null || typeof block !== 'object' || Array.isArray(block)) throw invalidReplay(`block ${index} must be an object`)
|
|
61
|
+
if (!['text', 'reasoning', 'tool-call'].includes(block.type)) throw invalidReplay(`block ${index} has an unknown type`)
|
|
62
|
+
for (const signature of ['textSignature', 'thinkingSignature', 'thoughtSignature']) {
|
|
63
|
+
if (block[signature] !== undefined && typeof block[signature] !== 'string') {
|
|
64
|
+
throw invalidReplay(`block ${index} ${signature} must be a string`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (block.redacted !== undefined && typeof block.redacted !== 'boolean') throw invalidReplay(`block ${index} redacted must be boolean`)
|
|
68
|
+
}
|
|
69
|
+
return { response, blocks: value.blocks }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function foreignAssistant(message) {
|
|
73
|
+
const source = message?.source?.kind === 'model' ? message.source : undefined
|
|
74
|
+
const content = []
|
|
75
|
+
for (const block of message.content ?? []) {
|
|
76
|
+
if (block?.type === 'text') content.push({ type: 'text', text: block.text })
|
|
77
|
+
else if (block?.type === 'reasoning') content.push({ type: 'thinking', thinking: block.text })
|
|
78
|
+
else if (block?.type === 'tool-call') {
|
|
79
|
+
content.push({ type: 'toolCall', id: String(block.id), name: String(block.name), arguments: parseArguments(block.arguments) })
|
|
80
|
+
} else if (block?.type === 'image') {
|
|
81
|
+
throw unsupportedContent('assistant image')
|
|
82
|
+
} else {
|
|
83
|
+
throw unsupportedContent(block?.type)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
role: 'assistant',
|
|
88
|
+
content,
|
|
89
|
+
api: 'dsh-foreign',
|
|
90
|
+
provider: source?.provider ?? 'dsh-foreign',
|
|
91
|
+
model: source?.model ?? 'dsh-foreign',
|
|
92
|
+
usage: emptyUsage(),
|
|
93
|
+
stopReason: content.some((block) => block.type === 'toolCall') ? 'toolUse' : 'stop',
|
|
94
|
+
timestamp: 0,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function replayedAssistant(message, source) {
|
|
99
|
+
const state = readDshPiReplayState(source.replayState)
|
|
100
|
+
if (state.response.provider !== source.provider) throw invalidReplay('provider does not match assistant source')
|
|
101
|
+
if (state.response.model !== source.model) throw invalidReplay('model does not match assistant source')
|
|
102
|
+
if (state.blocks.length !== message.content.length) throw invalidReplay('block count does not match assistant content')
|
|
103
|
+
const content = message.content.map((block, index) => {
|
|
104
|
+
const replay = state.blocks[index]
|
|
105
|
+
if (replayBlockType(block?.type) !== replay?.type) throw invalidReplay(`block ${index} does not match assistant content`)
|
|
106
|
+
if (block.type === 'text') {
|
|
107
|
+
return { type: 'text', text: block.text, ...(replay.textSignature === undefined ? {} : { textSignature: replay.textSignature }) }
|
|
108
|
+
}
|
|
109
|
+
if (block.type === 'reasoning') {
|
|
110
|
+
return {
|
|
111
|
+
type: 'thinking',
|
|
112
|
+
thinking: block.text,
|
|
113
|
+
...(replay.thinkingSignature === undefined ? {} : { thinkingSignature: replay.thinkingSignature }),
|
|
114
|
+
...(replay.redacted === undefined ? {} : { redacted: replay.redacted }),
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
type: 'toolCall',
|
|
119
|
+
id: String(block.id),
|
|
120
|
+
name: String(block.name),
|
|
121
|
+
arguments: parseArguments(block.arguments),
|
|
122
|
+
...(replay.thoughtSignature === undefined ? {} : { thoughtSignature: replay.thoughtSignature }),
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
return {
|
|
126
|
+
role: 'assistant',
|
|
127
|
+
content,
|
|
128
|
+
api: state.response.api,
|
|
129
|
+
provider: state.response.provider,
|
|
130
|
+
model: state.response.model,
|
|
131
|
+
...(state.response.responseModel === undefined ? {} : { responseModel: state.response.responseModel }),
|
|
132
|
+
...(state.response.responseId === undefined ? {} : { responseId: state.response.responseId }),
|
|
133
|
+
usage: emptyUsage(),
|
|
134
|
+
stopReason: state.response.stopReason,
|
|
135
|
+
timestamp: 0,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function toPiAssistant(message, onReplayDegrade) {
|
|
140
|
+
const source = message?.source
|
|
141
|
+
if (source?.kind !== 'model' || source.replayState === undefined) return foreignAssistant(message)
|
|
142
|
+
try {
|
|
143
|
+
return replayedAssistant(message, source)
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (error?.code !== 'LCX_COMPACT_INVALID_REPLAY_STATE') throw error
|
|
146
|
+
onReplayDegrade?.(error.message)
|
|
147
|
+
return foreignAssistant(message)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function hasImageBlocks(blocks) {
|
|
152
|
+
return (blocks ?? []).some((block) => block?.type === 'image' || (block?.type === 'tool-result' && hasImageBlocks(block.content)))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function resolveDshImage(block, options = {}) {
|
|
156
|
+
if (typeof options.resolveImage !== 'function') {
|
|
157
|
+
throw compactError('LCX Compact cannot resolve image attachment without the DSH attachment service', 'LCX_COMPACT_IMAGE_UNAVAILABLE')
|
|
158
|
+
}
|
|
159
|
+
let stored
|
|
160
|
+
try {
|
|
161
|
+
stored = await options.resolveImage(block, options.signal)
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error?.code?.startsWith?.('LCX_COMPACT_IMAGE_')) throw error
|
|
164
|
+
throw compactError('LCX Compact failed to read image attachment', 'LCX_COMPACT_IMAGE_UNAVAILABLE', error)
|
|
165
|
+
}
|
|
166
|
+
const data = stored?.data
|
|
167
|
+
const mediaType = String(stored?.mediaType ?? stored?.ref?.mediaType ?? block?.attachment?.mediaType ?? '').toLowerCase()
|
|
168
|
+
const bytes = data?.byteLength ?? data?.length
|
|
169
|
+
if (!SUPPORTED_IMAGE_MEDIA_TYPES.has(mediaType)) {
|
|
170
|
+
throw compactError(`LCX Compact does not support image media type: ${mediaType || 'missing'}`, 'LCX_COMPACT_IMAGE_UNSUPPORTED')
|
|
171
|
+
}
|
|
172
|
+
if (!Number.isSafeInteger(bytes) || bytes <= 0) {
|
|
173
|
+
throw compactError('LCX Compact received empty or invalid image attachment data', 'LCX_COMPACT_IMAGE_UNAVAILABLE')
|
|
174
|
+
}
|
|
175
|
+
const base64 = Buffer.from(data).toString('base64')
|
|
176
|
+
const maxBytes = options.maxRequestImageBytes ?? DEFAULT_MAX_REQUEST_IMAGE_BYTES
|
|
177
|
+
if (base64.length > maxBytes) {
|
|
178
|
+
throw compactError(`LCX Compact image attachment exceeds the ${maxBytes}-byte base64 payload limit`, 'LCX_COMPACT_IMAGE_TOO_LARGE')
|
|
179
|
+
}
|
|
180
|
+
const imageUrl = `data:${mediaType};base64,${base64}`
|
|
181
|
+
options.onImageResolved?.({ imageUrl, attachment: structuredClone(stored?.ref ?? block.attachment) })
|
|
182
|
+
return { type: 'image', data: base64, mimeType: mediaType }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function piUserContent(blocks, options) {
|
|
186
|
+
const content = []
|
|
187
|
+
for (const block of blocks ?? []) {
|
|
188
|
+
if (block?.type === 'text') {
|
|
189
|
+
if (block.text.length > 0) content.push({ type: 'text', text: block.text })
|
|
190
|
+
} else if (block?.type === 'reasoning') {
|
|
191
|
+
continue
|
|
192
|
+
} else if (block?.type === 'image') {
|
|
193
|
+
if (options.imageSupport === 'unsupported') {
|
|
194
|
+
content.push({ type: 'image', data: '', mimeType: block?.attachment?.mediaType ?? 'image/png' })
|
|
195
|
+
} else {
|
|
196
|
+
content.push(await resolveDshImage(block, options))
|
|
197
|
+
}
|
|
198
|
+
} else if (block?.type === 'tool-result') {
|
|
199
|
+
const nested = await piUserContent(block.content, options)
|
|
200
|
+
if (typeof nested === 'string') {
|
|
201
|
+
if (nested.length > 0) content.push({ type: 'text', text: nested })
|
|
202
|
+
} else {
|
|
203
|
+
content.push(...nested)
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
throw unsupportedContent(block?.type)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (content.every((block) => block.type === 'text')) return content.map((block) => block.text).join('')
|
|
210
|
+
return content
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function dshToPiContext(messages, options) {
|
|
214
|
+
const converted = []
|
|
215
|
+
const toolNames = new Map()
|
|
216
|
+
for (const message of messages ?? []) {
|
|
217
|
+
if (message === null || typeof message !== 'object' || Array.isArray(message) || !Array.isArray(message.content)) {
|
|
218
|
+
throw unsupportedContent('message')
|
|
219
|
+
}
|
|
220
|
+
if (message.role === 'system') {
|
|
221
|
+
if (hasImageBlocks(message.content)) throw unsupportedContent('system image')
|
|
222
|
+
const text = message.content.filter((block) => block?.type === 'text').map((block) => block.text).join('')
|
|
223
|
+
if (message.content.some((block) => block?.type !== 'text' && block?.type !== 'reasoning')) throw unsupportedContent('system block')
|
|
224
|
+
if (text) converted.push({ role: 'user', content: text, timestamp: 0 })
|
|
225
|
+
continue
|
|
226
|
+
}
|
|
227
|
+
if (message.role === 'assistant') {
|
|
228
|
+
const assistant = toPiAssistant(message, options.onReplayDegrade)
|
|
229
|
+
for (const block of assistant.content) {
|
|
230
|
+
if (block.type === 'toolCall') toolNames.set(block.id, block.name)
|
|
231
|
+
}
|
|
232
|
+
converted.push(assistant)
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
if (message.role !== 'user') throw unsupportedContent(`message role ${String(message.role)}`)
|
|
236
|
+
const ordinary = message.content.filter((block) => block?.type !== 'tool-result')
|
|
237
|
+
if (ordinary.some((block) => !['text', 'reasoning', 'image'].includes(block?.type))) throw unsupportedContent(ordinary.find((block) => !['text', 'reasoning', 'image'].includes(block?.type))?.type)
|
|
238
|
+
const content = await piUserContent(ordinary, options)
|
|
239
|
+
const results = message.content.filter((block) => block?.type === 'tool-result')
|
|
240
|
+
if ((typeof content === 'string' ? content.length > 0 : content.length > 0) || results.length === 0) {
|
|
241
|
+
converted.push({ role: 'user', content, timestamp: 0 })
|
|
242
|
+
}
|
|
243
|
+
for (const result of results) {
|
|
244
|
+
const resultContent = await piUserContent(result.content, options)
|
|
245
|
+
converted.push({
|
|
246
|
+
role: 'toolResult',
|
|
247
|
+
toolCallId: String(result.toolCallId),
|
|
248
|
+
toolName: toolNames.get(String(result.toolCallId)) ?? 'unknown',
|
|
249
|
+
content: typeof resultContent === 'string'
|
|
250
|
+
? [{ type: 'text', text: resultContent || '(no output)' }]
|
|
251
|
+
: resultContent,
|
|
252
|
+
isError: result.isError ?? false,
|
|
253
|
+
timestamp: 0,
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return { messages: converted }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export async function resolveModelImageSupport(llm, route, signal) {
|
|
261
|
+
if (typeof llm?.resolveModelInfo !== 'function') return 'unknown'
|
|
262
|
+
try {
|
|
263
|
+
const model = await llm.resolveModelInfo(route.provider, route.model, signal)
|
|
264
|
+
if (!Array.isArray(model?.inputModalities)) return 'unknown'
|
|
265
|
+
return model.inputModalities.includes('image') ? 'supported' : 'unsupported'
|
|
266
|
+
} catch {
|
|
267
|
+
return 'unknown'
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export async function serializeDshResponsesInput(messages, options = {}) {
|
|
272
|
+
const route = options.route ?? {}
|
|
273
|
+
const imageSupport = options.imageSupport ?? 'unknown'
|
|
274
|
+
if (!['supported', 'unsupported', 'unknown'].includes(imageSupport)) {
|
|
275
|
+
throw compactError(`Invalid LCX Compact image capability: ${String(imageSupport)}`, 'LCX_COMPACT_IMAGE_CAPABILITY_UNKNOWN')
|
|
276
|
+
}
|
|
277
|
+
if (imageSupport === 'unknown' && (messages ?? []).some((message) => hasImageBlocks(message?.content))) {
|
|
278
|
+
throw compactError('LCX Compact cannot determine whether the target model accepts images', 'LCX_COMPACT_IMAGE_CAPABILITY_UNKNOWN')
|
|
279
|
+
}
|
|
280
|
+
const context = await dshToPiContext(messages, { ...options, imageSupport })
|
|
281
|
+
const model = {
|
|
282
|
+
id: String(route.model ?? ''),
|
|
283
|
+
name: String(route.model ?? ''),
|
|
284
|
+
api: 'openai-responses',
|
|
285
|
+
provider: String(route.provider ?? ''),
|
|
286
|
+
baseUrl: String(route.baseURL ?? ''),
|
|
287
|
+
input: imageSupport === 'supported' ? ['text', 'image'] : ['text'],
|
|
288
|
+
reasoning: true,
|
|
289
|
+
contextWindow: 1,
|
|
290
|
+
maxTokens: 1,
|
|
291
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
292
|
+
}
|
|
293
|
+
const allowedToolCallProviders = new Set([...RESPONSES_TOOL_CALL_PROVIDERS, model.provider])
|
|
294
|
+
const converted = convertResponsesMessages(model, context, allowedToolCallProviders, { includeSystemPrompt: false })
|
|
295
|
+
// Pi emits user messages in the SDK shorthand `{ role, content }`. DSH's
|
|
296
|
+
// checkpoint/store contract is the explicit Responses item shape, so make
|
|
297
|
+
// that boundary canonical without changing Pi's internal serializer.
|
|
298
|
+
const stripImageDetail = (value) => {
|
|
299
|
+
if (!Array.isArray(value)) return value
|
|
300
|
+
return value.map((part) => {
|
|
301
|
+
if (part?.type === 'input_image') {
|
|
302
|
+
const { detail: _detail, ...withoutDetail } = part
|
|
303
|
+
return withoutDetail
|
|
304
|
+
}
|
|
305
|
+
return part
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
return converted.map((item) => {
|
|
309
|
+
if (item?.role === 'user') {
|
|
310
|
+
const content = typeof item.content === 'string'
|
|
311
|
+
? [{ type: 'input_text', text: item.content }]
|
|
312
|
+
: item.content
|
|
313
|
+
return { ...(item.type === undefined ? { type: 'message' } : {}), role: 'user', content: stripImageDetail(content) }
|
|
314
|
+
}
|
|
315
|
+
if (item?.type === 'function_call_output') return { ...item, output: stripImageDetail(item.output) }
|
|
316
|
+
return item
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function hasCompleteImageMetadata(messages) {
|
|
321
|
+
const complete = (blocks) => blocks.every((block) => {
|
|
322
|
+
if (block.type === 'image') {
|
|
323
|
+
const bytes = Number(block.attachment?.bytes)
|
|
324
|
+
return Number.isSafeInteger(bytes) && bytes > 0
|
|
325
|
+
}
|
|
326
|
+
return block.type !== 'tool-result' || complete(block.content)
|
|
327
|
+
})
|
|
328
|
+
return messages.every((message) => complete(message.content))
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function offloadDshRequestImages(messages, maxRequestImageBytes = DEFAULT_MAX_REQUEST_IMAGE_BYTES) {
|
|
332
|
+
if (!Number.isSafeInteger(maxRequestImageBytes) || maxRequestImageBytes <= 0) {
|
|
333
|
+
throw compactError('LCX Compact maxRequestImageBytes must be a positive integer', 'LCX_COMPACT_IMAGE_LIMIT_INVALID')
|
|
334
|
+
}
|
|
335
|
+
const input = messages ?? []
|
|
336
|
+
return hasCompleteImageMetadata(input)
|
|
337
|
+
? offloadRequestImages(input, maxRequestImageBytes)
|
|
338
|
+
: input
|
|
339
|
+
}
|