Files
orca/src/main/codex/codex-structured-notification-retry.ts
T
Brennan BensonandMerge Sim 872bd51d47 fix(native-chat): reland large structured command results (#17720)
* fix(native-chat): preserve large structured command results (#17707)

* fix(native-chat): preserve large structured command results

* chore: place native chat validation artifacts under docs

* chore: drop stale root package config

* fix(native-chat): enforce rebuilt lifecycle append slots

---------

Co-authored-by: Merge Sim <sim@local>

* chore: omit native-chat reland planning docs

* fix(native-chat): remove journal store import cycle

* fix(native-chat): keep journal factory acyclic

---------

Co-authored-by: Merge Sim <sim@local>
2026-08-31 13:12:19 -07:00

162 lines
5.1 KiB
TypeScript

import type { CodexAppServerConnection } from './codex-app-server-connection'
import type { CodexJournalTranslationAdmission } from './codex-structured-journal-translation'
import type { CodexSession } from './codex-structured-session-state'
const MAX_RETRY_EVENTS = 256
const MAX_RETRY_BYTES = 8 * 1024 * 1024
const RETRY_DELAY_MS = 25
type PendingNotification = { method: string; params: unknown; bytes: number }
type RetryState = {
connection: CodexAppServerConnection
events: PendingNotification[]
bytes: number
timer: ReturnType<typeof setTimeout> | null
running: boolean
failed: boolean
}
export function createCodexStructuredNotificationRetry(deps: {
sessionFor: (sessionId: string) => CodexSession | undefined
translate: (
sessionId: string,
session: CodexSession,
method: string,
params: unknown
) => CodexJournalTranslationAdmission
}) {
const states = new Map<string, RetryState>()
const retry = (sessionId: string, connection: CodexAppServerConnection): void => {
const state = states.get(sessionId)
if (!state || state.connection !== connection || state.running) {
return
}
if (state.timer) {
clearTimeout(state.timer)
state.timer = null
}
state.running = true
try {
while (state.events.length > 0) {
const pending = state.events[0]
if (!pending) {
break
}
const session = deps.sessionFor(sessionId)
if (!session || session.connection !== connection || session.ended) {
fail(sessionId, state, 'notification retry owner is no longer live')
break
}
const admission = deps.translate(sessionId, session, pending.method, pending.params)
if (!admission.accepted) {
if (admission.reason === 'backpressure') {
state.timer = setTimeout(() => {
state.timer = null
retry(sessionId, connection)
}, RETRY_DELAY_MS)
state.timer.unref?.()
} else {
fail(sessionId, state, `notification admission failed (${admission.reason})`)
}
break
}
state.events.shift()
state.bytes = Math.max(0, state.bytes - pending.bytes)
}
if (state.events.length === 0) {
states.delete(sessionId)
}
} finally {
state.running = false
}
}
const fail = (sessionId: string, state: RetryState, reason: string): void => {
if (state.failed) {
return
}
state.failed = true
if (state.timer) {
clearTimeout(state.timer)
state.timer = null
}
// The queue is no longer replayable. Drop it explicitly, release the read
// pause, and enter the adapter's generation-checked unexpected-exit seam.
state.events.length = 0
state.bytes = 0
state.connection.resumeReading?.()
states.delete(sessionId)
const session = deps.sessionFor(sessionId)
if (session?.connection === state.connection) {
void session.forceCloseUnexpected?.(new Error(reason))
}
}
const enqueue = (
sessionId: string,
connection: CodexAppServerConnection,
method: string,
params: unknown
): void => {
const bytes = Buffer.byteLength(JSON.stringify({ method, params }), 'utf8')
let state = states.get(sessionId)
if (!state || state.connection !== connection) {
state = { connection, events: [], bytes: 0, timer: null, running: false, failed: false }
states.set(sessionId, state)
}
if (state.events.length >= MAX_RETRY_EVENTS || state.bytes + bytes > MAX_RETRY_BYTES) {
// A bounded queue cannot retain more traffic. Fail it truthfully so the
// provider's generation enters host recovery instead of stranding a pause.
fail(sessionId, state, 'notification retry queue overflow')
return
}
state.events.push({ method, params, bytes })
state.bytes += bytes
connection.pauseReading?.()
}
return {
handle: (
sessionId: string,
method: string,
params: unknown
): CodexJournalTranslationAdmission => {
const session = deps.sessionFor(sessionId)
if (!session) {
return { accepted: true }
}
const state = states.get(sessionId)
if (state && state.events.length > 0) {
enqueue(sessionId, state.connection, method, params)
retry(sessionId, state.connection)
return { accepted: false, reason: 'backpressure' }
}
const admission = deps.translate(sessionId, session, method, params)
if (!admission.accepted) {
enqueue(sessionId, session.connection, method, params)
retry(sessionId, session.connection)
}
return admission
},
retry,
clear: (sessionId: string, connection: CodexAppServerConnection | null): void => {
const state = states.get(sessionId)
if (!state || (connection && state.connection !== connection)) {
return
}
if (state.timer) {
clearTimeout(state.timer)
}
state.events.length = 0
state.bytes = 0
state.connection.resumeReading?.()
states.delete(sessionId)
}
}
}
export type CodexStructuredNotificationRetry = ReturnType<
typeof createCodexStructuredNotificationRetry
>