aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorDhravya Shah <[email protected]>2026-01-18 16:55:32 -0800
committerGitHub <[email protected]>2026-01-18 16:55:32 -0800
commit87b361c26bf5fc16049cd2727825891aa14b8e8b (patch)
treec2f9f4f6223a7c1734578b772a16490ba2eb8b96 /packages
parentAdd Claude Code GitHub Workflow (#681) (diff)
downloadsupermemory-87b361c26bf5fc16049cd2727825891aa14b8e8b.tar.xz
supermemory-87b361c26bf5fc16049cd2727825891aa14b8e8b.zip
docs changes (#678)
Co-authored-by: Claude Opus 4.5 <[email protected]>
Diffstat (limited to 'packages')
-rw-r--r--packages/ai-sdk/package.json2
-rw-r--r--packages/ai-sdk/src/tools.ts2
-rw-r--r--packages/docs-test/.gitignore1
-rw-r--r--packages/docs-test/package.json26
-rw-r--r--packages/docs-test/requirements.txt5
-rw-r--r--packages/docs-test/run.ts121
-rw-r--r--packages/docs-test/tests/integrations/ai-sdk.ts100
-rw-r--r--packages/docs-test/tests/integrations/claude-memory.ts101
-rw-r--r--packages/docs-test/tests/integrations/openai-sdk.py90
-rw-r--r--packages/docs-test/tests/integrations/openai-sdk.ts80
-rw-r--r--packages/docs-test/tests/python/quickstart.py53
-rw-r--r--packages/docs-test/tests/python/sdk.py152
-rw-r--r--packages/docs-test/tests/python/search.py113
-rw-r--r--packages/docs-test/tests/python/user_profiles.py102
-rw-r--r--packages/docs-test/tests/typescript/quickstart.ts57
-rw-r--r--packages/docs-test/tests/typescript/sdk.ts146
-rw-r--r--packages/docs-test/tests/typescript/search.ts110
-rw-r--r--packages/docs-test/tests/typescript/user-profiles.ts89
-rw-r--r--packages/openai-sdk-python/src/supermemory_openai/middleware.py114
-rw-r--r--packages/openai-sdk-python/src/supermemory_openai/tools.py8
-rw-r--r--packages/pipecat-sdk-python/src/supermemory_pipecat/service.py25
-rw-r--r--packages/tools/src/ai-sdk.ts2
-rw-r--r--packages/tools/src/claude-memory.ts22
-rw-r--r--packages/tools/src/openai/middleware.ts2
-rw-r--r--packages/tools/src/openai/tools.ts2
-rw-r--r--packages/tools/src/vercel/middleware.ts2
26 files changed, 1456 insertions, 71 deletions
diff --git a/packages/ai-sdk/package.json b/packages/ai-sdk/package.json
index 5e59fa5e..deae39ad 100644
--- a/packages/ai-sdk/package.json
+++ b/packages/ai-sdk/package.json
@@ -25,7 +25,7 @@
},
"main": "./dist/index.js",
"module": "./dist/index.js",
- "types": "./dist/index-CITmF79o.d.ts",
+ "types": "./dist/index-h2cm3lSK.d.ts",
"exports": {
".": "./dist/index.js",
"./package.json": "./package.json"
diff --git a/packages/ai-sdk/src/tools.ts b/packages/ai-sdk/src/tools.ts
index f04df10b..f5126863 100644
--- a/packages/ai-sdk/src/tools.ts
+++ b/packages/ai-sdk/src/tools.ts
@@ -90,7 +90,7 @@ export function supermemoryTools(
try {
const metadata: Record<string, string | number | boolean> = {}
- const response = await client.memories.add({
+ const response = await client.add({
content: memory,
containerTags,
...(Object.keys(metadata).length > 0 && { metadata }),
diff --git a/packages/docs-test/.gitignore b/packages/docs-test/.gitignore
new file mode 100644
index 00000000..4c49bd78
--- /dev/null
+++ b/packages/docs-test/.gitignore
@@ -0,0 +1 @@
+.env
diff --git a/packages/docs-test/package.json b/packages/docs-test/package.json
new file mode 100644
index 00000000..1c1b6674
--- /dev/null
+++ b/packages/docs-test/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "docs-test",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "test": "bun run run.ts",
+ "test:ts": "bun run run.ts typescript",
+ "test:py": "bun run run.ts python",
+ "test:integrations": "bun run run.ts integrations",
+ "test:quickstart": "bun run run.ts quickstart",
+ "test:sdk": "bun run run.ts sdk",
+ "test:search": "bun run run.ts search"
+ },
+ "dependencies": {
+ "@ai-sdk/anthropic": "^3.0.15",
+ "@ai-sdk/openai": "latest",
+ "@anthropic-ai/sdk": "latest",
+ "@supermemory/tools": "workspace:*",
+ "ai": "latest",
+ "dotenv": "latest",
+ "openai": "latest",
+ "supermemory": "latest",
+ "zod": "latest"
+ }
+}
diff --git a/packages/docs-test/requirements.txt b/packages/docs-test/requirements.txt
new file mode 100644
index 00000000..ad5ebad6
--- /dev/null
+++ b/packages/docs-test/requirements.txt
@@ -0,0 +1,5 @@
+supermemory
+openai
+anthropic
+httpx
+python-dotenv
diff --git a/packages/docs-test/run.ts b/packages/docs-test/run.ts
new file mode 100644
index 00000000..a98d4a1b
--- /dev/null
+++ b/packages/docs-test/run.ts
@@ -0,0 +1,121 @@
+#!/usr/bin/env bun
+import { spawn } from "child_process"
+import path from "path"
+
+const args = process.argv.slice(2)
+const filter = args[0] // e.g., "typescript", "python", "integrations", or specific file
+
+const TESTS_DIR = path.join(import.meta.dir, "tests")
+
+interface TestFile {
+ name: string
+ path: string
+ type: "ts" | "py"
+}
+
+function getTests(): TestFile[] {
+ const tests: TestFile[] = []
+
+ // TypeScript tests
+ const tsTests = ["quickstart", "sdk", "search", "user-profiles"]
+ for (const t of tsTests) {
+ tests.push({
+ name: `typescript/${t}`,
+ path: path.join(TESTS_DIR, "typescript", `${t}.ts`),
+ type: "ts",
+ })
+ }
+
+ // Python tests
+ const pyTests = ["quickstart", "sdk", "search", "user_profiles"]
+ for (const t of pyTests) {
+ tests.push({
+ name: `python/${t}`,
+ path: path.join(TESTS_DIR, "python", `${t}.py`),
+ type: "py",
+ })
+ }
+
+ // Integration tests
+ const intTests = [
+ { name: "ai-sdk", type: "ts" as const },
+ { name: "openai-sdk", type: "ts" as const },
+ { name: "openai-sdk", type: "py" as const },
+ { name: "claude-memory", type: "ts" as const },
+ ]
+ for (const t of intTests) {
+ tests.push({
+ name: `integrations/${t.name}`,
+ path: path.join(TESTS_DIR, "integrations", `${t.name}.${t.type === "ts" ? "ts" : "py"}`),
+ type: t.type,
+ })
+ }
+
+ return tests
+}
+
+async function runTest(test: TestFile): Promise<boolean> {
+ return new Promise((resolve) => {
+ console.log(`\n${"=".repeat(60)}`)
+ console.log(`Running: ${test.name}`)
+ console.log("=".repeat(60))
+
+ const cmd = test.type === "ts" ? "bun" : path.join(import.meta.dir, ".venv", "bin", "python3")
+ const proc = spawn(cmd, [test.path], {
+ stdio: "inherit",
+ env: { ...process.env },
+ })
+
+ proc.on("close", (code) => {
+ resolve(code === 0)
+ })
+ })
+}
+
+async function main() {
+ console.log("Supermemory Docs Test Runner")
+ console.log("============================\n")
+
+ let tests = getTests()
+
+ // Filter tests if specified
+ if (filter) {
+ tests = tests.filter((t) => t.name.includes(filter) || t.type === filter.replace(".", ""))
+ }
+
+ if (tests.length === 0) {
+ console.log("No tests matched the filter:", filter)
+ console.log("\nAvailable tests:")
+ getTests().forEach((t) => console.log(` - ${t.name} (${t.type})`))
+ process.exit(1)
+ }
+
+ console.log(`Running ${tests.length} test(s)...\n`)
+
+ const results: { test: string; passed: boolean }[] = []
+
+ for (const test of tests) {
+ const passed = await runTest(test)
+ results.push({ test: test.name, passed })
+ }
+
+ // Summary
+ console.log(`\n${"=".repeat(60)}`)
+ console.log("SUMMARY")
+ console.log("=".repeat(60))
+
+ const passed = results.filter((r) => r.passed).length
+ const failed = results.filter((r) => !r.passed).length
+
+ for (const r of results) {
+ console.log(`${r.passed ? "✅" : "❌"} ${r.test}`)
+ }
+
+ console.log(`\nTotal: ${passed} passed, ${failed} failed`)
+
+ if (failed > 0) {
+ process.exit(1)
+ }
+}
+
+main().catch(console.error)
diff --git a/packages/docs-test/tests/integrations/ai-sdk.ts b/packages/docs-test/tests/integrations/ai-sdk.ts
new file mode 100644
index 00000000..7caba72f
--- /dev/null
+++ b/packages/docs-test/tests/integrations/ai-sdk.ts
@@ -0,0 +1,100 @@
+import "dotenv/config"
+import { openai } from "@ai-sdk/openai"
+import { createAnthropic } from "@ai-sdk/anthropic"
+import {
+ withSupermemory,
+ supermemoryTools,
+ searchMemoriesTool,
+ addMemoryTool,
+ type MemoryPromptData,
+} from "@supermemory/tools/ai-sdk"
+
+async function testMiddleware() {
+ console.log("=== Middleware ===")
+
+ // Basic wrapper
+ const model = withSupermemory(openai("gpt-4"), "user-123")
+ console.log("✓ withSupermemory basic")
+
+ // With addMemory option
+ const modelWithAdd = withSupermemory(openai("gpt-4"), "user-123", {
+ addMemory: "always",
+ })
+ console.log("✓ withSupermemory with addMemory")
+
+ // With verbose logging
+ const modelVerbose = withSupermemory(openai("gpt-4"), "user-123", {
+ verbose: true,
+ })
+ console.log("✓ withSupermemory with verbose")
+}
+
+async function testSearchModes() {
+ console.log("\n=== Search Modes ===")
+
+ const profileModel = withSupermemory(openai("gpt-4"), "user-123", { mode: "profile" })
+ console.log("✓ mode: profile")
+
+ const queryModel = withSupermemory(openai("gpt-4"), "user-123", { mode: "query" })
+ console.log("✓ mode: query")
+
+ const fullModel = withSupermemory(openai("gpt-4"), "user-123", { mode: "full" })
+ console.log("✓ mode: full")
+}
+
+async function testCustomPrompt() {
+ console.log("\n=== Custom Prompt Template ===")
+
+ const anthropic = createAnthropic({ apiKey: "test-key" })
+
+ const claudePrompt = (data: MemoryPromptData) =>
+ `
+<context>
+ <user_profile>${data.userMemories}</user_profile>
+ <relevant_memories>${data.generalSearchMemories}</relevant_memories>
+</context>
+`.trim()
+
+ const model = withSupermemory(anthropic("claude-3-sonnet-20240229"), "user-123", {
+ mode: "full",
+ promptTemplate: claudePrompt,
+ })
+ console.log("✓ Custom prompt template")
+}
+
+async function testTools() {
+ console.log("\n=== Memory Tools ===")
+
+ // All tools
+ const tools = supermemoryTools("YOUR_API_KEY")
+ console.log("✓ supermemoryTools")
+
+ // Individual tools
+ const searchTool = searchMemoriesTool("API_KEY", { projectId: "personal" })
+ console.log("✓ searchMemoriesTool")
+
+ const addTool = addMemoryTool("API_KEY")
+ console.log("✓ addMemoryTool")
+
+ // Combined
+ const toolsObj = {
+ searchMemories: searchTool,
+ addMemory: addTool,
+ }
+ console.log("✓ Combined tools object")
+}
+
+async function main() {
+ console.log("AI SDK Integration Tests")
+ console.log("========================\n")
+
+ await testMiddleware()
+ await testSearchModes()
+ await testCustomPrompt()
+ await testTools()
+
+ console.log("\n========================")
+ console.log("✅ All AI SDK tests passed!")
+}
+
+main().catch(console.error)
diff --git a/packages/docs-test/tests/integrations/claude-memory.ts b/packages/docs-test/tests/integrations/claude-memory.ts
new file mode 100644
index 00000000..c4078789
--- /dev/null
+++ b/packages/docs-test/tests/integrations/claude-memory.ts
@@ -0,0 +1,101 @@
+import "dotenv/config"
+import { createClaudeMemoryTool } from "@supermemory/tools/claude-memory"
+
+async function testConfiguration() {
+ console.log("=== Configuration ===")
+
+ // Basic
+ const tool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
+ projectId: "my-app",
+ })
+ console.log("✓ Basic config")
+
+ // Full options
+ const toolFull = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
+ projectId: "my-app",
+ containerTags: ["user-123", "project-alpha"],
+ memoryContainerTag: "my_memory_prefix",
+ })
+ console.log("✓ Full config")
+
+ return tool
+}
+
+async function testMethods(tool: ReturnType<typeof createClaudeMemoryTool>) {
+ console.log("\n=== Methods ===")
+
+ console.log(`✓ handleCommand exists: ${typeof tool.handleCommand === "function"}`)
+ console.log(
+ `✓ handleCommandForToolResult exists: ${typeof tool.handleCommandForToolResult === "function"}`
+ )
+}
+
+async function testCommands(tool: ReturnType<typeof createClaudeMemoryTool>) {
+ console.log("\n=== Commands ===")
+
+ // View (list)
+ const listResult = await tool.handleCommand({
+ command: "view",
+ path: "/memories/",
+ })
+ console.log(`✓ view (list): ${listResult.success}`)
+
+ // Create
+ const createResult = await tool.handleCommand({
+ command: "create",
+ path: "/memories/test-file.txt",
+ file_text: "User prefers dark mode\nFavorite language: TypeScript",
+ })
+ console.log(`✓ create: ${createResult.success}`)
+
+ // Wait for indexing
+ await new Promise((r) => setTimeout(r, 3000))
+
+ // str_replace
+ const replaceResult = await tool.handleCommand({
+ command: "str_replace",
+ path: "/memories/test-file.txt",
+ old_str: "dark mode",
+ new_str: "light mode",
+ })
+ console.log(`✓ str_replace: ${replaceResult.success}`)
+
+ // Delete
+ const deleteResult = await tool.handleCommand({
+ command: "delete",
+ path: "/memories/test-file.txt",
+ })
+ console.log(`✓ delete: ${deleteResult.success}`)
+}
+
+async function testToolResultFormat(tool: ReturnType<typeof createClaudeMemoryTool>) {
+ console.log("\n=== Tool Result Format ===")
+
+ const toolResult = await tool.handleCommandForToolResult(
+ { command: "view", path: "/memories/" },
+ "test-tool-id-123"
+ )
+
+ const hasCorrectStructure =
+ toolResult.type === "tool_result" &&
+ toolResult.tool_use_id === "test-tool-id-123" &&
+ typeof toolResult.content === "string" &&
+ typeof toolResult.is_error === "boolean"
+
+ console.log(`✓ Tool result structure: ${hasCorrectStructure}`)
+}
+
+async function main() {
+ console.log("Claude Memory Tool Tests")
+ console.log("========================\n")
+
+ const tool = await testConfiguration()
+ await testMethods(tool)
+ await testCommands(tool)
+ await testToolResultFormat(tool)
+
+ console.log("\n========================")
+ console.log("✅ All Claude Memory tests passed!")
+}
+
+main().catch(console.error)
diff --git a/packages/docs-test/tests/integrations/openai-sdk.py b/packages/docs-test/tests/integrations/openai-sdk.py
new file mode 100644
index 00000000..8f8c2d40
--- /dev/null
+++ b/packages/docs-test/tests/integrations/openai-sdk.py
@@ -0,0 +1,90 @@
+import os
+from dotenv import load_dotenv
+from openai import OpenAI
+
+load_dotenv()
+
+
+def test_openai_with_supermemory():
+ """Test OpenAI SDK with Supermemory context"""
+ print("=== OpenAI SDK with Supermemory ===")
+
+ if not os.getenv("OPENAI_API_KEY"):
+ print("⚠ OPENAI_API_KEY not set, skipping live tests")
+ return
+
+ # This demonstrates manual integration pattern for Python
+ # since @supermemory/tools is TypeScript-only
+
+ from supermemory import Supermemory
+
+ memory_client = Supermemory()
+ openai_client = OpenAI()
+
+ USER_ID = "docs-test-openai-py"
+
+ # Get memory context
+ profile = memory_client.profile(
+ container_tag=USER_ID,
+ q="What are my preferences?",
+ )
+
+ context = f"""User Profile:
+{chr(10).join(profile.profile.static)}
+
+Relevant Memories:
+{chr(10).join(r.content for r in profile.search_results.results)}"""
+
+ print("✓ Got memory context")
+
+ # Use with OpenAI
+ response = openai_client.chat.completions.create(
+ model="gpt-4o-mini",
+ messages=[
+ {"role": "system", "content": f"Use this context:\n{context}"},
+ {"role": "user", "content": "What do you know about me?"},
+ ],
+ max_tokens=100,
+ )
+ print(f"✓ OpenAI response: {response.choices[0].message.content[:50]}...")
+
+
+def test_save_conversation():
+ """Test saving conversation to Supermemory"""
+ print("\n=== Save Conversation ===")
+
+ if not os.getenv("OPENAI_API_KEY"):
+ print("⚠ Skipped (no OPENAI_API_KEY)")
+ return
+
+ from supermemory import Supermemory
+
+ memory_client = Supermemory()
+ USER_ID = "docs-test-openai-py"
+
+ conversation = [
+ {"role": "user", "content": "My favorite programming language is Python"},
+ {"role": "assistant", "content": "That's great! Python is very versatile."},
+ ]
+
+ # Save conversation
+ memory_client.add(
+ content="\n".join(f"{m['role']}: {m['content']}" for m in conversation),
+ container_tag=USER_ID,
+ )
+ print("✓ Saved conversation to memory")
+
+
+def main():
+ print("OpenAI SDK Integration Tests (Python)")
+ print("=====================================\n")
+
+ test_openai_with_supermemory()
+ test_save_conversation()
+
+ print("\n=====================================")
+ print("✅ All OpenAI SDK tests passed!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/packages/docs-test/tests/integrations/openai-sdk.ts b/packages/docs-test/tests/integrations/openai-sdk.ts
new file mode 100644
index 00000000..c651e7dc
--- /dev/null
+++ b/packages/docs-test/tests/integrations/openai-sdk.ts
@@ -0,0 +1,80 @@
+import "dotenv/config"
+import OpenAI from "openai"
+import { withSupermemory } from "@supermemory/tools/openai"
+
+const OPENAI_API_KEY = process.env.OPENAI_API_KEY
+
+async function testOpenAIWrapper() {
+ console.log("=== OpenAI SDK Wrapper ===")
+
+ if (!OPENAI_API_KEY) {
+ console.log("⚠ OPENAI_API_KEY not set, skipping live tests")
+ return false
+ }
+
+ const openai = new OpenAI()
+
+ // Basic wrapper
+ const client = withSupermemory(openai, "docs-test-openai")
+ console.log("✓ withSupermemory basic")
+
+ // With options
+ const clientWithOptions = withSupermemory(openai, "docs-test-openai", {
+ mode: "full",
+ addMemory: "always",
+ verbose: true,
+ })
+ console.log("✓ withSupermemory with options")
+}
+
+async function testChatCompletion() {
+ console.log("\n=== Chat Completion with Memory ===")
+
+ if (!OPENAI_API_KEY) {
+ console.log("⚠ OPENAI_API_KEY not set, skipping")
+ return
+ }
+
+ const openai = new OpenAI()
+ const client = withSupermemory(openai, "docs-test-openai", {
+ mode: "full",
+ addMemory: "always",
+ })
+
+ // Add context
+ const addResponse = await client.chat.completions.create({
+ model: "gpt-4o-mini",
+ messages: [
+ { role: "system", content: "You are a helpful assistant." },
+ { role: "user", content: "Remember that my favorite color is blue." },
+ ],
+ max_tokens: 100,
+ })
+ console.log("✓ Add context:", addResponse.choices[0]?.message.content?.substring(0, 50))
+
+ // Retrieve context
+ const retrieveResponse = await client.chat.completions.create({
+ model: "gpt-4o-mini",
+ messages: [
+ { role: "system", content: "You are a helpful assistant." },
+ { role: "user", content: "What do you know about my preferences?" },
+ ],
+ max_tokens: 100,
+ })
+ console.log("✓ Retrieve context:", retrieveResponse.choices[0]?.message.content?.substring(0, 50))
+}
+
+async function main() {
+ console.log("OpenAI SDK Integration Tests")
+ console.log("============================\n")
+
+ const hasKey = await testOpenAIWrapper()
+ if (hasKey !== false) {
+ await testChatCompletion()
+ }
+
+ console.log("\n============================")
+ console.log("✅ All OpenAI SDK tests passed!")
+}
+
+main().catch(console.error)
diff --git a/packages/docs-test/tests/python/quickstart.py b/packages/docs-test/tests/python/quickstart.py
new file mode 100644
index 00000000..e0689900
--- /dev/null
+++ b/packages/docs-test/tests/python/quickstart.py
@@ -0,0 +1,53 @@
+import os
+from dotenv import load_dotenv
+
+load_dotenv()
+
+from supermemory import Supermemory
+
+client = Supermemory()
+USER_ID = "docs-test-user-py"
+
+conversation = [
+ {"role": "assistant", "content": "Hello, how are you doing?"},
+ {"role": "user", "content": "Hello! I am Dhravya. I am 20 years old. I love to code!"},
+ {"role": "user", "content": "Can I go to the club?"},
+]
+
+print("Testing quickstart Python code...\n")
+
+# Get user profile + relevant memories for context
+print("1. Getting user profile...")
+profile = client.profile(container_tag=USER_ID, q=conversation[-1]["content"])
+
+print(f"Profile response: {profile}")
+
+def get_memory(r):
+ if hasattr(r, 'memory'):
+ return r.memory
+ return r.get('memory', '') if isinstance(r, dict) else str(r)
+
+context = f"""Static profile:
+{chr(10).join(profile.profile.static)}
+
+Dynamic profile:
+{chr(10).join(profile.profile.dynamic)}
+
+Relevant memories:
+{chr(10).join(get_memory(r) for r in profile.search_results.results)}"""
+
+print(f"\n2. Built context: {context}")
+
+# Build messages with memory-enriched context
+messages = [{"role": "system", "content": f"User context:\n{context}"}, *conversation]
+print("\n3. Messages built successfully")
+
+# Store conversation for future context
+print("\n4. Adding memory...")
+add_result = client.add(
+ content="\n".join(f"{m['role']}: {m['content']}" for m in conversation),
+ container_tag=USER_ID,
+)
+
+print(f"Add result: {add_result}")
+print("\n✅ Quickstart Python test passed!")
diff --git a/packages/docs-test/tests/python/sdk.py b/packages/docs-test/tests/python/sdk.py
new file mode 100644
index 00000000..a083ede2
--- /dev/null
+++ b/packages/docs-test/tests/python/sdk.py
@@ -0,0 +1,152 @@
+import os
+import time
+from pathlib import Path
+from dotenv import load_dotenv
+import httpx
+import supermemory
+from supermemory import Supermemory
+
+load_dotenv()
+
+client = Supermemory()
+
+
+def test_documents_crud():
+ """Test document CRUD operations"""
+ print("\n=== Document CRUD Operations ===")
+
+ # Create
+ doc = client.documents.add(content=f"Test content - {time.time()}")
+ print(f"✓ documents.add: {doc.id}")
+
+ # Read
+ fetched = client.documents.get(doc.id)
+ print(f"✓ documents.get: {fetched.id}")
+
+ # Update
+ updated = client.documents.update(doc.id, content=f"Updated - {time.time()}")
+ print(f"✓ documents.update: {updated.id}")
+
+ # Wait for processing
+ time.sleep(10)
+
+ # Delete
+ client.documents.delete(doc.id)
+ print("✓ documents.delete")
+
+
+def test_batch_operations():
+ """Test batch operations"""
+ print("\n=== Batch Operations ===")
+
+ batch = client.documents.batch_add(
+ documents=[
+ {"content": f"Batch 1 - {time.time()}"},
+ {"content": f"Batch 2 - {time.time()}"},
+ ]
+ )
+ print(f"✓ documents.batch_add: {batch}")
+
+
+def test_search():
+ """Test search"""
+ print("\n=== Search ===")
+
+ results = client.search.execute(q="test content")
+ print(f"✓ search.execute: {len(results.results) if results.results else 0} results")
+
+
+def test_file_uploads():
+ """Test file uploads"""
+ print("\n=== File Uploads ===")
+
+ test_path = Path("/tmp/test-py-upload.txt")
+ test_path.write_text(f"Test content {time.time()}")
+ client.documents.upload_file(file=test_path)
+ print("✓ documents.upload_file with Path")
+ test_path.unlink()
+
+
+def test_error_handling():
+ """Test error handling patterns"""
+ print("\n=== Error Handling ===")
+
+ try:
+ client.documents.add(content="Test content")
+ print("✓ Error handling pattern works")
+ except supermemory.APIConnectionError:
+ print("APIConnectionError handled")
+ except supermemory.RateLimitError:
+ print("RateLimitError handled")
+ except supermemory.APIStatusError:
+ print("APIStatusError handled")
+
+
+def test_client_config():
+ """Test client configuration"""
+ print("\n=== Client Configuration ===")
+
+ # Retries
+ client2 = Supermemory(max_retries=0)
+ print("✓ max_retries config")
+
+ # Timeout
+ client3 = Supermemory(timeout=20.0)
+ print("✓ timeout config")
+
+ # Granular timeout
+ client4 = Supermemory(timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0))
+ print("✓ granular timeout config")
+
+ # Per-request options
+ client.with_options(max_retries=5).documents.add(content="Test content")
+ print("✓ per-request options")
+
+
+def test_raw_response():
+ """Test raw response access"""
+ print("\n=== Raw Response ===")
+
+ response = client.documents.with_raw_response.add(content="Test content")
+ print(f"✓ with_raw_response: has headers={hasattr(response, 'headers')}")
+
+ memory = response.parse()
+ print(f"✓ parse(): {memory.id}")
+
+
+def test_streaming_response():
+ """Test streaming response"""
+ print("\n=== Streaming Response ===")
+
+ with client.documents.with_streaming_response.add(content="Test content") as response:
+ print(f"✓ with_streaming_response: has headers={hasattr(response, 'headers')}")
+
+
+def test_context_manager():
+ """Test context manager"""
+ print("\n=== Context Manager ===")
+
+ with Supermemory() as temp_client:
+ print("✓ Context manager works")
+
+
+def main():
+ print("Python SDK Tests")
+ print("================")
+
+ test_documents_crud()
+ test_batch_operations()
+ test_search()
+ test_file_uploads()
+ test_error_handling()
+ test_client_config()
+ test_raw_response()
+ test_streaming_response()
+ test_context_manager()
+
+ print("\n================")
+ print("✅ All Python SDK tests passed!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/packages/docs-test/tests/python/search.py b/packages/docs-test/tests/python/search.py
new file mode 100644
index 00000000..820629eb
--- /dev/null
+++ b/packages/docs-test/tests/python/search.py
@@ -0,0 +1,113 @@
+import os
+from dotenv import load_dotenv
+from supermemory import Supermemory
+
+load_dotenv()
+
+client = Supermemory()
+
+
+def test_search_modes():
+ """Test different search modes"""
+ print("=== Search Modes ===")
+
+ # Hybrid search
+ hybrid = client.search.memories(
+ q="quarterly goals",
+ container_tag="user_123",
+ search_mode="hybrid",
+ )
+ print(f"✓ hybrid search: {len(hybrid.results)} results")
+
+ # Memories only
+ memories = client.search.memories(
+ q="user preferences",
+ container_tag="user_123",
+ search_mode="memories",
+ )
+ print(f"✓ memories search: {len(memories.results)} results")
+
+
+def test_filtering():
+ """Test search filtering"""
+ print("\n=== Filtering ===")
+
+ # Basic containerTag filter
+ results = client.search.memories(
+ q="project updates",
+ container_tag="user_123",
+ search_mode="hybrid",
+ )
+ print(f"✓ containerTag filter: {len(results.results)} results")
+
+ # Metadata filtering
+ filtered = client.search.memories(
+ q="meeting notes",
+ container_tag="user_123",
+ filters={
+ "AND": [
+ {"key": "type", "value": "meeting"},
+ {"key": "year", "value": "2024"},
+ ]
+ },
+ )
+ print(f"✓ metadata filter: {len(filtered.results)} results")
+
+
+def test_reranking():
+ """Test reranking"""
+ print("\n=== Reranking ===")
+
+ results = client.search.memories(
+ q="complex technical question",
+ container_tag="user_123",
+ rerank=True,
+ )
+ print(f"✓ reranking: {len(results.results)} results")
+
+
+def test_threshold():
+ """Test similarity threshold"""
+ print("\n=== Threshold ===")
+
+ broad = client.search.memories(q="test query", threshold=0.3)
+ print(f"✓ broad threshold (0.3): {len(broad.results)} results")
+
+ precise = client.search.memories(q="test query", threshold=0.8)
+ print(f"✓ precise threshold (0.8): {len(precise.results)} results")
+
+
+def test_chatbot_context():
+ """Test chatbot context pattern"""
+ print("\n=== Chatbot Context Pattern ===")
+
+ def get_context(user_id: str, message: str) -> str:
+ results = client.search.memories(
+ q=message,
+ container_tag=user_id,
+ search_mode="hybrid",
+ threshold=0.6,
+ limit=5,
+ )
+ return "\n\n".join(r.memory or r.chunk or "" for r in results.results)
+
+ context = get_context("user_123", "What are the project updates?")
+ print(f"✓ chatbot context: {len(context)} chars")
+
+
+def main():
+ print("Search Tests")
+ print("============\n")
+
+ test_search_modes()
+ test_filtering()
+ test_reranking()
+ test_threshold()
+ test_chatbot_context()
+
+ print("\n============")
+ print("✅ All search tests passed!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/packages/docs-test/tests/python/user_profiles.py b/packages/docs-test/tests/python/user_profiles.py
new file mode 100644
index 00000000..f34c1499
--- /dev/null
+++ b/packages/docs-test/tests/python/user_profiles.py
@@ -0,0 +1,102 @@
+import os
+import time
+from dotenv import load_dotenv
+from supermemory import Supermemory
+
+load_dotenv()
+
+client = Supermemory()
+USER_ID = "docs-test-profiles-py"
+
+
+def test_basic_profile():
+ """Test basic profile retrieval"""
+ print("=== Basic Profile ===")
+
+ profile = client.profile(
+ container_tag=USER_ID,
+ q="What are my preferences?",
+ )
+
+ print(f"✓ Static profile: {len(profile.profile.static)} items")
+ print(f"✓ Dynamic profile: {len(profile.profile.dynamic)} items")
+ print(f"✓ Search results: {len(profile.search_results.results)} items")
+
+
+def test_profile_with_memories():
+ """Test profile with memory context"""
+ print("\n=== Profile with Memory Context ===")
+
+ # Add some memories
+ client.add(
+ content="User prefers dark mode for all applications",
+ container_tag=USER_ID,
+ )
+ client.add(
+ content="User is learning TypeScript and Rust",
+ container_tag=USER_ID,
+ )
+
+ # Wait for indexing
+ time.sleep(2)
+
+ # Get profile with search
+ profile = client.profile(
+ container_tag=USER_ID,
+ q="What programming languages does the user know?",
+ )
+
+ print("✓ Profile retrieved with memories")
+ print(f" Static: {profile.profile.static[:2]}")
+ print(f" Dynamic: {profile.profile.dynamic[:2]}")
+
+
+def get_memory(r):
+ if hasattr(r, 'memory'):
+ return r.memory
+ return r.get('memory', '') if isinstance(r, dict) else str(r)
+
+
+def test_building_context():
+ """Test building LLM context"""
+ print("\n=== Building LLM Context ===")
+
+ conversation = [{"role": "user", "content": "What theme should I use for my IDE?"}]
+
+ profile = client.profile(
+ container_tag=USER_ID,
+ q=conversation[-1]["content"],
+ )
+
+ context = f"""User Profile:
+{chr(10).join(profile.profile.static)}
+
+Recent Context:
+{chr(10).join(profile.profile.dynamic)}
+
+Relevant Memories:
+{chr(10).join(get_memory(r) for r in profile.search_results.results)}"""
+
+ print(f"✓ Built context: {len(context)} chars")
+
+ messages = [
+ {"role": "system", "content": f"Use this context about the user:\n{context}"},
+ *conversation,
+ ]
+ print(f"✓ Messages ready for LLM: {len(messages)} messages")
+
+
+def main():
+ print("User Profiles Tests")
+ print("===================\n")
+
+ test_basic_profile()
+ test_profile_with_memories()
+ test_building_context()
+
+ print("\n===================")
+ print("✅ All user profile tests passed!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/packages/docs-test/tests/typescript/quickstart.ts b/packages/docs-test/tests/typescript/quickstart.ts
new file mode 100644
index 00000000..12da131b
--- /dev/null
+++ b/packages/docs-test/tests/typescript/quickstart.ts
@@ -0,0 +1,57 @@
+import "dotenv/config"
+import Supermemory from "supermemory"
+
+const client = new Supermemory()
+const USER_ID = "docs-test-user"
+
+const conversation = [
+ { role: "assistant", content: "Hello, how are you doing?" },
+ {
+ role: "user",
+ content: "Hello! I am Dhravya. I am 20 years old. I love to code!",
+ },
+ { role: "user", content: "Can I go to the club?" },
+]
+
+async function main() {
+ console.log("Testing quickstart TypeScript code...\n")
+
+ // Get user profile + relevant memories for context
+ console.log("1. Getting user profile...")
+ const profile = await client.profile({
+ containerTag: USER_ID,
+ q: conversation.at(-1)!.content,
+ })
+
+ console.log("Profile response:", JSON.stringify(profile, null, 2))
+
+ const context = `Static profile:
+${profile.profile.static.join("\n")}
+
+Dynamic profile:
+${profile.profile.dynamic.join("\n")}
+
+Relevant memories:
+${profile.searchResults?.results.map((r) => r.content).join("\n")}`
+
+ console.log("\n2. Built context:", context)
+
+ // Build messages with memory-enriched context
+ const messages = [
+ { role: "system", content: `User context:\n${context}` },
+ ...conversation,
+ ]
+ console.log("\n3. Messages built successfully")
+
+ // Store conversation for future context
+ console.log("\n4. Adding memory...")
+ const addResult = await client.add({
+ content: conversation.map((m) => `${m.role}: ${m.content}`).join("\n"),
+ containerTag: USER_ID,
+ })
+
+ console.log("Add result:", JSON.stringify(addResult, null, 2))
+ console.log("\n✅ Quickstart TypeScript test passed!")
+}
+
+main().catch(console.error)
diff --git a/packages/docs-test/tests/typescript/sdk.ts b/packages/docs-test/tests/typescript/sdk.ts
new file mode 100644
index 00000000..7dc477e0
--- /dev/null
+++ b/packages/docs-test/tests/typescript/sdk.ts
@@ -0,0 +1,146 @@
+import "dotenv/config"
+import fs from "fs"
+import Supermemory, { toFile } from "supermemory"
+
+const client = new Supermemory()
+
+async function testDocumentsCRUD() {
+ console.log("\n=== Document CRUD Operations ===")
+
+ // Create
+ const doc = await client.documents.add({
+ content: "Test content for TypeScript SDK - " + Date.now(),
+ })
+ console.log("✓ documents.add:", doc.id)
+
+ // Read
+ const fetched = await client.documents.get(doc.id)
+ console.log("✓ documents.get:", fetched.id)
+
+ // Update
+ const updated = await client.documents.update(doc.id, {
+ content: "Updated content - " + Date.now(),
+ })
+ console.log("✓ documents.update:", updated.id)
+
+ // Wait for processing before delete
+ await new Promise((r) => setTimeout(r, 10000))
+
+ // Delete
+ await client.documents.delete(doc.id)
+ console.log("✓ documents.delete")
+}
+
+async function testBatchOperations() {
+ console.log("\n=== Batch Operations ===")
+
+ const batch = await client.documents.batchAdd({
+ documents: [
+ { content: "Batch doc 1 - " + Date.now() },
+ { content: "Batch doc 2 - " + Date.now() },
+ ],
+ })
+ console.log("✓ documents.batchAdd:", batch)
+}
+
+async function testSearch() {
+ console.log("\n=== Search ===")
+
+ const results = await client.search.execute({ q: "test content" })
+ console.log("✓ search.execute:", results.results?.length ?? 0, "results")
+}
+
+async function testFileUploads() {
+ console.log("\n=== File Uploads ===")
+
+ // Using fs.createReadStream
+ const testFile = "/tmp/test-doc-upload.txt"
+ fs.writeFileSync(testFile, "Test file content")
+ await client.documents.uploadFile({ file: fs.createReadStream(testFile) })
+ console.log("✓ uploadFile with fs.createReadStream")
+
+ // Using File API
+ await client.documents.uploadFile({ file: new File(["my bytes"], "file") })
+ console.log("✓ uploadFile with File API")
+
+ // Using toFile
+ await client.documents.uploadFile({ file: await toFile(Buffer.from("my bytes"), "file") })
+ console.log("✓ uploadFile with toFile")
+
+ fs.unlinkSync(testFile)
+}
+
+async function testClientConfig() {
+ console.log("\n=== Client Configuration ===")
+
+ // Retries
+ const clientWithRetries = new Supermemory({ maxRetries: 0 })
+ console.log("✓ maxRetries config")
+
+ // Timeout
+ const clientWithTimeout = new Supermemory({ timeout: 20 * 1000 })
+ console.log("✓ timeout config")
+
+ // Per-request options
+ await client.add({ content: "Test - " + Date.now() }, { maxRetries: 5, timeout: 5000 })
+ console.log("✓ per-request options")
+}
+
+async function testRawResponse() {
+ console.log("\n=== Raw Response ===")
+
+ const response = await client.documents
+ .add({ content: "Test - " + Date.now() })
+ .asResponse()
+ console.log("✓ .asResponse() status:", response.status)
+
+ const { data, response: raw } = await client.documents
+ .add({ content: "Test - " + Date.now() })
+ .withResponse()
+ console.log("✓ .withResponse() data:", data.id, "status:", raw.status)
+}
+
+async function testTypes() {
+ console.log("\n=== Types ===")
+
+ const params: Supermemory.AddParams = {
+ content: "Typed content",
+ }
+ const response: Supermemory.AddResponse = await client.add(params)
+ console.log("✓ TypeScript types work:", response.id)
+}
+
+async function testErrorHandling() {
+ console.log("\n=== Error Handling ===")
+
+ const response = await client.documents
+ .add({ content: "Test - " + Date.now() })
+ .catch(async (err) => {
+ if (err instanceof Supermemory.APIError) {
+ console.log("Caught APIError:", err.status, err.name)
+ } else {
+ throw err
+ }
+ return null
+ })
+ console.log("✓ Error handling pattern works:", response?.id)
+}
+
+async function main() {
+ console.log("TypeScript SDK Tests")
+ console.log("====================")
+
+ await testDocumentsCRUD()
+ await testBatchOperations()
+ await testSearch()
+ await testFileUploads()
+ await testClientConfig()
+ await testRawResponse()
+ await testTypes()
+ await testErrorHandling()
+
+ console.log("\n====================")
+ console.log("✅ All TypeScript SDK tests passed!")
+}
+
+main().catch(console.error)
diff --git a/packages/docs-test/tests/typescript/search.ts b/packages/docs-test/tests/typescript/search.ts
new file mode 100644
index 00000000..45f903eb
--- /dev/null
+++ b/packages/docs-test/tests/typescript/search.ts
@@ -0,0 +1,110 @@
+import "dotenv/config"
+import Supermemory from "supermemory"
+
+const client = new Supermemory()
+
+async function testSearchModes() {
+ console.log("=== Search Modes ===")
+
+ // Hybrid: memories + document chunks (recommended)
+ const hybridResults = await client.search.memories({
+ q: "quarterly goals",
+ containerTag: "user_123",
+ searchMode: "hybrid",
+ })
+ console.log("✓ hybrid search:", hybridResults.results.length, "results")
+
+ // Memories only
+ const memoriesResults = await client.search.memories({
+ q: "user preferences",
+ containerTag: "user_123",
+ searchMode: "memories",
+ })
+ console.log("✓ memories search:", memoriesResults.results.length, "results")
+}
+
+async function testFiltering() {
+ console.log("\n=== Filtering ===")
+
+ // Basic containerTag filter
+ const results = await client.search.memories({
+ q: "project updates",
+ containerTag: "user_123",
+ searchMode: "hybrid",
+ })
+ console.log("✓ containerTag filter:", results.results.length, "results")
+
+ // Metadata-based filtering
+ const filteredResults = await client.search.memories({
+ q: "meeting notes",
+ containerTag: "user_123",
+ filters: {
+ AND: [
+ { key: "type", value: "meeting" },
+ { key: "year", value: "2024" },
+ ],
+ },
+ })
+ console.log("✓ metadata filter:", filteredResults.results.length, "results")
+}
+
+async function testReranking() {
+ console.log("\n=== Reranking ===")
+
+ const results = await client.search.memories({
+ q: "complex technical question",
+ containerTag: "user_123",
+ rerank: true,
+ })
+ console.log("✓ reranking:", results.results.length, "results")
+}
+
+async function testThreshold() {
+ console.log("\n=== Threshold ===")
+
+ const broadResults = await client.search.memories({
+ q: "test query",
+ threshold: 0.3,
+ })
+ console.log("✓ broad threshold (0.3):", broadResults.results.length, "results")
+
+ const preciseResults = await client.search.memories({
+ q: "test query",
+ threshold: 0.8,
+ })
+ console.log("✓ precise threshold (0.8):", preciseResults.results.length, "results")
+}
+
+async function testChatbotContext() {
+ console.log("\n=== Chatbot Context Pattern ===")
+
+ async function getContext(userId: string, message: string) {
+ const results = await client.search.memories({
+ q: message,
+ containerTag: userId,
+ searchMode: "hybrid",
+ threshold: 0.6,
+ limit: 5,
+ })
+ return results.results.map((r) => r.memory || r.chunk).join("\n\n")
+ }
+
+ const context = await getContext("user_123", "What are the project updates?")
+ console.log("✓ chatbot context:", context.length, "chars")
+}
+
+async function main() {
+ console.log("Search Tests")
+ console.log("============\n")
+
+ await testSearchModes()
+ await testFiltering()
+ await testReranking()
+ await testThreshold()
+ await testChatbotContext()
+
+ console.log("\n============")
+ console.log("✅ All search tests passed!")
+}
+
+main().catch(console.error)
diff --git a/packages/docs-test/tests/typescript/user-profiles.ts b/packages/docs-test/tests/typescript/user-profiles.ts
new file mode 100644
index 00000000..4643af4f
--- /dev/null
+++ b/packages/docs-test/tests/typescript/user-profiles.ts
@@ -0,0 +1,89 @@
+import "dotenv/config"
+import Supermemory from "supermemory"
+
+const client = new Supermemory()
+const USER_ID = "docs-test-profiles"
+
+async function testBasicProfile() {
+ console.log("=== Basic Profile ===")
+
+ const profile = await client.profile({
+ containerTag: USER_ID,
+ q: "What are my preferences?",
+ })
+
+ console.log("✓ Static profile:", profile.profile.static.length, "items")
+ console.log("✓ Dynamic profile:", profile.profile.dynamic.length, "items")
+ console.log("✓ Search results:", profile.searchResults.results.length, "items")
+}
+
+async function testProfileWithMemories() {
+ console.log("\n=== Profile with Memory Context ===")
+
+ // First add some memories
+ await client.add({
+ content: "User prefers dark mode for all applications",
+ containerTag: USER_ID,
+ })
+ await client.add({
+ content: "User is learning TypeScript and Rust",
+ containerTag: USER_ID,
+ })
+
+ // Wait a bit for indexing
+ await new Promise((r) => setTimeout(r, 2000))
+
+ // Now get profile with search
+ const profile = await client.profile({
+ containerTag: USER_ID,
+ q: "What programming languages does the user know?",
+ })
+
+ console.log("✓ Profile retrieved with memories")
+ console.log(" Static:", profile.profile.static.slice(0, 2))
+ console.log(" Dynamic:", profile.profile.dynamic.slice(0, 2))
+}
+
+async function testBuildingContext() {
+ console.log("\n=== Building LLM Context ===")
+
+ const conversation = [
+ { role: "user", content: "What theme should I use for my IDE?" },
+ ]
+
+ const profile = await client.profile({
+ containerTag: USER_ID,
+ q: conversation.at(-1)!.content,
+ })
+
+ const context = `User Profile:
+${profile.profile.static.join("\n")}
+
+Recent Context:
+${profile.profile.dynamic.join("\n")}
+
+Relevant Memories:
+${profile.searchResults.results.map((r) => r.content).join("\n")}`
+
+ console.log("✓ Built context:", context.length, "chars")
+
+ const messages = [
+ { role: "system", content: `Use this context about the user:\n${context}` },
+ ...conversation,
+ ]
+ console.log("✓ Messages ready for LLM:", messages.length, "messages")
+}
+
+async function main() {
+ console.log("User Profiles Tests")
+ console.log("===================\n")
+
+ await testBasicProfile()
+ await testProfileWithMemories()
+ await testBuildingContext()
+
+ console.log("\n===================")
+ console.log("✅ All user profile tests passed!")
+}
+
+main().catch(console.error)
diff --git a/packages/openai-sdk-python/src/supermemory_openai/middleware.py b/packages/openai-sdk-python/src/supermemory_openai/middleware.py
index e2399bb6..4f6dc8ec 100644
--- a/packages/openai-sdk-python/src/supermemory_openai/middleware.py
+++ b/packages/openai-sdk-python/src/supermemory_openai/middleware.py
@@ -1,31 +1,31 @@
"""Supermemory middleware for OpenAI clients."""
-from dataclasses import dataclass
-from typing import Optional, Union, Any, Literal, cast
import asyncio
import os
+from dataclasses import dataclass
+from typing import Any, Literal, Optional, Union, cast
-from openai import OpenAI, AsyncOpenAI
+import supermemory
+from openai import AsyncOpenAI, OpenAI
from openai.types.chat import (
ChatCompletionMessageParam,
ChatCompletionSystemMessageParam,
)
-import supermemory
-from .utils import (
- Logger,
- create_logger,
- get_last_user_message,
- get_conversation_content,
- convert_profile_to_markdown,
- deduplicate_memories,
-)
from .exceptions import (
- SupermemoryConfigurationError,
SupermemoryAPIError,
+ SupermemoryConfigurationError,
SupermemoryMemoryOperationError,
SupermemoryNetworkError,
)
+from .utils import (
+ Logger,
+ convert_profile_to_markdown,
+ create_logger,
+ deduplicate_memories,
+ get_conversation_content,
+ get_last_user_message,
+)
@dataclass
@@ -75,7 +75,7 @@ async def supermemory_profile_search(
raise SupermemoryAPIError(
"Supermemory profile search failed",
status_code=response.status,
- response_text=error_text
+ response_text=error_text,
)
data = await response.json()
@@ -98,7 +98,7 @@ async def supermemory_profile_search(
raise SupermemoryAPIError(
"Supermemory profile search failed",
status_code=response.status_code,
- response_text=response.text
+ response_text=response.text,
)
return SupermemoryProfileSearch(response.json())
@@ -146,9 +146,18 @@ async def add_system_prompt(
logger.debug(
"Memory deduplication completed",
{
- "static": {"original": memory_count_static, "deduplicated": len(deduplicated.static)},
- "dynamic": {"original": memory_count_dynamic, "deduplicated": len(deduplicated.dynamic)},
- "search_results": {"original": memory_count_search, "deduplicated": len(deduplicated.search_results)},
+ "static": {
+ "original": memory_count_static,
+ "deduplicated": len(deduplicated.static),
+ },
+ "dynamic": {
+ "original": memory_count_dynamic,
+ "deduplicated": len(deduplicated.dynamic),
+ },
+ "search_results": {
+ "original": memory_count_search,
+ "deduplicated": len(deduplicated.search_results),
+ },
},
)
@@ -217,10 +226,10 @@ async def add_memory_tool(
# Handle both sync and async supermemory clients
try:
- response = await client.memories.add(**add_params)
+ response = await client.add(**add_params)
except TypeError:
# If it's not awaitable, call it synchronously
- response = client.memories.add(**add_params)
+ response = client.add(**add_params)
logger.info(
"Memory saved successfully",
@@ -237,18 +246,14 @@ async def add_memory_tool(
{"error": str(network_error)},
)
raise SupermemoryNetworkError(
- "Failed to save memory due to network error",
- network_error
+ "Failed to save memory due to network error", network_error
)
except Exception as error:
logger.error(
"Error saving memory",
{"error": str(error)},
)
- raise SupermemoryMemoryOperationError(
- "Failed to save memory",
- error
- )
+ raise SupermemoryMemoryOperationError("Failed to save memory", error)
class SupermemoryOpenAIWrapper:
@@ -271,16 +276,17 @@ class SupermemoryOpenAIWrapper:
if not hasattr(supermemory, "Supermemory"):
raise SupermemoryConfigurationError(
"supermemory package is required but not found",
- ImportError("supermemory package not installed")
+ ImportError("supermemory package not installed"),
)
api_key = self._get_api_key()
try:
- self._supermemory_client: supermemory.Supermemory = supermemory.Supermemory(api_key=api_key)
+ self._supermemory_client: supermemory.Supermemory = supermemory.Supermemory(
+ api_key=api_key
+ )
except Exception as e:
raise SupermemoryConfigurationError(
- f"Failed to initialize Supermemory client: {e}",
- e
+ f"Failed to initialize Supermemory client: {e}", e
)
# Wrap the chat completions create method
@@ -359,15 +365,24 @@ class SupermemoryOpenAIWrapper:
try:
if task_obj.exception() is not None:
exception = task_obj.exception()
- if isinstance(exception, (SupermemoryNetworkError, SupermemoryAPIError)):
+ if isinstance(
+ exception,
+ (SupermemoryNetworkError, SupermemoryAPIError),
+ ):
self._logger.warn(
"Background memory storage failed",
- {"error": str(exception), "type": type(exception).__name__}
+ {
+ "error": str(exception),
+ "type": type(exception).__name__,
+ },
)
else:
self._logger.error(
"Unexpected error in background memory storage",
- {"error": str(exception), "type": type(exception).__name__}
+ {
+ "error": str(exception),
+ "type": type(exception).__name__,
+ },
)
except asyncio.CancelledError:
self._logger.debug("Memory storage task was cancelled")
@@ -440,7 +455,7 @@ class SupermemoryOpenAIWrapper:
# We're in an async context, log warning and skip memory saving
self._logger.warn(
"Cannot save memory in sync client from async context",
- {"error": str(e)}
+ {"error": str(e)},
)
else:
raise
@@ -454,7 +469,7 @@ class SupermemoryOpenAIWrapper:
# Unexpected errors should be investigated
self._logger.error(
"Unexpected error saving memory",
- {"error": str(e), "type": type(e).__name__}
+ {"error": str(e), "type": type(e).__name__},
)
# Handle memory search and injection
@@ -464,11 +479,14 @@ class SupermemoryOpenAIWrapper:
self._logger.debug("No user message found, skipping memory search")
return original_create(**kwargs)
- self._logger.info("Starting memory search", {
- "container_tag": self._container_tag,
- "conversation_id": self._options.conversation_id,
- "mode": self._options.mode,
- })
+ self._logger.info(
+ "Starting memory search",
+ {
+ "container_tag": self._container_tag,
+ "conversation_id": self._options.conversation_id,
+ "mode": self._options.mode,
+ },
+ )
# Use asyncio.run() for memory search and injection
try:
@@ -495,7 +513,7 @@ class SupermemoryOpenAIWrapper:
self._logger,
self._options.mode,
self._get_api_key(),
- )
+ ),
)
enhanced_messages = future.result()
else:
@@ -517,20 +535,24 @@ class SupermemoryOpenAIWrapper:
if not self._background_tasks:
return
- self._logger.debug(f"Waiting for {len(self._background_tasks)} background tasks to complete")
+ self._logger.debug(
+ f"Waiting for {len(self._background_tasks)} background tasks to complete"
+ )
try:
if timeout is not None:
await asyncio.wait_for(
asyncio.gather(*self._background_tasks, return_exceptions=True),
- timeout=timeout
+ timeout=timeout,
)
else:
await asyncio.gather(*self._background_tasks, return_exceptions=True)
self._logger.debug("All background tasks completed")
except asyncio.TimeoutError:
- self._logger.warn(f"Background tasks did not complete within {timeout}s timeout")
+ self._logger.warn(
+ f"Background tasks did not complete within {timeout}s timeout"
+ )
# Cancel remaining tasks
for task in self._background_tasks:
if not task.done():
@@ -580,7 +602,9 @@ class SupermemoryOpenAIWrapper:
else:
raise
except asyncio.TimeoutError:
- self._logger.warn("Some background memory tasks did not complete on exit")
+ self._logger.warn(
+ "Some background memory tasks did not complete on exit"
+ )
self.cancel_background_tasks()
def __getattr__(self, name: str) -> Any:
diff --git a/packages/openai-sdk-python/src/supermemory_openai/tools.py b/packages/openai-sdk-python/src/supermemory_openai/tools.py
index 111253cd..87c19428 100644
--- a/packages/openai-sdk-python/src/supermemory_openai/tools.py
+++ b/packages/openai-sdk-python/src/supermemory_openai/tools.py
@@ -1,14 +1,14 @@
"""Supermemory tools for OpenAI function calling."""
import json
-from typing import Dict, List, Optional, Union, TypedDict
+from typing import Dict, List, Optional, TypedDict, Union
+import supermemory
from openai.types.chat import (
+ ChatCompletionFunctionToolParam,
ChatCompletionMessageToolCall,
ChatCompletionToolMessageParam,
- ChatCompletionFunctionToolParam,
)
-import supermemory
from supermemory.types import (
MemoryAddResponse,
MemoryGetResponse,
@@ -230,7 +230,7 @@ class SupermemoryTools:
if metadata:
add_params["metadata"] = metadata
- response: MemoryAddResponse = await self.client.memories.add(**add_params)
+ response: MemoryAddResponse = await self.client.add(**add_params)
return MemoryAddResult(
success=True,
diff --git a/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py b/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py
index ab07e672..01bc03df 100644
--- a/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py
+++ b/packages/pipecat-sdk-python/src/supermemory_pipecat/service.py
@@ -11,12 +11,11 @@ import os
from typing import Any, Dict, List, Literal, Optional
from loguru import logger
-from pydantic import BaseModel, Field
-
from pipecat.frames.frames import Frame, LLMContextFrame, LLMMessagesFrame
from pipecat.processors.aggregators.llm_context import LLMContext
from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContextFrame
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
+from pydantic import BaseModel, Field
from .exceptions import (
ConfigurationError,
@@ -73,7 +72,9 @@ class SupermemoryPipecatService(FrameProcessor):
search_limit: int = Field(default=10, ge=1)
search_threshold: float = Field(default=0.1, ge=0.0, le=1.0)
- system_prompt: str = Field(default="Based on previous conversations, I recall:\n\n")
+ system_prompt: str = Field(
+ default="Based on previous conversations, I recall:\n\n"
+ )
mode: Literal["profile", "query", "full"] = Field(default="full")
def __init__(
@@ -202,7 +203,9 @@ class SupermemoryPipecatService(FrameProcessor):
messages: List of message dicts with 'role' and 'content' keys.
"""
if self._supermemory_client is None:
- logger.warning("Supermemory client not initialized, skipping memory storage")
+ logger.warning(
+ "Supermemory client not initialized, skipping memory storage"
+ )
return
if not messages:
@@ -223,7 +226,7 @@ class SupermemoryPipecatService(FrameProcessor):
if self.session_id:
add_params["custom_id"] = f"{self.session_id}"
- await self._supermemory_client.memories.add(**add_params)
+ await self._supermemory_client.add(**add_params)
logger.debug(f"Successfully stored {len(messages)} messages in Supermemory")
except Exception as e:
@@ -317,16 +320,22 @@ class SupermemoryPipecatService(FrameProcessor):
if latest_user_message:
# Retrieve memories from Supermemory
try:
- memories_data = await self._retrieve_memories(latest_user_message)
+ memories_data = await self._retrieve_memories(
+ latest_user_message
+ )
self._enhance_context_with_memories(
context, latest_user_message, memories_data
)
except MemoryRetrievalError as e:
- logger.warning(f"Memory retrieval failed, continuing without memories: {e}")
+ logger.warning(
+ f"Memory retrieval failed, continuing without memories: {e}"
+ )
# Store unsent messages (user and assistant only, skip system)
storable_messages = [
- msg for msg in context_messages if msg["role"] in ("user", "assistant")
+ msg
+ for msg in context_messages
+ if msg["role"] in ("user", "assistant")
]
unsent_messages = storable_messages[self._messages_sent_count :]
diff --git a/packages/tools/src/ai-sdk.ts b/packages/tools/src/ai-sdk.ts
index 8b893cd0..0f2d5fe0 100644
--- a/packages/tools/src/ai-sdk.ts
+++ b/packages/tools/src/ai-sdk.ts
@@ -87,7 +87,7 @@ export const addMemoryTool = (
try {
const metadata: Record<string, string | number | boolean> = {}
- const response = await client.memories.add({
+ const response = await client.add({
content: memory,
containerTags,
...(Object.keys(metadata).length > 0 && { metadata }),
diff --git a/packages/tools/src/claude-memory.ts b/packages/tools/src/claude-memory.ts
index 54da77a1..2ef45f21 100644
--- a/packages/tools/src/claude-memory.ts
+++ b/packages/tools/src/claude-memory.ts
@@ -47,17 +47,23 @@ export class ClaudeMemoryTool {
private memoryContainerPrefix: string
/**
- * Normalize file path to be used as customId (replace / with --)
+ * Normalize file path to be used as customId
+ * Converts /memories/file.txt -> memories_file_txt
*/
private normalizePathToCustomId(path: string): string {
- return path.replace(/\//g, "--")
+ return path
+ .replace(/^\//, "") // Remove leading slash
+ .replace(/\//g, "_") // Replace / with _
+ .replace(/\./g, "_") // Replace . with _
}
/**
- * Convert customId back to file path (replace -- with /)
+ * Convert customId back to file path
+ * Note: This is lossy since we can't distinguish _ from . or /
+ * We rely on metadata.file_path for accurate path reconstruction
*/
private customIdToPath(customId: string): string {
- return customId.replace(/--/g, "/")
+ return "/" + customId.replace(/_/g, "/")
}
constructor(apiKey: string, config?: ClaudeMemoryConfig) {
@@ -329,7 +335,7 @@ export class ClaudeMemoryTool {
try {
const normalizedId = this.normalizePathToCustomId(filePath)
- const response = await this.client.memories.add({
+ const response = await this.client.add({
content: fileText,
customId: normalizedId,
containerTags: this.containerTags,
@@ -388,7 +394,7 @@ export class ClaudeMemoryTool {
// Update the document
const normalizedId = this.normalizePathToCustomId(filePath)
- const updateResponse = await this.client.memories.add({
+ const updateResponse = await this.client.add({
content: newContent,
customId: normalizedId,
containerTags: this.containerTags,
@@ -447,7 +453,7 @@ export class ClaudeMemoryTool {
// Update the document
const normalizedId = this.normalizePathToCustomId(filePath)
- await this.client.memories.add({
+ await this.client.add({
content: newContent,
customId: normalizedId,
containerTags: this.containerTags,
@@ -530,7 +536,7 @@ export class ClaudeMemoryTool {
const newNormalizedId = this.normalizePathToCustomId(newPath)
// Create new document with new path
- await this.client.memories.add({
+ await this.client.add({
content: originalContent,
customId: newNormalizedId,
containerTags: this.containerTags,
diff --git a/packages/tools/src/openai/middleware.ts b/packages/tools/src/openai/middleware.ts
index 4d030d0e..d390d841 100644
--- a/packages/tools/src/openai/middleware.ts
+++ b/packages/tools/src/openai/middleware.ts
@@ -363,7 +363,7 @@ const addMemoryTool = async (
}
// Fallback to old behavior for non-conversation memories
- const response = await client.memories.add({
+ const response = await client.add({
content,
containerTags: [containerTag],
customId,
diff --git a/packages/tools/src/openai/tools.ts b/packages/tools/src/openai/tools.ts
index 4078df09..c0a3e25d 100644
--- a/packages/tools/src/openai/tools.ts
+++ b/packages/tools/src/openai/tools.ts
@@ -141,7 +141,7 @@ export function createAddMemoryFunction(
try {
const metadata: Record<string, string | number | boolean> = {}
- const response = await client.memories.add({
+ const response = await client.add({
content: memory,
containerTags,
...(Object.keys(metadata).length > 0 && { metadata }),
diff --git a/packages/tools/src/vercel/middleware.ts b/packages/tools/src/vercel/middleware.ts
index 43306934..8336b397 100644
--- a/packages/tools/src/vercel/middleware.ts
+++ b/packages/tools/src/vercel/middleware.ts
@@ -137,7 +137,7 @@ export const saveMemoryAfterResponse = async (
? `${getConversationContent(params)} \n\n Assistant: ${assistantResponseText}`
: `User: ${userMessage} \n\n Assistant: ${assistantResponseText}`
- const response = await client.memories.add({
+ const response = await client.add({
content,
containerTags: [containerTag],
customId,