Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions agents/adapters/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { readCapturedFile } from '../container/profile.ts';
import { createClaudeCommand, createPhasePolicy, MAX_COMMAND_SCHEMA_BYTES } from '../policy.ts';
import { launchInvocation } from './supervisor.ts';
import { setUpProfile } from './setup.ts';
import { assertAdapterRequest, createAdapterInvocationBudget, type AgentAdapterOptions, type AgentAdapterRequest } from './types.ts';
import { assertAdapterRequest, capAdapterInvocationBudget, createAdapterInvocationBudget,
type AgentAdapterOptions, type AgentAdapterRequest } from './types.ts';

/**
* Read Claude's `--output-format json` envelope. A result whose `subtype` is not `success`, or whose `is_error` is
Expand Down Expand Up @@ -46,7 +47,9 @@ export function startClaudeInvocation(request: AgentAdapterRequest,
oauthToken: string, options: AgentAdapterOptions = {}): InvocationHandle {
if (!oauthToken || oauthToken.includes('\0')) throw new Error('Claude OAuth token is malformed.');
const policy = createPhasePolicy(request.invocation);
const remaining = createAdapterInvocationBudget(request.invocation, options.timeoutMs);
const remaining = options.invocationBudget
? capAdapterInvocationBudget(options.invocationBudget, options.timeoutMs)
: createAdapterInvocationBudget(request.invocation, options.timeoutMs);
const structured = policy.phase === 'planning';
// Invalid input throws here, before anything is allocated; only Docker setup runs inside the handle.
const command = createClaudeCommand(policy, request.prompt,
Expand All @@ -55,5 +58,6 @@ export function startClaudeInvocation(request: AgentAdapterRequest,
return launchInvocation(request.invocation, remaining, (signal, start) => setUpProfile(request, remaining, signal,
network => ({ ...request, policy, network, command, claudeToken: oauthToken }),
profile => start(profile, { ...options, secrets: { CLAUDE_CODE_OAUTH_TOKEN: oauthToken },
processLifecycle: request.processLifecycle,
invocationBudget: remaining, decode: (_profile, raw) => parseClaudeOutput(raw, structured) })));
}
7 changes: 5 additions & 2 deletions agents/adapters/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { agentContainerId } from '../container/run.ts';
import { createCodexCommand, createPhasePolicy } from '../policy.ts';
import { launchInvocation, readBoundedContainerFile } from './supervisor.ts';
import { setUpProfile } from './setup.ts';
import { assertAdapterRequest, createAdapterInvocationBudget, type AgentAdapterOptions, type AgentAdapterRequest } from './types.ts';
import { assertAdapterRequest, capAdapterInvocationBudget, createAdapterInvocationBudget,
type AgentAdapterOptions, type AgentAdapterRequest } from './types.ts';

export const CODEX_OUTPUT_FILE = '/run/codeboost-output/final.txt';
// Read the output from the container this invocation created, by ID; a same-named replacement must not answer.
Expand All @@ -25,7 +26,9 @@ export function startCodexInvocation(request: AgentAdapterRequest,
authFile: string, options: AgentAdapterOptions = {}): InvocationHandle {
if (!authFile || authFile.includes('\0')) throw new Error('Codex auth path is malformed.');
const policy = createPhasePolicy(request.invocation);
const remaining = createAdapterInvocationBudget(request.invocation, options.timeoutMs);
const remaining = options.invocationBudget
? capAdapterInvocationBudget(options.invocationBudget, options.timeoutMs)
: createAdapterInvocationBudget(request.invocation, options.timeoutMs);
// Invalid input, including any phase Codex is refused (today every phase, #93), throws here, before anything is
// allocated; only Docker setup runs inside the handle.
const command = createCodexCommand(policy, request.prompt);
Expand Down
14 changes: 8 additions & 6 deletions agents/adapters/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,29 +20,31 @@ export async function setUpProfile(request: AgentAdapterRequest, remaining: () =
profileOptions: (network: VendorNetwork) => Omit<ProfileOptions, 'timeoutMs' | 'signal'>,
start: (profile: ContainerProfile) => InvocationHandle): Promise<InvocationHandle> {
const network = await createVendorNetwork(request.invocation, request.imageId, request.networkAllocationId,
Math.min(60_000, remaining()), signal);
Math.min(60_000, remaining()), signal, request.processLifecycle, remaining);
let profile: ContainerProfile;
try {
profile = await createContainerProfile({ ...profileOptions(network), timeoutMs: Math.min(60_000, remaining()),
signal });
invocationBudget: remaining, signal });
} catch (error) {
// Profile creation removes the network itself once it has claimed it; otherwise the network is still ours.
if (error instanceof ProfileCreationCleanupError) throw error;
try { await removeVendorNetwork(network, CLEANUP_TIMEOUT_MS); }
try { await removeVendorNetwork(network, CLEANUP_TIMEOUT_MS, request.processLifecycle); }
catch (cleanupError) {
throw new AdapterSetupCleanupError(error, cleanupError,
(budgetMs = CLEANUP_TIMEOUT_MS) => removeVendorNetwork(network, budgetMs), () => vendorNetworkResources(network));
(budgetMs = CLEANUP_TIMEOUT_MS) => removeVendorNetwork(network, budgetMs, request.processLifecycle),
() => vendorNetworkResources(network));
}
throw error;
}
// The profile owns the network and staging now. If the handoff itself throws, nothing else will release them:
// the launcher treats a failure without a handle as "nothing allocated", so release (or retain) them here.
try { return start(profile); }
catch (error) {
try { await disposeContainerProfile(profile, CLEANUP_TIMEOUT_MS); }
try { await disposeContainerProfile(profile, CLEANUP_TIMEOUT_MS, request.processLifecycle); }
catch (cleanupError) {
throw new AdapterSetupCleanupError(error, cleanupError,
(budgetMs = CLEANUP_TIMEOUT_MS) => disposeContainerProfile(profile, budgetMs), () => containerProfileResources(profile));
(budgetMs = CLEANUP_TIMEOUT_MS) => disposeContainerProfile(profile, budgetMs, request.processLifecycle),
() => containerProfileResources(profile));
}
throw error;
}
Expand Down
Loading
Loading