From edae8761b380fbdeeff1e00357bb24e98b19bf80 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Sun, 15 Mar 2026 20:01:19 +0000 Subject: [PATCH] Memory: extract embedding runtime surface --- src/commands/doctor-memory-search.ts | 6 +- .../embedding-runtime-registry.ts | 6 +- src/extension-host/embedding-runtime-types.ts | 61 +++++++++++++ src/extension-host/embedding-runtime.ts | 30 +++++++ src/memory/embeddings.ts | 90 ++++--------------- src/memory/manager-sync-ops.ts | 18 ++-- src/memory/manager.ts | 4 +- 7 files changed, 125 insertions(+), 90 deletions(-) create mode 100644 src/extension-host/embedding-runtime-types.ts create mode 100644 src/extension-host/embedding-runtime.ts diff --git a/src/commands/doctor-memory-search.ts b/src/commands/doctor-memory-search.ts index 4dd2914613f..4bafe3f244c 100644 --- a/src/commands/doctor-memory-search.ts +++ b/src/commands/doctor-memory-search.ts @@ -4,8 +4,8 @@ import { resolveMemorySearchConfig } from "../agents/memory-search.js"; import { resolveApiKeyForProvider } from "../agents/model-auth.js"; import { formatCliCommand } from "../cli/command-format.js"; import type { OpenClawConfig } from "../config/config.js"; +import { DEFAULT_LOCAL_EMBEDDING_MODEL } from "../extension-host/embedding-runtime.js"; import { resolveMemoryBackendConfig } from "../memory/backend-config.js"; -import { DEFAULT_LOCAL_MODEL } from "../memory/embeddings.js"; import { hasConfiguredMemorySecretInput } from "../memory/secret-input.js"; import { note } from "../terminal/note.js"; import { resolveUserPath } from "../utils.js"; @@ -160,7 +160,7 @@ export async function noteMemorySearchHealth( * * When `useDefaultFallback` is true (explicit `provider: "local"`), an empty * modelPath is treated as available because the runtime falls back to - * DEFAULT_LOCAL_MODEL (an auto-downloaded HuggingFace model). + * DEFAULT_LOCAL_EMBEDDING_MODEL (an auto-downloaded HuggingFace model). * * When false (provider: "auto"), we only consider local available if the user * explicitly configured a local file path — matching `canAutoSelectLocal()` @@ -168,7 +168,7 @@ export async function noteMemorySearchHealth( */ function hasLocalEmbeddings(local: { modelPath?: string }, useDefaultFallback = false): boolean { const modelPath = - local.modelPath?.trim() || (useDefaultFallback ? DEFAULT_LOCAL_MODEL : undefined); + local.modelPath?.trim() || (useDefaultFallback ? DEFAULT_LOCAL_EMBEDDING_MODEL : undefined); if (!modelPath) { return false; } diff --git a/src/extension-host/embedding-runtime-registry.ts b/src/extension-host/embedding-runtime-registry.ts index a4e8ee5dcc9..4a3c1efe782 100644 --- a/src/extension-host/embedding-runtime-registry.ts +++ b/src/extension-host/embedding-runtime-registry.ts @@ -23,14 +23,14 @@ import { createVoyageEmbeddingProvider, type VoyageEmbeddingClient, } from "../memory/embeddings-voyage.js"; +import { importNodeLlamaCpp } from "../memory/node-llama.js"; +import { resolveUserPath } from "../utils.js"; import type { EmbeddingProvider, EmbeddingProviderId, EmbeddingProviderOptions, EmbeddingProviderResult, -} from "../memory/embeddings.js"; -import { importNodeLlamaCpp } from "../memory/node-llama.js"; -import { resolveUserPath } from "../utils.js"; +} from "./embedding-runtime-types.js"; export type { GeminiEmbeddingClient, diff --git a/src/extension-host/embedding-runtime-types.ts b/src/extension-host/embedding-runtime-types.ts new file mode 100644 index 00000000000..fd28e48c252 --- /dev/null +++ b/src/extension-host/embedding-runtime-types.ts @@ -0,0 +1,61 @@ +import type { OpenClawConfig } from "../config/config.js"; +import type { SecretInput } from "../config/types.secrets.js"; +import type { EmbeddingInput } from "../memory/embedding-inputs.js"; +import type { GeminiEmbeddingClient, GeminiTaskType } from "../memory/embeddings-gemini.js"; +import type { MistralEmbeddingClient } from "../memory/embeddings-mistral.js"; +import type { OllamaEmbeddingClient } from "../memory/embeddings-ollama.js"; +import type { OpenAiEmbeddingClient } from "../memory/embeddings-openai.js"; +import type { VoyageEmbeddingClient } from "../memory/embeddings-voyage.js"; + +export type { GeminiEmbeddingClient } from "../memory/embeddings-gemini.js"; +export type { MistralEmbeddingClient } from "../memory/embeddings-mistral.js"; +export type { OpenAiEmbeddingClient } from "../memory/embeddings-openai.js"; +export type { VoyageEmbeddingClient } from "../memory/embeddings-voyage.js"; +export type { OllamaEmbeddingClient } from "../memory/embeddings-ollama.js"; + +export type EmbeddingProvider = { + id: string; + model: string; + maxInputTokens?: number; + embedQuery: (text: string) => Promise; + embedBatch: (texts: string[]) => Promise; + embedBatchInputs?: (inputs: EmbeddingInput[]) => Promise; +}; + +export type EmbeddingProviderId = "openai" | "local" | "gemini" | "voyage" | "mistral" | "ollama"; +export type EmbeddingProviderRequest = EmbeddingProviderId | "auto"; +export type EmbeddingProviderFallback = EmbeddingProviderId | "none"; + +export type EmbeddingProviderResult = { + provider: EmbeddingProvider | null; + requestedProvider: EmbeddingProviderRequest; + fallbackFrom?: EmbeddingProviderId; + fallbackReason?: string; + providerUnavailableReason?: string; + openAi?: OpenAiEmbeddingClient; + gemini?: GeminiEmbeddingClient; + voyage?: VoyageEmbeddingClient; + mistral?: MistralEmbeddingClient; + ollama?: OllamaEmbeddingClient; +}; + +export type EmbeddingProviderOptions = { + config: OpenClawConfig; + agentDir?: string; + provider: EmbeddingProviderRequest; + remote?: { + baseUrl?: string; + apiKey?: SecretInput; + headers?: Record; + }; + model: string; + fallback: EmbeddingProviderFallback; + local?: { + modelPath?: string; + modelCacheDir?: string; + }; + /** Gemini embedding-2: output vector dimensions (768, 1536, or 3072). */ + outputDimensionality?: number; + /** Gemini: override the default task type sent with embedding requests. */ + taskType?: GeminiTaskType; +}; diff --git a/src/extension-host/embedding-runtime.ts b/src/extension-host/embedding-runtime.ts new file mode 100644 index 00000000000..04ebf0cd5a5 --- /dev/null +++ b/src/extension-host/embedding-runtime.ts @@ -0,0 +1,30 @@ +import { + DEFAULT_EXTENSION_HOST_LOCAL_EMBEDDING_MODEL, + createExtensionHostEmbeddingProvider, +} from "./embedding-runtime-registry.js"; +import type { + EmbeddingProviderOptions, + EmbeddingProviderResult, +} from "./embedding-runtime-types.js"; + +export type { + EmbeddingProvider, + EmbeddingProviderFallback, + EmbeddingProviderId, + EmbeddingProviderOptions, + EmbeddingProviderRequest, + EmbeddingProviderResult, + GeminiEmbeddingClient, + MistralEmbeddingClient, + OllamaEmbeddingClient, + OpenAiEmbeddingClient, + VoyageEmbeddingClient, +} from "./embedding-runtime-types.js"; + +export const DEFAULT_LOCAL_EMBEDDING_MODEL = DEFAULT_EXTENSION_HOST_LOCAL_EMBEDDING_MODEL; + +export async function createEmbeddingProvider( + options: EmbeddingProviderOptions, +): Promise { + return createExtensionHostEmbeddingProvider(options); +} diff --git a/src/memory/embeddings.ts b/src/memory/embeddings.ts index a4178f3f419..0ea9554b028 100644 --- a/src/memory/embeddings.ts +++ b/src/memory/embeddings.ts @@ -1,73 +1,17 @@ -import type { OpenClawConfig } from "../config/config.js"; -import type { SecretInput } from "../config/types.secrets.js"; -import { - DEFAULT_EXTENSION_HOST_LOCAL_EMBEDDING_MODEL, - createExtensionHostEmbeddingProvider, -} from "../extension-host/embedding-runtime-registry.js"; -import type { EmbeddingInput } from "./embedding-inputs.js"; -import { type GeminiEmbeddingClient, type GeminiTaskType } from "./embeddings-gemini.js"; -import { type MistralEmbeddingClient } from "./embeddings-mistral.js"; -import type { OllamaEmbeddingClient } from "./embeddings-ollama.js"; -import type { OpenAiEmbeddingClient } from "./embeddings-openai.js"; -import type { VoyageEmbeddingClient } from "./embeddings-voyage.js"; - -export type { GeminiEmbeddingClient } from "./embeddings-gemini.js"; -export type { MistralEmbeddingClient } from "./embeddings-mistral.js"; -export type { OpenAiEmbeddingClient } from "./embeddings-openai.js"; -export type { VoyageEmbeddingClient } from "./embeddings-voyage.js"; -export type { OllamaEmbeddingClient } from "./embeddings-ollama.js"; - -export type EmbeddingProvider = { - id: string; - model: string; - maxInputTokens?: number; - embedQuery: (text: string) => Promise; - embedBatch: (texts: string[]) => Promise; - embedBatchInputs?: (inputs: EmbeddingInput[]) => Promise; -}; - -export type EmbeddingProviderId = "openai" | "local" | "gemini" | "voyage" | "mistral" | "ollama"; -export type EmbeddingProviderRequest = EmbeddingProviderId | "auto"; -export type EmbeddingProviderFallback = EmbeddingProviderId | "none"; - -export type EmbeddingProviderResult = { - provider: EmbeddingProvider | null; - requestedProvider: EmbeddingProviderRequest; - fallbackFrom?: EmbeddingProviderId; - fallbackReason?: string; - providerUnavailableReason?: string; - openAi?: OpenAiEmbeddingClient; - gemini?: GeminiEmbeddingClient; - voyage?: VoyageEmbeddingClient; - mistral?: MistralEmbeddingClient; - ollama?: OllamaEmbeddingClient; -}; - -export type EmbeddingProviderOptions = { - config: OpenClawConfig; - agentDir?: string; - provider: EmbeddingProviderRequest; - remote?: { - baseUrl?: string; - apiKey?: SecretInput; - headers?: Record; - }; - model: string; - fallback: EmbeddingProviderFallback; - local?: { - modelPath?: string; - modelCacheDir?: string; - }; - /** Gemini embedding-2: output vector dimensions (768, 1536, or 3072). */ - outputDimensionality?: number; - /** Gemini: override the default task type sent with embedding requests. */ - taskType?: GeminiTaskType; -}; - -export const DEFAULT_LOCAL_MODEL = DEFAULT_EXTENSION_HOST_LOCAL_EMBEDDING_MODEL; - -export async function createEmbeddingProvider( - options: EmbeddingProviderOptions, -): Promise { - return createExtensionHostEmbeddingProvider(options); -} +export { + createEmbeddingProvider, + DEFAULT_LOCAL_EMBEDDING_MODEL as DEFAULT_LOCAL_MODEL, +} from "../extension-host/embedding-runtime.js"; +export type { + EmbeddingProvider, + EmbeddingProviderFallback, + EmbeddingProviderId, + EmbeddingProviderOptions, + EmbeddingProviderRequest, + EmbeddingProviderResult, + GeminiEmbeddingClient, + MistralEmbeddingClient, + OllamaEmbeddingClient, + OpenAiEmbeddingClient, + VoyageEmbeddingClient, +} from "../extension-host/embedding-runtime.js"; diff --git a/src/memory/manager-sync-ops.ts b/src/memory/manager-sync-ops.ts index 6babe931707..77194d2024d 100644 --- a/src/memory/manager-sync-ops.ts +++ b/src/memory/manager-sync-ops.ts @@ -8,14 +8,6 @@ import { resolveAgentDir } from "../agents/agent-scope.js"; import { ResolvedMemorySearchConfig } from "../agents/memory-search.js"; import { type OpenClawConfig } from "../config/config.js"; import { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js"; -import { createSubsystemLogger } from "../logging/subsystem.js"; -import { onSessionTranscriptUpdate } from "../sessions/transcript-events.js"; -import { resolveUserPath } from "../utils.js"; -import { DEFAULT_GEMINI_EMBEDDING_MODEL } from "./embeddings-gemini.js"; -import { DEFAULT_MISTRAL_EMBEDDING_MODEL } from "./embeddings-mistral.js"; -import { DEFAULT_OLLAMA_EMBEDDING_MODEL } from "./embeddings-ollama.js"; -import { DEFAULT_OPENAI_EMBEDDING_MODEL } from "./embeddings-openai.js"; -import { DEFAULT_VOYAGE_EMBEDDING_MODEL } from "./embeddings-voyage.js"; import { createEmbeddingProvider, type EmbeddingProvider, @@ -24,7 +16,15 @@ import { type OllamaEmbeddingClient, type OpenAiEmbeddingClient, type VoyageEmbeddingClient, -} from "./embeddings.js"; +} from "../extension-host/embedding-runtime.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; +import { onSessionTranscriptUpdate } from "../sessions/transcript-events.js"; +import { resolveUserPath } from "../utils.js"; +import { DEFAULT_GEMINI_EMBEDDING_MODEL } from "./embeddings-gemini.js"; +import { DEFAULT_MISTRAL_EMBEDDING_MODEL } from "./embeddings-mistral.js"; +import { DEFAULT_OLLAMA_EMBEDDING_MODEL } from "./embeddings-ollama.js"; +import { DEFAULT_OPENAI_EMBEDDING_MODEL } from "./embeddings-openai.js"; +import { DEFAULT_VOYAGE_EMBEDDING_MODEL } from "./embeddings-voyage.js"; import { isFileMissingError } from "./fs-utils.js"; import { buildFileEntry, diff --git a/src/memory/manager.ts b/src/memory/manager.ts index 61e2cd71af8..b9e6249bd6f 100644 --- a/src/memory/manager.ts +++ b/src/memory/manager.ts @@ -6,7 +6,6 @@ import { resolveAgentDir, resolveAgentWorkspaceDir } from "../agents/agent-scope import type { ResolvedMemorySearchConfig } from "../agents/memory-search.js"; import { resolveMemorySearchConfig } from "../agents/memory-search.js"; import type { OpenClawConfig } from "../config/config.js"; -import { createSubsystemLogger } from "../logging/subsystem.js"; import { createEmbeddingProvider, type EmbeddingProvider, @@ -16,7 +15,8 @@ import { type OllamaEmbeddingClient, type OpenAiEmbeddingClient, type VoyageEmbeddingClient, -} from "./embeddings.js"; +} from "../extension-host/embedding-runtime.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; import { isFileMissingError, statRegularFile } from "./fs-utils.js"; import { bm25RankToScore, buildFtsQuery, mergeHybridResults } from "./hybrid.js"; import { isMemoryPath, normalizeExtraMemoryPaths } from "./internal.js";