mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 01:31:08 +00:00
refactor: dedupe extension lowercase readers
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/channel-actions";
|
||||
import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { extractToolSend } from "openclaw/plugin-sdk/tool-send";
|
||||
import { resolveBlueBubblesAccount } from "./accounts.js";
|
||||
import {
|
||||
@@ -100,7 +101,7 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
const normalizedTarget = currentChannelId
|
||||
? normalizeBlueBubblesMessagingTarget(currentChannelId)
|
||||
: undefined;
|
||||
const lowered = normalizedTarget?.trim().toLowerCase() ?? "";
|
||||
const lowered = normalizeOptionalLowercaseString(normalizedTarget) ?? "";
|
||||
const isGroupTarget =
|
||||
lowered.startsWith("chat_guid:") ||
|
||||
lowered.startsWith("chat_id:") ||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import crypto from "node:crypto";
|
||||
import path from "node:path";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
|
||||
import { assertMultipartActionOk, postMultipartFormData } from "./multipart.js";
|
||||
import {
|
||||
@@ -54,7 +57,7 @@ function ensureExtension(filename: string, extension: string, fallbackBase: stri
|
||||
}
|
||||
|
||||
function resolveVoiceInfo(filename: string, contentType?: string) {
|
||||
const normalizedType = contentType?.trim().toLowerCase();
|
||||
const normalizedType = normalizeOptionalLowercaseString(contentType);
|
||||
const extension = path.extname(filename).toLowerCase();
|
||||
const isMp3 =
|
||||
extension === ".mp3" || (normalizedType ? AUDIO_MIME_MP3.has(normalizedType) : false);
|
||||
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
sendMediaWithLeadingCaption,
|
||||
} from "openclaw/plugin-sdk/reply-payload";
|
||||
import { isPrivateNetworkOptInEnabled } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { downloadBlueBubblesAttachment } from "./attachments.js";
|
||||
import { markBlueBubblesChatRead, sendBlueBubblesTyping } from "./chat.js";
|
||||
import { resolveBlueBubblesConversationRoute } from "./conversation-route.js";
|
||||
@@ -93,7 +96,7 @@ const pendingOutboundMessageIds: PendingOutboundMessageId[] = [];
|
||||
let pendingOutboundMessageIdCounter = 0;
|
||||
|
||||
function normalizeSnippet(value: string): string {
|
||||
return stripMarkdown(value).replace(/\s+/g, " ").trim().toLowerCase();
|
||||
return normalizeOptionalLowercaseString(stripMarkdown(value).replace(/\s+/g, " ")) ?? "";
|
||||
}
|
||||
|
||||
type BlueBubblesChatRecord = Record<string, unknown>;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import crypto from "node:crypto";
|
||||
import { normalizeOptionalString, stripMarkdown } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
stripMarkdown,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
|
||||
import {
|
||||
getCachedBlueBubblesPrivateApiStatus,
|
||||
@@ -62,10 +66,10 @@ const EFFECT_MAP: Record<string, string> = {
|
||||
};
|
||||
|
||||
function resolveEffectId(raw?: string): string | undefined {
|
||||
if (!raw) {
|
||||
const trimmed = normalizeOptionalLowercaseString(raw);
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = raw.trim().toLowerCase();
|
||||
if (EFFECT_MAP[trimmed]) {
|
||||
return EFFECT_MAP[trimmed];
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { isRecord } from "./src/record-shared.js";
|
||||
|
||||
function listContainsBrowser(value: unknown): boolean {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.some((entry) => typeof entry === "string" && entry.trim().toLowerCase() === "browser")
|
||||
value.some((entry) => normalizeOptionalLowercaseString(entry) === "browser")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
wrapWebContent,
|
||||
writeCachedSearchPayload,
|
||||
} from "openclaw/plugin-sdk/provider-web-search";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
const EXA_SEARCH_ENDPOINT = "https://api.exa.ai/search";
|
||||
const EXA_SEARCH_TYPES = ["auto", "neural", "fast", "deep", "deep-reasoning", "instant"] as const;
|
||||
@@ -69,10 +70,10 @@ type ExaSearchResponse = {
|
||||
};
|
||||
|
||||
function normalizeExaFreshness(value: string | undefined): ExaFreshness | undefined {
|
||||
if (!value) {
|
||||
const trimmed = normalizeOptionalLowercaseString(value);
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim().toLowerCase();
|
||||
return EXA_FRESHNESS_VALUES.includes(trimmed as ExaFreshness)
|
||||
? (trimmed as ExaFreshness)
|
||||
: undefined;
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ensureAuthProfileStore,
|
||||
listProfilesForProvider,
|
||||
} from "openclaw/plugin-sdk/provider-auth";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { PROVIDER_ID, resolveCopilotForwardCompatModel } from "./models.js";
|
||||
import { buildGithubCopilotReplayPolicy } from "./replay-policy.js";
|
||||
import { wrapCopilotProviderStream } from "./stream.js";
|
||||
@@ -170,7 +171,9 @@ export default definePluginEntry({
|
||||
wrapStreamFn: wrapCopilotProviderStream,
|
||||
buildReplayPolicy: ({ modelId }) => buildGithubCopilotReplayPolicy(modelId),
|
||||
supportsXHighThinking: ({ modelId }) =>
|
||||
COPILOT_XHIGH_MODEL_IDS.includes(modelId.trim().toLowerCase() as never),
|
||||
COPILOT_XHIGH_MODEL_IDS.includes(
|
||||
(normalizeOptionalLowercaseString(modelId) ?? "") as never,
|
||||
),
|
||||
prepareRuntimeAuth: async (ctx) => {
|
||||
const { resolveCopilotApiToken } = await loadGithubCopilotRuntime();
|
||||
const token = await resolveCopilotApiToken({
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
ProviderRuntimeModel,
|
||||
} from "openclaw/plugin-sdk/core";
|
||||
import { normalizeModelCompat } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export const PROVIDER_ID = "github-copilot";
|
||||
const CODEX_GPT_54_MODEL_ID = "gpt-5.4";
|
||||
@@ -14,7 +15,7 @@ const DEFAULT_MAX_TOKENS = 8192;
|
||||
export function resolveCopilotTransportApi(
|
||||
modelId: string,
|
||||
): "anthropic-messages" | "openai-responses" {
|
||||
return modelId.trim().toLowerCase().includes("claude")
|
||||
return (normalizeOptionalLowercaseString(modelId) ?? "").includes("claude")
|
||||
? "anthropic-messages"
|
||||
: "openai-responses";
|
||||
}
|
||||
@@ -28,14 +29,15 @@ export function resolveCopilotForwardCompatModel(
|
||||
}
|
||||
|
||||
// If the model is already in the registry, let the normal path handle it.
|
||||
const existing = ctx.modelRegistry.find(PROVIDER_ID, trimmedModelId.toLowerCase());
|
||||
const lowerModelId = normalizeOptionalLowercaseString(trimmedModelId) ?? "";
|
||||
const existing = ctx.modelRegistry.find(PROVIDER_ID, lowerModelId);
|
||||
if (existing) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// For gpt-5.4 specifically, clone from the gpt-5.2-codex template
|
||||
// to preserve any special settings the registry has for codex models.
|
||||
if (trimmedModelId.toLowerCase() === CODEX_GPT_54_MODEL_ID) {
|
||||
if (lowerModelId === CODEX_GPT_54_MODEL_ID) {
|
||||
for (const templateId of CODEX_TEMPLATE_MODEL_IDS) {
|
||||
const template = ctx.modelRegistry.find(
|
||||
PROVIDER_ID,
|
||||
@@ -58,7 +60,6 @@ export function resolveCopilotForwardCompatModel(
|
||||
// model isn't available on the user's plan. This lets new models be used
|
||||
// by simply adding them to agents.defaults.models in openclaw.json — no
|
||||
// code change required.
|
||||
const lowerModelId = trimmedModelId.toLowerCase();
|
||||
const reasoning = /^o[13](\b|$)/.test(lowerModelId);
|
||||
return normalizeModelCompat({
|
||||
id: trimmedModelId,
|
||||
|
||||
@@ -106,7 +106,7 @@ export function resolveGoogleGeminiForwardCompatModel(params: {
|
||||
ctx: ProviderResolveDynamicModelContext;
|
||||
}): ProviderRuntimeModel | undefined {
|
||||
const trimmed = params.ctx.modelId.trim();
|
||||
const lower = trimmed.toLowerCase();
|
||||
const lower = normalizeOptionalLowercaseString(trimmed) ?? "";
|
||||
|
||||
let family: GoogleForwardCompatFamily;
|
||||
let patch: Partial<ProviderRuntimeModel> | undefined;
|
||||
@@ -178,7 +178,7 @@ export function resolveGoogleGeminiForwardCompatModel(params: {
|
||||
}
|
||||
|
||||
export function isModernGoogleModel(modelId: string): boolean {
|
||||
const lower = modelId.trim().toLowerCase();
|
||||
const lower = normalizeOptionalLowercaseString(modelId) ?? "";
|
||||
return (
|
||||
lower.startsWith("gemini-2.5") || lower.startsWith("gemini-3") || lower.startsWith(GEMMA_PREFIX)
|
||||
);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import {
|
||||
definePluginEntry,
|
||||
type OpenClawPluginApi,
|
||||
@@ -54,10 +57,7 @@ function formatGroupList(): string {
|
||||
}
|
||||
|
||||
function parseDurationMs(input: string | undefined): number | null {
|
||||
if (!input) {
|
||||
return null;
|
||||
}
|
||||
const raw = input.trim().toLowerCase();
|
||||
const raw = normalizeOptionalLowercaseString(input);
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export type UrbitBaseUrlValidation =
|
||||
| { ok: true; baseUrl: string; hostname: string }
|
||||
@@ -8,6 +9,10 @@ function hasScheme(value: string): boolean {
|
||||
return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
|
||||
}
|
||||
|
||||
export function normalizeUrbitHostname(hostname: string | undefined): string {
|
||||
return normalizeLowercaseStringOrEmpty(hostname).replace(/\.$/, "");
|
||||
}
|
||||
|
||||
export function validateUrbitBaseUrl(raw: string): UrbitBaseUrlValidation {
|
||||
const trimmed = String(raw ?? "").trim();
|
||||
if (!trimmed) {
|
||||
@@ -31,7 +36,7 @@ export function validateUrbitBaseUrl(raw: string): UrbitBaseUrlValidation {
|
||||
return { ok: false, error: "URL must not include credentials" };
|
||||
}
|
||||
|
||||
const hostname = parsed.hostname.trim().toLowerCase().replace(/\.$/, "");
|
||||
const hostname = normalizeUrbitHostname(parsed.hostname);
|
||||
if (!hostname) {
|
||||
return { ok: false, error: "Invalid hostname" };
|
||||
}
|
||||
@@ -49,7 +54,7 @@ export function validateUrbitBaseUrl(raw: string): UrbitBaseUrlValidation {
|
||||
}
|
||||
|
||||
export function isBlockedUrbitHostname(hostname: string): boolean {
|
||||
const normalized = hostname.trim().toLowerCase().replace(/\.$/, "");
|
||||
const normalized = normalizeUrbitHostname(hostname);
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ export {
|
||||
ssrfPolicyFromDangerouslyAllowPrivateNetwork,
|
||||
ssrfPolicyFromAllowPrivateNetwork,
|
||||
} from "openclaw/plugin-sdk/ssrf-runtime";
|
||||
import { validateUrbitBaseUrl } from "./base-url.js";
|
||||
import { normalizeUrbitHostname, validateUrbitBaseUrl } from "./base-url.js";
|
||||
import { UrbitUrlError } from "./errors.js";
|
||||
|
||||
export type UrbitContext = {
|
||||
@@ -13,7 +13,7 @@ export type UrbitContext = {
|
||||
};
|
||||
|
||||
export function resolveShipFromHostname(hostname: string): string {
|
||||
const trimmed = hostname.trim().toLowerCase().replace(/\.$/, "");
|
||||
const trimmed = normalizeUrbitHostname(hostname);
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { format } from "node:util";
|
||||
import type { Command } from "commander";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { sleep } from "../api.js";
|
||||
import type { VoiceCallConfig } from "./config.js";
|
||||
import type { VoiceCallRuntime } from "./runtime.js";
|
||||
@@ -28,7 +29,7 @@ function writeStdoutJson(value: unknown): void {
|
||||
}
|
||||
|
||||
function resolveMode(input: string): "off" | "serve" | "funnel" {
|
||||
const raw = input.trim().toLowerCase();
|
||||
const raw = normalizeOptionalLowercaseString(input) ?? "";
|
||||
if (raw === "serve" || raw === "off") {
|
||||
return raw;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import type { EndReason } from "../../types.js";
|
||||
|
||||
const TERMINAL_PROVIDER_STATUS_TO_END_REASON: Record<string, EndReason> = {
|
||||
@@ -9,7 +10,7 @@ const TERMINAL_PROVIDER_STATUS_TO_END_REASON: Record<string, EndReason> = {
|
||||
};
|
||||
|
||||
export function normalizeProviderStatus(status: string | null | undefined): string {
|
||||
const normalized = status?.trim().toLowerCase();
|
||||
const normalized = normalizeOptionalLowercaseString(status);
|
||||
return normalized && normalized.length > 0 ? normalized : "unknown";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
export const XAI_BASE_URL = "https://api.x.ai/v1";
|
||||
export const XAI_DEFAULT_MODEL_ID = "grok-4";
|
||||
@@ -201,7 +202,8 @@ export function buildXaiCatalogModels(): ModelDefinitionConfig[] {
|
||||
}
|
||||
|
||||
export function resolveXaiCatalogEntry(modelId: string) {
|
||||
const lower = modelId.trim().toLowerCase();
|
||||
const trimmed = modelId.trim();
|
||||
const lower = normalizeOptionalLowercaseString(modelId) ?? "";
|
||||
const exact = XAI_MODEL_CATALOG.find((entry) => entry.id.toLowerCase() === lower);
|
||||
if (exact) {
|
||||
return toModelDefinition(exact);
|
||||
@@ -211,8 +213,8 @@ export function resolveXaiCatalogEntry(modelId: string) {
|
||||
}
|
||||
if (lower.startsWith("grok-code-fast")) {
|
||||
return toModelDefinition({
|
||||
id: modelId.trim(),
|
||||
name: modelId.trim(),
|
||||
id: trimmed,
|
||||
name: trimmed,
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
contextWindow: XAI_CODE_CONTEXT_WINDOW,
|
||||
@@ -234,8 +236,8 @@ export function resolveXaiCatalogEntry(modelId: string) {
|
||||
? { input: 5, output: 25, cacheRead: 1.25, cacheWrite: 0 }
|
||||
: XAI_GROK_4_COST;
|
||||
return toModelDefinition({
|
||||
id: modelId.trim(),
|
||||
name: modelId.trim(),
|
||||
id: trimmed,
|
||||
name: trimmed,
|
||||
reasoning: lower.includes("mini"),
|
||||
input: ["text"],
|
||||
contextWindow: XAI_LEGACY_CONTEXT_WINDOW,
|
||||
@@ -249,8 +251,8 @@ export function resolveXaiCatalogEntry(modelId: string) {
|
||||
lower.startsWith("grok-4-fast")
|
||||
) {
|
||||
return toModelDefinition({
|
||||
id: modelId.trim(),
|
||||
name: modelId.trim(),
|
||||
id: trimmed,
|
||||
name: trimmed,
|
||||
reasoning: !lower.includes("non-reasoning"),
|
||||
input: ["text", "image"],
|
||||
contextWindow: XAI_LARGE_CONTEXT_WINDOW,
|
||||
|
||||
@@ -3,13 +3,14 @@ import type {
|
||||
ProviderRuntimeModel,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeModelCompat } from "openclaw/plugin-sdk/provider-model-shared";
|
||||
import { normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { applyXaiModelCompat } from "./api.js";
|
||||
import { resolveXaiCatalogEntry, XAI_BASE_URL } from "./model-definitions.js";
|
||||
|
||||
const XAI_MODERN_MODEL_PREFIXES = ["grok-3", "grok-4", "grok-code-fast"] as const;
|
||||
|
||||
export function isModernXaiModel(modelId: string): boolean {
|
||||
const lower = modelId.trim().toLowerCase();
|
||||
const lower = normalizeOptionalLowercaseString(modelId) ?? "";
|
||||
if (!lower || lower.includes("multi-agent")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user