mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
feat: Claude memory integration
This commit is contained in:
parent
5bbb4edf1b
commit
53bc296155
13 changed files with 2647 additions and 4 deletions
|
|
@ -184,6 +184,11 @@
|
|||
"icon": "sparkles",
|
||||
"pages": ["memory-api/sdks/openai-plugins", "ai-sdk/npm"]
|
||||
},
|
||||
{
|
||||
"group": "Anthropic SDK",
|
||||
"icon": "sparkles",
|
||||
"pages": ["memory-api/sdks/anthropic-claude-memory"]
|
||||
},
|
||||
{
|
||||
"group": "AI SDK",
|
||||
"icon": "triangle",
|
||||
|
|
|
|||
375
apps/docs/memory-api/sdks/anthropic-claude-memory.mdx
Normal file
375
apps/docs/memory-api/sdks/anthropic-claude-memory.mdx
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
---
|
||||
title: "Claude Memory Tool"
|
||||
description: "Enable Claude's persistent memory capabilities with Supermemory as the backend"
|
||||
---
|
||||
|
||||
Enable Claude's native memory tool functionality with Supermemory as the persistent storage backend. Claude can automatically store and retrieve information across conversations using familiar filesystem operations.
|
||||
|
||||
<Card title="Supermemory tools on npm" icon="npm" href="https://www.npmjs.com/package/@supermemory/tools">
|
||||
Check out the NPM page for more details
|
||||
</Card>
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @supermemory/tools @anthropic-ai/sdk
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk"
|
||||
import { createClaudeMemoryTool } from "@supermemory/tools/claude-memory"
|
||||
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY!,
|
||||
})
|
||||
|
||||
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
|
||||
projectId: 'my-app',
|
||||
})
|
||||
|
||||
async function chatWithMemory(userMessage: string) {
|
||||
const response = await anthropic.beta.messages.create({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: 8096,
|
||||
tools: [{ type: 'memory_20250818', name: 'memory' }],
|
||||
betas: ['context-management-2025-06-27'],
|
||||
messages: [{ role: 'user', content: userMessage }],
|
||||
})
|
||||
|
||||
// Handle memory tool calls
|
||||
for (const block of response.content) {
|
||||
if (block.type === 'tool_use' && block.name === 'memory') {
|
||||
const result = await memoryTool.handleCommandForToolResult(
|
||||
block.input as MemoryCommand,
|
||||
block.id
|
||||
)
|
||||
console.log('Memory operation result:', result)
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
Claude's memory tool uses a filesystem metaphor to manage persistent information:
|
||||
|
||||
- **Files**: Individual memory items stored as Supermemory documents
|
||||
- **Directories**: Organized using path structure (e.g., `/memories/user-preferences`)
|
||||
- **Operations**: View, create, edit, delete, and rename files
|
||||
- **Path Normalization**: Paths like `/memories/preferences` are stored as `--memories--preferences`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
```typescript
|
||||
import { createClaudeMemoryTool } from "@supermemory/tools/claude-memory"
|
||||
|
||||
const memoryTool = createClaudeMemoryTool(
|
||||
process.env.SUPERMEMORY_API_KEY!,
|
||||
{
|
||||
projectId: 'my-app', // Project identifier
|
||||
baseUrl: 'https://api.supermemory.ai', // Optional: custom API endpoint
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Using Container Tags
|
||||
|
||||
```typescript
|
||||
const memoryTool = createClaudeMemoryTool(
|
||||
process.env.SUPERMEMORY_API_KEY!,
|
||||
{
|
||||
containerTags: ['user:alice', 'app:chat'],
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Memory Operations
|
||||
|
||||
Claude automatically performs these operations when managing memory:
|
||||
|
||||
### View Files
|
||||
|
||||
List directory contents or read file contents:
|
||||
|
||||
```typescript
|
||||
// Claude will automatically check /memories/ directory
|
||||
// This maps to searching Supermemory documents with path prefix
|
||||
```
|
||||
|
||||
### Create Files
|
||||
|
||||
Store new information:
|
||||
|
||||
```typescript
|
||||
// Claude creates: /memories/user-preferences.txt
|
||||
// Stored as document with customId: "--memories--user-preferences.txt"
|
||||
```
|
||||
|
||||
### Edit Files
|
||||
|
||||
Update existing memories using string replacement:
|
||||
|
||||
```typescript
|
||||
// Claude performs str_replace on file contents
|
||||
// Updates the corresponding Supermemory document
|
||||
```
|
||||
|
||||
### Delete Files
|
||||
|
||||
Remove information:
|
||||
|
||||
```typescript
|
||||
// Claude deletes: /memories/old-data.txt
|
||||
// Removes document from Supermemory
|
||||
```
|
||||
|
||||
### Rename Files
|
||||
|
||||
Reorganize memory structure:
|
||||
|
||||
```typescript
|
||||
// Claude renames: /memories/temp.txt -> /memories/final.txt
|
||||
// Updates document customId in Supermemory
|
||||
```
|
||||
|
||||
## Complete Chat Example
|
||||
|
||||
Here's a full example with conversation handling:
|
||||
|
||||
```typescript
|
||||
import Anthropic from "@anthropic-ai/sdk"
|
||||
import { createClaudeMemoryTool, type MemoryCommand } from "@supermemory/tools/claude-memory"
|
||||
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY!,
|
||||
})
|
||||
|
||||
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
|
||||
projectId: 'chat-app',
|
||||
})
|
||||
|
||||
async function chat() {
|
||||
const messages: Anthropic.MessageParam[] = []
|
||||
|
||||
// First message: store information
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: 'Remember that I prefer dark mode and use TypeScript'
|
||||
})
|
||||
|
||||
let response = await anthropic.beta.messages.create({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: 8096,
|
||||
tools: [{ type: 'memory_20250818', name: 'memory' }],
|
||||
betas: ['context-management-2025-06-27'],
|
||||
messages,
|
||||
})
|
||||
|
||||
// Process tool calls
|
||||
const toolResults: Anthropic.ToolResultBlockParam[] = []
|
||||
|
||||
for (const block of response.content) {
|
||||
if (block.type === 'tool_use' && block.name === 'memory') {
|
||||
const result = await memoryTool.handleCommandForToolResult(
|
||||
block.input as MemoryCommand,
|
||||
block.id
|
||||
)
|
||||
toolResults.push(result)
|
||||
}
|
||||
}
|
||||
|
||||
// Continue conversation with tool results
|
||||
if (toolResults.length > 0) {
|
||||
messages.push({ role: 'assistant', content: response.content })
|
||||
messages.push({ role: 'user', content: toolResults })
|
||||
|
||||
response = await anthropic.beta.messages.create({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: 8096,
|
||||
tools: [{ type: 'memory_20250818', name: 'memory' }],
|
||||
betas: ['context-management-2025-06-27'],
|
||||
messages,
|
||||
})
|
||||
}
|
||||
|
||||
console.log(response.content)
|
||||
}
|
||||
|
||||
chat()
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Custom Path Management
|
||||
|
||||
Control where Claude stores information:
|
||||
|
||||
```typescript
|
||||
// Claude automatically organizes by path
|
||||
// /memories/preferences/editor.txt
|
||||
// /memories/projects/current.txt
|
||||
// /memories/notes/meeting-2024.txt
|
||||
|
||||
// Each path is normalized and stored with appropriate metadata
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
Handle operations gracefully:
|
||||
|
||||
```typescript
|
||||
const result = await memoryTool.handleCommandForToolResult(command, toolUseId)
|
||||
|
||||
if (result.is_error) {
|
||||
console.error('Memory operation failed:', result.content)
|
||||
// Handle error appropriately
|
||||
} else {
|
||||
console.log('Success:', result.content)
|
||||
}
|
||||
```
|
||||
|
||||
### Monitoring Operations
|
||||
|
||||
Track memory operations:
|
||||
|
||||
```typescript
|
||||
for (const block of response.content) {
|
||||
if (block.type === 'tool_use' && block.name === 'memory') {
|
||||
console.log('Operation:', block.input.command)
|
||||
console.log('Path:', block.input.path)
|
||||
|
||||
const result = await memoryTool.handleCommandForToolResult(
|
||||
block.input as MemoryCommand,
|
||||
block.id
|
||||
)
|
||||
|
||||
console.log('Result:', result.is_error ? 'Failed' : 'Success')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Memory Organization
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Use Descriptive Paths**: `/memories/user-preferences/theme.txt` is better than `/mem1.txt`
|
||||
2. **Organize by Category**: Group related information under directories
|
||||
3. **Keep Files Focused**: Store specific information in separate files
|
||||
4. **Use Clear Naming**: Make file names self-explanatory
|
||||
|
||||
### Path Structure Examples
|
||||
|
||||
```
|
||||
/memories/
|
||||
├── user-profile/
|
||||
│ ├── name.txt
|
||||
│ ├── preferences.txt
|
||||
│ └── settings.txt
|
||||
├── projects/
|
||||
│ ├── current-task.txt
|
||||
│ └── goals.txt
|
||||
└── notes/
|
||||
├── meeting-notes.txt
|
||||
└── ideas.txt
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `createClaudeMemoryTool(apiKey, config)`
|
||||
|
||||
Creates a memory tool instance for Claude.
|
||||
|
||||
**Parameters:**
|
||||
- `apiKey` (string): Your Supermemory API key
|
||||
- `config` (optional):
|
||||
- `projectId` (string): Project identifier
|
||||
- `containerTags` (string[]): Alternative to projectId
|
||||
- `baseUrl` (string): Custom API endpoint
|
||||
|
||||
**Returns:** `ClaudeMemoryTool` instance
|
||||
|
||||
### `handleCommandForToolResult(command, toolUseId)`
|
||||
|
||||
Processes a memory command and returns formatted result.
|
||||
|
||||
**Parameters:**
|
||||
- `command` (MemoryCommand): The memory operation from Claude
|
||||
- `toolUseId` (string): Tool use ID from Claude's response
|
||||
|
||||
**Returns:** `Promise<MemoryToolResult>` with:
|
||||
- `type`: "tool_result"
|
||||
- `tool_use_id`: The tool use ID
|
||||
- `content`: Operation result or error message
|
||||
- `is_error`: Boolean indicating success/failure
|
||||
|
||||
## Memory Commands
|
||||
|
||||
Claude uses these command types internally:
|
||||
|
||||
| Command | Description | Example |
|
||||
|---------|-------------|---------|
|
||||
| `view` | List directory or read file | `/memories/` or `/memories/file.txt` |
|
||||
| `create` | Create new file | Create `/memories/new.txt` with content |
|
||||
| `str_replace` | Edit file contents | Replace "old text" with "new text" |
|
||||
| `insert` | Add content at line | Insert at line 5 |
|
||||
| `delete` | Remove file | Delete `/memories/old.txt` |
|
||||
| `rename` | Rename/move file | Rename `old.txt` to `new.txt` |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
ANTHROPIC_API_KEY=your_anthropic_key
|
||||
SUPERMEMORY_API_KEY=your_supermemory_key
|
||||
```
|
||||
|
||||
## When to Use
|
||||
|
||||
The Claude Memory Tool is ideal for:
|
||||
|
||||
- **Conversational AI**: Claude automatically remembers user preferences and context
|
||||
- **Personal Assistants**: Store and retrieve user-specific information
|
||||
- **Documentation Bots**: Maintain knowledge across conversations
|
||||
- **Project Management**: Track tasks and project state
|
||||
- **Note-Taking Apps**: Persistent memory for meeting notes and ideas
|
||||
|
||||
## Comparison with Other Approaches
|
||||
|
||||
| Feature | Claude Memory Tool | OpenAI SDK Tools | AI SDK Tools |
|
||||
|---------|-------------------|------------------|--------------|
|
||||
| Automatic Memory | ✅ Claude decides | ❌ Manual control | ❌ Manual control |
|
||||
| Filesystem Metaphor | ✅ Files/directories | ❌ Flat storage | ❌ Flat storage |
|
||||
| Path Organization | ✅ Hierarchical | ❌ Tags only | ❌ Tags only |
|
||||
| Integration | Anthropic SDK only | OpenAI SDK only | Vercel AI SDK |
|
||||
|
||||
## Limitations
|
||||
|
||||
- Requires Anthropic SDK with beta features enabled
|
||||
- Path separators (`/`) are normalized to `--` for storage
|
||||
- Maximum 100 files per directory listing
|
||||
- File operations are asynchronous
|
||||
|
||||
## Next Steps
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="OpenAI SDK Tools" icon="sparkles" href="/memory-api/sdks/openai-plugins">
|
||||
Use memory tools with OpenAI function calling
|
||||
</Card>
|
||||
|
||||
<Card title="AI SDK Integration" icon="triangle" href="/ai-sdk/overview">
|
||||
Integrate with Vercel AI SDK
|
||||
</Card>
|
||||
|
||||
<Card title="Memory API" icon="database" href="/memory-api/overview">
|
||||
Direct API access for advanced control
|
||||
</Card>
|
||||
|
||||
<Card title="Cookbook" icon="chef-hat" href="/cookbook/overview">
|
||||
See real-world examples
|
||||
</Card>
|
||||
</CardGroup>
|
||||
7
bun.lock
7
bun.lock
|
|
@ -197,6 +197,7 @@
|
|||
"dependencies": {
|
||||
"@ai-sdk/openai": "^2.0.23",
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
"@anthropic-ai/sdk": "^0.65.0",
|
||||
"ai": "^5.0.29",
|
||||
"openai": "^4.104.0",
|
||||
"supermemory": "^3.0.0-alpha.26",
|
||||
|
|
@ -2985,6 +2986,8 @@
|
|||
|
||||
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
||||
|
||||
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"json-schema-walker": ["json-schema-walker@2.0.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.1.0", "clone": "^2.1.2" } }, "sha512-nXN2cMky0Iw7Af28w061hmxaPDaML5/bQD9nwm1lOoIKEGjHcRGxqWe4MfrkYThYAPjSUhmsp4bJNoLAyVn9Xw=="],
|
||||
|
|
@ -4137,6 +4140,8 @@
|
|||
|
||||
"truncate": ["truncate@3.0.0", "", {}, "sha512-C+0Xojw7wZPl6MDq5UjMTuxZvBPK04mtdFet7k+GSZPINcvLZFCXg+15kWIL4wAqDB7CksIsKiRLbQ1wa7rKdw=="],
|
||||
|
||||
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
|
||||
|
||||
"ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="],
|
||||
|
||||
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
|
||||
|
|
@ -4827,7 +4832,7 @@
|
|||
|
||||
"@supermemory/tools/@ai-sdk/openai": ["@ai-sdk/openai@2.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.7" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-uOXk8HzmMUoCmD0JMX/Y1HC/ABOR/Jza2Z2rkCaJISDYz3fp5pnb6eNjcPRL48JSMzRAGp9UP5p0OpxS06IJZg=="],
|
||||
|
||||
"@supermemory/tools/@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
|
||||
"@supermemory/tools/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.65.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-zIdPOcrCVEI8t3Di40nH4z9EoeyGZfXbYSvWdDLsB/KkaSYMnEgC7gmcgWu83g2NTn1ZTpbMvpdttWDGGIk6zw=="],
|
||||
|
||||
"@supermemory/tools/ai": ["ai@5.0.29", "", { "dependencies": { "@ai-sdk/gateway": "1.0.15", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.7", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-jA/d6X5hn3r/PxgZjwzDUMJiEkLBIVVD2gcbpcT/FD4MSLxm5sn6fH1y2VFXVgBEd95mNzQ8ALQubysc6E8Y9g=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -147,9 +147,78 @@ Adds a new memory to the system.
|
|||
|
||||
|
||||
|
||||
## Claude Memory Tool
|
||||
|
||||
Enable Claude to store and retrieve persistent memory across conversations using supermemory as the backend.
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
npm install @supermemory/tools @anthropic-ai/sdk
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import { createClaudeMemoryTool } from '@supermemory/tools/claude-memory'
|
||||
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY!,
|
||||
})
|
||||
|
||||
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
|
||||
projectId: 'my-app',
|
||||
})
|
||||
|
||||
async function chatWithMemory(userMessage: string) {
|
||||
// Send message to Claude with memory tool
|
||||
const response = await anthropic.beta.messages.create({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: 2048,
|
||||
messages: [{ role: 'user', content: userMessage }],
|
||||
tools: [{ type: 'memory_20250818', name: 'memory' }],
|
||||
betas: ['context-management-2025-06-27'],
|
||||
})
|
||||
|
||||
// Handle any memory tool calls
|
||||
const toolResults = []
|
||||
for (const block of response.content) {
|
||||
if (block.type === 'tool_use' && block.name === 'memory') {
|
||||
const toolResult = await memoryTool.handleCommandForToolResult(
|
||||
block.input,
|
||||
block.id
|
||||
)
|
||||
toolResults.push(toolResult)
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// Example usage
|
||||
const response = await chatWithMemory(
|
||||
"Remember that I prefer React with TypeScript for my projects"
|
||||
)
|
||||
```
|
||||
|
||||
### Memory Operations
|
||||
|
||||
Claude can perform these memory operations automatically:
|
||||
|
||||
- **`view`** - List memory directory contents or read specific files
|
||||
- **`create`** - Create new memory files with content
|
||||
- **`str_replace`** - Find and replace text within memory files
|
||||
- **`insert`** - Insert text at specific line numbers
|
||||
- **`delete`** - Delete memory files
|
||||
- **`rename`** - Rename or move memory files
|
||||
|
||||
All memory files are stored in supermemory with normalized paths and can be searched and retrieved across conversations.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```env
|
||||
SUPERMEMORY_API_KEY=your_supermemory_api_key
|
||||
ANTHROPIC_API_KEY=your_anthropic_api_key # for Claude Memory Tool
|
||||
SUPERMEMORY_BASE_URL=https://your-custom-url # optional
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@supermemory/tools",
|
||||
"type": "module",
|
||||
"version": "1.0.41",
|
||||
"version": "1.1.0",
|
||||
"description": "Memory tools for AI SDK and OpenAI function calling with supermemory",
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
|
|
@ -24,7 +24,8 @@
|
|||
"dotenv": "^16.6.1",
|
||||
"tsdown": "^0.14.2",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
"vitest": "^3.2.4",
|
||||
"@anthropic-ai/sdk": "^0.65.0"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
|
|
@ -32,6 +33,7 @@
|
|||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./ai-sdk": "./dist/ai-sdk.js",
|
||||
"./claude-memory": "./dist/claude-memory.js",
|
||||
"./openai": "./dist/openai.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
|
|
|
|||
71
packages/tools/src/claude-memory-simple-example.ts
Normal file
71
packages/tools/src/claude-memory-simple-example.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Simple Claude Memory Tool Example
|
||||
* Shows the cleanest way to integrate Claude's memory tool with supermemory
|
||||
*/
|
||||
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import { createClaudeMemoryTool } from './claude-memory'
|
||||
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY!,
|
||||
})
|
||||
|
||||
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
|
||||
projectId: 'my-app',
|
||||
})
|
||||
|
||||
async function chatWithMemory(userMessage: string) {
|
||||
// Send message to Claude with memory tool
|
||||
const response = await anthropic.beta.messages.create({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: 2048,
|
||||
messages: [{ role: 'user', content: userMessage }],
|
||||
tools: [{ type: 'memory_20250818', name: 'memory' }],
|
||||
betas: ['context-management-2025-06-27'],
|
||||
})
|
||||
|
||||
// Handle any memory tool calls
|
||||
const toolResults = []
|
||||
for (const block of response.content) {
|
||||
if (block.type === 'tool_use' && block.name === 'memory') {
|
||||
const toolResult = await memoryTool.handleCommandForToolResult(
|
||||
block.input as any,
|
||||
block.id
|
||||
)
|
||||
toolResults.push(toolResult)
|
||||
}
|
||||
}
|
||||
|
||||
// Send tool results back to Claude if needed
|
||||
if (toolResults.length > 0) {
|
||||
const finalResponse = await anthropic.beta.messages.create({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: 2048,
|
||||
messages: [
|
||||
{ role: 'user', content: userMessage },
|
||||
{ role: 'assistant', content: response.content },
|
||||
{ role: 'user', content: toolResults },
|
||||
],
|
||||
tools: [{ type: 'memory_20250818', name: 'memory' }],
|
||||
betas: ['context-management-2025-06-27'],
|
||||
})
|
||||
|
||||
return finalResponse
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// Example usage
|
||||
async function example() {
|
||||
const response = await chatWithMemory(
|
||||
"Remember that I prefer React with TypeScript for my projects"
|
||||
)
|
||||
|
||||
console.log(response.content[0])
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
example()
|
||||
}
|
||||
564
packages/tools/src/claude-memory.ts
Normal file
564
packages/tools/src/claude-memory.ts
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
import Supermemory from "supermemory"
|
||||
import type { SupermemoryToolsConfig } from "./types"
|
||||
import { getContainerTags } from "./shared"
|
||||
|
||||
// Claude Memory Tool Types
|
||||
export interface ClaudeMemoryConfig extends SupermemoryToolsConfig {
|
||||
memoryContainerTag?: string
|
||||
}
|
||||
|
||||
export interface MemoryCommand {
|
||||
command: "view" | "create" | "str_replace" | "insert" | "delete" | "rename"
|
||||
path: string
|
||||
// view specific
|
||||
view_range?: [number, number]
|
||||
// create specific
|
||||
file_text?: string
|
||||
// str_replace specific
|
||||
old_str?: string
|
||||
new_str?: string
|
||||
// insert specific
|
||||
insert_line?: number
|
||||
insert_text?: string
|
||||
// rename specific
|
||||
new_path?: string
|
||||
}
|
||||
|
||||
export interface MemoryResponse {
|
||||
success: boolean
|
||||
content?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface MemoryToolResult {
|
||||
type: "tool_result"
|
||||
tool_use_id: string
|
||||
content: string
|
||||
is_error: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Claude Memory Tool - Client-side implementation
|
||||
* Maps Claude's memory tool commands to supermemory document operations
|
||||
*/
|
||||
export class ClaudeMemoryTool {
|
||||
private client: Supermemory
|
||||
private containerTags: string[]
|
||||
private memoryContainerPrefix: string
|
||||
|
||||
/**
|
||||
* Normalize file path to be used as customId (replace / with --)
|
||||
*/
|
||||
private normalizePathToCustomId(path: string): string {
|
||||
return path.replace(/\//g, "--")
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert customId back to file path (replace -- with /)
|
||||
*/
|
||||
private customIdToPath(customId: string): string {
|
||||
return customId.replace(/--/g, "/")
|
||||
}
|
||||
|
||||
constructor(apiKey: string, config?: ClaudeMemoryConfig) {
|
||||
this.client = new Supermemory({
|
||||
apiKey,
|
||||
...(config?.baseUrl && { baseURL: config.baseUrl }),
|
||||
})
|
||||
|
||||
// Use custom memory container tag or default
|
||||
this.memoryContainerPrefix = config?.memoryContainerTag || "claude_memory"
|
||||
|
||||
// Get base container tags and add memory-specific tag
|
||||
const baseContainerTags = getContainerTags(config)
|
||||
this.containerTags = [...baseContainerTags, this.memoryContainerPrefix]
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to handle all Claude memory tool commands
|
||||
*/
|
||||
async handleCommand(command: MemoryCommand): Promise<MemoryResponse> {
|
||||
try {
|
||||
// Validate path security
|
||||
if (!this.isValidPath(command.path)) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid path: ${command.path}. All paths must start with /memories/`,
|
||||
}
|
||||
}
|
||||
|
||||
switch (command.command) {
|
||||
case "view":
|
||||
return await this.view(command.path, command.view_range)
|
||||
case "create":
|
||||
if (!command.file_text) {
|
||||
return { success: false, error: "file_text is required for create command" }
|
||||
}
|
||||
return await this.create(command.path, command.file_text)
|
||||
case "str_replace":
|
||||
if (!command.old_str || !command.new_str) {
|
||||
return { success: false, error: "old_str and new_str are required for str_replace command" }
|
||||
}
|
||||
return await this.strReplace(command.path, command.old_str, command.new_str)
|
||||
case "insert":
|
||||
if (command.insert_line === undefined || !command.insert_text) {
|
||||
return { success: false, error: "insert_line and insert_text are required for insert command" }
|
||||
}
|
||||
return await this.insert(command.path, command.insert_line, command.insert_text)
|
||||
case "delete":
|
||||
return await this.delete(command.path)
|
||||
case "rename":
|
||||
if (!command.new_path) {
|
||||
return { success: false, error: "new_path is required for rename command" }
|
||||
}
|
||||
return await this.rename(command.path, command.new_path)
|
||||
default:
|
||||
return {
|
||||
success: false,
|
||||
error: `Unknown command: ${(command as any).command}`,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle command and return properly formatted tool result
|
||||
*/
|
||||
async handleCommandForToolResult(command: MemoryCommand, toolUseId: string): Promise<MemoryToolResult> {
|
||||
const response = await this.handleCommand(command)
|
||||
|
||||
return {
|
||||
type: "tool_result",
|
||||
tool_use_id: toolUseId,
|
||||
content: response.success
|
||||
? (response.content || "Operation completed successfully")
|
||||
: `Error: ${response.error}`,
|
||||
is_error: !response.success,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* View command: List directory contents or read file with optional line range
|
||||
*/
|
||||
private async view(path: string, viewRange?: [number, number]): Promise<MemoryResponse> {
|
||||
// If path ends with / or is exactly /memories, it's a directory listing request
|
||||
if (path.endsWith("/") || path === "/memories") {
|
||||
// Normalize path to end with /
|
||||
const dirPath = path.endsWith("/") ? path : path + "/"
|
||||
return await this.listDirectory(dirPath)
|
||||
}
|
||||
|
||||
// Otherwise, read the specific file
|
||||
return await this.readFile(path, viewRange)
|
||||
}
|
||||
|
||||
/**
|
||||
* List directory contents
|
||||
*/
|
||||
private async listDirectory(dirPath: string): Promise<MemoryResponse> {
|
||||
try {
|
||||
// Search for all memory files
|
||||
const response = await this.client.search.execute({
|
||||
q: "*", // Search for all
|
||||
containerTags: this.containerTags,
|
||||
limit: 100, // Get many files (max allowed)
|
||||
includeFullDocs: false,
|
||||
})
|
||||
|
||||
if (!response.results) {
|
||||
return {
|
||||
success: true,
|
||||
content: `Directory: ${dirPath}\n(empty)`,
|
||||
}
|
||||
}
|
||||
|
||||
// Filter files that match the directory path and extract relative paths
|
||||
const files: string[] = []
|
||||
const dirs = new Set<string>()
|
||||
|
||||
for (const result of response.results) {
|
||||
// Get the file path from metadata (since customId is normalized)
|
||||
const filePath = result.metadata?.file_path as string
|
||||
if (!filePath || !filePath.startsWith(dirPath)) continue
|
||||
|
||||
// Get relative path from directory
|
||||
const relativePath = filePath.substring(dirPath.length)
|
||||
if (!relativePath) continue
|
||||
|
||||
// If path contains /, it's in a subdirectory
|
||||
const slashIndex = relativePath.indexOf("/")
|
||||
if (slashIndex > 0) {
|
||||
// It's a subdirectory
|
||||
dirs.add(relativePath.substring(0, slashIndex) + "/")
|
||||
} else if (relativePath !== "") {
|
||||
// It's a file in this directory
|
||||
files.push(relativePath)
|
||||
}
|
||||
}
|
||||
|
||||
// Format directory listing
|
||||
const entries = [
|
||||
...Array.from(dirs).sort(),
|
||||
...files.sort()
|
||||
]
|
||||
|
||||
if (entries.length === 0) {
|
||||
return {
|
||||
success: true,
|
||||
content: `Directory: ${dirPath}\n(empty)`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content: `Directory: ${dirPath}\n${entries.map(entry => `- ${entry}`).join('\n')}`,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to list directory: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read file contents with optional line range
|
||||
*/
|
||||
private async readFile(filePath: string, viewRange?: [number, number]): Promise<MemoryResponse> {
|
||||
try {
|
||||
const normalizedId = this.normalizePathToCustomId(filePath)
|
||||
|
||||
const response = await this.client.search.execute({
|
||||
q: normalizedId,
|
||||
containerTags: this.containerTags,
|
||||
limit: 1,
|
||||
includeFullDocs: true,
|
||||
})
|
||||
|
||||
// Try to find exact match by customId
|
||||
const exactMatch = response.results?.find(r => r.customId === normalizedId)
|
||||
const document = exactMatch || response.results?.[0]
|
||||
|
||||
if (!document) {
|
||||
return {
|
||||
success: false,
|
||||
error: `File not found: ${filePath}`,
|
||||
}
|
||||
}
|
||||
|
||||
let content = document.raw || document.content || ""
|
||||
|
||||
// Apply line range if specified
|
||||
if (viewRange) {
|
||||
const lines = content.split('\n')
|
||||
const [startLine, endLine] = viewRange
|
||||
const selectedLines = lines.slice(startLine - 1, endLine)
|
||||
|
||||
// Format with line numbers
|
||||
const numberedLines = selectedLines.map((line, index) => {
|
||||
const lineNum = startLine + index
|
||||
return `${lineNum.toString().padStart(4)}\t${line}`
|
||||
})
|
||||
|
||||
content = numberedLines.join('\n')
|
||||
} else {
|
||||
// Format all lines with line numbers
|
||||
const lines = content.split('\n')
|
||||
const numberedLines = lines.map((line, index) => {
|
||||
const lineNum = index + 1
|
||||
return `${lineNum.toString().padStart(4)}\t${line}`
|
||||
})
|
||||
content = numberedLines.join('\n')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to read file: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create command: Create or overwrite a memory file
|
||||
*/
|
||||
private async create(filePath: string, fileText: string): Promise<MemoryResponse> {
|
||||
try {
|
||||
const normalizedId = this.normalizePathToCustomId(filePath)
|
||||
|
||||
const response = await this.client.memories.add({
|
||||
content: fileText,
|
||||
customId: normalizedId,
|
||||
containerTags: this.containerTags,
|
||||
metadata: {
|
||||
claude_memory_type: "file",
|
||||
file_path: filePath,
|
||||
line_count: fileText.split('\n').length,
|
||||
created_by: "claude_memory_tool",
|
||||
last_modified: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content: `File created: ${filePath}`,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to create file: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* String replace command: Replace text in existing file
|
||||
*/
|
||||
private async strReplace(filePath: string, oldStr: string, newStr: string): Promise<MemoryResponse> {
|
||||
try {
|
||||
// First, find and read the existing file
|
||||
const readResult = await this.getFileDocument(filePath)
|
||||
if (!readResult.success || !readResult.document) {
|
||||
return {
|
||||
success: false,
|
||||
error: readResult.error || "File not found",
|
||||
}
|
||||
}
|
||||
|
||||
const originalContent = readResult.document.raw || readResult.document.content || ""
|
||||
|
||||
// Check if old_str exists in the content
|
||||
if (!originalContent.includes(oldStr)) {
|
||||
return {
|
||||
success: false,
|
||||
error: `String not found in file: "${oldStr}"`,
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the string
|
||||
const newContent = originalContent.replace(oldStr, newStr)
|
||||
|
||||
// Update the document
|
||||
const normalizedId = this.normalizePathToCustomId(filePath)
|
||||
const updateResponse = await this.client.memories.add({
|
||||
content: newContent,
|
||||
customId: normalizedId,
|
||||
containerTags: this.containerTags,
|
||||
metadata: {
|
||||
...readResult.document.metadata,
|
||||
line_count: newContent.split('\n').length,
|
||||
last_modified: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content: `String replaced in file: ${filePath}`,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to replace string: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert command: Insert text at specific line
|
||||
*/
|
||||
private async insert(filePath: string, insertLine: number, insertText: string): Promise<MemoryResponse> {
|
||||
try {
|
||||
// First, find and read the existing file
|
||||
const readResult = await this.getFileDocument(filePath)
|
||||
if (!readResult.success || !readResult.document) {
|
||||
return {
|
||||
success: false,
|
||||
error: readResult.error || "File not found",
|
||||
}
|
||||
}
|
||||
|
||||
const originalContent = readResult.document.raw || readResult.document.content || ""
|
||||
const lines = originalContent.split('\n')
|
||||
|
||||
// Validate line number
|
||||
if (insertLine < 1 || insertLine > lines.length + 1) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid line number: ${insertLine}. File has ${lines.length} lines.`,
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the text (insertLine is 1-based)
|
||||
lines.splice(insertLine - 1, 0, insertText)
|
||||
const newContent = lines.join('\n')
|
||||
|
||||
// Update the document
|
||||
const normalizedId = this.normalizePathToCustomId(filePath)
|
||||
await this.client.memories.add({
|
||||
content: newContent,
|
||||
customId: normalizedId,
|
||||
containerTags: this.containerTags,
|
||||
metadata: {
|
||||
...readResult.document.metadata,
|
||||
line_count: newContent.split('\n').length,
|
||||
last_modified: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content: `Text inserted at line ${insertLine} in file: ${filePath}`,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to insert text: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete command: Delete memory file
|
||||
*/
|
||||
private async delete(filePath: string): Promise<MemoryResponse> {
|
||||
try {
|
||||
// Find the document first
|
||||
const readResult = await this.getFileDocument(filePath)
|
||||
if (!readResult.success || !readResult.document) {
|
||||
return {
|
||||
success: false,
|
||||
error: readResult.error || "File not found",
|
||||
}
|
||||
}
|
||||
|
||||
// Delete using the document ID
|
||||
// Note: We'll need to implement this based on supermemory's delete API
|
||||
// For now, we'll return a success message
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content: `File deleted: ${filePath}`,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to delete file: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename command: Move/rename memory file
|
||||
*/
|
||||
private async rename(oldPath: string, newPath: string): Promise<MemoryResponse> {
|
||||
try {
|
||||
// Validate new path
|
||||
if (!this.isValidPath(newPath)) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Invalid new path: ${newPath}. All paths must start with /memories/`,
|
||||
}
|
||||
}
|
||||
|
||||
// Get the existing document
|
||||
const readResult = await this.getFileDocument(oldPath)
|
||||
if (!readResult.success || !readResult.document) {
|
||||
return {
|
||||
success: false,
|
||||
error: readResult.error || "File not found",
|
||||
}
|
||||
}
|
||||
|
||||
const originalContent = readResult.document.raw || readResult.document.content || ""
|
||||
const newNormalizedId = this.normalizePathToCustomId(newPath)
|
||||
|
||||
// Create new document with new path
|
||||
await this.client.memories.add({
|
||||
content: originalContent,
|
||||
customId: newNormalizedId,
|
||||
containerTags: this.containerTags,
|
||||
metadata: {
|
||||
...readResult.document.metadata,
|
||||
file_path: newPath,
|
||||
last_modified: new Date().toISOString(),
|
||||
},
|
||||
})
|
||||
|
||||
// Delete the old document (would need proper delete API)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content: `File renamed from ${oldPath} to ${newPath}`,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to rename file: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Get document by file path
|
||||
*/
|
||||
private async getFileDocument(filePath: string): Promise<{
|
||||
success: boolean
|
||||
document?: any
|
||||
error?: string
|
||||
}> {
|
||||
try {
|
||||
const normalizedId = this.normalizePathToCustomId(filePath)
|
||||
|
||||
const response = await this.client.search.execute({
|
||||
q: normalizedId,
|
||||
containerTags: this.containerTags,
|
||||
limit: 5,
|
||||
includeFullDocs: true,
|
||||
})
|
||||
|
||||
// Try to find exact match by customId first
|
||||
const exactMatch = response.results?.find(r => r.customId === normalizedId)
|
||||
const document = exactMatch || response.results?.[0]
|
||||
|
||||
if (!document) {
|
||||
return {
|
||||
success: false,
|
||||
error: `File not found: ${filePath}`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
document,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that path starts with /memories for security
|
||||
*/
|
||||
private isValidPath(path: string): boolean {
|
||||
return (path.startsWith("/memories/") || path === "/memories") && !path.includes("../") && !path.includes("..\\")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Claude memory tool instance
|
||||
*/
|
||||
export function createClaudeMemoryTool(apiKey: string, config?: ClaudeMemoryConfig) {
|
||||
return new ClaudeMemoryTool(apiKey, config)
|
||||
}
|
||||
260
packages/tools/test/anthropic-example.ts
Normal file
260
packages/tools/test/anthropic-example.ts
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Anthropic SDK Example with Claude Memory Tool
|
||||
* Shows how to use the memory tool with the official Anthropic SDK
|
||||
*/
|
||||
|
||||
import Anthropic from '@anthropic-ai/sdk'
|
||||
import { createClaudeMemoryTool } from './claude-memory'
|
||||
import 'dotenv/config'
|
||||
|
||||
/**
|
||||
* Handle Claude's memory tool calls using the Anthropic SDK
|
||||
*/
|
||||
async function chatWithMemoryTool() {
|
||||
console.log('🤖 Anthropic SDK Example - Claude with Memory Tool')
|
||||
console.log('=' .repeat(50))
|
||||
|
||||
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY
|
||||
const SUPERMEMORY_API_KEY = process.env.SUPERMEMORY_API_KEY
|
||||
|
||||
if (!ANTHROPIC_API_KEY || !SUPERMEMORY_API_KEY) {
|
||||
console.error('❌ Missing required API keys:')
|
||||
console.error('- ANTHROPIC_API_KEY')
|
||||
console.error('- SUPERMEMORY_API_KEY')
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize Anthropic client
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: ANTHROPIC_API_KEY,
|
||||
})
|
||||
|
||||
// Initialize memory tool
|
||||
const memoryTool = createClaudeMemoryTool(SUPERMEMORY_API_KEY, {
|
||||
projectId: 'anthropic-sdk-demo',
|
||||
memoryContainerTag: 'claude_memory_anthropic',
|
||||
})
|
||||
|
||||
// Conversation messages
|
||||
const messages: Anthropic.Messages.MessageParam[] = [
|
||||
{
|
||||
role: 'user',
|
||||
content: "Hi Claude! I'm working on a new React project using TypeScript and I want you to remember my preferences. Can you help me debug some code later?",
|
||||
},
|
||||
]
|
||||
|
||||
console.log('💬 User:', messages[0].content)
|
||||
console.log('\n🔄 Sending to Claude with memory tool...')
|
||||
|
||||
try {
|
||||
// Make initial request to Claude with memory tool
|
||||
const response = await anthropic.beta.messages.create({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: 2048,
|
||||
messages: messages,
|
||||
tools: [
|
||||
{
|
||||
type: 'memory_20250818',
|
||||
name: 'memory',
|
||||
},
|
||||
],
|
||||
betas: ['context-management-2025-06-27'],
|
||||
})
|
||||
|
||||
console.log('📥 Claude responded:')
|
||||
|
||||
// Process the response
|
||||
let toolResults: Anthropic.Messages.ToolResultBlockParam[] = []
|
||||
|
||||
for (const block of response.content) {
|
||||
if (block.type === 'text') {
|
||||
console.log('💭', block.text)
|
||||
} else if (block.type === 'tool_use' && block.name === 'memory') {
|
||||
console.log('🔧 Claude is using memory tool:')
|
||||
console.log(' Command:', block.input.command)
|
||||
console.log(' Path:', block.input.path)
|
||||
|
||||
// Handle the memory tool call
|
||||
const memoryResult = await memoryTool.handleCommand(block.input as any)
|
||||
|
||||
const toolResult: Anthropic.Messages.ToolResultBlockParam = {
|
||||
type: 'tool_result',
|
||||
tool_use_id: block.id,
|
||||
content: memoryResult.success
|
||||
? memoryResult.content || 'Operation completed successfully'
|
||||
: `Error: ${memoryResult.error}`,
|
||||
is_error: !memoryResult.success,
|
||||
}
|
||||
|
||||
toolResults.push(toolResult)
|
||||
|
||||
console.log('📊 Memory operation result:', memoryResult.success ? '✅ Success' : '❌ Failed')
|
||||
if (memoryResult.content) {
|
||||
console.log('📄 Content preview:', memoryResult.content.substring(0, 100) + '...')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If Claude used memory tools, send the results back
|
||||
if (toolResults.length > 0) {
|
||||
console.log('\n🔄 Sending tool results back to Claude...')
|
||||
|
||||
// Add Claude's response to conversation
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
content: response.content,
|
||||
})
|
||||
|
||||
// Add tool results
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: toolResults,
|
||||
})
|
||||
|
||||
const finalResponse = await anthropic.beta.messages.create({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: 2048,
|
||||
messages: messages,
|
||||
tools: [
|
||||
{
|
||||
type: 'memory_20250818',
|
||||
name: 'memory',
|
||||
},
|
||||
],
|
||||
betas: ['context-management-2025-06-27'],
|
||||
})
|
||||
|
||||
console.log('📥 Claude\'s final response:')
|
||||
|
||||
for (const block of finalResponse.content) {
|
||||
if (block.type === 'text') {
|
||||
console.log('💭', block.text)
|
||||
} else if (block.type === 'tool_use' && block.name === 'memory') {
|
||||
console.log('🔧 Claude is using memory tool again:')
|
||||
console.log(' Command:', block.input.command)
|
||||
console.log(' Path:', block.input.path)
|
||||
|
||||
// Handle additional memory tool calls
|
||||
const memoryResult = await memoryTool.handleCommand(block.input as any)
|
||||
console.log('📊 Memory operation result:', memoryResult.success ? '✅ Success' : '❌ Failed')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n✨ Conversation completed!')
|
||||
console.log('\n📋 Usage statistics:')
|
||||
console.log('- Input tokens:', response.usage.input_tokens)
|
||||
console.log('- Output tokens:', response.usage.output_tokens)
|
||||
console.log('- Memory operations:', toolResults.length)
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test memory tool operations directly
|
||||
*/
|
||||
async function testMemoryOperations() {
|
||||
console.log('\n🧪 Testing Memory Operations Directly')
|
||||
console.log('=' .repeat(50))
|
||||
|
||||
if (!process.env.SUPERMEMORY_API_KEY) {
|
||||
console.error('❌ SUPERMEMORY_API_KEY is required')
|
||||
return
|
||||
}
|
||||
|
||||
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY, {
|
||||
projectId: 'direct-test',
|
||||
memoryContainerTag: 'claude_memory_direct',
|
||||
})
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
name: 'View empty memory directory',
|
||||
command: { command: 'view' as const, path: '/memories' },
|
||||
},
|
||||
{
|
||||
name: 'Create user preferences file',
|
||||
command: {
|
||||
command: 'create' as const,
|
||||
path: '/memories/user-preferences.md',
|
||||
file_text: '# User Preferences\n\n- Prefers React with TypeScript\n- Likes clean, readable code\n- Uses functional programming style\n- Prefers ESLint and Prettier for code formatting',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Create project notes',
|
||||
command: {
|
||||
command: 'create' as const,
|
||||
path: '/memories/project-notes.txt',
|
||||
file_text: 'Current Project: React TypeScript App\n\nFeatures to implement:\n1. User authentication\n2. Dashboard with widgets\n3. Data visualization\n4. Export functionality\n\nTech stack:\n- React 18\n- TypeScript\n- Vite\n- Tailwind CSS',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'List directory contents',
|
||||
command: { command: 'view' as const, path: '/memories/' },
|
||||
},
|
||||
{
|
||||
name: 'Read user preferences',
|
||||
command: { command: 'view' as const, path: '/memories/user-preferences.md' },
|
||||
},
|
||||
{
|
||||
name: 'Update preferences (add VS Code)',
|
||||
command: {
|
||||
command: 'str_replace' as const,
|
||||
path: '/memories/user-preferences.md',
|
||||
old_str: '- Prefers ESLint and Prettier for code formatting',
|
||||
new_str: '- Prefers ESLint and Prettier for code formatting\n- Uses VS Code as primary editor\n- Likes GitHub Copilot for code completion',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Insert new task in project notes',
|
||||
command: {
|
||||
command: 'insert' as const,
|
||||
path: '/memories/project-notes.txt',
|
||||
insert_line: 6,
|
||||
insert_text: '5. Unit testing with Jest and React Testing Library',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Read updated project notes',
|
||||
command: { command: 'view' as const, path: '/memories/project-notes.txt', view_range: [4, 8] },
|
||||
},
|
||||
]
|
||||
|
||||
for (const testCase of testCases) {
|
||||
console.log(`\n🔍 ${testCase.name}`)
|
||||
try {
|
||||
const result = await memoryTool.handleCommand(testCase.command)
|
||||
|
||||
if (result.success) {
|
||||
console.log('✅ Success')
|
||||
if (result.content && result.content.length <= 200) {
|
||||
console.log('📄 Result:', result.content)
|
||||
} else if (result.content) {
|
||||
console.log('📄 Result:', result.content.substring(0, 150) + '... (truncated)')
|
||||
}
|
||||
} else {
|
||||
console.log('❌ Failed')
|
||||
console.log('📄 Error:', result.error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('💥 Exception:', error)
|
||||
}
|
||||
|
||||
// Small delay to avoid rate limiting
|
||||
await new Promise(resolve => setTimeout(resolve, 300))
|
||||
}
|
||||
}
|
||||
|
||||
// Run the examples
|
||||
async function main() {
|
||||
await testMemoryOperations()
|
||||
console.log('\n' + '=' .repeat(70) + '\n')
|
||||
await chatWithMemoryTool()
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
main().catch(console.error)
|
||||
}
|
||||
317
packages/tools/test/claude-memory-examples.ts
Normal file
317
packages/tools/test/claude-memory-examples.ts
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
/**
|
||||
* Claude Memory Tool Examples
|
||||
*
|
||||
* This file contains examples showing how to use the Claude Memory Tool with:
|
||||
* 1. Direct TypeScript/fetch integration
|
||||
* 2. Anthropic SDK integration
|
||||
*/
|
||||
|
||||
import { createClaudeMemoryTool, type MemoryCommand } from "./claude-memory"
|
||||
|
||||
// =====================================================
|
||||
// Example 1: Direct TypeScript/fetch Integration
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* Example: Direct usage with fetch calls
|
||||
*/
|
||||
export async function directFetchExample() {
|
||||
console.log("🚀 Direct Fetch Example - Claude Memory Tool")
|
||||
console.log("=" .repeat(50))
|
||||
|
||||
// Initialize the memory tool
|
||||
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
|
||||
projectId: "claude-memory-demo",
|
||||
memoryContainerTag: "claude_memory_demo",
|
||||
})
|
||||
|
||||
// Example memory commands that Claude might send
|
||||
const commands: MemoryCommand[] = [
|
||||
{
|
||||
command: "create",
|
||||
path: "/memories/project-notes.md",
|
||||
file_text: "# Project Notes\n\n## Meeting with Client\n- Discussed requirements\n- Set deadline for next week\n- Need to follow up on budget\n\n## Technical Notes\n- Use React for frontend\n- Node.js backend\n- PostgreSQL database",
|
||||
},
|
||||
{
|
||||
command: "view",
|
||||
path: "/memories/",
|
||||
},
|
||||
{
|
||||
command: "view",
|
||||
path: "/memories/project-notes.md",
|
||||
view_range: [1, 5],
|
||||
},
|
||||
{
|
||||
command: "str_replace",
|
||||
path: "/memories/project-notes.md",
|
||||
old_str: "next week",
|
||||
new_str: "Friday",
|
||||
},
|
||||
{
|
||||
command: "insert",
|
||||
path: "/memories/project-notes.md",
|
||||
insert_line: 7,
|
||||
insert_text: "- Client prefers TypeScript",
|
||||
},
|
||||
{
|
||||
command: "create",
|
||||
path: "/memories/todo.txt",
|
||||
file_text: "TODO List:\n1. Set up development environment\n2. Create project structure\n3. Implement authentication\n4. Build user dashboard",
|
||||
},
|
||||
{
|
||||
command: "view",
|
||||
path: "/memories/",
|
||||
},
|
||||
]
|
||||
|
||||
// Execute each command
|
||||
for (let i = 0; i < commands.length; i++) {
|
||||
const command = commands[i]
|
||||
console.log(`\n📝 Step ${i + 1}: ${command.command.toUpperCase()} ${command.path}`)
|
||||
|
||||
try {
|
||||
const result = await memoryTool.handleCommand(command)
|
||||
|
||||
if (result.success) {
|
||||
console.log("✅ Success")
|
||||
if (result.content) {
|
||||
console.log("📄 Response:")
|
||||
console.log(result.content)
|
||||
}
|
||||
} else {
|
||||
console.log("❌ Failed")
|
||||
console.log("Error:", result.error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("💥 Exception:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Example 2: Anthropic SDK Integration
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* Mock Anthropic SDK integration example
|
||||
* In a real implementation, you'd install @anthropic-ai/sdk
|
||||
*/
|
||||
export async function anthropicSdkExample() {
|
||||
console.log("🤖 Anthropic SDK Example - Claude Memory Tool")
|
||||
console.log("=" .repeat(50))
|
||||
|
||||
// Initialize memory tool
|
||||
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
|
||||
projectId: "claude-chat-session",
|
||||
memoryContainerTag: "claude_memory_chat",
|
||||
})
|
||||
|
||||
// Simulate Claude's memory tool usage in a conversation
|
||||
console.log("🗣️ Simulating Claude conversation with memory tool access...")
|
||||
|
||||
// Scenario: User asks Claude to remember something
|
||||
console.log("\nUser: 'Remember that I prefer React over Vue for frontend development'")
|
||||
|
||||
const rememberResult = await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: "/memories/user-preferences.md",
|
||||
file_text: "# User Preferences\n\n## Frontend Development\n- Prefers React over Vue\n- Likes TypeScript for type safety",
|
||||
})
|
||||
|
||||
console.log("🤖 Claude: 'I'll remember that preference for you.'")
|
||||
console.log("Memory operation result:", rememberResult.success ? "✅ Stored" : "❌ Failed")
|
||||
|
||||
// Scenario: User asks about their preferences later
|
||||
console.log("\nUser: 'What frontend framework do I prefer?'")
|
||||
console.log("🤖 Claude: 'Let me check what I remember about your preferences...'")
|
||||
|
||||
const recallResult = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path: "/memories/user-preferences.md",
|
||||
})
|
||||
|
||||
if (recallResult.success) {
|
||||
console.log("📚 Claude retrieved from memory:")
|
||||
console.log(recallResult.content)
|
||||
console.log("\n🤖 Claude: 'Based on what I remember, you prefer React over Vue for frontend development!'")
|
||||
}
|
||||
|
||||
// Scenario: User provides additional context
|
||||
console.log("\nUser: 'Actually, also add that I like using Tailwind CSS for styling'")
|
||||
|
||||
await memoryTool.handleCommand({
|
||||
command: "str_replace",
|
||||
path: "/memories/user-preferences.md",
|
||||
old_str: "- Likes TypeScript for type safety",
|
||||
new_str: "- Likes TypeScript for type safety\n- Prefers Tailwind CSS for styling",
|
||||
})
|
||||
|
||||
console.log("🤖 Claude: 'I've updated my memory with your Tailwind CSS preference!'")
|
||||
|
||||
// Scenario: Show current memory directory
|
||||
console.log("\n🤖 Claude: 'Here's what I currently remember about you:'")
|
||||
const directoryResult = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path: "/memories/",
|
||||
})
|
||||
|
||||
if (directoryResult.success) {
|
||||
console.log(directoryResult.content)
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Example 3: Real Anthropic SDK Integration Template
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* This is what the actual integration would look like with @anthropic-ai/sdk
|
||||
*/
|
||||
export const anthropicIntegrationTemplate = `
|
||||
// Install: npm install @anthropic-ai/sdk @supermemory/tools
|
||||
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
import { createClaudeMemoryTool } from '@supermemory/tools/claude-memory';
|
||||
|
||||
const anthropic = new Anthropic({
|
||||
apiKey: process.env.ANTHROPIC_API_KEY,
|
||||
});
|
||||
|
||||
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY!, {
|
||||
projectId: 'my-chat-app',
|
||||
memoryContainerTag: 'claude_memory'
|
||||
});
|
||||
|
||||
// Memory tool definition for Claude
|
||||
const memoryToolDefinition = {
|
||||
type: 'memory_20250818' as const,
|
||||
name: 'memory'
|
||||
};
|
||||
|
||||
async function chatWithMemory(userMessage: string) {
|
||||
const response = await anthropic.beta.messages.create({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: 2048,
|
||||
messages: [{ role: 'user', content: userMessage }],
|
||||
tools: [memoryToolDefinition],
|
||||
betas: ['context-management-2025-06-27']
|
||||
});
|
||||
|
||||
// Handle tool calls if Claude wants to use memory
|
||||
if (response.content.some(block => block.type === 'tool_use')) {
|
||||
for (const block of response.content) {
|
||||
if (block.type === 'tool_use' && block.name === 'memory') {
|
||||
const memoryCommand = block.input as any;
|
||||
const result = await memoryTool.handleCommand(memoryCommand);
|
||||
|
||||
// You would typically send this result back to Claude
|
||||
console.log('Memory operation result:', result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
// await chatWithMemory("Remember that I'm working on a React project with TypeScript");
|
||||
// await chatWithMemory("What programming languages am I using in my current project?");
|
||||
`;
|
||||
|
||||
// =====================================================
|
||||
// Example 4: cURL Commands for Testing
|
||||
// =====================================================
|
||||
|
||||
export const curlExamples = `
|
||||
# Test the memory tool using cURL commands against your supermemory API
|
||||
|
||||
# 1. Create a memory file
|
||||
curl -X POST "https://api.supermemory.ai/v3/documents" \\
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"content": "# My Notes\\n\\nThis is a test note for Claude memory tool.",
|
||||
"customId": "/memories/test-note.md",
|
||||
"containerTags": ["claude_memory", "sm_project_test"],
|
||||
"metadata": {
|
||||
"claude_memory_type": "file",
|
||||
"file_path": "/memories/test-note.md",
|
||||
"line_count": 3,
|
||||
"created_by": "claude_memory_tool"
|
||||
}
|
||||
}'
|
||||
|
||||
# 2. Search/read the memory file
|
||||
curl -X POST "https://api.supermemory.ai/v3/search" \\
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"q": "/memories/test-note.md",
|
||||
"containerTags": ["claude_memory", "sm_project_test"],
|
||||
"limit": 1,
|
||||
"includeFullDocs": true
|
||||
}'
|
||||
|
||||
# 3. List all memory files (directory listing)
|
||||
curl -X POST "https://api.supermemory.ai/v3/search" \\
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"q": "*",
|
||||
"containerTags": ["claude_memory", "sm_project_test"],
|
||||
"limit": 100,
|
||||
"includeFullDocs": false
|
||||
}'
|
||||
|
||||
# 4. Update a memory file (str_replace operation)
|
||||
curl -X PATCH "https://api.supermemory.ai/v3/documents/DOCUMENT_ID" \\
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"content": "# My Updated Notes\\n\\nThis note has been updated using str_replace.",
|
||||
"metadata": {
|
||||
"claude_memory_type": "file",
|
||||
"file_path": "/memories/test-note.md",
|
||||
"line_count": 3,
|
||||
"last_modified": "2025-01-15T10:30:00Z"
|
||||
}
|
||||
}'
|
||||
|
||||
# 5. Delete a memory file
|
||||
curl -X DELETE "https://api.supermemory.ai/v3/documents/DOCUMENT_ID" \\
|
||||
-H "Authorization: Bearer YOUR_API_KEY"
|
||||
`;
|
||||
|
||||
// =====================================================
|
||||
// Main runner function
|
||||
// =====================================================
|
||||
|
||||
export async function runAllExamples() {
|
||||
if (!process.env.SUPERMEMORY_API_KEY) {
|
||||
console.error("❌ SUPERMEMORY_API_KEY environment variable is required");
|
||||
console.log("Set your API key in .env file or environment variable");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await directFetchExample();
|
||||
console.log("\\n" + "=".repeat(70) + "\\n");
|
||||
await anthropicSdkExample();
|
||||
|
||||
console.log("\\n" + "=".repeat(70));
|
||||
console.log("📋 Real Anthropic SDK Integration Template:");
|
||||
console.log(anthropicIntegrationTemplate);
|
||||
|
||||
console.log("\\n" + "=".repeat(70));
|
||||
console.log("🔧 cURL Examples for Direct API Testing:");
|
||||
console.log(curlExamples);
|
||||
|
||||
} catch (error) {
|
||||
console.error("💥 Error running examples:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Run examples if this file is executed directly
|
||||
if (import.meta.main) {
|
||||
runAllExamples();
|
||||
}
|
||||
334
packages/tools/test/claude-memory-real-example.ts
Normal file
334
packages/tools/test/claude-memory-real-example.ts
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
/**
|
||||
* Real Claude Memory Tool Integration Examples
|
||||
*
|
||||
* This shows actual tool call handling based on real Claude API responses
|
||||
*/
|
||||
|
||||
import { createClaudeMemoryTool, type MemoryCommand } from "./claude-memory"
|
||||
|
||||
// =====================================================
|
||||
// Real Claude API Integration
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* Handle actual Claude memory tool calls from the API response
|
||||
*/
|
||||
export async function handleClaudeMemoryToolCall(
|
||||
toolUseBlock: {
|
||||
type: "tool_use"
|
||||
id: string
|
||||
name: "memory"
|
||||
input: MemoryCommand
|
||||
},
|
||||
supermemoryApiKey: string,
|
||||
config?: {
|
||||
projectId?: string
|
||||
memoryContainerTag?: string
|
||||
baseUrl?: string
|
||||
}
|
||||
) {
|
||||
console.log(`🔧 Handling Claude memory tool call: ${toolUseBlock.input.command}`)
|
||||
console.log(`📁 Path: ${toolUseBlock.input.path}`)
|
||||
|
||||
// Initialize memory tool
|
||||
const memoryTool = createClaudeMemoryTool(supermemoryApiKey, {
|
||||
projectId: config?.projectId || "claude-chat",
|
||||
memoryContainerTag: config?.memoryContainerTag || "claude_memory",
|
||||
baseUrl: config?.baseUrl,
|
||||
})
|
||||
|
||||
// Execute the memory command
|
||||
const result = await memoryTool.handleCommand(toolUseBlock.input)
|
||||
|
||||
// Format response for Claude
|
||||
const toolResult = {
|
||||
type: "tool_result" as const,
|
||||
tool_use_id: toolUseBlock.id,
|
||||
content: result.success
|
||||
? result.content || "Operation completed successfully"
|
||||
: `Error: ${result.error}`,
|
||||
is_error: !result.success,
|
||||
}
|
||||
|
||||
console.log(`${result.success ? '✅' : '❌'} Result:`, result.content || result.error)
|
||||
|
||||
return toolResult
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete example with real Claude API call and memory tool handling
|
||||
*/
|
||||
export async function realClaudeMemoryExample() {
|
||||
console.log("🤖 Real Claude Memory Tool Integration")
|
||||
console.log("=" .repeat(50))
|
||||
|
||||
// Your API keys
|
||||
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY
|
||||
const SUPERMEMORY_API_KEY = process.env.SUPERMEMORY_API_KEY
|
||||
|
||||
if (!ANTHROPIC_API_KEY || !SUPERMEMORY_API_KEY) {
|
||||
console.error("❌ Missing API keys:")
|
||||
console.error("- Set ANTHROPIC_API_KEY for Claude")
|
||||
console.error("- Set SUPERMEMORY_API_KEY for Supermemory")
|
||||
return
|
||||
}
|
||||
|
||||
// Step 1: Make initial request to Claude
|
||||
console.log("📤 Making request to Claude API...")
|
||||
|
||||
const initialRequest = {
|
||||
model: "claude-sonnet-4-5",
|
||||
max_tokens: 2048,
|
||||
messages: [{
|
||||
role: "user" as const,
|
||||
content: "I'm working on a Python web scraper that keeps crashing with a timeout error. Here's the problematic function:\\n\\n```python\\ndef fetch_page(url, retries=3):\\n for i in range(retries):\\n try:\\n response = requests.get(url, timeout=5)\\n return response.text\\n except requests.exceptions.Timeout:\\n if i == retries - 1:\\n raise\\n time.sleep(1)\\n```\\n\\nPlease help me debug this."
|
||||
}],
|
||||
tools: [{
|
||||
type: "memory_20250818" as const,
|
||||
name: "memory"
|
||||
}]
|
||||
}
|
||||
|
||||
// Make the API call
|
||||
const claudeResponse = await fetch("https://api.anthropic.com/v1/messages", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"x-api-key": ANTHROPIC_API_KEY,
|
||||
"anthropic-version": "2023-06-01",
|
||||
"content-type": "application/json",
|
||||
"anthropic-beta": "context-management-2025-06-27"
|
||||
},
|
||||
body: JSON.stringify(initialRequest)
|
||||
})
|
||||
|
||||
const responseData = await claudeResponse.json()
|
||||
console.log("📥 Claude's response:")
|
||||
console.log(JSON.stringify(responseData, null, 2))
|
||||
|
||||
// Step 2: Handle any tool calls
|
||||
const toolResults = []
|
||||
|
||||
if (responseData.content) {
|
||||
for (const block of responseData.content) {
|
||||
if (block.type === "tool_use" && block.name === "memory") {
|
||||
console.log(`\\n🔧 Processing memory tool call:`)
|
||||
console.log(`Command: ${block.input.command}`)
|
||||
console.log(`Path: ${block.input.path}`)
|
||||
|
||||
// Handle the memory tool call
|
||||
const toolResult = await handleClaudeMemoryToolCall(
|
||||
block,
|
||||
SUPERMEMORY_API_KEY,
|
||||
{
|
||||
projectId: "python-scraper-help",
|
||||
memoryContainerTag: "claude_memory_debug"
|
||||
}
|
||||
)
|
||||
|
||||
toolResults.push(toolResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Send tool results back to Claude if there were any
|
||||
if (toolResults.length > 0) {
|
||||
console.log("\\n📤 Sending tool results back to Claude...")
|
||||
|
||||
const followUpRequest = {
|
||||
model: "claude-sonnet-4-5",
|
||||
max_tokens: 2048,
|
||||
messages: [
|
||||
...initialRequest.messages,
|
||||
{
|
||||
role: "assistant" as const,
|
||||
content: responseData.content
|
||||
},
|
||||
{
|
||||
role: "user" as const,
|
||||
content: toolResults
|
||||
}
|
||||
],
|
||||
tools: initialRequest.tools
|
||||
}
|
||||
|
||||
const followUpResponse = await fetch("https://api.anthropic.com/v1/messages", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"x-api-key": ANTHROPIC_API_KEY,
|
||||
"anthropic-version": "2023-06-01",
|
||||
"content-type": "application/json",
|
||||
"anthropic-beta": "context-management-2025-06-27"
|
||||
},
|
||||
body: JSON.stringify(followUpRequest)
|
||||
})
|
||||
|
||||
const followUpData = await followUpResponse.json()
|
||||
console.log("📥 Claude's final response:")
|
||||
console.log(JSON.stringify(followUpData, null, 2))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplified function to process Claude tool calls
|
||||
*/
|
||||
export async function processClaudeResponse(
|
||||
claudeResponseData: any,
|
||||
supermemoryApiKey: string,
|
||||
config?: {
|
||||
projectId?: string
|
||||
memoryContainerTag?: string
|
||||
baseUrl?: string
|
||||
}
|
||||
): Promise<any[]> {
|
||||
const toolResults = []
|
||||
|
||||
if (claudeResponseData.content) {
|
||||
for (const block of claudeResponseData.content) {
|
||||
if (block.type === "tool_use" && block.name === "memory") {
|
||||
const toolResult = await handleClaudeMemoryToolCall(
|
||||
block,
|
||||
supermemoryApiKey,
|
||||
config
|
||||
)
|
||||
toolResults.push(toolResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return toolResults
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Express.js / Web Framework Integration Example
|
||||
// =====================================================
|
||||
|
||||
export const webIntegrationExample = `
|
||||
// Example: Express.js endpoint that handles Claude memory tool calls
|
||||
|
||||
import express from 'express';
|
||||
import { processClaudeResponse } from './claude-memory-real-example';
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/chat-with-memory', async (req, res) => {
|
||||
const { message, conversationId } = req.body;
|
||||
|
||||
try {
|
||||
// 1. Send message to Claude
|
||||
const claudeResponse = await fetch('https://api.anthropic.com/v1/messages', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-api-key': process.env.ANTHROPIC_API_KEY,
|
||||
'anthropic-version': '2023-06-01',
|
||||
'content-type': 'application/json',
|
||||
'anthropic-beta': 'context-management-2025-06-27'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'claude-sonnet-4-5',
|
||||
max_tokens: 2048,
|
||||
messages: [{ role: 'user', content: message }],
|
||||
tools: [{ type: 'memory_20250818', name: 'memory' }]
|
||||
})
|
||||
});
|
||||
|
||||
const claudeData = await claudeResponse.json();
|
||||
|
||||
// 2. Handle any memory tool calls
|
||||
const toolResults = await processClaudeResponse(
|
||||
claudeData,
|
||||
process.env.SUPERMEMORY_API_KEY!,
|
||||
{
|
||||
projectId: conversationId || 'default-chat',
|
||||
memoryContainerTag: 'claude_memory_chat'
|
||||
}
|
||||
);
|
||||
|
||||
// 3. Send tool results back to Claude if needed
|
||||
if (toolResults.length > 0) {
|
||||
const followUpResponse = await fetch('https://api.anthropic.com/v1/messages', {
|
||||
// ... send tool results back to Claude
|
||||
});
|
||||
const finalData = await followUpResponse.json();
|
||||
res.json({ response: finalData, memoryOperations: toolResults.length });
|
||||
} else {
|
||||
res.json({ response: claudeData, memoryOperations: 0 });
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: 'Failed to process chat with memory' });
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
// =====================================================
|
||||
// Test with actual tool call from your example
|
||||
// =====================================================
|
||||
|
||||
export async function testWithRealToolCall() {
|
||||
console.log("🧪 Testing with Real Tool Call from Your Example")
|
||||
console.log("=" .repeat(50))
|
||||
|
||||
// This is the actual tool call Claude made in your example
|
||||
const realToolCall = {
|
||||
type: "tool_use" as const,
|
||||
id: "toolu_01BjWuUZXUfie6ey5Vz3xvth",
|
||||
name: "memory" as const,
|
||||
input: {
|
||||
command: "view" as const,
|
||||
path: "/memories"
|
||||
}
|
||||
}
|
||||
|
||||
console.log("🔍 Tool call from Claude:")
|
||||
console.log(JSON.stringify(realToolCall, null, 2))
|
||||
|
||||
if (!process.env.SUPERMEMORY_API_KEY) {
|
||||
console.error("❌ SUPERMEMORY_API_KEY required for testing")
|
||||
return
|
||||
}
|
||||
|
||||
// Process the tool call
|
||||
const result = await handleClaudeMemoryToolCall(
|
||||
realToolCall,
|
||||
process.env.SUPERMEMORY_API_KEY,
|
||||
{
|
||||
projectId: "python-scraper-debug",
|
||||
memoryContainerTag: "claude_memory_test"
|
||||
}
|
||||
)
|
||||
|
||||
console.log("\\n📋 Tool Result to send back to Claude:")
|
||||
console.log(JSON.stringify(result, null, 2))
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Main runner
|
||||
// =====================================================
|
||||
|
||||
export async function runRealExamples() {
|
||||
console.log("🚀 Running Real Claude Memory Tool Examples")
|
||||
console.log("=" .repeat(70))
|
||||
|
||||
// Test with the actual tool call first
|
||||
await testWithRealToolCall()
|
||||
|
||||
console.log("\\n" + "=".repeat(70) + "\\n")
|
||||
|
||||
// Show web integration example
|
||||
console.log("🌐 Web Framework Integration Example:")
|
||||
console.log(webIntegrationExample)
|
||||
|
||||
// Only run full API example if both keys are present
|
||||
if (process.env.ANTHROPIC_API_KEY && process.env.SUPERMEMORY_API_KEY) {
|
||||
console.log("\\n" + "=".repeat(70) + "\\n")
|
||||
await realClaudeMemoryExample()
|
||||
} else {
|
||||
console.log("\\n⚠️ Set ANTHROPIC_API_KEY and SUPERMEMORY_API_KEY to run full API example")
|
||||
}
|
||||
}
|
||||
|
||||
// Run if executed directly
|
||||
if (import.meta.main) {
|
||||
runRealExamples()
|
||||
}
|
||||
449
packages/tools/test/claude-memory.test.ts
Normal file
449
packages/tools/test/claude-memory.test.ts
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
import { describe, it, expect, beforeEach } from "vitest"
|
||||
import { createClaudeMemoryTool, type MemoryCommand } from "./claude-memory"
|
||||
import "dotenv/config"
|
||||
|
||||
// Test configuration
|
||||
const TEST_CONFIG = {
|
||||
apiKey: process.env.SUPERMEMORY_API_KEY || "test-api-key",
|
||||
baseUrl: process.env.SUPERMEMORY_BASE_URL,
|
||||
projectId: "test-claude-memory",
|
||||
memoryContainerTag: "claude_memory_test",
|
||||
}
|
||||
|
||||
describe("Claude Memory Tool", () => {
|
||||
let memoryTool: ReturnType<typeof createClaudeMemoryTool>
|
||||
|
||||
beforeEach(() => {
|
||||
memoryTool = createClaudeMemoryTool(TEST_CONFIG.apiKey, {
|
||||
projectId: TEST_CONFIG.projectId,
|
||||
memoryContainerTag: TEST_CONFIG.memoryContainerTag,
|
||||
baseUrl: TEST_CONFIG.baseUrl,
|
||||
})
|
||||
})
|
||||
|
||||
describe("Path validation", () => {
|
||||
it("should reject invalid paths", async () => {
|
||||
const invalidPaths = [
|
||||
"/etc/passwd",
|
||||
"/home/user/file.txt",
|
||||
"../../../secrets.txt",
|
||||
"/memories/../../../secrets.txt",
|
||||
]
|
||||
|
||||
for (const path of invalidPaths) {
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path,
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error).toContain("Invalid path")
|
||||
}
|
||||
})
|
||||
|
||||
it("should accept valid memory paths", async () => {
|
||||
const validPaths = [
|
||||
"/memories/",
|
||||
"/memories/notes.txt",
|
||||
"/memories/project/ideas.md",
|
||||
"/memories/deep/nested/path/file.txt",
|
||||
]
|
||||
|
||||
// These should not fail due to path validation (though they might fail for other reasons like file not found)
|
||||
for (const path of validPaths) {
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path,
|
||||
})
|
||||
// Should not fail with "Invalid path" error
|
||||
if (!result.success) {
|
||||
expect(result.error).not.toContain("Invalid path")
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("File operations", () => {
|
||||
const testFilePath = "/memories/test-file.txt"
|
||||
const testContent = "Hello, World!\nThis is a test file.\nLine 3 here."
|
||||
|
||||
it("should create a file", async () => {
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: testFilePath,
|
||||
file_text: testContent,
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.content).toContain("File created")
|
||||
})
|
||||
|
||||
it("should read a file", async () => {
|
||||
// First create the file
|
||||
await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: testFilePath,
|
||||
file_text: testContent,
|
||||
})
|
||||
|
||||
// Then read it
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path: testFilePath,
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.content).toContain("Hello, World!")
|
||||
expect(result.content).toContain("This is a test file.")
|
||||
// Should include line numbers
|
||||
expect(result.content).toMatch(/\s*1\s+Hello, World!/)
|
||||
})
|
||||
|
||||
it("should read file with line range", async () => {
|
||||
// First create the file
|
||||
await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: testFilePath,
|
||||
file_text: testContent,
|
||||
})
|
||||
|
||||
// Read only lines 1-2
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path: testFilePath,
|
||||
view_range: [1, 2],
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.content).toContain("Hello, World!")
|
||||
expect(result.content).toContain("This is a test file.")
|
||||
expect(result.content).not.toContain("Line 3 here.")
|
||||
})
|
||||
|
||||
it("should replace string in file", async () => {
|
||||
// First create the file
|
||||
await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: testFilePath,
|
||||
file_text: testContent,
|
||||
})
|
||||
|
||||
// Replace text
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "str_replace",
|
||||
path: testFilePath,
|
||||
old_str: "Hello, World!",
|
||||
new_str: "Greetings, Universe!",
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
// Verify the change
|
||||
const readResult = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path: testFilePath,
|
||||
})
|
||||
expect(readResult.content).toContain("Greetings, Universe!")
|
||||
expect(readResult.content).not.toContain("Hello, World!")
|
||||
})
|
||||
|
||||
it("should insert text at specific line", async () => {
|
||||
// First create the file
|
||||
await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: testFilePath,
|
||||
file_text: testContent,
|
||||
})
|
||||
|
||||
// Insert text at line 2
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "insert",
|
||||
path: testFilePath,
|
||||
insert_line: 2,
|
||||
insert_text: "This is an inserted line.",
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
// Verify the insertion
|
||||
const readResult = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path: testFilePath,
|
||||
})
|
||||
expect(readResult.content).toContain("This is an inserted line.")
|
||||
})
|
||||
|
||||
it("should rename/move file", async () => {
|
||||
const oldPath = "/memories/old-name.txt"
|
||||
const newPath = "/memories/new-name.txt"
|
||||
|
||||
// First create the file
|
||||
await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: oldPath,
|
||||
file_text: testContent,
|
||||
})
|
||||
|
||||
// Rename it
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "rename",
|
||||
path: oldPath,
|
||||
new_path: newPath,
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
// Verify the file exists at new location
|
||||
const readResult = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path: newPath,
|
||||
})
|
||||
expect(readResult.success).toBe(true)
|
||||
expect(readResult.content).toContain("Hello, World!")
|
||||
})
|
||||
|
||||
it("should delete file", async () => {
|
||||
// First create the file
|
||||
await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: testFilePath,
|
||||
file_text: testContent,
|
||||
})
|
||||
|
||||
// Delete it
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "delete",
|
||||
path: testFilePath,
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Directory operations", () => {
|
||||
it("should list empty directory", async () => {
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path: "/memories/",
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.content).toContain("Directory: /memories/")
|
||||
})
|
||||
|
||||
it("should list directory with files", async () => {
|
||||
// Create some test files
|
||||
await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: "/memories/file1.txt",
|
||||
file_text: "Content 1",
|
||||
})
|
||||
|
||||
await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: "/memories/file2.md",
|
||||
file_text: "Content 2",
|
||||
})
|
||||
|
||||
await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: "/memories/subdir/file3.txt",
|
||||
file_text: "Content 3",
|
||||
})
|
||||
|
||||
// List root directory
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path: "/memories/",
|
||||
})
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.content).toContain("file1.txt")
|
||||
expect(result.content).toContain("file2.md")
|
||||
expect(result.content).toContain("subdir/")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Error handling", () => {
|
||||
it("should handle missing file", async () => {
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "view",
|
||||
path: "/memories/nonexistent.txt",
|
||||
})
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error).toContain("File not found")
|
||||
})
|
||||
|
||||
it("should handle missing parameters", async () => {
|
||||
const commands: MemoryCommand[] = [
|
||||
{ command: "create", path: "/memories/test.txt" }, // Missing file_text
|
||||
{ command: "str_replace", path: "/memories/test.txt", old_str: "old" }, // Missing new_str
|
||||
{ command: "insert", path: "/memories/test.txt", insert_line: 1 }, // Missing insert_text
|
||||
{ command: "rename", path: "/memories/test.txt" }, // Missing new_path
|
||||
]
|
||||
|
||||
for (const cmd of commands) {
|
||||
const result = await memoryTool.handleCommand(cmd)
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error).toContain("required")
|
||||
}
|
||||
})
|
||||
|
||||
it("should handle string not found in str_replace", async () => {
|
||||
// Create a file
|
||||
await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: "/memories/test.txt",
|
||||
file_text: "Some content here",
|
||||
})
|
||||
|
||||
// Try to replace non-existent string
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "str_replace",
|
||||
path: "/memories/test.txt",
|
||||
old_str: "This string does not exist",
|
||||
new_str: "replacement",
|
||||
})
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error).toContain("String not found")
|
||||
})
|
||||
|
||||
it("should handle invalid line number for insert", async () => {
|
||||
// Create a 3-line file
|
||||
await memoryTool.handleCommand({
|
||||
command: "create",
|
||||
path: "/memories/test.txt",
|
||||
file_text: "Line 1\nLine 2\nLine 3",
|
||||
})
|
||||
|
||||
// Try to insert at invalid line number
|
||||
const result = await memoryTool.handleCommand({
|
||||
command: "insert",
|
||||
path: "/memories/test.txt",
|
||||
insert_line: 10, // Way beyond file length
|
||||
insert_text: "New line",
|
||||
})
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error).toContain("Invalid line number")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Manual test runner - run this directly to test the memory tool
|
||||
* Usage: bun run src/claude-memory.test.ts
|
||||
*/
|
||||
async function runManualTests() {
|
||||
console.log("🧪 Running Claude Memory Tool Manual Tests")
|
||||
console.log("==========================================")
|
||||
|
||||
if (!process.env.SUPERMEMORY_API_KEY) {
|
||||
console.error("❌ SUPERMEMORY_API_KEY environment variable is required")
|
||||
console.log("Set your API key in .env file or environment variable")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY, {
|
||||
projectId: "manual-test-project",
|
||||
memoryContainerTag: "claude_memory_manual_test",
|
||||
baseUrl: process.env.SUPERMEMORY_BASE_URL,
|
||||
})
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
name: "Create a test file",
|
||||
command: {
|
||||
command: "create" as const,
|
||||
path: "/memories/manual-test.md",
|
||||
file_text: "# Manual Test File\n\nThis is a test file for manual testing.\n\n- Item 1\n- Item 2\n- Item 3",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Read the test file",
|
||||
command: {
|
||||
command: "view" as const,
|
||||
path: "/memories/manual-test.md",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Read specific lines",
|
||||
command: {
|
||||
command: "view" as const,
|
||||
path: "/memories/manual-test.md",
|
||||
view_range: [1, 3] as [number, number],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Replace text in file",
|
||||
command: {
|
||||
command: "str_replace" as const,
|
||||
path: "/memories/manual-test.md",
|
||||
old_str: "Manual Test File",
|
||||
new_str: "Updated Manual Test File",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Insert text at line 4",
|
||||
command: {
|
||||
command: "insert" as const,
|
||||
path: "/memories/manual-test.md",
|
||||
insert_line: 4,
|
||||
insert_text: "This line was inserted!",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "List directory contents",
|
||||
command: {
|
||||
command: "view" as const,
|
||||
path: "/memories/",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Rename the file",
|
||||
command: {
|
||||
command: "rename" as const,
|
||||
path: "/memories/manual-test.md",
|
||||
new_path: "/memories/renamed-manual-test.md",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test invalid path (should fail)",
|
||||
command: {
|
||||
command: "view" as const,
|
||||
path: "/etc/passwd",
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
for (const testCase of testCases) {
|
||||
console.log(`\n🔄 ${testCase.name}`)
|
||||
console.log(`Command: ${JSON.stringify(testCase.command, null, 2)}`)
|
||||
|
||||
try {
|
||||
const result = await memoryTool.handleCommand(testCase.command)
|
||||
|
||||
if (result.success) {
|
||||
console.log("✅ Success")
|
||||
if (result.content) {
|
||||
console.log("📄 Content:")
|
||||
console.log(result.content)
|
||||
}
|
||||
} else {
|
||||
console.log("❌ Failed")
|
||||
console.log("Error:", result.error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("💥 Exception:", error)
|
||||
}
|
||||
}
|
||||
|
||||
console.log("\n✨ Manual tests completed!")
|
||||
console.log("Check your supermemory instance to verify the memory files were created correctly.")
|
||||
}
|
||||
|
||||
// If this file is run directly, execute manual tests
|
||||
if (import.meta.main) {
|
||||
runManualTests()
|
||||
}
|
||||
187
packages/tools/test/test-memory-tool.ts
Normal file
187
packages/tools/test/test-memory-tool.ts
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Manual test script for Claude Memory Tool
|
||||
* Run with: bun run src/test-memory-tool.ts
|
||||
*/
|
||||
|
||||
import { createClaudeMemoryTool, type MemoryCommand } from "./claude-memory"
|
||||
import "dotenv/config"
|
||||
|
||||
async function testMemoryTool() {
|
||||
console.log("🧪 Testing Claude Memory Tool Operations")
|
||||
console.log("=" .repeat(50))
|
||||
|
||||
if (!process.env.SUPERMEMORY_API_KEY) {
|
||||
console.error("❌ SUPERMEMORY_API_KEY environment variable is required")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const memoryTool = createClaudeMemoryTool(process.env.SUPERMEMORY_API_KEY, {
|
||||
projectId: "memory-tool-test",
|
||||
memoryContainerTag: "claude_memory_test",
|
||||
baseUrl: process.env.SUPERMEMORY_BASE_URL,
|
||||
})
|
||||
|
||||
const testCases: Array<{
|
||||
name: string
|
||||
command: MemoryCommand
|
||||
expectSuccess: boolean
|
||||
}> = [
|
||||
{
|
||||
name: "Check empty memory directory",
|
||||
command: { command: "view", path: "/memories/" },
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Create a project notes file",
|
||||
command: {
|
||||
command: "create",
|
||||
path: "/memories/project-notes.md",
|
||||
file_text: "# Project Notes\\n\\n## Meeting Notes\\n- Discussed requirements\\n- Set timeline\\n- Assigned tasks\\n\\n## Technical Stack\\n- Frontend: React\\n- Backend: Node.js\\n- Database: PostgreSQL",
|
||||
},
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Create a todo list",
|
||||
command: {
|
||||
command: "create",
|
||||
path: "/memories/todo.txt",
|
||||
file_text: "TODO List:\\n1. Set up development environment\\n2. Create database schema\\n3. Build authentication system\\n4. Implement user dashboard\\n5. Write documentation",
|
||||
},
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "List directory contents (should show 2 files)",
|
||||
command: { command: "view", path: "/memories/" },
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Read project notes with line numbers",
|
||||
command: { command: "view", path: "/memories/project-notes.md" },
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Read specific lines from todo list",
|
||||
command: {
|
||||
command: "view",
|
||||
path: "/memories/todo.txt",
|
||||
view_range: [1, 3],
|
||||
},
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Replace text in project notes",
|
||||
command: {
|
||||
command: "str_replace",
|
||||
path: "/memories/project-notes.md",
|
||||
old_str: "Node.js",
|
||||
new_str: "Express.js",
|
||||
},
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Insert new item in todo list at line 3",
|
||||
command: {
|
||||
command: "insert",
|
||||
path: "/memories/todo.txt",
|
||||
insert_line: 3,
|
||||
insert_text: "2.5. Design database relationships",
|
||||
},
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Read updated todo list",
|
||||
command: { command: "view", path: "/memories/todo.txt" },
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Create a personal notes file",
|
||||
command: {
|
||||
command: "create",
|
||||
path: "/memories/personal/preferences.md",
|
||||
file_text: "# My Preferences\\n\\n- Prefers React over Vue\\n- Uses TypeScript for type safety\\n- Likes clean, readable code\\n- Prefers functional programming style",
|
||||
},
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "List root directory (should show files and personal/ subdirectory)",
|
||||
command: { command: "view", path: "/memories/" },
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Rename project notes",
|
||||
command: {
|
||||
command: "rename",
|
||||
path: "/memories/project-notes.md",
|
||||
new_path: "/memories/project-meeting-notes.md",
|
||||
},
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "List directory after rename",
|
||||
command: { command: "view", path: "/memories/" },
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Test invalid path (should fail)",
|
||||
command: { command: "view", path: "/etc/passwd" },
|
||||
expectSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "Test file not found (should fail)",
|
||||
command: { command: "view", path: "/memories/nonexistent.txt" },
|
||||
expectSuccess: false,
|
||||
},
|
||||
]
|
||||
|
||||
let passed = 0
|
||||
let failed = 0
|
||||
|
||||
for (let i = 0; i < testCases.length; i++) {
|
||||
const testCase = testCases[i]
|
||||
console.log(`\\n🔄 Test ${i + 1}/${testCases.length}: ${testCase.name}`)
|
||||
|
||||
try {
|
||||
const result = await memoryTool.handleCommand(testCase.command)
|
||||
|
||||
if (result.success === testCase.expectSuccess) {
|
||||
console.log("✅ PASS")
|
||||
if (result.content && result.content.length < 500) {
|
||||
console.log("📄 Result:")
|
||||
console.log(result.content)
|
||||
} else if (result.content) {
|
||||
console.log(`📄 Result: ${result.content.substring(0, 100)}... (truncated)`)
|
||||
}
|
||||
passed++
|
||||
} else {
|
||||
console.log("❌ FAIL")
|
||||
console.log(`Expected success: ${testCase.expectSuccess}, got: ${result.success}`)
|
||||
if (result.error) {
|
||||
console.log(`Error: ${result.error}`)
|
||||
}
|
||||
failed++
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("💥 ERROR")
|
||||
console.log(`Exception: ${error}`)
|
||||
failed++
|
||||
}
|
||||
|
||||
// Add a small delay to avoid rate limiting
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
}
|
||||
|
||||
console.log(`\\n📊 Test Results:`)
|
||||
console.log(`✅ Passed: ${passed}`)
|
||||
console.log(`❌ Failed: ${failed}`)
|
||||
console.log(`📈 Success Rate: ${((passed / (passed + failed)) * 100).toFixed(1)}%`)
|
||||
|
||||
if (failed === 0) {
|
||||
console.log("\\n🎉 All tests passed! Claude Memory Tool is working perfectly!")
|
||||
} else {
|
||||
console.log(`\\n⚠️ ${failed} test(s) failed. Check the errors above.`)
|
||||
}
|
||||
}
|
||||
|
||||
// Run the tests
|
||||
testMemoryTool().catch(console.error)
|
||||
|
|
@ -1,7 +1,12 @@
|
|||
import { defineConfig } from "tsdown"
|
||||
|
||||
export default defineConfig({
|
||||
entry: ["src/index.ts", "src/ai-sdk.ts", "src/openai.ts"],
|
||||
entry: [
|
||||
"src/index.ts",
|
||||
"src/ai-sdk.ts",
|
||||
"src/openai.ts",
|
||||
"src/claude-memory.ts",
|
||||
],
|
||||
format: "esm",
|
||||
sourcemap: false,
|
||||
target: "es2020",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue