mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-16 19:51:11 +00:00
refactor: dedupe agent lowercase helpers
This commit is contained in:
@@ -6,6 +6,7 @@ import { loadConfig } from "../config/config.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { computeBackoff, type BackoffPolicy } from "../infra/backoff.js";
|
||||
import { consumeRootOptionToken, FLAG_TERMINATOR } from "../infra/cli-root-options.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { resolveOpenClawAgentDir } from "./agent-paths.js";
|
||||
import { lookupCachedContextTokens, MODEL_CONTEXT_TOKEN_CACHE } from "./context-cache.js";
|
||||
import { CONTEXT_WINDOW_RUNTIME_STATE } from "./context-runtime-state.js";
|
||||
@@ -92,10 +93,7 @@ function loadModelsConfigRuntime() {
|
||||
}
|
||||
|
||||
function isLikelyOpenClawCliProcess(argv: string[] = process.argv): boolean {
|
||||
const entryBasename = path
|
||||
.basename(argv[1] ?? "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const entryBasename = normalizeLowercaseStringOrEmpty(path.basename(argv[1] ?? ""));
|
||||
return (
|
||||
entryBasename === "openclaw" ||
|
||||
entryBasename === "openclaw.mjs" ||
|
||||
@@ -275,9 +273,9 @@ function resolveConfiguredModelParams(
|
||||
if (!models) {
|
||||
return undefined;
|
||||
}
|
||||
const key = `${provider}/${model}`.trim().toLowerCase();
|
||||
const key = normalizeLowercaseStringOrEmpty(`${provider}/${model}`);
|
||||
for (const [rawKey, entry] of Object.entries(models)) {
|
||||
if (rawKey.trim().toLowerCase() === key) {
|
||||
if (normalizeLowercaseStringOrEmpty(rawKey) === key) {
|
||||
const params = (entry as AgentModelEntry | undefined)?.params;
|
||||
return params && typeof params === "object" ? params : undefined;
|
||||
}
|
||||
@@ -360,7 +358,9 @@ function resolveConfiguredProviderContextTokens(
|
||||
}
|
||||
|
||||
// 1. Exact match (case-insensitive, no alias expansion).
|
||||
const exactResult = findContextTokens((id) => id.trim().toLowerCase() === provider.toLowerCase());
|
||||
const exactResult = findContextTokens(
|
||||
(id) => normalizeLowercaseStringOrEmpty(id) === normalizeLowercaseStringOrEmpty(provider),
|
||||
);
|
||||
if (exactResult !== undefined) {
|
||||
return exactResult;
|
||||
}
|
||||
@@ -374,7 +374,7 @@ function isAnthropic1MModel(provider: string, model: string): boolean {
|
||||
if (provider !== "anthropic") {
|
||||
return false;
|
||||
}
|
||||
const normalized = model.trim().toLowerCase();
|
||||
const normalized = normalizeLowercaseStringOrEmpty(model);
|
||||
const modelId = normalized.includes("/")
|
||||
? (normalized.split("/").at(-1) ?? normalized)
|
||||
: normalized;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type OpenClawConfig, loadConfig } from "../config/config.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { augmentModelCatalogWithProviderPlugins } from "../plugins/provider-runtime.runtime.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { resolveOpenClawAgentDir } from "./agent-paths.js";
|
||||
import { ensureOpenClawModelsJson } from "./models-config.js";
|
||||
import { normalizeProviderId } from "./provider-id.js";
|
||||
@@ -164,11 +165,12 @@ export async function loadModelCatalog(params?: {
|
||||
if (supplemental.length > 0) {
|
||||
const seen = new Set(
|
||||
models.map(
|
||||
(entry) => `${entry.provider.toLowerCase().trim()}::${entry.id.toLowerCase().trim()}`,
|
||||
(entry) =>
|
||||
`${normalizeLowercaseStringOrEmpty(entry.provider)}::${normalizeLowercaseStringOrEmpty(entry.id)}`,
|
||||
),
|
||||
);
|
||||
for (const entry of supplemental) {
|
||||
const key = `${entry.provider.toLowerCase().trim()}::${entry.id.toLowerCase().trim()}`;
|
||||
const key = `${normalizeLowercaseStringOrEmpty(entry.provider)}::${normalizeLowercaseStringOrEmpty(entry.id)}`;
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
@@ -226,10 +228,10 @@ export function findModelInCatalog(
|
||||
modelId: string,
|
||||
): ModelCatalogEntry | undefined {
|
||||
const normalizedProvider = normalizeProviderId(provider);
|
||||
const normalizedModelId = modelId.toLowerCase().trim();
|
||||
const normalizedModelId = normalizeLowercaseStringOrEmpty(modelId);
|
||||
return catalog.find(
|
||||
(entry) =>
|
||||
normalizeProviderId(entry.provider) === normalizedProvider &&
|
||||
entry.id.toLowerCase() === normalizedModelId,
|
||||
normalizeLowercaseStringOrEmpty(entry.id) === normalizedModelId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "../../shared/string-coerce.js";
|
||||
|
||||
type AnthropicCacheRetentionFamily =
|
||||
| "anthropic-direct"
|
||||
| "anthropic-bedrock"
|
||||
| "custom-anthropic-api";
|
||||
|
||||
export function isAnthropicModelRef(modelId: string): boolean {
|
||||
return modelId.trim().toLowerCase().startsWith("anthropic/");
|
||||
return normalizeLowercaseStringOrEmpty(modelId).startsWith("anthropic/");
|
||||
}
|
||||
|
||||
/** Matches Application Inference Profile ARNs across all AWS partitions with Bedrock. */
|
||||
const BEDROCK_APP_INFERENCE_PROFILE_ARN_RE = /^arn:aws(-cn|-us-gov)?:bedrock:/;
|
||||
|
||||
export function isAnthropicBedrockModel(modelId: string): boolean {
|
||||
const normalized = modelId.trim().toLowerCase();
|
||||
const normalized = normalizeLowercaseStringOrEmpty(modelId);
|
||||
|
||||
// Direct Anthropic Claude model IDs and regional inference profiles
|
||||
// e.g. "anthropic.claude-sonnet-4-6", "us.anthropic.claude-sonnet-4-6", "global.anthropic.claude-opus-4-6-v1"
|
||||
@@ -41,7 +46,9 @@ export function isAnthropicBedrockModel(modelId: string): boolean {
|
||||
}
|
||||
|
||||
export function isOpenRouterAnthropicModelRef(provider: string, modelId: string): boolean {
|
||||
return provider.trim().toLowerCase() === "openrouter" && isAnthropicModelRef(modelId);
|
||||
return (
|
||||
normalizeOptionalLowercaseString(provider) === "openrouter" && isAnthropicModelRef(modelId)
|
||||
);
|
||||
}
|
||||
|
||||
export function isAnthropicFamilyCacheTtlEligible(params: {
|
||||
@@ -49,7 +56,7 @@ export function isAnthropicFamilyCacheTtlEligible(params: {
|
||||
modelApi?: string;
|
||||
modelId: string;
|
||||
}): boolean {
|
||||
const normalizedProvider = params.provider.trim().toLowerCase();
|
||||
const normalizedProvider = normalizeOptionalLowercaseString(params.provider);
|
||||
if (normalizedProvider === "anthropic" || normalizedProvider === "anthropic-vertex") {
|
||||
return true;
|
||||
}
|
||||
@@ -65,7 +72,7 @@ export function resolveAnthropicCacheRetentionFamily(params: {
|
||||
modelId?: string;
|
||||
hasExplicitCacheConfig: boolean;
|
||||
}): AnthropicCacheRetentionFamily | undefined {
|
||||
const normalizedProvider = params.provider.trim().toLowerCase();
|
||||
const normalizedProvider = normalizeOptionalLowercaseString(params.provider);
|
||||
if (normalizedProvider === "anthropic" || normalizedProvider === "anthropic-vertex") {
|
||||
return "anthropic-direct";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Api } from "@mariozechner/pi-ai";
|
||||
import type { ModelDefinitionConfig, ModelProviderConfig } from "../../config/types.js";
|
||||
import { normalizeGoogleApiBaseUrl } from "../../infra/google-api-base-url.js";
|
||||
import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js";
|
||||
import { isSecretRefHeaderValueMarker } from "../model-auth-markers.js";
|
||||
import {
|
||||
attachModelProviderRequestTransport,
|
||||
@@ -68,12 +69,12 @@ function isLegacyFoundryVisionModelCandidate(params: {
|
||||
modelId?: string;
|
||||
modelName?: string;
|
||||
}): boolean {
|
||||
if (params.provider?.trim().toLowerCase() !== "microsoft-foundry") {
|
||||
if (normalizeOptionalLowercaseString(params.provider) !== "microsoft-foundry") {
|
||||
return false;
|
||||
}
|
||||
const normalizedCandidates = [params.modelId, params.modelName]
|
||||
.filter((value): value is string => typeof value === "string")
|
||||
.map((value) => value.trim().toLowerCase())
|
||||
.map((value) => normalizeOptionalLowercaseString(value))
|
||||
.filter(Boolean);
|
||||
return normalizedCandidates.some(
|
||||
(candidate) =>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import { resolveAnthropicCacheRetentionFamily } from "./anthropic-family-cache-semantics.js";
|
||||
|
||||
type CacheRetention = "none" | "short" | "long";
|
||||
@@ -9,7 +10,7 @@ export function isGooglePromptCacheEligible(params: {
|
||||
if (params.modelApi !== "google-generative-ai") {
|
||||
return false;
|
||||
}
|
||||
const normalizedModelId = params.modelId?.trim().toLowerCase() ?? "";
|
||||
const normalizedModelId = normalizeLowercaseStringOrEmpty(params.modelId);
|
||||
return normalizedModelId.startsWith("gemini-2.5") || normalizedModelId.startsWith("gemini-3");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import type { RuntimeVersionEnv } from "../version.js";
|
||||
import { resolveRuntimeServiceVersion } from "../version.js";
|
||||
import { normalizeProviderId } from "./provider-id.js";
|
||||
@@ -129,7 +133,7 @@ function formatOpenClawUserAgent(version: string): string {
|
||||
|
||||
function tryParseHostname(value: string): string | undefined {
|
||||
try {
|
||||
return new URL(value).hostname.toLowerCase();
|
||||
return normalizeOptionalLowercaseString(new URL(value).hostname);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
@@ -140,11 +144,10 @@ function isSchemelessHostnameCandidate(value: string): boolean {
|
||||
}
|
||||
|
||||
function resolveUrlHostname(value: unknown): string | undefined {
|
||||
if (typeof value !== "string" || !value.trim()) {
|
||||
const trimmed = normalizeOptionalString(value);
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const trimmed = value.trim();
|
||||
const parsedHostname = tryParseHostname(trimmed);
|
||||
if (parsedHostname) {
|
||||
return parsedHostname;
|
||||
@@ -156,7 +159,7 @@ function resolveUrlHostname(value: unknown): string | undefined {
|
||||
}
|
||||
|
||||
function normalizeComparableBaseUrl(value: string): string | undefined {
|
||||
const trimmed = value.trim();
|
||||
const trimmed = normalizeOptionalString(value);
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -172,7 +175,7 @@ function normalizeComparableBaseUrl(value: string): string | undefined {
|
||||
}
|
||||
url.hash = "";
|
||||
url.search = "";
|
||||
return url.toString().replace(/\/+$/, "").toLowerCase();
|
||||
return normalizeOptionalLowercaseString(url.toString().replace(/\/+$/, ""));
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
@@ -465,7 +468,7 @@ export function resolveProviderRequestPolicy(
|
||||
const policy = resolveProviderAttributionPolicy(provider, env);
|
||||
const endpointResolution = resolveProviderEndpoint(input.baseUrl);
|
||||
const endpointClass = endpointResolution.endpointClass;
|
||||
const api = input.api?.trim().toLowerCase();
|
||||
const api = normalizeOptionalLowercaseString(input.api);
|
||||
const usesConfiguredBaseUrl = endpointClass !== "default";
|
||||
const usesKnownNativeOpenAIEndpoint =
|
||||
endpointClass === "openai-public" ||
|
||||
@@ -535,8 +538,8 @@ export function resolveProviderRequestCapabilities(
|
||||
): ProviderRequestCapabilities {
|
||||
const policy = resolveProviderRequestPolicy(input, env);
|
||||
const provider = policy.provider;
|
||||
const api = input.api?.trim().toLowerCase();
|
||||
const normalizedModelId = input.modelId?.trim().toLowerCase();
|
||||
const api = normalizeOptionalLowercaseString(input.api);
|
||||
const normalizedModelId = normalizeOptionalLowercaseString(input.modelId);
|
||||
const endpointClass = policy.endpointClass;
|
||||
const isKnownNativeEndpoint =
|
||||
endpointClass === "anthropic-public" ||
|
||||
|
||||
@@ -5,6 +5,10 @@ import { resolveChannelApprovalCapability } from "../channels/plugins/approvals.
|
||||
import { getChannelPlugin } from "../channels/plugins/index.js";
|
||||
import type { MemoryCitationsMode } from "../config/types.memory.js";
|
||||
import { buildMemoryPromptSection } from "../plugins/memory-state.js";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalLowercaseString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { listDeliverableMessageChannels } from "../utils/message-channel.js";
|
||||
import type { ResolvedTimeFormat } from "./date-time.js";
|
||||
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
|
||||
@@ -47,7 +51,7 @@ function normalizeContextFilePath(pathValue: string): string {
|
||||
|
||||
function getContextFileBasename(pathValue: string): string {
|
||||
const normalizedPath = normalizeContextFilePath(pathValue);
|
||||
return (normalizedPath.split("/").pop() ?? normalizedPath).toLowerCase();
|
||||
return normalizeLowercaseStringOrEmpty(normalizedPath.split("/").pop() ?? normalizedPath);
|
||||
}
|
||||
|
||||
function isDynamicContextFile(pathValue: string): boolean {
|
||||
@@ -297,7 +301,7 @@ function buildExecApprovalPromptGuidance(params: {
|
||||
runtimeChannel?: string;
|
||||
inlineButtonsEnabled?: boolean;
|
||||
}) {
|
||||
const runtimeChannel = params.runtimeChannel?.trim().toLowerCase();
|
||||
const runtimeChannel = normalizeOptionalLowercaseString(params.runtimeChannel);
|
||||
const usesNativeApprovalUi =
|
||||
runtimeChannel === "webchat" ||
|
||||
params.inlineButtonsEnabled === true ||
|
||||
@@ -368,7 +372,7 @@ export function buildAgentSystemPrompt(params: {
|
||||
// Preserve caller casing while deduping tool names by lowercase.
|
||||
const canonicalByNormalized = new Map<string, string>();
|
||||
for (const name of canonicalToolNames) {
|
||||
const normalized = name.toLowerCase();
|
||||
const normalized = normalizeLowercaseStringOrEmpty(name);
|
||||
if (!canonicalByNormalized.has(normalized)) {
|
||||
canonicalByNormalized.set(normalized, name);
|
||||
}
|
||||
@@ -376,7 +380,7 @@ export function buildAgentSystemPrompt(params: {
|
||||
const resolveToolName = (normalized: string) =>
|
||||
canonicalByNormalized.get(normalized) ?? normalized;
|
||||
|
||||
const normalizedTools = canonicalToolNames.map((tool) => tool.toLowerCase());
|
||||
const normalizedTools = canonicalToolNames.map((tool) => normalizeLowercaseStringOrEmpty(tool));
|
||||
const availableTools = new Set(normalizedTools);
|
||||
const hasSessionsSpawn = availableTools.has("sessions_spawn");
|
||||
const hasUpdatePlanTool = availableTools.has("update_plan");
|
||||
@@ -430,10 +434,10 @@ export function buildAgentSystemPrompt(params: {
|
||||
? normalizeStructuredPromptSection(params.heartbeatPrompt)
|
||||
: undefined;
|
||||
const runtimeInfo = params.runtimeInfo;
|
||||
const runtimeChannel = runtimeInfo?.channel?.trim().toLowerCase();
|
||||
const runtimeChannel = normalizeOptionalLowercaseString(runtimeInfo?.channel);
|
||||
const runtimeCapabilities = runtimeInfo?.capabilities ?? [];
|
||||
const runtimeCapabilitiesLower = new Set(
|
||||
runtimeCapabilities.map((cap) => String(cap).trim().toLowerCase()).filter(Boolean),
|
||||
runtimeCapabilities.map((cap) => normalizeLowercaseStringOrEmpty(String(cap))).filter(Boolean),
|
||||
);
|
||||
const inlineButtonsEnabled = runtimeCapabilitiesLower.has("inlinebuttons");
|
||||
const messageChannelOptions = listDeliverableMessageChannels().join("|");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import {
|
||||
CORE_TOOL_GROUPS,
|
||||
resolveCoreToolProfilePolicy,
|
||||
@@ -17,7 +18,7 @@ const TOOL_NAME_ALIASES: Record<string, string> = {
|
||||
export const TOOL_GROUPS: Record<string, string[]> = { ...CORE_TOOL_GROUPS };
|
||||
|
||||
export function normalizeToolName(name: string) {
|
||||
const normalized = name.trim().toLowerCase();
|
||||
const normalized = normalizeLowercaseStringOrEmpty(name);
|
||||
return TOOL_NAME_ALIASES[normalized] ?? normalized;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user