openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
290 lines (289 loc) • 12.7 kB
JavaScript
import { t as formatErrorMessage } from "./errors-BvOkmQM5.mjs";
import { r as getRuntimeConfig } from "./io.runtime-B134MOMz.mjs";
import { n as AgentHarnessSessionCleanupError, r as AgentHarnessSessionSupersededError, t as AgentHarnessPreflightError } from "./errors-BAipjK5G.mjs";
import { y as wrapNativeSessionDeletionMutation } from "./session-accessor.sqlite-deletion-CHbs1aOr.mjs";
import { t as abortAndDrainEmbeddedAgentRun } from "./runs-Qhulcs4s.mjs";
import { t as log } from "./logger-Wi08e7sJ.mjs";
import "./runtime-config-snapshot--IkldAgP.mjs";
import { m as toolPolicy, w as resolveAgentExecutorController } from "./agent-harness-runtime-C1yVhaNg.mjs";
import { l as combineNativeSessionBindingAuthority, n as prepareNativeSessionGenerationAuthority, u as createNativeSessionBindingAuthority } from "./agent-harness-session-runtime-Cu1e3v8y.mjs";
import { a as resolveApiKeyForProvider } from "./provider-auth-runtime-B1U4nl7B.mjs";
import { t as AgentsApiClient } from "./agentsapi-client-4xhmrPo7.mjs";
import { n as retireAgentsApiExecutor } from "./agentsapi-environment-BwO7ihw0.mjs";
import { t as requireAgentsApiSessionTarget } from "./agentsapi-target-ClP2ww2I.mjs";
import { t as runAgentsApiAttempt } from "./agentsapi-attempt-BruNxiPz.mjs";
import { t as migrateAgentsApiBinding } from "./agentsapi-binding-migration-BEGm1Lb2.mjs";
import { t as createAgentsApiBindings } from "./agentsapi-bindings-Bqrf6PmS.mjs";
import { t as runAgentsApiIsolatedCompletion } from "./agentsapi-isolated-completion-B2HPHUgo.mjs";
//#region extensions/agentsapi/agentsapi-harness.ts
const AGENTS_API_NATIVE_TOOL_REQUIREMENTS = [
"exec",
"process",
"read",
"write",
"edit",
"apply_patch",
"web_search"
];
/** Agents API owns native protocol; the host harness runtime owns coordination. */
function createAgentsApiHarness(runtime) {
let disposed = false;
let closing = false;
const runningSessions = /* @__PURE__ */ new Map();
const isolatedRuns = /* @__PURE__ */ new Map();
const promptHistories = /* @__PURE__ */ new WeakMap();
const preparedNativeCleanup = /* @__PURE__ */ new Map();
let bindings;
const getBindings = () => bindings ??= createAgentsApiBindings(runtime, {
settle: async (localSessionId, binding, assertCleanupCurrent, targetAgentId) => {
let prepared = preparedNativeCleanup.get(localSessionId);
if (prepared?.nativeSessionId !== binding.sessionId || prepared.configFingerprint !== binding.configFingerprint) {
assertCurrent();
assertCleanupCurrent();
const config = getRuntimeConfig();
const agentId = binding.executor?.agentId ?? targetAgentId;
if (!agentId) throw new Error("Agents API session cleanup requires its owning agent");
if (targetAgentId && targetAgentId !== agentId) throw new Error("Agents API session cleanup target differs from its owning agent");
const auth = await resolveApiKeyForProvider({
provider: "openai",
cfg: config,
agentDir: runtime.agent.resolveAgentDir(config, agentId),
workspaceDir: runtime.agent.resolveAgentWorkspaceDir(config, agentId),
signal: AbortSignal.timeout(3e4)
});
assertCurrent();
assertCleanupCurrent();
if (auth.mode !== "api-key" || !auth.apiKey?.trim()) throw new Error("Agents API session cleanup requires an OpenAI API key; restore API-key authentication and retry reset or deletion");
prepared = prepareNativeCleanup(localSessionId, binding, auth.apiKey);
}
try {
await prepared.settle(assertCleanupCurrent);
} catch (error) {
preparedNativeCleanup.delete(localSessionId);
throw error;
}
},
retire: async (_localSessionId, binding, assertCleanupCurrent) => {
await retireExecutor(binding, assertCleanupCurrent);
}
});
const assertCurrent = () => {
if (disposed) throw new Error("Agents API harness is disposed");
};
return {
id: "agentsapi",
label: "OpenAI Agents API (MVP)",
autoSelection: { providerIds: [] },
deliveryDefaults: { visibleReplies: "automatic" },
conversationToolPolicySupport: "exact",
conversationToolPolicyNativeTools: AGENTS_API_NATIVE_TOOL_REQUIREMENTS,
conversationToolPolicySafeDenyTools: [
"gateway",
"agents_list",
"openclaw",
"session_status",
"progress_card",
"automations",
"message",
"sessions_send",
"conversations_list",
"conversations_send",
"conversations_turn",
"subagents",
"sessions_list",
"sessions_history",
"sessions_search",
"sessions_spawn"
],
supports: (ctx) => {
if (ctx.provider !== "openai") return {
supported: false,
reason: "Agents API requires the OpenAI provider"
};
if (ctx.modelProvider?.preparedAuth?.requirement === "subscription" || ctx.modelProvider?.api && ctx.modelProvider.api !== "openai-responses" || ctx.modelProvider?.requestTransportOverrides === "present" || ctx.modelProvider?.baseUrl && ctx.modelProvider.baseUrl !== "https://api.openai.com/v1") return {
supported: false,
reason: "Agents API MVP requires the official API-key route"
};
return { supported: true };
},
runIsolatedCompletionV2: (params) => {
assertCurrent();
if (closing) throw new Error("Agents API harness is closing");
const controller = new AbortController();
const pending = runAgentsApiIsolatedCompletion({
...params,
abortSignal: params.abortSignal ? AbortSignal.any([params.abortSignal, controller.signal]) : controller.signal
}, assertCurrent);
isolatedRuns.set(controller, pending);
return pending.finally(() => isolatedRuns.delete(controller));
},
runAttempt: async (params) => {
assertCurrent();
if (closing) throw new Error("Agents API harness is closing");
const target = validateAgentsApiInput(params);
const authority = (await prepareNativeSessionGenerationAuthority({
target,
config: params.config,
storePath: target.storePath,
assertCurrent: () => {
assertCurrent();
params.hostCapabilities.assertActive();
},
createSupersededError: (sessionId) => new AgentHarnessSessionSupersededError(`Agents API session generation is no longer current: ${sessionId}`)
})).authority;
authority.assertLegacyCurrent();
runningSessions.set(params.sessionId, (runningSessions.get(params.sessionId) ?? 0) + 1);
try {
return await getBindings().withSession(params.sessionId, () => authority.assertLegacyCurrent(), (binding, bind, assertLeaseCurrent) => {
if (closing) throw new Error("Agents API harness is closing");
return runAgentsApiAttempt(params, binding, bind, combineNativeSessionBindingAuthority(authority, createNativeSessionBindingAuthority([], assertLeaseCurrent)).assertLegacyCurrent, () => {
assertCurrent();
assertLeaseCurrent();
}, target, () => runtime.config.current().plugins?.entries?.agentsapi?.config, promptHistories, (nativeBinding, apiKey) => prepareNativeCleanup(params.sessionId, nativeBinding, apiKey), (failedBinding) => retireExecutor(failedBinding, () => {
assertCurrent();
assertLeaseCurrent();
}));
}, (binding, assertMigrationCurrent) => migrateAgentsApiBinding(binding, params, () => runtime.config.current().plugins?.entries?.agentsapi?.config, assertMigrationCurrent));
} finally {
const count = runningSessions.get(params.sessionId) - 1;
if (count > 0) runningSessions.set(params.sessionId, count);
else runningSessions.delete(params.sessionId);
}
},
reset: async (params) => {
try {
assertCurrent();
if (params.sessionId) {
if (runningSessions.has(params.sessionId) || preparedNativeCleanup.has(params.sessionId)) {
await drainForExecutorCleanup(params.sessionId);
assertCurrent();
}
await getBindings().reset(params.sessionId, assertCurrent, params.agentId);
preparedNativeCleanup.delete(params.sessionId);
}
} catch (error) {
throw new AgentHarnessSessionCleanupError(formatErrorMessage(error), { cause: error });
}
},
withSessionDeletion,
withSessionContextReset: async (params, run) => {
params.assertCurrent();
assertCurrent();
const sessionId = getBindings().resolveContextResetSessionId(params.sessionId, params.previousSessionId);
return withSessionDeletion({
...params,
sessionId
}, run, true);
},
dispose: async () => {
closing = true;
for (const controller of isolatedRuns.keys()) controller.abort();
await Promise.allSettled(isolatedRuns.values());
await Promise.all([...runningSessions.keys()].map((sessionId) => abortAndDrainEmbeddedAgentRun({
sessionId,
settleMs: 95e3
})));
if (bindings) await bindings.withExclusiveMutationFence(async () => {
disposed = true;
});
else disposed = true;
preparedNativeCleanup.clear();
}
};
async function withSessionDeletion(params, run, retireAfterCommit = false) {
params.assertCurrent();
assertCurrent();
if (runningSessions.has(params.sessionId) || preparedNativeCleanup.has(params.sessionId)) {
await drainForExecutorCleanup(params.sessionId);
params.assertCurrent();
assertCurrent();
}
let committed = false;
try {
return await getBindings().withSessionDeletion({
...params,
assertCurrent: () => {
params.assertCurrent();
assertCurrent();
}
}, (mutation) => run(wrapNativeSessionDeletionMutation(mutation, {
assertCurrent: () => {
params.assertCurrent();
assertCurrent();
},
committed: () => {
committed = true;
},
rolledBack: () => {
committed = false;
}
})), retireAfterCommit);
} finally {
if (committed) preparedNativeCleanup.delete(params.sessionId);
}
}
async function retireExecutor(binding, assertCleanupCurrent) {
const executor = binding.executor;
if (!executor) return;
await attemptExecutorCleanup(() => retireAgentsApiExecutor(resolveAgentExecutorController(binding.executorControllerPluginId), executor, assertCleanupCurrent), assertCleanupCurrent);
}
async function attemptExecutorCleanup(run, assertCleanupCurrent) {
try {
assertCleanupCurrent();
await run();
assertCleanupCurrent();
} catch (error) {
assertCleanupCurrent();
log.warn("Agents API executor cleanup was not acknowledged", { error });
}
}
async function drainForExecutorCleanup(sessionId) {
const result = await abortAndDrainEmbeddedAgentRun({
sessionId,
settleMs: 95e3
});
assertCurrent();
if (result.aborted && !result.drained || runningSessions.has(sessionId)) throw new Error("Agents API session is still settling; retry reset or deletion after its active turn finishes");
}
function prepareNativeCleanup(localSessionId, binding, apiKey) {
assertCurrent();
const prepared = {
nativeSessionId: binding.sessionId,
configFingerprint: binding.configFingerprint,
settle: async (assertCleanupCurrent) => {
const assertCleanup = () => {
assertCurrent();
assertCleanupCurrent();
};
assertCleanup();
const client = new AgentsApiClient(apiKey, assertCleanup);
const signal = AbortSignal.timeout(3e4);
const session = await client.session(binding.sessionId, signal);
assertCleanup();
if (session.status !== "idle" && session.status !== "failed") {
await client.cancel(binding.sessionId, signal);
assertCleanup();
}
}
};
preparedNativeCleanup.set(localSessionId, prepared);
return prepared;
}
}
function validateAgentsApiInput(params) {
const runtimeToolAllowed = toolPolicy.createToolPolicyMatcher({ allow: params.toolsAllow });
if (params.disableTools || params.pluginHarnessToolPolicyRestricted || params.toolExecutionAllow !== void 0 || params.toolsAllow?.length === 0 || AGENTS_API_NATIVE_TOOL_REQUIREMENTS.some((name) => !runtimeToolAllowed(name))) throw new AgentHarnessPreflightError("Agents API cannot enforce this run's restrictions on native shell, file, or web-search tools.", {
scope: "harness",
userMessage: "Agents API cannot run with this chat's tool restrictions because it cannot enforce them on native tools. Choose a harness that supports these restrictions or update the tool settings."
});
const target = requireAgentsApiSessionTarget(params);
if (!params.resolvedApiKey) throw new Error("Agents API MVP requires an OpenAI API key");
if (params.sandbox) throw new AgentHarnessPreflightError("Agents API does not support Gateway sandbox placement.", {
scope: "harness",
userMessage: "Agents API cannot run in the configured Gateway sandbox. Choose a harness that supports Gateway sandbox placement before retrying."
});
if (params.contextEngine && params.contextEngine.info.id !== "legacy") throw new Error("Agents API MVP currently supports only the default legacy context engine");
return target;
}
//#endregion
export { createAgentsApiHarness as t };