mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-19 04:36:44 +00:00
refactor: simplify provider inference and zoned parsing helpers
This commit is contained in:
@@ -20,6 +20,16 @@ export type CommandAuthorization = {
|
||||
to?: string;
|
||||
};
|
||||
|
||||
type InferredProviderCandidate = {
|
||||
providerId: ChannelId;
|
||||
hadResolutionError: boolean;
|
||||
};
|
||||
|
||||
type InferredProviderProbe = {
|
||||
candidates: InferredProviderCandidate[];
|
||||
droppedResolutionError: boolean;
|
||||
};
|
||||
|
||||
function resolveProviderFromContext(
|
||||
ctx: MsgContext,
|
||||
cfg: OpenClawConfig,
|
||||
@@ -56,8 +66,25 @@ function resolveProviderFromContext(
|
||||
return { providerId: normalized, hadResolutionError: false };
|
||||
}
|
||||
}
|
||||
let droppedInferenceResolutionError = false;
|
||||
const configured = listChannelPlugins()
|
||||
const inferredProviders = probeInferredProviders(ctx, cfg);
|
||||
const inferred = inferredProviders.candidates;
|
||||
if (inferred.length === 1) {
|
||||
return {
|
||||
providerId: inferred[0].providerId,
|
||||
hadResolutionError: inferred[0].hadResolutionError,
|
||||
};
|
||||
}
|
||||
return {
|
||||
providerId: undefined,
|
||||
hadResolutionError:
|
||||
inferredProviders.droppedResolutionError ||
|
||||
inferred.some((entry) => entry.hadResolutionError),
|
||||
};
|
||||
}
|
||||
|
||||
function probeInferredProviders(ctx: MsgContext, cfg: OpenClawConfig): InferredProviderProbe {
|
||||
let droppedResolutionError = false;
|
||||
const candidates = listChannelPlugins()
|
||||
.map((plugin) => {
|
||||
const resolvedAllowFrom = resolveProviderAllowFrom({
|
||||
plugin,
|
||||
@@ -72,36 +99,19 @@ function resolveProviderFromContext(
|
||||
});
|
||||
if (allowFrom.length === 0) {
|
||||
if (resolvedAllowFrom.hadResolutionError) {
|
||||
droppedInferenceResolutionError = true;
|
||||
droppedResolutionError = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
providerId: plugin.id,
|
||||
allowFrom,
|
||||
hadResolutionError: resolvedAllowFrom.hadResolutionError,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(
|
||||
value,
|
||||
): value is {
|
||||
providerId: ChannelId;
|
||||
allowFrom: string[];
|
||||
hadResolutionError: boolean;
|
||||
} => Boolean(value),
|
||||
);
|
||||
const inferred = configured.filter((entry) => entry.allowFrom.length > 0);
|
||||
if (inferred.length === 1) {
|
||||
return {
|
||||
providerId: inferred[0].providerId,
|
||||
hadResolutionError: inferred[0].hadResolutionError,
|
||||
};
|
||||
}
|
||||
.filter((value): value is InferredProviderCandidate => Boolean(value));
|
||||
return {
|
||||
providerId: undefined,
|
||||
hadResolutionError:
|
||||
droppedInferenceResolutionError || configured.some((entry) => entry.hadResolutionError),
|
||||
candidates,
|
||||
droppedResolutionError,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
const SCRIPT_ATTRIBUTE_NAME_RE = /\s([^\s=/>]+)(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?/g;
|
||||
|
||||
/**
|
||||
* Compute SHA-256 CSP hashes for inline `<script>` blocks in an HTML string.
|
||||
* Only scripts without a `src` attribute are considered inline.
|
||||
@@ -24,57 +26,9 @@ export function computeInlineScriptHashes(html: string): string[] {
|
||||
}
|
||||
|
||||
function hasScriptSrcAttribute(openTag: string): boolean {
|
||||
let i = openTag.search(/\bscript\b/i);
|
||||
if (i < 0) {
|
||||
return false;
|
||||
}
|
||||
i += "script".length;
|
||||
while (i < openTag.length) {
|
||||
while (i < openTag.length && /\s/.test(openTag[i] ?? "")) {
|
||||
i += 1;
|
||||
}
|
||||
const current = openTag[i];
|
||||
if (!current || current === ">") {
|
||||
return false;
|
||||
}
|
||||
if (current === "/") {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
const nameStart = i;
|
||||
while (i < openTag.length && /[^\s=/>]/.test(openTag[i] ?? "")) {
|
||||
i += 1;
|
||||
}
|
||||
const attributeName = openTag.slice(nameStart, i).toLowerCase();
|
||||
if (attributeName === "src") {
|
||||
return true;
|
||||
}
|
||||
while (i < openTag.length && /\s/.test(openTag[i] ?? "")) {
|
||||
i += 1;
|
||||
}
|
||||
if ((openTag[i] ?? "") !== "=") {
|
||||
continue;
|
||||
}
|
||||
i += 1;
|
||||
while (i < openTag.length && /\s/.test(openTag[i] ?? "")) {
|
||||
i += 1;
|
||||
}
|
||||
const quote = openTag[i];
|
||||
if (quote === '"' || quote === "'") {
|
||||
i += 1;
|
||||
while (i < openTag.length && openTag[i] !== quote) {
|
||||
i += 1;
|
||||
}
|
||||
if (openTag[i] === quote) {
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
while (i < openTag.length && /[^\s>]/.test(openTag[i] ?? "")) {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Array.from(openTag.matchAll(SCRIPT_ATTRIBUTE_NAME_RE)).some(
|
||||
(match) => (match[1] ?? "").toLowerCase() === "src",
|
||||
);
|
||||
}
|
||||
|
||||
export function buildControlUiCspHeader(opts?: { inlineScriptHashes?: string[] }): string {
|
||||
|
||||
@@ -21,11 +21,8 @@ export function parseOffsetlessIsoDateTimeInTimeZone(raw: string, timeZone: stri
|
||||
if (!expectedParts) {
|
||||
return null;
|
||||
}
|
||||
if (!isOffsetlessIsoDateTime(raw)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
new Intl.DateTimeFormat("en-US", { timeZone }).format(new Date());
|
||||
getZonedDateTimeParts(Date.now(), timeZone);
|
||||
|
||||
const naiveMs = new Date(`${raw}Z`).getTime();
|
||||
if (Number.isNaN(naiveMs)) {
|
||||
@@ -69,36 +66,33 @@ function matchesOffsetlessIsoDateTimeParts(
|
||||
timeZone: string,
|
||||
expected: OffsetlessIsoDateTimeParts,
|
||||
): boolean {
|
||||
const utcDate = new Date(utcMs);
|
||||
if (utcDate.getUTCMilliseconds() !== expected.millisecond) {
|
||||
return false;
|
||||
}
|
||||
const parts = new Intl.DateTimeFormat("en-US", {
|
||||
timeZone,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false,
|
||||
hourCycle: "h23",
|
||||
}).formatToParts(utcDate);
|
||||
const getNumericPart = (type: string) => {
|
||||
const part = parts.find((candidate) => candidate.type === type);
|
||||
return Number.parseInt(part?.value ?? "0", 10);
|
||||
};
|
||||
const actual = getZonedDateTimeParts(utcMs, timeZone);
|
||||
return (
|
||||
getNumericPart("year") === expected.year &&
|
||||
getNumericPart("month") === expected.month &&
|
||||
getNumericPart("day") === expected.day &&
|
||||
getNumericPart("hour") === expected.hour &&
|
||||
getNumericPart("minute") === expected.minute &&
|
||||
getNumericPart("second") === expected.second
|
||||
actual.year === expected.year &&
|
||||
actual.month === expected.month &&
|
||||
actual.day === expected.day &&
|
||||
actual.hour === expected.hour &&
|
||||
actual.minute === expected.minute &&
|
||||
actual.second === expected.second &&
|
||||
actual.millisecond === expected.millisecond
|
||||
);
|
||||
}
|
||||
|
||||
function getTimeZoneOffsetMs(utcMs: number, timeZone: string): number {
|
||||
const parts = getZonedDateTimeParts(utcMs, timeZone);
|
||||
const localAsUtc = Date.UTC(
|
||||
parts.year,
|
||||
parts.month - 1,
|
||||
parts.day,
|
||||
parts.hour,
|
||||
parts.minute,
|
||||
parts.second,
|
||||
);
|
||||
|
||||
return localAsUtc - utcMs;
|
||||
}
|
||||
|
||||
function getZonedDateTimeParts(utcMs: number, timeZone: string): OffsetlessIsoDateTimeParts {
|
||||
const utcDate = new Date(utcMs);
|
||||
const parts = new Intl.DateTimeFormat("en-US", {
|
||||
timeZone,
|
||||
@@ -111,20 +105,17 @@ function getTimeZoneOffsetMs(utcMs: number, timeZone: string): number {
|
||||
hour12: false,
|
||||
hourCycle: "h23",
|
||||
}).formatToParts(utcDate);
|
||||
|
||||
const getNumericPart = (type: string) => {
|
||||
const part = parts.find((candidate) => candidate.type === type);
|
||||
return Number.parseInt(part?.value ?? "0", 10);
|
||||
};
|
||||
|
||||
const localAsUtc = Date.UTC(
|
||||
getNumericPart("year"),
|
||||
getNumericPart("month") - 1,
|
||||
getNumericPart("day"),
|
||||
getNumericPart("hour"),
|
||||
getNumericPart("minute"),
|
||||
getNumericPart("second"),
|
||||
);
|
||||
|
||||
return localAsUtc - utcMs;
|
||||
return {
|
||||
year: getNumericPart("year"),
|
||||
month: getNumericPart("month"),
|
||||
day: getNumericPart("day"),
|
||||
hour: getNumericPart("hour"),
|
||||
minute: getNumericPart("minute"),
|
||||
second: getNumericPart("second"),
|
||||
millisecond: utcDate.getUTCMilliseconds(),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user