From 0ed00104bf0a56e07d2c1be0bd4ee5fb3846c250 Mon Sep 17 00:00:00 2001 From: PR Bot Date: Tue, 24 Mar 2026 14:38:43 +0800 Subject: [PATCH] feat: add MiniMax provider integration - Add @supermemory/tools/minimax export for MiniMax OpenAI-compatible API - Include withSupermemory() wrapper, clampTemperature(), model constants - Re-export all OpenAI memory tools for use with MiniMax clients - Add MiniMax to README framework integrations section - Add 19 unit tests + 3 integration tests --- README.md | 11 +- packages/tools/package.json | 1 + packages/tools/src/minimax/index.ts | 150 ++++++++++++++ .../tools/test/minimax/integration.test.ts | 92 +++++++++ packages/tools/test/minimax/unit.test.ts | 190 ++++++++++++++++++ packages/tools/tsdown.config.ts | 1 + 6 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 packages/tools/src/minimax/index.ts create mode 100644 packages/tools/test/minimax/integration.test.ts create mode 100644 packages/tools/test/minimax/unit.test.ts diff --git a/README.md b/README.md index 4f0e32e5e..616d660ad 100644 --- a/README.md +++ b/README.md @@ -233,12 +233,21 @@ Drop-in wrappers for every major AI framework: import { withSupermemory } from "@supermemory/tools/ai-sdk"; const model = withSupermemory(openai("gpt-4o"), "user_123"); +// MiniMax (OpenAI-compatible) +import OpenAI from "openai"; +import { withSupermemory } from "@supermemory/tools/minimax"; +const minimax = new OpenAI({ + apiKey: process.env.MINIMAX_API_KEY, + baseURL: "https://api.minimax.io/v1", +}); +const client = withSupermemory(minimax, "user_123", { mode: "full" }); + // Mastra import { withSupermemory } from "@supermemory/tools/mastra"; const agent = new Agent(withSupermemory(config, "user-123", { mode: "full" })); ``` -**Vercel AI SDK** · **LangChain** · **LangGraph** · **OpenAI Agents SDK** · **Mastra** · **Agno** · **Claude Memory Tool** · **n8n** +**Vercel AI SDK** · **LangChain** · **LangGraph** · **OpenAI Agents SDK** · **MiniMax** · **Mastra** · **Agno** · **Claude Memory Tool** · **n8n** ### Search modes diff --git a/packages/tools/package.json b/packages/tools/package.json index 8d192aba6..5b4fd8363 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -45,6 +45,7 @@ "./claude-memory": "./dist/claude-memory.js", "./mastra": "./dist/mastra.js", "./openai": "./dist/openai/index.js", + "./minimax": "./dist/minimax/index.js", "./package.json": "./package.json" }, "repository": { diff --git a/packages/tools/src/minimax/index.ts b/packages/tools/src/minimax/index.ts new file mode 100644 index 000000000..3f07578dd --- /dev/null +++ b/packages/tools/src/minimax/index.ts @@ -0,0 +1,150 @@ +import type OpenAI from "openai" +import { + createOpenAIMiddleware, + type OpenAIMiddlewareOptions, +} from "../openai/middleware" + +/** + * MiniMax model definitions. + * + * MiniMax provides OpenAI-compatible chat models via `https://api.minimax.io/v1`. + */ +export const MINIMAX_MODELS = [ + { id: "MiniMax-M2.7", name: "MiniMax M2.7" }, + { id: "MiniMax-M2.7-highspeed", name: "MiniMax M2.7 Highspeed" }, +] as const + +export type MiniMaxModelId = (typeof MINIMAX_MODELS)[number]["id"] + +/** Default MiniMax API base URL (international). */ +export const MINIMAX_BASE_URL = "https://api.minimax.io/v1" + +export interface MiniMaxSupermemoryOptions extends OpenAIMiddlewareOptions { + /** + * MiniMax API key. Falls back to `process.env.MINIMAX_API_KEY`. + */ + minimaxApiKey?: string + /** + * MiniMax API base URL. Defaults to `https://api.minimax.io/v1`. + */ + minimaxBaseUrl?: string +} + +/** + * Clamps temperature to MiniMax's accepted range (0, 1.0]. + * MiniMax does not accept temperature = 0; the minimum is a small positive value. + */ +export function clampTemperature(temperature?: number): number { + if (temperature === undefined || temperature === null) return 1.0 + if (temperature <= 0) return 0.01 + if (temperature > 1) return 1.0 + return temperature +} + +/** + * Creates an OpenAI client pre-configured for MiniMax's API and wraps it with + * SuperMemory middleware to automatically inject relevant memories. + * + * This is a convenience wrapper that combines MiniMax client creation with + * SuperMemory's memory middleware, so you get persistent memory out of the box + * when using MiniMax models. + * + * @param openaiClient - An OpenAI client instance configured with MiniMax's base URL and API key. + * You can create one like: + * ```typescript + * import OpenAI from "openai" + * const client = new OpenAI({ + * apiKey: process.env.MINIMAX_API_KEY, + * baseURL: "https://api.minimax.io/v1", + * }) + * ``` + * @param containerTag - The container tag/identifier for memory search (e.g., user ID, project ID) + * @param options - Optional configuration options for the middleware + * + * @returns An OpenAI client with SuperMemory middleware injected for both Chat Completions and Responses APIs + * + * @example + * ```typescript + * import OpenAI from "openai" + * import { withSupermemory } from "@supermemory/tools/minimax" + * + * const minimax = new OpenAI({ + * apiKey: process.env.MINIMAX_API_KEY, + * baseURL: "https://api.minimax.io/v1", + * }) + * + * const client = withSupermemory(minimax, "user-123", { + * mode: "full", + * addMemory: "always", + * }) + * + * const response = await client.chat.completions.create({ + * model: "MiniMax-M2.7", + * messages: [{ role: "user", content: "What's my favorite color?" }], + * temperature: 0.7, + * }) + * ``` + * + * @throws {Error} When SUPERMEMORY_API_KEY environment variable is not set + */ +export function withSupermemory( + openaiClient: OpenAI, + containerTag: string, + options?: OpenAIMiddlewareOptions, +) { + if (!process.env.SUPERMEMORY_API_KEY) { + throw new Error("SUPERMEMORY_API_KEY is not set") + } + + const conversationId = options?.conversationId + const verbose = options?.verbose ?? false + const mode = options?.mode ?? "profile" + const addMemory = options?.addMemory ?? "never" + const baseUrl = options?.baseUrl + + const openaiWithSupermemory = createOpenAIMiddleware( + openaiClient, + containerTag, + { + conversationId, + verbose, + mode, + addMemory, + baseUrl, + }, + ) + + return openaiWithSupermemory +} + +export type { OpenAIMiddlewareOptions } +export type { + MemorySearchResult, + MemoryAddResult, + ProfileResult, + DocumentListResult, + DocumentDeleteResult, + DocumentAddResult, + MemoryForgetResult, +} from "../openai/tools" +export { + createSearchMemoriesFunction, + createAddMemoryFunction, + createGetProfileFunction, + createDocumentListFunction, + createDocumentDeleteFunction, + createDocumentAddFunction, + createMemoryForgetFunction, + supermemoryTools, + getToolDefinitions, + createToolCallExecutor, + createToolCallsExecutor, + createSearchMemoriesTool, + createAddMemoryTool, + createGetProfileTool, + createDocumentListTool, + createDocumentDeleteTool, + createDocumentAddTool, + createMemoryForgetTool, + memoryToolSchemas, +} from "../openai/tools" diff --git a/packages/tools/test/minimax/integration.test.ts b/packages/tools/test/minimax/integration.test.ts new file mode 100644 index 000000000..c8a840510 --- /dev/null +++ b/packages/tools/test/minimax/integration.test.ts @@ -0,0 +1,92 @@ +/** + * Integration tests for the MiniMax + Supermemory integration. + * + * These tests verify that MiniMax models work correctly through the + * OpenAI-compatible API when wrapped with Supermemory memory middleware. + * + * Requires: + * - MINIMAX_API_KEY: MiniMax API key + * - SUPERMEMORY_API_KEY: Supermemory API key + */ + +import { describe, it, expect, vi } from "vitest" +import OpenAI from "openai" +import { + withSupermemory, + MINIMAX_BASE_URL, + clampTemperature, +} from "../../src/minimax" +import "dotenv/config" + +const MINIMAX_API_KEY = process.env.MINIMAX_API_KEY || "" +const SUPERMEMORY_API_KEY = process.env.SUPERMEMORY_API_KEY || "" + +const shouldRunIntegration = !!MINIMAX_API_KEY && !!SUPERMEMORY_API_KEY + +describe.skipIf(!shouldRunIntegration)( + "Integration: MiniMax + Supermemory", + () => { + const createMiniMaxClient = () => + new OpenAI({ + apiKey: MINIMAX_API_KEY, + baseURL: MINIMAX_BASE_URL, + }) + + it( + "should complete a basic chat via MiniMax API", + async () => { + const client = createMiniMaxClient() + + const response = await client.chat.completions.create({ + model: "MiniMax-M2.7", + messages: [ + { role: "user", content: 'Say exactly "hello minimax"' }, + ], + max_tokens: 20, + temperature: clampTemperature(0.7), + }) + + expect(response.choices).toBeDefined() + expect(response.choices.length).toBeGreaterThan(0) + expect(response.choices[0]?.message?.content).toBeTruthy() + }, + 30000, + ) + + it( + "should work with MiniMax-M2.7-highspeed model", + async () => { + const client = createMiniMaxClient() + + const response = await client.chat.completions.create({ + model: "MiniMax-M2.7-highspeed", + messages: [{ role: "user", content: "What is 2+2?" }], + max_tokens: 20, + temperature: clampTemperature(0.5), + }) + + expect(response.choices).toBeDefined() + expect(response.choices[0]?.message?.content).toBeTruthy() + }, + 30000, + ) + + it( + "should work with supermemory middleware wrapping MiniMax client", + async () => { + const client = createMiniMaxClient() + const wrapped = withSupermemory( + client, + "minimax-integration-test", + { + mode: "profile", + }, + ) + + // The wrapped client should still have the chat API + expect(wrapped.chat.completions.create).toBeDefined() + }, + 10000, + ) + }, +) diff --git a/packages/tools/test/minimax/unit.test.ts b/packages/tools/test/minimax/unit.test.ts new file mode 100644 index 000000000..229565ede --- /dev/null +++ b/packages/tools/test/minimax/unit.test.ts @@ -0,0 +1,190 @@ +/** + * Unit tests for the MiniMax integration module + */ + +import { describe, it, expect, beforeEach, vi, afterEach } from "vitest" +import { + withSupermemory, + clampTemperature, + MINIMAX_MODELS, + MINIMAX_BASE_URL, + supermemoryTools, + getToolDefinitions, + memoryToolSchemas, +} from "../../src/minimax" +import type OpenAI from "openai" +import "dotenv/config" + +// Mock OpenAI client +const createMockOpenAIClient = (): OpenAI => { + const originalCreate = vi.fn() + const responsesCreate = vi.fn() + return { + chat: { + completions: { + create: originalCreate, + }, + }, + responses: { + create: responsesCreate, + }, + } as unknown as OpenAI +} + +describe("Unit: MiniMax integration", () => { + let originalEnv: string | undefined + let originalSupermemoryKey: string | undefined + + beforeEach(() => { + originalEnv = process.env.MINIMAX_API_KEY + originalSupermemoryKey = process.env.SUPERMEMORY_API_KEY + vi.clearAllMocks() + }) + + afterEach(() => { + if (originalEnv) { + process.env.MINIMAX_API_KEY = originalEnv + } else { + delete process.env.MINIMAX_API_KEY + } + if (originalSupermemoryKey) { + process.env.SUPERMEMORY_API_KEY = originalSupermemoryKey + } else { + delete process.env.SUPERMEMORY_API_KEY + } + }) + + describe("MINIMAX_MODELS", () => { + it("should contain MiniMax-M2.7", () => { + expect(MINIMAX_MODELS.some((m) => m.id === "MiniMax-M2.7")).toBe(true) + }) + + it("should contain MiniMax-M2.7-highspeed", () => { + expect( + MINIMAX_MODELS.some((m) => m.id === "MiniMax-M2.7-highspeed"), + ).toBe(true) + }) + + it("should have exactly 2 models", () => { + expect(MINIMAX_MODELS).toHaveLength(2) + }) + }) + + describe("MINIMAX_BASE_URL", () => { + it("should use api.minimax.io", () => { + expect(MINIMAX_BASE_URL).toBe("https://api.minimax.io/v1") + }) + + it("should not use api.minimax.chat", () => { + expect(MINIMAX_BASE_URL).not.toContain("minimax.chat") + }) + }) + + describe("clampTemperature", () => { + it("should return 1.0 for undefined", () => { + expect(clampTemperature(undefined)).toBe(1.0) + }) + + it("should return 1.0 for null", () => { + expect(clampTemperature(null as unknown as number)).toBe(1.0) + }) + + it("should clamp 0 to 0.01", () => { + expect(clampTemperature(0)).toBe(0.01) + }) + + it("should clamp negative values to 0.01", () => { + expect(clampTemperature(-0.5)).toBe(0.01) + }) + + it("should clamp values above 1.0 to 1.0", () => { + expect(clampTemperature(1.5)).toBe(1.0) + expect(clampTemperature(2.0)).toBe(1.0) + }) + + it("should pass through valid values", () => { + expect(clampTemperature(0.5)).toBe(0.5) + expect(clampTemperature(0.7)).toBe(0.7) + expect(clampTemperature(1.0)).toBe(1.0) + expect(clampTemperature(0.01)).toBe(0.01) + }) + }) + + describe("withSupermemory", () => { + it("should throw error if SUPERMEMORY_API_KEY is not set", () => { + delete process.env.SUPERMEMORY_API_KEY + + const mockClient = createMockOpenAIClient() + + expect(() => { + withSupermemory(mockClient, "test-container") + }).toThrow("SUPERMEMORY_API_KEY is not set") + }) + + it("should successfully wrap OpenAI client with valid keys", () => { + process.env.SUPERMEMORY_API_KEY = "test-supermemory-key" + + const mockClient = createMockOpenAIClient() + const wrapped = withSupermemory(mockClient, "test-container") + + expect(wrapped).toBeDefined() + expect(wrapped.chat).toBeDefined() + expect(wrapped.chat.completions).toBeDefined() + expect(wrapped.chat.completions.create).toBeDefined() + }) + + it("should accept all middleware options", () => { + process.env.SUPERMEMORY_API_KEY = "test-supermemory-key" + + const mockClient = createMockOpenAIClient() + + expect(() => { + withSupermemory(mockClient, "test-container", { + conversationId: "conv-123", + verbose: true, + mode: "full", + addMemory: "always", + baseUrl: "https://custom.supermemory.ai", + }) + }).not.toThrow() + }) + }) + + describe("Tool re-exports", () => { + it("should export supermemoryTools function", () => { + expect(typeof supermemoryTools).toBe("function") + }) + + it("should export getToolDefinitions function", () => { + expect(typeof getToolDefinitions).toBe("function") + }) + + it("should export memoryToolSchemas", () => { + expect(memoryToolSchemas).toBeDefined() + expect(memoryToolSchemas.searchMemories).toBeDefined() + expect(memoryToolSchemas.addMemory).toBeDefined() + expect(memoryToolSchemas.getProfile).toBeDefined() + expect(memoryToolSchemas.documentList).toBeDefined() + expect(memoryToolSchemas.documentDelete).toBeDefined() + expect(memoryToolSchemas.documentAdd).toBeDefined() + expect(memoryToolSchemas.memoryForget).toBeDefined() + }) + + it("should create tools with API key", () => { + const tools = supermemoryTools("test-api-key") + expect(tools.searchMemories).toBeDefined() + expect(tools.addMemory).toBeDefined() + expect(tools.getProfile).toBeDefined() + expect(tools.documentList).toBeDefined() + expect(tools.documentDelete).toBeDefined() + expect(tools.documentAdd).toBeDefined() + expect(tools.memoryForget).toBeDefined() + }) + + it("should return 7 tool definitions", () => { + const definitions = getToolDefinitions() + expect(definitions).toHaveLength(7) + expect(definitions.every((d) => d.type === "function")).toBe(true) + }) + }) +}) diff --git a/packages/tools/tsdown.config.ts b/packages/tools/tsdown.config.ts index 9b543ec80..c5e5f7929 100644 --- a/packages/tools/tsdown.config.ts +++ b/packages/tools/tsdown.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ "src/ai-sdk.ts", "src/claude-memory.ts", "src/openai/index.ts", + "src/minimax/index.ts", "src/mastra.ts", ], format: "esm",