UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

531 lines (530 loc) • 20.5 kB
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 };