dsh-lcx-codex 0.3.3 → 0.4.0-rc.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.
@@ -0,0 +1,34 @@
1
+ import { mkdirSync, readFileSync, renameSync, writeFileSync, chmodSync } from 'node:fs'
2
+ import { dirname } from 'node:path'
3
+
4
+ export class JsonStore {
5
+ constructor(file, empty, validate, corruptCode = 'LCX_STORE_CORRUPT') {
6
+ this.file = file
7
+ this.empty = empty
8
+ this.validate = validate
9
+ this.corruptCode = corruptCode
10
+ this.data = empty()
11
+ this.refresh()
12
+ }
13
+ refresh() {
14
+ try {
15
+ const parsed = JSON.parse(readFileSync(this.file, 'utf8'))
16
+ if (!this.validate(parsed)) throw new Error('invalid store')
17
+ this.data = parsed
18
+ } catch (error) {
19
+ if (error?.code === 'ENOENT') { this.data = this.empty(); return }
20
+ const wrapped = new Error(`Invalid LCX store ${this.file}`, { cause: error }); wrapped.code = this.corruptCode; throw wrapped
21
+ }
22
+ }
23
+ update(mutator) {
24
+ this.refresh()
25
+ const next = mutator(structuredClone(this.data))
26
+ if (!this.validate(next)) throw new Error(`Refusing to write invalid LCX store ${this.file}`)
27
+ mkdirSync(dirname(this.file), { recursive: true })
28
+ const tmp = `${this.file}.${process.pid}.tmp`
29
+ writeFileSync(tmp, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 })
30
+ try { chmodSync(tmp, 0o600) } catch {}
31
+ renameSync(tmp, this.file)
32
+ this.data = next
33
+ }
34
+ }
@@ -0,0 +1,20 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { baseURLFingerprint, sessionAncestry } from './route.js'
3
+
4
+ const PATTERN = /\[dsh-lcx-codex-v3-checkpoint:([0-9a-f-]{36})\]/iu
5
+ function textOf(message) { return (message?.content ?? []).filter((b) => b?.type === 'text').map((b) => b.text).join('') }
6
+ export function legacyV3Id(message) { return textOf(message).match(PATTERN)?.[1]?.toLowerCase() }
7
+ export function loadLegacyRecord(file, id) {
8
+ if (!file || !id) return undefined
9
+ try {
10
+ const parsed = JSON.parse(readFileSync(file, 'utf8'))
11
+ const record = parsed?.version === 3 ? parsed.checkpoints?.[id] : undefined
12
+ if (!record || record.version !== 3 || !Array.isArray(record.nativeOutput)) return undefined
13
+ return structuredClone(record)
14
+ } catch { return undefined }
15
+ }
16
+ export function legacyRouteCompatible(record, route, ctx) {
17
+ if (!record || record.provider !== route.provider || record.model !== route.model) return false
18
+ if (record.baseURLFingerprint !== baseURLFingerprint(route.baseURL)) return false
19
+ return sessionAncestry(ctx, route.sessionId).includes(record.sourceSessionId ?? record.lineageId)
20
+ }
@@ -0,0 +1,93 @@
1
+ import { baseURLFingerprint, routeCompatible } from './route.js'
2
+ import { persistNativeImageReferences } from './dsh-responses.js'
3
+
4
+ export const NATIVE_BLOCK_TYPE = 'lcx-native-compaction-v5'
5
+ export const NATIVE_BLOCK_VERSION = 5
6
+ export const LEGACY_V4_BLOCK_TYPE = 'lcx-native-compaction-v4'
7
+ export const LEGACY_V4_BLOCK_VERSION = 4
8
+ const LEGACY_V3_PATTERN = /\[dsh-lcx-codex-v3-checkpoint:([0-9a-f-]{36})\]/iu
9
+
10
+ export const RETAINED_MESSAGE_TOKEN_BUDGET = 64_000
11
+ export const ASSISTANT_RETENTION_TOKEN_RESERVE = 24_000
12
+ export const ASSISTANT_RETENTION_PER_MESSAGE_TOKEN_CAP = 3_000
13
+
14
+ function isObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) }
15
+ function textOf(message) { return (message?.content ?? []).filter((b) => b?.type === 'text').map((b) => b.text).join('') }
16
+ function estimatedItemTokens(item) { try { return Math.max(1, Math.ceil(JSON.stringify(item).length / 4)) } catch { return RETAINED_MESSAGE_TOKEN_BUDGET + 1 } }
17
+ function isRetainedClientItem(item) { return isObject(item) && (item.type === undefined || item.type === 'message') && ['user', 'developer', 'system'].includes(String(item.role ?? '')) }
18
+ function assistantTextParts(item) { if (!isObject(item) || item.type !== 'message' || item.role !== 'assistant' || !Array.isArray(item.content)) return []; return item.content.filter((part) => part?.type === 'output_text' && typeof part.text === 'string' && part.text.length > 0) }
19
+ function isRetainedAssistantItem(item) { return assistantTextParts(item).length > 0 }
20
+ function assistantText(item) { return assistantTextParts(item).map((part) => part.text).join('') }
21
+
22
+ function truncateVisibleAssistantItem(item, maxTokens = ASSISTANT_RETENTION_PER_MESSAGE_TOKEN_CAP) {
23
+ const text = assistantText(item); if (!text) return undefined
24
+ const maxChars = Math.max(256, maxTokens * 4); let retained = text
25
+ if (text.length > maxChars) { const marker = '\n…[LCX retained answer truncated]…\n'; const available = Math.max(0, maxChars - marker.length); const head = Math.floor(available * 0.72); const tail = available - head; retained = `${text.slice(0, head)}${marker}${text.slice(Math.max(head, text.length - tail))}` }
26
+ return { type: 'message', role: 'assistant', content: [{ type: 'output_text', text: retained }] }
27
+ }
28
+
29
+ function selectNewest(candidates, budget, transform = (value) => structuredClone(value.item)) {
30
+ const selected = []; let used = 0
31
+ for (let index = candidates.length - 1; index >= 0; index -= 1) { const candidate = candidates[index]; const item = transform(candidate); if (!item) continue; const tokens = estimatedItemTokens(item); if (tokens > budget || used + tokens > budget) continue; selected.push({ index: candidate.index, item, tokens }); used += tokens }
32
+ return { selected, used }
33
+ }
34
+
35
+ export function retainedCompactionInput(input, options = {}) {
36
+ const budget = Number.isSafeInteger(options.tokenBudget) && options.tokenBudget > 0 ? options.tokenBudget : RETAINED_MESSAGE_TOKEN_BUDGET
37
+ const candidates = (input ?? []).map((item, index) => ({ item, index })).filter(({ item }) => isRetainedClientItem(item))
38
+ return selectNewest(candidates, budget).selected.sort((a, b) => a.index - b.index).map(({ item }) => item)
39
+ }
40
+
41
+ export function retainedConversationPlan(input, options = {}) {
42
+ const totalBudget = Number.isSafeInteger(options.tokenBudget) && options.tokenBudget > 0 ? options.tokenBudget : RETAINED_MESSAGE_TOKEN_BUDGET
43
+ const assistantReserve = Math.min(totalBudget, Number.isSafeInteger(options.assistantTokenReserve) && options.assistantTokenReserve >= 0 ? options.assistantTokenReserve : ASSISTANT_RETENTION_TOKEN_RESERVE)
44
+ const perMessageCap = Number.isSafeInteger(options.assistantPerMessageTokenCap) && options.assistantPerMessageTokenCap > 0 ? options.assistantPerMessageTokenCap : ASSISTANT_RETENTION_PER_MESSAGE_TOKEN_CAP
45
+ const indexed = (input ?? []).map((item, index) => ({ item, index }))
46
+ const assistant = selectNewest(indexed.filter(({ item }) => isRetainedAssistantItem(item)), assistantReserve, ({ item }) => truncateVisibleAssistantItem(item, perMessageCap))
47
+ const remaining = Math.max(0, totalBudget - assistant.used)
48
+ const client = selectNewest(indexed.filter(({ item }) => isRetainedClientItem(item)), remaining)
49
+ const selected = [...client.selected, ...assistant.selected].sort((a, b) => a.index - b.index)
50
+ return { items: selected.map(({ item }) => item), clientCount: client.selected.length, assistantCount: assistant.selected.length, estimatedTokens: client.used + assistant.used, clientEstimatedTokens: client.used, assistantEstimatedTokens: assistant.used }
51
+ }
52
+ export function retainedConversationInput(input, options = {}) { return retainedConversationPlan(input, options).items }
53
+ export function hasRetainedCompactionInput(items) { return (items ?? []).some((item) => isRetainedClientItem(item) || isRetainedAssistantItem(item)) }
54
+ export function compactCheckpointId(message) { const source = message?.source; return source?.kind === 'plugin' && source?.plugin === 'compact' && typeof source.compactionId === 'string' && source.compactionId ? source.compactionId : undefined }
55
+ export function legacyCheckpointId(message) { return textOf(message).match(LEGACY_V3_PATTERN)?.[1]?.toLowerCase() }
56
+ export function activeCompactionId(session) { if (!session?.events) return undefined; const ended = new Set(); for (let index = session.events.length - 1; index >= 0; index -= 1) { const event = session.events[index]; if (event.type === 'compaction/end' && event.data?.compactionId) ended.add(event.data.compactionId); if (event.type === 'compaction/start' && event.data?.compactionId && !ended.has(event.data.compactionId)) return event.data.compactionId } return undefined }
57
+
58
+ export function createNativeCheckpointBlock({ session, route, result, input = [], imageMap, retentionOptions = {} }) {
59
+ const compactionId = activeCompactionId(session)
60
+ if (!compactionId) { const error = new Error('Native compaction could not correlate the active DSH compaction transaction'); error.code = 'LCX_COMPACTION_ID_UNAVAILABLE'; throw error }
61
+ const retention = retainedConversationPlan(input, retentionOptions)
62
+ const nativeOutput = persistNativeImageReferences([...retention.items, structuredClone(result.compaction)], imageMap)
63
+ return { type: NATIVE_BLOCK_TYPE, version: NATIVE_BLOCK_VERSION, retentionPolicy: 'conversation-fidelity-v1', compactionId, provider: route.provider, model: route.model, baseURLFingerprint: baseURLFingerprint(route.baseURL), sourceSessionId: route.sessionId, nativeOutput, nativeCompaction: structuredClone(result.compaction), retainedInputCount: retention.items.length, retainedClientCount: retention.clientCount, retainedAssistantCount: retention.assistantCount, retainedEstimatedTokens: retention.estimatedTokens, createdAt: Date.now() }
64
+ }
65
+
66
+ export function nativeCheckpointChunks(block, usage) {
67
+ const text = 'LCX Native V2 checkpoint saved in the DSH session log.'
68
+ return [{ type: 'block-start', index: 0, blockType: 'text' }, { type: 'text-delta', index: 0, text }, { type: 'block-end', index: 0, block: { type: 'text', text } }, { type: 'block-start', index: 1, blockType: NATIVE_BLOCK_TYPE }, { type: 'block-end', index: 1, block: structuredClone(block) }, ...(usage ? [{ type: 'usage', usage: structuredClone(usage) }] : []), { type: 'finish', reason: { kind: 'stop' } }]
69
+ }
70
+
71
+ export function compactionSummaryEvent(session, compactionId) { if (!session?.events || !compactionId) return undefined; for (let index = session.events.length - 1; index >= 0; index -= 1) { const event = session.events[index]; if (event.type === 'compaction/summary' && event.data?.compactionId === compactionId) return event } return undefined }
72
+ function nativeBlockFromSummary(event) { const raw = event?.data?.rawOutput ?? []; return raw.find((value) => value?.type === NATIVE_BLOCK_TYPE && value?.version === NATIVE_BLOCK_VERSION) ?? raw.find((value) => value?.type === LEGACY_V4_BLOCK_TYPE && value?.version === LEGACY_V4_BLOCK_VERSION) }
73
+ function validateCount(value) { return value === undefined || (Number.isSafeInteger(value) && value >= 0) }
74
+ export function stateFromSummaryEvent(event) {
75
+ if (event?.type !== 'compaction/summary') return undefined
76
+ const block = nativeBlockFromSummary(event); if (!isObject(block)) return undefined
77
+ if (block.compactionId !== event.data.compactionId || !Array.isArray(block.nativeOutput) || block.nativeOutput.length === 0) return undefined
78
+ if (typeof block.provider !== 'string' || typeof block.model !== 'string' || typeof block.baseURLFingerprint !== 'string' || typeof block.sourceSessionId !== 'string') return undefined
79
+ if (!validateCount(block.retainedInputCount) || !validateCount(block.retainedClientCount) || !validateCount(block.retainedAssistantCount)) return undefined
80
+ const compactions = block.nativeOutput.filter((item) => item?.type === 'compaction'); if (compactions.length !== 1 || typeof compactions[0]?.encrypted_content !== 'string') return undefined
81
+ const compactionIndex = block.nativeOutput.findIndex((item) => item?.type === 'compaction'); const prefix = compactionIndex >= 0 ? block.nativeOutput.slice(0, compactionIndex) : []
82
+ if (block.version === NATIVE_BLOCK_VERSION) { const clients = prefix.filter(isRetainedClientItem).length; const assistants = prefix.filter(isRetainedAssistantItem).length; const total = clients + assistants; if (block.retainedInputCount !== undefined && block.retainedInputCount !== total) return undefined; if (block.retainedClientCount !== undefined && block.retainedClientCount !== clients) return undefined; if (block.retainedAssistantCount !== undefined && block.retainedAssistantCount !== assistants) return undefined } else { const clients = prefix.filter(isRetainedClientItem).length; if (block.retainedInputCount !== undefined && block.retainedInputCount !== clients) return undefined }
83
+ return structuredClone(block)
84
+ }
85
+ export function checkpointStateForMessage(session, message) { const id = compactCheckpointId(message); return id ? stateFromSummaryEvent(compactionSummaryEvent(session, id)) : undefined }
86
+ export function stateRouteCompatible(state, route, ctx) { return routeCompatible(state, route, ctx) }
87
+ function eventMessage(session, seq) { const events = session?.events ?? []; const event = events[seq]?.seq === seq ? events[seq] : events.find((candidate) => candidate?.seq === seq); if (!event) return undefined; if (typeof session.deriveEventMessage === 'function') return session.deriveEventMessage(event) ?? undefined; if (event.type === 'user/message') return event.data; if (event.type === 'assistant/message') return event.data?.message; if (event.type === 'tool/result') return event.data?.message; return undefined }
88
+ function estimateChars(message) { try { return JSON.stringify(message).length } catch { return 0 } }
89
+ function expandedCheckpointMessages(session, compactionId, visited, depth) { if (depth > 16 || visited.has(compactionId)) return []; visited.add(compactionId); const summary = compactionSummaryEvent(session, compactionId); if (!summary) return []; const result = []; for (const seq of summary.data?.shadowedSeqs ?? []) { const message = eventMessage(session, seq); if (!message) continue; const nested = compactCheckpointId(message); if (nested) result.push(...expandedCheckpointMessages(session, nested, visited, depth + 1)); else result.push(structuredClone(message)) } return result }
90
+ export function shadowedMessagesForCheckpoint(session, compactionId) { return expandedCheckpointMessages(session, compactionId, new Set(), 0) }
91
+ function groupMessages(messages) { const groups = []; for (let index = 0; index < messages.length; index += 1) { const message = messages[index]; const toolCalls = message?.role === 'assistant' ? (message.content ?? []).filter((b) => b?.type === 'tool-call').map((b) => String(b.id)) : []; if (toolCalls.length === 0) { groups.push([message]); continue } const group = [message]; const pending = new Set(toolCalls); let cursor = index + 1; while (cursor < messages.length && pending.size > 0) { const next = messages[cursor]; const results = next?.role === 'user' ? (next.content ?? []).filter((b) => b?.type === 'tool-result').map((b) => String(b.toolCallId)) : []; if (results.length === 0) break; group.push(next); for (const id of results) pending.delete(id); cursor += 1 } if (pending.size === 0) index = cursor - 1; groups.push(group) } return groups }
92
+ export function portableMessagesForCheckpoint(session, compactionId, options = {}) { const maxChars = Number.isSafeInteger(options.maxChars) && options.maxChars > 0 ? options.maxChars : 80_000; const expanded = shadowedMessagesForCheckpoint(session, compactionId); if (expanded.length === 0) return []; const groups = groupMessages(expanded); const kept = []; let chars = 0; for (let index = groups.length - 1; index >= 0; index -= 1) { const group = groups[index]; const size = group.reduce((sum, message) => sum + estimateChars(message), 0); if (kept.length > 0 && chars + size > maxChars) break; kept.unshift(...group); chars += size } return kept }
93
+ 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 }
@@ -0,0 +1,281 @@
1
+ import { fetchSseWithRetry } from './transport.js'
2
+ import { mergeFeatureHeader, responsesGenerationEnvelope } from './compact-v2.js'
3
+ import { responsesTools } from './dsh-responses.js'
4
+
5
+ function isObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) }
6
+ function fail(message, code = 'LCX_RESPONSES_UPSTREAM_ERROR') { const e = new Error(message); e.code = code; return e }
7
+
8
+ function usageFrom(raw) {
9
+ if (!isObject(raw)) return undefined
10
+ const inputTotal = Number(raw.input_tokens ?? 0)
11
+ const output = Number(raw.output_tokens ?? 0)
12
+ const details = raw.input_tokens_details ?? raw.input_token_details ?? raw.prompt_tokens_details ?? {}
13
+ const cacheRead = Number(details.cached_tokens ?? 0)
14
+ const cacheWrite = Number(details.cache_write_tokens ?? 0)
15
+ const usage = {
16
+ inputTokens: Math.max(0, Number.isFinite(inputTotal) ? inputTotal - (Number.isFinite(cacheRead) ? cacheRead : 0) - (Number.isFinite(cacheWrite) ? cacheWrite : 0) : 0),
17
+ outputTokens: Number.isFinite(output) && output > 0 ? output : 0,
18
+ }
19
+ if (Number.isFinite(cacheRead) && cacheRead > 0) usage.cacheReadTokens = cacheRead
20
+ if (Number.isFinite(cacheWrite) && cacheWrite > 0) usage.cacheWriteTokens = cacheWrite
21
+ const reasoning = Number(raw.output_tokens_details?.reasoning_tokens ?? raw.output_token_details?.reasoning_tokens ?? 0)
22
+ if (Number.isFinite(reasoning) && reasoning > 0) usage.reasoningTokens = reasoning
23
+ return usage
24
+ }
25
+
26
+ async function* sseEvents(response, options = {}) {
27
+ if (!response?.body) throw fail('Responses replay returned no SSE body', 'LCX_INVALID_SSE')
28
+ const reader = response.body.getReader()
29
+ const decoder = new TextDecoder()
30
+ let pending = ''
31
+ let dataLines = []
32
+ let bytes = 0
33
+ const maxBytes = options.maxResponseBytes ?? 8 * 1024 * 1024
34
+ const decodeEvent = () => {
35
+ if (dataLines.length === 0) return undefined
36
+ const data = dataLines.join('\n')
37
+ dataLines = []
38
+ if (data === '[DONE]') return undefined
39
+ try { return JSON.parse(data) } catch (cause) { throw fail(`malformed Responses SSE JSON: ${cause}`, 'LCX_INVALID_SSE') }
40
+ }
41
+ try {
42
+ while (true) {
43
+ if (options.signal?.aborted) throw options.signal.reason ?? fail('request aborted', 'LCX_ABORTED')
44
+ const { done, value } = await reader.read()
45
+ if (done) break
46
+ bytes += value.byteLength
47
+ if (bytes > maxBytes) throw fail(`Responses SSE exceeds ${maxBytes} bytes`, 'LCX_RESPONSE_TOO_LARGE')
48
+ pending += decoder.decode(value, { stream: true })
49
+ let newline
50
+ while ((newline = pending.indexOf('\n')) >= 0) {
51
+ const line = pending.slice(0, newline).replace(/\r$/u, '')
52
+ pending = pending.slice(newline + 1)
53
+ if (line === '') {
54
+ const event = decodeEvent()
55
+ if (event) yield event
56
+ } else if (!line.startsWith(':') && line.startsWith('data:')) dataLines.push(line.slice(5).replace(/^ /u, ''))
57
+ }
58
+ }
59
+ pending += decoder.decode()
60
+ if (pending.startsWith('data:')) dataLines.push(pending.slice(5).replace(/^ /u, ''))
61
+ const event = decodeEvent()
62
+ if (event) yield event
63
+ } finally {
64
+ await reader.cancel().catch(() => undefined)
65
+ }
66
+ }
67
+
68
+ function streamError(event) {
69
+ const e = fail(`Responses stream ended with ${String(event?.type)}`)
70
+ if (Number.isInteger(event?.error?.status)) e.status = event.error.status
71
+ return e
72
+ }
73
+
74
+ function textSignature(itemId, phase) {
75
+ if (typeof itemId !== 'string' || itemId.length === 0) return undefined
76
+ const value = { v: 1, id: itemId }
77
+ if (phase === 'commentary' || phase === 'final_answer') value.phase = phase
78
+ return JSON.stringify(value)
79
+ }
80
+
81
+ function replayBlockFor(item, fallbackKind) {
82
+ const kind = item?.kind ?? fallbackKind
83
+ if (kind === 'text') {
84
+ const signature = textSignature(item?.itemId, item?.phase)
85
+ return { type: 'text', ...(signature ? { textSignature: signature } : {}) }
86
+ }
87
+ if (kind === 'reasoning') {
88
+ const signature = isObject(item?.rawItem) ? JSON.stringify(item.rawItem) : undefined
89
+ return { type: 'reasoning', ...(signature ? { thinkingSignature: signature } : {}) }
90
+ }
91
+ return { type: 'tool-call' }
92
+ }
93
+
94
+ function replayStopReason(reason) {
95
+ if (reason?.kind === 'tool-calls') return 'toolUse'
96
+ if (reason?.kind === 'max-tokens') return 'length'
97
+ if (reason?.kind === 'error') return 'error'
98
+ if (reason?.kind === 'aborted') return 'aborted'
99
+ return 'stop'
100
+ }
101
+ function dshToolCallId(callId, itemId) {
102
+ const call = typeof callId === 'string' && callId.length > 0 ? callId : undefined
103
+ const item = typeof itemId === 'string' && itemId.length > 0 ? itemId : undefined
104
+ if (call && item) return `${call}|${item}`
105
+ return call ?? item ?? ''
106
+ }
107
+
108
+ export function replayBody({ model, input, system, tools, promptCacheKey, promptCacheRetention, reasoningEffort, temperature, maxTokens }) {
109
+ return {
110
+ model,
111
+ input: structuredClone(input),
112
+ stream: true,
113
+ store: false,
114
+ tool_choice: 'auto',
115
+ parallel_tool_calls: true,
116
+ ...(system !== undefined ? { instructions: system } : {}),
117
+ ...(tools !== undefined ? { tools: responsesTools(tools) } : {}),
118
+ ...(promptCacheKey ? { prompt_cache_key: String(promptCacheKey) } : {}),
119
+ ...(promptCacheRetention ? { prompt_cache_retention: String(promptCacheRetention) } : {}),
120
+ ...responsesGenerationEnvelope({ reasoningEffort, temperature, maxTokens }),
121
+ }
122
+ }
123
+
124
+ export async function* requestNativeReplay({ baseURL, provider, model, input, system, tools, promptCacheKey, promptCacheRetention, reasoningEffort, temperature, maxTokens, headers, signal, timeoutMs, maxAttempts, maxResponseBytes }) {
125
+ const response = await fetchSseWithRetry(`${String(baseURL).replace(/\/+$/u, '')}/responses`, replayBody({ model, input, system, tools, promptCacheKey, promptCacheRetention, reasoningEffort, temperature, maxTokens }), mergeFeatureHeader(headers), signal, timeoutMs, { maxAttempts, maxResponseBytes })
126
+ const records = []
127
+ const byStreamIdentity = new Map()
128
+ let nextIndex = 0
129
+ let terminal
130
+ const identity = (kind, itemId, partIndex) => itemId === undefined ? undefined : `${kind}:${String(itemId)}:${partIndex ?? 0}`
131
+ const createRecord = (kind, streamIdentity, outputIndex, partIndex) => {
132
+ const existing = streamIdentity === undefined ? undefined : byStreamIdentity.get(streamIdentity)
133
+ if (existing) return { record: existing, created: false }
134
+ const record = { kind, index: nextIndex++, outputIndex, partIndex, streamIdentity, text: '', arguments: '', name: '', callId: undefined, itemId: undefined, terminal: undefined }
135
+ records.push(record)
136
+ if (streamIdentity !== undefined) byStreamIdentity.set(streamIdentity, record)
137
+ return { record, created: true }
138
+ }
139
+ for await (const event of sseEvents(response, { signal, maxResponseBytes })) {
140
+ if (!isObject(event)) continue
141
+ if (event.type === 'error' || event.type === 'response.failed' || event.type === 'response.incomplete') throw streamError(event)
142
+ if (event.type === 'response.output_text.delta') {
143
+ const itemId = typeof event.item_id === 'string' ? event.item_id : undefined
144
+ const key = identity('text', itemId, event.content_index)
145
+ const allocated = createRecord('text', key ?? `text:index:${event.output_index ?? ''}:${event.content_index ?? ''}`, event.output_index, event.content_index)
146
+ const record = allocated.record
147
+ if (allocated.created) yield { type: 'block-start', index: record.index, blockType: 'text' }
148
+ if (typeof event.delta === 'string' && event.delta) { record.text += event.delta; yield { type: 'text-delta', index: record.index, text: event.delta } }
149
+ continue
150
+ }
151
+ if (event.type === 'response.reasoning_summary_text.delta' || event.type === 'response.reasoning_text.delta') {
152
+ const itemId = typeof event.item_id === 'string' ? event.item_id : undefined
153
+ const key = identity('reasoning', itemId, event.summary_index ?? event.content_index)
154
+ const allocated = createRecord('reasoning', key ?? `reasoning:index:${event.output_index ?? ''}:${event.summary_index ?? event.content_index ?? ''}`, event.output_index, event.summary_index ?? event.content_index)
155
+ const record = allocated.record
156
+ if (allocated.created) yield { type: 'block-start', index: record.index, blockType: 'reasoning' }
157
+ if (typeof event.delta === 'string' && event.delta) { record.text += event.delta; yield { type: 'reasoning-delta', index: record.index, text: event.delta } }
158
+ continue
159
+ }
160
+ if (event.type === 'response.function_call_arguments.delta') {
161
+ const itemId = typeof event.item_id === 'string' ? event.item_id : undefined
162
+ const callId = typeof event.call_id === 'string' ? event.call_id : itemId ?? `call-${event.output_index ?? nextIndex}`
163
+ const key = identity('tool-call', typeof event.item_id === 'string' ? event.item_id : callId, 0) ?? `tool-call:${callId}`
164
+ const allocated = createRecord('tool-call', key, event.output_index, 0)
165
+ const record = allocated.record
166
+ record.callId = callId
167
+ record.itemId = itemId
168
+ if (typeof event.name === 'string') record.name = event.name
169
+ if (allocated.created) yield { type: 'block-start', index: record.index, blockType: 'tool-call' }
170
+ const delta = typeof event.delta === 'string' ? event.delta : ''
171
+ record.arguments += delta
172
+ yield { type: 'tool-call-delta', index: record.index, id: dshToolCallId(callId, itemId), ...(record.name ? { name: record.name } : {}), argumentsDelta: delta }
173
+ continue
174
+ }
175
+ if (event.type === 'response.output_item.added' && event.item?.type === 'function_call') {
176
+ const itemId = typeof event.item.id === 'string' ? event.item.id : undefined
177
+ const callId = typeof event.item.call_id === 'string' ? event.item.call_id : itemId ?? `call-${event.output_index ?? nextIndex}`
178
+ const key = identity('tool-call', itemId ?? callId, 0) ?? `tool-call:${callId}`
179
+ const allocated = createRecord('tool-call', key, event.output_index, 0)
180
+ const record = allocated.record
181
+ record.callId = callId
182
+ record.itemId = itemId
183
+ record.name = typeof event.item.name === 'string' ? event.item.name : record.name
184
+ if (allocated.created) {
185
+ yield { type: 'block-start', index: record.index, blockType: 'tool-call' }
186
+ if (typeof event.item.arguments === 'string' && event.item.arguments) { record.arguments = event.item.arguments; yield { type: 'tool-call-delta', index: record.index, id: dshToolCallId(callId, itemId), ...(record.name ? { name: record.name } : {}), argumentsDelta: event.item.arguments } }
187
+ }
188
+ continue
189
+ }
190
+ if (event.type === 'response.output_item.done' && event.item?.type === 'function_call') {
191
+ const itemId = typeof event.item.id === 'string' ? event.item.id : undefined
192
+ const callId = typeof event.item.call_id === 'string' ? event.item.call_id : itemId ?? `call-${event.output_index ?? nextIndex}`
193
+ const key = identity('tool-call', itemId ?? callId, 0) ?? `tool-call:${callId}`
194
+ const allocated = createRecord('tool-call', key, event.output_index, 0)
195
+ const record = allocated.record
196
+ record.callId = callId
197
+ record.itemId = itemId
198
+ record.name = typeof event.item.name === 'string' ? event.item.name : record.name
199
+ const completedArguments = typeof event.item.arguments === 'string' ? event.item.arguments : record.arguments
200
+ if (allocated.created) {
201
+ yield { type: 'block-start', index: record.index, blockType: 'tool-call' }
202
+ if (completedArguments) yield { type: 'tool-call-delta', index: record.index, id: dshToolCallId(callId, itemId), ...(record.name ? { name: record.name } : {}), argumentsDelta: completedArguments }
203
+ }
204
+ record.arguments = completedArguments
205
+ continue
206
+ }
207
+ if (event.type === 'response.completed') terminal = event.response
208
+ }
209
+ if (!isObject(terminal)) throw fail('Responses replay ended without response.completed', 'LCX_RESPONSES_INCOMPLETE')
210
+ if (terminal.status !== 'completed') throw fail('Responses replay response.completed did not carry completed status', 'LCX_RESPONSES_INCOMPLETE')
211
+
212
+ const terminalItems = []
213
+ for (const [outputIndex, item] of (terminal.output ?? []).entries()) {
214
+ if (item?.type === 'message') {
215
+ for (const [contentIndex, part] of (item.content ?? []).entries()) if (part?.type === 'output_text') terminalItems.push({ kind: 'text', outputIndex, partIndex: contentIndex, itemId: item.id, phase: item.phase, text: String(part.text ?? ''), rawItem: item })
216
+ } else if (item?.type === 'reasoning') {
217
+ const parts = Array.isArray(item.summary) && item.summary.length ? item.summary : item.content ?? []
218
+ parts.forEach((part, index) => { if (typeof part?.text === 'string') terminalItems.push({ kind: 'reasoning', outputIndex, partIndex: index, itemId: item.id, text: part.text, rawItem: item }) })
219
+ } else if (item?.type === 'function_call') {
220
+ terminalItems.push({ kind: 'tool-call', outputIndex, partIndex: 0, itemId: item.id, callId: item.call_id, name: String(item.name ?? ''), arguments: String(item.arguments ?? ''), rawItem: item })
221
+ }
222
+ }
223
+ const matched = new Set()
224
+ const matchesRecord = (item) => {
225
+ const stable = identity(item.kind, item.itemId ?? item.callId, item.partIndex)
226
+ if (stable) {
227
+ const exact = records.find((record) => !matched.has(record) && record.kind === item.kind && (record.streamIdentity === stable || (item.kind === 'tool-call' && record.callId === item.callId)))
228
+ if (exact) return exact
229
+ }
230
+ const candidates = records.filter((record) => !matched.has(record) && record.kind === item.kind)
231
+ if (candidates.length === 1) return candidates[0]
232
+ const contentMatches = candidates.filter((record) => item.kind === 'tool-call'
233
+ ? record.callId === item.callId || (item.arguments && record.arguments && (item.arguments.startsWith(record.arguments) || record.arguments.startsWith(item.arguments)))
234
+ : item.text !== undefined && record.text && (item.text === record.text || item.text.startsWith(record.text) || record.text.startsWith(item.text)))
235
+ return contentMatches.length === 1 ? contentMatches[0] : undefined
236
+ }
237
+ for (const item of terminalItems) {
238
+ const record = matchesRecord(item)
239
+ if (!record) continue
240
+ matched.add(record)
241
+ record.terminal = item
242
+ }
243
+ const replayBlocks = []
244
+ for (const record of records) {
245
+ const item = record.terminal
246
+ replayBlocks[record.index] = replayBlockFor(item, record.kind)
247
+ if (record.kind === 'text') yield { type: 'block-end', index: record.index, block: { type: 'text', text: item?.text ?? record.text } }
248
+ else if (record.kind === 'reasoning') yield { type: 'block-end', index: record.index, block: { type: 'reasoning', text: item?.text ?? record.text } }
249
+ else yield { type: 'block-end', index: record.index, block: { type: 'tool-call', id: dshToolCallId(item?.callId ?? record.callId, item?.itemId ?? record.itemId), name: item?.name ?? record.name, arguments: item?.arguments ?? record.arguments } }
250
+ }
251
+ for (const item of terminalItems) {
252
+ const record = records.find((candidate) => candidate.terminal === item)
253
+ if (record) continue
254
+ if (item.kind === 'text' && !item.text) continue
255
+ const index = nextIndex++
256
+ replayBlocks[index] = replayBlockFor(item, item.kind)
257
+ yield { type: 'block-start', index, blockType: item.kind === 'tool-call' ? 'tool-call' : item.kind }
258
+ if (item.kind === 'text' && item.text) yield { type: 'text-delta', index, text: item.text }
259
+ if (item.kind === 'reasoning' && item.text) yield { type: 'reasoning-delta', index, text: item.text }
260
+ if (item.kind === 'tool-call') yield { type: 'tool-call-delta', index, id: dshToolCallId(item.callId, item.itemId), name: item.name, argumentsDelta: item.arguments }
261
+ yield { type: 'block-end', index, block: item.kind === 'text' ? { type: 'text', text: item.text } : item.kind === 'reasoning' ? { type: 'reasoning', text: item.text } : { type: 'tool-call', id: dshToolCallId(item.callId, item.itemId), name: item.name, arguments: item.arguments } }
262
+ }
263
+ const usage = usageFrom(terminal.usage)
264
+ if (usage) yield { type: 'usage', usage }
265
+ const reason = terminal.status === 'incomplete'
266
+ ? { kind: 'max-tokens' }
267
+ : terminal.error ? { kind: 'error', failure: { message: terminal.error.message ?? 'Responses error', code: 'LCX_RESPONSES_UPSTREAM_ERROR' } }
268
+ : terminalItems.some((item) => item.kind === 'tool-call') ? { kind: 'tool-calls' } : { kind: 'stop' }
269
+ const replayState = typeof provider === 'string' && provider.length > 0 && typeof model === 'string' && model.length > 0
270
+ ? {
271
+ response: {
272
+ kind: 'pi-ai', version: 2, api: 'openai-responses', provider, model,
273
+ ...(typeof terminal.model === 'string' && terminal.model.length > 0 ? { responseModel: terminal.model } : {}),
274
+ ...(typeof terminal.id === 'string' && terminal.id.length > 0 ? { responseId: terminal.id } : {}),
275
+ stopReason: replayStopReason(reason),
276
+ },
277
+ blocks: Array.from({ length: nextIndex }, (_, index) => replayBlocks[index] ?? { type: records.find((record) => record.index === index)?.kind ?? 'text' }),
278
+ }
279
+ : undefined
280
+ yield { type: 'finish', reason, ...(replayState ? { replayState } : {}) }
281
+ }