|
| 1 | +import type { InvocationHandle } from '../contract.ts'; |
| 2 | +import { createContainerProfile, ProfileCreationCleanupError } from '../container/profile.ts'; |
| 3 | +import { createVendorNetwork, removeVendorNetwork, VendorNetworkCreationCleanupError, |
| 4 | + type VendorNetwork } from '../network/network.ts'; |
| 5 | +import { createCodexCommand, createPhasePolicy } from '../policy.ts'; |
| 6 | +import { readBoundedContainerFile, retainNetworkCleanup, retainSetupCleanup, |
| 7 | + startProfileInvocation } from './supervisor.ts'; |
| 8 | +import { createAdapterInvocationBudget, type AgentAdapterOptions, type AgentAdapterRequest } from './types.ts'; |
| 9 | + |
| 10 | +export const CODEX_OUTPUT_FILE = '/run/codeboost-output/final.txt'; |
| 11 | + |
| 12 | +export async function readCodexOutput(container: string, maximumBytes: number, timeoutMs = 30_000, |
| 13 | + signal?: AbortSignal) { |
| 14 | + const output = await readBoundedContainerFile(container, CODEX_OUTPUT_FILE, maximumBytes, timeoutMs, signal); |
| 15 | + const text = new TextDecoder('utf-8', { fatal: true }).decode(output); |
| 16 | + return Object.freeze({ text, additionalBytes: output.length }); |
| 17 | +} |
| 18 | + |
| 19 | +export function startCodexInvocation(request: AgentAdapterRequest, |
| 20 | + authFile: string, options: AgentAdapterOptions = {}): InvocationHandle { |
| 21 | + if (!authFile || authFile.includes('\0')) throw new Error('Codex auth path is malformed.'); |
| 22 | + const policy = createPhasePolicy(request.invocation); |
| 23 | + const remaining = createAdapterInvocationBudget(request.invocation, options.timeoutMs); |
| 24 | + let network: VendorNetwork; |
| 25 | + try { network = createVendorNetwork(request.invocation, request.imageId, Math.min(60_000, remaining())); } |
| 26 | + catch (error) { |
| 27 | + if (error instanceof VendorNetworkCreationCleanupError) |
| 28 | + return retainSetupCleanup(request.invocation, error.retryCleanup, error.startupError, error, |
| 29 | + 'network creation cleanup'); |
| 30 | + throw error; |
| 31 | + } |
| 32 | + try { |
| 33 | + const profile = createContainerProfile({ ...request, policy, network, |
| 34 | + command: createCodexCommand(policy, request.prompt), codexAuthFile: authFile, deferredOutput: true, |
| 35 | + timeoutMs: Math.min(60_000, remaining()) }); |
| 36 | + return startProfileInvocation(profile, { ...options, |
| 37 | + invocationBudget: remaining, |
| 38 | + decode: (current, _raw, maximum, timeoutMs, signal) => |
| 39 | + readCodexOutput(current.name, maximum, timeoutMs, signal) }); |
| 40 | + } catch (error) { |
| 41 | + if (error instanceof ProfileCreationCleanupError) { |
| 42 | + const retryCleanup = (networkTimeoutMs = 30_000) => { |
| 43 | + const failures: unknown[] = []; |
| 44 | + try { error.retryCleanup(); } catch (cleanupError) { failures.push(cleanupError); } |
| 45 | + try { removeVendorNetwork(network, networkTimeoutMs); } catch (cleanupError) { failures.push(cleanupError); } |
| 46 | + if (failures.length) throw new AggregateError(failures, 'Adapter setup cleanup did not settle.'); |
| 47 | + }; |
| 48 | + try { retryCleanup(Math.min(30_000, remaining())); } |
| 49 | + catch (cleanupError) { return retainSetupCleanup(request.invocation, () => retryCleanup(), |
| 50 | + error.startupError, cleanupError, 'profile and network cleanup'); } |
| 51 | + throw error.startupError; |
| 52 | + } |
| 53 | + try { removeVendorNetwork(network, Math.min(30_000, remaining())); } |
| 54 | + catch (cleanupError) { return retainNetworkCleanup(request.invocation, network, error, cleanupError); } |
| 55 | + throw error; |
| 56 | + } |
| 57 | +} |
0 commit comments