feat(tools): support AI SDK 6 and 7 (3.1.0) (#1794)

This commit is contained in:
Mahesh Sanikommu 2026-10-12 03:47:50 -07:00 • committed by GitHub
parent a5e84e0676
commit 4036c7d447
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 193 additions and 24 deletions

View file

@ -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

View file

@ -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

View file

@ -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": {

View file

@ -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).

View file

@ -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",

View file

@ -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,
)

View file

@ -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
}

View file

@ -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

View 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 })
}