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.
@@ -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
+ }