mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 02:10:23 +00:00
refactor: add provider replay runtime hook surfaces (#59143)
Merged via squash.
Prepared head SHA: 56b41e87a5
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
This commit is contained in:
@@ -37,6 +37,9 @@ vi.mock("../plugins/provider-runtime.js", () => ({
|
||||
dropThinkingBlockModelHints: ["claude"],
|
||||
}
|
||||
: undefined,
|
||||
sanitizeProviderReplayHistoryWithPlugin: vi.fn(async ({ messages }) => messages),
|
||||
resolveProviderReplayPolicyWithPlugin: vi.fn(() => undefined),
|
||||
validateProviderReplayTurnsWithPlugin: vi.fn(() => undefined),
|
||||
}));
|
||||
|
||||
let sanitizeSessionHistory: SanitizeSessionHistoryFn;
|
||||
|
||||
@@ -54,11 +54,7 @@ import { ensureOpenClawModelsJson } from "../models-config.js";
|
||||
import { resolveOwnerDisplaySetting } from "../owner-display.js";
|
||||
import { createBundleLspToolRuntime } from "../pi-bundle-lsp-runtime.js";
|
||||
import { createBundleMcpToolRuntime } from "../pi-bundle-mcp-tools.js";
|
||||
import {
|
||||
ensureSessionHeader,
|
||||
validateAnthropicTurns,
|
||||
validateGeminiTurns,
|
||||
} from "../pi-embedded-helpers.js";
|
||||
import { ensureSessionHeader } from "../pi-embedded-helpers.js";
|
||||
import {
|
||||
consumeCompactionSafeguardCancelReason,
|
||||
setCompactionSafeguardCancelReason,
|
||||
@@ -106,6 +102,7 @@ import {
|
||||
logToolSchemasForGoogle,
|
||||
sanitizeSessionHistory,
|
||||
sanitizeToolsForGoogle,
|
||||
validateReplayTurns,
|
||||
} from "./google.js";
|
||||
import { getDmHistoryLimitFromSessionKey, limitHistoryTurns } from "./history.js";
|
||||
import { resolveGlobalLane, resolveSessionLane } from "./lanes.js";
|
||||
@@ -488,6 +485,12 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
const tools = sanitizeToolsForGoogle({
|
||||
tools: toolsEnabled ? toolsRaw : [],
|
||||
provider,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
modelId,
|
||||
modelApi: model.api,
|
||||
model,
|
||||
});
|
||||
const bundleMcpRuntime = toolsEnabled
|
||||
? await createBundleMcpToolRuntime({
|
||||
@@ -512,7 +515,16 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
...(bundleLspRuntime?.tools ?? []),
|
||||
];
|
||||
const allowedToolNames = collectAllowedToolNames({ tools: effectiveTools });
|
||||
logToolSchemasForGoogle({ tools: effectiveTools, provider });
|
||||
logToolSchemasForGoogle({
|
||||
tools: effectiveTools,
|
||||
provider,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
modelId,
|
||||
modelApi: model.api,
|
||||
model,
|
||||
});
|
||||
const machineName = await getMachineDisplayName();
|
||||
const runtimeChannel = normalizeMessageChannel(params.messageChannel ?? params.messageProvider);
|
||||
let runtimeCapabilities = runtimeChannel
|
||||
@@ -593,7 +605,14 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
channelActions,
|
||||
};
|
||||
const sandboxInfo = buildEmbeddedSandboxInfo(sandbox, params.bashElevated);
|
||||
const reasoningTagHint = isReasoningTagProvider(provider);
|
||||
const reasoningTagHint = isReasoningTagProvider(provider, {
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
modelId,
|
||||
modelApi: model.api,
|
||||
model,
|
||||
});
|
||||
const userTimezone = resolveUserTimezone(params.config?.agents?.defaults?.userTimezone);
|
||||
const userTimeFormat = resolveUserTimeFormat(params.config?.agents?.defaults?.timeFormat);
|
||||
const userTime = formatUserTime(new Date(), userTimezone, userTimeFormat);
|
||||
@@ -658,6 +677,10 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
modelApi: model.api,
|
||||
provider,
|
||||
modelId,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
model,
|
||||
});
|
||||
const sessionManager = guardSessionManager(SessionManager.open(params.sessionFile), {
|
||||
agentId: sessionAgentId,
|
||||
@@ -731,16 +754,25 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
provider,
|
||||
allowedToolNames,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
model,
|
||||
sessionManager,
|
||||
sessionId: params.sessionId,
|
||||
policy: transcriptPolicy,
|
||||
});
|
||||
const validatedGemini = transcriptPolicy.validateGeminiTurns
|
||||
? validateGeminiTurns(prior)
|
||||
: prior;
|
||||
const validated = transcriptPolicy.validateAnthropicTurns
|
||||
? validateAnthropicTurns(validatedGemini)
|
||||
: validatedGemini;
|
||||
const validated = await validateReplayTurns({
|
||||
messages: prior,
|
||||
modelApi: model.api,
|
||||
modelId,
|
||||
provider,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
model,
|
||||
sessionId: params.sessionId,
|
||||
policy: transcriptPolicy,
|
||||
});
|
||||
// Apply validated transcript to the live session even when no history limit is configured,
|
||||
// so compaction and hook metrics are based on the same message set.
|
||||
session.agent.replaceMessages(validated);
|
||||
|
||||
@@ -4,6 +4,12 @@ import type { SessionManager } from "@mariozechner/pi-coding-agent";
|
||||
import type { TSchema } from "@sinclair/typebox";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { registerUnhandledRejectionHandler } from "../../infra/unhandled-rejections.js";
|
||||
import {
|
||||
normalizeProviderToolSchemasWithPlugin,
|
||||
sanitizeProviderReplayHistoryWithPlugin,
|
||||
validateProviderReplayTurnsWithPlugin,
|
||||
} from "../../plugins/provider-runtime.js";
|
||||
import type { ProviderRuntimeModel } from "../../plugins/types.js";
|
||||
import {
|
||||
hasInterSessionUserProvenance,
|
||||
normalizeInputProvenance,
|
||||
@@ -16,6 +22,8 @@ import {
|
||||
isGoogleModelApi,
|
||||
sanitizeGoogleTurnOrdering,
|
||||
sanitizeSessionMessagesImages,
|
||||
validateAnthropicTurns,
|
||||
validateGeminiTurns,
|
||||
} from "../pi-embedded-helpers.js";
|
||||
import { cleanToolSchemaForGemini } from "../pi-tools.schema.js";
|
||||
import {
|
||||
@@ -23,6 +31,7 @@ import {
|
||||
stripToolResultDetails,
|
||||
sanitizeToolUseResultPairing,
|
||||
} from "../session-transcript-repair.js";
|
||||
import type { AnyAgentTool } from "../tools/common.js";
|
||||
import type { TranscriptPolicy } from "../transcript-policy.js";
|
||||
import { resolveTranscriptPolicy } from "../transcript-policy.js";
|
||||
import {
|
||||
@@ -407,12 +416,39 @@ export function sanitizeToolsForGoogle<
|
||||
>(params: {
|
||||
tools: AgentTool<TSchemaType, TResult>[];
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
modelId?: string;
|
||||
modelApi?: string | null;
|
||||
model?: ProviderRuntimeModel;
|
||||
}): AgentTool<TSchemaType, TResult>[] {
|
||||
const provider = params.provider.trim();
|
||||
const pluginNormalized = normalizeProviderToolSchemasWithPlugin({
|
||||
provider,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
context: {
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
provider,
|
||||
modelId: params.modelId,
|
||||
modelApi: params.modelApi,
|
||||
model: params.model,
|
||||
tools: params.tools as unknown as AnyAgentTool[],
|
||||
},
|
||||
});
|
||||
if (Array.isArray(pluginNormalized)) {
|
||||
return pluginNormalized as AgentTool<TSchemaType, TResult>[];
|
||||
}
|
||||
|
||||
// Cloud Code Assist uses the OpenAPI 3.03 `parameters` field for both Gemini
|
||||
// AND Claude models. This field does not support JSON Schema keywords such as
|
||||
// patternProperties, additionalProperties, $ref, etc. We must clean schemas
|
||||
// for every provider that routes through this path.
|
||||
if (params.provider !== "google-gemini-cli") {
|
||||
if (provider !== "google-gemini-cli") {
|
||||
return params.tools;
|
||||
}
|
||||
return params.tools.map((tool) => {
|
||||
@@ -428,8 +464,17 @@ export function sanitizeToolsForGoogle<
|
||||
});
|
||||
}
|
||||
|
||||
export function logToolSchemasForGoogle(params: { tools: AgentTool[]; provider: string }) {
|
||||
if (params.provider !== "google-gemini-cli") {
|
||||
export function logToolSchemasForGoogle(params: {
|
||||
tools: AgentTool[];
|
||||
provider: string;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
modelId?: string;
|
||||
modelApi?: string | null;
|
||||
model?: ProviderRuntimeModel;
|
||||
}) {
|
||||
if (params.provider.trim() !== "google-gemini-cli") {
|
||||
return;
|
||||
}
|
||||
const toolNames = params.tools.map((tool, index) => `${index}:${tool.name}`);
|
||||
@@ -581,6 +626,9 @@ export async function sanitizeSessionHistory(params: {
|
||||
provider?: string;
|
||||
allowedToolNames?: Iterable<string>;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
model?: ProviderRuntimeModel;
|
||||
sessionManager: SessionManager;
|
||||
sessionId: string;
|
||||
policy?: TranscriptPolicy;
|
||||
@@ -592,6 +640,10 @@ export async function sanitizeSessionHistory(params: {
|
||||
modelApi: params.modelApi,
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
model: params.model,
|
||||
});
|
||||
const withInterSessionMarkers = annotateInterSessionUserMessages(params.messages);
|
||||
const canonicalizedAssistantHistory = canonicalizeAssistantHistoryMessages({
|
||||
@@ -645,6 +697,29 @@ export async function sanitizeSessionHistory(params: {
|
||||
downgradeOpenAIReasoningBlocks(sanitizedCompactionUsage),
|
||||
)
|
||||
: sanitizedCompactionUsage;
|
||||
const provider = params.provider?.trim();
|
||||
const providerSanitized =
|
||||
provider && provider.length > 0
|
||||
? await sanitizeProviderReplayHistoryWithPlugin({
|
||||
provider,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
context: {
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
provider,
|
||||
modelId: params.modelId,
|
||||
modelApi: params.modelApi,
|
||||
model: params.model,
|
||||
sessionId: params.sessionId,
|
||||
messages: sanitizedOpenAI,
|
||||
allowedToolNames: params.allowedToolNames,
|
||||
},
|
||||
})
|
||||
: undefined;
|
||||
const sanitizedWithProvider = providerSanitized ?? sanitizedOpenAI;
|
||||
|
||||
if (hasSnapshot && (!priorSnapshot || modelChanged)) {
|
||||
appendModelSnapshot(params.sessionManager, {
|
||||
@@ -656,13 +731,13 @@ export async function sanitizeSessionHistory(params: {
|
||||
}
|
||||
|
||||
if (!policy.applyGoogleTurnOrdering) {
|
||||
return sanitizedOpenAI;
|
||||
return sanitizedWithProvider;
|
||||
}
|
||||
|
||||
// Google models use the full wrapper with logging and session markers.
|
||||
if (isGoogleModelApi(params.modelApi)) {
|
||||
return applyGoogleTurnOrderingFix({
|
||||
messages: sanitizedOpenAI,
|
||||
messages: sanitizedWithProvider,
|
||||
modelApi: params.modelApi,
|
||||
sessionManager: params.sessionManager,
|
||||
sessionId: params.sessionId,
|
||||
@@ -673,5 +748,58 @@ export async function sanitizeSessionHistory(params: {
|
||||
// conversations that start with an assistant turn (e.g. delivery-mirror
|
||||
// messages after /new). Apply the same ordering fix without the
|
||||
// Google-specific session markers. See #38962.
|
||||
return sanitizeGoogleTurnOrdering(sanitizedOpenAI);
|
||||
return sanitizeGoogleTurnOrdering(sanitizedWithProvider);
|
||||
}
|
||||
|
||||
export async function validateReplayTurns(params: {
|
||||
messages: AgentMessage[];
|
||||
modelApi?: string | null;
|
||||
modelId?: string;
|
||||
provider?: string;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
model?: ProviderRuntimeModel;
|
||||
sessionId?: string;
|
||||
policy?: TranscriptPolicy;
|
||||
}): Promise<AgentMessage[]> {
|
||||
const policy =
|
||||
params.policy ??
|
||||
resolveTranscriptPolicy({
|
||||
modelApi: params.modelApi,
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
model: params.model,
|
||||
});
|
||||
const provider = params.provider?.trim();
|
||||
if (provider) {
|
||||
const providerValidated = await validateProviderReplayTurnsWithPlugin({
|
||||
provider,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
context: {
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
provider,
|
||||
modelId: params.modelId,
|
||||
modelApi: params.modelApi,
|
||||
model: params.model,
|
||||
sessionId: params.sessionId,
|
||||
messages: params.messages,
|
||||
},
|
||||
});
|
||||
if (providerValidated) {
|
||||
return providerValidated;
|
||||
}
|
||||
}
|
||||
|
||||
const validatedGemini = policy.validateGeminiTurns
|
||||
? validateGeminiTurns(params.messages)
|
||||
: params.messages;
|
||||
return policy.validateAnthropicTurns ? validateAnthropicTurns(validatedGemini) : validatedGemini;
|
||||
}
|
||||
|
||||
@@ -68,8 +68,6 @@ import {
|
||||
resolveBootstrapMaxChars,
|
||||
resolveBootstrapPromptTruncationWarningMode,
|
||||
resolveBootstrapTotalMaxChars,
|
||||
validateAnthropicTurns,
|
||||
validateGeminiTurns,
|
||||
} from "../../pi-embedded-helpers.js";
|
||||
import { subscribeEmbeddedPiSession } from "../../pi-embedded-subscribe.js";
|
||||
import { createPreparedEmbeddedPiSettingsManager } from "../../pi-project-settings.js";
|
||||
@@ -107,6 +105,7 @@ import {
|
||||
logToolSchemasForGoogle,
|
||||
sanitizeSessionHistory,
|
||||
sanitizeToolsForGoogle,
|
||||
validateReplayTurns,
|
||||
} from "../google.js";
|
||||
import { getDmHistoryLimitFromSessionKey, limitHistoryTurns } from "../history.js";
|
||||
import { log } from "../logger.js";
|
||||
@@ -419,64 +418,64 @@ export async function runEmbeddedAttempt(
|
||||
? []
|
||||
: (() => {
|
||||
const allTools = createOpenClawCodingTools({
|
||||
agentId: sessionAgentId,
|
||||
trigger: params.trigger,
|
||||
memoryFlushWritePath: params.memoryFlushWritePath,
|
||||
exec: {
|
||||
...params.execOverrides,
|
||||
elevated: params.bashElevated,
|
||||
},
|
||||
sandbox,
|
||||
messageProvider: params.messageChannel ?? params.messageProvider,
|
||||
agentAccountId: params.agentAccountId,
|
||||
messageTo: params.messageTo,
|
||||
messageThreadId: params.messageThreadId,
|
||||
groupId: params.groupId,
|
||||
groupChannel: params.groupChannel,
|
||||
groupSpace: params.groupSpace,
|
||||
spawnedBy: params.spawnedBy,
|
||||
senderId: params.senderId,
|
||||
senderName: params.senderName,
|
||||
senderUsername: params.senderUsername,
|
||||
senderE164: params.senderE164,
|
||||
senderIsOwner: params.senderIsOwner,
|
||||
allowGatewaySubagentBinding: params.allowGatewaySubagentBinding,
|
||||
sessionKey: sandboxSessionKey,
|
||||
sessionId: params.sessionId,
|
||||
runId: params.runId,
|
||||
agentDir,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
// When sandboxing uses a copied workspace (`ro` or `none`), effectiveWorkspace points
|
||||
// at the sandbox copy. Spawned subagents should inherit the real workspace instead.
|
||||
spawnWorkspaceDir: resolveAttemptSpawnWorkspaceDir({
|
||||
agentId: sessionAgentId,
|
||||
trigger: params.trigger,
|
||||
memoryFlushWritePath: params.memoryFlushWritePath,
|
||||
exec: {
|
||||
...params.execOverrides,
|
||||
elevated: params.bashElevated,
|
||||
},
|
||||
sandbox,
|
||||
resolvedWorkspace,
|
||||
}),
|
||||
config: params.config,
|
||||
abortSignal: runAbortController.signal,
|
||||
modelProvider: params.model.provider,
|
||||
modelId: params.modelId,
|
||||
modelCompat: params.model.compat,
|
||||
modelApi: params.model.api,
|
||||
modelContextWindowTokens: params.model.contextWindow,
|
||||
modelAuthMode: resolveModelAuthMode(params.model.provider, params.config),
|
||||
currentChannelId: params.currentChannelId,
|
||||
currentThreadTs: params.currentThreadTs,
|
||||
currentMessageId: params.currentMessageId,
|
||||
replyToMode: params.replyToMode,
|
||||
hasRepliedRef: params.hasRepliedRef,
|
||||
modelHasVision,
|
||||
requireExplicitMessageTarget:
|
||||
params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
|
||||
disableMessageTool: params.disableMessageTool,
|
||||
onYield: (message) => {
|
||||
yieldDetected = true;
|
||||
yieldMessage = message;
|
||||
queueYieldInterruptForSession?.();
|
||||
runAbortController.abort("sessions_yield");
|
||||
abortSessionForYield?.();
|
||||
},
|
||||
});
|
||||
messageProvider: params.messageChannel ?? params.messageProvider,
|
||||
agentAccountId: params.agentAccountId,
|
||||
messageTo: params.messageTo,
|
||||
messageThreadId: params.messageThreadId,
|
||||
groupId: params.groupId,
|
||||
groupChannel: params.groupChannel,
|
||||
groupSpace: params.groupSpace,
|
||||
spawnedBy: params.spawnedBy,
|
||||
senderId: params.senderId,
|
||||
senderName: params.senderName,
|
||||
senderUsername: params.senderUsername,
|
||||
senderE164: params.senderE164,
|
||||
senderIsOwner: params.senderIsOwner,
|
||||
allowGatewaySubagentBinding: params.allowGatewaySubagentBinding,
|
||||
sessionKey: sandboxSessionKey,
|
||||
sessionId: params.sessionId,
|
||||
runId: params.runId,
|
||||
agentDir,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
// When sandboxing uses a copied workspace (`ro` or `none`), effectiveWorkspace points
|
||||
// at the sandbox copy. Spawned subagents should inherit the real workspace instead.
|
||||
spawnWorkspaceDir: resolveAttemptSpawnWorkspaceDir({
|
||||
sandbox,
|
||||
resolvedWorkspace,
|
||||
}),
|
||||
config: params.config,
|
||||
abortSignal: runAbortController.signal,
|
||||
modelProvider: params.model.provider,
|
||||
modelId: params.modelId,
|
||||
modelCompat: params.model.compat,
|
||||
modelApi: params.model.api,
|
||||
modelContextWindowTokens: params.model.contextWindow,
|
||||
modelAuthMode: resolveModelAuthMode(params.model.provider, params.config),
|
||||
currentChannelId: params.currentChannelId,
|
||||
currentThreadTs: params.currentThreadTs,
|
||||
currentMessageId: params.currentMessageId,
|
||||
replyToMode: params.replyToMode,
|
||||
hasRepliedRef: params.hasRepliedRef,
|
||||
modelHasVision,
|
||||
requireExplicitMessageTarget:
|
||||
params.requireExplicitMessageTarget ?? isSubagentSessionKey(params.sessionKey),
|
||||
disableMessageTool: params.disableMessageTool,
|
||||
onYield: (message) => {
|
||||
yieldDetected = true;
|
||||
yieldMessage = message;
|
||||
queueYieldInterruptForSession?.();
|
||||
runAbortController.abort("sessions_yield");
|
||||
abortSessionForYield?.();
|
||||
},
|
||||
});
|
||||
if (params.toolsAllow && params.toolsAllow.length > 0) {
|
||||
const allowSet = new Set(params.toolsAllow);
|
||||
return allTools.filter((tool) => allowSet.has(tool.name));
|
||||
@@ -487,6 +486,12 @@ export async function runEmbeddedAttempt(
|
||||
const tools = sanitizeToolsForGoogle({
|
||||
tools: toolsEnabled ? toolsRaw : [],
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
modelId: params.modelId,
|
||||
modelApi: params.model.api,
|
||||
model: params.model,
|
||||
});
|
||||
const clientTools = toolsEnabled ? params.clientTools : undefined;
|
||||
const bundleMcpSessionRuntime = toolsEnabled
|
||||
@@ -526,7 +531,16 @@ export async function runEmbeddedAttempt(
|
||||
tools: effectiveTools,
|
||||
clientTools,
|
||||
});
|
||||
logToolSchemasForGoogle({ tools: effectiveTools, provider: params.provider });
|
||||
logToolSchemasForGoogle({
|
||||
tools: effectiveTools,
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
modelId: params.modelId,
|
||||
modelApi: params.model.api,
|
||||
model: params.model,
|
||||
});
|
||||
|
||||
const machineName = await getMachineDisplayName();
|
||||
const runtimeChannel = normalizeMessageChannel(params.messageChannel ?? params.messageProvider);
|
||||
@@ -568,7 +582,14 @@ export async function runEmbeddedAttempt(
|
||||
})
|
||||
: undefined;
|
||||
const sandboxInfo = buildEmbeddedSandboxInfo(sandbox, params.bashElevated);
|
||||
const reasoningTagHint = isReasoningTagProvider(params.provider);
|
||||
const reasoningTagHint = isReasoningTagProvider(params.provider, {
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
modelId: params.modelId,
|
||||
modelApi: params.model.api,
|
||||
model: params.model,
|
||||
});
|
||||
// Resolve channel-specific message actions for system prompt
|
||||
const channelActions = runtimeChannel
|
||||
? listChannelSupportedActions(
|
||||
@@ -621,7 +642,7 @@ export async function runEmbeddedAttempt(
|
||||
const promptMode = resolvePromptModeForSession(params.sessionKey);
|
||||
|
||||
// When toolsAllow is set, use minimal prompt and strip skills catalog
|
||||
const effectivePromptMode = params.toolsAllow?.length ? "minimal" as const : promptMode;
|
||||
const effectivePromptMode = params.toolsAllow?.length ? ("minimal" as const) : promptMode;
|
||||
const effectiveSkillsPrompt = params.toolsAllow?.length ? undefined : skillsPrompt;
|
||||
const docsPath = await resolveOpenClawDocsPath({
|
||||
workspaceDir: effectiveWorkspace,
|
||||
@@ -724,6 +745,10 @@ export async function runEmbeddedAttempt(
|
||||
modelApi: params.model?.api,
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
model: params.model,
|
||||
});
|
||||
|
||||
await prewarmSessionFile(params.sessionFile);
|
||||
@@ -1098,17 +1123,26 @@ export async function runEmbeddedAttempt(
|
||||
provider: params.provider,
|
||||
allowedToolNames,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
model: params.model,
|
||||
sessionManager,
|
||||
sessionId: params.sessionId,
|
||||
policy: transcriptPolicy,
|
||||
});
|
||||
cacheTrace?.recordStage("session:sanitized", { messages: prior });
|
||||
const validatedGemini = transcriptPolicy.validateGeminiTurns
|
||||
? validateGeminiTurns(prior)
|
||||
: prior;
|
||||
const validated = transcriptPolicy.validateAnthropicTurns
|
||||
? validateAnthropicTurns(validatedGemini)
|
||||
: validatedGemini;
|
||||
const validated = await validateReplayTurns({
|
||||
messages: prior,
|
||||
modelApi: params.model.api,
|
||||
modelId: params.modelId,
|
||||
provider: params.provider,
|
||||
config: params.config,
|
||||
workspaceDir: effectiveWorkspace,
|
||||
env: process.env,
|
||||
model: params.model,
|
||||
sessionId: params.sessionId,
|
||||
policy: transcriptPolicy,
|
||||
});
|
||||
const truncated = limitHistoryTurns(
|
||||
validated,
|
||||
getDmHistoryLimitFromSessionKey(params.sessionKey, params.config),
|
||||
|
||||
@@ -24,6 +24,7 @@ vi.mock("../plugins/provider-runtime.js", () => ({
|
||||
return undefined;
|
||||
}
|
||||
}),
|
||||
resolveProviderReplayPolicyWithPlugin: vi.fn(() => undefined),
|
||||
resetProviderRuntimeHookCacheForTest: vi.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveProviderReplayPolicyWithPlugin } from "../plugins/provider-runtime.js";
|
||||
import type { ProviderRuntimeModel } from "../plugins/types.js";
|
||||
import { normalizeProviderId } from "./model-selection.js";
|
||||
import { isGoogleModelApi } from "./pi-embedded-helpers/google.js";
|
||||
import {
|
||||
@@ -61,6 +64,10 @@ export function resolveTranscriptPolicy(params: {
|
||||
modelApi?: string | null;
|
||||
provider?: string | null;
|
||||
modelId?: string | null;
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
model?: ProviderRuntimeModel;
|
||||
}): TranscriptPolicy {
|
||||
const provider = normalizeProviderId(params.provider ?? "");
|
||||
const modelId = params.modelId ?? "";
|
||||
@@ -111,7 +118,7 @@ export function resolveTranscriptPolicy(params: {
|
||||
? { allowBase64Only: true, includeCamelCase: true }
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
const basePolicy: TranscriptPolicy = {
|
||||
sanitizeMode: isOpenAi ? "images-only" : needsNonImageSanitize ? "full" : "images-only",
|
||||
sanitizeToolCallIds:
|
||||
(!isOpenAi && sanitizeToolCallIds) || requiresOpenAiCompatibleToolIdSanitization,
|
||||
@@ -126,4 +133,52 @@ export function resolveTranscriptPolicy(params: {
|
||||
validateAnthropicTurns: !isOpenAi && (isAnthropic || isStrictOpenAiCompatible),
|
||||
allowSyntheticToolResults: !isOpenAi && (isGoogle || isAnthropic),
|
||||
};
|
||||
|
||||
const pluginPolicy = provider
|
||||
? resolveProviderReplayPolicyWithPlugin({
|
||||
provider,
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
context: {
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
provider,
|
||||
modelId,
|
||||
modelApi: params.modelApi,
|
||||
model: params.model,
|
||||
},
|
||||
})
|
||||
: undefined;
|
||||
if (!pluginPolicy) {
|
||||
return basePolicy;
|
||||
}
|
||||
|
||||
return {
|
||||
...basePolicy,
|
||||
...(pluginPolicy.sanitizeMode != null ? { sanitizeMode: pluginPolicy.sanitizeMode } : {}),
|
||||
...(typeof pluginPolicy.sanitizeToolCallIds === "boolean"
|
||||
? { sanitizeToolCallIds: pluginPolicy.sanitizeToolCallIds }
|
||||
: {}),
|
||||
...(pluginPolicy.toolCallIdMode ? { toolCallIdMode: pluginPolicy.toolCallIdMode } : {}),
|
||||
...(typeof pluginPolicy.repairToolUseResultPairing === "boolean"
|
||||
? { repairToolUseResultPairing: pluginPolicy.repairToolUseResultPairing }
|
||||
: {}),
|
||||
...(typeof pluginPolicy.preserveSignatures === "boolean"
|
||||
? { preserveSignatures: pluginPolicy.preserveSignatures }
|
||||
: {}),
|
||||
...(pluginPolicy.sanitizeThoughtSignatures
|
||||
? { sanitizeThoughtSignatures: pluginPolicy.sanitizeThoughtSignatures }
|
||||
: {}),
|
||||
...(typeof pluginPolicy.dropThinkingBlocks === "boolean"
|
||||
? { dropThinkingBlocks: pluginPolicy.dropThinkingBlocks }
|
||||
: {}),
|
||||
...(typeof pluginPolicy.applyAssistantFirstOrderingFix === "boolean"
|
||||
? { applyGoogleTurnOrdering: pluginPolicy.applyAssistantFirstOrderingFix }
|
||||
: {}),
|
||||
...(typeof pluginPolicy.allowSyntheticToolResults === "boolean"
|
||||
? { allowSyntheticToolResults: pluginPolicy.allowSyntheticToolResults }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user