mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:30:44 +00:00
perf(agents): memoize transcript policy safely
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Agents/runtime: memoize transcript replay-policy resolution for stable config and process-env runs while preserving custom-env provider hook behavior. Thanks @DmitryPogodaev.
|
||||
- Infra/path-guards: add a fast path for canonical absolute POSIX containment checks, avoiding repeated `path.resolve` and `path.relative` work in hot filesystem walkers. Refs #75895, #75575, and #68782. Thanks @Enderfga.
|
||||
- Tools: add a platform-level tool descriptor planner for descriptor-first visibility, generic availability checks, and executor references. Thanks @shakkernerd.
|
||||
- Docs/Codex: clarify that ChatGPT/Codex subscription setups should use `openai/gpt-*` with `agentRuntime.id: "codex"` for native Codex runtime, while `openai-codex/*` remains the PI OAuth route. Thanks @pashpashpash.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { resolveProviderRuntimePlugin } from "../plugins/provider-hook-runtime.js";
|
||||
|
||||
vi.mock("../plugins/provider-hook-runtime.js", async () => {
|
||||
const replayHelpers = await vi.importActual<
|
||||
@@ -13,6 +15,7 @@ vi.mock("../plugins/provider-hook-runtime.js", async () => {
|
||||
"anthropic",
|
||||
"google",
|
||||
"github-copilot",
|
||||
"env-sensitive",
|
||||
"kilocode",
|
||||
"kimi",
|
||||
"kimi-code",
|
||||
@@ -38,9 +41,20 @@ vi.mock("../plugins/provider-hook-runtime.js", async () => {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
buildReplayPolicy: (context?: { modelId?: string; modelApi?: string }) => {
|
||||
buildReplayPolicy: (context?: {
|
||||
modelId?: string;
|
||||
modelApi?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}) => {
|
||||
const modelId = context?.modelId?.toLowerCase() ?? "";
|
||||
switch (provider) {
|
||||
case "env-sensitive":
|
||||
return {
|
||||
sanitizeToolCallIds: context?.env?.OPENCLAW_TEST_TRANSCRIPT_POLICY === "strict",
|
||||
...(context?.env?.OPENCLAW_TEST_TRANSCRIPT_POLICY === "strict"
|
||||
? { toolCallIdMode: "strict" as const }
|
||||
: {}),
|
||||
};
|
||||
case "amazon-bedrock":
|
||||
case "anthropic":
|
||||
return {
|
||||
@@ -190,6 +204,7 @@ vi.mock("../plugins/provider-hook-runtime.js", async () => {
|
||||
|
||||
let resolveTranscriptPolicy: typeof import("./transcript-policy.js").resolveTranscriptPolicy;
|
||||
let shouldAllowProviderOwnedThinkingReplay: typeof import("./transcript-policy.js").shouldAllowProviderOwnedThinkingReplay;
|
||||
const mockResolveProviderRuntimePlugin = vi.mocked(resolveProviderRuntimePlugin);
|
||||
|
||||
describe("resolveTranscriptPolicy", () => {
|
||||
beforeAll(async () => {
|
||||
@@ -225,6 +240,56 @@ describe("resolveTranscriptPolicy", () => {
|
||||
expect(policy.toolCallIdMode).toBe("strict");
|
||||
});
|
||||
|
||||
it("memoizes replay policy resolution for the same config and process env", () => {
|
||||
const config = {} as OpenClawConfig;
|
||||
|
||||
resolveTranscriptPolicy({
|
||||
provider: "mistral",
|
||||
modelId: "mistral-large-latest",
|
||||
config,
|
||||
env: process.env,
|
||||
});
|
||||
resolveTranscriptPolicy({
|
||||
provider: "mistral",
|
||||
modelId: "mistral-large-latest",
|
||||
config,
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
expect(mockResolveProviderRuntimePlugin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not reuse cached replay policies across custom env objects", () => {
|
||||
const config = {} as OpenClawConfig;
|
||||
const strictEnv = {
|
||||
...process.env,
|
||||
OPENCLAW_TEST_TRANSCRIPT_POLICY: "strict",
|
||||
};
|
||||
const looseEnv = {
|
||||
...process.env,
|
||||
OPENCLAW_TEST_TRANSCRIPT_POLICY: "loose",
|
||||
};
|
||||
|
||||
const strictPolicy = resolveTranscriptPolicy({
|
||||
provider: "env-sensitive",
|
||||
modelId: "env-demo",
|
||||
config,
|
||||
env: strictEnv,
|
||||
});
|
||||
const loosePolicy = resolveTranscriptPolicy({
|
||||
provider: "env-sensitive",
|
||||
modelId: "env-demo",
|
||||
config,
|
||||
env: looseEnv,
|
||||
});
|
||||
|
||||
expect(strictPolicy.sanitizeToolCallIds).toBe(true);
|
||||
expect(strictPolicy.toolCallIdMode).toBe("strict");
|
||||
expect(loosePolicy.sanitizeToolCallIds).toBe(false);
|
||||
expect(loosePolicy.toolCallIdMode).toBeUndefined();
|
||||
expect(mockResolveProviderRuntimePlugin).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("enables sanitizeToolCallIds for Google provider", () => {
|
||||
const policy = resolveTranscriptPolicy({
|
||||
provider: "google",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { resolvePluginControlPlaneFingerprint } from "../plugins/plugin-control-plane-context.js";
|
||||
import { resolveProviderRuntimePlugin } from "../plugins/provider-hook-runtime.js";
|
||||
import { shouldPreserveThinkingBlocks } from "../plugins/provider-replay-helpers.js";
|
||||
import type { ProviderRuntimeModel } from "../plugins/provider-runtime-model.types.js";
|
||||
@@ -177,6 +178,39 @@ function mergeTranscriptPolicy(
|
||||
};
|
||||
}
|
||||
|
||||
const transcriptPolicyCache = new WeakMap<OpenClawConfig, Map<string, TranscriptPolicy>>();
|
||||
|
||||
function canCacheTranscriptPolicy(params: {
|
||||
config?: OpenClawConfig;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): params is { config: OpenClawConfig; env?: NodeJS.ProcessEnv } {
|
||||
if (!params.config) {
|
||||
return false;
|
||||
}
|
||||
return !params.env || params.env === process.env;
|
||||
}
|
||||
|
||||
function resolveTranscriptPolicyCacheKey(params: {
|
||||
modelApi?: string | null;
|
||||
provider: string;
|
||||
modelId?: string | null;
|
||||
config: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): string {
|
||||
return JSON.stringify({
|
||||
provider: params.provider,
|
||||
modelApi: params.modelApi ?? "",
|
||||
modelId: params.modelId ?? "",
|
||||
workspaceDir: params.workspaceDir ?? "",
|
||||
pluginControlPlane: resolvePluginControlPlaneFingerprint({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveTranscriptPolicy(params: {
|
||||
modelApi?: string | null;
|
||||
provider?: string | null;
|
||||
@@ -187,6 +221,15 @@ export function resolveTranscriptPolicy(params: {
|
||||
model?: ProviderRuntimeModel;
|
||||
}): TranscriptPolicy {
|
||||
const provider = normalizeProviderId(params.provider ?? "");
|
||||
const cacheKey = canCacheTranscriptPolicy(params)
|
||||
? resolveTranscriptPolicyCacheKey({ ...params, provider, config: params.config })
|
||||
: undefined;
|
||||
if (cacheKey) {
|
||||
const cached = transcriptPolicyCache.get(params.config)?.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
const runtimePlugin = provider
|
||||
? resolveProviderRuntimePlugin({
|
||||
provider,
|
||||
@@ -208,15 +251,21 @@ export function resolveTranscriptPolicy(params: {
|
||||
// Once a provider adopts the replay-policy hook, replay policy should come
|
||||
// from the plugin, not from transport-family defaults in core.
|
||||
const buildReplayPolicy = runtimePlugin?.buildReplayPolicy;
|
||||
if (buildReplayPolicy) {
|
||||
const pluginPolicy = buildReplayPolicy(context);
|
||||
return mergeTranscriptPolicy(pluginPolicy ?? undefined);
|
||||
const policy = buildReplayPolicy
|
||||
? mergeTranscriptPolicy(buildReplayPolicy(context) ?? undefined)
|
||||
: mergeTranscriptPolicy(
|
||||
buildUnownedProviderTransportReplayFallback({
|
||||
modelApi: params.modelApi,
|
||||
modelId: params.modelId,
|
||||
}),
|
||||
);
|
||||
if (cacheKey) {
|
||||
let configCache = transcriptPolicyCache.get(params.config);
|
||||
if (!configCache) {
|
||||
configCache = new Map();
|
||||
transcriptPolicyCache.set(params.config, configCache);
|
||||
}
|
||||
configCache.set(cacheKey, policy);
|
||||
}
|
||||
|
||||
return mergeTranscriptPolicy(
|
||||
buildUnownedProviderTransportReplayFallback({
|
||||
modelApi: params.modelApi,
|
||||
modelId: params.modelId,
|
||||
}),
|
||||
);
|
||||
return policy;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user