mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-16 04:20:44 +00:00
* Onboard: add Mistral auth choice and CLI flags * Onboard/Auth: add Mistral provider config defaults * Auth choice: wire Mistral API-key flow * Onboard non-interactive: support --mistral-api-key * Media understanding: add Mistral Voxtral audio provider * Changelog: note Mistral onboarding and media support * Docs: add Mistral provider and onboarding/media references * Tests: cover Mistral media registry/defaults and auth mapping * Memory: add Mistral embeddings provider support * Onboarding: refresh Mistral model metadata * Docs: document Mistral embeddings and endpoints * Memory: persist Mistral embedding client state in managers * Memory: add regressions for mistral provider wiring * Gateway: add live tool probe retry helper * Gateway: cover live tool probe retry helper * Gateway: retry malformed live tool-read probe responses * Memory: support plain-text batch error bodies * Tests: add Mistral Voxtral live transcription smoke * Docs: add Mistral live audio test command * Revert: remove Mistral live voice test and docs entry * Onboard: re-export Mistral default model ref from models * Changelog: credit joeVenner for Mistral work * fix: include Mistral in auto audio key fallback * Update CHANGELOG.md * Update CHANGELOG.md --------- Co-authored-by: Shakker <shakkerdroid@gmail.com>
148 lines
5.0 KiB
TypeScript
148 lines
5.0 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
import type {
|
|
EmbeddingProvider,
|
|
EmbeddingProviderResult,
|
|
MistralEmbeddingClient,
|
|
OpenAiEmbeddingClient,
|
|
} from "./embeddings.js";
|
|
import { getMemorySearchManager, type MemoryIndexManager } from "./index.js";
|
|
|
|
const { createEmbeddingProviderMock } = vi.hoisted(() => ({
|
|
createEmbeddingProviderMock: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("./embeddings.js", () => ({
|
|
createEmbeddingProvider: createEmbeddingProviderMock,
|
|
}));
|
|
|
|
vi.mock("./sqlite-vec.js", () => ({
|
|
loadSqliteVecExtension: async () => ({ ok: false, error: "sqlite-vec disabled in tests" }),
|
|
}));
|
|
|
|
function createProvider(id: string): EmbeddingProvider {
|
|
return {
|
|
id,
|
|
model: `${id}-model`,
|
|
embedQuery: async () => [0.1, 0.2, 0.3],
|
|
embedBatch: async (texts: string[]) => texts.map(() => [0.1, 0.2, 0.3]),
|
|
};
|
|
}
|
|
|
|
function buildConfig(params: {
|
|
workspaceDir: string;
|
|
indexPath: string;
|
|
provider: "openai" | "mistral";
|
|
fallback?: "none" | "mistral";
|
|
}): OpenClawConfig {
|
|
return {
|
|
agents: {
|
|
defaults: {
|
|
workspace: params.workspaceDir,
|
|
memorySearch: {
|
|
provider: params.provider,
|
|
model: params.provider === "mistral" ? "mistral/mistral-embed" : "text-embedding-3-small",
|
|
fallback: params.fallback ?? "none",
|
|
store: { path: params.indexPath, vector: { enabled: false } },
|
|
sync: { watch: false, onSessionStart: false, onSearch: false },
|
|
query: { minScore: 0, hybrid: { enabled: false } },
|
|
},
|
|
},
|
|
list: [{ id: "main", default: true }],
|
|
},
|
|
} as OpenClawConfig;
|
|
}
|
|
|
|
describe("memory manager mistral provider wiring", () => {
|
|
let workspaceDir = "";
|
|
let indexPath = "";
|
|
let manager: MemoryIndexManager | null = null;
|
|
|
|
beforeEach(async () => {
|
|
createEmbeddingProviderMock.mockReset();
|
|
workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-memory-mistral-"));
|
|
indexPath = path.join(workspaceDir, "index.sqlite");
|
|
await fs.mkdir(path.join(workspaceDir, "memory"), { recursive: true });
|
|
await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), "test");
|
|
});
|
|
|
|
afterEach(async () => {
|
|
if (manager) {
|
|
await manager.close();
|
|
manager = null;
|
|
}
|
|
if (workspaceDir) {
|
|
await fs.rm(workspaceDir, { recursive: true, force: true });
|
|
workspaceDir = "";
|
|
indexPath = "";
|
|
}
|
|
});
|
|
|
|
it("stores mistral client when mistral provider is selected", async () => {
|
|
const mistralClient: MistralEmbeddingClient = {
|
|
baseUrl: "https://api.mistral.ai/v1",
|
|
headers: { authorization: "Bearer test-key" },
|
|
model: "mistral-embed",
|
|
};
|
|
const providerResult: EmbeddingProviderResult = {
|
|
requestedProvider: "mistral",
|
|
provider: createProvider("mistral"),
|
|
mistral: mistralClient,
|
|
};
|
|
createEmbeddingProviderMock.mockResolvedValueOnce(providerResult);
|
|
|
|
const cfg = buildConfig({ workspaceDir, indexPath, provider: "mistral" });
|
|
const result = await getMemorySearchManager({ cfg, agentId: "main" });
|
|
if (!result.manager) {
|
|
throw new Error(`manager missing: ${result.error ?? "no error provided"}`);
|
|
}
|
|
manager = result.manager as unknown as MemoryIndexManager;
|
|
|
|
const internal = manager as unknown as { mistral?: MistralEmbeddingClient };
|
|
expect(internal.mistral).toBe(mistralClient);
|
|
});
|
|
|
|
it("stores mistral client after fallback activation", async () => {
|
|
const openAiClient: OpenAiEmbeddingClient = {
|
|
baseUrl: "https://api.openai.com/v1",
|
|
headers: { authorization: "Bearer openai-key" },
|
|
model: "text-embedding-3-small",
|
|
};
|
|
const mistralClient: MistralEmbeddingClient = {
|
|
baseUrl: "https://api.mistral.ai/v1",
|
|
headers: { authorization: "Bearer mistral-key" },
|
|
model: "mistral-embed",
|
|
};
|
|
createEmbeddingProviderMock.mockResolvedValueOnce({
|
|
requestedProvider: "openai",
|
|
provider: createProvider("openai"),
|
|
openAi: openAiClient,
|
|
} as EmbeddingProviderResult);
|
|
createEmbeddingProviderMock.mockResolvedValueOnce({
|
|
requestedProvider: "mistral",
|
|
provider: createProvider("mistral"),
|
|
mistral: mistralClient,
|
|
} as EmbeddingProviderResult);
|
|
|
|
const cfg = buildConfig({ workspaceDir, indexPath, provider: "openai", fallback: "mistral" });
|
|
const result = await getMemorySearchManager({ cfg, agentId: "main" });
|
|
if (!result.manager) {
|
|
throw new Error(`manager missing: ${result.error ?? "no error provided"}`);
|
|
}
|
|
manager = result.manager as unknown as MemoryIndexManager;
|
|
const internal = manager as unknown as {
|
|
activateFallbackProvider: (reason: string) => Promise<boolean>;
|
|
openAi?: OpenAiEmbeddingClient;
|
|
mistral?: MistralEmbeddingClient;
|
|
};
|
|
|
|
const activated = await internal.activateFallbackProvider("forced test");
|
|
expect(activated).toBe(true);
|
|
expect(internal.openAi).toBeUndefined();
|
|
expect(internal.mistral).toBe(mistralClient);
|
|
});
|
|
});
|