blob: f93a785d592e47d670766fd0d495dabf1966e4b7 [file]
import {
INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID,
preservesHostedExecutionEnvironment,
RUNTIME_HOST_PROTOCOL_VERSION,
type HostedExecutionProjection,
type HostedExecutionStartInput,
} from '../protocol/index.js';
import { connectOwnedRuntimeHost } from './connect-or-spawn.js';
import type { RuntimeHostConnection } from './connection.js';
import { configureHostedExecutionTarget } from './hosted-execution-target.js';
export interface RunHostedExecutionInput {
readonly rootPath: string;
readonly execution: HostedExecutionStartInput;
readonly baseUrl?: string;
readonly signal?: AbortSignal;
readonly hostSettlementTimeoutMs?: number;
}
interface RunHostedExecutionDependencies {
readonly connectOwnedRuntimeHost: typeof connectOwnedRuntimeHost;
}
const defaultDependencies: RunHostedExecutionDependencies = { connectOwnedRuntimeHost };
export async function runHostedExecution(
input: RunHostedExecutionInput,
): Promise<HostedExecutionProjection> {
return runHostedExecutionWithDependencies(input, defaultDependencies);
}
export async function runHostedExecutionWithDependencies(
input: RunHostedExecutionInput,
dependencies: RunHostedExecutionDependencies,
): Promise<HostedExecutionProjection> {
if (input.signal?.aborted) {
return indeterminate(input.execution.executionId, 'Hosted execution was cancelled');
}
const initial = await dependencies.connectOwnedRuntimeHost({
rootPath: input.rootPath,
surface: 'run',
protocol: {
min: RUNTIME_HOST_PROTOCOL_VERSION,
max: RUNTIME_HOST_PROTOCOL_VERSION,
},
compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID,
...(input.signal ? { signal: input.signal } : {}),
});
if (initial.kind !== 'connected') {
if (input.signal?.aborted) {
return indeterminate(input.execution.executionId, 'Hosted execution was cancelled');
}
const cause = initial.kind === 'failed' ? initial.reason : initial.kind;
return indeterminate(input.execution.executionId, `Runtime Host did not start: ${cause}`);
}
let connected: Extract<
Awaited<ReturnType<typeof connectOwnedRuntimeHost>>,
{ kind: 'connected' }
> = initial;
let projection: HostedExecutionProjection;
try {
input.signal?.throwIfAborted();
const target = input.execution.session.modelTarget;
if (target.kind === 'explicit') {
if (!input.baseUrl) throw new Error('Explicit model target requires baseUrl');
const changed = await configureHostedExecutionTarget(
connected.connection,
{
connectionSlug: target.connectionSlug,
model: target.model,
baseUrl: input.baseUrl,
},
input.signal,
);
if (changed) {
await connected.connection.close().catch(() => undefined);
if (!(await connected.host.settle(input.hostSettlementTimeoutMs ?? 15_000))) {
return indeterminate(input.execution.executionId, 'Runtime Host did not exit cleanly');
}
const reconnected = await dependencies.connectOwnedRuntimeHost({
rootPath: input.rootPath,
surface: 'run',
protocol: {
min: RUNTIME_HOST_PROTOCOL_VERSION,
max: RUNTIME_HOST_PROTOCOL_VERSION,
},
compositionId: INTERACTIVE_RUNTIME_HOST_COMPOSITION_ID,
...(input.signal ? { signal: input.signal } : {}),
});
if (reconnected.kind !== 'connected') {
if (input.signal?.aborted) {
return indeterminate(input.execution.executionId, 'Hosted execution was cancelled');
}
const cause = reconnected.kind === 'failed' ? reconnected.reason : reconnected.kind;
return indeterminate(input.execution.executionId, `Runtime Host did not start: ${cause}`);
}
connected = reconnected;
}
}
projection = await executeHostedExecution(connected.connection, input.execution, input.signal);
} catch {
projection = input.signal?.aborted
? indeterminate(input.execution.executionId, 'Hosted execution was cancelled')
: indeterminate(
input.execution.executionId,
'Runtime Host connection failed before execution settlement',
);
} finally {
await connected.connection.close().catch(() => undefined);
}
if (preservesHostedExecutionEnvironment(projection)) {
connected.host.releaseToEnvironment();
return projection;
}
const clean = await connected.host.settle(input.hostSettlementTimeoutMs ?? 15_000);
return clean
? projection
: indeterminate(input.execution.executionId, 'Runtime Host did not exit cleanly');
}
async function executeHostedExecution(
connection: Pick<RuntimeHostConnection, 'request'>,
execution: HostedExecutionStartInput,
signal: AbortSignal | undefined,
): Promise<HostedExecutionProjection> {
const cancel = () => {
void connection
.request('hosted.execution.cancel', { executionId: execution.executionId })
.catch(() => undefined);
};
signal?.addEventListener('abort', cancel, { once: true });
if (signal?.aborted) cancel();
try {
return await connection.request('hosted.execution.start', execution);
} finally {
signal?.removeEventListener('abort', cancel);
}
}
function indeterminate(executionId: string, failureReason: string): HostedExecutionProjection {
return { executionId, kind: 'indeterminate', failureReason };
}