Merge upstream changes from supermemoryai/supermemory

This commit is contained in:
Vidya Rupak 2025-12-23 13:37:07 -07:00
commit 6db8deb09b
43 changed files with 1464 additions and 149 deletions

View file

@ -291,7 +291,7 @@ For feature requests, please provide:
- **Discord**: [Join our Discord server](https://supermemory.link/discord)
- **GitHub Discussions**: For questions and ideas
- **Issues**: For bug reports and feature requests
- **Email**: [support@supermemory.com](mailto:support@supermemory.com)
- **Email**: [support@supermemory.ai](mailto:support@supermemory.ai)
## 📄 License

View file

@ -78,7 +78,7 @@ Go to [app.supermemory.ai](https://app.supermemory.ai) and sign in with your acc
Have questions or feedback? We're here to help:
- Email: [support@supermemory.com](mailto:support@supermemory.com)
- Email: [support@supermemory.ai](mailto:support@supermemory.ai)
- Discord: [Join our Discord server](https://supermemory.link/discord)
- Documentation: [docs.supermemory.ai](https://docs.supermemory.ai)

View file

@ -679,7 +679,7 @@ function App() {
<button
className="bg-transparent border-none text-blue-500 cursor-pointer underline text-sm p-0 hover:text-blue-700"
onClick={() => {
window.open("mailto:support@supermemory.com", "_blank")
window.open("mailto:support@supermemory.ai", "_blank")
}}
type="button"
>

View file

@ -245,7 +245,7 @@ curl -X PATCH "https://api.supermemory.ai/v3/documents/doc_id" \
## Next Steps
- [Track Processing Status](/api/track-progress) - Monitor document processing
- [Track Processing Status](/memory-api/track-progress) - Monitor document processing
- [Search Memories](/search/overview) - Search your content
- [List Memories](/list-memories/overview) - Browse stored memories
- [Update & Delete](/update-delete-memories/overview) - Manage memories

View file

@ -28,7 +28,7 @@ The Supermemory Cookbook provides complete, production-ready examples that show
## Coming Soon
We're working on more comprehensive recipes. Have a suggestion? [Let us know!](mailto:support@supermemory.com)
We're working on more comprehensive recipes. Have a suggestion? [Let us know!](mailto:support@supermemory.ai)
<CardGroup cols={2}>
<Card title="Research Assistant" icon="search" color="#gray">
@ -55,10 +55,10 @@ Can't find what you're looking for?
- Browse [Search Examples](/search/examples/document-search) for specific feature usage
- Check the [AI SDK Examples](/cookbook/ai-sdk-integration) for complete implementations
- Reach out to [support](mailto:support@supermemory.com) for help
- Reach out to [support](mailto:support@supermemory.ai) for help
## Contributing Recipes
Have a great Supermemory use case? We'd love to add it to the cookbook!
[Suggest a recipe →](mailto:support@supermemory.com?subject=Cookbook%20Recipe%20Suggestion)
[Suggest a recipe →](mailto:support@supermemory.ai?subject=Cookbook%20Recipe%20Suggestion)

View file

@ -42,7 +42,7 @@
"navbar": {
"links": [
{
"href": "mailto:support@supermemory.com",
"href": "mailto:support@supermemory.ai",
"label": "Support"
}
],
@ -174,7 +174,7 @@
},
{
"group": "Migration Guides",
"pages": ["migration/from-mem0"]
"pages": ["migration/from-mem0", "migration/from-zep"]
},
{
"group": "Deployment",

View file

@ -58,7 +58,7 @@ Along with the user context, developers can also choose to do a search on the ra
- Works well with the memory engine
<Info>
You can reference the full API reference for the Memory API [here](/api-reference/manage-documents/add-document).
You can reference the full API reference for the Memory API in the API Reference tab.
</Info>

View file

@ -373,4 +373,4 @@ requests.patch(
## Next Steps
Explore more advanced features in our [API Reference](/api-reference/manage-memories/add-memory)
Explore more advanced features in our API Reference tab.

View file

@ -31,7 +31,7 @@ Check out the following resources to get started:
<Card title="Quickstart" icon="zap" href="/memory-api/overview">
Get started in 5 minutes
</Card>
<Card title="API Reference" icon="unplug" href="/api-reference">
<Card title="API Reference" icon="unplug">
Learn more about the API
</Card>
<Card title="Use Cases" icon="brain" href="/overview/use-cases">

View file

@ -81,7 +81,7 @@ client.memory.add(
This will add a new memory to your supermemory account.
Try it out in the [API Playground](/api-reference/manage-memories/add-memory).
Try it out in the API Reference tab.
## Content Processing
@ -132,7 +132,7 @@ client.search.execute(
</CodeGroup>
Try it out in the [API Playground](/api-reference/search-memories/search-memories).
Try it out in the API Reference tab.
You can do a lot more with supermemory, and we will walk through everything you need to.

View file

@ -18,7 +18,7 @@ title: "Overview"
Use supermemory with the python and javascript OpenAI SDKs
</Card>
<Card title="Request more plugins" icon="life-buoy" href="mailto:support@supermemory.com">
<Card title="Request more plugins" icon="life-buoy" href="mailto:support@supermemory.ai">
We will add support for your favorite SDKs asap.
</Card>
</Columns>

View file

@ -178,4 +178,4 @@ Forgotten memories are memories that have been explicitly forgotten using the fo
## Next Steps
Explore more advanced features in our [API Reference](/api-reference/search-memories/search-memories).
Explore more advanced features in our API Reference tab.

View file

@ -154,7 +154,7 @@ curl -X GET "https://api.supermemory.ai/v3/documents/doc_abc123" \
}
```
For more comprehensive information on the get documents by ID endpoint, refer to the [API reference.](/api-reference/manage-documents/get-document)
For more comprehensive information on the get documents by ID endpoint, refer to the API Reference tab.
## Status Values

View file

@ -292,7 +292,7 @@ response = client.chat.completions.create(
</CodeGroup>
<Note>
For enterprise migrations, [contact us](mailto:support@supermemory.com) for assistance.
For enterprise migrations, [contact us](mailto:support@supermemory.ai) for assistance.
</Note>
## Next Steps

View file

@ -0,0 +1,372 @@
---
title: "Migrating from Zep to Supermemory"
description: "Quick guide to migrate from Zep to Supermemory"
sidebarTitle: "From Zep"
---
## Key Differences
| Zep AI | Supermemory |
|--------|-------------|
| Sessions & Messages | Documents & Container Tags |
| `session.create()` | Use `containerTags` parameter |
| `memory.add(session_id, ...)` | `add({containerTag: [...]})` |
| `memory.search(session_id, {text: ...})` | `search.execute({q: ..., containerTags: [...]})` |
## Installation
<CodeGroup>
```bash Python
pip install supermemory
```
```bash TypeScript
npm install supermemory
```
</CodeGroup>
<CodeGroup>
```python Python
from supermemory import Supermemory
client = Supermemory(api_key="your-api-key")
```
```typescript TypeScript
import { Supermemory } from "supermemory";
const client = new Supermemory({ apiKey: "your-api-key" });
```
</CodeGroup>
## API Mapping
### Session Management
<CodeGroup>
```python Zep AI
session = client.session.create(
session_id="user_123",
user_id="user_123"
)
```
```python Supermemory
# No explicit session creation - use containerTag
containerTag = ["user_123"]
```
</CodeGroup>
### Adding Memories
<CodeGroup>
```python Zep AI
client.memory.add(
session_id="user_123",
memory={"content": "User prefers dark mode"}
)
```
```python Supermemory
client.add({
"content": "User prefers dark mode",
"containerTag": ["user_123"]
})
```
</CodeGroup>
### Searching
<CodeGroup>
```python Zep AI
results = client.memory.search(
session_id="user_123",
search_payload={"text": "preferences", "limit": 5}
)
```
```python Supermemory
results = client.search.execute({
"q": "preferences",
"containerTag": ["user_123"],
"limit": 5
})
```
</CodeGroup>
### Getting All Memories
<CodeGroup>
```python Zep AI
memories = client.memory.get(session_id="user_123")
```
```python Supermemory
documents = client.memories.list({
"containerTag": ["user_123"],
"limit": 100
})
```
</CodeGroup>
## Migration Steps
1. **Replace client initialization** - Use Supermemory client instead of Zep
2. **Map sessions to container tags** - Replace `session_id="user_123"` with `containerTag: ["user_123"]`
3. **Update method calls** - Use `add()` and `search.execute()` instead of `memory.add()` and `memory.search()`
4. **Change search parameter** - Use `q` instead of `text`
5. **Handle async processing** - Documents process asynchronously (status: `queued` → `done`)
## Complete Example
<CodeGroup>
```python Zep AI
from zep_python import ZepClient
client = ZepClient(api_key="...")
session = client.session.create(session_id="user_123", user_id="user_123")
client.memory.add("user_123", {
"content": "I love Python",
"role": "user"
})
results = client.memory.search("user_123", {
"text": "programming",
"limit": 3
})
```
```python Supermemory
from supermemory import Supermemory
client = Supermemory(api_key="...")
containerTag = ["user_123"]
client.add({
"content": "I love Python",
"containerTag": containerTag,
"metadata": {"role": "user"}
})
results = client.search.execute({
"q": "programming",
"containerTag": containerTag,
"limit": 3
})
```
</CodeGroup>
## Important Notes
- **No session creation needed** - Just use `containerTag` in requests
- **Messages are documents** - Store with `metadata.role` and `metadata.type`
- **Async processing** - Documents may take a moment to be searchable
- **Response structure** - Supermemory returns chunks with scores, not direct memory content
## Migrating Existing Data
### Quick Migration (All-in-One)
Complete migration in one script:
<CodeGroup>
```typescript TypeScript
import { ZepClient } from "@getzep/zep-js";
import { Supermemory } from "supermemory";
// Initialize clients
const zep = new ZepClient({ apiKey: "your_zep_api_key" });
const supermemory = new Supermemory({ apiKey: "your_supermemory_api_key" });
// Export from Zep and import to Supermemory
const sessionIds = ["session_1", "session_2"]; // Add your session IDs
for (const sessionId of sessionIds) {
const memory = await zep.memory.get(sessionId);
const memories = memory?.memories || [];
for (const mem of memories) {
if (mem.content) {
await supermemory.add({
content: mem.content,
containerTag: [`session_${sessionId}`, `user_${memory.user_id || "unknown"}`],
metadata: {
role: mem.role,
type: "message",
original_uuid: mem.uuid,
...mem.metadata
}
});
console.log(`✅ Imported: ${mem.content.substring(0, 50)}...`);
}
}
}
console.log("Migration complete!");
```
```python Python
from zep_python import ZepClient
from supermemory import Supermemory
# Initialize clients
zep = ZepClient(api_key="your_zep_api_key")
supermemory = Supermemory(api_key="your_supermemory_api_key")
# Export from Zep and import to Supermemory
session_ids = ["session_1", "session_2"] # Add your session IDs
for session_id in session_ids:
memory = zep.memory.get(session_id)
memories = memory.memories if memory else []
for mem in memories:
if mem.content:
supermemory.add({
"content": mem.content,
"containerTag": [f"session_{session_id}", f"user_{memory.user_id or 'unknown'}"],
"metadata": {
"role": mem.role,
"type": "message",
"original_uuid": mem.uuid,
**(mem.metadata or {})
}
})
print(f"✅ Imported: {mem.content[:50]}...")
print("Migration complete!")
```
</CodeGroup>
### Full Migration Script
For a complete migration script with error handling, verification, and progress tracking, copy this TypeScript script:
```typescript
import { ZepClient } from "@getzep/zep-js";
import { Supermemory } from "supermemory";
import * as dotenv from "dotenv";
import * as fs from "fs";
dotenv.config();
interface MigrationStats {
imported: number;
failed: number;
skipped: number;
}
async function migrateFromZep(
zepApiKey: string,
supermemoryApiKey: string,
sessionIds: string[]
) {
const zep = new ZepClient({ apiKey: zepApiKey });
const supermemory = new Supermemory({ apiKey: supermemoryApiKey });
const stats: MigrationStats = { imported: 0, failed: 0, skipped: 0 };
const exportedData: any = {};
console.log("🔄 Starting migration...");
// Export from Zep
for (const sessionId of sessionIds) {
try {
const session = await zep.session.get(sessionId);
const memory = await zep.memory.get(sessionId);
const memories = memory?.memories || [];
exportedData[sessionId] = {
session: { session_id: sessionId, user_id: session?.user_id },
memories: memories.map((m: any) => ({
content: m.content,
role: m.role,
metadata: m.metadata,
uuid: m.uuid,
})),
};
console.log(`✅ Exported ${memories.length} memories from ${sessionId}`);
} catch (error: any) {
console.log(`❌ Error exporting ${sessionId}: ${error.message}`);
}
}
// Save backup
const backupFile = `zep_export_${Date.now()}.json`;
fs.writeFileSync(backupFile, JSON.stringify(exportedData, null, 2));
console.log(`💾 Backup saved to: ${backupFile}`);
// Import to Supermemory
let totalMemories = 0;
for (const [sessionId, data] of Object.entries(exportedData) as any) {
const containerTag = ["imported_from_zep", `session_${sessionId}`];
if (data.session.user_id) {
containerTag.push(`user_${data.session.user_id}`);
}
for (const memory of data.memories) {
totalMemories++;
try {
if (!memory.content?.trim()) {
stats.skipped++;
continue;
}
await supermemory.add({
content: memory.content,
containerTag: containerTag,
metadata: {
source: "zep_migration",
role: memory.role,
type: "message",
original_uuid: memory.uuid,
...memory.metadata,
},
});
stats.imported++;
console.log(`✅ [${stats.imported}/${totalMemories}] Imported`);
} catch (error: any) {
stats.failed++;
console.log(`❌ Failed: ${error.message}`);
}
}
}
console.log("\n📊 Migration Summary:");
console.log(`✅ Imported: ${stats.imported}`);
console.log(`⚠️ Skipped: ${stats.skipped}`);
console.log(`❌ Failed: ${stats.failed}`);
}
// Usage
const sessionIds = ["session_1", "session_2"]; // Add your session IDs
migrateFromZep(
process.env.ZEP_API_KEY!,
process.env.SUPERMEMORY_API_KEY!,
sessionIds
).catch(console.error);
```
## Resources
- [Supermemory SDKs](/memory-api/sdks/overview)
- [API Reference](/memory-api/overview)
- [Search Documentation](/search/overview)

View file

@ -50,7 +50,7 @@ Follow these steps to build a workflow that captures and stores your Gmail messa
1. **Add an HTTP Request node** after the Gmail Trigger
2. **Method**: `POST`
3. **URL**: [`https://api.supermemory.ai/v3/documents`](/api-reference/manage-documents/add-document)
3. **URL**: `https://api.supermemory.ai/v3/documents`
4. Select your auth credentials you created with the Supermemory API Key.
#### Step 3: Format Email Data for Supermemory
@ -89,4 +89,4 @@ If you want to process attachments:
3. Check the execution to ensure the email was captured
4. Verify in Supermemory that the email appears in search results
Refer to the [API reference](/api-reference/manage-documents/add-document) to learn more about other supermemory API endpoints.
Refer to the API Reference tab to learn more about other supermemory API endpoints.

View file

@ -464,9 +464,232 @@ Combining features for comprehensive results:
</Tab>
</Tabs>
## Comon Use Cases
## Hybrid Search Mode
Hybrid search mode allows you to search both memories and document chunks in a single request. When `searchMode="hybrid"`, results contain objects with either a `memory` key (for memory results) or a `chunk` key (for chunk results).
### Basic Hybrid Search
<Tabs>
<Tab title="TypeScript">
```typescript
const results = await client.search.memories({
q: "machine learning best practices",
searchMode: "hybrid", // Search memories + chunks
limit: 10
});
// Handle mixed results
results.results.forEach(result => {
if ('memory' in result) {
console.log('Memory:', result.memory);
} else if ('chunk' in result) {
console.log('Chunk:', result.chunk);
console.log('From document:', result.documents?.[0]?.title);
}
});
```
</Tab>
<Tab title="Python">
```python
results = client.search.memories(
q="machine learning best practices",
search_mode="hybrid", # Search memories + chunks
limit=10
)
# Handle mixed results
for result in results.results:
if 'memory' in result:
print('Memory:', result['memory'])
elif 'chunk' in result:
print('Chunk:', result['chunk'])
print('From document:', result.get('documents', [{}])[0].get('title'))
```
</Tab>
<Tab title="cURL">
```bash
curl -X POST "https://api.supermemory.ai/v4/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"q": "machine learning best practices",
"searchMode": "hybrid",
"limit": 10
}'
```
</Tab>
</Tabs>
### When to Use Hybrid Mode
Use hybrid mode when:
- You want comprehensive search across both memories and documents
- Memories might not exist for certain queries but document content is available
- You need flexibility to get either memory or document chunk results
- You want a single search endpoint that covers all content types
Use memories-only mode (`searchMode="memories"`) when:
- You only need user memories and preferences
- You want faster, more focused results
- You're building a personalized chatbot that relies on user context
### Handling Mixed Results
When using hybrid mode, you'll receive mixed results. Here's how to process them:
<Tabs>
<Tab title="TypeScript">
```typescript
const results = await client.search.memories({
q: "quantum computing applications",
searchMode: "hybrid",
limit: 10
});
// Separate memory and chunk results
const memoryResults = results.results.filter(r => 'memory' in r);
const chunkResults = results.results.filter(r => 'chunk' in r);
console.log(`Found ${memoryResults.length} memories and ${chunkResults.length} chunks`);
// Process memories
memoryResults.forEach(mem => {
console.log('Memory:', mem.memory);
console.log('Similarity:', mem.similarity);
});
// Process chunks
chunkResults.forEach(chunk => {
console.log('Chunk:', chunk.chunk);
console.log('Document:', chunk.documents?.[0]?.title);
console.log('Similarity:', chunk.similarity);
});
```
</Tab>
<Tab title="Python">
```python
results = client.search.memories(
q="quantum computing applications",
search_mode="hybrid",
limit=10
)
# Separate memory and chunk results
memory_results = [r for r in results.results if 'memory' in r]
chunk_results = [r for r in results.results if 'chunk' in r]
print(f"Found {len(memory_results)} memories and {len(chunk_results)} chunks")
# Process memories
for mem in memory_results:
print('Memory:', mem['memory'])
print('Similarity:', mem['similarity'])
# Process chunks
for chunk in chunk_results:
print('Chunk:', chunk['chunk'])
print('Document:', chunk.get('documents', [{}])[0].get('title'))
print('Similarity:', chunk['similarity'])
```
</Tab>
</Tabs>
### Hybrid Search with All Features
Combining hybrid mode with other features:
<Tabs>
<Tab title="TypeScript">
```typescript
const results = await client.search.memories({
q: "research findings on AI",
searchMode: "hybrid",
containerTag: "research_team",
threshold: 0.7,
rerank: true,
include: {
documents: true,
relatedMemories: true,
summaries: true
},
limit: 10
});
// Results are automatically sorted by similarity
// Memory results have 'memory' field, chunk results have 'chunk' field
results.results.forEach(result => {
if ('memory' in result) {
// Memory result
console.log('Memory:', result.memory);
console.log('Context:', result.context);
} else {
// Chunk result
console.log('Chunk:', result.chunk);
console.log('Document:', result.documents?.[0]);
}
});
```
</Tab>
<Tab title="Python">
```python
results = client.search.memories(
q="research findings on AI",
search_mode="hybrid",
container_tag="research_team",
threshold=0.7,
rerank=True,
include={
"documents": True,
"relatedMemories": True,
"summaries": True
},
limit=10
)
# Results are automatically sorted by similarity
# Memory results have 'memory' field, chunk results have 'chunk' field
for result in results.results:
if 'memory' in result:
# Memory result
print('Memory:', result['memory'])
print('Context:', result.get('context'))
else:
# Chunk result
print('Chunk:', result['chunk'])
print('Document:', result.get('documents', [{}])[0])
```
</Tab>
<Tab title="cURL">
```bash
curl -X POST "https://api.supermemory.ai/v4/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"q": "research findings on AI",
"searchMode": "hybrid",
"containerTag": "research_team",
"threshold": 0.7,
"rerank": true,
"include": {
"documents": true,
"relatedMemories": true,
"summaries": true
},
"limit": 10
}'
```
</Tab>
</Tabs>
<Note>
**Important**: In hybrid mode, results are automatically merged and sorted by similarity score. Memory results and chunk results are deduplicated - if a chunk is already associated with a memory result, it won't appear as a separate chunk result.
</Note>
## Common Use Cases
- **Chatbots**: Basic search with container tag and low threshold
- **Q&A Systems**: Add reranking for better relevance
- **Knowledge Retrieval**: Include documents and summaries
- **Real-time Search**: Skip rewriting and reranking for maximum speed
- **Hybrid Search**: Use `searchMode="hybrid"` when you need comprehensive search across both memories and documents

View file

@ -194,33 +194,65 @@ Companies like Composio [Rube.app](https://rube.app) use memories search for let
This endpoint works best for conversational AI use cases like chatbots.
</Info>
**Hybrid Search Mode:**
The `/v4/search` endpoint supports a `searchMode` parameter with two options:
- **`"memories"`** (default): Searches only memory entries. Returns results with a `memory` key containing the memory content.
- **`"hybrid"`**: Searches memories first, then falls back to document chunks if needed. Returns mixed results where each result object has either a `memory` key (for memory results) or a `chunk` key (for chunk results from documents).
<Note>
In hybrid mode, results are automatically merged by similarity score and deduplicated. Check for the presence of `memory` or `chunk` keys to distinguish result types.
</Note>
<Tabs>
<Tab title="TypeScript">
```typescript
// Memories search
// Memories search (default mode)
const results = await client.search.memories({
q: "machine learning accuracy",
limit: 5,
containerTag: "research",
threshold: 0.7,
rerank: true
rerank: true,
searchMode: "memories" // Default: only search memories
});
// Hybrid search (memories + chunks)
const hybridResults = await client.search.memories({
q: "machine learning accuracy",
limit: 5,
containerTag: "research",
threshold: 0.7,
searchMode: "hybrid" // Search memories + fallback to chunks
});
```
</Tab>
<Tab title="Python">
```python
# Memories search
# Memories search (default mode)
results = client.search.memories(
q="machine learning accuracy",
limit=5,
container_tag="research",
threshold=0.7,
rerank=True
rerank=True,
search_mode="memories" # Default: only search memories
)
# Hybrid search (memories + chunks)
hybrid_results = client.search.memories(
q="machine learning accuracy",
limit=5,
container_tag="research",
threshold=0.7,
search_mode="hybrid" # Search memories + fallback to chunks
)
```
</Tab>
<Tab title="cURL">
```bash
# Memories search (default mode)
curl -X POST "https://api.supermemory.ai/v4/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
@ -229,7 +261,20 @@ Companies like Composio [Rube.app](https://rube.app) use memories search for let
"limit": 5,
"containerTag": "research",
"threshold": 0.7,
"rerank": true
"rerank": true,
}'
# Hybrid search (memories + chunks)
curl -X POST "https://api.supermemory.ai/v4/search" \
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"q": "machine learning accuracy",
"limit": 5,
"containerTag": "research",
"threshold": 0.7,
"rerank": true,
"searchMode": "hybrid"
}'
```
</Tab>
@ -283,7 +328,7 @@ Companies like Composio [Rube.app](https://rube.app) use memories search for let
```
The `/v4/search` endpoint searches through and returns memories.
The `/v4/search` endpoint searches through and returns memories. With `searchMode="hybrid"`, it can also return document chunks when memories aren't found, providing comprehensive search coverage.
## Direct Document Retrieval
@ -324,7 +369,7 @@ curl -X GET "https://api.supermemory.ai/v3/documents/{YOUR-DOCUMENT-ID}" \
</CodeGroup>
<Note>
This endpoint returns the complete document with all fields including content, metadata, containerTags, summary, and processing status. For more details, see the [API reference](/api-reference/manage-documents/get-document).
This endpoint returns the complete document with all fields including content, metadata, containerTags, summary, and processing status. For more details, see the API Reference tab.
</Note>
## Search Flow Architecture

View file

@ -205,6 +205,28 @@ These parameters are specific to `client.search.memories()`:
```
</ParamField>
<ParamField query="searchMode" type="string" default="memories">
**Search mode - memories only or hybrid search**
Controls whether to search only memories or also include document chunks:
- **`"memories"`** (default): Searches only memory entries. Returns results with `memory` field.
- **`"hybrid"`**: Searches memories first, then falls back to document chunks if needed. Returns mixed results with either `memory` field (for memory results) or `chunk` field (for chunk results).
<Note>
In hybrid mode, results are automatically merged and deduplicated. Results contain objects with either a `memory` key (for memory results) or a `chunk` key (for chunk results from document search).
</Note>
```typescript
searchMode: "memories" // Only search memories (default)
searchMode: "hybrid" // Search memories + fallback to chunks
```
**When to use hybrid mode:**
- When you want comprehensive search across both memories and documents
- When memories might not exist for certain queries but document content is available
- When you need the flexibility to get either memory or document chunk results
</ParamField>
<ParamField query="containerTag" type="string">
**Filter by single container tag**

View file

@ -114,6 +114,8 @@ Response from `client.search.documents()` and `client.search.execute()`:
Response from `client.search.memories()`:
When `searchMode="memories"` (default), all results are memory entries:
```json
{
"results": [
@ -160,14 +162,101 @@ Response from `client.search.memories()`:
}
```
When `searchMode="hybrid"`, results can contain both memory entries and document chunks. **Memory results have a `memory` key, chunk results have a `chunk` key:**
```json
{
"results": [
{
"id": "mem_xyz789",
"memory": "Complete memory content about quantum computing applications...",
"similarity": 0.87,
"metadata": {
"category": "research",
"topic": "quantum-computing"
},
"updatedAt": "2024-01-18T09:15:00Z",
"version": 3,
"context": {
"parents": [],
"children": []
},
"documents": [
{
"id": "doc_quantum_paper",
"title": "Quantum Computing Applications",
"type": "pdf",
"createdAt": "2024-01-10T08:00:00Z",
"updatedAt": "2024-01-10T08:00:00Z"
}
]
},
{
"id": "chunk_abc123",
"chunk": "This is a chunk of content from a document about quantum computing...",
"similarity": 0.82,
"metadata": {
"category": "research",
"source": "document"
},
"updatedAt": "2024-01-15T10:30:00Z",
"version": 1,
"context": {
"parents": [],
"children": []
},
"documents": [
{
"id": "doc_quantum_research",
"title": "Quantum Computing Research Paper",
"type": "pdf",
"metadata": {
"author": "Dr. Smith"
},
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
}
]
}
],
"timing": 198,
"total": 2
}
```
<Note>
**Distinguishing Memory vs Chunk Results:**
In hybrid mode, check which key exists on the result object:
- **Memory results**: Have a `memory` key (no `chunk` key)
- **Chunk results**: Have a `chunk` key (no `memory` key)
```typescript
// TypeScript example
results.results.forEach(result => {
if ('memory' in result) {
// This is a memory result
console.log('Memory:', result.memory);
} else if ('chunk' in result) {
// This is a chunk result
console.log('Chunk:', result.chunk);
}
});
```
</Note>
### Memory Result Fields
<ResponseField name="id" type="string">
Unique identifier for the memory entry.
Unique identifier for the memory entry or chunk ID. In hybrid mode, can be either a memory ID (e.g., `mem_xyz789`) or a chunk ID (e.g., `chunk_abc123`).
</ResponseField>
<ResponseField name="memory" type="string">
**Complete memory content**. Unlike document search which returns chunks, memory search returns the full memory text.
<ResponseField name="memory" type="string" optional>
**Complete memory content**. Only present for memory results (when `searchMode="memories"` or when a memory result is returned in hybrid mode). This field is not present for chunk results.
</ResponseField>
<ResponseField name="chunk" type="string" optional>
**Chunk content from a document**. Only present for chunk results when `searchMode="hybrid"`. This field is not present for memory results. Contains the actual text content from the document chunk.
</ResponseField>
<ResponseField name="similarity" type="number" range="0-1">
@ -189,7 +278,11 @@ Response from `client.search.memories()`:
</ResponseField>
<ResponseField name="version" type="number | null" optional>
Version number of this memory entry. Used for tracking memory evolution and relationships.
Version number of this memory entry. Used for tracking memory evolution and relationships. For chunk results, this is typically `1`.
</ResponseField>
<ResponseField name="rootMemoryId" type="string | null" optional>
Root memory ID for memory entries. Only present for memory results. Always `null` for chunk results.
</ResponseField>
<ResponseField name="context" type="object" optional>

View file

@ -7,7 +7,7 @@ description: "Learn how to use the code block to integrate supermemory with Zap
With Supermemory you can now easily add memory to your Zapier workflow steps. Here's how:
## Prerequisites
- A Supermemory API Key. Get yours [here](console.supermemory.ai)
- A Supermemory API Key. Get yours [here](https://console.supermemory.ai)
## Step-by-step tutorial
@ -30,7 +30,7 @@ For this tutorial, we're building a simple flow that adds incoming emails in Gma
![](/images/map-content-to-gmail.png)
</Step>
<Step title="Integrate Supermemory">
Since we're ingesting data here, we'll use the [add documents endpoint.](/api-reference/manage-documents/add-document)
Since we're ingesting data here, we'll use the add documents endpoint.
Add the following code block:
@ -61,4 +61,4 @@ For this tutorial, we're building a simple flow that adds incoming emails in Gma
</Note>
You can perform other operations like search, filtering, user profiles, etc., by using other Supermemory API endpoints which can be found in our [API Reference.](api-reference/search/search-memory-entries)
You can perform other operations like search, filtering, user profiles, etc., by using other Supermemory API endpoints which can be found in our API Reference tab.

View file

@ -101,7 +101,11 @@ const DocumentCard = memo(
(document.metadata?.website_og_image as string | undefined) ||
document.ogImage
}
description={document.content && typeof document.content === "string" ? document.content : undefined}
description={
document.content && typeof document.content === "string"
? document.content
: undefined
}
onOpenDetails={() => onOpenDetails(document)}
onDelete={() => onDelete(document)}
/>
@ -155,15 +159,13 @@ export const MasonryMemoryList = ({
return documents
}
return documents
.map((doc) => ({
...doc,
memoryEntries: doc.memoryEntries.filter(
(memory) =>
(memory.spaceContainerTag ?? memory.spaceId) === selectedSpace,
),
}))
.filter((doc) => doc.memoryEntries.length > 0)
return documents.map((doc) => ({
...doc,
memoryEntries: doc.memoryEntries.filter(
(memory) =>
(memory.spaceContainerTag ?? memory.spaceId) === selectedSpace,
),
}))
}, [documents, selectedSpace])
const handleOpenDetails = useCallback((document: DocumentWithMemories) => {

View file

@ -239,15 +239,13 @@ export const MemoryListView = ({
return documents
}
return documents
.map((doc) => ({
...doc,
memoryEntries: doc.memoryEntries.filter(
(memory) =>
(memory.spaceContainerTag ?? memory.spaceId) === selectedSpace,
),
}))
.filter((doc) => doc.memoryEntries.length > 0)
return documents.map((doc) => ({
...doc,
memoryEntries: doc.memoryEntries.filter(
(memory) =>
(memory.spaceContainerTag ?? memory.spaceId) === selectedSpace,
),
}))
}, [documents, selectedSpace])
const handleOpenDetails = useCallback((document: DocumentWithMemories) => {

View file

@ -194,9 +194,9 @@
"dependencies": {
"@ai-sdk/openai": "^2.0.22",
"@ai-sdk/provider": "^2.0.0",
"ai": "^5.0.26",
"ai": "^5.0.113",
"supermemory": "^3.0.0-alpha.26",
"zod": "^4.1.4",
"zod": "^4.1.8",
},
"devDependencies": {
"@total-typescript/tsconfig": "^1.0.4",
@ -222,7 +222,7 @@
},
"packages/memory-graph": {
"name": "@supermemory/memory-graph",
"version": "0.1.2",
"version": "0.1.7",
"dependencies": {
"@emotion/is-prop-valid": "^1.4.0",
"@radix-ui/react-collapsible": "^1.1.12",
@ -250,7 +250,7 @@
},
"packages/tools": {
"name": "@supermemory/tools",
"version": "1.3.13",
"version": "1.3.60",
"dependencies": {
"@ai-sdk/anthropic": "^2.0.25",
"@ai-sdk/openai": "^2.0.23",
@ -609,7 +609,7 @@
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20251202.0", "", { "os": "win32", "cpu": "x64" }, "sha512-gzeU6eDydTi7ib+Q9DD/c0hpXtqPucnHk2tfGU03mljPObYxzMkkPGgB5qxpksFvub3y4K0ChjqYxGJB4F+j3g=="],
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251219.0", "", {}, "sha512-qwuvc3ZDdCfcK9dJrBSFHOsX8kL72sypfBilzEWbbb+slB2NiggjsHeGMV2ZQiQc1zyBMQPjIvsVeE7Apxp7hw=="],
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20251223.0", "", {}, "sha512-r7oxkFjbMcmzhIrzjXaiJlGFDmmeu3+GlwkLlZbUxVWrXHTCkvqu+DrWnNmF6xZEf9j+2/PpuKIS21J522xhJA=="],
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
@ -1457,7 +1457,7 @@
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
"@smithy/util-waiter": ["@smithy/util-waiter@4.2.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g=="],
"@smithy/util-waiter": ["@smithy/util-waiter@2.2.0", "", { "dependencies": { "@smithy/abort-controller": "^2.2.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-IHk53BVw6MPMi2Gsn+hCng8rFA3ZmR3Rk7GllxDUW9qFJl/hiSvskn7XldkECapQVkIg/1dHpMAxI9xSTaLLSA=="],
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
@ -4411,10 +4411,14 @@
"@aws-sdk/client-cloudfront/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
"@aws-sdk/client-cloudfront/@smithy/util-waiter": ["@smithy/util-waiter@2.2.0", "", { "dependencies": { "@smithy/abort-controller": "^2.2.0", "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-IHk53BVw6MPMi2Gsn+hCng8rFA3ZmR3Rk7GllxDUW9qFJl/hiSvskn7XldkECapQVkIg/1dHpMAxI9xSTaLLSA=="],
"@aws-sdk/client-cloudfront/fast-xml-parser": ["fast-xml-parser@4.2.5", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g=="],
"@aws-sdk/client-dynamodb/@smithy/util-waiter": ["@smithy/util-waiter@4.2.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g=="],
"@aws-sdk/client-lambda/@smithy/util-waiter": ["@smithy/util-waiter@4.2.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g=="],
"@aws-sdk/client-s3/@smithy/util-waiter": ["@smithy/util-waiter@4.2.5", "", { "dependencies": { "@smithy/abort-controller": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g=="],
"@aws-sdk/client-sts/@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@3.0.0", "", { "dependencies": { "@aws-crypto/ie11-detection": "^3.0.0", "@aws-crypto/sha256-js": "^3.0.0", "@aws-crypto/supports-web-crypto": "^3.0.0", "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } }, "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ=="],
"@aws-sdk/client-sts/@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@3.0.0", "", { "dependencies": { "@aws-crypto/util": "^3.0.0", "@aws-sdk/types": "^3.222.0", "tslib": "^1.11.1" } }, "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ=="],
@ -4711,6 +4715,10 @@
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
"@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw=="],
"@smithy/util-waiter/@smithy/types": ["@smithy/types@2.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw=="],
"@stoplight/better-ajv-errors/leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
"@stoplight/json/safe-stable-stringify": ["safe-stable-stringify@1.1.1", "", {}, "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw=="],
@ -4727,12 +4735,12 @@
"@stoplight/yaml/@stoplight/types": ["@stoplight/types@14.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g=="],
"@supermemory/ai-sdk/ai": ["ai@5.0.113", "", { "dependencies": { "@ai-sdk/gateway": "2.0.21", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-26vivpSO/mzZj0k1Si2IpsFspp26ttQICHRySQiMrtWcRd5mnJMX2a8sG28vmZ38C+JUn1cWmfZrsLMxkSMw9g=="],
"@supermemory/ai-sdk/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"@supermemory/ai-sdk/zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="],
"@supermemory/memory-graph/@supermemory/memory-graph": ["@supermemory/memory-graph@0.1.7", "", { "dependencies": { "@emotion/is-prop-valid": "^1.4.0", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-slot": "^1.2.4", "@vanilla-extract/css": "^1.17.4", "@vanilla-extract/recipes": "^0.5.7", "@vanilla-extract/sprinkles": "^1.6.5", "lucide-react": "^0.552.0", "motion": "^12.23.24" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-5R4x4BeyX/8pq0yLxj/EefU2T48Dv1T0FRrN6SIRpBXbipYJFRLqKMBjBiDGI3EBv9hHBOJDnANnenvoxnNccg=="],
"@supermemory/memory-graph/lucide-react": ["lucide-react@0.552.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw=="],
"@supermemory/memory-graph/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
@ -5241,8 +5249,6 @@
"@aws-sdk/client-cloudfront/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"@aws-sdk/client-cloudfront/@smithy/util-waiter/@smithy/abort-controller": ["@smithy/abort-controller@2.2.0", "", { "dependencies": { "@smithy/types": "^2.12.0", "tslib": "^2.6.2" } }, "sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw=="],
"@aws-sdk/client-cloudfront/fast-xml-parser/strnum": ["strnum@1.1.2", "", {}, "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA=="],
"@aws-sdk/client-sts/@aws-crypto/sha256-browser/@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@3.0.0", "", { "dependencies": { "tslib": "^1.11.1" } }, "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg=="],
@ -5627,6 +5633,10 @@
"@stoplight/spectral-core/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"@supermemory/ai-sdk/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@2.0.21", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.19", "@vercel/oidc": "3.0.5" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-BwV7DU/lAm3Xn6iyyvZdWgVxgLu3SNXzl5y57gMvkW4nGhAOV5269IrJzQwGt03bb107sa6H6uJwWxc77zXoGA=="],
"@supermemory/ai-sdk/ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA=="],
"@supermemory/tools/@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ=="],
"@vanilla-extract/integration/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="],

View file

@ -12,9 +12,9 @@
"dependencies": {
"@ai-sdk/openai": "^2.0.22",
"@ai-sdk/provider": "^2.0.0",
"ai": "^5.0.26",
"ai": "^5.0.113",
"supermemory": "^3.0.0-alpha.26",
"zod": "^4.1.4"
"zod": "^4.1.8"
},
"devDependencies": {
"@total-typescript/tsconfig": "^1.0.4",
@ -25,7 +25,7 @@
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"types": "./dist/index-CITmF79o.d.ts",
"exports": {
".": "./dist/index.js",
"./package.json": "./package.json"

View file

@ -1,6 +1,6 @@
{
"name": "@supermemory/memory-graph",
"version": "0.1.2",
"version": "0.1.7",
"description": "Interactive graph visualization component for Supermemory - visualize and explore your memory connections",
"type": "module",
"main": "./dist/memory-graph.cjs",

View file

@ -88,7 +88,6 @@ export function useGraphData(
memoryEntries: memories,
}
})
.filter((doc) => doc.memoryEntries.length > 0)
// Apply maxNodes limit using Option B (dynamic cap per document)
if (maxNodes && maxNodes > 0) {

View file

@ -1,6 +1,6 @@
# @supermemory/tools
Memory tools for AI SDK and OpenAI function calling with supermemory.
Memory tools for AI SDK and OpenAI function calling with supermemory
This package provides supermemory tools for both AI SDK and OpenAI function calling through dedicated submodule exports, each with function-based architectures optimized for their respective use cases.

View file

@ -1,7 +1,7 @@
{
"name": "@supermemory/tools",
"type": "module",
"version": "1.3.13",
"version": "1.3.60",
"description": "Memory tools for AI SDK and OpenAI function calling with supermemory",
"scripts": {
"build": "tsdown",

View file

@ -0,0 +1,100 @@
/**
* Client for the Supermemory Conversations API
*
* This module provides a helper function to ingest conversations using the
* /v4/conversations endpoint, which supports structured messages with smart
* diffing and append detection on the backend.
*/
export interface ConversationMessage {
role: "user" | "assistant" | "system" | "tool"
content: string | ContentPart[]
name?: string
tool_calls?: ToolCall[]
tool_call_id?: string
}
export interface ContentPart {
type: "text" | "image_url"
text?: string
image_url?: { url: string }
}
export interface ToolCall {
id: string
type: "function"
function: {
name: string
arguments: string
}
}
export interface AddConversationParams {
conversationId: string
messages: ConversationMessage[]
containerTags?: string[]
metadata?: Record<string, string | number | boolean>
apiKey: string
baseUrl?: string
}
export interface AddConversationResponse {
id: string
conversationId: string
status: string
}
/**
* Adds a conversation to Supermemory using the /v4/conversations endpoint
*
* This endpoint supports:
* - Structured messages with roles (user, assistant, system, tool)
* - Multi-modal content (text, images)
* - Tool calls and responses
*
* @param params - Configuration for adding the conversation
* @returns Promise resolving to the conversation response
* @throws Error if the API request fails
*
* @example
* ```typescript
* const response = await addConversation({
* conversationId: "conv-123",
* messages: [
* { role: "user", content: "Hello!" },
* { role: "assistant", content: "Hi there!" }
* ],
* containerTags: ["user-456"],
* apiKey: process.env.SUPERMEMORY_API_KEY,
* })
* ```
*/
export async function addConversation(
params: AddConversationParams,
): Promise<AddConversationResponse> {
const baseUrl = params.baseUrl || "https://api.supermemory.ai"
const url = `${baseUrl}/v4/conversations`
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${params.apiKey}`,
},
body: JSON.stringify({
conversationId: params.conversationId,
messages: params.messages,
containerTags: params.containerTags,
metadata: params.metadata,
}),
})
if (!response.ok) {
const errorText = await response.text().catch(() => "Unknown error")
throw new Error(
`Failed to add conversation: ${response.status} ${response.statusText}. ${errorText}`,
)
}
return await response.json()
}

View file

@ -71,6 +71,7 @@ export function withSupermemory(
const verbose = options?.verbose ?? false
const mode = options?.mode ?? "profile"
const addMemory = options?.addMemory ?? "never"
const baseUrl = options?.baseUrl
const openaiWithSupermemory = createOpenAIMiddleware(
openaiClient,
@ -80,6 +81,7 @@ export function withSupermemory(
verbose,
mode,
addMemory,
baseUrl,
},
)

View file

@ -1,13 +1,22 @@
import type OpenAI from "openai"
import Supermemory from "supermemory"
import { addConversation } from "../conversations-client"
import { deduplicateMemories } from "../shared"
import { createLogger, type Logger } from "../vercel/logger"
import { convertProfileToMarkdown } from "../vercel/util"
const normalizeBaseUrl = (url?: string): string => {
const defaultUrl = "https://api.supermemory.ai"
if (!url) return defaultUrl
return url.endsWith("/") ? url.slice(0, -1) : url
}
export interface OpenAIMiddlewareOptions {
conversationId?: string
verbose?: boolean
mode?: "profile" | "query" | "full"
addMemory?: "always" | "never"
baseUrl?: string
}
interface SupermemoryProfileSearch {
@ -78,6 +87,7 @@ const getLastUserMessage = (
const supermemoryProfileSearch = async (
containerTag: string,
queryText: string,
baseUrl: string,
): Promise<SupermemoryProfileSearch> => {
const payload = queryText
? JSON.stringify({
@ -89,7 +99,7 @@ const supermemoryProfileSearch = async (
})
try {
const response = await fetch("https://api.supermemory.ai/v4/profile", {
const response = await fetch(`${baseUrl}/v4/profile`, {
method: "POST",
headers: {
"Content-Type": "application/json",
@ -147,19 +157,77 @@ const addSystemPrompt = async (
containerTag: string,
logger: Logger,
mode: "profile" | "query" | "full",
baseUrl: string,
) => {
const systemPromptExists = messages.some((msg) => msg.role === "system")
const queryText = mode !== "profile" ? getLastUserMessage(messages) : ""
const memories = await searchAndFormatMemories(
queryText,
const memoriesResponse = await supermemoryProfileSearch(
containerTag,
logger,
mode,
"chat",
queryText,
baseUrl,
)
const memoryCountStatic = memoriesResponse.profile.static?.length || 0
const memoryCountDynamic = memoriesResponse.profile.dynamic?.length || 0
logger.info("Memory search completed for chat API", {
containerTag,
memoryCountStatic,
memoryCountDynamic,
queryText:
queryText.substring(0, 100) + (queryText.length > 100 ? "..." : ""),
mode,
})
const deduplicated = deduplicateMemories({
static: memoriesResponse.profile.static,
dynamic: memoriesResponse.profile.dynamic,
searchResults: memoriesResponse.searchResults.results,
})
logger.debug("Memory deduplication completed for chat API", {
static: {
original: memoryCountStatic,
deduplicated: deduplicated.static.length,
},
dynamic: {
original: memoryCountDynamic,
deduplicated: deduplicated.dynamic.length,
},
searchResults: {
original: memoriesResponse.searchResults.results.length,
deduplicated: deduplicated.searchResults.length,
},
})
const profileData =
mode !== "query"
? convertProfileToMarkdown({
profile: {
static: deduplicated.static,
dynamic: deduplicated.dynamic,
},
searchResults: { results: [] },
})
: ""
const searchResultsMemories =
mode !== "profile"
? `Search results for user's recent message: \n${deduplicated.searchResults
.map((memory) => `- ${memory}`)
.join("\n")}`
: ""
const memories = `${profileData}\n${searchResultsMemories}`.trim()
if (memories) {
logger.debug("Memory content preview for chat API", {
content: memories,
fullLength: memories.length,
})
}
if (systemPromptExists) {
logger.debug("Added memories to existing system prompt")
return messages.map((msg) =>
@ -215,11 +283,17 @@ const getConversationContent = (
* Saves the provided content as a memory with the specified container tag and
* optional custom ID. Logs success or failure information for debugging.
*
* If customId starts with "conversation:" and messages are provided, uses the
* /v4/conversations endpoint with structured messages instead of the memories endpoint.
*
* @param client - SuperMemory client instance
* @param containerTag - The container tag/identifier for the memory
* @param content - The content to save as a memory
* @param customId - Optional custom ID for the memory (e.g., conversation ID)
* @param content - The content to save as a memory (used for fallback)
* @param customId - Optional custom ID for the memory (e.g., conversation:456)
* @param logger - Logger instance for debugging and info output
* @param messages - Optional OpenAI messages array (for conversation endpoint)
* @param apiKey - API key for direct conversation endpoint calls
* @param baseUrl - Base URL for API calls
* @returns Promise that resolves when memory is saved (or fails silently)
*
* @example
@ -227,9 +301,12 @@ const getConversationContent = (
* await addMemoryTool(
* supermemoryClient,
* "user-123",
* "User prefers React with TypeScript",
* "conversation-456",
* logger
* "User: Hello\n\nAssistant: Hi!",
* "conversation:456",
* logger,
* messages, // OpenAI messages array
* apiKey,
* baseUrl
* )
* ```
*/
@ -239,8 +316,51 @@ const addMemoryTool = async (
content: string,
customId: string | undefined,
logger: Logger,
messages?: OpenAI.Chat.Completions.ChatCompletionMessageParam[],
apiKey?: string,
baseUrl?: string,
): Promise<void> => {
try {
if (customId && messages && apiKey) {
const conversationId = customId.replace("conversation:", "")
// Convert OpenAI messages to conversation format
const conversationMessages = messages.map((msg) => ({
role: msg.role as "user" | "assistant" | "system" | "tool",
content:
typeof msg.content === "string"
? msg.content
: Array.isArray(msg.content)
? msg.content
.filter((c) => c.type === "text")
.map((c) => ({
type: "text" as const,
text: (c as { type: "text"; text: string }).text,
}))
: "",
...((msg as any).name && { name: (msg as any).name }),
...((msg as any).tool_calls && { tool_calls: (msg as any).tool_calls }),
...((msg as any).tool_call_id && { tool_call_id: (msg as any).tool_call_id }),
}))
const response = await addConversation({
conversationId,
messages: conversationMessages,
containerTags: [containerTag],
apiKey,
baseUrl,
})
logger.info("Conversation saved successfully via /v4/conversations", {
containerTag,
conversationId,
messageCount: messages.length,
responseId: response.id,
})
return
}
// Fallback to old behavior for non-conversation memories
const response = await client.memories.add({
content,
containerTags: [containerTag],
@ -293,8 +413,10 @@ export function createOpenAIMiddleware(
options?: OpenAIMiddlewareOptions,
) {
const logger = createLogger(options?.verbose ?? false)
const baseUrl = normalizeBaseUrl(options?.baseUrl)
const client = new Supermemory({
apiKey: process.env.SUPERMEMORY_API_KEY,
...(baseUrl !== "https://api.supermemory.ai" ? { baseURL: baseUrl } : {}),
})
const conversationId = options?.conversationId
@ -327,6 +449,7 @@ export function createOpenAIMiddleware(
const memoriesResponse = await supermemoryProfileSearch(
containerTag,
queryText,
baseUrl,
)
const memoryCountStatic = memoriesResponse.profile.static?.length || 0
@ -341,26 +464,41 @@ export function createOpenAIMiddleware(
mode,
})
const deduplicated = deduplicateMemories({
static: memoriesResponse.profile.static,
dynamic: memoriesResponse.profile.dynamic,
searchResults: memoriesResponse.searchResults.results,
})
logger.debug(`Memory deduplication completed for ${context} API`, {
static: {
original: memoryCountStatic,
deduplicated: deduplicated.static.length,
},
dynamic: {
original: memoryCountDynamic,
deduplicated: deduplicated.dynamic.length,
},
searchResults: {
original: memoriesResponse.searchResults.results.length,
deduplicated: deduplicated.searchResults.length,
},
})
const profileData =
mode !== "query"
? convertProfileToMarkdown({
profile: {
static: memoriesResponse.profile.static?.map((item) => item.memory),
dynamic: memoriesResponse.profile.dynamic?.map(
(item) => item.memory,
),
},
searchResults: {
results: memoriesResponse.searchResults.results.map((item) => ({
memory: item.memory,
})) as [{ memory: string }],
static: deduplicated.static,
dynamic: deduplicated.dynamic,
},
searchResults: { results: [] },
})
: ""
const searchResultsMemories =
mode !== "profile"
? `Search results for user's ${context === "chat" ? "recent message" : "input"}: \n${memoriesResponse.searchResults.results
.map((result) => `- ${result.memory}`)
? `Search results for user's ${context === "chat" ? "recent message" : "input"}: \n${deduplicated.searchResults
.map((memory) => `- ${memory}`)
.join("\n")}`
: ""
@ -380,7 +518,9 @@ export function createOpenAIMiddleware(
params: Parameters<typeof originalResponsesCreate>[0],
) => {
if (!originalResponsesCreate) {
throw new Error("Responses API is not available in this OpenAI client version")
throw new Error(
"Responses API is not available in this OpenAI client version",
)
}
const input = typeof params.input === "string" ? params.input : ""
@ -399,24 +539,26 @@ export function createOpenAIMiddleware(
const operations: Promise<any>[] = []
if (addMemory === "always" && input?.trim()) {
const content = conversationId
? `Input: ${input}`
: input
const content = conversationId ? `Input: ${input}` : input
const customId = conversationId
? `conversation:${conversationId}`
: undefined
operations.push(addMemoryTool(client, containerTag, content, customId, logger))
operations.push(
addMemoryTool(client, containerTag, content, customId, logger),
)
}
const queryText = mode !== "profile" ? input : ""
operations.push(searchAndFormatMemories(
queryText,
containerTag,
logger,
mode,
"responses",
))
operations.push(
searchAndFormatMemories(
queryText,
containerTag,
logger,
mode,
"responses",
),
)
const results = await Promise.all(operations)
const memories = results[results.length - 1] // Memory search result is always last
@ -462,16 +604,24 @@ export function createOpenAIMiddleware(
? `conversation:${conversationId}`
: undefined
operations.push(addMemoryTool(client, containerTag, content, customId, logger))
operations.push(
addMemoryTool(
client,
containerTag,
content,
customId,
logger,
messages,
process.env.SUPERMEMORY_API_KEY,
baseUrl,
),
)
}
}
operations.push(addSystemPrompt(
messages,
containerTag,
logger,
mode,
))
operations.push(
addSystemPrompt(messages, containerTag, logger, mode, baseUrl),
)
const results = await Promise.all(operations)
const enhancedMessages = results[results.length - 1] // Enhanced messages result is always last

View file

@ -45,3 +45,102 @@ export function getContainerTags(config?: {
}
return config?.containerTags ?? CONTAINER_TAG_CONSTANTS.defaultTags
}
/**
* Memory item interface representing a single memory with optional metadata
*/
export interface MemoryItem {
memory: string
metadata?: Record<string, unknown>
}
/**
* Profile data structure containing memory items from different sources
*/
export interface ProfileWithMemories {
static?: Array<MemoryItem>
dynamic?: Array<MemoryItem>
searchResults?: Array<MemoryItem>
}
/**
* Deduplicated memory strings organized by source
*/
export interface DeduplicatedMemories {
static: string[]
dynamic: string[]
searchResults: string[]
}
/**
* Deduplicates memory items across static, dynamic, and search result sources.
* Priority: Static > Dynamic > Search Results
*
* @param data - Profile data with memory items from different sources
* @returns Deduplicated memory strings for each source
*
* @example
* ```typescript
* const deduplicated = deduplicateMemories({
* static: [{ memory: "User likes TypeScript" }],
* dynamic: [{ memory: "User likes TypeScript" }, { memory: "User works remotely" }],
* searchResults: [{ memory: "User prefers async/await" }]
* });
* // Returns:
* // {
* // static: ["User likes TypeScript"],
* // dynamic: ["User works remotely"],
* // searchResults: ["User prefers async/await"]
* // }
* ```
*/
export function deduplicateMemories(
data: ProfileWithMemories,
): DeduplicatedMemories {
const staticItems = data.static ?? []
const dynamicItems = data.dynamic ?? []
const searchItems = data.searchResults ?? []
const getMemoryString = (item: MemoryItem): string | null => {
if (!item || typeof item.memory !== "string") return null
const trimmed = item.memory.trim()
return trimmed.length > 0 ? trimmed : null
}
const staticMemories: string[] = []
const seenMemories = new Set<string>()
for (const item of staticItems) {
const memory = getMemoryString(item)
if (memory !== null) {
staticMemories.push(memory)
seenMemories.add(memory)
}
}
const dynamicMemories: string[] = []
for (const item of dynamicItems) {
const memory = getMemoryString(item)
if (memory !== null && !seenMemories.has(memory)) {
dynamicMemories.push(memory)
seenMemories.add(memory)
}
}
const searchMemories: string[] = []
for (const item of searchItems) {
const memory = getMemoryString(item)
if (memory !== null && !seenMemories.has(memory)) {
searchMemories.push(memory)
seenMemories.add(memory)
}
}
return {
static: staticMemories,
dynamic: dynamicMemories,
searchResults: searchMemories,
}
}

View file

@ -8,6 +8,7 @@ interface WrapVercelLanguageModelOptions {
mode?: "profile" | "query" | "full";
addMemory?: "always" | "never";
apiKey?: string;
baseUrl?: string;
}
/**
@ -26,6 +27,7 @@ interface WrapVercelLanguageModelOptions {
* @param options.mode - Optional mode for memory search: "profile", "query", or "full" (default: "profile")
* @param options.addMemory - Optional mode for memory search: "always", "never" (default: "never")
* @param options.apiKey - Optional Supermemory API key to use instead of the environment variable
* @param options.baseUrl - Optional base URL for the Supermemory API (default: "https://api.supermemory.ai")
*
* @returns A wrapped language model that automatically includes relevant memories in prompts
*
@ -64,10 +66,11 @@ const wrapVercelLanguageModel = (
const verbose = options?.verbose ?? false
const mode = options?.mode ?? "profile"
const addMemory = options?.addMemory ?? "never"
const baseUrl = options?.baseUrl
const wrappedModel = wrapLanguageModel({
model,
middleware: createSupermemoryMiddleware(containerTag, providedApiKey, conversationId, verbose, mode, addMemory),
middleware: createSupermemoryMiddleware(containerTag, providedApiKey, conversationId, verbose, mode, addMemory, baseUrl),
})
return wrappedModel

View file

@ -1,10 +1,18 @@
import type { LanguageModelV2CallOptions } from "@ai-sdk/provider"
import { deduplicateMemories } from "../shared"
import type { Logger } from "./logger"
import { convertProfileToMarkdown, type ProfileStructure } from "./util"
export const normalizeBaseUrl = (url?: string): string => {
const defaultUrl = "https://api.supermemory.ai"
if (!url) return defaultUrl
return url.endsWith("/") ? url.slice(0, -1) : url
}
const supermemoryProfileSearch = async (
containerTag: string,
queryText: string,
baseUrl: string,
): Promise<ProfileStructure> => {
const payload = queryText
? JSON.stringify({
@ -16,7 +24,7 @@ const supermemoryProfileSearch = async (
})
try {
const response = await fetch("https://api.supermemory.ai/v4/profile", {
const response = await fetch(`${baseUrl}/v4/profile`, {
method: "POST",
headers: {
"Content-Type": "application/json",
@ -46,6 +54,7 @@ export const addSystemPrompt = async (
containerTag: string,
logger: Logger,
mode: "profile" | "query" | "full",
baseUrl = "https://api.supermemory.ai",
) => {
const systemPromptExists = params.prompt.some(
(prompt) => prompt.role === "system",
@ -65,6 +74,7 @@ export const addSystemPrompt = async (
const memoriesResponse = await supermemoryProfileSearch(
containerTag,
queryText,
baseUrl,
)
const memoryCountStatic = memoriesResponse.profile.static?.length || 0
@ -79,12 +89,41 @@ export const addSystemPrompt = async (
mode,
})
const deduplicated = deduplicateMemories({
static: memoriesResponse.profile.static,
dynamic: memoriesResponse.profile.dynamic,
searchResults: memoriesResponse.searchResults.results,
})
logger.debug("Memory deduplication completed", {
static: {
original: memoryCountStatic,
deduplicated: deduplicated.static.length,
},
dynamic: {
original: memoryCountDynamic,
deduplicated: deduplicated.dynamic.length,
},
searchResults: {
original: memoriesResponse.searchResults.results.length,
deduplicated: deduplicated.searchResults.length,
},
})
const profileData =
mode !== "query" ? convertProfileToMarkdown(memoriesResponse) : ""
mode !== "query"
? convertProfileToMarkdown({
profile: {
static: deduplicated.static,
dynamic: deduplicated.dynamic,
},
searchResults: { results: [] },
})
: ""
const searchResultsMemories =
mode !== "profile"
? `Search results for user's recent message: \n${memoriesResponse.searchResults.results
.map((result) => `- ${result.memory}`)
? `Search results for user's recent message: \n${deduplicated.searchResults
.map((memory) => `- ${memory}`)
.join("\n")}`
: ""

View file

@ -4,13 +4,17 @@ import type {
LanguageModelV2StreamPart,
} from "@ai-sdk/provider"
import Supermemory from "supermemory"
import {
addConversation,
type ConversationMessage,
} from "../conversations-client"
import { createLogger, type Logger } from "./logger"
import {
type OutputContentItem,
getLastUserMessage,
filterOutSupermemories,
} from "./util"
import { addSystemPrompt } from "./memory-prompt"
import { addSystemPrompt, normalizeBaseUrl } from "./memory-prompt"
const getConversationContent = (params: LanguageModelV2CallOptions) => {
return params.prompt
@ -31,6 +35,66 @@ const getConversationContent = (params: LanguageModelV2CallOptions) => {
.join("\n\n")
}
const convertToConversationMessages = (
params: LanguageModelV2CallOptions,
assistantResponseText: string,
): ConversationMessage[] => {
const messages: ConversationMessage[] = []
for (const msg of params.prompt) {
if (typeof msg.content === "string") {
const filteredContent = filterOutSupermemories(msg.content)
if (filteredContent) {
messages.push({
role: msg.role as "user" | "assistant" | "system" | "tool",
content: filteredContent,
})
}
} else {
const contentParts = msg.content
.map((c) => {
if (c.type === "text") {
const filteredText = filterOutSupermemories(c.text)
if (filteredText) {
return {
type: "text" as const,
text: filteredText,
}
}
}
if (
c.type === "file" &&
typeof c.data === "string" &&
c.mediaType.startsWith("image/")
) {
return {
type: "image_url" as const,
image_url: { url: c.data },
}
}
return null
})
.filter((part) => part !== null)
if (contentParts.length > 0) {
messages.push({
role: msg.role as "user" | "assistant" | "system" | "tool",
content: contentParts,
})
}
}
}
if (assistantResponseText) {
messages.push({
role: "assistant",
content: assistantResponseText,
})
}
return messages
}
const addMemoryTool = async (
client: Supermemory,
containerTag: string,
@ -38,21 +102,47 @@ const addMemoryTool = async (
assistantResponseText: string,
params: LanguageModelV2CallOptions,
logger: Logger,
apiKey: string,
baseUrl: string,
): Promise<void> => {
const userMessage = getLastUserMessage(params)
const content = conversationId
? `${getConversationContent(params)} \n\n Assistant: ${assistantResponseText}`
: `User: ${userMessage} \n\n Assistant: ${assistantResponseText}`
const customId = conversationId ? `conversation:${conversationId}` : undefined
try {
if (customId && conversationId) {
const conversationMessages = convertToConversationMessages(
params,
assistantResponseText,
)
const response = await addConversation({
conversationId,
messages: conversationMessages,
containerTags: [containerTag],
apiKey,
baseUrl,
})
logger.info("Conversation saved successfully via /v4/conversations", {
containerTag,
conversationId,
messageCount: conversationMessages.length,
responseId: response.id,
})
return
}
const userMessage = getLastUserMessage(params)
const content = conversationId
? `${getConversationContent(params)} \n\n Assistant: ${assistantResponseText}`
: `User: ${userMessage} \n\n Assistant: ${assistantResponseText}`
const response = await client.memories.add({
content,
containerTags: [containerTag],
customId,
})
logger.info("Memory saved successfully", {
logger.info("Memory saved successfully via /v3/documents", {
containerTag,
customId,
content,
@ -73,11 +163,16 @@ export const createSupermemoryMiddleware = (
verbose = false,
mode: "profile" | "query" | "full" = "profile",
addMemory: "always" | "never" = "never",
baseUrl?: string,
): LanguageModelV2Middleware => {
const logger = createLogger(verbose)
const normalizedBaseUrl = normalizeBaseUrl(baseUrl)
const client = new Supermemory({
apiKey,
...(normalizedBaseUrl !== "https://api.supermemory.ai"
? { baseURL: normalizedBaseUrl }
: {}),
})
return {
@ -102,6 +197,7 @@ export const createSupermemoryMiddleware = (
containerTag,
logger,
mode,
normalizedBaseUrl,
)
return transformedParams
},
@ -123,6 +219,8 @@ export const createSupermemoryMiddleware = (
assistantResponseText,
params,
logger,
apiKey,
normalizedBaseUrl,
)
}
@ -168,6 +266,8 @@ export const createSupermemoryMiddleware = (
generatedText,
params,
logger,
apiKey,
normalizedBaseUrl,
)
}
},

View file

@ -1,16 +1,22 @@
import type { LanguageModelV2CallOptions, LanguageModelV2Message } from "@ai-sdk/provider"
export interface ProfileStructure {
profile: {
static?: Array<{ memory: string; metadata?: Record<string, unknown> }>
dynamic?: Array<{ memory: string; metadata?: Record<string, unknown> }>
}
searchResults: {
results: Array<{ memory: string; metadata?: Record<string, unknown> }>
}
}
export interface ProfileMarkdownData {
profile: {
static?: string[]
dynamic?: string[]
}
searchResults: {
results: [
{
memory: string
},
]
results: Array<{ memory: string }>
}
}
@ -32,12 +38,11 @@ export type OutputContentItem =
}
/**
* Convert ProfileStructure to markdown
* based on profile.static and profile.dynamic properties
* @param data ProfileStructure
* @returns Markdown string
* Convert profile data to markdown format
* @param data Profile data with string arrays for static and dynamic memories
* @returns Markdown string with profile sections
*/
export function convertProfileToMarkdown(data: ProfileStructure): string {
export function convertProfileToMarkdown(data: ProfileMarkdownData): string {
const sections: string[] = []
if (data.profile.static && data.profile.static.length > 0) {

View file

@ -0,0 +1,53 @@
import { OpenAI } from "openai"
import { withSupermemory } from "./src/openai"
// Make sure to set these environment variables:
// OPENAI_API_KEY=your_openai_api_key
// SUPERMEMORY_API_KEY=your_supermemory_api_key
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
})
// Wrap OpenAI client with supermemory
const openaiWithSupermemory = withSupermemory(openai, "test_user_123", {
verbose: true, // Enable logging to see what's happening
mode: "full", // Search both profile and query memories
addMemory: "always", // Auto-save conversations as memories
})
// async function testChatCompletion() {
// console.log("\n=== Testing Chat Completion ===")
// const response = await openaiWithSupermemory.chat.completions.create({
// model: "gpt-4o-mini",
// messages: [
// { role: "user", content: "My favorite color is blue" },
// ],
// })
// console.log("Response:", response.choices[0]?.message.content)
// }
async function testResponses() {
console.log("\n=== Testing Responses API ===")
const response = await openaiWithSupermemory.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "user", content: "what's my favoritge color?" },
],
})
console.log("Response:", JSON.stringify(response.choices[0]?.message.content, null, 2))
}
// Run tests
async function main() {
try {
// await testChatCompletion()
await testResponses()
} catch (error) {
console.error("Error:", error)
}
}
main()

View file

@ -1,5 +1,4 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp

View file

@ -19,6 +19,7 @@ export async function POST(req: Request) {
mode: "full",
addMemory: "always",
verbose: true,
baseUrl: process.env.SUPERMEMORY_BASE_URL,
})
const completion = await openaiWithSupermemory.chat.completions.create({

View file

@ -7,6 +7,7 @@ const model = withSupermemory(openai("gpt-4"), "user-123", {
addMemory: "always",
conversationId: "chat-session",
verbose: true,
baseUrl: process.env.SUPERMEMORY_BASE_URL,
})
export async function POST(req: Request) {

View file

@ -17,4 +17,5 @@ export default defineConfig({
sourcemap: false,
},
exports: true,
unbundle: true,
})

View file

@ -29,19 +29,17 @@ export function useGraphData(
const allEdges: GraphEdge[] = [];
// Filter documents that have memories in selected space
const filteredDocuments = data.documents
.map((doc) => ({
...doc,
memoryEntries:
selectedSpace === "all"
? doc.memoryEntries
: doc.memoryEntries.filter(
(memory) =>
(memory.spaceContainerTag ?? memory.spaceId ?? "default") ===
selectedSpace,
),
}))
.filter((doc) => doc.memoryEntries.length > 0);
const filteredDocuments = data.documents.map((doc) => ({
...doc,
memoryEntries:
selectedSpace === "all"
? doc.memoryEntries
: doc.memoryEntries.filter(
(memory) =>
(memory.spaceContainerTag ?? memory.spaceId ?? "default") ===
selectedSpace,
),
}));
// Group documents by space for better clustering
const documentsBySpace = new Map<string, typeof filteredDocuments>();