openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
531 lines (530 loc) • 20.5 kB
JavaScript
import { a as asOptionalRecord } from "./record-coerce-TIsquoVR.mjs";
import { r as racePromiseWithAbortSignal } from "./abort-signal-Z3A36sLL.mjs";
import { t as formatErrorMessage } from "./errors-BSdPMFao.mjs";
import { a as resolveAgentDir } from "./agent-scope-config-BU4qc9bA.mjs";
import "./string-coerce-runtime-B1b8UNzb.mjs";
import { N as setActiveEmbeddedRun, i as clearActiveEmbeddedRun } from "./runs-CDN_ch6v.mjs";
import { o as awaitAgentEndSideEffects, s as runAgentEndSideEffects } from "./context-engine-lifecycle-CxEzFgWh.mjs";
import { t as buildAgentHookContextChannelFields } from "./hook-agent-context-CHMCw5IP.mjs";
import { t as log } from "./logger-DaNnv73s.mjs";
import { n as buildCurrentInboundPrompt } from "./runtime-context-prompt-K3ClPtvW.mjs";
import { s as buildEmbeddedForegroundPromptContext } from "./settled-turn-finalization-result-D-Zh_c5X.mjs";
import { t as SessionManager } from "./session-manager-fQvjf7cC.mjs";
import { l as sanitizeToolArgs } from "./embedded-agent-tool-results-Diy6K9Rh.mjs";
import { o as runAgentHarnessLlmOutputHook } from "./lifecycle-hook-helpers-B52pEwr1.mjs";
import { m as resolveAgentHarnessBeforePromptBuildResult } from "./agent-harness-runtime-ADe6s8a3.mjs";
import { a as resolveAgentHarnessHistoryLimits, d as createAgentHarnessAttemptCancellation, f as createAgentHarnessAttemptDeadlineController, l as createAgentHarnessAttemptLifecycle, t as AgentHarnessProjectionSettlement, u as emitAgentHarnessAttemptEvent } from "./agent-harness-attempt-runtime-DclhpkiN.mjs";
import "./agent-sessions-6o3yE0qK.mjs";
import { t as AgentsApiClient } from "./agentsapi-client-BM1ZHCEo.mjs";
import { n as prepareInputs, r as uploadInputs, t as collectOutputs } from "./agentsapi-files-D0u6FYbF.mjs";
import { s as recordAgentsApiNativeToolTranscript } from "./agentsapi-transcript-Bg_UtQuK.mjs";
import { t as AgentsApiMessageProjection } from "./agentsapi-messages-CLE6Pt37.mjs";
import { n as buildAgentsApiTurnContext, t as buildAgentsApiInstructions } from "./agentsapi-prompt-BUOUqeOP.mjs";
import { t as resolveAgentsApiReasoningEffort } from "./agentsapi-reasoning-BG-0vejj.mjs";
import { t as createAgentsApiSession } from "./agentsapi-session-_woLFYHG.mjs";
import { t as buildAgentsApiToolSurface } from "./agentsapi-tools-A1IZDTno.mjs";
import { n as resolveAgentsApiEnvironment } from "./config-F4JNS3Fd.mjs";
import { createHash } from "node:crypto";
//#region extensions/agentsapi/agentsapi-attempt.ts
async function runAgentsApiAttempt(params, binding, bind, assertOwnerCurrent, assertHarnessCurrent, target, readPluginConfig) {
const startedAtMs = Date.now();
const cancellationState = {
explicitCancellationObserved: false,
terminalOutcomeFrozen: false,
sharedAbortAllowedAfterTerminalOutcome: false
};
const cancellation = createAgentHarnessAttemptCancellation({
upstreamSignal: params.abortSignal,
onAttemptAbort: params.onAttemptAbort,
state: cancellationState
});
const { controller } = cancellation;
const assertCurrent = () => {
assertOwnerCurrent();
controller.signal.throwIfAborted();
};
let finalizingProjection = false;
let finalizingProjectionSignal;
const assertProjectionCurrent = () => {
assertOwnerCurrent();
if (finalizingProjection) finalizingProjectionSignal?.throwIfAborted();
else controller.signal.throwIfAborted();
};
let lastToolError;
let toolTerminalObserved = false;
const observeToolTerminal = params.observeToolTerminal;
const runParams = observeToolTerminal ? {
...params,
observeToolTerminal: (observation) => {
assertProjectionCurrent();
const resolution = observeToolTerminal(observation);
assertProjectionCurrent();
toolTerminalObserved = true;
lastToolError = resolution.lastToolError;
return resolution;
}
} : params;
let timeout;
let settling = false;
let settlementDeadlineAtMs;
const deadlines = createAgentHarnessAttemptDeadlineController({
startedAtMs,
timeoutMs: params.timeoutMs,
settlementTimeoutMs: 3e4,
signal: controller.signal,
onDeadlineChanged: (deadline) => {
if (settling && deadline.kind === "bounded") settlementDeadlineAtMs = deadline.deadlineAtMs;
params.onAttemptDeadlineChanged?.(deadline);
},
onTimeout: (expired) => {
timeout = expired;
const error = /* @__PURE__ */ new Error(`Agents API ${expired.kind} timed out`);
params.onAttemptTimeout?.(error);
cancellation.abortExplicitly(error);
}
});
const beginSettlement = () => {
settling = true;
deadlines.beginSettlement(Date.now());
};
const emitEvent = (event) => emitAgentHarnessAttemptEvent(params, event, {
label: "Agents API",
log
});
const lifecycle = createAgentHarnessAttemptLifecycle({
attempt: params,
backend: "agentsapi",
startedAtMs,
state: {
lifecycleStarted: false,
lifecycleTerminalEmitted: false
},
emitEvent
});
const contextWindow = {
contextTokenBudget: params.contextWindowInfo?.tokens ?? params.contextTokenBudget,
contextWindowSource: params.contextWindowInfo?.source,
contextWindowReferenceTokens: params.contextWindowInfo?.referenceTokens
};
const hookContext = {
runId: params.runId,
agentId: target.agentId,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
workspaceDir: params.workspaceDir,
modelProviderId: params.provider,
modelId: params.model.id,
trigger: params.trigger,
inputProvenance: params.inputProvenance,
...buildAgentHookContextChannelFields(params),
channelContext: params.channelContext,
...contextWindow
};
let native;
let remoteSessionId = binding?.sessionId;
let terminal = { kind: "ok" };
let reply;
let projection;
let usageRecorded = false;
let projectionClosed = false;
const projectionSettlement = new AgentHarnessProjectionSettlement(runParams, () => {
if (projectionClosed || controller.signal.aborted) return false;
try {
assertOwnerCurrent();
return true;
} catch {
return false;
}
}, { label: "Agents API" });
let terminalTurnId;
const toolCleanups = [];
let toolSurface;
let outputMedia;
let startedToolCount = 0;
let completedToolCount = 0;
const handle = {
kind: "embedded",
toolAuthorityFingerprint: params.toolAuthorityFingerprint,
sourceReplyDeliveryMode: params.sourceReplyDeliveryMode,
taskSuggestionDeliveryMode: params.taskSuggestionDeliveryMode,
supportsTranscriptCommitWait: true,
runId: params.runId,
startedAtMs,
queueMessage: async (text, options) => {
assertCurrent();
if (!native?.isAvailable()) throw new Error("Agents API turn is not ready for steering");
if (options?.images?.length) throw new Error("Agents API MVP accepts text steering only");
await native.queueMessage(buildCurrentInboundPrompt({
context: options?.currentInboundContext,
prompt: text
}), async () => {
await options?.userTurnTranscriptRecorder?.persistApproved();
});
options?.userTurnTranscriptRecorder?.markSentToProvider?.();
},
isStreaming: () => native?.isAvailable() ?? false,
isStopped: () => controller.signal.aborted || (native?.isSettled() ?? false),
isAborted: () => controller.signal.aborted,
isCompacting: () => false,
abort: () => cancellation.abortExplicitly(/* @__PURE__ */ new Error("Agents API turn interrupted")),
cancel: () => cancellation.abortExplicitly(/* @__PURE__ */ new Error("Agents API turn interrupted"))
};
try {
params.replyOperation?.attachBackend(handle);
setActiveEmbeddedRun(params.sessionId, handle, params.sessionKey, params.sessionFile, params.agentId);
assertCurrent();
const environment = resolveAgentsApiEnvironment(readPluginConfig(), params.workspaceDir);
const surface = buildAgentsApiToolSurface(runParams, controller.signal, assertCurrent, (cleanup) => toolCleanups.push(cleanup));
toolSurface = surface;
const sessionIdentity = [
params.model.id,
params.resolvedApiKey,
...environment.type === "self_hosted" ? [environment] : []
];
const fingerprint = createHash("sha256").update(JSON.stringify(sessionIdentity)).digest("hex");
if (binding && binding.authFingerprint !== fingerprint) {
const toolsFingerprint = createHash("sha256").update(JSON.stringify([
params.model.id,
params.resolvedApiKey,
surface.declarations
])).digest("hex");
if (environment.type !== "openai_hosted" || binding.authFingerprint !== toolsFingerprint) throw new Error("Agents API model, credential, or environment changed; reset the OpenClaw session before continuing");
await bind({
sessionId: binding.sessionId,
authFingerprint: fingerprint
});
}
if (environment.type === "self_hosted" && params.media?.length) throw new Error("Agents API file transfers require an OpenAI-hosted environment");
const inputs = environment.type === "openai_hosted" ? await prepareInputs(params.media, params.workspaceDir, assertCurrent, controller.signal) : {
files: [],
mappingText: ""
};
const client = new AgentsApiClient(params.resolvedApiKey, assertOwnerCurrent);
const reasoningEffort = resolveAgentsApiReasoningEffort(params);
const creatingSession = !remoteSessionId;
const instructions = creatingSession ? await buildAgentsApiInstructions(params, surface.declarations, environment) : "";
assertCurrent();
const admittedMessage = params.userTurnTranscriptRecorder?.message ?? await params.userTurnTranscriptRecorder?.resolveMessage();
assertCurrent();
const promptBuild = await resolveAgentHarnessBeforePromptBuildResult({
prompt: params.prompt,
currentInboundContext: params.currentInboundContext,
currentUserMessage: admittedMessage ?? params.prompt,
developerInstructions: instructions,
messages: async () => {
assertCurrent();
const history = await SessionManager.openModelContextAsync(target, {
cwd: params.workspaceDir,
admission: params.userTurnTranscriptRecorder?.getAdmissionReceipt(),
signal: controller.signal,
limits: resolveAgentHarnessHistoryLimits(params.contextWindowInfo?.tokens ?? params.contextTokenBudget)
});
assertCurrent();
return history.buildSessionContext().messages;
},
ctx: hookContext,
bootstrapContextRunKind: params.bootstrapContextRunKind,
toolAuthority: {
fingerprint: params.toolAuthorityFingerprint,
activeToolNames: () => surface.declarations.map((tool) => tool.name),
assertActive: assertCurrent
}
});
assertCurrent();
if (!remoteSessionId) {
remoteSessionId = await client.create(controller.signal, promptBuild.developerInstructions, params.model.id, {
functions: surface.declarations,
files: inputs.files,
environment,
reasoning: {
effort: reasoningEffort,
...params.reasoningLevel && params.reasoningLevel !== "off" ? { summary: "auto" } : {}
}
});
assertCurrent();
await bind({
sessionId: remoteSessionId,
authFingerprint: fingerprint
});
} else {
await client.setReasoningEffort(remoteSessionId, reasoningEffort, controller.signal);
assertCurrent();
}
if (!creatingSession && inputs.files.length) await uploadInputs(client, remoteSessionId, inputs.files, assertCurrent, controller.signal);
projection = new AgentsApiMessageProjection(projectionSettlement.params, remoteSessionId, async (event) => {
assertCurrent();
await emitEvent(event);
assertCurrent();
}, assertProjectionCurrent);
reply = projection.reply;
native = createAgentsApiSession({
client,
cleanupClient: new AgentsApiClient(params.resolvedApiKey, assertHarnessCurrent),
sessionId: remoteSessionId,
signal: controller.signal,
assertCurrent,
onSettled: beginSettlement,
onReconcile: (turn, items) => projection.reconcile(turn, items, { presentation: !finalizingProjection }),
onUsageError: (error) => log.warn("Agents API token accounting unavailable", { error }),
onTranscriptOrderingGap: () => projection.reportTranscriptOrderingGap(),
onReconcileHistory: async (entries) => {
for (const { turn, items } of entries) for (const item of items) {
assertProjectionCurrent();
await recordAgentsApiNativeToolTranscript(runParams, remoteSessionId, turn.id, item, assertProjectionCurrent, Date.now, { enclosingStatus: turn.status });
assertProjectionCurrent();
}
},
executeFunction: async (call) => {
startedToolCount++;
await emitEvent({
stream: "tool",
data: {
phase: "start",
name: call.name,
toolCallId: call.call_id,
args: asOptionalRecord(sanitizeToolArgs(call.arguments))
}
});
assertCurrent();
const result = await surface.execute(call);
assertCurrent();
projection.recordGatewayTranscriptReceipt(call.turn_id, call.call_id);
return result;
},
onFunctionResult: async (call, result) => {
completedToolCount++;
await emitEvent({
stream: "tool",
data: {
phase: "result",
name: call.name,
toolCallId: call.call_id,
isError: !result.success,
result: { content: [{
type: "text",
text: result.success ? result.output : result.error
}] }
}
});
assertCurrent();
},
onEvent: async (event) => {
await projection.observe(event);
assertCurrent();
params.onRunProgress?.({
reason: event.type,
provider: "openai",
model: params.model.id,
backend: "agentsapi"
});
}
});
lifecycle.emitLifecycleStart({
provider: "openai",
model: params.model.id
});
const result = await native.run([
buildAgentsApiTurnContext(params, surface.declarations),
promptBuild.prompt,
inputs.mappingText
].filter(Boolean).join("\n\n"), async () => {
await params.userTurnTranscriptRecorder?.persistApproved();
}, () => params.userTurnTranscriptRecorder?.markSentToProvider?.());
terminalTurnId = result.turn.id;
const turns = await native.readUsageTurns();
assertCurrent();
projection.recordUsage(params.model, turns);
usageRecorded = true;
params.hostCapabilities.reportOutputTokens?.(reply.usage?.output ?? 0);
if (result.cancelled) terminal = {
kind: "aborted",
source: "runtime"
};
else if (result.terminatedByTool) {
await projection.commitUsage(result.turn);
assertCurrent();
} else {
const items = await client.items(remoteSessionId, result.turn.id, controller.signal);
assertCurrent();
try {
if (environment.type === "openai_hosted") outputMedia = await collectOutputs(client, remoteSessionId, result.turn.id, assertCurrent, controller.signal, params.hostCapabilities.prepareReplyMedia);
} finally {
await projection.commit(result.turn, items);
assertCurrent();
}
}
} catch (error) {
terminal = timeout ? {
kind: "timeout",
phase: "prompt",
source: "runtime",
aborted: true
} : params.abortSignal?.aborted ? {
kind: "aborted",
source: "external"
} : cancellationState.explicitCancellationObserved ? {
kind: "aborted",
source: "runtime"
} : {
kind: "failed",
source: "prompt",
error
};
if (terminal.kind === "failed") log.warn("Agents API session failed", { error });
} finally {
beginSettlement();
const cleanupMs = Math.max(0, Math.min(3e4, (settlementDeadlineAtMs ?? Date.now() + 3e4) - Date.now()));
const cleanupSignal = cleanupMs > 0 ? AbortSignal.timeout(cleanupMs) : AbortSignal.abort(/* @__PURE__ */ new Error("Agents API settlement timed out"));
try {
await native?.close();
} catch (error) {
terminal = {
kind: "failed",
source: "prompt",
error
};
}
try {
if (native && projection && !usageRecorded) {
const turns = await native.readUsageTurns();
assertHarnessCurrent();
projection.recordUsage(params.model, turns);
}
} catch (error) {
terminal = {
kind: "failed",
source: "prompt",
error
};
}
if ((controller.signal.aborted || terminal.kind !== "ok") && native && projection) {
let ownerCurrent = false;
try {
assertOwnerCurrent();
ownerCurrent = true;
} catch {}
if (ownerCurrent) {
finalizingProjection = true;
finalizingProjectionSignal = cleanupSignal;
try {
await native.reconcileAfterClose(cleanupSignal);
} catch (error) {
log.warn("Agents API terminal history reconciliation failed", { error });
} finally {
finalizingProjection = false;
finalizingProjectionSignal = void 0;
}
}
}
try {
await racePromiseWithAbortSignal(projectionSettlement.drain(), cleanupSignal);
} catch (error) {
if (!controller.signal.aborted) terminal = {
kind: "failed",
source: "prompt",
error
};
}
projectionClosed = true;
if (timeout) terminal = {
kind: "timeout",
phase: "prompt",
source: "runtime",
aborted: true
};
else if (terminal.kind === "ok" && controller.signal.aborted) terminal = {
kind: "aborted",
source: params.abortSignal?.aborted ? "external" : "runtime"
};
cancellation.freezeTerminalOutcome();
deadlines.dispose();
cancellation.dispose();
controller.abort();
for (const cleanup of toolCleanups.toReversed()) try {
await cleanup("Agents API attempt settled");
} catch (error) {
log.warn("Agents API tool cleanup failed", { error });
}
clearActiveEmbeddedRun(params.sessionId, handle, params.sessionKey, params.sessionFile);
lifecycle.emitLifecycleTerminal({ phase: terminal.kind === "failed" ? "error" : "end" });
}
const result = {
terminal,
sessionIdUsed: params.sessionId,
sessionFileUsed: params.sessionFile,
agentHarnessId: "agentsapi",
messagesSnapshot: SessionManager.open(target, params.workspaceDir).buildSessionContext().messages,
assistantTexts: reply?.lastAssistant?.content.filter((part) => part.type === "text").map((part) => part.text) ?? [],
lastAssistant: reply?.lastAssistant,
currentAttemptAssistant: reply?.lastAssistant,
currentAttemptCompletedAssistant: reply?.lastAssistant,
assistantTranscriptOwned: Boolean(reply?.lastAssistant),
assistantTranscriptIdempotencyKey: reply?.lastAssistant && terminalTurnId ? `agentsapi:${remoteSessionId}:${terminalTurnId}` : void 0,
toolMetas: [...projection?.toolMetas ?? [], ...toolSurface?.toolMetas ?? []],
lastToolError: toolTerminalObserved ? lastToolError : toolSurface?.lastToolError ?? projection?.lastToolError,
...toolSurface?.runtimeFacts,
didSendViaMessagingTool: false,
messagingToolSentTexts: [],
messagingToolSentMediaUrls: [],
messagingToolSentTargets: [],
...toolSurface?.delivery,
...outputMedia && {
hostOwnedToolMediaUrls: [...outputMedia],
toolMediaUrls: [.../* @__PURE__ */ new Set([...toolSurface?.delivery.toolMediaUrls ?? [], ...outputMedia])],
toolTrustedLocalMedia: outputMedia.length && !toolSurface?.delivery.toolMediaUrls?.length ? true : toolSurface?.delivery.toolTrustedLocalMedia
},
cloudCodeAssistFormatError: false,
attemptUsage: projection?.tokenUsage,
agentHarnessResultClassification: projection?.resultClassification,
replayMetadata: {
hadPotentialSideEffects: native?.wasSubmitted() ?? false,
replaySafe: !native?.wasSubmitted()
},
itemLifecycle: {
startedCount: startedToolCount + (projection?.itemLifecycle.startedCount ?? 0),
completedCount: completedToolCount + (projection?.itemLifecycle.completedCount ?? 0),
activeCount: Math.max(0, startedToolCount - completedToolCount) + (projection?.itemLifecycle.activeCount ?? 0)
}
};
assertHarnessCurrent();
runAgentHarnessLlmOutputHook({
event: {
runId: params.runId,
sessionId: params.sessionId,
provider: params.provider,
model: params.model.id,
resolvedRef: `${params.provider}/${params.model.id}`,
harnessId: "agentsapi",
prompt: params.prompt,
...contextWindow,
assistantTexts: result.assistantTexts,
lastAssistant: result.lastAssistant,
usage: result.attemptUsage
},
ctx: hookContext
});
const agentEnd = {
event: {
runId: params.runId,
messages: result.messagesSnapshot,
success: terminal.kind === "ok",
error: terminal.kind === "failed" ? formatErrorMessage(terminal.error) : void 0,
durationMs: Date.now() - startedAtMs
},
ctx: {
...hookContext,
config: params.config,
foregroundPromptContext: buildEmbeddedForegroundPromptContext({
...params,
agentId: target.agentId
}, params.agentDir ?? resolveAgentDir(params.config ?? {}, target.agentId)),
skillWorkshopAvailable: false,
compacted: false
}
};
if (!params.messageChannel && !params.messageProvider) await awaitAgentEndSideEffects(agentEnd);
else runAgentEndSideEffects(agentEnd);
return result;
}
//#endregion
export { runAgentsApiAttempt as t };