Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,22 @@ jobs:
- name: Run TypeScript type checking
run: bunx turbo run check-types --filter='@supermemory/ai-sdk' --filter='@supermemory/memory-graph'

- name: Detect Tools package changes
id: tools-changes
run: |
if git diff --quiet "${{ github.event.pull_request.base.sha }}" HEAD -- packages/tools; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Run Tools unit tests
if: steps.tools-changes.outputs.changed == 'true'
run: bun run --cwd packages/tools test:unit

- name: Build Tools package
if: steps.tools-changes.outputs.changed == 'true'
run: bun run --cwd packages/tools build

- name: Run Biome CI (format & lint on changed files)
run: bunx biome ci --changed --since=origin/main --no-errors-on-unmatched
22 changes: 10 additions & 12 deletions apps/docs/integrations/voltagent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Supermemory integrates with [VoltAgent](https://github.com/VoltAgent/voltagent),
## Installation

```bash
npm install @supermemory/tools @voltagent/core
npm install @supermemory/tools @voltagent/core ai@^6 @ai-sdk/openai@^3
```

Set up your API key as an environment variable:
Expand Down Expand Up @@ -52,9 +52,7 @@ const configWithMemory = withSupermemory({
const agent = new Agent(configWithMemory)

// Memories are automatically injected and saved
const result = await agent.generateText({
messages: [{ role: "user", content: "What's my name?" }],
})
const result = await agent.generateText("What's my name?")
```

<Note>
Expand Down Expand Up @@ -131,14 +129,13 @@ const configWithMemory = withSupermemory({

// Search tuning
searchMode: "hybrid", // "memories" | "documents" | "hybrid"
threshold: 0.1, // 0.0-1.0 (higher = more accurate)
limit: 10, // Max results to return
threshold: 0.6, // 0.0-1.0 (higher = more accurate)
limit: 10, // Integer from 1 to 100
rerank: true, // Rerank for best relevance
rewriteQuery: false, // AI-rewrite query (+400ms latency)

// Context
entityContext: "This is John, a software engineer", // Guides memory extraction (max 1500 chars)
metadata: { source: "voltagent" }, // Attached to saved conversations
metadata: { source: "voltagent" }, // Attached to saved conversations

// API
apiKey: "sk-...", // Falls back to SUPERMEMORY_API_KEY env var
Expand All @@ -154,14 +151,16 @@ const configWithMemory = withSupermemory({
| `addMemory` | string | `"always"` | Whether to save conversations after each response |
| `customId` | string | **required** | Custom ID to group messages into a conversation |
| `searchMode` | string | — | `"memories"`, `"documents"`, or `"hybrid"` |
| `threshold` | number | `0.1` | Similarity threshold (0 = more results, 1 = more accurate) |
| `limit` | number | `10` | Maximum number of memory results |
| `threshold` | number | | Similarity threshold (0 = more results, 1 = more accurate) |
| `limit` | number | | Maximum number of memory results (integer from 1 to 100) |
| `rerank` | boolean | `false` | Rerank results for relevance |
| `rewriteQuery` | boolean | `false` | AI-rewrite query for better results (+400ms) |
| `entityContext` | string | — | Context for memory extraction (max 1500 chars) |
| `entityContext` | string | — | Deprecated and ignored. [Configure it on the container tag instead](/concepts/customization#entity-context). |
| `metadata` | object | — | Custom metadata attached to saved conversations |
| `promptTemplate` | function | — | Custom function to format memory data into prompt |

When `threshold` or `limit` is omitted, the selected Supermemory backend route applies its own default. Set them explicitly when you need consistent search tuning across modes.

## Search Modes

The `searchMode` option controls what type of results are searched:
Expand All @@ -171,4 +170,3 @@ The `searchMode` option controls what type of results are searched:
| `"memories"` | Search only memory entries (atomic facts about the user) |
| `"documents"` | Search only document chunks |
| `"hybrid"` | Search both memories AND document chunks (recommended) |

8 changes: 5 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 11 additions & 4 deletions packages/tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The package provides three submodule imports:
```typescript
import { supermemoryTools, searchMemoriesTool, addMemoryTool } from "@supermemory/tools/ai-sdk"
import { createOpenAI } from "@ai-sdk/openai"
import { generateText } from "ai"
import { generateText, stepCountIs } from "ai"

const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY!,
Expand All @@ -43,6 +43,7 @@ const result = await generateText({
},
],
tools,
stopWhen: stepCountIs(5),
})

// Or create individual tools
Expand Down Expand Up @@ -271,6 +272,8 @@ import { withSupermemory } from "@supermemory/tools/openai"
const openaiWithSupermemory = withSupermemory(openai, {
containerTag: "user-123", // Required: identifies the user/container
customId: "conversation-456", // Required: groups messages into the same document
apiKey: process.env.SUPERMEMORY_API_KEY, // Optional env fallback
baseUrl: process.env.SUPERMEMORY_BASE_URL,
mode: "full",
addMemory: "always", // Default: "always"
verbose: true,
Expand All @@ -295,6 +298,8 @@ The middleware supports the same configuration options as the AI SDK version:
const openaiWithSupermemory = withSupermemory(openai, {
containerTag: "user-123", // Required: identifies the user/container
customId: "conversation-456", // Required: groups messages for contextual memory
apiKey: process.env.SUPERMEMORY_API_KEY, // Optional; captured per client
baseUrl: process.env.SUPERMEMORY_BASE_URL,
mode: "full", // "profile" | "query" | "full"
addMemory: "always", // "always" (default) | "never"
verbose: true, // Enable detailed logging
Expand All @@ -319,6 +324,8 @@ export async function POST(req: Request) {
const openaiWithSupermemory = withSupermemory(openai, {
containerTag: "user-123",
customId: conversationId,
apiKey: process.env.SUPERMEMORY_API_KEY,
baseUrl: process.env.SUPERMEMORY_BASE_URL,
mode: "full",
addMemory: "always",
verbose: true,
Expand Down Expand Up @@ -606,7 +613,7 @@ interface SupermemoryToolsConfig {
```

- **baseUrl**: Custom base URL for the supermemory API
- **containerTags**: Array of custom container tags (mutually exclusive with projectId)
- **containerTags**: Non-empty array of custom container tags (mutually exclusive with `projectId`). `searchMemories`, `getProfile`, and `memoryForget` use the first tag because v4 memory APIs are single-space. Add operations attach every configured tag, while `documentList` and `documentDelete` use the configured tags as their supported union scope. `documentDelete` still refuses a document with any tag outside that scope or a nonterminal processing status.
- **projectId**: Project ID which gets converted to container tag format (mutually exclusive with containerTags)
- **strict**: Enable strict schema mode for OpenAI strict validation. When `true`, all schema properties are required (satisfies OpenAI strict mode). When `false` (default), optional fields remain optional for maximum compatibility with all models.

Expand Down Expand Up @@ -670,11 +677,11 @@ interface WithSupermemoryOptions {
## Available Tools

### Search Memories
Searches through stored memories based on a query string.
Runs v4 hybrid search in the primary (first) configured container tag. Results can contain learned memories (`memory`) and source chunks (`chunk`). Only IDs on results containing `memory` can be passed to `memoryForget`; chunk-result IDs cannot.

**Parameters:**
- `informationToGet` (string): Terms to search for
- `includeFullDocs` (boolean, optional): Whether to include full document content (default: true)
- `includeFullDocs` (boolean, optional): Deprecated compatibility input; ignored by v4 hybrid search
- `limit` (number, optional): Maximum number of results (default: 10)

### Add Memory
Expand Down
5 changes: 3 additions & 2 deletions packages/tools/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
{
"name": "@supermemory/tools",
"type": "module",
"version": "2.1.1",
"version": "2.2.0",
"description": "Memory tools for AI SDK, OpenAI, Voltagent and Mastra with supermemory",
"scripts": {
"build": "tsdown",
"dev": "tsdown --watch --ignore-watch .turbo",
"check-types": "tsc --noEmit",
"test": "vitest --testTimeout 100000",
"test:unit": "vitest run --testTimeout 100000 src/tools-shared.test.ts src/tool-operations.test.ts src/claude-memory.test.ts test/with-supermemory/unit.test.ts test/with-supermemory/conversation-conversion.test.ts test/mastra/unit.test.ts",
"test:watch": "vitest --watch --testTimeout 100000"
},
"dependencies": {
Expand All @@ -16,7 +17,7 @@
"ai": "^5.0.29",
"lru-cache": "^11.2.6",
"openai": "^4.104.0",
"supermemory": "^3.0.0-alpha.26",
"supermemory": "^4.25.4",
"zod": "^4.1.5"
},
"devDependencies": {
Expand Down
40 changes: 27 additions & 13 deletions packages/tools/src/ai-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
DEFAULT_VALUES,
PARAMETER_DESCRIPTIONS,
TOOL_DESCRIPTIONS,
deleteDocumentByIdentifier,
getContainerTags,
} from "./tools-shared"
import { forgetMemoryRequest } from "./shared/forget-memory"
Expand Down Expand Up @@ -50,18 +51,14 @@ export const searchMemoriesTool = (
.default(DEFAULT_VALUES.limit)
.describe(PARAMETER_DESCRIPTIONS.limit),
}),
execute: async ({
informationToGet,
includeFullDocs = DEFAULT_VALUES.includeFullDocs,
limit = DEFAULT_VALUES.limit,
}) => {
execute: async ({ informationToGet, limit = DEFAULT_VALUES.limit }) => {
try {
const response = await client.search.execute({
const response = await client.search({
q: informationToGet,
containerTags,
containerTag: containerTags[0],
limit,
chunkThreshold: DEFAULT_VALUES.chunkThreshold,
includeFullDocs,
threshold: DEFAULT_VALUES.searchThreshold,
searchMode: "hybrid",
})

return {
Expand Down Expand Up @@ -196,10 +193,12 @@ export const documentListTool = (
}),
execute: async ({ containerTag, limit, page }) => {
try {
const tag = containerTag || containerTags[0]
const scopeTags: [string, ...string[]] = containerTag
? [containerTag]
: containerTags

const response = await client.documents.list({
containerTags: [tag],
containerTags: scopeTags,
limit: limit || DEFAULT_VALUES.limit,
...(page !== undefined && { page }),
})
Expand Down Expand Up @@ -227,15 +226,29 @@ export const documentDeleteTool = (
apiKey,
...(config?.baseUrl ? { baseURL: config.baseUrl } : {}),
})
const containerTags = getContainerTags(config)
const strict = config?.strict ?? false

return tool({
description: TOOL_DESCRIPTIONS.documentDelete,
inputSchema: z.object({
documentId: z.string().describe(PARAMETER_DESCRIPTIONS.documentId),
containerTag: strict
? z
.string()
.nullable()
.describe(PARAMETER_DESCRIPTIONS.documentContainerTag)
: z
.string()
.optional()
.describe(PARAMETER_DESCRIPTIONS.documentContainerTag),
}),
execute: async ({ documentId }) => {
execute: async ({ documentId, containerTag }) => {
try {
await client.documents.delete(documentId)
const scopeTags: [string, ...string[]] = containerTag
? [containerTag]
: containerTags
await deleteDocumentByIdentifier(client, documentId, scopeTags)

return {
success: true,
Expand Down Expand Up @@ -373,3 +386,4 @@ export function supermemoryTools(
}

export { withSupermemory } from "./vercel"
export { getContainerTags } from "./tools-shared"
Loading
Loading