mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 01:36:23 +00:00
* refactor: extract agent core package Introduce packages/agent-core as the OpenClaw-owned home for reusable agent loop, harness, session, prompt, and runtime dependency contracts. * refactor: extract shared llm runtime Move provider model registries, stream wrappers, OAuth helpers, and LLM utilities into src/llm with plugin-sdk barrels instead of depending on the old embedded runtime layout. * refactor: remove pi runtime internals Rename remaining Pi-shaped agent surfaces to OpenClaw agent runtime names, delete obsolete Pi docs and package graph checks, and add the third-party notice for incorporated code. * refactor: tighten agent session runtime Make agent-core/runtime dependencies explicit, consolidate compaction and session transcript helpers, and move model/session helpers behind OpenClaw-owned contracts. * refactor: remove static model and pi auth paths Drop static model catalogs and Pi auth bridges, move model/provider facts to manifest-owned runtime contracts, and harden internal embedded-agent utilities. * refactor: remove legacy provider compat paths * docs: remove agent parity notes * fix: skip provider wildcard metadata parsing * refactor: share session extension sdk loading * refactor: inline acpx proxy error formatter * refactor: fold edit recovery into edit tool * fix: accept extension batch separator * test: align startup provider plugin expectations * fix: restore provider-scoped release discovery * test: align static asset packaging expectations * fix: run static provider catalogs during scoped discovery * fix: add provider entry catalogs for scoped live discovery * fix: load lightweight provider catalog entries * fix: refresh provider-scoped plugin metadata * fix: keep provider catalog entries on release live path * fix: keep static manifest models in release live checks * fix: harden release model discovery * fix: reduce OpenAI live cache probe reasoning * fix: disable OpenAI cache probe reasoning * ci: extend OpenAI gateway live timeout * fix: extend live gateway model budget * fix: stabilize release validation regressions * fix: honor provider aliases in model rows * fix: stabilize release validation lanes * fix: stabilize release memory qa * ci: stabilize release validation lanes * ci: prefer ipv4 for live docker node calls * fix: restore shared tool-call stream wrapper * ci: remove legacy pi test shard alias * fix: clean up embedded agent test drift * fix: stabilize runtime alias status * fix: clean up embedded agent ci drift * fix: restore release ci invariants * fix: clean up post-rebase runtime drift * fix: restore release ci checks * fix: restore release ci after rebase * fix: remove stale pi runtime path * test: align compaction runtime expectations * test: update plugin prerelease expectations * fix: handle claude live tool approvals * fix: stabilize release validation gates * fix: finish agent runtime import * test: finish post-rebase agent runtime mocks * fix: keep codex compaction native * fix: stabilize codex app-server hook tests * test: isolate codex diagnostic active run * test: remove codex diagnostic completion race # Conflicts: # extensions/codex/src/app-server/run-attempt.test.ts * ci: fix full release manifest performance run id * refactor: narrow llm plugin sdk boundary * chore: drop generated google boundary stamps * fix: repair rebase fallout * fix: clean up rebased runtime references * fix: decode codex jwt payloads as base64url * fix: preserve shipped pi runtime alias * fix: add scoped sdk virtual modules * fix: decode llm codex oauth jwt as base64url * fix: avoid stale vertex adc negative cache * fix: harden tool arg decoding and codeql path * fix: keep vertex adc negative checks live * refactor: consolidate codex jwt and edit helpers * fix: await codex oauth node runtime imports * fix: preserve sdk tool and notice contracts * fix: preserve shipped compat config boundaries * fix: align codex oauth callback host * fix: terminate agent-core loop streams on failure * fix: keep codex oauth callback alive during fallback * ci: include session tools in critical codeql scans * fix: keep Cloudflare Anthropic provider auth header * docs: redirect legacy pi runtime pages * fix: honor bundled web provider compat discovery * fix: protect session output spill files * fix: keep legacy agent dir env blocked * fix: contain auto-discovered skill symlinks * fix: harden agent core sdk proxy surfaces * fix: restore approval reaction sdk compat * fix: keep live docker runs bounded * fix: keep codex oauth redirect host aligned * fix: resolve post-rebase agent runtime drift * fix: redact anthropic oauth parse failures * fix: preserve responses strict tool shaping * fix: repair agent runtime rebase cleanup * docs: redirect retired parity pages * fix: bound auto-discovered resources to roots * fix: repair post-rebase agent test drift * fix: preserve bundled provider allowlist migration * fix: preserve manifest-owned provider aliases * fix: declare photon image dependency * fix: keep provider headers out of proxy body * fix: preserve shipped env aliases * fix: refresh control ui i18n generated state * fix: quote read fallback paths * fix: preview edits through configured backend * test: satisfy core test typecheck * fix: preserve ZAI usage auth fallback * test: repair codex diagnostic test * fix: repair agent runtime rebase drift * test: finish embedded runner import rename * fix: repair agent runtime rebase integrations * test: align compaction oauth fallback expectations * fix: allow sdk-auth session models * fix: update doctor tool schema import * fix: preserve bedrock plugin region * fix: stream harmony-like prose immediately * ci: include session runtime in codeql shards * fix: repair latest rebase integrations * fix: honor explicit codex websocket transport * fix: keep openai-compatible credentials provider-scoped * fix: refresh sdk api baseline after rebase * fix: route cli runtime aliases through openclaw harness * test: rename stale harness mock expectation * test: rename embedded agent overflow calls * test: clean embedded auth test wording * test: use openclaw stream types in deepinfra cache test * fix: refresh sdk api baseline on latest main * fix: honor bundled discovery compat allowlists * fix: refresh sdk api baseline after latest rebase * fix: remove stale rebase imports * test: rename stale model catalog mock * test: mock renamed doctor runtime modules * fix: map canonical kimi env auth * fix: use internal model registry in bench script * fix: migrate deepinfra provider catalog entry * fix: enforce builtin tool suppression * fix: route compaction auth and proxy payloads safely * refactor: prune unused llm registry leftovers * test: update codex hooks session import * test: fix model picker ci coverage * test: align model picker auth mock types
344 lines
12 KiB
TypeScript
344 lines
12 KiB
TypeScript
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import { resolvePluginControlPlaneFingerprint } from "../plugins/plugin-control-plane-context.js";
|
|
import type { ProviderRuntimePluginHandle } from "../plugins/provider-hook-runtime.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";
|
|
import type { ProviderReplayPolicy } from "../plugins/types.js";
|
|
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
|
import { isGoogleModelApi } from "./embedded-agent-helpers/google.js";
|
|
import { normalizeProviderId } from "./model-selection.js";
|
|
import type { ToolCallIdMode } from "./tool-call-id.js";
|
|
|
|
export type TranscriptSanitizeMode = "full" | "images-only";
|
|
|
|
export type TranscriptPolicy = {
|
|
sanitizeMode: TranscriptSanitizeMode;
|
|
sanitizeToolCallIds: boolean;
|
|
toolCallIdMode?: ToolCallIdMode;
|
|
preserveNativeAnthropicToolUseIds: boolean;
|
|
repairToolUseResultPairing: boolean;
|
|
preserveSignatures: boolean;
|
|
sanitizeThoughtSignatures?: {
|
|
allowBase64Only?: boolean;
|
|
includeCamelCase?: boolean;
|
|
};
|
|
sanitizeThinkingSignatures: boolean;
|
|
dropThinkingBlocks: boolean;
|
|
dropReasoningFromHistory?: boolean;
|
|
applyGoogleTurnOrdering: boolean;
|
|
validateGeminiTurns: boolean;
|
|
validateAnthropicTurns: boolean;
|
|
allowSyntheticToolResults: boolean;
|
|
};
|
|
|
|
const SIGNED_THINKING_PROVIDERS = new Set(["anthropic", "amazon-bedrock", "anthropic-vertex"]);
|
|
|
|
export function providerRequiresSignedThinking(provider?: string | null): boolean {
|
|
return SIGNED_THINKING_PROVIDERS.has(normalizeProviderId(provider ?? ""));
|
|
}
|
|
|
|
export function shouldAllowProviderOwnedThinkingReplay(params: {
|
|
modelApi?: string | null;
|
|
provider?: string | null;
|
|
policy: Pick<
|
|
TranscriptPolicy,
|
|
"validateAnthropicTurns" | "preserveSignatures" | "dropThinkingBlocks"
|
|
>;
|
|
}): boolean {
|
|
const hasProviderOwnedSignedThinking =
|
|
params.policy.preserveSignatures || providerRequiresSignedThinking(params.provider);
|
|
return (
|
|
isAnthropicApi(params.modelApi) &&
|
|
params.policy.validateAnthropicTurns &&
|
|
hasProviderOwnedSignedThinking &&
|
|
!params.policy.dropThinkingBlocks
|
|
);
|
|
}
|
|
|
|
const DEFAULT_TRANSCRIPT_POLICY: TranscriptPolicy = {
|
|
sanitizeMode: "images-only",
|
|
sanitizeToolCallIds: false,
|
|
toolCallIdMode: undefined,
|
|
preserveNativeAnthropicToolUseIds: false,
|
|
repairToolUseResultPairing: true,
|
|
preserveSignatures: false,
|
|
sanitizeThoughtSignatures: undefined,
|
|
sanitizeThinkingSignatures: false,
|
|
dropThinkingBlocks: false,
|
|
dropReasoningFromHistory: false,
|
|
applyGoogleTurnOrdering: false,
|
|
validateGeminiTurns: false,
|
|
validateAnthropicTurns: false,
|
|
allowSyntheticToolResults: false,
|
|
};
|
|
|
|
function isAnthropicApi(modelApi?: string | null): boolean {
|
|
return modelApi === "anthropic-messages" || modelApi === "bedrock-converse-stream";
|
|
}
|
|
|
|
function isOpenAiResponsesCompatibleApi(modelApi?: string | null): boolean {
|
|
return (
|
|
modelApi === "openai-responses" ||
|
|
modelApi === "openai-codex-responses" ||
|
|
modelApi === "azure-openai-responses"
|
|
);
|
|
}
|
|
|
|
function isClaudeFamilyModelId(modelId?: string | null): boolean {
|
|
const id = normalizeLowercaseStringOrEmpty(modelId);
|
|
return /(?:^|[./:_-])claude(?:$|[./:_-])/.test(id);
|
|
}
|
|
|
|
function modelDisablesReasoningEffort(model?: ProviderRuntimeModel): boolean {
|
|
const compat = model?.compat as { supportsReasoningEffort?: boolean } | undefined;
|
|
return compat?.supportsReasoningEffort === false;
|
|
}
|
|
|
|
/**
|
|
* 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: {
|
|
modelApi?: string | null;
|
|
modelId?: string | null;
|
|
model?: ProviderRuntimeModel;
|
|
}): ProviderReplayPolicy | undefined {
|
|
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-codex-responses" ||
|
|
params.modelApi === "azure-openai-responses";
|
|
|
|
if (
|
|
!isGoogle &&
|
|
!isAnthropic &&
|
|
!isStrictOpenAiCompatible &&
|
|
!requiresOpenAiCompatibleToolIdSanitization
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
const modelId = normalizeLowercaseStringOrEmpty(params.modelId);
|
|
const isClaudeOpenAiResponses = isOpenAiResponsesCompatibleApi(params.modelApi)
|
|
? isClaudeFamilyModelId(modelId)
|
|
: false;
|
|
return {
|
|
...(isGoogle || isAnthropic ? { sanitizeMode: "full" as const } : {}),
|
|
...(isGoogle || isAnthropic || requiresOpenAiCompatibleToolIdSanitization
|
|
? {
|
|
sanitizeToolCallIds: true,
|
|
toolCallIdMode: "strict" as const,
|
|
}
|
|
: {}),
|
|
...(isAnthropic ? { preserveSignatures: true } : {}),
|
|
...(isGoogle
|
|
? {
|
|
sanitizeThoughtSignatures: {
|
|
allowBase64Only: true,
|
|
includeCamelCase: true,
|
|
},
|
|
}
|
|
: {}),
|
|
...(isAnthropic && modelId.includes("claude")
|
|
? { dropThinkingBlocks: !shouldPreserveThinkingBlocks(modelId) }
|
|
: {}),
|
|
...(isAnthropic && modelDisablesReasoningEffort(params.model)
|
|
? { dropThinkingBlocks: true }
|
|
: {}),
|
|
...(isStrictOpenAiCompatible
|
|
? { dropReasoningFromHistory: !requiresReasoningContentReplay(params.modelId) }
|
|
: {}),
|
|
...(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 = new Set([
|
|
"kimi-for-coding",
|
|
"kimi-k2.5",
|
|
"kimi-k2.6",
|
|
"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: string | null | undefined): boolean {
|
|
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: ProviderReplayPolicy | undefined,
|
|
basePolicy: TranscriptPolicy = DEFAULT_TRANSCRIPT_POLICY,
|
|
): TranscriptPolicy {
|
|
if (!policy) {
|
|
return basePolicy;
|
|
}
|
|
|
|
return {
|
|
...basePolicy,
|
|
...(policy.sanitizeMode != null ? { sanitizeMode: policy.sanitizeMode } : {}),
|
|
...(typeof policy.sanitizeToolCallIds === "boolean"
|
|
? { sanitizeToolCallIds: policy.sanitizeToolCallIds }
|
|
: {}),
|
|
...(policy.toolCallIdMode ? { toolCallIdMode: policy.toolCallIdMode as ToolCallIdMode } : {}),
|
|
...(typeof policy.preserveNativeAnthropicToolUseIds === "boolean"
|
|
? { preserveNativeAnthropicToolUseIds: policy.preserveNativeAnthropicToolUseIds }
|
|
: {}),
|
|
...(typeof policy.repairToolUseResultPairing === "boolean"
|
|
? { repairToolUseResultPairing: policy.repairToolUseResultPairing }
|
|
: {}),
|
|
...(typeof policy.preserveSignatures === "boolean"
|
|
? { preserveSignatures: policy.preserveSignatures }
|
|
: {}),
|
|
...(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 = 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;
|
|
model?: ProviderRuntimeModel;
|
|
config: OpenClawConfig;
|
|
workspaceDir?: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
}): string {
|
|
return JSON.stringify({
|
|
provider: params.provider,
|
|
modelApi: params.modelApi ?? "",
|
|
modelId: params.modelId ?? "",
|
|
dropsThinkingForReasoningCompat: modelDisablesReasoningEffort(params.model),
|
|
workspaceDir: params.workspaceDir ?? "",
|
|
pluginControlPlane: resolvePluginControlPlaneFingerprint({
|
|
config: params.config,
|
|
workspaceDir: params.workspaceDir,
|
|
env: params.env,
|
|
}),
|
|
});
|
|
}
|
|
|
|
export function resolveTranscriptPolicy(params: {
|
|
modelApi?: string | null;
|
|
provider?: string | null;
|
|
modelId?: string | null;
|
|
config?: OpenClawConfig;
|
|
workspaceDir?: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
model?: ProviderRuntimeModel;
|
|
runtimeHandle?: ProviderRuntimePluginHandle;
|
|
}): TranscriptPolicy {
|
|
const provider = normalizeProviderId(params.provider ?? "");
|
|
const cacheConfig = canCacheTranscriptPolicy(params) ? params.config : undefined;
|
|
const cacheKey = cacheConfig
|
|
? resolveTranscriptPolicyCacheKey({ ...params, provider, config: cacheConfig })
|
|
: undefined;
|
|
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,
|
|
})
|
|
: undefined);
|
|
const context = {
|
|
config: params.config,
|
|
workspaceDir: params.workspaceDir,
|
|
env: params.env,
|
|
provider,
|
|
modelId: params.modelId ?? "",
|
|
modelApi: params.modelApi,
|
|
model: params.model,
|
|
};
|
|
|
|
// 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;
|
|
const policy = buildReplayPolicy
|
|
? mergeTranscriptPolicy(buildReplayPolicy(context) ?? undefined)
|
|
: mergeTranscriptPolicy(
|
|
buildUnownedProviderTransportReplayFallback({
|
|
modelApi: params.modelApi,
|
|
modelId: params.modelId,
|
|
model: params.model,
|
|
}),
|
|
);
|
|
if (cacheConfig && cacheKey) {
|
|
let configCache = transcriptPolicyCache.get(cacheConfig);
|
|
if (!configCache) {
|
|
configCache = new Map();
|
|
transcriptPolicyCache.set(cacheConfig, configCache);
|
|
}
|
|
configCache.set(cacheKey, policy);
|
|
}
|
|
return policy;
|
|
}
|