133 lines
4.0 KiB
TypeScript
133 lines
4.0 KiB
TypeScript
import { CODEX_LINK_RESPONSE_BYTES, parseControlResponse, validateControlRequest } from '../codex-link-control'
|
|
import type { CodexLinkControlResponse } from '../codex-link-control.types'
|
|
|
|
import { type AiturkCliRunnerDeps, resolveAiturkCliPath } from './aiturk-login'
|
|
|
|
type Dependencies = AiturkCliRunnerDeps
|
|
// Only the fixed entrypoint and fixed arguments go through cmd.exe. All values travel on stdin.
|
|
// eslint-disable-next-line no-control-regex -- Reject control bytes in executable paths before cmd parsing.
|
|
const UNSAFE_PATH = /["%^!\u0000-\u001f]/
|
|
const ABSOLUTE_PATH = /^[A-Za-z]:\\/
|
|
|
|
export function createCodexLinkClient(deps: Dependencies) {
|
|
const setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms))
|
|
const clearTimer = deps.clearTimer ?? (timer => clearTimeout(timer as ReturnType<typeof setTimeout>))
|
|
|
|
return async function control(raw: unknown): Promise<CodexLinkControlResponse> {
|
|
validateControlRequest(raw)
|
|
const cli = resolveAiturkCliPath(deps)
|
|
|
|
if (!cli) {
|
|
throw new Error('codex-link:cli_unavailable')
|
|
}
|
|
|
|
const systemRoot = String(deps.env.SystemRoot || deps.env.SYSTEMROOT || 'C:\\Windows').replace(/[\\/]+$/, '')
|
|
|
|
if (UNSAFE_PATH.test(cli) || UNSAFE_PATH.test(systemRoot) || !ABSOLUTE_PATH.test(systemRoot)) {
|
|
throw new Error('codex-link:invalid_entrypoint')
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let settled = false
|
|
let timer: unknown
|
|
let bytes = 0
|
|
const chunks: Buffer[] = []
|
|
let child: any
|
|
|
|
const finish = (error?: Error, response?: CodexLinkControlResponse) => {
|
|
if (settled) {
|
|
return
|
|
}
|
|
|
|
settled = true
|
|
|
|
if (timer !== undefined) {
|
|
clearTimer(timer)
|
|
}
|
|
|
|
error ? reject(error) : resolve(response!)
|
|
}
|
|
|
|
try {
|
|
child = deps.spawn(
|
|
`${systemRoot}\\System32\\cmd.exe`,
|
|
['/d', '/s', '/c', `""${cli}" codex-link control --stdin"`],
|
|
{
|
|
shell: false,
|
|
windowsHide: true,
|
|
windowsVerbatimArguments: true,
|
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
}
|
|
)
|
|
} catch {
|
|
finish(new Error('codex-link:cli_unavailable'))
|
|
|
|
return
|
|
}
|
|
|
|
timer = setTimer(() => {
|
|
// The durable manager may have accepted a write. Never repeat it under a new ID.
|
|
finish(new Error('codex-link:outcome_unknown'))
|
|
|
|
try {
|
|
child.kill()
|
|
} catch {
|
|
/* proxy already exited; manager is independent */
|
|
}
|
|
}, deps.timeoutMs ?? 30_000)
|
|
child.stdout.on('data', (chunk: Buffer) => {
|
|
if (settled) {
|
|
return
|
|
}
|
|
|
|
bytes += chunk.length
|
|
|
|
if (bytes > CODEX_LINK_RESPONSE_BYTES) {
|
|
finish(new Error('codex-link:outcome_unknown'))
|
|
|
|
try {
|
|
child.kill()
|
|
} catch {
|
|
/* no process tree termination */
|
|
}
|
|
} else {
|
|
chunks.push(Buffer.from(chunk))
|
|
}
|
|
})
|
|
// Drain concurrently, with no history, paths, credentials or uncontrolled diagnostics logged.
|
|
child.stderr.on('data', () => {})
|
|
child.stdin.on('error', () => {})
|
|
child.once('error', () => finish(new Error('codex-link:outcome_unknown')))
|
|
child.once('close', (code: number | null) => {
|
|
if (settled) {
|
|
return
|
|
}
|
|
|
|
if (code !== 0 && code !== 1) {
|
|
finish(new Error('codex-link:outcome_unknown'))
|
|
|
|
return
|
|
}
|
|
|
|
try {
|
|
const response = parseControlResponse(Buffer.concat(chunks), raw)
|
|
|
|
if (code === 1 && response.kind !== 'control_error') {
|
|
finish(new Error('codex-link:outcome_unknown'))
|
|
} else {
|
|
finish(undefined, response)
|
|
}
|
|
} catch {
|
|
finish(new Error('codex-link:outcome_unknown'))
|
|
}
|
|
})
|
|
|
|
try {
|
|
child.stdin.end(JSON.stringify(raw) + '\n')
|
|
} catch {
|
|
finish(new Error('codex-link:outcome_unknown'))
|
|
}
|
|
})
|
|
}
|
|
}
|