UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

988 lines (987 loc) 49.1 kB
import { F as resolveTimerTimeoutMs } from "./number-coercion-CLj0HTDM.js"; import { n as estimateStringChars } from "./cjk-chars-CGxY6W63.js"; import { c as isRecord } from "./record-coerce-DItp3I4t.js"; import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js"; import { s as sleepWithAbort } from "./src-BQ327IOM.js"; import { n as isAbortError } from "./abort-signal-D2k14JsD.js"; import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js"; import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js"; import { r as resolveRuntimeWorkerUrl } from "./runtime-worker-url-CpdriB1D.js"; import { t as retryAsync } from "./retry-DIUON3ys.js"; import { i as generateSecureToken } from "./secure-random-Ds4AFLgz.js"; import { m as resolvePluginControlPlaneFingerprint } from "./current-plugin-metadata-snapshot-CmSX4G3W.js"; import { c as normalizeProviderId } from "./model-ref-shared-Dz7QU0Lx.js"; import "./defaults-CdX9UGcX.js"; import "./backoff-BkMI1WEL.js"; import { f as resolveProviderRuntimePlugin } from "./provider-hook-runtime-DgKv_Z8O.js"; import { i as bindsClaudeThinkingPrefix } from "./anthropic-DfKaEfcc.js"; import "./src-g7CpceXJ.js"; import { mt as generateSummary } from "./sessions-BdNAJTEP.js"; import { a as hasNonzeroUsage, c as normalizeUsage, i as hasBillableUsage, t as deriveContextPromptTokens } from "./usage-CtmV8Xxq.js"; import { D as CompactionError, n as IMAGE_BLOCK_TOKENS, s as calculateContextTokens } from "./compaction-Gq_Mr3Zu.js"; import { a as BRANCH_SUMMARY_PREFIX, c as COMPACTION_SUMMARY_SUFFIX, l as bashExecutionToText, o as BRANCH_SUMMARY_SUFFIX, s as COMPACTION_SUMMARY_PREFIX } from "./session-CcAchsL_.js"; import "./session-manager-codec-DIXSw5vP.js"; import { t as resolveEffectiveCompactionReserveTokens } from "./agent-compaction-constants-CzVH4jGZ.js"; import { n as WorkerTaskPool, t as WorkerTaskError } from "./worker-task-pool-BNbf5LmH.js"; import { n as extractAssistantTextForPhase } from "./chat-message-content-Dws_XUEQ.js"; import { c as buildSummaryChunks, d as projectCompactionMessagesForPlanning, f as sanitizeCompactionMessages, l as computeAdaptiveChunkRatio, o as buildOversizedFallbackPlan, r as SAFETY_MARGIN, s as buildStageSplitPlan } from "./compaction-planning-DWqFldf6.js"; import "./model-selection-di2kjKCB.js"; import { s as isTimeoutError } from "./error-Bb_OF8ag.js"; import { o as isGoogleModelApi } from "./embedded-agent-helpers-D0eiT3IT.js"; import "./failover-error-BkNxlp8A.js"; import { t as estimateToolResultReductionPotential } from "./tool-result-truncation-myeYtakZ.js"; import { u as shouldDropClaudeThinkingBlocks } from "./provider-replay-helpers-36erCKTj.js"; import { o as extractAssistantVisibleText } from "./embedded-agent-utils-DLNh77Fj.js"; import { resolveCompactionReplayPressure } from "@openclaw/ai/transports"; //#region src/agents/compaction-planning-worker-runtime.ts const COMPACTION_PLANNING_WORKER_TIMEOUT_MS = 6e4; var CompactionPlanningWorkerError = class extends Error { constructor(message, code) { super(message); this.code = code; this.name = "CompactionPlanningWorkerError"; } }; const planningPool = new WorkerTaskPool({ workerUrl: resolveRuntimeWorkerUrl({ currentModuleUrl: import.meta.url, sourceWorkerName: "compaction-planning.worker", distWorkerPath: "agents/compaction-planning.worker.js" }) }); async function runCompactionPlanningWorker(params) { const pool = params.workerUrl ? new WorkerTaskPool({ workerUrl: params.workerUrl }) : planningPool; try { return await pool.run(params.input, { timeoutMs: resolveTimerTimeoutMs(params.timeoutMs, COMPACTION_PLANNING_WORKER_TIMEOUT_MS), signal: params.signal }); } catch (error) { if (error instanceof WorkerTaskError && error !== params.signal?.reason) throw new CompactionPlanningWorkerError(error.code === "timeout" ? "compaction planning worker timed out" : error.message, error.code); throw error; } finally { if (params.workerUrl) await pool.close(); } } //#endregion //#region src/agents/compaction-planning-worker.ts /** * Runs CPU-heavy compaction planning in a worker thread when histories are * large enough to risk starving the main event loop. */ const COMPACTION_PLANNING_WORKER_MIN_MESSAGES = 64; function restoreIndexedMessages(source, indexes) { return indexes.map((index) => { const message = source.at(index); if (!Number.isInteger(index) || index < 0 || !message) throw new CompactionPlanningWorkerError("compaction planning result contains an invalid message index", "failed"); return message; }); } async function runCompactionPlan(params) { params.signal?.throwIfAborted(); const messages = sanitizeCompactionMessages(params.input.messages); if (messages.length < COMPACTION_PLANNING_WORKER_MIN_MESSAGES) return params.fallback(params.input.messages); try { const value = await runCompactionPlanningWorker({ input: { ...params.input, messages: projectCompactionMessagesForPlanning(messages) }, signal: params.signal }); params.signal?.throwIfAborted(); if (value.kind !== params.input.kind) throw new CompactionPlanningWorkerError("unexpected compaction planning worker result", "failed"); return params.restore(value, messages); } catch (error) { if (error instanceof CompactionPlanningWorkerError && error.code === "unavailable") { params.signal?.throwIfAborted(); return params.fallback(messages); } throw error; } } /** Builds summary chunks, offloading large histories to the planning worker. */ async function buildSummaryChunksWithWorker(params) { const { signal, ...planningInput } = params; return runCompactionPlan({ input: { kind: "summaryChunks", ...planningInput }, signal, fallback: (messages) => buildSummaryChunks({ ...planningInput, messages }), restore: (value, messages) => value.chunkIndexes.map((indexes) => restoreIndexedMessages(messages, indexes)) }); } /** Builds an oversized-message fallback plan, using the worker when worthwhile. */ async function buildOversizedFallbackPlanWithWorker(params) { const { signal, ...planningInput } = params; return runCompactionPlan({ input: { kind: "oversizedFallback", ...planningInput }, signal, fallback: (messages) => buildOversizedFallbackPlan({ ...planningInput, messages }), restore: (value, messages) => ({ smallMessages: restoreIndexedMessages(messages, value.smallMessageIndexes), oversizedNotes: value.oversizedNotes }) }); } /** Builds a staged summarization split plan with worker fallback. */ async function buildStageSplitPlanWithWorker(params) { const { signal, ...planningInput } = params; return runCompactionPlan({ input: { kind: "stageSplit", ...planningInput }, signal, fallback: (messages) => buildStageSplitPlan({ ...planningInput, messages }), restore: (value, messages) => value.mode === "split" ? { mode: "split", chunks: value.chunkIndexes.map((indexes) => restoreIndexedMessages(messages, indexes)) } : { mode: "single" } }); } /** Computes the adaptive compaction chunk ratio with worker fallback. */ async function computeAdaptiveChunkRatioWithWorker(params) { const { signal, ...planningInput } = params; return runCompactionPlan({ input: { kind: "adaptiveChunkRatio", ...planningInput }, signal, fallback: () => computeAdaptiveChunkRatio(planningInput.messages, planningInput.contextWindow), restore: (value) => value.ratio }); } //#endregion //#region src/agents/compaction.ts const log = createSubsystemLogger("compaction"); const DEFAULT_SUMMARY_FALLBACK = "No prior history."; const MERGE_SUMMARIES_INSTRUCTIONS = [ "Merge these partial summaries into a single cohesive summary.", "", "MUST PRESERVE:", "- Active tasks and their current status (in-progress, blocked, pending)", "- Batch operation progress (e.g., '5/17 items completed')", "- The last thing the user requested and what was being done about it", "- Decisions made and their rationale", "- TODOs, open questions, and constraints", "- Any commitments or follow-ups promised", "", "PRIORITIZE recent context over older history. The agent needs to know", "what it was doing, not just what was discussed." ].join("\n"); const IDENTIFIER_PRESERVATION_INSTRUCTIONS = "Preserve all opaque identifiers exactly as written (no shortening or reconstruction), including UUIDs, hashes, IDs, hostnames, IPs, ports, URLs, and file names."; function resolveIdentifierPreservationInstructions(instructions) { if (instructions?.identifierPolicy === "off") return; return instructions?.identifierPolicy === "custom" ? instructions.identifierInstructions?.trim() || IDENTIFIER_PRESERVATION_INSTRUCTIONS : IDENTIFIER_PRESERVATION_INSTRUCTIONS; } /** Combines identifier-preservation and caller-provided compaction instructions. */ function buildCompactionSummarizationInstructions(customInstructions, instructions) { const custom = customInstructions?.trim(); const identifierPreservation = resolveIdentifierPreservationInstructions(instructions); if (!custom) return identifierPreservation; return identifierPreservation ? `${identifierPreservation}\n\nAdditional focus:\n${custom}` : `Additional focus:\n${custom}`; } async function summarizeChunks(params) { if (params.messages.length === 0) return params.previousSummary ?? DEFAULT_SUMMARY_FALLBACK; const chunks = await buildSummaryChunksWithWorker({ messages: params.messages, maxChunkTokens: params.maxChunkTokens, signal: params.signal }); let summary = params.previousSummary; const effectiveInstructions = buildCompactionSummarizationInstructions(params.customInstructions, params.summarizationInstructions); for (const [completedChunks, chunk] of chunks.entries()) try { summary = await retryAsync(() => generateSummary(chunk, params.model, params.reserveTokens, params.apiKey, params.headers, params.signal, effectiveInstructions, summary, params.thinkingLevel, params.streamFn, params.usageSink), { attempts: 3, minDelayMs: 500, maxDelayMs: 5e3, jitter: .2, label: "compaction/generateSummary", sleep: (ms) => sleepWithAbort(ms, params.signal), shouldRetry: (err) => !params.signal.aborted && (isAbortError(err) || !isTimeoutError(err)) }); } catch (err) { if (params.signal.aborted || !isAbortError(err) && isTimeoutError(err) || completedChunks === 0 || summary === void 0) throw err; log.warn("chunk summarization failed after retries; partial summary available", { err, completedChunks, totalChunks: chunks.length }); const partial = /* @__PURE__ */ new Error("partial summarization failure"); partial.partialSummary = `${summary}\n\n[Partial summary: chunks 1-${completedChunks} of ${chunks.length} were summarized. Chunks ${completedChunks + 1}-${chunks.length} could not be processed.]`; throw partial; } return summary ?? DEFAULT_SUMMARY_FALLBACK; } /** * Summarize with progressive fallback for handling oversized messages. * If full summarization fails, tries partial summarization excluding oversized messages. */ async function summarizeWithFallback(params) { const { messages, contextWindow } = params; if (messages.length === 0) return params.previousSummary ?? DEFAULT_SUMMARY_FALLBACK; let partialSummaryFallback; let lastError; try { return await summarizeChunks(params); } catch (err) { lastError = err; if (params.signal.aborted) throw lastError; log.warn(`Full summarization failed: ${formatErrorMessage(lastError)}`); partialSummaryFallback = lastError.partialSummary; } const { smallMessages, oversizedNotes } = await buildOversizedFallbackPlanWithWorker({ messages, contextWindow, signal: params.signal }); const oversizedSuffix = oversizedNotes.length > 0 ? `\n\n${oversizedNotes.join("\n")}` : ""; if (smallMessages.length > 0 && smallMessages.length !== messages.length) try { return await summarizeChunks({ ...params, messages: smallMessages }) + oversizedSuffix; } catch (partialError) { lastError = partialError; if (params.signal.aborted) throw lastError; log.warn(`Partial summarization also failed: ${formatErrorMessage(lastError)}`); const retryPartial = lastError.partialSummary; if (retryPartial) partialSummaryFallback = retryPartial + oversizedSuffix; } if (partialSummaryFallback) return partialSummaryFallback; throw new CompactionError("summarization_failed", `All summarization attempts failed for ${messages.length} messages. Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}`, lastError instanceof Error ? lastError : void 0); } /** Extracts a compact timestamp range from a chunk of messages for merge metadata. */ function extractChunkTimeRange(chunk) { let earliest = Number.POSITIVE_INFINITY; let latest = 0; for (const message of chunk) { const timestamp = message.timestamp; if (typeof timestamp !== "number" || timestamp <= 0 || !Number.isFinite(new Date(timestamp).getTime())) continue; earliest = Math.min(earliest, timestamp); latest = Math.max(latest, timestamp); } if (!Number.isFinite(earliest)) return ""; const format = (timestamp) => new Date(timestamp).toISOString().replace("T", " ").slice(0, 16); return ` [${earliest === latest ? format(earliest) : `${format(earliest)}${format(latest)}`} UTC]`; } /** Summarizes history in multiple stages when a single pass would be too large. */ async function summarizeInStages(params) { const { messages } = params; if (messages.length === 0) return await summarizeWithFallback(params); const plan = await buildStageSplitPlanWithWorker({ messages, maxChunkTokens: params.maxChunkTokens, parts: params.parts, minMessagesForSplit: params.minMessagesForSplit, signal: params.signal }); if (plan.mode === "single") return await summarizeWithFallback(params); const partialSummaries = []; for (const [index, chunk] of plan.chunks.entries()) try { const summary = await summarizeWithFallback({ ...params, messages: chunk, previousSummary: void 0 }); partialSummaries.push(summary); } catch (err) { if (err instanceof CompactionError) throw err; throw new CompactionError("summarization_failed", `Chunk ${index + 1} summarization failed: ${err instanceof Error ? err.message : String(err)}`, err instanceof Error ? err : void 0); } if (partialSummaries.length === 1) { const summary = partialSummaries.at(0); if (summary === void 0) throw new Error("Compaction summary plan produced no summary"); return summary; } const now = Date.now(); const summaryMessages = partialSummaries.map((summary, index) => { const chunk = plan.chunks.at(index); if (!chunk) throw new Error(`Compaction summary plan is missing chunk ${index}`); const timeRange = extractChunkTimeRange(chunk); return { role: "user", content: `${index === 0 ? `[Chunk 1 — oldest messages${timeRange}]` : index === partialSummaries.length - 1 ? `[Chunk ${partialSummaries.length} — most recent messages${timeRange}]` : `[Chunk ${index + 1}/${partialSummaries.length}${timeRange}]`}\n${summary}`, timestamp: now - (partialSummaries.length - 1 - index) }; }); const custom = params.customInstructions?.trim(); const mergeInstructions = custom ? `${MERGE_SUMMARIES_INSTRUCTIONS}\n\n${custom}` : MERGE_SUMMARIES_INSTRUCTIONS; return await summarizeWithFallback({ ...params, messages: summaryMessages, customInstructions: mergeInstructions }); } /** Resolves a positive context-window token count from model metadata. */ function resolveContextWindowTokens(model) { const effective = model?.contextTokens ?? model?.contextWindow; return Math.max(1, Math.floor(effective ?? 2e5)); } if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.compactionTestApi")] = { buildCompactionSummarizationInstructions, summarizeWithFallback }; //#endregion //#region src/agents/embedded-agent-runner/run/preemptive-compaction.ts /** * Estimates prompt pressure and decides pre-prompt compaction routing. */ const PREEMPTIVE_OVERFLOW_ERROR_TEXT = "Context overflow: prompt too large for the model (precheck)."; const ESTIMATED_CHARS_PER_TOKEN = 4; const TOOL_RESULT_CHARS_PER_TOKEN = 2; const JSON_PAYLOAD_CHARS_PER_TOKEN = 3; const MESSAGE_BOUNDARY_OVERHEAD_TOKENS = 12; const CONTENT_BLOCK_OVERHEAD_TOKENS = 6; const TRUNCATION_ROUTE_BUFFER_TOKENS = 512; function estimateStringTokenPressure(text, charsPerToken = ESTIMATED_CHARS_PER_TOKEN, mode = "general") { const estimatedTokens = Math.ceil(estimateStringChars(text) / charsPerToken); return mode === "tool-result" ? Math.max(Math.ceil(text.length / TOOL_RESULT_CHARS_PER_TOKEN), estimatedTokens) : estimatedTokens; } function estimateJsonPayloadTokenPressure(value, charsPerToken = JSON_PAYLOAD_CHARS_PER_TOKEN, mode = "general") { try { const serialized = JSON.stringify(value); return typeof serialized === "string" ? estimateStringTokenPressure(serialized, charsPerToken, mode) : 1; } catch { return 256; } } function estimateIdentifierTokenPressure(value, charsPerToken = JSON_PAYLOAD_CHARS_PER_TOKEN) { if (value == null) return 0; if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return estimateStringTokenPressure(String(value), charsPerToken); return estimateJsonPayloadTokenPressure(value, charsPerToken); } function estimateContentBlockTokenPressure(block, charsPerToken = ESTIMATED_CHARS_PER_TOKEN, mode = "general") { if (typeof block === "string") return estimateStringTokenPressure(block, charsPerToken, mode); if (!isRecord(block)) return estimateJsonPayloadTokenPressure(block, charsPerToken, mode); const type = block.type; const text = type === "text" ? block.text : type === "thinking" ? block.thinking : void 0; if (typeof text === "string") return CONTENT_BLOCK_OVERHEAD_TOKENS + estimateStringTokenPressure(text, charsPerToken, mode); if (type === "image") return IMAGE_BLOCK_TOKENS; return CONTENT_BLOCK_OVERHEAD_TOKENS + estimateJsonPayloadTokenPressure(block, charsPerToken, mode); } function estimateAssistantToolCallTokenPressure(block) { const args = block.arguments ?? block.input ?? block.args ?? {}; return CONTENT_BLOCK_OVERHEAD_TOKENS + estimateIdentifierTokenPressure(block.name, JSON_PAYLOAD_CHARS_PER_TOKEN) + estimateJsonPayloadTokenPressure(args, JSON_PAYLOAD_CHARS_PER_TOKEN); } function estimateContentTokenPressure(content, mode = "general") { if (typeof content === "string") return estimateStringTokenPressure(content, ESTIMATED_CHARS_PER_TOKEN, mode); if (Array.isArray(content)) return content.reduce((sum, block) => sum + estimateContentBlockTokenPressure(block, ESTIMATED_CHARS_PER_TOKEN, mode), 0); if (content !== void 0) return estimateJsonPayloadTokenPressure(content, mode === "tool-result" ? ESTIMATED_CHARS_PER_TOKEN : JSON_PAYLOAD_CHARS_PER_TOKEN, mode); return 0; } function estimateMessageTokenPressure(message) { if ("excludeFromContext" in message && message.excludeFromContext === true) return 0; const legacy = isRecord(message) ? message : {}; let tokens = MESSAGE_BOUNDARY_OVERHEAD_TOKENS; if (message.role === "toolResult" || legacy.role === "tool" || legacy.type === "toolResult") { const content = message.role === "toolResult" ? message.content : legacy.content; const toolName = message.role === "toolResult" ? message.toolName : legacy.toolName; tokens += estimateContentTokenPressure(content, "tool-result"); tokens += estimateIdentifierTokenPressure(toolName ?? legacy.tool_name); return tokens; } if (message.role === "bashExecution") { tokens += estimateStringTokenPressure(bashExecutionToText(message)); return tokens; } if (message.role === "branchSummary" || message.role === "compactionSummary") { const [prefix, suffix] = message.role === "branchSummary" ? [BRANCH_SUMMARY_PREFIX, BRANCH_SUMMARY_SUFFIX] : [COMPACTION_SUMMARY_PREFIX, COMPACTION_SUMMARY_SUFFIX]; return tokens + estimateStringTokenPressure(prefix + message.summary + suffix); } if (message.role === "assistant") { if (Array.isArray(message.content)) for (const block of message.content) { if (isRecord(block)) { const blockType = block.type; if (blockType === "toolCall" || blockType === "tool_use") { tokens += estimateAssistantToolCallTokenPressure(block); continue; } } tokens += estimateContentBlockTokenPressure(block); } else tokens += estimateContentTokenPressure(message.content); const toolCalls = legacy.toolCalls ?? legacy.tool_calls; if (Array.isArray(toolCalls)) for (const toolCall of toolCalls) tokens += isRecord(toolCall) ? estimateAssistantToolCallTokenPressure(toolCall) : estimateJsonPayloadTokenPressure(toolCall); return tokens; } tokens += estimateContentTokenPressure(legacy.content); return tokens; } /** * Estimates the prompt pressure at the LLM boundary from transcript messages, * optional system prompt, and current prompt text. The result intentionally * includes a safety margin because this path runs before provider tokenization. */ function estimateRenderedPromptTokens(params) { return (typeof params.systemPrompt === "string" && params.systemPrompt.trim().length > 0 ? MESSAGE_BOUNDARY_OVERHEAD_TOKENS + estimateStringTokenPressure(params.systemPrompt) : 0) + MESSAGE_BOUNDARY_OVERHEAD_TOKENS + estimateStringTokenPressure(params.prompt); } function isProviderContextUsageBarrier(message) { if (message.role !== "assistant" || !message.usage) return false; return message.api === "cli" && message.usage.contextUsage === void 0 || message.usage.contextUsage?.state === "unavailable" && calculateContextTokens(message.usage) === 0; } function resolveProviderContextBoundary(messages) { for (let index = messages.length - 1; index >= 0; index -= 1) { const message = messages[index]; if (message && isProviderContextUsageBarrier(message)) return; const contextUsage = message?.role === "assistant" ? message.usage?.contextUsage : void 0; if (contextUsage?.state === "available" && Number.isFinite(contextUsage.totalTokens) && contextUsage.totalTokens > 0) return { index, totalTokens: Math.ceil(contextUsage.totalTokens) }; } } function estimateTranscriptBoundaryTokenPressure(params) { const replay = params.replay ? resolveCompactionReplayPressure(params.messages, params.replay.model, params.replay, { text: estimateStringTokenPressure, image: () => IMAGE_BLOCK_TOKENS, json: estimateJsonPayloadTokenPressure }) : void 0; const messages = replay?.messages ?? params.messages; const boundary = resolveProviderContextBoundary(messages); const locallyEstimatedTokens = (boundary ? messages.slice(boundary.index + 1) : messages).reduce((sum, message) => sum + estimateMessageTokenPressure(message), estimateRenderedPromptTokens(params) + (boundary ? 0 : replay?.prefixTokens ?? 0)); return { estimatedPromptTokens: (boundary?.totalTokens ?? 0) + Math.ceil(locallyEstimatedTokens * SAFETY_MARGIN), source: boundary ? "provider_context_usage" : replay ? "provider_compaction_estimate" : "transcript_estimate", messages, hasCompactionReplay: Boolean(replay) }; } function estimateLlmBoundaryTokenPressure(params) { return estimateTranscriptBoundaryTokenPressure(params).estimatedPromptTokens; } /** Estimates only the rendered prompt/system portion when history has already been accounted for. */ function estimateRenderedLlmBoundaryTokenPressure(params) { return Math.max(0, Math.ceil(estimateRenderedPromptTokens(params) * SAFETY_MARGIN)); } function normalizeLlmBoundaryTokenPressure(pressure) { if (!pressure || !Number.isFinite(pressure.estimatedPromptTokens)) return; return { estimatedPromptTokens: Math.max(0, Math.ceil(pressure.estimatedPromptTokens)), source: pressure.source.trim() || "rendered_llm_boundary", ...typeof pressure.renderedChars === "number" && Number.isFinite(pressure.renderedChars) ? { renderedChars: Math.max(0, Math.ceil(pressure.renderedChars)) } : {} }; } /** * Decides whether a run should compact before submitting the prompt, and * whether reducible tool results can avoid or follow compaction. Rendered LLM * boundary pressure wins over local transcript estimates when supplied. */ function shouldPreemptivelyCompactBeforePrompt(params) { const llmBoundaryTokenPressure = normalizeLlmBoundaryTokenPressure(params.llmBoundaryTokenPressure); const transcriptTokenPressure = llmBoundaryTokenPressure && !params.replay ? void 0 : estimateTranscriptBoundaryTokenPressure({ messages: params.messages, systemPrompt: params.systemPrompt, prompt: params.prompt, replay: params.replay }); const boundaryPressure = transcriptTokenPressure?.hasCompactionReplay ? void 0 : llmBoundaryTokenPressure; const outgoingDecision = resolveCompactionPressureDecision({ messages: transcriptTokenPressure?.messages ?? params.messages, estimatedPromptTokens: boundaryPressure?.estimatedPromptTokens ?? transcriptTokenPressure?.estimatedPromptTokens ?? 0, source: boundaryPressure?.source ?? transcriptTokenPressure?.source ?? "transcript_estimate" }, params); let diagnosticDecision = outgoingDecision; if (params.unwindowedMessages && params.unwindowedMessages !== params.messages) { const unwindowedTokenPressure = estimateTranscriptBoundaryTokenPressure({ messages: params.unwindowedMessages, systemPrompt: params.systemPrompt, prompt: params.prompt }); if (unwindowedTokenPressure.estimatedPromptTokens > outgoingDecision.estimatedPromptTokens) diagnosticDecision = resolveCompactionPressureDecision({ ...unwindowedTokenPressure, source: `unwindowed_${unwindowedTokenPressure.source}` }, params); } return { ...diagnosticDecision, ...transcriptTokenPressure?.hasCompactionReplay ? { compactionReplay: outgoingDecision } : {} }; } function resolveCompactionPressureDecision(pressure, params) { const { estimatedPromptTokens } = pressure; const contextTokenBudget = Math.max(1, Math.floor(params.contextTokenBudget)); const effectiveReserveTokens = resolveEffectiveCompactionReserveTokens({ contextTokenBudget, reserveTokens: params.reserveTokens }); const promptBudgetBeforeReserve = Math.max(1, contextTokenBudget - effectiveReserveTokens); const overflowTokens = Math.max(0, estimatedPromptTokens - promptBudgetBeforeReserve); const toolResultPotential = estimateToolResultReductionPotential({ messages: pressure.messages, contextWindowTokens: params.contextTokenBudget, maxCharsOverride: params.toolResultMaxChars }); const overflowChars = overflowTokens * ESTIMATED_CHARS_PER_TOKEN; const truncateOnlyThresholdChars = Math.max(overflowChars + TRUNCATION_ROUTE_BUFFER_TOKENS * ESTIMATED_CHARS_PER_TOKEN, Math.ceil(overflowChars * 1.5)); const toolResultReducibleChars = toolResultPotential.maxReducibleChars; let route = "fits"; if (overflowTokens > 0) { if (toolResultReducibleChars <= 0) route = "compact_only"; else if (toolResultReducibleChars >= truncateOnlyThresholdChars) route = "truncate_tool_results_only"; else route = "compact_then_truncate"; } return { route, shouldCompact: route === "compact_only" || route === "compact_then_truncate", estimatedPromptTokens, pressureSource: pressure.source, promptBudgetBeforeReserve, overflowTokens, toolResultReducibleChars, effectiveReserveTokens }; } /** Formats the compact operator log line for one pre-prompt budget check. */ function formatPrePromptPrecheckLog(params) { const { result } = params; return `[context-overflow-precheck] pre-prompt check sessionKey=${params.sessionKey ?? params.sessionId ?? "unknown"} provider=${params.provider}/${params.modelId} route=${result.route} estimatedPromptTokens=${result.estimatedPromptTokens} pressureSource=${result.pressureSource ?? "unknown"} promptBudgetBeforeReserve=${result.promptBudgetBeforeReserve} overflowTokens=${result.overflowTokens} toolResultReducibleChars=${result.toolResultReducibleChars} reserveTokens=${params.reserveTokens} effectiveReserveTokens=${result.effectiveReserveTokens} contextTokenBudget=${params.contextTokenBudget} messages=${params.messageCount} unwindowedMessages=${params.unwindowedMessageCount ?? params.messageCount} sessionFile=${params.sessionFile}`; } /** Converts the pre-prompt decision into the persisted session context-budget status record. */ function buildPrePromptContextBudgetStatus(params) { const { result } = params; const remainingPromptBudgetTokens = Math.max(0, result.promptBudgetBeforeReserve - result.estimatedPromptTokens); return { schemaVersion: 1, source: "pre-prompt-estimate", updatedAt: params.now ?? Date.now(), provider: params.provider, model: params.modelId, route: result.route, shouldCompact: result.shouldCompact, estimatedPromptTokens: result.estimatedPromptTokens, contextTokenBudget: Math.max(1, Math.floor(params.contextTokenBudget)), promptBudgetBeforeReserve: result.promptBudgetBeforeReserve, reserveTokens: Math.max(0, Math.floor(params.reserveTokens)), effectiveReserveTokens: result.effectiveReserveTokens, remainingPromptBudgetTokens, overflowTokens: result.overflowTokens, toolResultReducibleChars: result.toolResultReducibleChars, messageCount: Math.max(0, Math.floor(params.messageCount)), unwindowedMessageCount: Math.max(0, Math.floor(params.unwindowedMessageCount ?? params.messageCount)), ...params.sessionId ? { sessionId: params.sessionId } : {} }; } //#endregion //#region src/agents/transcript-policy.ts /** * Transcript replay policy resolution. * Combines provider plugin replay hooks with core transport fallbacks so chat * history sanitization, tool IDs, thinking blocks, and turn validation align. */ const SIGNED_THINKING_PROVIDERS = /* @__PURE__ */ new Set([ "anthropic", "amazon-bedrock", "anthropic-vertex" ]); /** Return true when a provider family owns signed thinking blocks. */ function providerRequiresSignedThinking(provider) { return SIGNED_THINKING_PROVIDERS.has(normalizeProviderId(provider ?? "")); } /** Decide whether signed thinking can be replayed under the current provider policy. */ function shouldAllowProviderOwnedThinkingReplay(params) { const hasProviderOwnedSignedThinking = params.policy.preserveSignatures || providerRequiresSignedThinking(params.provider); return isAnthropicApi(params.modelApi) && params.policy.validateAnthropicTurns && hasProviderOwnedSignedThinking && !params.policy.dropThinkingBlocks; } /** * Bedrock Converse still requires strict role alternation, so only the direct * Messages API keeps consecutive user turns separate under append-only replay. */ function shouldMergeConsecutiveUserTurns(policy, modelApi) { return !(policy.appendOnlyRuntimeContext && modelApi === "anthropic-messages"); } const DEFAULT_TRANSCRIPT_POLICY = { sanitizeMode: "images-only", sanitizeToolCallIds: false, toolCallIdMode: void 0, duplicateToolCallIdStyle: void 0, preserveNativeAnthropicToolUseIds: false, repairToolUseResultPairing: true, preserveSignatures: false, appendOnlyRuntimeContext: false, sanitizeThoughtSignatures: void 0, dropThinkingBlocks: false, dropReasoningFromHistory: false, applyGoogleTurnOrdering: false, validateGeminiTurns: false, validateAnthropicTurns: false, allowSyntheticToolResults: false }; function isAnthropicApi(modelApi) { return modelApi === "anthropic-messages" || modelApi === "bedrock-converse-stream"; } function isOpenAiResponsesCompatibleApi(modelApi) { return modelApi === "openai-responses" || modelApi === "openai-chatgpt-responses" || modelApi === "azure-openai-responses"; } function isClaudeFamilyModelId(modelId) { const id = normalizeLowercaseStringOrEmpty(modelId); return /(?:^|[./:_-])claude(?:$|[./:_-])/.test(id); } function modelDisablesReasoningEffort(model) { return (model?.compat)?.supportsReasoningEffort === false; } function shouldPreserveReasoningContentReplay(params) { return params.model?.reasoning === true || requiresReasoningContentReplay(params.modelId); } /** * Provides a narrow replay-policy fallback for providers that do not have an * owning runtime plugin. * * This exists to preserve generic custom-provider behavior. Bundled providers * should express replay ownership through `buildReplayPolicy` instead. */ function buildUnownedProviderTransportReplayFallback(params) { const isGoogle = isGoogleModelApi(params.modelApi); const isAnthropic = isAnthropicApi(params.modelApi); const isStrictOpenAiCompatible = params.modelApi === "openai-completions"; const requiresOpenAiCompatibleToolIdSanitization = params.modelApi === "openai-completions" || params.modelApi === "openai-responses" || params.modelApi === "openai-chatgpt-responses" || params.modelApi === "azure-openai-responses"; if (!isGoogle && !isAnthropic && !isStrictOpenAiCompatible && !requiresOpenAiCompatibleToolIdSanitization) return; const modelId = normalizeLowercaseStringOrEmpty(params.modelId); const isClaudeOpenAiResponses = isOpenAiResponsesCompatibleApi(params.modelApi) ? isClaudeFamilyModelId(modelId) : false; return { ...isGoogle || isAnthropic ? { sanitizeMode: "full" } : {}, ...isGoogle || isAnthropic || requiresOpenAiCompatibleToolIdSanitization ? { sanitizeToolCallIds: true, toolCallIdMode: "strict" } : {}, ...isAnthropic ? { preserveSignatures: true, appendOnlyRuntimeContext: bindsClaudeThinkingPrefix({ id: modelId, params: params.model?.params }) } : {}, ...isGoogle ? { sanitizeThoughtSignatures: { allowBase64Only: true, includeCamelCase: true } } : {}, ...isAnthropic && shouldDropClaudeThinkingBlocks(modelId, params.model) ? { dropThinkingBlocks: true } : {}, ...isAnthropic && modelDisablesReasoningEffort(params.model) ? { dropThinkingBlocks: true } : {}, ...isStrictOpenAiCompatible ? { dropReasoningFromHistory: !shouldPreserveReasoningContentReplay(params) } : {}, ...isGoogle || isStrictOpenAiCompatible ? { applyAssistantFirstOrderingFix: true } : {}, ...isGoogle || isStrictOpenAiCompatible ? { validateGeminiTurns: true } : {}, ...isAnthropic || isStrictOpenAiCompatible || isClaudeOpenAiResponses ? { validateAnthropicTurns: true } : {}, ...isGoogle || isAnthropic || isOpenAiResponsesCompatibleApi(params.modelApi) ? { allowSyntheticToolResults: true } : {} }; } const REASONING_CONTENT_REPLAY_MODEL_IDS = /* @__PURE__ */ new Set([ "kimi-for-coding", "kimi-k2.5", "kimi-k2.6", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k3", "kimi-k2-thinking", "kimi-k2-thinking-turbo", "mimo-v2-pro", "mimo-v2-omni", "mimo-v2.5", "mimo-v2.5-pro", "mimo-v2.6-pro" ]); function requiresReasoningContentReplay(modelId) { const normalized = normalizeLowercaseStringOrEmpty(modelId); if (!normalized) return false; const parts = normalized.split("/").filter(Boolean); const finalPart = parts[parts.length - 1] ?? normalized; const candidates = [finalPart]; const colonParts = finalPart.split(":").filter(Boolean); if (colonParts.length > 1) candidates.push(colonParts[0] ?? "", colonParts[colonParts.length - 1] ?? ""); return candidates.some((candidate) => REASONING_CONTENT_REPLAY_MODEL_IDS.has(candidate)); } function mergeTranscriptPolicy(policy, basePolicy = DEFAULT_TRANSCRIPT_POLICY) { if (!policy) return basePolicy; return { ...basePolicy, ...policy.sanitizeMode != null ? { sanitizeMode: policy.sanitizeMode } : {}, ...typeof policy.sanitizeToolCallIds === "boolean" ? { sanitizeToolCallIds: policy.sanitizeToolCallIds } : {}, ...policy.toolCallIdMode ? { toolCallIdMode: policy.toolCallIdMode } : {}, ...policy.duplicateToolCallIdStyle ? { duplicateToolCallIdStyle: policy.duplicateToolCallIdStyle } : {}, ...typeof policy.preserveNativeAnthropicToolUseIds === "boolean" ? { preserveNativeAnthropicToolUseIds: policy.preserveNativeAnthropicToolUseIds } : {}, ...typeof policy.repairToolUseResultPairing === "boolean" ? { repairToolUseResultPairing: policy.repairToolUseResultPairing } : {}, ...typeof policy.preserveSignatures === "boolean" ? { preserveSignatures: policy.preserveSignatures } : {}, ...typeof policy.appendOnlyRuntimeContext === "boolean" ? { appendOnlyRuntimeContext: policy.appendOnlyRuntimeContext } : {}, ...policy.sanitizeThoughtSignatures ? { sanitizeThoughtSignatures: policy.sanitizeThoughtSignatures } : {}, ...typeof policy.dropThinkingBlocks === "boolean" ? { dropThinkingBlocks: policy.dropThinkingBlocks } : {}, ...typeof policy.dropReasoningFromHistory === "boolean" ? { dropReasoningFromHistory: policy.dropReasoningFromHistory } : {}, ...typeof policy.applyAssistantFirstOrderingFix === "boolean" ? { applyGoogleTurnOrdering: policy.applyAssistantFirstOrderingFix } : {}, ...typeof policy.validateGeminiTurns === "boolean" ? { validateGeminiTurns: policy.validateGeminiTurns } : {}, ...typeof policy.validateAnthropicTurns === "boolean" ? { validateAnthropicTurns: policy.validateAnthropicTurns } : {}, ...typeof policy.allowSyntheticToolResults === "boolean" ? { allowSyntheticToolResults: policy.allowSyntheticToolResults } : {} }; } const transcriptPolicyCache = /* @__PURE__ */ new WeakMap(); function canCacheTranscriptPolicy(params) { if (!params.config) return false; return !params.env || params.env === process.env; } function resolveTranscriptPolicyCacheKey(params) { return JSON.stringify({ provider: params.provider, modelApi: params.modelApi ?? "", modelId: params.modelId ?? "", canonicalModelId: typeof params.model?.params?.canonicalModelId === "string" ? params.model.params.canonicalModelId : "", dropsThinkingForReasoningCompat: modelDisablesReasoningEffort(params.model), preservesReasoningContentReplay: params.model?.reasoning === true, workspaceDir: params.workspaceDir ?? "", pluginControlPlane: resolvePluginControlPlaneFingerprint({ config: params.config, workspaceDir: params.workspaceDir, env: params.env }) }); } /** Resolve and cache the effective replay policy for a provider/model/config tuple. */ function resolveTranscriptPolicy(params) { const provider = normalizeProviderId(params.provider ?? ""); const cacheConfig = canCacheTranscriptPolicy(params) ? params.config : void 0; const cacheKey = cacheConfig ? resolveTranscriptPolicyCacheKey({ ...params, provider, config: cacheConfig }) : void 0; if (cacheConfig && cacheKey) { const cached = transcriptPolicyCache.get(cacheConfig)?.get(cacheKey); if (cached) return cached; } const runtimePlugin = params.runtimeHandle?.plugin ?? (provider ? resolveProviderRuntimePlugin({ provider, modelId: params.modelId, config: params.config, workspaceDir: params.workspaceDir, env: params.env }) : void 0); const context = { config: params.config, workspaceDir: params.workspaceDir, env: params.env, provider, modelId: params.modelId ?? "", modelApi: params.modelApi, model: params.model }; const buildReplayPolicy = runtimePlugin?.buildReplayPolicy; const policy = buildReplayPolicy ? mergeTranscriptPolicy(buildReplayPolicy(context) ?? void 0) : mergeTranscriptPolicy(buildUnownedProviderTransportReplayFallback({ modelApi: params.modelApi, modelId: params.modelId, model: params.model })); if (cacheConfig && cacheKey) { let configCache = transcriptPolicyCache.get(cacheConfig); if (!configCache) { configCache = /* @__PURE__ */ new Map(); transcriptPolicyCache.set(cacheConfig, configCache); } configCache.set(cacheKey, policy); } return policy; } //#endregion //#region src/agents/embedded-agent-runner/usage-accumulator.ts /** * Accumulates per-call token usage and monetary totals across embedded runs. */ const createUsageAccumulator = () => ({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cacheWrite1h: 0, reasoningTokens: 0, total: 0, cost: void 0, assistantTurns: 0 }); const mergeUsageIntoAccumulator = (target, usage) => { if (!hasBillableUsage(usage)) return; const callTotal = usage.total ?? (usage.input ?? 0) + (usage.output ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0); target.input += usage.input ?? 0; target.output += usage.output ?? 0; target.cacheRead += usage.cacheRead ?? 0; target.cacheWrite += usage.cacheWrite ?? 0; target.cacheWrite1h += usage.cacheWrite1h ?? 0; target.reasoningTokens += usage.reasoningTokens ?? 0; target.total += callTotal; target.cost = target.cost !== "unavailable" && usage.cost ? { total: (target.cost?.total ?? 0) + usage.cost.total } : "unavailable"; }; /** * Folds one attempt's run stats into the accumulator. Attempt cleanup clears * the per-attempt tool-search catalog, so retries would otherwise discard * earlier bridge counts and undercount the documented cumulative run totals. */ const mergeAttemptRunStatsIntoAccumulator = (target, attempt) => { target.assistantTurns += attempt.assistantTurns ?? 0; if (!attempt.bridgeCalls) return; const bridgeCalls = target.bridgeCalls ?? { search: 0, describe: 0, call: 0 }; bridgeCalls.search += attempt.bridgeCalls.search; bridgeCalls.describe += attempt.bridgeCalls.describe; bridgeCalls.call += attempt.bridgeCalls.call; target.bridgeCalls = bridgeCalls; }; const toNormalizedUsage = (usage) => { const hasUsage = usage.input > 0 || usage.output > 0 || usage.cacheRead > 0 || usage.cacheWrite > 0 || usage.reasoningTokens > 0 || usage.total > 0; const cost = usage.cost === "unavailable" ? void 0 : usage.cost; if (!hasUsage && !cost) return; const derivedTotal = usage.input + usage.output + usage.cacheRead + usage.cacheWrite; return { input: usage.input || void 0, output: usage.output || void 0, cacheRead: usage.cacheRead || void 0, cacheWrite: usage.cacheWrite || void 0, ...usage.cacheWrite1h > 0 ? { cacheWrite1h: usage.cacheWrite1h } : {}, ...usage.reasoningTokens > 0 ? { reasoningTokens: usage.reasoningTokens } : {}, total: usage.total || derivedTotal || void 0, ...cost ? { cost: { ...cost } } : {} }; }; //#endregion //#region src/agents/embedded-agent-runner/run/helpers.ts /** * Shared run helpers for retry limits, model reporting, and final text. */ const RUNTIME_AUTH_REFRESH_MARGIN_MS = 3e5; const RUNTIME_AUTH_REFRESH_RETRY_MS = 6e4; const RUNTIME_AUTH_REFRESH_MIN_DELAY_MS = 5e3; const DEFAULT_MAX_OVERLOAD_PROFILE_ROTATIONS = 1; const DEFAULT_MAX_RATE_LIMIT_PROFILE_ROTATIONS = 1; const MAX_TRANSIENT_RETRY_TIME_MS = 9e4; const TRANSIENT_RETRY_BASE_DELAY_MS = 1e3; const TRANSIENT_RETRY_MAX_DELAY_MS = 3e4; function resolveOverloadProfileRotationLimit() { return DEFAULT_MAX_OVERLOAD_PROFILE_ROTATIONS; } function resolveRateLimitProfileRotationLimit() { return DEFAULT_MAX_RATE_LIMIT_PROFILE_ROTATIONS; } /** Resolves jittered exponential backoff without exceeding the turn retry ceiling. */ function resolveTransientRetryDelayMs(params) { const remainingMs = MAX_TRANSIENT_RETRY_TIME_MS - Math.max(0, params.elapsedMs); if (remainingMs <= 0) return; const exponentialMs = Math.min(TRANSIENT_RETRY_MAX_DELAY_MS, TRANSIENT_RETRY_BASE_DELAY_MS * 2 ** Math.max(0, params.retryNumber - 1)); const jitteredMs = Math.min(TRANSIENT_RETRY_MAX_DELAY_MS, Math.round(exponentialMs * (.5 + Math.random()))); const retryAfterMs = Number.isFinite(params.retryAfterMs) ? Math.max(0, Math.ceil(params.retryAfterMs ?? 0)) : 0; const delayMs = Math.max(jitteredMs, retryAfterMs); return delayMs <= remainingMs ? delayMs : void 0; } const ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL = "ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL"; const ANTHROPIC_MAGIC_STRING_REPLACEMENT = "[redacted]"; function scrubAnthropicRefusalMagic(prompt) { if (!prompt.includes(ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL)) return prompt; return prompt.replaceAll(ANTHROPIC_MAGIC_STRING_TRIGGER_REFUSAL, ANTHROPIC_MAGIC_STRING_REPLACEMENT); } /** Anthropic's transport interprets this marker even for native-owned attempts. */ function resolveEmbeddedAttemptBasePrompt(params) { if (params.provider !== "anthropic") return params.prompt; return scrubAnthropicRefusalMagic(params.prompt); } function createRunRecoveryDiagId() { return `ovf-${Date.now().toString(36)}-${generateSecureToken(4)}`; } const BASE_RUN_RETRY_ITERATIONS = 24; const RUN_RETRY_ITERATIONS_PER_PROFILE = 8; const MIN_RUN_RETRY_ITERATIONS = 32; const MAX_RUN_RETRY_ITERATIONS = 160; function resolveMaxRunRetryIterations(profileCandidateCount) { const scaled = BASE_RUN_RETRY_ITERATIONS + Math.max(1, profileCandidateCount) * RUN_RETRY_ITERATIONS_PER_PROFILE; return Math.min(MAX_RUN_RETRY_ITERATIONS, Math.max(MIN_RUN_RETRY_ITERATIONS, scaled)); } function resolveActiveErrorContext(params) { return resolveReportedModelRef(params); } function isEmbeddedHarnessProvider(provider) { return provider.trim().toLowerCase() === "openclaw"; } function resolveReportedModelRef(params) { const assistantProvider = params.assistant?.provider?.trim(); const assistantModel = params.assistant?.model?.trim(); if (!assistantProvider) return { provider: params.provider, model: assistantModel || params.model }; if (isEmbeddedHarnessProvider(assistantProvider)) return { provider: params.provider, model: params.model }; return { provider: assistantProvider, model: assistantModel || params.model }; } function resolveLatestCallUsage(params) { const currentAttempt = params.currentAttemptCandidates.find(hasNonzeroUsage); const carriedUsage = hasNonzeroUsage(params.carriedUsage) ? params.carriedUsage : void 0; const transcriptFallback = hasNonzeroUsage(params.transcriptFallback) ? params.transcriptFallback : void 0; return { currentAttempt, latest: currentAttempt ?? carriedUsage ?? transcriptFallback }; } function normalizeAssistantUsageForContext(assistant) { if (assistant?.api === "cli" && assistant.usage && typeof assistant.usage === "object" && !Array.isArray(assistant.usage) && assistant.usage.contextUsage === void 0) return { contextUsage: { state: "unavailable" } }; return normalizeUsage(assistant?.usage); } function buildUsageAgentMetaFields(params) { const usage = toNormalizedUsage(params.usageAccumulator); const latestUsage = normalizeUsage(params.latestUsage); const lastCallUsage = hasNonzeroUsage(latestUsage) ? latestUsage : hasNonzeroUsage(params.lastRunPromptUsage) ? params.lastRunPromptUsage : void 0; return { usage, lastCallUsage, promptTokens: deriveContextPromptTokens({ lastCallUsage }), ...usage?.cost ? { costUsd: usage.cost.total } : {} }; } /** * Build agentMeta for error return paths, preserving accumulated usage so that * session totalTokens reflects the actual context size rather than going stale. * Without this, error returns omit usage and the session keeps whatever * totalTokens was set by the previous successful run. */ function buildErrorAgentMeta(params) { const usageMeta = buildUsageAgentMetaFields({ usageAccumulator: params.usageAccumulator, latestUsage: normalizeAssistantUsageForContext(params.currentAttemptAssistant), lastRunPromptUsage: params.lastRunPromptUsage }); return { sessionId: params.sessionId, ...params.sessionFile ? { sessionFile: params.sessionFile } : {}, provider: params.provider, model: params.model, ...params.credentialSource ? { credentialSource: params.credentialSource } : {}, ...params.contextTokens ? { contextTokens: params.contextTokens } : {}, ...params.contextTokens ? { contextTokensSource: "resolved" } : {}, ...usageMeta.usage ? { usage: usageMeta.usage } : {}, ...usageMeta.lastCallUsage ? { lastCallUsage: usageMeta.lastCallUsage } : {}, ...usageMeta.promptTokens ? { promptTokens: usageMeta.promptTokens } : {}, ...usageMeta.costUsd !== void 0 ? { costUsd: usageMeta.costUsd } : {} }; } function resolveFinalAssistantVisibleText(lastAssistant) { if (!lastAssistant) return; return extractAssistantVisibleText(lastAssistant).trim() || void 0; } function resolveFinalAssistantRawText(lastAssistant) { if (!lastAssistant) return; return (extractAssistantTextForPhase(lastAssistant, { phase: "final_answer" }) ?? extractAssistantTextForPhase(lastAssistant) ?? "").trim() || void 0; } //#endregion export { formatPrePromptPrecheckLog as A, resolveTranscriptPolicy as C, buildPrePromptContextBudgetStatus as D, PREEMPTIVE_OVERFLOW_ERROR_TEXT as E, resolveContextWindowTokens as M, summarizeInStages as N, estimateLlmBoundaryTokenPressure as O, computeAdaptiveChunkRatioWithWorker as P, providerRequiresSignedThinking as S, shouldMergeConsecutiveUserTurns as T, resolveTransientRetryDelayMs as _, buildUsageAgentMetaFields as a, mergeUsageIntoAccumulator as b, resolveActiveErrorContext as c, resolveFinalAssistantVisibleText as d, resolveLatestCallUsage as f, resolveReportedModelRef as g, resolveRateLimitProfileRotationLimit as h, buildErrorAgentMeta as i, shouldPreemptivelyCompactBeforePrompt as j, estimateRenderedLlmBoundaryTokenPressure as k, resolveEmbeddedAttemptBasePrompt as l, resolveOverloadProfileRotationLimit as m, RUNTIME_AUTH_REFRESH_MIN_DELAY_MS as n, createRunRecoveryDiagId as o, resolveMaxRunRetryIterations as p, RUNTIME_AUTH_REFRESH_RETRY_MS as r, normalizeAssistantUsageForContext as s, RUNTIME_AUTH_REFRESH_MARGIN_MS as t, resolveFinalAssistantRawText as u, createUsageAccumulator as v, shouldAllowProviderOwnedThinkingReplay as w, toNormalizedUsage as x, mergeAttemptRunStatsIntoAccumulator as y };