openskynet / src /acp /control-plane /manager.runtime-controls.ts
Darochin's picture
Mirror OpenSkyNet workspace snapshot from Git HEAD
fc93158 verified
import { AcpRuntimeError, withAcpRuntimeErrorBoundary } from "../runtime/errors.js";
import type { AcpRuntime, AcpRuntimeCapabilities, AcpRuntimeHandle } from "../runtime/types.js";
import type { SessionAcpMeta } from "./manager.types.js";
import { createUnsupportedControlError } from "./manager.utils.js";
import type { CachedRuntimeState } from "./runtime-cache.js";
import {
buildRuntimeConfigOptionPairs,
buildRuntimeControlSignature,
normalizeText,
resolveRuntimeOptionsFromMeta,
} from "./runtime-options.js";
export async function resolveManagerRuntimeCapabilities(params: {
runtime: AcpRuntime;
handle: AcpRuntimeHandle;
}): Promise<AcpRuntimeCapabilities> {
let reported: AcpRuntimeCapabilities | undefined;
if (params.runtime.getCapabilities) {
reported = await withAcpRuntimeErrorBoundary({
run: async () => await params.runtime.getCapabilities!({ handle: params.handle }),
fallbackCode: "ACP_TURN_FAILED",
fallbackMessage: "Could not read ACP runtime capabilities.",
});
}
const controls = new Set<AcpRuntimeCapabilities["controls"][number]>(reported?.controls ?? []);
if (params.runtime.setMode) {
controls.add("session/set_mode");
}
if (params.runtime.setConfigOption) {
controls.add("session/set_config_option");
}
if (params.runtime.getStatus) {
controls.add("session/status");
}
const normalizedKeys = (reported?.configOptionKeys ?? [])
.map((entry) => normalizeText(entry))
.filter(Boolean) as string[];
return {
controls: [...controls].toSorted(),
...(normalizedKeys.length > 0 ? { configOptionKeys: normalizedKeys } : {}),
};
}
export async function applyManagerRuntimeControls(params: {
sessionKey: string;
runtime: AcpRuntime;
handle: AcpRuntimeHandle;
meta: SessionAcpMeta;
getCachedRuntimeState: (sessionKey: string) => CachedRuntimeState | null;
}): Promise<void> {
const options = resolveRuntimeOptionsFromMeta(params.meta);
const signature = buildRuntimeControlSignature(options);
const cached = params.getCachedRuntimeState(params.sessionKey);
if (cached?.appliedControlSignature === signature) {
return;
}
const capabilities = await resolveManagerRuntimeCapabilities({
runtime: params.runtime,
handle: params.handle,
});
const backend = params.handle.backend || params.meta.backend;
const runtimeMode = normalizeText(options.runtimeMode);
const configOptions = buildRuntimeConfigOptionPairs(options);
const advertisedKeys = new Set(
(capabilities.configOptionKeys ?? [])
.map((entry) => normalizeText(entry))
.filter(Boolean) as string[],
);
await withAcpRuntimeErrorBoundary({
run: async () => {
if (runtimeMode) {
if (!capabilities.controls.includes("session/set_mode") || !params.runtime.setMode) {
throw createUnsupportedControlError({
backend,
control: "session/set_mode",
});
}
await params.runtime.setMode({
handle: params.handle,
mode: runtimeMode,
});
}
if (configOptions.length > 0) {
if (
!capabilities.controls.includes("session/set_config_option") ||
!params.runtime.setConfigOption
) {
throw createUnsupportedControlError({
backend,
control: "session/set_config_option",
});
}
for (const [key, value] of configOptions) {
if (advertisedKeys.size > 0 && !advertisedKeys.has(key)) {
throw new AcpRuntimeError(
"ACP_BACKEND_UNSUPPORTED_CONTROL",
`ACP backend "${backend}" does not accept config key "${key}".`,
);
}
await params.runtime.setConfigOption({
handle: params.handle,
key,
value,
});
}
}
},
fallbackCode: "ACP_TURN_FAILED",
fallbackMessage: "Could not apply ACP runtime options before turn execution.",
});
if (cached) {
cached.appliedControlSignature = signature;
}
}