mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-13 03:58:30 +00:00
feat(tools): support AI SDK 6 and 7 (3.1.0) (#1794)
This commit is contained in:
parent
a5e84e0676
commit
4036c7d447
9 changed files with 193 additions and 24 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
|
@ -78,6 +78,10 @@ jobs:
|
|||
if: steps.sdk-changes.outputs.tools == 'true' || steps.sdk-changes.outputs.sdk_playground == 'true'
|
||||
run: bun run --cwd packages/tools build
|
||||
|
||||
- name: Check Tools against AI SDK 5, 6 and 7
|
||||
if: steps.sdk-changes.outputs.tools == 'true'
|
||||
run: bun run --cwd packages/tools test:compat
|
||||
|
||||
- name: Run SDK Playground type checking
|
||||
if: steps.sdk-changes.outputs.sdk_playground == 'true'
|
||||
run: bun run --cwd apps/sdk-playground check-types:app
|
||||
|
|
|
|||
4
.github/workflows/publish-tools.yml
vendored
4
.github/workflows/publish-tools.yml
vendored
|
|
@ -58,6 +58,10 @@ jobs:
|
|||
if: steps.version-check.outputs.changed == 'true'
|
||||
run: bun run build
|
||||
|
||||
- name: Check against AI SDK 5, 6 and 7
|
||||
if: steps.version-check.outputs.changed == 'true'
|
||||
run: bun run test:compat
|
||||
|
||||
- name: Publish
|
||||
if: steps.version-check.outputs.changed == 'true'
|
||||
run: npm publish --access public --provenance
|
||||
|
|
|
|||
12
bun.lock
12
bun.lock
|
|
@ -227,34 +227,36 @@
|
|||
},
|
||||
"packages/tools": {
|
||||
"name": "@supermemory/tools",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.25",
|
||||
"@ai-sdk/openai": "^2.0.23",
|
||||
"ai": "^5.0.29",
|
||||
"lru-cache": "^11.2.6",
|
||||
"openai": "^4.104.0",
|
||||
"supermemory": "^5.0.0",
|
||||
"zod": "^4.1.5",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.25",
|
||||
"@ai-sdk/openai": "^2.0.23",
|
||||
"@ai-sdk/provider": "^3.0.0",
|
||||
"@anthropic-ai/sdk": "^0.65.0",
|
||||
"@mastra/core": "^1.0.0",
|
||||
"@total-typescript/tsconfig": "^1.0.4",
|
||||
"@types/bun": "^1.2.21",
|
||||
"@voltagent/core": "^2.6.12",
|
||||
"ai": "^5.0.29",
|
||||
"dotenv": "^16.6.1",
|
||||
"tsdown": "^0.14.2",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "3.2.6",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ai-sdk/provider": "^2.0.0 || ^3.0.0",
|
||||
"@ai-sdk/provider": "^2.0.0 || ^3.0.0 || ^4.0.0",
|
||||
"@voltagent/core": "^2.6.12",
|
||||
"ai": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
||||
},
|
||||
"optionalPeers": [
|
||||
"@voltagent/core",
|
||||
"ai",
|
||||
],
|
||||
},
|
||||
"packages/ui": {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ npm install @supermemory/tools
|
|||
|
||||
Requires Node 18 or later and a `SUPERMEMORY_API_KEY` from [console.supermemory.ai](https://console.supermemory.ai). The `supermemory@5` SDK comes along as a dependency. Against a self-hosted server, pass `baseUrl` and run `supermemory-server` v0.0.9 or later, which is the first version that serves the v5 routes.
|
||||
|
||||
The `ai-sdk` entry point works with AI SDK 5, 6 and 7. It uses the `ai` package your app already installs, so install `ai` yourself if you use that entry point.
|
||||
|
||||
## Migrating from 2.x
|
||||
|
||||
3.0 moves every call to the Supermemory v5 API (`supermemory@5`). The full guide is at [supermemory.ai/docs/migration/tools-v3-upgrade](https://supermemory.ai/docs/migration/tools-v3-upgrade).
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@supermemory/tools",
|
||||
"type": "module",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"description": "Memory tools for AI SDK, OpenAI, Voltagent and Mastra with supermemory",
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
|
|
@ -9,36 +9,41 @@
|
|||
"check-types": "tsc --noEmit",
|
||||
"test": "vitest --testTimeout 100000",
|
||||
"test:unit": "vitest run --testTimeout 100000 src/tools-shared.test.ts src/tool-operations.test.ts src/claude-memory.test.ts test/with-supermemory/unit.test.ts test/with-supermemory/conversation-conversion.test.ts test/openai-middleware.unit.test.ts test/mastra/unit.test.ts test/voltagent.unit.test.ts",
|
||||
"test:watch": "vitest --watch --testTimeout 100000"
|
||||
"test:watch": "vitest --watch --testTimeout 100000",
|
||||
"test:compat": "bun test/ai-sdk-compat/run.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.25",
|
||||
"@ai-sdk/openai": "^2.0.23",
|
||||
"ai": "^5.0.29",
|
||||
"lru-cache": "^11.2.6",
|
||||
"openai": "^4.104.0",
|
||||
"supermemory": "^5.0.0",
|
||||
"zod": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ai-sdk/anthropic": "^2.0.25",
|
||||
"@ai-sdk/openai": "^2.0.23",
|
||||
"@ai-sdk/provider": "^3.0.0",
|
||||
"@anthropic-ai/sdk": "^0.65.0",
|
||||
"@voltagent/core": "^2.6.12",
|
||||
"@mastra/core": "^1.0.0",
|
||||
"@total-typescript/tsconfig": "^1.0.4",
|
||||
"@types/bun": "^1.2.21",
|
||||
"ai": "^5.0.29",
|
||||
"dotenv": "^16.6.1",
|
||||
"tsdown": "^0.14.2",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "3.2.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ai-sdk/provider": "^2.0.0 || ^3.0.0",
|
||||
"@voltagent/core": "^2.6.12"
|
||||
"@ai-sdk/provider": "^2.0.0 || ^3.0.0 || ^4.0.0",
|
||||
"@voltagent/core": "^2.6.12",
|
||||
"ai": "^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@voltagent/core": {
|
||||
"optional": true
|
||||
},
|
||||
"ai": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
|
|
|
|||
|
|
@ -78,10 +78,10 @@ interface WrapVercelLanguageModelOptions {
|
|||
* a new system prompt with the memories. Pre-LLM profile retrieval uses a fixed internal
|
||||
* time budget and cannot be configured via options.
|
||||
*
|
||||
* Supports both Vercel AI SDK 5 (LanguageModelV2) and SDK 6 (LanguageModelV3) via runtime
|
||||
* Supports Vercel AI SDK 5 (LanguageModelV2), 6 (LanguageModelV3) and 7 (LanguageModelV4) via runtime
|
||||
* detection of `model.specificationVersion`.
|
||||
*
|
||||
* @param model - The language model to wrap with supermemory capabilities (V2 or V3)
|
||||
* @param model - The language model to wrap with supermemory capabilities (V2, V3 or V4)
|
||||
* @param options - Configuration options for Supermemory integration
|
||||
* @param options.namespace - Required. The namespace for memory search (e.g., user ID, project ID)
|
||||
* @param options.id - Required. ID that groups messages into a single document for contextual memory generation
|
||||
|
|
@ -180,7 +180,7 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
|
|||
}
|
||||
|
||||
try {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility across V2, V3 and V4
|
||||
const result = await target.doGenerate(modelParams as any)
|
||||
|
||||
if (
|
||||
|
|
@ -245,7 +245,7 @@ const wrapVercelLanguageModel = <T extends LanguageModel>(
|
|||
|
||||
try {
|
||||
const { stream, ...rest } = await target.doStream(
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility across V2, V3 and V4
|
||||
modelParams as any,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export const extractQueryText = (
|
|||
return content
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility across V2, V3 and V4
|
||||
return (content as any[])
|
||||
.filter((part) => part.type === "text")
|
||||
.map((part) => part.text || "")
|
||||
|
|
@ -74,7 +74,7 @@ export const injectMemoriesIntoParams = (
|
|||
if (systemPromptExists) {
|
||||
logger.debug("Replaced Supermemory context in existing system prompt")
|
||||
let injected = false
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3 prompt types
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility across V2, V3 and V4 prompt types
|
||||
const newPrompt = params.prompt.map((prompt: any) => {
|
||||
if (prompt.role !== "system") return prompt
|
||||
const content = String(prompt.content ?? "")
|
||||
|
|
@ -95,7 +95,7 @@ export const injectMemoriesIntoParams = (
|
|||
const newPrompt = [
|
||||
{ role: "system" as const, content: memoryContext },
|
||||
...params.prompt,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility between V2 and V3 prompt types
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Union type compatibility across V2, V3 and V4 prompt types
|
||||
] as any
|
||||
return { ...params, prompt: newPrompt } as LanguageModelCallOptions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,20 +12,19 @@ export type {
|
|||
ProfileMarkdownData,
|
||||
} from "../shared"
|
||||
|
||||
// Provider v2 does not export V3 names, so keep the public declaration on the
|
||||
// common V2 surface and structurally accept V3 models at the wrapper boundary.
|
||||
type LanguageModelV3Compat = Omit<
|
||||
// Provider v2 has no V3/V4 names, so newer models (AI SDK 6 = v3, AI SDK 7 = v4) are matched by shape.
|
||||
type LanguageModelNextCompat = Omit<
|
||||
LanguageModelV2,
|
||||
"specificationVersion" | "doGenerate" | "doStream"
|
||||
> & {
|
||||
readonly specificationVersion: "v3"
|
||||
readonly specificationVersion: "v3" | "v4"
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Bridges mutually exclusive provider major declarations.
|
||||
doGenerate(...args: any[]): PromiseLike<any>
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Bridges mutually exclusive provider major declarations.
|
||||
doStream(...args: any[]): PromiseLike<any>
|
||||
}
|
||||
|
||||
export type LanguageModel = LanguageModelV2 | LanguageModelV3Compat
|
||||
export type LanguageModel = LanguageModelV2 | LanguageModelNextCompat
|
||||
export type LanguageModelCallOptions = LanguageModelV2CallOptions
|
||||
export type LanguageModelMessage = LanguageModelV2Message
|
||||
export type LanguageModelStreamPart = LanguageModelV2StreamPart
|
||||
|
|
|
|||
153
packages/tools/test/ai-sdk-compat/run.ts
Normal file
153
packages/tools/test/ai-sdk-compat/run.ts
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
// Packs the built package and installs it next to each supported AI SDK major, then typechecks and runs a consumer app.
|
||||
import { mkdtemp, rm } from "node:fs/promises"
|
||||
import { tmpdir } from "node:os"
|
||||
import path from "node:path"
|
||||
import { $ } from "bun"
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, "../..")
|
||||
const FINISHED = { unified: "stop", raw: "stop" }
|
||||
const TOOL_CALLS = { unified: "tool-calls", raw: "tool_calls" }
|
||||
const USAGE = {
|
||||
inputTokens: { total: 1, noCache: 1, cacheRead: 0, cacheWrite: 0 },
|
||||
outputTokens: { total: 1, text: 1, reasoning: 0 },
|
||||
}
|
||||
|
||||
const VERSIONS = [
|
||||
{
|
||||
ai: "5",
|
||||
mock: "MockLanguageModelV2",
|
||||
stop: "stop",
|
||||
toolCalls: "tool-calls",
|
||||
usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 },
|
||||
},
|
||||
{
|
||||
ai: "6",
|
||||
mock: "MockLanguageModelV3",
|
||||
stop: FINISHED,
|
||||
toolCalls: TOOL_CALLS,
|
||||
usage: USAGE,
|
||||
},
|
||||
{
|
||||
ai: "7",
|
||||
mock: "MockLanguageModelV4",
|
||||
stop: FINISHED,
|
||||
toolCalls: TOOL_CALLS,
|
||||
usage: USAGE,
|
||||
},
|
||||
]
|
||||
|
||||
const fixture = (v: (typeof VERSIONS)[number]) => `
|
||||
import { generateText, stepCountIs, streamText } from "ai"
|
||||
import { ${v.mock} } from "ai/test"
|
||||
import { supermemoryTools, withSupermemory } from "@supermemory/tools/ai-sdk"
|
||||
|
||||
let searchCalls = 0
|
||||
const server = Bun.serve({
|
||||
port: 0,
|
||||
fetch(req) {
|
||||
const route = new URL(req.url).pathname
|
||||
if (route.endsWith("/profile"))
|
||||
return Response.json({ profile: { static: [{ id: "s0", memory: "User prefers dark mode" }], dynamic: [], buckets: {} } })
|
||||
if (route.endsWith("/search")) {
|
||||
searchCalls++
|
||||
return Response.json({ results: [], searchTime: 1 })
|
||||
}
|
||||
return Response.json({ id: "doc_1", status: "queued" })
|
||||
},
|
||||
})
|
||||
const baseUrl = \`http://127.0.0.1:\${server.port}\`
|
||||
|
||||
const prompts: string[] = []
|
||||
let calls = 0
|
||||
const model = new ${v.mock}({
|
||||
doGenerate: async (options) => {
|
||||
prompts.push(JSON.stringify(options.prompt))
|
||||
const callTool = calls++ === 0 && (options.tools?.length ?? 0) > 0
|
||||
return {
|
||||
content: callTool
|
||||
? [{ type: "tool-call", toolCallId: "c1", toolName: "searchMemories", input: JSON.stringify({ informationToGet: "theme" }) }]
|
||||
: [{ type: "text", text: "ok" }],
|
||||
finishReason: callTool ? ${JSON.stringify(v.toolCalls)} : ${JSON.stringify(v.stop)},
|
||||
usage: ${JSON.stringify(v.usage)},
|
||||
warnings: [],
|
||||
} as never
|
||||
},
|
||||
})
|
||||
|
||||
const tools = supermemoryTools("sm_test", { baseUrl })
|
||||
const withTools = await generateText({ model, tools, prompt: "what theme?", stopWhen: stepCountIs(3) })
|
||||
if (withTools.text !== "ok" || searchCalls !== 1) throw new Error(\`tools: text=\${withTools.text} searchCalls=\${searchCalls}\`)
|
||||
|
||||
const wrapped = withSupermemory(model, { apiKey: "sm_test", namespace: "user_1", id: "conv_1", baseUrl })
|
||||
const withMemory = await generateText({ model: wrapped, prompt: "hi" })
|
||||
if (withMemory.text !== "ok" || !prompts.at(-1)?.includes("User prefers dark mode"))
|
||||
throw new Error("withSupermemory: memory was not injected into the prompt")
|
||||
|
||||
// Type-only: the wrapped model and tools must also be accepted by streamText.
|
||||
const _typecheckStream = () => streamText({ model: wrapped, tools, prompt: "hi" })
|
||||
|
||||
server.stop(true)
|
||||
console.log("ai@${v.ai}: tools and withSupermemory OK")
|
||||
`
|
||||
|
||||
const work = await mkdtemp(path.join(tmpdir(), "sm-tools-compat-"))
|
||||
try {
|
||||
await $`bun run build`.cwd(ROOT).quiet()
|
||||
const packed = await $`npm pack --silent --pack-destination ${work}`
|
||||
.cwd(ROOT)
|
||||
.text()
|
||||
const tarball = path.join(work, packed.trim().split("\n").at(-1) ?? "")
|
||||
|
||||
for (const v of VERSIONS) {
|
||||
const app = path.join(work, `ai-v${v.ai}`)
|
||||
await Bun.write(
|
||||
path.join(app, "package.json"),
|
||||
JSON.stringify({
|
||||
type: "module",
|
||||
dependencies: {
|
||||
"@supermemory/tools": `file:${tarball}`,
|
||||
ai: `^${v.ai}`,
|
||||
typescript: "^5.9.2",
|
||||
zod: "^4.1.5",
|
||||
"@types/bun": "^1.2.21",
|
||||
},
|
||||
}),
|
||||
)
|
||||
await Bun.write(
|
||||
path.join(app, "tsconfig.json"),
|
||||
JSON.stringify({
|
||||
compilerOptions: {
|
||||
target: "ES2022",
|
||||
module: "ESNext",
|
||||
moduleResolution: "bundler",
|
||||
strict: true,
|
||||
noEmit: true,
|
||||
skipLibCheck: true,
|
||||
types: ["bun"],
|
||||
},
|
||||
files: ["app.ts"],
|
||||
}),
|
||||
)
|
||||
await Bun.write(path.join(app, "app.ts"), fixture(v))
|
||||
await $`npm install --silent --no-audit --no-fund`.cwd(app).quiet()
|
||||
const installed = (
|
||||
await Bun.file(path.join(app, "node_modules/ai/package.json")).json()
|
||||
).version
|
||||
const nested = await Bun.file(
|
||||
path.join(
|
||||
app,
|
||||
"node_modules/@supermemory/tools/node_modules/ai/package.json",
|
||||
),
|
||||
).exists()
|
||||
if (nested)
|
||||
throw new Error(
|
||||
`ai@${v.ai}: a second copy of ai was installed under @supermemory/tools`,
|
||||
)
|
||||
const tsc = await $`npx tsc -p .`.cwd(app).nothrow().quiet()
|
||||
if (tsc.exitCode !== 0)
|
||||
throw new Error(`ai@${installed}: typecheck failed\n${tsc.stdout}`)
|
||||
await $`bun app.ts`.cwd(app)
|
||||
}
|
||||
} finally {
|
||||
await rm(work, { recursive: true, force: true })
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue