chore: Enable "curly" rule to avoid single-statement if confusion/errors.

This commit is contained in:
cpojer
2026-01-31 16:19:20 +09:00
parent 009b16fab8
commit 5ceff756e1
1266 changed files with 27871 additions and 9393 deletions

View File

@@ -52,6 +52,8 @@ export async function runAgentStep(params: {
},
timeoutMs: stepWaitMs + 2000,
});
if (wait?.status !== "ok") return undefined;
if (wait?.status !== "ok") {
return undefined;
}
return await readLatestAssistantReply({ sessionKey: params.sessionKey });
}

View File

@@ -59,16 +59,22 @@ export function createAgentsListTool(opts?: {
const configuredNameMap = new Map<string, string>();
for (const entry of configuredAgents) {
const name = entry?.name?.trim() ?? "";
if (!name) continue;
if (!name) {
continue;
}
configuredNameMap.set(normalizeAgentId(entry.id), name);
}
const allowed = new Set<string>();
allowed.add(requesterAgentId);
if (allowAny) {
for (const id of configuredIds) allowed.add(id);
for (const id of configuredIds) {
allowed.add(id);
}
} else {
for (const id of allowSet) allowed.add(id);
for (const id of allowSet) {
allowed.add(id);
}
}
const all = Array.from(allowed);

View File

@@ -70,7 +70,9 @@ async function resolveBrowserNodeTarget(params: {
if (params.sandboxBridgeUrl?.trim() && params.target !== "node" && !params.requestedNode) {
return null;
}
if (params.target && params.target !== "node") return null;
if (params.target && params.target !== "node") {
return null;
}
if (mode === "manual" && params.target !== "node" && !params.requestedNode) {
return null;
}
@@ -101,7 +103,9 @@ async function resolveBrowserNodeTarget(params: {
);
}
if (mode === "manual") return null;
if (mode === "manual") {
return null;
}
if (browserNodes.length === 1) {
const node = browserNodes[0];
@@ -152,7 +156,9 @@ async function callBrowserProxy(params: {
}
async function persistProxyFiles(files: BrowserProxyFile[] | undefined) {
if (!files || files.length === 0) return new Map<string, string>();
if (!files || files.length === 0) {
return new Map<string, string>();
}
const mapping = new Map<string, string>();
for (const file of files) {
const buffer = Buffer.from(file.base64, "base64");
@@ -163,7 +169,9 @@ async function persistProxyFiles(files: BrowserProxyFile[] | undefined) {
}
function applyProxyPaths(result: unknown, mapping: Map<string, string>) {
if (!result || typeof result !== "object") return;
if (!result || typeof result !== "object") {
return;
}
const obj = result as Record<string, unknown>;
if (typeof obj.path === "string" && mapping.has(obj.path)) {
obj.path = mapping.get(obj.path);
@@ -402,8 +410,11 @@ export function createBrowserTool(opts?: {
});
return jsonResult(result);
}
if (targetId) await browserCloseTab(baseUrl, targetId, { profile });
else await browserAct(baseUrl, { kind: "close" }, { profile });
if (targetId) {
await browserCloseTab(baseUrl, targetId, { profile });
} else {
await browserAct(baseUrl, { kind: "close" }, { profile });
}
return jsonResult({ ok: true });
}
case "snapshot": {
@@ -592,7 +603,9 @@ export function createBrowserTool(opts?: {
}
case "upload": {
const paths = Array.isArray(params.paths) ? params.paths.map((p) => String(p)) : [];
if (paths.length === 0) throw new Error("paths required");
if (paths.length === 0) {
throw new Error("paths required");
}
const ref = readStringParam(params, "ref");
const inputRef = readStringParam(params, "inputRef");
const element = readStringParam(params, "element");

View File

@@ -164,7 +164,9 @@ export function createCanvasTool(): AnyAgentTool {
: typeof params.jsonlPath === "string" && params.jsonlPath.trim()
? await fs.readFile(params.jsonlPath.trim(), "utf8")
: "";
if (!jsonl.trim()) throw new Error("jsonl or jsonlPath required");
if (!jsonl.trim()) {
throw new Error("jsonl or jsonlPath required");
}
await invoke("canvas.a2ui.pushJSONL", { jsonl });
return jsonResult({ ok: true });
}

View File

@@ -25,7 +25,9 @@ export function createActionGate<T extends Record<string, boolean | undefined>>(
): ActionGate<T> {
return (key, defaultValue = true) => {
const value = actions?.[key];
if (value === undefined) return defaultValue;
if (value === undefined) {
return defaultValue;
}
return value !== false;
};
}
@@ -48,12 +50,16 @@ export function readStringParam(
const { required = false, trim = true, label = key, allowEmpty = false } = options;
const raw = params[key];
if (typeof raw !== "string") {
if (required) throw new Error(`${label} required`);
if (required) {
throw new Error(`${label} required`);
}
return undefined;
}
const value = trim ? raw.trim() : raw;
if (!value && !allowEmpty) {
if (required) throw new Error(`${label} required`);
if (required) {
throw new Error(`${label} required`);
}
return undefined;
}
return value;
@@ -71,9 +77,13 @@ export function readStringOrNumberParam(
}
if (typeof raw === "string") {
const value = raw.trim();
if (value) return value;
if (value) {
return value;
}
}
if (required) {
throw new Error(`${label} required`);
}
if (required) throw new Error(`${label} required`);
return undefined;
}
@@ -91,11 +101,15 @@ export function readNumberParam(
const trimmed = raw.trim();
if (trimmed) {
const parsed = Number.parseFloat(trimmed);
if (Number.isFinite(parsed)) value = parsed;
if (Number.isFinite(parsed)) {
value = parsed;
}
}
}
if (value === undefined) {
if (required) throw new Error(`${label} required`);
if (required) {
throw new Error(`${label} required`);
}
return undefined;
}
return integer ? Math.trunc(value) : value;
@@ -124,7 +138,9 @@ export function readStringArrayParam(
.map((entry) => entry.trim())
.filter(Boolean);
if (values.length === 0) {
if (required) throw new Error(`${label} required`);
if (required) {
throw new Error(`${label} required`);
}
return undefined;
}
return values;
@@ -132,12 +148,16 @@ export function readStringArrayParam(
if (typeof raw === "string") {
const value = raw.trim();
if (!value) {
if (required) throw new Error(`${label} required`);
if (required) {
throw new Error(`${label} required`);
}
return undefined;
}
return [value];
}
if (required) throw new Error(`${label} required`);
if (required) {
throw new Error(`${label} required`);
}
return undefined;
}

View File

@@ -51,12 +51,16 @@ type ChatMessage = {
function stripExistingContext(text: string) {
const index = text.indexOf(REMINDER_CONTEXT_MARKER);
if (index === -1) return text;
if (index === -1) {
return text;
}
return text.slice(0, index).trim();
}
function truncateText(input: string, maxLen: number) {
if (input.length <= maxLen) return input;
if (input.length <= maxLen) {
return input;
}
const truncated = truncateUtf16Safe(input, Math.max(0, maxLen - 3)).trimEnd();
return `${truncated}...`;
}
@@ -67,17 +71,25 @@ function normalizeContextText(raw: string) {
function extractMessageText(message: ChatMessage): { role: string; text: string } | null {
const role = typeof message.role === "string" ? message.role : "";
if (role !== "user" && role !== "assistant") return null;
if (role !== "user" && role !== "assistant") {
return null;
}
const content = message.content;
if (typeof content === "string") {
const normalized = normalizeContextText(content);
return normalized ? { role, text: normalized } : null;
}
if (!Array.isArray(content)) return null;
if (!Array.isArray(content)) {
return null;
}
const chunks: string[] = [];
for (const block of content) {
if (!block || typeof block !== "object") continue;
if ((block as { type?: unknown }).type !== "text") continue;
if (!block || typeof block !== "object") {
continue;
}
if ((block as { type?: unknown }).type !== "text") {
continue;
}
const text = (block as { text?: unknown }).text;
if (typeof text === "string" && text.trim()) {
chunks.push(text);
@@ -96,9 +108,13 @@ async function buildReminderContextLines(params: {
REMINDER_CONTEXT_MESSAGES_MAX,
Math.max(0, Math.floor(params.contextMessages)),
);
if (maxMessages <= 0) return [];
if (maxMessages <= 0) {
return [];
}
const sessionKey = params.agentSessionKey?.trim();
if (!sessionKey) return [];
if (!sessionKey) {
return [];
}
const cfg = loadConfig();
const { mainKey, alias } = resolveMainSessionAlias(cfg);
const resolvedKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey });
@@ -112,7 +128,9 @@ async function buildReminderContextLines(params: {
.map((msg) => extractMessageText(msg as ChatMessage))
.filter((msg): msg is { role: string; text: string } => Boolean(msg));
const recent = parsed.slice(-maxMessages);
if (recent.length === 0) return [];
if (recent.length === 0) {
return [];
}
const lines: string[] = [];
let total = 0;
for (const entry of recent) {
@@ -120,7 +138,9 @@ async function buildReminderContextLines(params: {
const text = truncateText(entry.text, REMINDER_CONTEXT_PER_MESSAGE_MAX);
const line = `- ${label}: ${text}`;
total += line.length;
if (total > REMINDER_CONTEXT_TOTAL_MAX) break;
if (total > REMINDER_CONTEXT_TOTAL_MAX) {
break;
}
lines.push(line);
}
return lines;

View File

@@ -30,8 +30,12 @@ import {
} from "./common.js";
function readParentIdParam(params: Record<string, unknown>): string | null | undefined {
if (params.clearParent === true) return null;
if (params.parentId === null) return null;
if (params.clearParent === true) {
return null;
}
if (params.parentId === null) {
return null;
}
return readStringParam(params, "parentId");
}

View File

@@ -60,7 +60,9 @@ export async function handleDiscordMessagingAction(
);
const accountId = readStringParam(params, "accountId");
const normalizeMessage = (message: unknown) => {
if (!message || typeof message !== "object") return message;
if (!message || typeof message !== "object") {
return message;
}
return withNormalizedTimestamp(
message as Record<string, unknown>,
(message as { timestamp?: unknown }).timestamp,

View File

@@ -14,7 +14,9 @@ import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";
import { callGatewayTool } from "./gateway.js";
function resolveBaseHashFromSnapshot(snapshot: unknown): string | undefined {
if (!snapshot || typeof snapshot !== "object") return undefined;
if (!snapshot || typeof snapshot !== "object") {
return undefined;
}
const hashValue = (snapshot as { hash?: unknown }).hash;
const rawValue = (snapshot as { raw?: unknown }).raw;
const hash = resolveConfigSnapshotHash({

View File

@@ -12,7 +12,9 @@ export function decodeDataUrl(dataUrl: string): {
} {
const trimmed = dataUrl.trim();
const match = /^data:([^;,]+);base64,([a-z0-9+/=\r\n]+)$/i.exec(trimmed);
if (!match) throw new Error("Invalid data URL (expected base64 data: URL).");
if (!match) {
throw new Error("Invalid data URL (expected base64 data: URL).");
}
const mimeType = (match[1] ?? "").trim().toLowerCase();
if (!mimeType.startsWith("image/")) {
throw new Error(`Unsupported data URL type: ${mimeType || "unknown"}`);
@@ -43,7 +45,9 @@ export function coerceImageAssistantText(params: {
throw new Error(`Image model failed (${params.provider}/${params.model}): ${errorMessage}`);
}
const text = extractAssistantText(params.message);
if (text.trim()) return text.trim();
if (text.trim()) {
return text.trim();
}
throw new Error(`Image model returned no text (${params.provider}/${params.model}).`);
}

View File

@@ -148,7 +148,9 @@ describe("image tool implicit imageModel config", () => {
};
const tool = createImageTool({ config: cfg, agentDir, sandboxRoot });
expect(tool).not.toBeNull();
if (!tool) throw new Error("expected image tool");
if (!tool) {
throw new Error("expected image tool");
}
await expect(tool.execute("t1", { image: "https://example.com/a.png" })).rejects.toThrow(
/Sandboxed image tool does not allow remote URLs/i,
@@ -198,7 +200,9 @@ describe("image tool implicit imageModel config", () => {
};
const tool = createImageTool({ config: cfg, agentDir, sandboxRoot });
expect(tool).not.toBeNull();
if (!tool) throw new Error("expected image tool");
if (!tool) {
throw new Error("expected image tool");
}
const res = await tool.execute("t1", {
prompt: "Describe the image.",
@@ -266,7 +270,9 @@ describe("image tool MiniMax VLM routing", () => {
};
const tool = createImageTool({ config: cfg, agentDir });
expect(tool).not.toBeNull();
if (!tool) throw new Error("expected image tool");
if (!tool) {
throw new Error("expected image tool");
}
const res = await tool.execute("t1", {
prompt: "Describe the image.",
@@ -308,7 +314,9 @@ describe("image tool MiniMax VLM routing", () => {
};
const tool = createImageTool({ config: cfg, agentDir });
expect(tool).not.toBeNull();
if (!tool) throw new Error("expected image tool");
if (!tool) {
throw new Error("expected image tool");
}
await expect(
tool.execute("t1", {

View File

@@ -48,7 +48,9 @@ function resolveDefaultModelRef(cfg?: OpenClawConfig): {
}
function hasAuthForProvider(params: { provider: string; agentDir: string }): boolean {
if (resolveEnvApiKey(params.provider)?.apiKey) return true;
if (resolveEnvApiKey(params.provider)?.apiKey) {
return true;
}
const store = ensureAuthProfileStore(params.agentDir, {
allowKeychainPrompt: false,
});
@@ -89,8 +91,12 @@ export function resolveImageModelConfigForTool(params: {
const fallbacks: string[] = [];
const addFallback = (modelRef: string | null) => {
const ref = (modelRef ?? "").trim();
if (!ref) return;
if (fallbacks.includes(ref)) return;
if (!ref) {
return;
}
if (fallbacks.includes(ref)) {
return;
}
fallbacks.push(ref);
};
@@ -117,8 +123,12 @@ export function resolveImageModelConfigForTool(params: {
}
if (preferred?.trim()) {
if (openaiOk) addFallback("openai/gpt-5-mini");
if (anthropicOk) addFallback("anthropic/claude-opus-4-5");
if (openaiOk) {
addFallback("openai/gpt-5-mini");
}
if (anthropicOk) {
addFallback("anthropic/claude-opus-4-5");
}
// Don't duplicate primary in fallbacks.
const pruned = fallbacks.filter((ref) => ref !== preferred);
return {
@@ -129,7 +139,9 @@ export function resolveImageModelConfigForTool(params: {
// Cross-provider fallback when we can't pair with the primary provider.
if (openaiOk) {
if (anthropicOk) addFallback("anthropic/claude-opus-4-5");
if (anthropicOk) {
addFallback("anthropic/claude-opus-4-5");
}
return {
primary: "openai/gpt-5-mini",
...(fallbacks.length ? { fallbacks } : {}),
@@ -305,7 +317,9 @@ export function createImageTool(options?: {
cfg: options?.config,
agentDir,
});
if (!imageModelConfig) return null;
if (!imageModelConfig) {
return null;
}
// If model has native vision, images in the prompt are auto-injected
// so this tool is only needed when image wasn't provided in the prompt
@@ -329,7 +343,9 @@ export function createImageTool(options?: {
const imageRaw = imageRawInput.startsWith("@")
? imageRawInput.slice(1).trim()
: imageRawInput;
if (!imageRaw) throw new Error("image required");
if (!imageRaw) {
throw new Error("image required");
}
// The tool accepts file paths, file/data URLs, or http(s) URLs. In some
// agent/model contexts, images can be referenced as pseudo-URIs like
@@ -371,8 +387,12 @@ export function createImageTool(options?: {
}
const resolvedImage = (() => {
if (sandboxRoot) return imageRaw;
if (imageRaw.startsWith("~")) return resolveUserPath(imageRaw);
if (sandboxRoot) {
return imageRaw;
}
if (imageRaw.startsWith("~")) {
return resolveUserPath(imageRaw);
}
return imageRaw;
})();
const resolvedPathInfo: { resolved: string; rewrittenFrom?: string } = isDataUrl

View File

@@ -34,7 +34,9 @@ describe("memory tools", () => {
const cfg = { agents: { list: [{ id: "main", default: true }] } };
const tool = createMemorySearchTool({ config: cfg });
expect(tool).not.toBeNull();
if (!tool) throw new Error("tool missing");
if (!tool) {
throw new Error("tool missing");
}
const result = await tool.execute("call_1", { query: "hello" });
expect(result.details).toEqual({
@@ -48,7 +50,9 @@ describe("memory tools", () => {
const cfg = { agents: { list: [{ id: "main", default: true }] } };
const tool = createMemoryGetTool({ config: cfg });
expect(tool).not.toBeNull();
if (!tool) throw new Error("tool missing");
if (!tool) {
throw new Error("tool missing");
}
const result = await tool.execute("call_2", { path: "memory/NOPE.md" });
expect(result.details).toEqual({

View File

@@ -24,12 +24,16 @@ export function createMemorySearchTool(options: {
agentSessionKey?: string;
}): AnyAgentTool | null {
const cfg = options.config;
if (!cfg) return null;
if (!cfg) {
return null;
}
const agentId = resolveSessionAgentId({
sessionKey: options.agentSessionKey,
config: cfg,
});
if (!resolveMemorySearchConfig(cfg, agentId)) return null;
if (!resolveMemorySearchConfig(cfg, agentId)) {
return null;
}
return {
label: "Memory Search",
name: "memory_search",
@@ -73,12 +77,16 @@ export function createMemoryGetTool(options: {
agentSessionKey?: string;
}): AnyAgentTool | null {
const cfg = options.config;
if (!cfg) return null;
if (!cfg) {
return null;
}
const agentId = resolveSessionAgentId({
sessionKey: options.agentSessionKey,
config: cfg,
});
if (!resolveMemorySearchConfig(cfg, agentId)) return null;
if (!resolveMemorySearchConfig(cfg, agentId)) {
return null;
}
return {
label: "Memory Get",
name: "memory_get",

View File

@@ -88,8 +88,12 @@ function buildSendSchema(options: { includeButtons: boolean; includeCards: boole
),
),
};
if (!options.includeButtons) delete props.buttons;
if (!options.includeCards) delete props.card;
if (!options.includeButtons) {
delete props.buttons;
}
if (!options.includeCards) {
delete props.card;
}
return props;
}
@@ -262,7 +266,9 @@ function buildMessageToolSchema(cfg: OpenClawConfig) {
function resolveAgentAccountId(value?: string): string | undefined {
const trimmed = value?.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
return normalizeAccountId(trimmed);
}
@@ -272,9 +278,13 @@ function filterActionsForContext(params: {
currentChannelId?: string;
}): ChannelMessageActionName[] {
const channel = normalizeMessageChannel(params.channel);
if (!channel || channel !== "bluebubbles") return params.actions;
if (!channel || channel !== "bluebubbles") {
return params.actions;
}
const currentChannelId = params.currentChannelId?.trim();
if (!currentChannelId) return params.actions;
if (!currentChannelId) {
return params.actions;
}
const normalizedTarget =
normalizeTargetForProvider(channel, currentChannelId) ?? currentChannelId;
const lowered = normalizedTarget.trim().toLowerCase();
@@ -283,7 +293,9 @@ function filterActionsForContext(params: {
lowered.startsWith("chat_id:") ||
lowered.startsWith("chat_identifier:") ||
lowered.startsWith("group:");
if (isGroupTarget) return params.actions;
if (isGroupTarget) {
return params.actions;
}
return params.actions.filter((action) => !BLUEBUBBLES_GROUP_ACTIONS.has(action));
}
@@ -396,7 +408,9 @@ export function createMessageTool(options?: MessageToolOptions): AnyAgentTool {
});
const toolResult = getToolResult(result);
if (toolResult) return toolResult;
if (toolResult) {
return toolResult;
}
return jsonResult(result.payload);
},
};

View File

@@ -89,11 +89,15 @@ function pickDefaultNode(nodes: NodeListNode[]): NodeListNode | null {
const withCanvas = nodes.filter((n) =>
Array.isArray(n.caps) ? n.caps.includes("canvas") : true,
);
if (withCanvas.length === 0) return null;
if (withCanvas.length === 0) {
return null;
}
const connected = withCanvas.filter((n) => n.connected);
const candidates = connected.length > 0 ? connected : withCanvas;
if (candidates.length === 1) return candidates[0];
if (candidates.length === 1) {
return candidates[0];
}
const local = candidates.filter(
(n) =>
@@ -101,7 +105,9 @@ function pickDefaultNode(nodes: NodeListNode[]): NodeListNode | null {
typeof n.nodeId === "string" &&
n.nodeId.startsWith("mac-"),
);
if (local.length === 1) return local[0];
if (local.length === 1) {
return local[0];
}
return null;
}
@@ -119,22 +125,34 @@ export function resolveNodeIdFromList(
if (!q) {
if (allowDefault) {
const picked = pickDefaultNode(nodes);
if (picked) return picked.nodeId;
if (picked) {
return picked.nodeId;
}
}
throw new Error("node required");
}
const qNorm = normalizeNodeKey(q);
const matches = nodes.filter((n) => {
if (n.nodeId === q) return true;
if (typeof n.remoteIp === "string" && n.remoteIp === q) return true;
if (n.nodeId === q) {
return true;
}
if (typeof n.remoteIp === "string" && n.remoteIp === q) {
return true;
}
const name = typeof n.displayName === "string" ? n.displayName : "";
if (name && normalizeNodeKey(name) === qNorm) return true;
if (q.length >= 6 && n.nodeId.startsWith(q)) return true;
if (name && normalizeNodeKey(name) === qNorm) {
return true;
}
if (q.length >= 6 && n.nodeId.startsWith(q)) {
return true;
}
return false;
});
if (matches.length === 1) return matches[0].nodeId;
if (matches.length === 1) {
return matches[0].nodeId;
}
if (matches.length === 0) {
const known = nodes
.map((n) => n.displayName || n.remoteIp || n.nodeId)

View File

@@ -55,7 +55,9 @@ const SessionStatusToolSchema = Type.Object({
function formatApiKeySnippet(apiKey: string): string {
const compact = apiKey.replace(/\s+/g, "");
if (!compact) return "unknown";
if (!compact) {
return "unknown";
}
const edge = compact.length >= 12 ? 6 : 4;
const head = compact.slice(0, edge);
const tail = compact.slice(-edge);
@@ -69,7 +71,9 @@ function resolveModelAuthLabel(params: {
agentDir?: string;
}): string | undefined {
const resolvedProvider = params.provider?.trim();
if (!resolvedProvider) return undefined;
if (!resolvedProvider) {
return undefined;
}
const providerKey = normalizeProviderId(resolvedProvider);
const store = ensureAuthProfileStore(params.agentDir, {
@@ -126,7 +130,9 @@ function resolveSessionEntry(params: {
mainKey: string;
}): { key: string; entry: SessionEntry } | null {
const keyRaw = params.keyRaw.trim();
if (!keyRaw) return null;
if (!keyRaw) {
return null;
}
const internal = resolveInternalSessionKey({
key: keyRaw,
alias: params.alias,
@@ -149,7 +155,9 @@ function resolveSessionEntry(params: {
for (const key of candidates) {
const entry = params.store[key];
if (entry) return { key, entry };
if (entry) {
return { key, entry };
}
}
return null;
@@ -161,11 +169,17 @@ function resolveSessionKeyFromSessionId(params: {
agentId?: string;
}): string | null {
const trimmed = params.sessionId.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const { store } = loadCombinedSessionStoreForGateway(params.cfg);
const match = Object.entries(store).find(([key, entry]) => {
if (entry?.sessionId !== trimmed) return false;
if (!params.agentId) return true;
if (entry?.sessionId !== trimmed) {
return false;
}
if (!params.agentId) {
return true;
}
return resolveAgentIdFromSessionKey(key) === params.agentId;
});
return match?.[0] ?? null;
@@ -186,8 +200,12 @@ async function resolveModelOverride(params: {
}
> {
const raw = params.raw.trim();
if (!raw) return { kind: "reset" };
if (raw.toLowerCase() === "default") return { kind: "reset" };
if (!raw) {
return { kind: "reset" };
}
if (raw.toLowerCase() === "default") {
return { kind: "reset" };
}
const configDefault = resolveDefaultModelForAgent({
cfg: params.cfg,
@@ -256,7 +274,9 @@ export function createSessionStatusTool(opts?: {
opts?.agentSessionKey ?? requestedKeyRaw,
);
const ensureAgentAccess = (targetAgentId: string) => {
if (targetAgentId === requesterAgentId) return;
if (targetAgentId === requesterAgentId) {
return;
}
// Gate cross-agent access behind tools.agentToAgent settings.
if (!a2aPolicy.enabled) {
throw new Error(

View File

@@ -46,7 +46,9 @@ export async function resolveAnnounceTarget(params: {
const accountId =
(typeof deliveryContext?.accountId === "string" ? deliveryContext.accountId : undefined) ??
(typeof match?.lastAccountId === "string" ? match.lastAccountId : undefined);
if (channel && to) return { channel, to, accountId };
if (channel && to) {
return { channel, to, accountId };
}
} catch {
// ignore
}

View File

@@ -53,13 +53,19 @@ export function resolveMainSessionAlias(cfg: OpenClawConfig) {
}
export function resolveDisplaySessionKey(params: { key: string; alias: string; mainKey: string }) {
if (params.key === params.alias) return "main";
if (params.key === params.mainKey) return "main";
if (params.key === params.alias) {
return "main";
}
if (params.key === params.mainKey) {
return "main";
}
return params.key;
}
export function resolveInternalSessionKey(params: { key: string; alias: string; mainKey: string }) {
if (params.key === "main") return params.alias;
if (params.key === "main") {
return params.alias;
}
return params.key;
}
@@ -74,20 +80,32 @@ export function createAgentToAgentPolicy(cfg: OpenClawConfig): AgentToAgentPolic
const enabled = routingA2A?.enabled === true;
const allowPatterns = Array.isArray(routingA2A?.allow) ? routingA2A.allow : [];
const matchesAllow = (agentId: string) => {
if (allowPatterns.length === 0) return true;
if (allowPatterns.length === 0) {
return true;
}
return allowPatterns.some((pattern) => {
const raw = String(pattern ?? "").trim();
if (!raw) return false;
if (raw === "*") return true;
if (!raw.includes("*")) return raw === agentId;
if (!raw) {
return false;
}
if (raw === "*") {
return true;
}
if (!raw.includes("*")) {
return raw === agentId;
}
const escaped = raw.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const re = new RegExp(`^${escaped.replaceAll("\\*", ".*")}$`, "i");
return re.test(agentId);
});
};
const isAllowed = (requesterAgentId: string, targetAgentId: string) => {
if (requesterAgentId === targetAgentId) return true;
if (!enabled) return false;
if (requesterAgentId === targetAgentId) {
return true;
}
if (!enabled) {
return false;
}
return matchesAllow(requesterAgentId) && matchesAllow(targetAgentId);
};
return { enabled, matchesAllow, isAllowed };
@@ -101,14 +119,28 @@ export function looksLikeSessionId(value: string): boolean {
export function looksLikeSessionKey(value: string): boolean {
const raw = value.trim();
if (!raw) return false;
if (!raw) {
return false;
}
// These are canonical key shapes that should never be treated as sessionIds.
if (raw === "main" || raw === "global" || raw === "unknown") return true;
if (isAcpSessionKey(raw)) return true;
if (raw.startsWith("agent:")) return true;
if (raw.startsWith("cron:") || raw.startsWith("hook:")) return true;
if (raw.startsWith("node-") || raw.startsWith("node:")) return true;
if (raw.includes(":group:") || raw.includes(":channel:")) return true;
if (raw === "main" || raw === "global" || raw === "unknown") {
return true;
}
if (isAcpSessionKey(raw)) {
return true;
}
if (raw.startsWith("agent:")) {
return true;
}
if (raw.startsWith("cron:") || raw.startsWith("hook:")) {
return true;
}
if (raw.startsWith("node-") || raw.startsWith("node:")) {
return true;
}
if (raw.includes(":group:") || raw.includes(":channel:")) {
return true;
}
return false;
}
@@ -196,7 +228,9 @@ async function resolveSessionKeyFromKey(params: {
},
});
const key = typeof result?.key === "string" ? result.key.trim() : "";
if (!key) return null;
if (!key) {
return null;
}
return {
ok: true,
key,
@@ -229,7 +263,9 @@ export async function resolveSessionReference(params: {
requesterInternalKey: params.requesterInternalKey,
restrictToSpawned: params.restrictToSpawned,
});
if (resolvedByKey) return resolvedByKey;
if (resolvedByKey) {
return resolvedByKey;
}
return await resolveSessionKeyFromSessionId({
sessionId: raw,
alias: params.alias,
@@ -259,11 +295,21 @@ export function classifySessionKind(params: {
mainKey: string;
}): SessionKind {
const key = params.key;
if (key === params.alias || key === params.mainKey) return "main";
if (key.startsWith("cron:")) return "cron";
if (key.startsWith("hook:")) return "hook";
if (key.startsWith("node-") || key.startsWith("node:")) return "node";
if (params.gatewayKind === "group") return "group";
if (key === params.alias || key === params.mainKey) {
return "main";
}
if (key.startsWith("cron:")) {
return "cron";
}
if (key.startsWith("hook:")) {
return "hook";
}
if (key.startsWith("node-") || key.startsWith("node:")) {
return "node";
}
if (params.gatewayKind === "group") {
return "group";
}
if (key.includes(":group:") || key.includes(":channel:")) {
return "group";
}
@@ -276,11 +322,17 @@ export function deriveChannel(params: {
channel?: string | null;
lastChannel?: string | null;
}): string {
if (params.kind === "cron" || params.kind === "hook" || params.kind === "node") return "internal";
if (params.kind === "cron" || params.kind === "hook" || params.kind === "node") {
return "internal";
}
const channel = normalizeKey(params.channel ?? undefined);
if (channel) return channel;
if (channel) {
return channel;
}
const lastChannel = normalizeKey(params.lastChannel ?? undefined);
if (lastChannel) return lastChannel;
if (lastChannel) {
return lastChannel;
}
const parts = params.key.split(":").filter(Boolean);
if (parts.length >= 3 && (parts[1] === "group" || parts[1] === "channel")) {
return parts[0];
@@ -290,7 +342,9 @@ export function deriveChannel(params: {
export function stripToolMessages(messages: unknown[]): unknown[] {
return messages.filter((msg) => {
if (!msg || typeof msg !== "object") return true;
if (!msg || typeof msg !== "object") {
return true;
}
const role = (msg as { role?: unknown }).role;
return role !== "toolResult";
});
@@ -301,19 +355,31 @@ export function stripToolMessages(messages: unknown[]): unknown[] {
* This ensures user-facing text doesn't leak internal tool representations.
*/
export function sanitizeTextContent(text: string): string {
if (!text) return text;
if (!text) {
return text;
}
return stripThinkingTagsFromText(stripDowngradedToolCallText(stripMinimaxToolCallXml(text)));
}
export function extractAssistantText(message: unknown): string | undefined {
if (!message || typeof message !== "object") return undefined;
if ((message as { role?: unknown }).role !== "assistant") return undefined;
if (!message || typeof message !== "object") {
return undefined;
}
if ((message as { role?: unknown }).role !== "assistant") {
return undefined;
}
const content = (message as { content?: unknown }).content;
if (!Array.isArray(content)) return undefined;
if (!Array.isArray(content)) {
return undefined;
}
const chunks: string[] = [];
for (const block of content) {
if (!block || typeof block !== "object") continue;
if ((block as { type?: unknown }).type !== "text") continue;
if (!block || typeof block !== "object") {
continue;
}
if ((block as { type?: unknown }).type !== "text") {
continue;
}
const text = (block as { text?: unknown }).text;
if (typeof text === "string") {
const sanitized = sanitizeTextContent(text);

View File

@@ -97,20 +97,32 @@ export function createSessionsListTool(opts?: {
const rows: SessionListRow[] = [];
for (const entry of sessions) {
if (!entry || typeof entry !== "object") continue;
if (!entry || typeof entry !== "object") {
continue;
}
const key = typeof entry.key === "string" ? entry.key : "";
if (!key) continue;
if (!key) {
continue;
}
const entryAgentId = resolveAgentIdFromSessionKey(key);
const crossAgent = entryAgentId !== requesterAgentId;
if (crossAgent && !a2aPolicy.isAllowed(requesterAgentId, entryAgentId)) continue;
if (crossAgent && !a2aPolicy.isAllowed(requesterAgentId, entryAgentId)) {
continue;
}
if (key === "unknown") continue;
if (key === "global" && alias !== "global") continue;
if (key === "unknown") {
continue;
}
if (key === "global" && alias !== "global") {
continue;
}
const gatewayKind = typeof entry.kind === "string" ? entry.kind : undefined;
const kind = classifySessionKind({ key, gatewayKind, alias, mainKey });
if (allowedKinds && !allowedKinds.has(kind)) continue;
if (allowedKinds && !allowedKinds.has(kind)) {
continue;
}
const displayKey = resolveDisplaySessionKey({
key,

View File

@@ -20,9 +20,13 @@ export type AnnounceTarget = {
export function resolveAnnounceTargetFromKey(sessionKey: string): AnnounceTarget | null {
const rawParts = sessionKey.split(":").filter(Boolean);
const parts = rawParts.length >= 3 && rawParts[0] === "agent" ? rawParts.slice(2) : rawParts;
if (parts.length < 3) return null;
if (parts.length < 3) {
return null;
}
const [channelRaw, kind, ...rest] = parts;
if (kind !== "group" && kind !== "channel") return null;
if (kind !== "group" && kind !== "channel") {
return null;
}
// Extract topic/thread ID from rest (supports both :topic: and :thread:)
// Telegram uses :topic:, other platforms use :thread:
@@ -39,12 +43,18 @@ export function resolveAnnounceTargetFromKey(sessionKey: string): AnnounceTarget
// Remove :topic:N or :thread:N suffix from ID for target
const id = match ? restJoined.replace(/:(topic|thread):\d+$/, "") : restJoined.trim();
if (!id) return null;
if (!channelRaw) return null;
if (!id) {
return null;
}
if (!channelRaw) {
return null;
}
const normalizedChannel = normalizeAnyChannelId(channelRaw) ?? normalizeChatChannelId(channelRaw);
const channel = normalizedChannel ?? channelRaw.toLowerCase();
const kindTarget = (() => {
if (!normalizedChannel) return id;
if (!normalizedChannel) {
return id;
}
if (normalizedChannel === "discord" || normalizedChannel === "slack") {
return `channel:${id}`;
}
@@ -148,7 +158,9 @@ export function isReplySkip(text?: string) {
export function resolvePingPongTurns(cfg?: OpenClawConfig) {
const raw = cfg?.session?.agentToAgent?.maxPingPongTurns;
const fallback = DEFAULT_PING_PONG_TURNS;
if (typeof raw !== "number" || !Number.isFinite(raw)) return fallback;
if (typeof raw !== "number" || !Number.isFinite(raw)) {
return fallback;
}
const rounded = Math.floor(raw);
return Math.max(0, Math.min(MAX_PING_PONG_TURNS, rounded));
}

View File

@@ -48,7 +48,9 @@ export async function runSessionsSendA2AFlow(params: {
latestReply = primaryReply;
}
}
if (!latestReply) return;
if (!latestReply) {
return;
}
const announceTarget = await resolveAnnounceTarget({
sessionKey: params.targetSessionKey,

View File

@@ -38,11 +38,17 @@ const SessionsSpawnToolSchema = Type.Object({
});
function splitModelRef(ref?: string) {
if (!ref) return { provider: undefined, model: undefined };
if (!ref) {
return { provider: undefined, model: undefined };
}
const trimmed = ref.trim();
if (!trimmed) return { provider: undefined, model: undefined };
if (!trimmed) {
return { provider: undefined, model: undefined };
}
const [provider, model] = trimmed.split("/", 2);
if (model) return { provider, model };
if (model) {
return { provider, model };
}
return { provider: undefined, model: trimmed };
}
@@ -51,9 +57,13 @@ function normalizeModelSelection(value: unknown): string | undefined {
const trimmed = value.trim();
return trimmed || undefined;
}
if (!value || typeof value !== "object") return undefined;
if (!value || typeof value !== "object") {
return undefined;
}
const primary = (value as { primary?: unknown }).primary;
if (typeof primary === "string" && primary.trim()) return primary.trim();
if (typeof primary === "string" && primary.trim()) {
return primary.trim();
}
return undefined;
}
@@ -96,7 +106,9 @@ export function createSessionsSpawnTool(opts?: {
typeof params.runTimeoutSeconds === "number" && Number.isFinite(params.runTimeoutSeconds)
? Math.max(0, Math.floor(params.runTimeoutSeconds))
: undefined;
if (explicit !== undefined) return explicit;
if (explicit !== undefined) {
return explicit;
}
const legacy =
typeof params.timeoutSeconds === "number" && Number.isFinite(params.timeoutSeconds)
? Math.max(0, Math.floor(params.timeoutSeconds))

View File

@@ -49,16 +49,24 @@ function resolveThreadTsFromContext(
context: SlackActionContext | undefined,
): string | undefined {
// Agent explicitly provided threadTs - use it
if (explicitThreadTs) return explicitThreadTs;
if (explicitThreadTs) {
return explicitThreadTs;
}
// No context or missing required fields
if (!context?.currentThreadTs || !context?.currentChannelId) return undefined;
if (!context?.currentThreadTs || !context?.currentChannelId) {
return undefined;
}
const parsedTarget = parseSlackTarget(targetChannel, { defaultKind: "channel" });
if (!parsedTarget || parsedTarget.kind !== "channel") return undefined;
if (!parsedTarget || parsedTarget.kind !== "channel") {
return undefined;
}
const normalizedTarget = parsedTarget.id;
// Different channel - don't inject
if (normalizedTarget !== context.currentChannelId) return undefined;
if (normalizedTarget !== context.currentChannelId) {
return undefined;
}
// Check replyToMode
if (context.replyToMode === "all") {
@@ -93,15 +101,21 @@ export async function handleSlackAction(
// Choose the most appropriate token for Slack read/write operations.
const getTokenForOperation = (operation: "read" | "write") => {
if (operation === "read") return userToken ?? botToken;
if (!allowUserWrites) return botToken;
if (operation === "read") {
return userToken ?? botToken;
}
if (!allowUserWrites) {
return botToken;
}
return botToken ?? userToken;
};
const buildActionOpts = (operation: "read" | "write") => {
const token = getTokenForOperation(operation);
const tokenOverride = token && token !== botToken ? token : undefined;
if (!accountId && !tokenOverride) return undefined;
if (!accountId && !tokenOverride) {
return undefined;
}
return {
...(accountId ? { accountId } : {}),
...(tokenOverride ? { token: tokenOverride } : {}),

View File

@@ -32,7 +32,9 @@ export function readTelegramButtons(
params: Record<string, unknown>,
): TelegramButton[][] | undefined {
const raw = params.buttons;
if (raw == null) return undefined;
if (raw == null) {
return undefined;
}
if (!Array.isArray(raw)) {
throw new Error("buttons must be an array of button rows");
}

View File

@@ -38,7 +38,9 @@ export function createTtsTool(opts?: {
if (result.success && result.audioPath) {
const lines: string[] = [];
// Tag Telegram Opus output as a voice bubble instead of a file attachment.
if (result.voiceCompatible) lines.push("[[audio_as_voice]]");
if (result.voiceCompatible) {
lines.push("[[audio_as_voice]]");
}
lines.push(`MEDIA:${result.audioPath}`);
return {
content: [{ type: "text", text: lines.join("\n") }],

View File

@@ -34,7 +34,9 @@ export function htmlToMarkdown(html: string): { text: string; title?: string } {
.replace(/<noscript[\s\S]*?<\/noscript>/gi, "");
text = text.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (_, href, body) => {
const label = normalizeWhitespace(stripTags(body));
if (!label) return href;
if (!label) {
return href;
}
return `[${label}](${href})`;
});
text = text.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, level, body) => {
@@ -72,7 +74,9 @@ export function truncateText(
value: string,
maxChars: number,
): { text: string; truncated: boolean } {
if (value.length <= maxChars) return { text: value, truncated: false };
if (value.length <= maxChars) {
return { text: value, truncated: false };
}
return { text: value.slice(0, maxChars), truncated: true };
}
@@ -102,7 +106,9 @@ export async function extractReadableContent(params: {
}
const reader = new Readability(document, { charThreshold: 0 });
const parsed = reader.parse();
if (!parsed?.content) return fallback();
if (!parsed?.content) {
return fallback();
}
const title = parsed.title || undefined;
if (params.extractMode === "text") {
const text = normalizeWhitespace(parsed.textContent ?? "");

View File

@@ -80,24 +80,34 @@ type FirecrawlFetchConfig =
function resolveFetchConfig(cfg?: OpenClawConfig): WebFetchConfig {
const fetch = cfg?.tools?.web?.fetch;
if (!fetch || typeof fetch !== "object") return undefined;
if (!fetch || typeof fetch !== "object") {
return undefined;
}
return fetch as WebFetchConfig;
}
function resolveFetchEnabled(params: { fetch?: WebFetchConfig; sandboxed?: boolean }): boolean {
if (typeof params.fetch?.enabled === "boolean") return params.fetch.enabled;
if (typeof params.fetch?.enabled === "boolean") {
return params.fetch.enabled;
}
return true;
}
function resolveFetchReadabilityEnabled(fetch?: WebFetchConfig): boolean {
if (typeof fetch?.readability === "boolean") return fetch.readability;
if (typeof fetch?.readability === "boolean") {
return fetch.readability;
}
return true;
}
function resolveFirecrawlConfig(fetch?: WebFetchConfig): FirecrawlFetchConfig {
if (!fetch || typeof fetch !== "object") return undefined;
if (!fetch || typeof fetch !== "object") {
return undefined;
}
const firecrawl = "firecrawl" in fetch ? fetch.firecrawl : undefined;
if (!firecrawl || typeof firecrawl !== "object") return undefined;
if (!firecrawl || typeof firecrawl !== "object") {
return undefined;
}
return firecrawl as FirecrawlFetchConfig;
}
@@ -114,7 +124,9 @@ function resolveFirecrawlEnabled(params: {
firecrawl?: FirecrawlFetchConfig;
apiKey?: string;
}): boolean {
if (typeof params.firecrawl?.enabled === "boolean") return params.firecrawl.enabled;
if (typeof params.firecrawl?.enabled === "boolean") {
return params.firecrawl.enabled;
}
return Boolean(params.apiKey);
}
@@ -127,7 +139,9 @@ function resolveFirecrawlBaseUrl(firecrawl?: FirecrawlFetchConfig): string {
}
function resolveFirecrawlOnlyMainContent(firecrawl?: FirecrawlFetchConfig): boolean {
if (typeof firecrawl?.onlyMainContent === "boolean") return firecrawl.onlyMainContent;
if (typeof firecrawl?.onlyMainContent === "boolean") {
return firecrawl.onlyMainContent;
}
return true;
}
@@ -136,14 +150,18 @@ function resolveFirecrawlMaxAgeMs(firecrawl?: FirecrawlFetchConfig): number | un
firecrawl && "maxAgeMs" in firecrawl && typeof firecrawl.maxAgeMs === "number"
? firecrawl.maxAgeMs
: undefined;
if (typeof raw !== "number" || !Number.isFinite(raw)) return undefined;
if (typeof raw !== "number" || !Number.isFinite(raw)) {
return undefined;
}
const parsed = Math.max(0, Math.floor(raw));
return parsed > 0 ? parsed : undefined;
}
function resolveFirecrawlMaxAgeMsOrDefault(firecrawl?: FirecrawlFetchConfig): number {
const resolved = resolveFirecrawlMaxAgeMs(firecrawl);
if (typeof resolved === "number") return resolved;
if (typeof resolved === "number") {
return resolved;
}
return DEFAULT_FIRECRAWL_MAX_AGE_MS;
}
@@ -159,7 +177,9 @@ function resolveMaxRedirects(value: unknown, fallback: number): number {
function looksLikeHtml(value: string): boolean {
const trimmed = value.trimStart();
if (!trimmed) return false;
if (!trimmed) {
return false;
}
const head = trimmed.slice(0, 256).toLowerCase();
return head.startsWith("<!doctype html") || head.startsWith("<html");
}
@@ -243,7 +263,9 @@ function formatWebFetchErrorDetail(params: {
maxChars: number;
}): string {
const { detail, contentType, maxChars } = params;
if (!detail) return "";
if (!detail) {
return "";
}
let text = detail;
const contentTypeLower = contentType?.toLowerCase();
if (contentTypeLower?.includes("text/html") || looksLikeHtml(detail)) {
@@ -351,7 +373,9 @@ async function runWebFetch(params: {
`fetch:${params.url}:${params.extractMode}:${params.maxChars}`,
);
const cached = readCache(FETCH_CACHE, cacheKey);
if (cached) return { ...cached.value, cached: true };
if (cached) {
return { ...cached.value, cached: true };
}
let parsedUrl: URL;
try {
@@ -535,7 +559,9 @@ async function tryFirecrawlFallback(params: {
firecrawlStoreInCache: boolean;
firecrawlTimeoutSeconds: number;
}): Promise<{ text: string; title?: string } | null> {
if (!params.firecrawlEnabled || !params.firecrawlApiKey) return null;
if (!params.firecrawlEnabled || !params.firecrawlApiKey) {
return null;
}
try {
const firecrawl = await fetchFirecrawlContent({
url: params.url,
@@ -556,7 +582,9 @@ async function tryFirecrawlFallback(params: {
function resolveFirecrawlEndpoint(baseUrl: string): string {
const trimmed = baseUrl.trim();
if (!trimmed) return `${DEFAULT_FIRECRAWL_BASE_URL}/v2/scrape`;
if (!trimmed) {
return `${DEFAULT_FIRECRAWL_BASE_URL}/v2/scrape`;
}
try {
const url = new URL(trimmed);
if (url.pathname && url.pathname !== "/") {
@@ -574,7 +602,9 @@ export function createWebFetchTool(options?: {
sandboxed?: boolean;
}): AnyAgentTool | null {
const fetch = resolveFetchConfig(options?.config);
if (!resolveFetchEnabled({ fetch, sandboxed: options?.sandboxed })) return null;
if (!resolveFetchEnabled({ fetch, sandboxed: options?.sandboxed })) {
return null;
}
const readabilityEnabled = resolveFetchReadabilityEnabled(fetch);
const firecrawl = resolveFirecrawlConfig(fetch);
const firecrawlApiKey = resolveFirecrawlApiKey(firecrawl);

View File

@@ -105,13 +105,19 @@ type PerplexityBaseUrlHint = "direct" | "openrouter";
function resolveSearchConfig(cfg?: OpenClawConfig): WebSearchConfig {
const search = cfg?.tools?.web?.search;
if (!search || typeof search !== "object") return undefined;
if (!search || typeof search !== "object") {
return undefined;
}
return search as WebSearchConfig;
}
function resolveSearchEnabled(params: { search?: WebSearchConfig; sandboxed?: boolean }): boolean {
if (typeof params.search?.enabled === "boolean") return params.search.enabled;
if (params.sandboxed) return true;
if (typeof params.search?.enabled === "boolean") {
return params.search.enabled;
}
if (params.sandboxed) {
return true;
}
return true;
}
@@ -143,15 +149,23 @@ function resolveSearchProvider(search?: WebSearchConfig): (typeof SEARCH_PROVIDE
search && "provider" in search && typeof search.provider === "string"
? search.provider.trim().toLowerCase()
: "";
if (raw === "perplexity") return "perplexity";
if (raw === "brave") return "brave";
if (raw === "perplexity") {
return "perplexity";
}
if (raw === "brave") {
return "brave";
}
return "brave";
}
function resolvePerplexityConfig(search?: WebSearchConfig): PerplexityConfig {
if (!search || typeof search !== "object") return {};
if (!search || typeof search !== "object") {
return {};
}
const perplexity = "perplexity" in search ? search.perplexity : undefined;
if (!perplexity || typeof perplexity !== "object") return {};
if (!perplexity || typeof perplexity !== "object") {
return {};
}
return perplexity as PerplexityConfig;
}
@@ -182,7 +196,9 @@ function normalizeApiKey(key: unknown): string {
}
function inferPerplexityBaseUrlFromApiKey(apiKey?: string): PerplexityBaseUrlHint | undefined {
if (!apiKey) return undefined;
if (!apiKey) {
return undefined;
}
const normalized = apiKey.toLowerCase();
if (PERPLEXITY_KEY_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
return "direct";
@@ -202,13 +218,23 @@ function resolvePerplexityBaseUrl(
perplexity && "baseUrl" in perplexity && typeof perplexity.baseUrl === "string"
? perplexity.baseUrl.trim()
: "";
if (fromConfig) return fromConfig;
if (apiKeySource === "perplexity_env") return PERPLEXITY_DIRECT_BASE_URL;
if (apiKeySource === "openrouter_env") return DEFAULT_PERPLEXITY_BASE_URL;
if (fromConfig) {
return fromConfig;
}
if (apiKeySource === "perplexity_env") {
return PERPLEXITY_DIRECT_BASE_URL;
}
if (apiKeySource === "openrouter_env") {
return DEFAULT_PERPLEXITY_BASE_URL;
}
if (apiKeySource === "config") {
const inferred = inferPerplexityBaseUrlFromApiKey(apiKey);
if (inferred === "direct") return PERPLEXITY_DIRECT_BASE_URL;
if (inferred === "openrouter") return DEFAULT_PERPLEXITY_BASE_URL;
if (inferred === "direct") {
return PERPLEXITY_DIRECT_BASE_URL;
}
if (inferred === "openrouter") {
return DEFAULT_PERPLEXITY_BASE_URL;
}
}
return DEFAULT_PERPLEXITY_BASE_URL;
}
@@ -228,27 +254,43 @@ function resolveSearchCount(value: unknown, fallback: number): number {
}
function normalizeFreshness(value: string | undefined): string | undefined {
if (!value) return undefined;
if (!value) {
return undefined;
}
const trimmed = value.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
const lower = trimmed.toLowerCase();
if (BRAVE_FRESHNESS_SHORTCUTS.has(lower)) return lower;
if (BRAVE_FRESHNESS_SHORTCUTS.has(lower)) {
return lower;
}
const match = trimmed.match(BRAVE_FRESHNESS_RANGE);
if (!match) return undefined;
if (!match) {
return undefined;
}
const [, start, end] = match;
if (!isValidIsoDate(start) || !isValidIsoDate(end)) return undefined;
if (start > end) return undefined;
if (!isValidIsoDate(start) || !isValidIsoDate(end)) {
return undefined;
}
if (start > end) {
return undefined;
}
return `${start}to${end}`;
}
function isValidIsoDate(value: string): boolean {
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
return false;
}
const [year, month, day] = value.split("-").map((part) => Number.parseInt(part, 10));
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) return false;
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) {
return false;
}
const date = new Date(Date.UTC(year, month - 1, day));
return (
@@ -257,7 +299,9 @@ function isValidIsoDate(value: string): boolean {
}
function resolveSiteName(url: string | undefined): string | undefined {
if (!url) return undefined;
if (!url) {
return undefined;
}
try {
return new URL(url).hostname;
} catch {
@@ -326,7 +370,9 @@ async function runWebSearch(params: {
: `${params.provider}:${params.query}:${params.count}:${params.country || "default"}:${params.search_lang || "default"}:${params.ui_lang || "default"}`,
);
const cached = readCache(SEARCH_CACHE, cacheKey);
if (cached) return { ...cached.value, cached: true };
if (cached) {
return { ...cached.value, cached: true };
}
const start = Date.now();
@@ -411,7 +457,9 @@ export function createWebSearchTool(options?: {
sandboxed?: boolean;
}): AnyAgentTool | null {
const search = resolveSearchConfig(options?.config);
if (!resolveSearchEnabled({ search, sandboxed: options?.sandboxed })) return null;
if (!resolveSearchEnabled({ search, sandboxed: options?.sandboxed })) {
return null;
}
const provider = resolveSearchProvider(search);
const perplexityConfig = resolvePerplexityConfig(search);

View File

@@ -28,7 +28,9 @@ export function readCache<T>(
key: string,
): { value: T; cached: boolean } | null {
const entry = cache.get(key);
if (!entry) return null;
if (!entry) {
return null;
}
if (Date.now() > entry.expiresAt) {
cache.delete(key);
return null;
@@ -42,10 +44,14 @@ export function writeCache<T>(
value: T,
ttlMs: number,
) {
if (ttlMs <= 0) return;
if (ttlMs <= 0) {
return;
}
if (cache.size >= DEFAULT_CACHE_MAX_ENTRIES) {
const oldest = cache.keys().next();
if (!oldest.done) cache.delete(oldest.value);
if (!oldest.done) {
cache.delete(oldest.value);
}
}
cache.set(key, {
value,
@@ -55,7 +61,9 @@ export function writeCache<T>(
}
export function withTimeout(signal: AbortSignal | undefined, timeoutMs: number): AbortSignal {
if (timeoutMs <= 0) return signal ?? new AbortController().signal;
if (timeoutMs <= 0) {
return signal ?? new AbortController().signal;
}
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
if (signal) {

View File

@@ -65,9 +65,15 @@ function errorHtmlResponse(
};
}
function requestUrl(input: RequestInfo): string {
if (typeof input === "string") return input;
if (input instanceof URL) return input.toString();
if ("url" in input && typeof input.url === "string") return input.url;
if (typeof input === "string") {
return input;
}
if (input instanceof URL) {
return input.toString();
}
if ("url" in input && typeof input.url === "string") {
return input.url;
}
return "";
}