Add AI SDK Utilities (#375)

### TL;DR

Upgraded Biome to v2.2.0 and added a new AI SDK package for supermemory integration.

### What changed?

- Updated Biome configuration with enhanced linting rules and formatting options
- Upgraded `@biomejs/biome` from v2.1.3 to v2.2.0
- Added a new `@supermemory/ai-sdk` package with:
    - Infinite Chat Provider for connecting to various LLM providers with unlimited context
    - Memory Tools for searching, adding, and fetching memories from supermemory
    - Comprehensive test suite for both features
    - Detailed README with usage examples and configuration options
- Removed unused ESLint and TypeScript configuration packages

### How to test?

1. Run `bun install` to update dependencies
2. Test the new AI SDK:
3. Verify Biome works with the updated configuration:

### Why make this change?

The new AI SDK package provides integration with Vercel's AI SDK, allowing developers to use supermemory's context management with various LLM providers. This enables unlimited context support and memory tools for AI agents. The Biome upgrade brings improved linting and formatting capabilities, enhancing code quality across the project.
This commit is contained in:
Shreyans Jain 2025-08-21 22:39:07 +05:30 • committed by GitHub
parent 9e1e392a2b
commit 8ff3c75847
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1183 additions and 250 deletions

View file

@ -1,75 +1,105 @@
{
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
"assist": {
"actions": {
"source": {
"organizeImports": "on",
"useSortedAttributes": "on",
"useSortedKeys": "off"
}
},
"enabled": true
},
"files": {
"includes": [
"**/*.js",
"**/*.jsx",
"**/*.ts",
"**/*.tsx",
"**/*.json",
"**/*.jsonc",
"**",
"!**/node_modules",
"!**/.next/",
"!**/.contentlayer",
"!**/.vercel",
"!**/.react-router",
"!**/.wrangler",
"!**/package.json",
"!**/worker-configuration.d.ts",
"!**/.turbo/",
"!**/.vercel",
"!**/dist",
"!**/.astro",
"!**/build",
"!**/.next",
"!**/.turbo",
"!**/*.d.ts"
"!**/.alchemy",
"!**/.build",
"!**/.open-next",
"!**/*.astro"
]
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"indentStyle": "tab",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 80,
"attributePosition": "auto",
"includes": ["**", "!**/*.md"]
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"recommended": true
},
"complexity": {
"recommended": true
},
"correctness": {
"recommended": true
},
"performance": {
"recommended": true
},
"security": {
"recommended": true
},
"style": {
"recommended": true
},
"suspicious": {
"recommended": true
}
}
"indentStyle": "tab"
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"jsxQuoteStyle": "double",
"trailingCommas": "all",
"semicolons": "asNeeded",
"arrowParentheses": "always",
"bracketSpacing": true,
"bracketSameLine": false,
"quoteProperties": "asNeeded",
"attributePosition": "auto"
"semicolons": "asNeeded"
}
},
"json": {
"formatter": {
"trailingCommas": "none"
"linter": {
"domains": {
"project": "none"
},
"enabled": true,
"rules": {
"a11y": {
"useValidAnchor": "warn"
},
"correctness": {
"noUnusedVariables": {
"level": "warn",
"options": {
"ignoreRestSiblings": true
}
},
"useExhaustiveDependencies": "warn",
"noUnusedImports": "warn"
},
"recommended": true,
"style": {
"noDefaultExport": "off",
"noInferrableTypes": "error",
"noNonNullAssertion": "warn",
"noParameterAssign": "error",
"noUnusedTemplateLiteral": "error",
"noUselessElse": "error",
"useAsConstAssertion": "error",
"useDefaultParameterLast": "error",
"useEnumInitializers": "error",
"useNamingConvention": {
"level": "off",
"options": {
"strictCase": false
}
},
"useNumberNamespace": "error",
"useSelfClosingElements": "error",
"useSingleVarDeclarator": "error"
}
}
},
"overrides": [
{
"includes": ["**/*.astro"],
"linter": {
"rules": {
"correctness": {
"noUnusedVariables": "off",
"noUnusedImports": "off"
}
}
}
}
],
"vcs": {
"clientKind": "git",
"enabled": true,
"useIgnoreFile": true
}
}

BIN
bun.lockb

Binary file not shown.

View file

@ -50,7 +50,7 @@
"zod-openapi": "^4.2.4"
},
"devDependencies": {
"@biomejs/biome": "^2.1.3",
"@biomejs/biome": "^2.2.0",
"@total-typescript/tsconfig": "^1.0.4",
"@types/pg": "^8.15.4",
"drizzle-kit": "^0.31.4",

313
packages/ai-sdk/README.md Normal file
View file

@ -0,0 +1,313 @@
# supermemory AI SDK Utilities
Vercel AI SDK utilities for supermemory
## Installation
```bash
npm install @supermemory/ai-sdk
# or
bun add @supermemory/ai-sdk
# or
pnpm add @supermemory/ai-sdk
# or
yarn add @supermemory/ai-sdk
```
## Features
Choose **one** of the following approaches (they cannot be used together):
- **Infinite Chat Provider**: Connect to various LLM providers with unlimited context support
- **Memory Tools**: Search, add, and fetch memories from supermemory using AI agents
## Infinite Chat Provider
The infinite chat provider allows you to connect to various LLM providers with supermemory's context management.
```typescript
import { createSupermemoryInfiniteChat } from '@supermemory/ai-sdk'
import { generateText } from 'ai'
// Using a named provider
const supermemory = createSupermemoryInfiniteChat('your-supermemory-api-key', {
providerName: 'openai',
providerApiKey: 'your-openai-api-key',
headers: {
// Optional additional headers
}
})
// Using a custom provider URL
const supermemory = createSupermemoryInfiniteChat('your-supermemory-api-key', {
providerUrl: 'https://your-custom-provider.com/v1/chat/completions',
providerApiKey: 'your-provider-api-key',
headers: {
// Optional additional headers
}
})
const result = await generateText({
model: supermemory('gpt-4-turbo'),
messages: [
{ role: 'user', content: 'Hello, how are you?' }
]
})
```
### Complete Infinite Chat Example
```typescript
import { createSupermemoryInfiniteChat } from '@supermemory/ai-sdk'
import { generateText } from 'ai'
const supermemoryApiKey = process.env.SUPERMEMORY_API_KEY!
const openaiApiKey = process.env.OPENAI_API_KEY!
// Initialize infinite chat provider
const supermemory = createSupermemoryInfiniteChat(supermemoryApiKey, {
providerName: 'openai',
providerApiKey: openaiApiKey,
headers: {}
})
async function chat(userMessage: string) {
const result = await generateText({
model: supermemory('gpt-4-turbo'),
messages: [
{
role: 'system',
content: 'You are a helpful assistant with unlimited context.'
},
{
role: 'user',
content: userMessage
}
]
// No tools - infinite chat handles context automatically
})
return result.text
}
```
### Configuration
```typescript
// Option 1: Use a named provider
interface ConfigWithProviderName {
providerName: 'openai' | 'anthropic' | 'openrouter' | 'deepinfra' | 'groq' | 'google' | 'cloudflare'
providerApiKey: string
headers?: Record<string, string>
}
// Option 2: Use a custom provider URL
interface ConfigWithProviderUrl {
providerUrl: string
providerApiKey: string
headers?: Record<string, string>
}
```
## Memory Tools
supermemory tools allow AI agents to interact with user memories for enhanced context and personalization.
```typescript
import { supermemoryTools } from '@supermemory/ai-sdk'
import { generateText } from 'ai'
const result = await generateText({
model: openai('gpt-4-turbo'),
messages: [
{ role: 'user', content: 'What do you remember about my preferences?' }
],
tools: {
...supermemoryTools('your-supermemory-api-key', {
// Optional: specify a base URL for self-hosted instances
baseUrl: 'https://api.supermemory.com',
// Use either projectId OR containerTags, not both
projectId: 'your-project-id',
// OR
containerTags: ['tag1', 'tag2']
}),
// Your other tools go here
}
})
```
### Complete Memory Tools Example
```typescript
import { supermemoryTools } from '@supermemory/ai-sdk'
import { generateText } from 'ai'
import { openai } from '@ai-sdk/openai'
const supermemoryApiKey = process.env.SUPERMEMORY_API_KEY!
async function chatWithTools(userMessage: string) {
const result = await generateText({
model: openai('gpt-4-turbo'), // Use standard provider
messages: [
{
role: 'system',
content: 'You are a helpful assistant with access to user memories.'
},
{
role: 'user',
content: userMessage
}
],
tools: {
...supermemoryTools(supermemoryApiKey, {
projectId: 'my-project'
})
},
maxToolRoundtrips: 5
})
return result.text
}
```
### Configuration
```typescript
interface SupermemoryConfig {
// Optional: Base URL for API calls (default: https://api.supermemory.com)
baseUrl?: string
// Container tags for organizing memories (cannot be used with projectId)
containerTags?: string[]
// Project ID for scoping memories (cannot be used with containerTags)
projectId?: string
}
```
### Self-Hosted supermemory
If you're running a self-hosted supermemory instance:
```typescript
const tools = supermemoryTools('your-api-key', {
baseUrl: 'https://your-supermemory-instance.com',
containerTags: ['production', 'user-memories']
})
```
### Available Tools
##### Search Memories
Search through user memories using semantic matching.
```typescript
const searchResult = await tools.searchMemories.execute({
informationToGet: 'user preferences about coffee'
})
```
##### Add Memory
Add new memories to the user's memory store.
```typescript
const addResult = await tools.addMemory.execute({
memory: 'User prefers dark roast coffee in the morning'
})
```
##### Fetch Memory
Retrieve a specific memory by its ID.
```typescript
const fetchResult = await tools.fetchMemory.execute({
memoryId: 'memory-id-123'
})
```
### Using Individual Tools
For more flexibility, you can import and use individual tools:
```typescript
import {
searchMemoriesTool,
addMemoryTool,
fetchMemoryTool
} from '@supermemory/ai-sdk'
const searchTool = searchMemoriesTool('your-api-key', {
projectId: 'your-project-id'
})
// Use only the search tool
const result = await generateText({
model: openai('gpt-4-turbo'),
messages: [...],
tools: {
searchMemories: searchTool
}
})
```
### Error Handling
All tool executions return a result object with a `success` field:
```typescript
const result = await tools.searchMemories.execute({
informationToGet: 'user preferences'
})
if (result.success) {
console.log('Found memories:', result.results)
console.log('Total count:', result.count)
} else {
console.error('Error searching memories:', result.error)
}
```
## Development
### Running Tests
```bash
# Run all tests
bun test
# Run tests in watch mode
bun test --watch
```
#### Environment Variables for Tests
All tests require API keys to run. Copy `.env.example` to `.env` and set the required values:
```bash
cp .env.example .env
```
**Required:**
- `SUPERMEMORY_API_KEY`: Your Supermemory API key
- `PROVIDER_API_KEY`: Your AI provider API key (OpenAI, Anthropic, etc.)
- `OPENAI_API_KEY`: Your OpenAI API key for tool integration tests
**Optional:**
- `SUPERMEMORY_BASE_URL`: Custom Supermemory base URL (defaults to `https://api.supermemory.ai`)
- `PROVIDER_NAME`: Provider name (defaults to `openai`) - one of: `openai`, `anthropic`, `openrouter`, `deepinfra`, `groq`, `google`, `cloudflare`
- `PROVIDER_URL`: Custom provider URL (use instead of `PROVIDER_NAME`)
- `MODEL_NAME`: Model to use in tests (defaults to `gpt-3.5-turbo`)
Tests will fail if required API keys are not provided.
## License
MIT
## Support
Email our [24/7 Founder/CEO/Support Executive](dhravya@supermemory.com)

View file

@ -0,0 +1,26 @@
{
"name": "@supermemory/ai-sdk",
"version": "0.0.1",
"main": "dist/index.mjs",
"types": "dist/index.d.mts",
"scripts": {
"build": "tsdown",
"dev": "tsdown --watch",
"check-types": "tsc --noEmit",
"test": "vitest",
"test:watch": "vitest --watch"
},
"dependencies": {
"@ai-sdk/openai": "^2.0.15",
"ai": "^5.0.17",
"supermemory": "^3.0.0-alpha.22",
"zod": "^4.0.17"
},
"devDependencies": {
"@total-typescript/tsconfig": "^1.0.4",
"@types/bun": "^1.2.20",
"tsdown": "^0.14.1",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
}
}

View file

@ -0,0 +1,2 @@
export * from "./infinite-chat"
export * from "./tools"

View file

@ -0,0 +1,365 @@
import { generateText } from "ai"
import { describe, expect, it } from "vitest"
import z from "zod"
import {
createSupermemoryInfiniteChat,
type SupermemoryInfiniteChatConfig,
} from "./infinite-chat"
import "dotenv/config"
const providers = z.enum([
"openai",
"anthropic",
"openrouter",
"deepinfra",
"groq",
"google",
"cloudflare",
] satisfies SupermemoryInfiniteChatConfig["providerName"][])
describe("createSupermemoryInfiniteChat", () => {
// Required API keys - tests will fail if not provided
const testApiKey = process.env.SUPERMEMORY_API_KEY
const testProviderApiKey = process.env.PROVIDER_API_KEY
if (!testApiKey) {
throw new Error(
"SUPERMEMORY_API_KEY environment variable is required for tests",
)
}
if (!testProviderApiKey) {
throw new Error(
"PROVIDER_API_KEY environment variable is required for tests",
)
}
// Optional configuration with defaults
const testProviderName = providers.parse(
process.env.PROVIDER_NAME ?? "openai",
)
const testProviderUrl = process.env.PROVIDER_URL
const testModelName = process.env.MODEL_NAME || "gpt-5-mini"
const testHeaders = { "custom-header": "test-value" }
// Validate provider configuration - either name OR URL, not both
if (testProviderUrl && process.env.PROVIDER_NAME) {
throw new Error(
"Cannot specify both PROVIDER_NAME and PROVIDER_URL - use one or the other",
)
}
// Test prompts and inputs
const testPrompts = [
"Hello, how are you?",
"What is 2 + 2?",
"Write a short poem about AI",
"Explain quantum computing in simple terms",
"What can you help me with today?",
]
const testMessages = [
[{ role: "user" as const, content: "Hello!" }],
[
{ role: "system" as const, content: "You are a helpful assistant." },
{ role: "user" as const, content: "What is AI?" },
],
[
{ role: "user" as const, content: "Tell me a joke" },
{
role: "assistant" as const,
content:
"Why don't scientists trust atoms? Because they make up everything!",
},
{ role: "user" as const, content: "Tell me another one" },
],
]
describe("client creation", () => {
it("should create client with configured provider", () => {
const config: SupermemoryInfiniteChatConfig = testProviderUrl
? {
providerUrl: testProviderUrl,
providerApiKey: testProviderApiKey,
headers: testHeaders,
}
: {
providerName: testProviderName,
providerApiKey: testProviderApiKey,
headers: testHeaders,
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
expect(client).toBeDefined()
expect(typeof client).toBe("function")
})
it("should create client with openai provider configuration", () => {
const config: SupermemoryInfiniteChatConfig = {
providerName: "openai",
providerApiKey: testProviderApiKey,
headers: testHeaders,
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
expect(client).toBeDefined()
expect(typeof client).toBe("function")
})
it("should create client with anthropic provider configuration", () => {
const config: SupermemoryInfiniteChatConfig = {
providerName: "anthropic",
providerApiKey: testProviderApiKey,
headers: testHeaders,
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
expect(client).toBeDefined()
expect(typeof client).toBe("function")
})
it("should create client with custom provider URL", () => {
const customUrl = "https://custom-provider.com/v1/chat"
const config: SupermemoryInfiniteChatConfig = {
providerUrl: customUrl,
providerApiKey: testProviderApiKey,
headers: testHeaders,
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
expect(client).toBeDefined()
expect(typeof client).toBe("function")
})
})
describe("AI SDK integration", () => {
it("should generate text with simple prompt", async () => {
const config: SupermemoryInfiniteChatConfig = testProviderUrl
? {
providerUrl: testProviderUrl,
providerApiKey: testProviderApiKey,
headers: {},
}
: {
providerName: testProviderName,
providerApiKey: testProviderApiKey,
headers: {},
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
console.log(client(testModelName))
const result = await generateText({
model: client(testModelName),
prompt: testPrompts[0], // "Hello, how are you?"
})
expect(result).toBeDefined()
expect(result.text).toBeDefined()
expect(typeof result.text).toBe("string")
expect(result.text.length).toBeGreaterThan(0)
})
it("should generate text with messages array", async () => {
const config: SupermemoryInfiniteChatConfig = testProviderUrl
? {
providerUrl: testProviderUrl,
providerApiKey: testProviderApiKey,
headers: {},
}
: {
providerName: testProviderName,
providerApiKey: testProviderApiKey,
headers: {},
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
const result = await generateText({
model: client(testModelName),
messages: testMessages[1], // System + user messages
})
expect(result).toBeDefined()
expect(result.text).toBeDefined()
expect(typeof result.text).toBe("string")
expect(result.text.length).toBeGreaterThan(0)
})
it("should handle conversation history", async () => {
const config: SupermemoryInfiniteChatConfig = testProviderUrl
? {
providerUrl: testProviderUrl,
providerApiKey: testProviderApiKey,
headers: {},
}
: {
providerName: testProviderName,
providerApiKey: testProviderApiKey,
headers: {},
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
const result = await generateText({
model: client(testModelName),
messages: testMessages[2], // Multi-turn conversation
})
expect(result).toBeDefined()
expect(result.text).toBeDefined()
expect(typeof result.text).toBe("string")
expect(result.text.length).toBeGreaterThan(0)
})
it("should work with different prompt variations", async () => {
const config: SupermemoryInfiniteChatConfig = testProviderUrl
? {
providerUrl: testProviderUrl,
providerApiKey: testProviderApiKey,
headers: {},
}
: {
providerName: testProviderName,
providerApiKey: testProviderApiKey,
headers: {},
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
// Test multiple prompts
for (const prompt of testPrompts.slice(0, 3)) {
const result = await generateText({
model: client(testModelName),
prompt,
})
expect(result).toBeDefined()
expect(result.text).toBeDefined()
expect(typeof result.text).toBe("string")
expect(result.text.length).toBeGreaterThan(0)
}
})
it("should work with configured and alternate models", async () => {
const config: SupermemoryInfiniteChatConfig = testProviderUrl
? {
providerUrl: testProviderUrl,
providerApiKey: testProviderApiKey,
headers: {},
}
: {
providerName: testProviderName,
providerApiKey: testProviderApiKey,
headers: {},
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
const modelsToTest = [testModelName]
// Add alternate model for OpenAI
if (testProviderName === "openai" && !testProviderUrl) {
modelsToTest.push("gpt-4o-mini")
}
for (const modelName of modelsToTest) {
const result = await generateText({
model: client(modelName),
prompt: "Say hello in one word",
})
expect(result).toBeDefined()
expect(result.text).toBeDefined()
expect(typeof result.text).toBe("string")
}
})
it("should work with custom headers", async () => {
const config: SupermemoryInfiniteChatConfig = testProviderUrl
? {
providerUrl: testProviderUrl,
providerApiKey: testProviderApiKey,
headers: {
"x-custom-header": "test-value",
},
}
: {
providerName: testProviderName,
providerApiKey: testProviderApiKey,
headers: {
"x-custom-header": "test-value",
},
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
const result = await generateText({
model: client(testModelName),
prompt: "Hello",
})
expect(result).toBeDefined()
expect(result.text).toBeDefined()
expect(typeof result.text).toBe("string")
})
})
describe("configuration validation", () => {
it("should handle empty headers object", () => {
const config: SupermemoryInfiniteChatConfig = testProviderUrl
? {
providerUrl: testProviderUrl,
providerApiKey: testProviderApiKey,
headers: {},
}
: {
providerName: testProviderName,
providerApiKey: testProviderApiKey,
headers: {},
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
expect(client).toBeDefined()
})
it("should handle configuration with custom headers", () => {
const customHeaders = {
authorization: "Bearer custom-token",
"x-custom": "custom-value",
}
const config: SupermemoryInfiniteChatConfig = testProviderUrl
? {
providerUrl: testProviderUrl,
providerApiKey: testProviderApiKey,
headers: customHeaders,
}
: {
providerName: testProviderName,
providerApiKey: testProviderApiKey,
headers: customHeaders,
}
const client = createSupermemoryInfiniteChat(testApiKey, config)
expect(client).toBeDefined()
})
it("should handle different API keys", () => {
const config: SupermemoryInfiniteChatConfig = {
providerName: "openai",
providerApiKey: "different-provider-key",
headers: {},
}
const client = createSupermemoryInfiniteChat("different-sm-key", config)
expect(client).toBeDefined()
})
})
})

View file

@ -0,0 +1,49 @@
import { createOpenAI } from "@ai-sdk/openai"
interface SupermemoryInfiniteChatConfigBase {
providerApiKey: string
headers: Record<string, string>
}
interface SupermemoryInfiniteChatConfigWithProviderName
extends SupermemoryInfiniteChatConfigBase {
providerName: keyof typeof providerMap
providerUrl?: never
}
interface SupermemoryInfiniteChatConfigWithProviderUrl
extends SupermemoryInfiniteChatConfigBase {
providerUrl: string
providerName?: never
}
export type SupermemoryInfiniteChatConfig =
| SupermemoryInfiniteChatConfigWithProviderName
| SupermemoryInfiniteChatConfigWithProviderUrl
type SupermemoryApiKey = string
const providerMap = {
openai: "https://api.openai.com/v1",
anthropic: "https://api.anthropic.com/v1",
openrouter: "https://openrouter.ai/api/v1",
deepinfra: "https://api.deepinfra.com/v1/openai",
groq: "https://api.groq.com/openai/v1",
google: "https://generativelanguage.googleapis.com/v1beta/openai",
cloudflare: "https://gateway.ai.cloudflare.com/v1/*/unlimited-context/openai",
} as const
export const createSupermemoryInfiniteChat = (
apiKey: SupermemoryApiKey,
config: SupermemoryInfiniteChatConfig,
) =>
createOpenAI({
apiKey: config.providerApiKey,
baseURL: config.providerName
? providerMap[config.providerName]
: config.providerUrl,
headers: {
"x-api-key": apiKey,
...config.headers,
},
}).chat

View file

@ -0,0 +1,185 @@
import { createOpenAI } from "@ai-sdk/openai"
import { generateText } from "ai"
import { describe, expect, it } from "vitest"
import { type SupermemoryToolsConfig, supermemoryTools } from "./tools"
import "dotenv/config"
describe("supermemoryTools", () => {
// Required API keys - tests will fail if not provided
const testApiKey = process.env.SUPERMEMORY_API_KEY
const testOpenAIKey = process.env.OPENAI_API_KEY
if (!testApiKey) {
throw new Error(
"SUPERMEMORY_API_KEY environment variable is required for tests",
)
}
if (!testOpenAIKey) {
throw new Error("OPENAI_API_KEY environment variable is required for tests")
}
// Optional configuration with defaults
const testBaseUrl = process.env.SUPERMEMORY_BASE_URL ?? undefined
const testModelName = process.env.MODEL_NAME || "gpt-5-mini"
const testPrompts = [
"What do you remember about my preferences?",
"Help me plan my day based on what you know about me",
"What are my current projects?",
"Remind me of my interests and hobbies",
"What should I focus on today?",
]
describe("client initialization", () => {
it("should create tools with default configuration", () => {
const config: SupermemoryToolsConfig = {}
const tools = supermemoryTools(testApiKey, config)
expect(tools).toBeDefined()
expect(tools.searchMemories).toBeDefined()
expect(tools.addMemory).toBeDefined()
expect(tools.fetchMemory).toBeDefined()
})
it("should create tools with custom baseUrl", () => {
const config: SupermemoryToolsConfig = {
baseUrl: testBaseUrl,
}
const tools = supermemoryTools(testApiKey, config)
expect(tools).toBeDefined()
expect(tools.searchMemories).toBeDefined()
expect(tools.addMemory).toBeDefined()
expect(tools.fetchMemory).toBeDefined()
})
it("should create tools with projectId configuration", () => {
const config: SupermemoryToolsConfig = {
projectId: "test-project-123",
}
const tools = supermemoryTools(testApiKey, config)
expect(tools).toBeDefined()
expect(tools.searchMemories).toBeDefined()
expect(tools.addMemory).toBeDefined()
expect(tools.fetchMemory).toBeDefined()
})
it("should create tools with custom container tags", () => {
const config: SupermemoryToolsConfig = {
containerTags: ["custom-tag-1", "custom-tag-2"],
}
const tools = supermemoryTools(testApiKey, config)
expect(tools).toBeDefined()
expect(tools.searchMemories).toBeDefined()
expect(tools.addMemory).toBeDefined()
expect(tools.fetchMemory).toBeDefined()
})
})
describe("AI SDK integration", () => {
it("should work with AI SDK generateText", async () => {
const openai = createOpenAI({
apiKey: testOpenAIKey,
})
const result = await generateText({
model: openai(testModelName),
messages: [
{
role: "system",
content:
"You are a helpful assistant with access to user memories. Use the search tool when the user asks about preferences or past information.",
},
{
role: "user",
content: testPrompts[0]!,
},
],
tools: {
...supermemoryTools(testApiKey, {
projectId: "test-ai-integration",
baseUrl: testBaseUrl,
}),
},
})
expect(result).toBeDefined()
expect(result.text).toBeDefined()
expect(typeof result.text).toBe("string")
})
it("should use tools when prompted", async () => {
const openai = createOpenAI({
apiKey: testOpenAIKey,
})
const tools = supermemoryTools(testApiKey, {
projectId: "test-tool-usage",
baseUrl: testBaseUrl,
})
const result = await generateText({
model: openai(testModelName),
messages: [
{
role: "system",
content:
"You are a helpful assistant. When the user asks you to remember something, use the addMemory tool.",
},
{
role: "user",
content: "Please remember that I prefer dark roast coffee",
},
],
tools: {
addMemory: tools.addMemory,
},
})
expect(result).toBeDefined()
expect(result.text).toBeDefined()
expect(result.toolCalls).toBeDefined()
if (result.toolCalls && result.toolCalls.length > 0) {
const addMemoryCall = result.toolCalls.find(
(call) => call.toolName === "addMemory",
)
expect(addMemoryCall).toBeDefined()
}
})
it("should handle multiple tool types", async () => {
const openai = createOpenAI({
apiKey: testOpenAIKey,
})
const result = await generateText({
model: openai(testModelName),
messages: [
{
role: "system",
content:
"You are a helpful assistant with access to user memories. Use search when asked about preferences, and addMemory when told to remember something.",
},
{
role: "user",
content:
"Search for my preferences and then remember that I also like green tea",
},
],
tools: {
...supermemoryTools(testApiKey, {
containerTags: ["test-multi-tools"],
}),
},
})
expect(result).toBeDefined()
expect(result.text).toBeDefined()
expect(typeof result.text).toBe("string")
})
})
})

View file

@ -0,0 +1,147 @@
import { tool } from "ai"
import Supermemory from "supermemory"
import { z } from "zod"
/**
* Supermemory configuration
* Only one of `projectId` or `containerTags` can be provided.
*/
export interface SupermemoryToolsConfig {
baseUrl?: string
containerTags?: string[]
projectId?: string
}
/**
* Create Supermemory tools for AI SDK
*/
export function supermemoryTools(
apiKey: string,
config: SupermemoryToolsConfig,
) {
const client = new Supermemory({
apiKey,
...(config.baseUrl && { baseURL: config.baseUrl }),
})
const containerTags = config.projectId
? [`sm_project_${config.projectId}`]
: (config.containerTags ?? ["sm_project_default"])
const searchMemories = tool({
description:
"Search user memories and patterns. Run when explicitly asked or when context about user's past choices would be helpful. Uses semantic matching to find relevant details across related experiences.",
inputSchema: z.object({
informationToGet: z
.string()
.describe("Terms to search for in the user's memories"),
}),
execute: async ({ informationToGet }) => {
try {
const response = await client.search.execute({
q: informationToGet,
containerTags,
limit: 10,
chunkThreshold: 0.6,
})
return {
success: true,
results: response.results,
count: response.results?.length || 0,
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
}
}
},
})
const addMemory = tool({
description:
"Add a new memory to the user's memories. Run when explicitly asked or when the user mentions any information generalizable beyond the context of the current conversation.",
inputSchema: z.object({
memory: z
.string()
.describe(
"The text content of the memory to add. This should be a single sentence or a short paragraph.",
),
}),
execute: async ({ memory }) => {
try {
const metadata: Record<string, string | number | boolean> = {}
const response = await client.memories.add({
content: memory,
containerTags,
...(Object.keys(metadata).length > 0 && { metadata }),
})
return {
success: true,
memory: response,
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
}
}
},
})
const fetchMemory = tool({
description: "Fetch a specific memory by ID to get its full details",
inputSchema: z.object({
memoryId: z.string().describe("The ID of the memory to fetch"),
}),
execute: async ({ memoryId }) => {
try {
const response = await client.memories.get(memoryId)
return {
success: true,
memory: response,
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error",
}
}
},
})
return {
searchMemories,
addMemory,
fetchMemory,
}
}
// Export individual tool creators for more flexibility
export const searchMemoriesTool = (
apiKey: string,
config: SupermemoryToolsConfig,
) => {
const { searchMemories } = supermemoryTools(apiKey, config)
return searchMemories
}
export const addMemoryTool = (
apiKey: string,
config: SupermemoryToolsConfig,
) => {
const { addMemory } = supermemoryTools(apiKey, config)
return addMemory
}
export const fetchMemoryTool = (
apiKey: string,
config: SupermemoryToolsConfig,
) => {
const { fetchMemory } = supermemoryTools(apiKey, config)
return fetchMemory
}

View file

@ -0,0 +1,10 @@
{
"extends": "@total-typescript/tsconfig/bundler/dom/library-monorepo",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
"@lib/*": ["../lib/*"]
}
}
}

View file

@ -1,3 +0,0 @@
# `@turbo/eslint-config`
Collection of internal eslint configurations.

View file

@ -1,32 +0,0 @@
import js from "@eslint/js"
import eslintConfigPrettier from "eslint-config-prettier"
import onlyWarn from "eslint-plugin-only-warn"
import turboPlugin from "eslint-plugin-turbo"
import tseslint from "typescript-eslint"
/**
* A shared ESLint configuration for the repository.
*
* @type {import("eslint").Linter.Config[]}
* */
export const config = [
js.configs.recommended,
eslintConfigPrettier,
...tseslint.configs.recommended,
{
plugins: {
turbo: turboPlugin,
},
rules: {
"turbo/no-undeclared-env-vars": "warn",
},
},
{
plugins: {
onlyWarn,
},
},
{
ignores: ["dist/**"],
},
]

View file

@ -1,49 +0,0 @@
import js from "@eslint/js"
import pluginNext from "@next/eslint-plugin-next"
import eslintConfigPrettier from "eslint-config-prettier"
import pluginReact from "eslint-plugin-react"
import pluginReactHooks from "eslint-plugin-react-hooks"
import globals from "globals"
import tseslint from "typescript-eslint"
import { config as baseConfig } from "./base.js"
/**
* A custom ESLint configuration for libraries that use Next.js.
*
* @type {import("eslint").Linter.Config[]}
* */
export const nextJsConfig = [
...baseConfig,
js.configs.recommended,
eslintConfigPrettier,
...tseslint.configs.recommended,
{
...pluginReact.configs.flat.recommended,
languageOptions: {
...pluginReact.configs.flat.recommended.languageOptions,
globals: {
...globals.serviceworker,
},
},
},
{
plugins: {
"@next/next": pluginNext,
},
rules: {
...pluginNext.configs.recommended.rules,
...pluginNext.configs["core-web-vitals"].rules,
},
},
{
plugins: {
"react-hooks": pluginReactHooks,
},
settings: { react: { version: "detect" } },
rules: {
...pluginReactHooks.configs.recommended.rules,
// React scope no longer necessary with new JSX transform.
"react/react-in-jsx-scope": "off",
},
},
]

View file

@ -1,24 +0,0 @@
{
"name": "@repo/eslint-config",
"version": "0.0.0",
"type": "module",
"private": true,
"exports": {
"./base": "./base.js",
"./next-js": "./next.js",
"./react-internal": "./react-internal.js"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
"@next/eslint-plugin-next": "^15.4.2",
"eslint": "^9.33.0",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-only-warn": "^1.1.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-turbo": "^2.5.0",
"globals": "^16.3.0",
"typescript": "^5.9.2",
"typescript-eslint": "^8.39.0"
}
}

View file

@ -1,39 +0,0 @@
import js from "@eslint/js"
import eslintConfigPrettier from "eslint-config-prettier"
import pluginReact from "eslint-plugin-react"
import pluginReactHooks from "eslint-plugin-react-hooks"
import globals from "globals"
import tseslint from "typescript-eslint"
import { config as baseConfig } from "./base.js"
/**
* A custom ESLint configuration for libraries that use React.
*
* @type {import("eslint").Linter.Config[]} */
export const config = [
...baseConfig,
js.configs.recommended,
eslintConfigPrettier,
...tseslint.configs.recommended,
pluginReact.configs.flat.recommended,
{
languageOptions: {
...pluginReact.configs.flat.recommended.languageOptions,
globals: {
...globals.serviceworker,
...globals.browser,
},
},
},
{
plugins: {
"react-hooks": pluginReactHooks,
},
settings: { react: { version: "detect" } },
rules: {
...pluginReactHooks.configs.recommended.rules,
// React scope no longer necessary with new JSX transform.
"react/react-in-jsx-scope": "off",
},
},
]

View file

@ -1,19 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"incremental": false,
"isolatedModules": true,
"lib": ["es2022", "DOM", "DOM.Iterable"],
"module": "NodeNext",
"moduleDetection": "force",
"moduleResolution": "NodeNext",
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022"
}
}

View file

@ -1,12 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"plugins": [{ "name": "next" }],
"module": "ESNext",
"moduleResolution": "Bundler",
"allowJs": true,
"jsx": "preserve",
"noEmit": true
}
}

View file

@ -1,9 +0,0 @@
{
"name": "@repo/typescript-config",
"version": "0.0.0",
"private": true,
"license": "MIT",
"publishConfig": {
"access": "public"
}
}

View file

@ -1,7 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx"
}
}