diff --git a/.github/workflows/publish-ai-sdk.yml b/.github/workflows/publish-ai-sdk.yml index 1ac60cf18..94800708c 100644 --- a/.github/workflows/publish-ai-sdk.yml +++ b/.github/workflows/publish-ai-sdk.yml @@ -56,7 +56,8 @@ jobs: - name: Build if: steps.version-check.outputs.changed == 'true' - run: bun run build + working-directory: . + run: bunx turbo run build --filter='@supermemory/ai-sdk' - name: Publish if: steps.version-check.outputs.changed == 'true' diff --git a/bun.lock b/bun.lock index d6b4edf3a..0e28c5ca3 100644 --- a/bun.lock +++ b/bun.lock @@ -242,19 +242,16 @@ }, "packages/ai-sdk": { "name": "@supermemory/ai-sdk", - "version": "1.0.8", + "version": "2.0.0", "dependencies": { - "@ai-sdk/openai": "^2.0.22", "@ai-sdk/provider": "^2.0.0", - "ai": "^5.0.113", - "supermemory": "^3.0.0-alpha.26", + "@supermemory/tools": "^2.1.0", }, "devDependencies": { "@total-typescript/tsconfig": "^1.0.4", "@types/bun": "^1.2.21", "tsdown": "^0.14.2", "typescript": "^5.9.2", - "vitest": "^3.2.4", }, }, "packages/docs-test": { diff --git a/packages/ai-sdk/README.md b/packages/ai-sdk/README.md index 0927cfdf5..0080a25f5 100644 --- a/packages/ai-sdk/README.md +++ b/packages/ai-sdk/README.md @@ -1,5 +1,18 @@ # supermemory AI SDK Utilities +> **Deprecated.** Use [`@supermemory/tools`](../tools) instead: +> +> ```ts +> import { supermemoryTools } from "@supermemory/tools/ai-sdk" +> ``` +> +> As of 2.0.0 this package is a thin re-export of `@supermemory/tools/ai-sdk`, so it +> inherits that package's container-scoping semantics. **This is a behavior change:** +> +> - Passing both `projectId` and `containerTags` now throws instead of silently ignoring `containerTags`. +> - With no config, calls are scoped to `["sm_project_default"]`. Previously they were **unscoped**, i.e. searching and writing across every project. If you relied on that, pass `containerTags` explicitly. +> - `limit` is coerced, so a model emitting `"5"` is accepted rather than rejected. + Vercel AI SDK utilities for supermemory ## Installation diff --git a/packages/ai-sdk/package.json b/packages/ai-sdk/package.json index 30ff931b7..438aecd17 100644 --- a/packages/ai-sdk/package.json +++ b/packages/ai-sdk/package.json @@ -1,30 +1,26 @@ { "name": "@supermemory/ai-sdk", "type": "module", - "version": "1.0.8", + "version": "2.0.0", + "description": "Deprecated: re-exports @supermemory/tools/ai-sdk", "scripts": { "build": "tsdown", "dev": "tsdown --watch --ignore-watch .turbo", - "check-types": "tsc --noEmit", - "test": "vitest", - "test:watch": "vitest --watch" + "check-types": "tsc --noEmit" }, "dependencies": { - "@ai-sdk/openai": "^2.0.22", "@ai-sdk/provider": "^2.0.0", - "ai": "^5.0.113", - "supermemory": "^3.0.0-alpha.26" + "@supermemory/tools": "^2.1.0" }, "devDependencies": { "@total-typescript/tsconfig": "^1.0.4", "@types/bun": "^1.2.21", "tsdown": "^0.14.2", - "typescript": "^5.9.2", - "vitest": "^3.2.4" + "typescript": "^5.9.2" }, "main": "./dist/index.js", "module": "./dist/index.js", - "types": "./dist/index-Dk1U5LBS.d.ts", + "types": "./dist/index-iQkOjAt3.d.ts", "exports": { ".": "./dist/index.js", "./package.json": "./package.json" diff --git a/packages/ai-sdk/src/index.ts b/packages/ai-sdk/src/index.ts index e419075c8..eb133d8ab 100644 --- a/packages/ai-sdk/src/index.ts +++ b/packages/ai-sdk/src/index.ts @@ -1 +1,12 @@ -export * from "./tools" +/** + * @deprecated Use `@supermemory/tools/ai-sdk` instead. This package is now a + * thin re-export of it and will not receive further changes. + * + * Behavior change in 2.0.0 (inherited from `@supermemory/tools`): + * - Passing both `projectId` and `containerTags` now throws instead of silently + * ignoring `containerTags`. + * - With no config, calls are scoped to `["sm_project_default"]` instead of being + * unscoped across every project. + * - `limit`/`offset` are coerced, so a model emitting `"5"` is accepted. + */ +export * from "@supermemory/tools/ai-sdk" diff --git a/packages/ai-sdk/src/tools.test.ts b/packages/ai-sdk/src/tools.test.ts deleted file mode 100644 index 0acfc7ed7..000000000 --- a/packages/ai-sdk/src/tools.test.ts +++ /dev/null @@ -1,181 +0,0 @@ -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-nano" - - 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() - }) - - 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() - }) - - 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() - }) - - 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() - }) - }) - - 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") - }) - }) -}) diff --git a/packages/ai-sdk/src/tools.ts b/packages/ai-sdk/src/tools.ts deleted file mode 100644 index 7d0b837cb..000000000 --- a/packages/ai-sdk/src/tools.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { jsonSchema, tool } from "ai" -import Supermemory from "supermemory" - -/** - * Supermemory configuration - * Only one of `projectId` or `containerTags` can be provided. - */ -export interface SupermemoryToolsConfig { - baseUrl?: string - containerTags?: string[] - projectId?: string -} - -type SearchMemoriesInput = { - informationToGet: string - includeFullDocs: boolean - limit: number -} - -type AddMemoryInput = { - memory: 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 - - const searchMemories = tool({ - description: - "Search (recall) memories/details/information about the user or other facts or entities. Run when explicitly asked or when context about user's past choices would be helpful.", - inputSchema: jsonSchema({ - type: "object", - properties: { - informationToGet: { - type: "string", - description: "Terms to search for in the user's memories", - }, - includeFullDocs: { - type: "boolean", - description: - "Whether to include the full document content in the response. Defaults to true for better AI context.", - default: true, - }, - limit: { - type: "number", - description: "Maximum number of results to return", - default: 10, - }, - }, - required: ["informationToGet"], - }), - execute: async ({ - informationToGet, - includeFullDocs = true, - limit = 10, - }) => { - try { - const response = await client.search.execute({ - q: informationToGet, - containerTags, - limit, - chunkThreshold: 0.6, - includeFullDocs, - }) - - 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 (remember) memories/details/information about the user or other facts or entities. Run when explicitly asked or when the user mentions any information generalizable beyond the context of the current conversation.", - inputSchema: jsonSchema({ - type: "object", - properties: { - memory: { - type: "string", - description: - "The text content of the memory to add. This should be a single sentence or a short paragraph.", - }, - }, - required: ["memory"], - }), - execute: async ({ memory }) => { - try { - const metadata: Record = {} - - const response = await client.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", - } - } - }, - }) - - return { - searchMemories, - addMemory, - } -} - -// 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 -} diff --git a/packages/tools/tsdown.config.ts b/packages/tools/tsdown.config.ts index 139dd7df4..b0de5b7c9 100644 --- a/packages/tools/tsdown.config.ts +++ b/packages/tools/tsdown.config.ts @@ -18,6 +18,8 @@ export default defineConfig({ dts: { sourcemap: false, }, - exports: true, + // exports are hand-maintained in package.json: tsdown's generator emits + // native path separators, producing broken "./openai\\index" on Windows. + exports: false, unbundle: true, }) diff --git a/turbo.json b/turbo.json index d635ddf88..a8e86ba27 100644 --- a/turbo.json +++ b/turbo.json @@ -11,7 +11,7 @@ "dependsOn": ["^lint"] }, "check-types": { - "dependsOn": ["^check-types"] + "dependsOn": ["^build"] }, "dev": { "cache": false,