mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
refactor: unify shared utility normalization helpers
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { callGateway } from "../../gateway/call.js";
|
||||
import { isAcpSessionKey, normalizeMainKey } from "../../routing/session-key.js";
|
||||
import { looksLikeSessionId } from "../../sessions/session-id.js";
|
||||
|
||||
function normalizeKey(value?: string) {
|
||||
const trimmed = value?.trim();
|
||||
@@ -112,11 +113,7 @@ export async function isResolvedSessionVisibleToRequester(params: {
|
||||
});
|
||||
}
|
||||
|
||||
const SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
export function looksLikeSessionId(value: string): boolean {
|
||||
return SESSION_ID_RE.test(value.trim());
|
||||
}
|
||||
export { looksLikeSessionId };
|
||||
|
||||
export function looksLikeSessionKey(value: string): boolean {
|
||||
const raw = value.trim();
|
||||
|
||||
@@ -31,7 +31,7 @@ export const ACP_INSTALL_USAGE = "Usage: /acp install";
|
||||
export const ACP_DOCTOR_USAGE = "Usage: /acp doctor";
|
||||
export const ACP_SESSIONS_USAGE = "Usage: /acp sessions";
|
||||
export const ACP_STEER_OUTPUT_LIMIT = 800;
|
||||
export const SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
export { SESSION_ID_RE } from "../../../sessions/session-id.js";
|
||||
|
||||
export type AcpAction =
|
||||
| "spawn"
|
||||
|
||||
@@ -18,6 +18,7 @@ import { parseDiscordTarget } from "../../../discord/targets.js";
|
||||
import { callGateway } from "../../../gateway/call.js";
|
||||
import { formatTimeAgo } from "../../../infra/format-time/format-relative.ts";
|
||||
import { parseAgentSessionKey } from "../../../routing/session-key.js";
|
||||
import { looksLikeSessionId } from "../../../sessions/session-id.js";
|
||||
import { extractTextFromChatContent } from "../../../shared/chat-content.js";
|
||||
import {
|
||||
formatDurationCompact,
|
||||
@@ -75,8 +76,6 @@ export const RECENT_WINDOW_MINUTES = 30;
|
||||
const SUBAGENT_TASK_PREVIEW_MAX = 110;
|
||||
export const STEER_ABORT_SETTLE_TIMEOUT_MS = 5_000;
|
||||
|
||||
const SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
function compactLine(value: string) {
|
||||
return value.replace(/\s+/g, " ").trim();
|
||||
}
|
||||
@@ -345,7 +344,7 @@ export async function resolveFocusTargetSession(params: {
|
||||
|
||||
const attempts: Array<Record<string, string>> = [];
|
||||
attempts.push({ key: token });
|
||||
if (SESSION_ID_RE.test(token)) {
|
||||
if (looksLikeSessionId(token)) {
|
||||
attempts.push({ sessionId: token });
|
||||
}
|
||||
attempts.push({ label: token });
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
import { normalizeEmbeddingModelWithPrefixes } from "./embeddings-model-normalize.js";
|
||||
import {
|
||||
createRemoteEmbeddingProvider,
|
||||
resolveRemoteEmbeddingClient,
|
||||
@@ -16,14 +17,11 @@ export const DEFAULT_MISTRAL_EMBEDDING_MODEL = "mistral-embed";
|
||||
const DEFAULT_MISTRAL_BASE_URL = "https://api.mistral.ai/v1";
|
||||
|
||||
export function normalizeMistralModel(model: string): string {
|
||||
const trimmed = model.trim();
|
||||
if (!trimmed) {
|
||||
return DEFAULT_MISTRAL_EMBEDDING_MODEL;
|
||||
}
|
||||
if (trimmed.startsWith("mistral/")) {
|
||||
return trimmed.slice("mistral/".length);
|
||||
}
|
||||
return trimmed;
|
||||
return normalizeEmbeddingModelWithPrefixes({
|
||||
model,
|
||||
defaultModel: DEFAULT_MISTRAL_EMBEDDING_MODEL,
|
||||
prefixes: ["mistral/"],
|
||||
});
|
||||
}
|
||||
|
||||
export async function createMistralEmbeddingProvider(
|
||||
|
||||
34
src/memory/embeddings-model-normalize.test.ts
Normal file
34
src/memory/embeddings-model-normalize.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeEmbeddingModelWithPrefixes } from "./embeddings-model-normalize.js";
|
||||
|
||||
describe("normalizeEmbeddingModelWithPrefixes", () => {
|
||||
it("returns default model when input is blank", () => {
|
||||
expect(
|
||||
normalizeEmbeddingModelWithPrefixes({
|
||||
model: " ",
|
||||
defaultModel: "fallback-model",
|
||||
prefixes: ["openai/"],
|
||||
}),
|
||||
).toBe("fallback-model");
|
||||
});
|
||||
|
||||
it("strips the first matching prefix", () => {
|
||||
expect(
|
||||
normalizeEmbeddingModelWithPrefixes({
|
||||
model: "openai/text-embedding-3-small",
|
||||
defaultModel: "fallback-model",
|
||||
prefixes: ["openai/"],
|
||||
}),
|
||||
).toBe("text-embedding-3-small");
|
||||
});
|
||||
|
||||
it("keeps explicit model names when no prefix matches", () => {
|
||||
expect(
|
||||
normalizeEmbeddingModelWithPrefixes({
|
||||
model: "voyage-4-large",
|
||||
defaultModel: "fallback-model",
|
||||
prefixes: ["voyage/"],
|
||||
}),
|
||||
).toBe("voyage-4-large");
|
||||
});
|
||||
});
|
||||
16
src/memory/embeddings-model-normalize.ts
Normal file
16
src/memory/embeddings-model-normalize.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export function normalizeEmbeddingModelWithPrefixes(params: {
|
||||
model: string;
|
||||
defaultModel: string;
|
||||
prefixes: string[];
|
||||
}): string {
|
||||
const trimmed = params.model.trim();
|
||||
if (!trimmed) {
|
||||
return params.defaultModel;
|
||||
}
|
||||
for (const prefix of params.prefixes) {
|
||||
if (trimmed.startsWith(prefix)) {
|
||||
return trimmed.slice(prefix.length);
|
||||
}
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { resolveEnvApiKey } from "../agents/model-auth.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js";
|
||||
import { normalizeEmbeddingModelWithPrefixes } from "./embeddings-model-normalize.js";
|
||||
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.js";
|
||||
import { buildRemoteBaseUrlPolicy, withRemoteHttpResponse } from "./remote-http.js";
|
||||
import { resolveMemorySecretInputString } from "./secret-input.js";
|
||||
@@ -28,14 +29,11 @@ function sanitizeAndNormalizeEmbedding(vec: number[]): number[] {
|
||||
}
|
||||
|
||||
function normalizeOllamaModel(model: string): string {
|
||||
const trimmed = model.trim();
|
||||
if (!trimmed) {
|
||||
return DEFAULT_OLLAMA_EMBEDDING_MODEL;
|
||||
}
|
||||
if (trimmed.startsWith("ollama/")) {
|
||||
return trimmed.slice("ollama/".length);
|
||||
}
|
||||
return trimmed;
|
||||
return normalizeEmbeddingModelWithPrefixes({
|
||||
model,
|
||||
defaultModel: DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
||||
prefixes: ["ollama/"],
|
||||
});
|
||||
}
|
||||
|
||||
function resolveOllamaApiBase(configuredBaseUrl?: string): string {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
import { normalizeEmbeddingModelWithPrefixes } from "./embeddings-model-normalize.js";
|
||||
import {
|
||||
createRemoteEmbeddingProvider,
|
||||
resolveRemoteEmbeddingClient,
|
||||
@@ -21,14 +22,11 @@ const OPENAI_MAX_INPUT_TOKENS: Record<string, number> = {
|
||||
};
|
||||
|
||||
export function normalizeOpenAiModel(model: string): string {
|
||||
const trimmed = model.trim();
|
||||
if (!trimmed) {
|
||||
return DEFAULT_OPENAI_EMBEDDING_MODEL;
|
||||
}
|
||||
if (trimmed.startsWith("openai/")) {
|
||||
return trimmed.slice("openai/".length);
|
||||
}
|
||||
return trimmed;
|
||||
return normalizeEmbeddingModelWithPrefixes({
|
||||
model,
|
||||
defaultModel: DEFAULT_OPENAI_EMBEDDING_MODEL,
|
||||
prefixes: ["openai/"],
|
||||
});
|
||||
}
|
||||
|
||||
export async function createOpenAiEmbeddingProvider(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SsrFPolicy } from "../infra/net/ssrf.js";
|
||||
import { normalizeEmbeddingModelWithPrefixes } from "./embeddings-model-normalize.js";
|
||||
import { resolveRemoteEmbeddingBearerClient } from "./embeddings-remote-client.js";
|
||||
import { fetchRemoteEmbeddingVectors } from "./embeddings-remote-fetch.js";
|
||||
import type { EmbeddingProvider, EmbeddingProviderOptions } from "./embeddings.js";
|
||||
@@ -19,14 +20,11 @@ const VOYAGE_MAX_INPUT_TOKENS: Record<string, number> = {
|
||||
};
|
||||
|
||||
export function normalizeVoyageModel(model: string): string {
|
||||
const trimmed = model.trim();
|
||||
if (!trimmed) {
|
||||
return DEFAULT_VOYAGE_EMBEDDING_MODEL;
|
||||
}
|
||||
if (trimmed.startsWith("voyage/")) {
|
||||
return trimmed.slice("voyage/".length);
|
||||
}
|
||||
return trimmed;
|
||||
return normalizeEmbeddingModelWithPrefixes({
|
||||
model,
|
||||
defaultModel: DEFAULT_VOYAGE_EMBEDDING_MODEL,
|
||||
prefixes: ["voyage/"],
|
||||
});
|
||||
}
|
||||
|
||||
export async function createVoyageEmbeddingProvider(
|
||||
|
||||
@@ -155,6 +155,16 @@ function pickTailnetIPv4(
|
||||
return pickIPv4Matching(networkInterfaces, isTailnetIPv4);
|
||||
}
|
||||
|
||||
function resolveGatewayTokenFromEnv(env: NodeJS.ProcessEnv): string | undefined {
|
||||
return env.OPENCLAW_GATEWAY_TOKEN?.trim() || env.CLAWDBOT_GATEWAY_TOKEN?.trim() || undefined;
|
||||
}
|
||||
|
||||
function resolveGatewayPasswordFromEnv(env: NodeJS.ProcessEnv): string | undefined {
|
||||
return (
|
||||
env.OPENCLAW_GATEWAY_PASSWORD?.trim() || env.CLAWDBOT_GATEWAY_PASSWORD?.trim() || undefined
|
||||
);
|
||||
}
|
||||
|
||||
function resolveAuth(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): ResolveAuthResult {
|
||||
const mode = cfg.gateway?.auth?.mode;
|
||||
const defaults = cfg.secrets?.defaults;
|
||||
@@ -166,13 +176,12 @@ function resolveAuth(cfg: OpenClawConfig, env: NodeJS.ProcessEnv): ResolveAuthRe
|
||||
value: cfg.gateway?.auth?.password,
|
||||
defaults,
|
||||
}).ref;
|
||||
const envToken = resolveGatewayTokenFromEnv(env);
|
||||
const envPassword = resolveGatewayPasswordFromEnv(env);
|
||||
const token =
|
||||
env.OPENCLAW_GATEWAY_TOKEN?.trim() ||
|
||||
env.CLAWDBOT_GATEWAY_TOKEN?.trim() ||
|
||||
(tokenRef ? undefined : normalizeSecretInputString(cfg.gateway?.auth?.token));
|
||||
envToken || (tokenRef ? undefined : normalizeSecretInputString(cfg.gateway?.auth?.token));
|
||||
const password =
|
||||
env.OPENCLAW_GATEWAY_PASSWORD?.trim() ||
|
||||
env.CLAWDBOT_GATEWAY_PASSWORD?.trim() ||
|
||||
envPassword ||
|
||||
(passwordRef ? undefined : normalizeSecretInputString(cfg.gateway?.auth?.password));
|
||||
|
||||
if (mode === "password") {
|
||||
@@ -208,9 +217,7 @@ async function resolveGatewayTokenSecretRef(
|
||||
if (!ref) {
|
||||
return cfg;
|
||||
}
|
||||
const hasTokenEnvCandidate = Boolean(
|
||||
env.OPENCLAW_GATEWAY_TOKEN?.trim() || env.CLAWDBOT_GATEWAY_TOKEN?.trim(),
|
||||
);
|
||||
const hasTokenEnvCandidate = Boolean(resolveGatewayTokenFromEnv(env));
|
||||
if (hasTokenEnvCandidate) {
|
||||
return cfg;
|
||||
}
|
||||
@@ -258,9 +265,7 @@ async function resolveGatewayPasswordSecretRef(
|
||||
if (!ref) {
|
||||
return cfg;
|
||||
}
|
||||
const hasPasswordEnvCandidate = Boolean(
|
||||
env.OPENCLAW_GATEWAY_PASSWORD?.trim() || env.CLAWDBOT_GATEWAY_PASSWORD?.trim(),
|
||||
);
|
||||
const hasPasswordEnvCandidate = Boolean(resolveGatewayPasswordFromEnv(env));
|
||||
if (hasPasswordEnvCandidate) {
|
||||
return cfg;
|
||||
}
|
||||
@@ -270,7 +275,7 @@ async function resolveGatewayPasswordSecretRef(
|
||||
}
|
||||
if (mode !== "password") {
|
||||
const hasTokenCandidate =
|
||||
Boolean(env.OPENCLAW_GATEWAY_TOKEN?.trim() || env.CLAWDBOT_GATEWAY_TOKEN?.trim()) ||
|
||||
Boolean(resolveGatewayTokenFromEnv(env)) ||
|
||||
hasConfiguredSecretInput(cfg.gateway?.auth?.token, cfg.secrets?.defaults);
|
||||
if (hasTokenCandidate) {
|
||||
return cfg;
|
||||
|
||||
14
src/sessions/session-id.test.ts
Normal file
14
src/sessions/session-id.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { SESSION_ID_RE, looksLikeSessionId } from "./session-id.js";
|
||||
|
||||
describe("session-id", () => {
|
||||
it("matches canonical UUID session ids", () => {
|
||||
expect(SESSION_ID_RE.test("123e4567-e89b-12d3-a456-426614174000")).toBe(true);
|
||||
expect(looksLikeSessionId(" 123e4567-e89b-12d3-a456-426614174000 ")).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects non-session-id values", () => {
|
||||
expect(SESSION_ID_RE.test("agent:main:main")).toBe(false);
|
||||
expect(looksLikeSessionId("session-label")).toBe(false);
|
||||
});
|
||||
});
|
||||
5
src/sessions/session-id.ts
Normal file
5
src/sessions/session-id.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
export function looksLikeSessionId(value: string): boolean {
|
||||
return SESSION_ID_RE.test(value.trim());
|
||||
}
|
||||
Reference in New Issue
Block a user