From 8c3aadb62be682467776b26f434dd0f5210c4b49 Mon Sep 17 00:00:00 2001 From: gharibyan Date: Thu, 11 Jun 2026 10:38:53 +0400 Subject: [PATCH 1/2] Add Anthropic Gemini and xAI providers --- .github/workflows/publish.yml | 24 ++ .github/workflows/test.yml | 9 + AGENTS.md | 3 + README.md | 22 +- package.json | 4 +- packages/agent-memory/README.md | 10 +- packages/agent-memory/package.json | 5 +- packages/agent-memory/src/index.ts | 6 + packages/anthropic/README.md | 14 + packages/anthropic/package.json | 47 ++++ packages/anthropic/src/index.ts | 156 +++++++++++ packages/anthropic/tsconfig.json | 10 + packages/gemini/README.md | 14 + packages/gemini/package.json | 48 ++++ packages/gemini/src/index.ts | 148 +++++++++++ packages/gemini/tsconfig.json | 10 + packages/xai/README.md | 14 + packages/xai/package.json | 48 ++++ packages/xai/src/index.ts | 139 ++++++++++ packages/xai/tsconfig.json | 10 + pnpm-lock.yaml | 388 +++++++++++++++++++++++++++- skills/agent-memory/SKILL.md | 5 +- test/adapter-packages.test.mjs | 70 +++++ test/automation.test.mjs | 37 ++- test/package-boundary.test.mjs | 42 +++ test/provider-sdk-adapters.test.mjs | 154 +++++++++++ test/repository-hygiene.test.mjs | 5 +- test/typescript-structure.test.mjs | 46 +++- 28 files changed, 1470 insertions(+), 18 deletions(-) create mode 100644 packages/anthropic/README.md create mode 100644 packages/anthropic/package.json create mode 100644 packages/anthropic/src/index.ts create mode 100644 packages/anthropic/tsconfig.json create mode 100644 packages/gemini/README.md create mode 100644 packages/gemini/package.json create mode 100644 packages/gemini/src/index.ts create mode 100644 packages/gemini/tsconfig.json create mode 100644 packages/xai/README.md create mode 100644 packages/xai/package.json create mode 100644 packages/xai/src/index.ts create mode 100644 packages/xai/tsconfig.json create mode 100644 test/provider-sdk-adapters.test.mjs diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f6f5107..bfce72e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -60,6 +60,15 @@ jobs: - name: Verify OpenAI adapter package contents run: pnpm --filter @agent-memory/openai pack --dry-run + - name: Verify Anthropic adapter package contents + run: pnpm --filter @agent-memory/anthropic pack --dry-run + + - name: Verify Gemini adapter package contents + run: pnpm --filter @agent-memory/gemini pack --dry-run + + - name: Verify xAI adapter package contents + run: pnpm --filter @agent-memory/xai pack --dry-run + - name: Verify public package contents run: pnpm --filter agent-memory pack --dry-run @@ -88,6 +97,21 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish Anthropic adapter + run: pnpm --filter @agent-memory/anthropic publish --access public --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish Gemini adapter + run: pnpm --filter @agent-memory/gemini publish --access public --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish xAI adapter + run: pnpm --filter @agent-memory/xai publish --access public --no-git-checks + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish public package run: pnpm --filter agent-memory publish --access public --no-git-checks env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad85b1c..9222bad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -56,5 +56,14 @@ jobs: - name: Verify OpenAI adapter package contents run: pnpm --filter @agent-memory/openai pack --dry-run + - name: Verify Anthropic adapter package contents + run: pnpm --filter @agent-memory/anthropic pack --dry-run + + - name: Verify Gemini adapter package contents + run: pnpm --filter @agent-memory/gemini pack --dry-run + + - name: Verify xAI adapter package contents + run: pnpm --filter @agent-memory/xai pack --dry-run + - name: Verify public package contents run: pnpm --filter agent-memory pack --dry-run diff --git a/AGENTS.md b/AGENTS.md index 349d4ae..259bcae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,6 +9,9 @@ This repo is a TypeScript-first pnpm workspace for the `agent-memory` SDK. - `packages/sqlite` (`@agent-memory/sqlite`) owns real SQLite `.memory/memory.sqlite` persistence. - `packages/postgres` (`@agent-memory/postgres`) owns Postgres persistence, automatic migrations, and pgvector search. - `packages/openai` (`@agent-memory/openai`) owns OpenAI model calls through the official `openai` SDK, plus compatible custom endpoint support. +- `packages/anthropic` (`@agent-memory/anthropic`) owns Anthropic model calls through the official `@anthropic-ai/sdk` package. +- `packages/gemini` (`@agent-memory/gemini`) owns Gemini model calls through the official `@google/genai` package. +- `packages/xai` (`@agent-memory/xai`) owns xAI model calls through the documented OpenAI SDK-compatible client path with xAI defaults. - `packages/agent-memory` is the public convenience package. It should keep `createAgent({ model })` easy and automatic. - `apps/playground` is private and must never ship in npm packages. diff --git a/README.md b/README.md index e4fc57b..5a67249 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ The Postgres adapter runs migrations automatically before the first memory opera ## Model Providers -First-party provider integrations should use official SDKs. The OpenAI adapter depends on the official `openai` TypeScript SDK: +First-party provider integrations should use provider SDKs or documented provider client paths. The OpenAI adapter depends on the official `openai` TypeScript SDK: ```ts import { openai } from "agent-memory" @@ -109,6 +109,23 @@ import { openai } from "agent-memory" const model = openai("gpt-5") ``` +Anthropic and Gemini live in their own adapter packages and are also re-exported by `agent-memory`: + +```ts +import { anthropic, gemini } from "agent-memory" + +const anthropicModel = anthropic("anthropic-model") +const geminiModel = gemini("gemini-2.5-pro") +``` + +xAI has a first-class package too. It uses the documented OpenAI SDK-compatible client path with xAI defaults: + +```ts +import { xai } from "agent-memory" + +const model = xai("grok-4") +``` + Use the OpenAI-compatible helper only for custom providers that expose a compatible chat completions API: ```ts @@ -129,6 +146,9 @@ const model = openAICompatible({ - `@agent-memory/sqlite`: real SQLite persistence adapter. - `@agent-memory/postgres`: Postgres persistence adapter with pgvector migrations. - `@agent-memory/openai`: OpenAI official SDK adapter, plus OpenAI-compatible custom endpoint support. +- `@agent-memory/anthropic`: Anthropic official SDK adapter. +- `@agent-memory/gemini`: Gemini official SDK adapter. +- `@agent-memory/xai`: xAI adapter using the documented OpenAI SDK-compatible client path. ## Playground diff --git a/package.json b/package.json index 331a7ed..da9752d 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,11 @@ "packages/*" ], "scripts": { - "build": "pnpm --filter @agent-memory/core build && pnpm --filter @agent-memory/local build && pnpm --filter @agent-memory/sqlite build && pnpm --filter @agent-memory/postgres build && pnpm --filter @agent-memory/openai build && pnpm --filter agent-memory build", + "build": "pnpm --filter @agent-memory/core build && pnpm --filter @agent-memory/local build && pnpm --filter @agent-memory/sqlite build && pnpm --filter @agent-memory/postgres build && pnpm --filter @agent-memory/openai build && pnpm --filter @agent-memory/anthropic build && pnpm --filter @agent-memory/gemini build && pnpm --filter @agent-memory/xai build && pnpm --filter agent-memory build", "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "pnpm build && node --test packages/agent-memory/test/*.test.mjs test/*.test.mjs", - "pack:check": "pnpm --filter @agent-memory/core pack --dry-run && pnpm --filter @agent-memory/local pack --dry-run && pnpm --filter @agent-memory/sqlite pack --dry-run && pnpm --filter @agent-memory/postgres pack --dry-run && pnpm --filter @agent-memory/openai pack --dry-run && pnpm --filter agent-memory pack --dry-run", + "pack:check": "pnpm --filter @agent-memory/core pack --dry-run && pnpm --filter @agent-memory/local pack --dry-run && pnpm --filter @agent-memory/sqlite pack --dry-run && pnpm --filter @agent-memory/postgres pack --dry-run && pnpm --filter @agent-memory/openai pack --dry-run && pnpm --filter @agent-memory/anthropic pack --dry-run && pnpm --filter @agent-memory/gemini pack --dry-run && pnpm --filter @agent-memory/xai pack --dry-run && pnpm --filter agent-memory pack --dry-run", "version:from-tag": "node scripts/sync-package-version-from-tag.mjs" }, "devDependencies": { diff --git a/packages/agent-memory/README.md b/packages/agent-memory/README.md index a4cd326..1e7cfb4 100644 --- a/packages/agent-memory/README.md +++ b/packages/agent-memory/README.md @@ -5,14 +5,10 @@ TypeScript SDK for building AI agents with automatic, scoped, persistent memory. Full project documentation: [github.com/gharibyan/agent-memory](https://github.com/gharibyan/agent-memory). ```ts -import { createAgent, openAICompatible } from "agent-memory" +import { createAgent, openai } from "agent-memory" const agent = createAgent({ - model: openAICompatible({ - model: "deepseek-chat", - baseURL: "https://api.deepseek.com/v1", - apiKey: process.env.DEEPSEEK_API_KEY - }) + model: openai("gpt-5") }) const result = await agent.generate({ @@ -27,4 +23,6 @@ const result = await agent.generate({ console.log(result.text) ``` +First-party helpers include `openai()`, `anthropic()`, `gemini()`, and `xai()`. Use `openAICompatible()` for custom chat-completions endpoints. + If `userId` is omitted, memory is stored in the shared default scope. Local memory defaults to `.memory/memory.json`; use `sqliteMemory()` for `.memory/memory.sqlite` or `postgresMemory()` for Postgres with automatic pgvector migrations. diff --git a/packages/agent-memory/package.json b/packages/agent-memory/package.json index d40af73..d55770c 100644 --- a/packages/agent-memory/package.json +++ b/packages/agent-memory/package.json @@ -31,11 +31,14 @@ "test": "pnpm build && node --test test/*.test.mjs" }, "dependencies": { + "@agent-memory/anthropic": "workspace:*", "@agent-memory/core": "workspace:*", + "@agent-memory/gemini": "workspace:*", "@agent-memory/local": "workspace:*", "@agent-memory/openai": "workspace:*", "@agent-memory/postgres": "workspace:*", - "@agent-memory/sqlite": "workspace:*" + "@agent-memory/sqlite": "workspace:*", + "@agent-memory/xai": "workspace:*" }, "engines": { "node": ">=20.12" diff --git a/packages/agent-memory/src/index.ts b/packages/agent-memory/src/index.ts index 55404d2..147e602 100644 --- a/packages/agent-memory/src/index.ts +++ b/packages/agent-memory/src/index.ts @@ -1,9 +1,15 @@ export { createAgent } from "./agent.js" export * from "@agent-memory/core" +export { anthropic } from "@agent-memory/anthropic" +export { gemini } from "@agent-memory/gemini" export { localMemory } from "@agent-memory/local" export { sqliteMemory } from "@agent-memory/sqlite" export { postgresMemory } from "@agent-memory/postgres" export { openAICompatible, openai } from "@agent-memory/openai" +export { xai } from "@agent-memory/xai" export type { AgentConfig, AgentMemoryConfig } from "@agent-memory/core" +export type { AnthropicOptions } from "@agent-memory/anthropic" +export type { GeminiOptions } from "@agent-memory/gemini" export type { OpenAICompatibleOptions } from "@agent-memory/openai" export type { PostgresMemoryOptions, PgPoolLike } from "@agent-memory/postgres" +export type { XAIOptions } from "@agent-memory/xai" diff --git a/packages/anthropic/README.md b/packages/anthropic/README.md new file mode 100644 index 0000000..4515d87 --- /dev/null +++ b/packages/anthropic/README.md @@ -0,0 +1,14 @@ +# @agent-memory/anthropic + +Anthropic model provider adapter for `agent-memory`, backed by the official `@anthropic-ai/sdk` TypeScript SDK. + +```ts +import { createAgent } from "agent-memory" +import { anthropic } from "@agent-memory/anthropic" + +const agent = createAgent({ + model: anthropic("anthropic-model") +}) +``` + +Set `ANTHROPIC_API_KEY` in the environment, or pass `apiKey` explicitly. diff --git a/packages/anthropic/package.json b/packages/anthropic/package.json new file mode 100644 index 0000000..22daa64 --- /dev/null +++ b/packages/anthropic/package.json @@ -0,0 +1,47 @@ +{ + "name": "@agent-memory/anthropic", + "version": "0.0.0", + "description": "Anthropic official SDK model provider adapter for agent-memory.", + "type": "module", + "author": "Gharibyan", + "homepage": "https://github.com/gharibyan/agent-memory#readme", + "bugs": { + "url": "https://github.com/gharibyan/agent-memory/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/gharibyan/agent-memory.git", + "directory": "packages/anthropic" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist", + "README.md", + "package.json" + ], + "scripts": { + "build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.json" + }, + "dependencies": { + "@agent-memory/core": "workspace:*", + "@anthropic-ai/sdk": "^0.104.1" + }, + "engines": { + "node": ">=20.12" + }, + "keywords": [ + "ai", + "agent", + "memory", + "anthropic", + "llm" + ], + "license": "MIT" +} diff --git a/packages/anthropic/src/index.ts b/packages/anthropic/src/index.ts new file mode 100644 index 0000000..a41346a --- /dev/null +++ b/packages/anthropic/src/index.ts @@ -0,0 +1,156 @@ +import Anthropic from "@anthropic-ai/sdk" +import { customModel } from "@agent-memory/core" +import type { AgentMessage, ModelProvider, ModelRequest, ModelResponse, ModelStreamChunk } from "@agent-memory/core" + +export type AnthropicClient = { + messages: { + create(input: AnthropicMessageInput): Promise> + } +} + +export type AnthropicOptions = { + id?: string + model: string + apiKey?: string + headers?: Record + client?: AnthropicClient + maxTokens?: number +} + +export function anthropic( + modelOrOptions: string | AnthropicOptions, + options: Partial = {} +): ModelProvider { + const config = typeof modelOrOptions === "string" + ? { model: modelOrOptions, ...options } + : modelOrOptions + + if (!config.model) { + throw new Error("anthropic provider requires a model") + } + + let client: AnthropicClient | undefined = config.client + + function sdk(): AnthropicClient { + if (!client) { + client = new Anthropic({ + apiKey: config.apiKey ?? process.env.ANTHROPIC_API_KEY, + defaultHeaders: config.headers + }) as unknown as AnthropicClient + } + return client + } + + return customModel({ + id: config.id ?? `anthropic:${config.model}`, + capabilities: { + streaming: true, + tools: true, + jsonSchema: false + }, + async generate(request: ModelRequest): Promise { + const response = await sdk().messages.create(anthropicInput(config, request, false)) + const json = response as AnthropicMessageResponse + const text = textFromAnthropicContent(json.content) + + return { + text, + messages: text ? [{ role: "assistant", content: text }] : undefined, + toolCalls: toolCallsFromAnthropicContent(json.content), + usage: json.usage, + finishReason: json.stop_reason, + raw: json + } + }, + async *stream(request: ModelRequest): AsyncIterable { + const stream = await sdk().messages.create(anthropicInput(config, request, true)) + + for await (const event of stream as AsyncIterable) { + const text = textFromAnthropicStreamEvent(event) + if (text.length > 0) yield text + } + } + }) +} + +function anthropicInput(config: AnthropicOptions, request: ModelRequest, stream: boolean): AnthropicMessageInput { + const system = systemInstruction(request) + + return { + model: config.model, + messages: request.messages.filter((message) => message.role !== "system").map(anthropicMessage), + max_tokens: request.maxTokens ?? config.maxTokens ?? 1024, + ...(system ? { system } : {}), + ...(request.tools ? { tools: request.tools } : {}), + ...(request.temperature === undefined ? {} : { temperature: request.temperature }), + stream + } +} + +function systemInstruction(request: ModelRequest): string { + return [ + request.system, + ...request.messages + .filter((message) => message.role === "system") + .map((message) => message.content) + ] + .filter((value): value is string => typeof value === "string" && value.length > 0) + .join("\n\n") +} + +function anthropicMessage(message: AgentMessage): { role: "user" | "assistant"; content: string } { + return { + role: message.role === "assistant" ? "assistant" : "user", + content: message.content + } +} + +function textFromAnthropicContent(content: AnthropicContentBlock[] | undefined): string { + if (!Array.isArray(content)) return "" + + return content + .map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "") + .join("") +} + +function toolCallsFromAnthropicContent(content: AnthropicContentBlock[] | undefined): unknown[] | undefined { + if (!Array.isArray(content)) return undefined + const toolCalls = content.filter((block) => block.type === "tool_use") + return toolCalls.length > 0 ? toolCalls : undefined +} + +function textFromAnthropicStreamEvent(event: AnthropicStreamEvent): string { + if (event.type !== "content_block_delta") return "" + const delta = event.delta + return delta?.type === "text_delta" && typeof delta.text === "string" ? delta.text : "" +} + +type AnthropicMessageInput = { + model: string + messages: Array<{ role: "user" | "assistant"; content: string }> + max_tokens: number + system?: string + tools?: unknown[] + temperature?: number + stream: boolean +} + +type AnthropicMessageResponse = { + content?: AnthropicContentBlock[] + stop_reason?: string + usage?: unknown +} + +type AnthropicContentBlock = { + type?: string + text?: string + [key: string]: unknown +} + +type AnthropicStreamEvent = { + type?: string + delta?: { + type?: string + text?: string + } +} diff --git a/packages/anthropic/tsconfig.json b/packages/anthropic/tsconfig.json new file mode 100644 index 0000000..cf9e1b1 --- /dev/null +++ b/packages/anthropic/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/packages/gemini/README.md b/packages/gemini/README.md new file mode 100644 index 0000000..eb869f6 --- /dev/null +++ b/packages/gemini/README.md @@ -0,0 +1,14 @@ +# @agent-memory/gemini + +Google Gemini model provider adapter for `agent-memory`, backed by the official `@google/genai` TypeScript SDK. + +```ts +import { createAgent } from "agent-memory" +import { gemini } from "@agent-memory/gemini" + +const agent = createAgent({ + model: gemini("gemini-2.5-pro") +}) +``` + +Set `GEMINI_API_KEY` in the environment, or pass `apiKey` explicitly. diff --git a/packages/gemini/package.json b/packages/gemini/package.json new file mode 100644 index 0000000..0d9496a --- /dev/null +++ b/packages/gemini/package.json @@ -0,0 +1,48 @@ +{ + "name": "@agent-memory/gemini", + "version": "0.0.0", + "description": "Google Gemini official SDK model provider adapter for agent-memory.", + "type": "module", + "author": "Gharibyan", + "homepage": "https://github.com/gharibyan/agent-memory#readme", + "bugs": { + "url": "https://github.com/gharibyan/agent-memory/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/gharibyan/agent-memory.git", + "directory": "packages/gemini" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist", + "README.md", + "package.json" + ], + "scripts": { + "build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.json" + }, + "dependencies": { + "@agent-memory/core": "workspace:*", + "@google/genai": "^2.8.0" + }, + "engines": { + "node": ">=20.12" + }, + "keywords": [ + "ai", + "agent", + "memory", + "gemini", + "google", + "llm" + ], + "license": "MIT" +} diff --git a/packages/gemini/src/index.ts b/packages/gemini/src/index.ts new file mode 100644 index 0000000..6c36309 --- /dev/null +++ b/packages/gemini/src/index.ts @@ -0,0 +1,148 @@ +import { GoogleGenAI } from "@google/genai" +import { customModel } from "@agent-memory/core" +import type { AgentMessage, ModelProvider, ModelRequest, ModelResponse, ModelStreamChunk } from "@agent-memory/core" + +export type GeminiClient = { + models: { + generateContent(input: GeminiGenerateInput): Promise + generateContentStream(input: GeminiGenerateInput): Promise> + } +} + +export type GeminiOptions = { + id?: string + model: string + apiKey?: string + client?: GeminiClient +} + +export function gemini( + modelOrOptions: string | GeminiOptions, + options: Partial = {} +): ModelProvider { + const config = typeof modelOrOptions === "string" + ? { model: modelOrOptions, ...options } + : modelOrOptions + + if (!config.model) { + throw new Error("gemini provider requires a model") + } + + let client: GeminiClient | undefined = config.client + + function sdk(): GeminiClient { + if (!client) { + client = new GoogleGenAI({ + apiKey: config.apiKey ?? process.env.GEMINI_API_KEY + }) as unknown as GeminiClient + } + return client + } + + return customModel({ + id: config.id ?? `gemini:${config.model}`, + capabilities: { + streaming: true, + tools: true, + jsonSchema: true + }, + async generate(request: ModelRequest): Promise { + const response = await sdk().models.generateContent(geminiInput(config.model, request)) + const text = textFromGeminiResponse(response) + + return { + text, + messages: text ? [{ role: "assistant", content: text }] : undefined, + usage: response.usageMetadata, + finishReason: response.candidates?.[0]?.finishReason, + raw: response + } + }, + async *stream(request: ModelRequest): AsyncIterable { + const stream = await sdk().models.generateContentStream(geminiInput(config.model, request)) + + for await (const chunk of stream) { + const text = textFromGeminiResponse(chunk) + if (text.length > 0) yield text + } + } + }) +} + +function geminiInput(model: string, request: ModelRequest): GeminiGenerateInput { + const system = systemInstruction(request) + const config = cleanConfig({ + ...(system ? { systemInstruction: system } : {}), + ...(request.temperature === undefined ? {} : { temperature: request.temperature }), + ...(request.maxTokens === undefined ? {} : { maxOutputTokens: request.maxTokens }), + ...(request.tools ? { tools: request.tools } : {}) + }) + + return { + model, + contents: request.messages.filter((message) => message.role !== "system").map(geminiContent), + ...(Object.keys(config).length > 0 ? { config } : {}) + } +} + +function systemInstruction(request: ModelRequest): string { + return [ + request.system, + ...request.messages + .filter((message) => message.role === "system") + .map((message) => message.content) + ] + .filter((value): value is string => typeof value === "string" && value.length > 0) + .join("\n\n") +} + +function geminiContent(message: AgentMessage): GeminiContent { + return { + role: message.role === "assistant" ? "model" : "user", + parts: [{ text: message.content }] + } +} + +function cleanConfig(config: GeminiConfig): GeminiConfig { + return Object.fromEntries( + Object.entries(config).filter(([, value]) => value !== undefined) + ) as GeminiConfig +} + +function textFromGeminiResponse(response: GeminiGenerateResponse): string { + if (typeof response.text === "string") return response.text + + return response.candidates + ?.flatMap((candidate) => candidate.content?.parts ?? []) + .map((part) => typeof part.text === "string" ? part.text : "") + .join("") ?? "" +} + +type GeminiGenerateInput = { + model: string + contents: GeminiContent[] + config?: GeminiConfig +} + +type GeminiContent = { + role: "user" | "model" + parts: Array<{ text: string }> +} + +type GeminiConfig = { + systemInstruction?: string + temperature?: number + maxOutputTokens?: number + tools?: unknown[] +} + +type GeminiGenerateResponse = { + text?: string + usageMetadata?: unknown + candidates?: Array<{ + finishReason?: string + content?: { + parts?: Array<{ text?: string }> + } + }> +} diff --git a/packages/gemini/tsconfig.json b/packages/gemini/tsconfig.json new file mode 100644 index 0000000..cf9e1b1 --- /dev/null +++ b/packages/gemini/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/packages/xai/README.md b/packages/xai/README.md new file mode 100644 index 0000000..554ef1f --- /dev/null +++ b/packages/xai/README.md @@ -0,0 +1,14 @@ +# @agent-memory/xai + +xAI model provider adapter for `agent-memory`, using the documented OpenAI SDK-compatible client path with xAI defaults. + +```ts +import { createAgent } from "agent-memory" +import { xai } from "@agent-memory/xai" + +const agent = createAgent({ + model: xai("grok-4") +}) +``` + +Set `XAI_API_KEY` in the environment, or pass `apiKey` explicitly. diff --git a/packages/xai/package.json b/packages/xai/package.json new file mode 100644 index 0000000..f2e9b6c --- /dev/null +++ b/packages/xai/package.json @@ -0,0 +1,48 @@ +{ + "name": "@agent-memory/xai", + "version": "0.0.0", + "description": "xAI model provider adapter for agent-memory using the documented OpenAI SDK path.", + "type": "module", + "author": "Gharibyan", + "homepage": "https://github.com/gharibyan/agent-memory#readme", + "bugs": { + "url": "https://github.com/gharibyan/agent-memory/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/gharibyan/agent-memory.git", + "directory": "packages/xai" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist", + "README.md", + "package.json" + ], + "scripts": { + "build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.json" + }, + "dependencies": { + "@agent-memory/core": "workspace:*", + "openai": "^6.10.0" + }, + "engines": { + "node": ">=20.12" + }, + "keywords": [ + "ai", + "agent", + "memory", + "xai", + "grok", + "llm" + ], + "license": "MIT" +} diff --git a/packages/xai/src/index.ts b/packages/xai/src/index.ts new file mode 100644 index 0000000..c154f5f --- /dev/null +++ b/packages/xai/src/index.ts @@ -0,0 +1,139 @@ +import OpenAI from "openai" +import { customModel } from "@agent-memory/core" +import type { AgentMessage, ModelProvider, ModelRequest, ModelResponse, ModelStreamChunk } from "@agent-memory/core" + +const XAI_BASE_URL = "https://api.x.ai/v1" + +export type XAIChatClient = { + chat: { + completions: { + create(input: XAIChatCompletionInput): Promise> + } + } +} + +export type XAIOptions = { + id?: string + model: string + apiKey?: string + baseURL?: string + headers?: Record + client?: XAIChatClient +} + +export function xai( + modelOrOptions: string | XAIOptions, + options: Partial = {} +): ModelProvider { + const config = typeof modelOrOptions === "string" + ? { model: modelOrOptions, ...options } + : modelOrOptions + + if (!config.model) { + throw new Error("xai provider requires a model") + } + + let client: XAIChatClient | undefined = config.client + + function sdk(): XAIChatClient { + if (!client) { + client = new OpenAI({ + apiKey: config.apiKey ?? process.env.XAI_API_KEY, + baseURL: config.baseURL ?? XAI_BASE_URL, + defaultHeaders: config.headers + }) as unknown as XAIChatClient + } + return client + } + + return customModel({ + id: config.id ?? `xai:${config.model}`, + capabilities: { + streaming: true, + tools: true, + jsonSchema: true + }, + async generate(request: ModelRequest): Promise { + const completion = await sdk().chat.completions.create(xaiChatCompletionInput(config.model, request, false)) + const json = completion as XAIChatCompletion + const choice = json.choices?.[0] + const text = textFromContent(choice?.message?.content) + + return { + text, + messages: text ? [{ role: "assistant", content: text }] : undefined, + toolCalls: choice?.message?.tool_calls, + usage: json.usage, + finishReason: choice?.finish_reason, + raw: json + } + }, + async *stream(request: ModelRequest): AsyncIterable { + const stream = await sdk().chat.completions.create(xaiChatCompletionInput(config.model, request, true)) + + for await (const chunk of stream as AsyncIterable) { + const text = chunk.choices?.[0]?.delta?.content + if (typeof text === "string" && text.length > 0) yield text + } + } + }) +} + +function xaiChatCompletionInput(model: string, request: ModelRequest, stream: boolean): XAIChatCompletionInput { + return { + model, + messages: request.messages.map(xaiMessage), + ...(request.tools ? { tools: request.tools } : {}), + ...(request.temperature === undefined ? {} : { temperature: request.temperature }), + ...(request.maxTokens === undefined ? {} : { max_tokens: request.maxTokens }), + stream + } +} + +function xaiMessage(message: AgentMessage): Record { + return { + role: message.role, + content: message.content, + ...(message.toolCallId ? { tool_call_id: message.toolCallId } : {}) + } +} + +function textFromContent(content: unknown): string { + if (typeof content === "string") return content + if (!Array.isArray(content)) return "" + + return content + .map((part) => { + if (part && typeof part === "object" && "text" in part) { + return String((part as { text?: unknown }).text ?? "") + } + return "" + }) + .join("") +} + +type XAIChatCompletion = { + choices?: Array<{ + message?: { + content?: unknown + tool_calls?: unknown[] + } + finish_reason?: string + }> + usage?: unknown +} + +type XAIChatCompletionChunk = { + choices?: Array<{ + delta?: { content?: string } + }> +} + +type XAIChatCompletionInput = { + model: string + messages: Array> + tools?: unknown[] + temperature?: number + max_tokens?: number + stream: boolean +} diff --git a/packages/xai/tsconfig.json b/packages/xai/tsconfig.json new file mode 100644 index 0000000..cf9e1b1 --- /dev/null +++ b/packages/xai/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": [ + "src/**/*.ts" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 716e611..bb10104 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,9 +32,15 @@ importers: packages/agent-memory: dependencies: + '@agent-memory/anthropic': + specifier: workspace:* + version: link:../anthropic '@agent-memory/core': specifier: workspace:* version: link:../core + '@agent-memory/gemini': + specifier: workspace:* + version: link:../gemini '@agent-memory/local': specifier: workspace:* version: link:../local @@ -47,9 +53,30 @@ importers: '@agent-memory/sqlite': specifier: workspace:* version: link:../sqlite + '@agent-memory/xai': + specifier: workspace:* + version: link:../xai + + packages/anthropic: + dependencies: + '@agent-memory/core': + specifier: workspace:* + version: link:../core + '@anthropic-ai/sdk': + specifier: ^0.104.1 + version: 0.104.1 packages/core: {} + packages/gemini: + dependencies: + '@agent-memory/core': + specifier: workspace:* + version: link:../core + '@google/genai': + specifier: ^2.8.0 + version: 2.8.0 + packages/local: dependencies: '@agent-memory/core': @@ -63,7 +90,7 @@ importers: version: link:../core openai: specifier: ^6.10.0 - version: 6.42.0 + version: 6.42.0(ws@8.21.0) packages/postgres: dependencies: @@ -83,8 +110,30 @@ importers: specifier: ^1.13.0 version: 1.14.1 + packages/xai: + dependencies: + '@agent-memory/core': + specifier: workspace:* + version: link:../core + openai: + specifier: ^6.10.0 + version: 6.42.0(ws@8.21.0) + packages: + '@anthropic-ai/sdk@0.104.1': + resolution: {integrity: sha512-gGACa/+IaiXzRRmF96aOhamoBgapKRBiFWbmmTFP8aMkpaEcuStF+Q61bjo4vPxBM7gqWJNZqsngslRdnLHv0Q==} + hasBin: true + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -123,6 +172,15 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@google/genai@2.8.0': + resolution: {integrity: sha512-pc2ayxqO5+O7AvnHBqpNHIk7PAZkHZgL31tbyx0gJZBSS9qPYiQoqwK7oYOw/ePmG6QY4EMSu+304vD5QlhXAw==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.25.2 + peerDependenciesMeta: + '@modelcontextprotocol/sdk': + optional: true + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -143,6 +201,39 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.2': + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + + '@stablelib/base64@1.0.1': + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} @@ -152,6 +243,9 @@ packages: '@types/node@20.19.43': resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@typescript-eslint/eslint-plugin@8.61.0': resolution: {integrity: sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -221,6 +315,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} @@ -238,6 +336,12 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + brace-expansion@1.1.15: resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} @@ -245,6 +349,9 @@ packages: resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -267,6 +374,10 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -279,6 +390,9 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -329,6 +443,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -338,6 +455,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -347,6 +467,10 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -362,6 +486,18 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + gaxios@7.1.5: + resolution: {integrity: sha512-5FZy72Rh8LhtjmvDrKkI+lVhrsQrVKVsItxMoDm5mNQE+xR0WVIIs+jzPSJgBvKVsLi24fZhXJIsNI0bihDzFg==} + engines: {node: '>=18'} + + gcp-metadata@8.1.2: + resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} + engines: {node: '>=18'} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -370,10 +506,22 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + google-auth-library@10.7.0: + resolution: {integrity: sha512-QpTAbNJ36TliZLx3TTtahR8HG0hN9RllL1e3FymOvQSIKK8JmgV58H924ub2wa2DsS3ANjjP1Aw1N+Ramc8hqQ==} + engines: {node: '>=18'} + + google-logging-utils@1.1.3: + resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} + engines: {node: '>=14'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -405,15 +553,28 @@ packages: resolution: {integrity: sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==} hasBin: true + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -428,6 +589,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -441,6 +605,15 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + openai@6.42.0: resolution: {integrity: sha512-1WFEt/uXMXOLhYRNkgJWo08Y2YNvNwpVU72K7ibrWgWpNOXd4VojXLbe6SQ4bLiUQ3Y8jz4IiyVkylJCL1DtZg==} peerDependencies: @@ -464,6 +637,10 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -534,6 +711,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + protobufjs@7.6.3: + resolution: {integrity: sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==} + engines: {node: '>=12.0.0'} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -542,6 +723,13 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + semver@7.8.4: resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==} engines: {node: '>=10'} @@ -562,6 +750,9 @@ packages: sql.js@1.14.1: resolution: {integrity: sha512-gcj8zBWU5cFsi9WUP+4bFNXAyF1iRpA3LLyS/DP5xlrNzGmPIizUeBggKa8DbDwdqaKwUcTEnChtd2grWo/x/A==} + standardwebhooks@1.0.0: + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -574,6 +765,9 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -602,6 +796,10 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -611,6 +809,18 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -621,6 +831,13 @@ packages: snapshots: + '@anthropic-ai/sdk@0.104.1': + dependencies: + json-schema-to-ts: 3.1.1 + standardwebhooks: 1.0.0 + + '@babel/runtime@7.29.7': {} + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': dependencies: eslint: 9.39.4 @@ -667,6 +884,17 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@google/genai@2.8.0': + dependencies: + google-auth-library: 10.7.0 + p-retry: 4.6.2 + protobufjs: 7.6.3 + ws: 8.21.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -683,6 +911,30 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + + '@stablelib/base64@1.0.1': {} + '@types/estree@1.0.9': {} '@types/json-schema@7.0.15': {} @@ -691,6 +943,8 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/retry@0.12.0': {} + '@typescript-eslint/eslint-plugin@8.61.0(@typescript-eslint/parser@8.61.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -788,6 +1042,8 @@ snapshots: acorn@8.16.0: {} + agent-base@7.1.4: {} + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -805,6 +1061,10 @@ snapshots: balanced-match@4.0.4: {} + base64-js@1.5.1: {} + + bignumber.js@9.3.1: {} + brace-expansion@1.1.15: dependencies: balanced-match: 1.0.2 @@ -814,6 +1074,8 @@ snapshots: dependencies: balanced-match: 4.0.4 + buffer-equal-constant-time@1.0.1: {} + callsites@3.1.0: {} chalk@4.1.2: @@ -835,12 +1097,18 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + data-uri-to-buffer@4.0.1: {} + debug@4.4.3: dependencies: ms: 2.1.3 deep-is@0.1.4: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + escape-string-regexp@4.0.0: {} eslint-scope@8.4.0: @@ -911,16 +1179,25 @@ snapshots: esutils@2.0.3: {} + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} + fast-sha256@1.3.0: {} + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -937,14 +1214,54 @@ snapshots: flatted@3.4.2: {} + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + gaxios@7.1.5: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + transitivePeerDependencies: + - supports-color + + gcp-metadata@8.1.2: + dependencies: + gaxios: 7.1.5 + google-logging-utils: 1.1.3 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 globals@14.0.0: {} + google-auth-library@10.7.0: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 7.1.5 + gcp-metadata: 8.1.2 + google-logging-utils: 1.1.3 + jws: 4.0.1 + transitivePeerDependencies: + - supports-color + + google-logging-utils@1.1.3: {} + has-flag@4.0.0: {} + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + ignore@5.3.2: {} ignore@7.0.5: {} @@ -968,12 +1285,32 @@ snapshots: dependencies: argparse: 2.0.1 + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + json-buffer@3.0.1: {} + json-schema-to-ts@3.1.1: + dependencies: + '@babel/runtime': 7.29.7 + ts-algebra: 2.0.0 + json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -989,6 +1326,8 @@ snapshots: lodash.merge@4.6.2: {} + long@5.3.2: {} + minimatch@10.2.5: dependencies: brace-expansion: 5.0.6 @@ -1001,7 +1340,17 @@ snapshots: natural-compare@1.4.0: {} - openai@6.42.0: {} + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + openai@6.42.0(ws@8.21.0): + optionalDependencies: + ws: 8.21.0 optionator@0.9.4: dependencies: @@ -1020,6 +1369,11 @@ snapshots: dependencies: p-limit: 3.1.0 + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -1077,10 +1431,29 @@ snapshots: prelude-ls@1.2.1: {} + protobufjs@7.6.3: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/node': 20.19.43 + long: 5.3.2 + punycode@2.3.1: {} resolve-from@4.0.0: {} + retry@0.13.1: {} + + safe-buffer@5.2.1: {} + semver@7.8.4: {} shebang-command@2.0.0: @@ -1093,6 +1466,11 @@ snapshots: sql.js@1.14.1: {} + standardwebhooks@1.0.0: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + strip-json-comments@3.1.1: {} supports-color@7.2.0: @@ -1104,6 +1482,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + ts-algebra@2.0.0: {} + ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -1131,12 +1511,16 @@ snapshots: dependencies: punycode: 2.3.1 + web-streams-polyfill@3.3.3: {} + which@2.0.2: dependencies: isexe: 2.0.0 word-wrap@1.2.5: {} + ws@8.21.0: {} + xtend@4.0.2: {} yocto-queue@0.1.0: {} diff --git a/skills/agent-memory/SKILL.md b/skills/agent-memory/SKILL.md index 5ce4725..3da5341 100644 --- a/skills/agent-memory/SKILL.md +++ b/skills/agent-memory/SKILL.md @@ -25,6 +25,9 @@ Use this when: - `@agent-memory/sqlite`: real SQLite `.memory/memory.sqlite` persistence. - `@agent-memory/postgres`: Postgres persistence with automatic migrations and pgvector retrieval. - `@agent-memory/openai`: OpenAI chat completions through the official `openai` SDK, including custom `baseURL` providers for compatible endpoints. +- `@agent-memory/anthropic`: Anthropic messages through the official `@anthropic-ai/sdk` package. +- `@agent-memory/gemini`: Gemini generation through the official `@google/genai` package. +- `@agent-memory/xai`: xAI chat completions through the documented OpenAI SDK-compatible client path with xAI defaults. - `agent-memory`: public convenience package. `createAgent({ model })` should work with automatic local memory. ## Basic Usage @@ -54,7 +57,7 @@ const result = await agent.generate({ ## Extending -For a provider adapter, create a package like `@agent-memory/openai` and return a `ModelProvider`. Major first-party provider packages should depend on the provider's official SDK; OpenAI-compatible wrappers are for custom model endpoints. +For a provider adapter, create a package like `@agent-memory/openai` and return a `ModelProvider`. Major first-party provider packages should depend on the provider's official SDK when one exists; OpenAI-compatible wrappers are for custom model endpoints and documented compatible providers. For a storage adapter, create a package like `@agent-memory/local` and implement `MemoryStore`. Database adapters should own their migration lifecycle instead of making application code run setup manually. diff --git a/test/adapter-packages.test.mjs b/test/adapter-packages.test.mjs index fe2cd83..39d127a 100644 --- a/test/adapter-packages.test.mjs +++ b/test/adapter-packages.test.mjs @@ -32,6 +32,60 @@ test("openai-compatible provider lives in its own publishable adapter package", assert.doesNotMatch(coreIndex, /openai/) }) +test("anthropic provider lives in its own official SDK adapter package", async () => { + const packageJson = await readJson("packages/anthropic/package.json") + const source = await read("packages/anthropic/src/index.ts") + const coreIndex = await read("packages/core/src/index.ts") + + assert.equal(packageJson.name, "@agent-memory/anthropic") + assert.equal(packageJson.main, "./dist/index.js") + assert.equal(packageJson.types, "./dist/index.d.ts") + assert.deepEqual(packageJson.files, ["dist", "README.md", "package.json"]) + assert.equal(packageJson.dependencies["@agent-memory/core"], "workspace:*") + assert.match(packageJson.dependencies["@anthropic-ai/sdk"], /^\^/) + assert.match(source, /from "@anthropic-ai\/sdk"/) + assert.match(source, /export function anthropic/) + assert.doesNotMatch(source, /fetch\(/) + assert.doesNotMatch(coreIndex, /@anthropic-ai\/sdk/) + assert.doesNotMatch(coreIndex, /anthropic/) +}) + +test("gemini provider lives in its own official SDK adapter package", async () => { + const packageJson = await readJson("packages/gemini/package.json") + const source = await read("packages/gemini/src/index.ts") + const coreIndex = await read("packages/core/src/index.ts") + + assert.equal(packageJson.name, "@agent-memory/gemini") + assert.equal(packageJson.main, "./dist/index.js") + assert.equal(packageJson.types, "./dist/index.d.ts") + assert.deepEqual(packageJson.files, ["dist", "README.md", "package.json"]) + assert.equal(packageJson.dependencies["@agent-memory/core"], "workspace:*") + assert.match(packageJson.dependencies["@google/genai"], /^\^/) + assert.match(source, /from "@google\/genai"/) + assert.match(source, /export function gemini/) + assert.doesNotMatch(source, /fetch\(/) + assert.doesNotMatch(coreIndex, /@google\/genai/) + assert.doesNotMatch(coreIndex, /gemini/) +}) + +test("xai provider lives in its own documented SDK adapter package", async () => { + const packageJson = await readJson("packages/xai/package.json") + const source = await read("packages/xai/src/index.ts") + const coreIndex = await read("packages/core/src/index.ts") + + assert.equal(packageJson.name, "@agent-memory/xai") + assert.equal(packageJson.main, "./dist/index.js") + assert.equal(packageJson.types, "./dist/index.d.ts") + assert.deepEqual(packageJson.files, ["dist", "README.md", "package.json"]) + assert.equal(packageJson.dependencies["@agent-memory/core"], "workspace:*") + assert.match(packageJson.dependencies.openai, /^\^/) + assert.match(source, /from "openai"/) + assert.match(source, /https:\/\/api\.x\.ai\/v1/) + assert.match(source, /export function xai/) + assert.doesNotMatch(source, /fetch\(/) + assert.doesNotMatch(coreIndex, /xai/) +}) + test("local persistence lives in its own publishable storage adapter package", async () => { const packageJson = await readJson("packages/local/package.json") const source = await read("packages/local/src/index.ts") @@ -87,15 +141,21 @@ test("public package re-exports the openai adapter as a convenience import", asy const packageJson = await readJson("packages/agent-memory/package.json") const source = await read("packages/agent-memory/src/index.ts") + assert.equal(packageJson.dependencies["@agent-memory/anthropic"], "workspace:*") + assert.equal(packageJson.dependencies["@agent-memory/gemini"], "workspace:*") assert.equal(packageJson.dependencies["@agent-memory/openai"], "workspace:*") assert.equal(packageJson.dependencies["@agent-memory/local"], "workspace:*") assert.equal(packageJson.dependencies["@agent-memory/sqlite"], "workspace:*") assert.equal(packageJson.dependencies["@agent-memory/postgres"], "workspace:*") + assert.equal(packageJson.dependencies["@agent-memory/xai"], "workspace:*") assert.match(source, /export \* from "@agent-memory\/core"/) + assert.match(source, /export \{ anthropic \} from "@agent-memory\/anthropic"/) + assert.match(source, /export \{ gemini \} from "@agent-memory\/gemini"/) assert.match(source, /export \{ openAICompatible, openai \} from "@agent-memory\/openai"/) assert.match(source, /export \{ localMemory \} from "@agent-memory\/local"/) assert.match(source, /export \{ sqliteMemory \} from "@agent-memory\/sqlite"/) assert.match(source, /export \{ postgresMemory \} from "@agent-memory\/postgres"/) + assert.match(source, /export \{ xai \} from "@agent-memory\/xai"/) }) test("built openai adapter exposes OpenAI-compatible provider helpers", async () => { @@ -136,6 +196,16 @@ test("built postgres adapter exposes pgvector-backed memory helper", async () => assert.equal(postgresMemory({ pool: createNoopPool() }).kind, "postgres") }) +test("built provider adapters expose first-class provider helpers", async () => { + const { anthropic } = await import("../packages/anthropic/dist/index.js") + const { gemini } = await import("../packages/gemini/dist/index.js") + const { xai } = await import("../packages/xai/dist/index.js") + + assert.equal(anthropic("anthropic-model", { apiKey: "test-key" }).id, "anthropic:anthropic-model") + assert.equal(gemini("gemini-test", { apiKey: "test-key" }).id, "gemini:gemini-test") + assert.equal(xai("grok-test", { apiKey: "test-key" }).id, "xai:grok-test") +}) + function createNoopPool() { return { async query() { diff --git a/test/automation.test.mjs b/test/automation.test.mjs index 701ea0f..24fcda5 100644 --- a/test/automation.test.mjs +++ b/test/automation.test.mjs @@ -29,6 +29,9 @@ test("github ci runs lint, tests, and package boundary check", async () => { assert.match(workflow, /pnpm --filter @agent-memory\/sqlite pack --dry-run/) assert.match(workflow, /pnpm --filter @agent-memory\/postgres pack --dry-run/) assert.match(workflow, /pnpm --filter @agent-memory\/openai pack --dry-run/) + assert.match(workflow, /pnpm --filter @agent-memory\/anthropic pack --dry-run/) + assert.match(workflow, /pnpm --filter @agent-memory\/gemini pack --dry-run/) + assert.match(workflow, /pnpm --filter @agent-memory\/xai pack --dry-run/) assert.match(workflow, /pnpm --filter agent-memory pack --dry-run/) }) @@ -46,13 +49,16 @@ test("github publish workflow is tag gated and syncs package version from tag", assert.match(workflow, /pnpm --filter @agent-memory\/sqlite publish --access public --no-git-checks/) assert.match(workflow, /pnpm --filter @agent-memory\/postgres publish --access public --no-git-checks/) assert.match(workflow, /pnpm --filter @agent-memory\/openai publish --access public --no-git-checks/) + assert.match(workflow, /pnpm --filter @agent-memory\/anthropic publish --access public --no-git-checks/) + assert.match(workflow, /pnpm --filter @agent-memory\/gemini publish --access public --no-git-checks/) + assert.match(workflow, /pnpm --filter @agent-memory\/xai publish --access public --no-git-checks/) assert.match(workflow, /pnpm --filter agent-memory publish --access public --no-git-checks/) }) test("root package exposes lint and version sync scripts", async () => { const packageJson = await readJson("package.json") - assert.equal(packageJson.scripts.build, "pnpm --filter @agent-memory/core build && pnpm --filter @agent-memory/local build && pnpm --filter @agent-memory/sqlite build && pnpm --filter @agent-memory/postgres build && pnpm --filter @agent-memory/openai build && pnpm --filter agent-memory build") + assert.equal(packageJson.scripts.build, "pnpm --filter @agent-memory/core build && pnpm --filter @agent-memory/local build && pnpm --filter @agent-memory/sqlite build && pnpm --filter @agent-memory/postgres build && pnpm --filter @agent-memory/openai build && pnpm --filter @agent-memory/anthropic build && pnpm --filter @agent-memory/gemini build && pnpm --filter @agent-memory/xai build && pnpm --filter agent-memory build") assert.equal(packageJson.scripts.lint, "eslint .") assert.equal(packageJson.scripts["version:from-tag"], "node scripts/sync-package-version-from-tag.mjs") }) @@ -73,25 +79,39 @@ test("version sync script updates the publishable package from a v-prefixed tag" try { const { mkdir } = await import("node:fs/promises") const publicPackageDir = join(tempDir, "packages", "agent-memory") + const anthropicPackageDir = join(tempDir, "packages", "anthropic") const corePackageDir = join(tempDir, "packages", "core") + const geminiPackageDir = join(tempDir, "packages", "gemini") const localPackageDir = join(tempDir, "packages", "local") const openaiPackageDir = join(tempDir, "packages", "openai") const postgresPackageDir = join(tempDir, "packages", "postgres") const sqlitePackageDir = join(tempDir, "packages", "sqlite") + const xaiPackageDir = join(tempDir, "packages", "xai") await mkdir(publicPackageDir, { recursive: true }) + await mkdir(anthropicPackageDir, { recursive: true }) await mkdir(corePackageDir, { recursive: true }) + await mkdir(geminiPackageDir, { recursive: true }) await mkdir(localPackageDir, { recursive: true }) await mkdir(openaiPackageDir, { recursive: true }) await mkdir(postgresPackageDir, { recursive: true }) await mkdir(sqlitePackageDir, { recursive: true }) + await mkdir(xaiPackageDir, { recursive: true }) await writeFile(join(publicPackageDir, "package.json"), JSON.stringify({ name: "agent-memory", version: "0.0.0" }, null, 2)) + await writeFile(join(anthropicPackageDir, "package.json"), JSON.stringify({ + name: "@agent-memory/anthropic", + version: "0.0.0" + }, null, 2)) await writeFile(join(corePackageDir, "package.json"), JSON.stringify({ name: "@agent-memory/core", version: "0.0.0" }, null, 2)) + await writeFile(join(geminiPackageDir, "package.json"), JSON.stringify({ + name: "@agent-memory/gemini", + version: "0.0.0" + }, null, 2)) await writeFile(join(localPackageDir, "package.json"), JSON.stringify({ name: "@agent-memory/local", version: "0.0.0" @@ -108,33 +128,46 @@ test("version sync script updates the publishable package from a v-prefixed tag" name: "@agent-memory/sqlite", version: "0.0.0" }, null, 2)) + await writeFile(join(xaiPackageDir, "package.json"), JSON.stringify({ + name: "@agent-memory/xai", + version: "0.0.0" + }, null, 2)) const { syncVersionFromTag } = await import(pathToFileURL(new URL("scripts/sync-package-version-from-tag.mjs", root).pathname)) const result = await syncVersionFromTag("v1.2.3", { rootDir: tempDir }) const packageJson = JSON.parse(await readFile(join(publicPackageDir, "package.json"), "utf8")) + const anthropicPackageJson = JSON.parse(await readFile(join(anthropicPackageDir, "package.json"), "utf8")) const corePackageJson = JSON.parse(await readFile(join(corePackageDir, "package.json"), "utf8")) + const geminiPackageJson = JSON.parse(await readFile(join(geminiPackageDir, "package.json"), "utf8")) const localPackageJson = JSON.parse(await readFile(join(localPackageDir, "package.json"), "utf8")) const openaiPackageJson = JSON.parse(await readFile(join(openaiPackageDir, "package.json"), "utf8")) const postgresPackageJson = JSON.parse(await readFile(join(postgresPackageDir, "package.json"), "utf8")) const sqlitePackageJson = JSON.parse(await readFile(join(sqlitePackageDir, "package.json"), "utf8")) + const xaiPackageJson = JSON.parse(await readFile(join(xaiPackageDir, "package.json"), "utf8")) assert.equal(result.version, "1.2.3") assert.deepEqual(result.updatedPackagePaths.sort(), [ "packages/agent-memory/package.json", + "packages/anthropic/package.json", "packages/core/package.json", + "packages/gemini/package.json", "packages/local/package.json", "packages/openai/package.json", "packages/postgres/package.json", - "packages/sqlite/package.json" + "packages/sqlite/package.json", + "packages/xai/package.json" ].sort()) assert.equal(packageJson.version, "1.2.3") + assert.equal(anthropicPackageJson.version, "1.2.3") assert.equal(corePackageJson.version, "1.2.3") + assert.equal(geminiPackageJson.version, "1.2.3") assert.equal(localPackageJson.version, "1.2.3") assert.equal(openaiPackageJson.version, "1.2.3") assert.equal(postgresPackageJson.version, "1.2.3") assert.equal(sqlitePackageJson.version, "1.2.3") + assert.equal(xaiPackageJson.version, "1.2.3") } finally { await rm(tempDir, { recursive: true, force: true }) } diff --git a/test/package-boundary.test.mjs b/test/package-boundary.test.mjs index cbe3be3..041498b 100644 --- a/test/package-boundary.test.mjs +++ b/test/package-boundary.test.mjs @@ -70,6 +70,34 @@ test("published local adapter package uses a restrictive files allowlist", async assert.equal(packageJson.files.includes(".ai-memory"), false) }) +test("published anthropic adapter package uses a restrictive files allowlist", async () => { + const packageJson = await readJson("packages/anthropic/package.json") + + assert.equal(packageJson.private, undefined) + assert.deepEqual(packageJson.files, [ + "dist", + "README.md", + "package.json" + ]) + assert.equal(packageJson.files.includes("../../apps/playground"), false) + assert.equal(packageJson.files.includes(".memory"), false) + assert.equal(packageJson.files.includes(".ai-memory"), false) +}) + +test("published gemini adapter package uses a restrictive files allowlist", async () => { + const packageJson = await readJson("packages/gemini/package.json") + + assert.equal(packageJson.private, undefined) + assert.deepEqual(packageJson.files, [ + "dist", + "README.md", + "package.json" + ]) + assert.equal(packageJson.files.includes("../../apps/playground"), false) + assert.equal(packageJson.files.includes(".memory"), false) + assert.equal(packageJson.files.includes(".ai-memory"), false) +}) + test("published sqlite adapter package uses a restrictive files allowlist", async () => { const packageJson = await readJson("packages/sqlite/package.json") @@ -111,3 +139,17 @@ test("published openai adapter package uses a restrictive files allowlist", asyn assert.equal(packageJson.files.includes(".memory"), false) assert.equal(packageJson.files.includes(".ai-memory"), false) }) + +test("published xai adapter package uses a restrictive files allowlist", async () => { + const packageJson = await readJson("packages/xai/package.json") + + assert.equal(packageJson.private, undefined) + assert.deepEqual(packageJson.files, [ + "dist", + "README.md", + "package.json" + ]) + assert.equal(packageJson.files.includes("../../apps/playground"), false) + assert.equal(packageJson.files.includes(".memory"), false) + assert.equal(packageJson.files.includes(".ai-memory"), false) +}) diff --git a/test/provider-sdk-adapters.test.mjs b/test/provider-sdk-adapters.test.mjs new file mode 100644 index 0000000..9e0799e --- /dev/null +++ b/test/provider-sdk-adapters.test.mjs @@ -0,0 +1,154 @@ +import assert from "node:assert/strict" +import { test } from "node:test" + +test("anthropic adapter generates and streams through the official SDK client shape", async () => { + const { anthropic } = await import("../packages/anthropic/dist/index.js") + const calls = [] + const provider = anthropic({ + id: "anthropic-test", + model: "anthropic-model", + client: { + messages: { + async create(input) { + calls.push(input) + if (input.stream) { + return (async function * events() { + yield { type: "content_block_delta", delta: { type: "text_delta", text: "hel" } } + yield { type: "content_block_delta", delta: { type: "text_delta", text: "lo" } } + })() + } + + return { + content: [{ type: "text", text: "anthropic text" }], + stop_reason: "end_turn", + usage: { input_tokens: 3, output_tokens: 4 } + } + } + } + } + }) + + const result = await provider.generate({ + system: "system rule", + messages: [{ role: "user", content: "hello" }], + temperature: 0.3, + maxTokens: 77 + }) + const chunks = [] + for await (const chunk of provider.stream({ messages: [{ role: "user", content: "stream" }] })) { + chunks.push(chunk) + } + + assert.equal(provider.id, "anthropic-test") + assert.equal(result.text, "anthropic text") + assert.equal(result.finishReason, "end_turn") + assert.deepEqual(result.usage, { input_tokens: 3, output_tokens: 4 }) + assert.equal(calls[0].model, "anthropic-model") + assert.equal(calls[0].system, "system rule") + assert.deepEqual(calls[0].messages, [{ role: "user", content: "hello" }]) + assert.equal(calls[0].temperature, 0.3) + assert.equal(calls[0].max_tokens, 77) + assert.equal(calls[0].stream, false) + assert.deepEqual(chunks, ["hel", "lo"]) +}) + +test("gemini adapter generates and streams through the official SDK client shape", async () => { + const { gemini } = await import("../packages/gemini/dist/index.js") + const calls = [] + const provider = gemini({ + model: "gemini-test", + client: { + models: { + async generateContent(input) { + calls.push(["generate", input]) + return { text: "gemini text", usageMetadata: { totalTokenCount: 12 } } + }, + async generateContentStream(input) { + calls.push(["stream", input]) + return (async function * chunks() { + yield { text: "gem" } + yield { candidates: [{ content: { parts: [{ text: "ini" }] } }] } + })() + } + } + } + }) + + const result = await provider.generate({ + system: "gemini system", + messages: [{ role: "user", content: "hello" }], + temperature: 0.4, + maxTokens: 88 + }) + const chunks = [] + for await (const chunk of provider.stream({ messages: [{ role: "user", content: "stream" }] })) { + chunks.push(chunk) + } + + assert.equal(provider.id, "gemini:gemini-test") + assert.equal(result.text, "gemini text") + assert.deepEqual(result.usage, { totalTokenCount: 12 }) + assert.equal(calls[0][0], "generate") + assert.equal(calls[0][1].model, "gemini-test") + assert.deepEqual(calls[0][1].contents, [{ role: "user", parts: [{ text: "hello" }] }]) + assert.deepEqual(calls[0][1].config, { + systemInstruction: "gemini system", + temperature: 0.4, + maxOutputTokens: 88 + }) + assert.deepEqual(chunks, ["gem", "ini"]) +}) + +test("xai adapter uses the documented OpenAI SDK client shape with xAI defaults", async () => { + const { xai } = await import("../packages/xai/dist/index.js") + const calls = [] + const provider = xai({ + model: "grok-test", + client: { + chat: { + completions: { + async create(input) { + calls.push(input) + if (input.stream) { + return (async function * chunks() { + yield { choices: [{ delta: { content: "gr" } }] } + yield { choices: [{ delta: { content: "ok" } }] } + })() + } + + return { + choices: [{ + message: { content: "xai text" }, + finish_reason: "stop" + }], + usage: { total_tokens: 10 } + } + } + } + } + } + }) + + const result = await provider.generate({ + messages: [{ role: "user", content: "hello" }], + temperature: 0.5, + maxTokens: 99 + }) + const chunks = [] + for await (const chunk of provider.stream({ messages: [{ role: "user", content: "stream" }] })) { + chunks.push(chunk) + } + + assert.equal(provider.id, "xai:grok-test") + assert.equal(result.text, "xai text") + assert.equal(result.finishReason, "stop") + assert.deepEqual(result.usage, { total_tokens: 10 }) + assert.deepEqual(calls[0], { + model: "grok-test", + messages: [{ role: "user", content: "hello" }], + temperature: 0.5, + max_tokens: 99, + stream: false + }) + assert.deepEqual(chunks, ["gr", "ok"]) +}) diff --git a/test/repository-hygiene.test.mjs b/test/repository-hygiene.test.mjs index 0084482..36f0c26 100644 --- a/test/repository-hygiene.test.mjs +++ b/test/repository-hygiene.test.mjs @@ -39,11 +39,14 @@ test("publishable packages point to the gharibyan GitHub repository", async () = for (const path of [ "package.json", "packages/agent-memory/package.json", + "packages/anthropic/package.json", "packages/core/package.json", + "packages/gemini/package.json", "packages/local/package.json", "packages/sqlite/package.json", "packages/postgres/package.json", - "packages/openai/package.json" + "packages/openai/package.json", + "packages/xai/package.json" ]) { const packageJson = await readJson(path) diff --git a/test/typescript-structure.test.mjs b/test/typescript-structure.test.mjs index 505ee5c..7ff175f 100644 --- a/test/typescript-structure.test.mjs +++ b/test/typescript-structure.test.mjs @@ -25,34 +25,46 @@ async function listFiles(dir, suffix) { test("source implementation is TypeScript-first", async () => { const coreSource = await listFiles("packages/core/src", ".ts") const publicSource = await listFiles("packages/agent-memory/src", ".ts") + const anthropicSource = await listFiles("packages/anthropic/src", ".ts") + const geminiSource = await listFiles("packages/gemini/src", ".ts") const localSource = await listFiles("packages/local/src", ".ts") const openaiSource = await listFiles("packages/openai/src", ".ts") const postgresSource = await listFiles("packages/postgres/src", ".ts") const sqliteSource = await listFiles("packages/sqlite/src", ".ts") + const xaiSource = await listFiles("packages/xai/src", ".ts") const javascriptSource = [ ...await listFiles("packages/core/src", ".js"), ...await listFiles("packages/agent-memory/src", ".js"), + ...await listFiles("packages/anthropic/src", ".js"), + ...await listFiles("packages/gemini/src", ".js"), ...await listFiles("packages/local/src", ".js"), ...await listFiles("packages/openai/src", ".js"), ...await listFiles("packages/postgres/src", ".js"), - ...await listFiles("packages/sqlite/src", ".js") + ...await listFiles("packages/sqlite/src", ".js"), + ...await listFiles("packages/xai/src", ".js") ] assert.ok(coreSource.length > 4) + assert.ok(anthropicSource.some((path) => path.endsWith("index.ts"))) + assert.ok(geminiSource.some((path) => path.endsWith("index.ts"))) assert.ok(localSource.some((path) => path.endsWith("index.ts"))) assert.ok(openaiSource.some((path) => path.endsWith("index.ts"))) assert.ok(postgresSource.some((path) => path.endsWith("index.ts"))) assert.ok(sqliteSource.some((path) => path.endsWith("index.ts"))) + assert.ok(xaiSource.some((path) => path.endsWith("index.ts"))) assert.deepEqual(javascriptSource, []) assert.ok(publicSource.some((path) => path.endsWith("index.ts"))) }) test("package structure separates core runtime, adapters, and public package", async () => { const corePackage = await readJson("packages/core/package.json") + const anthropicPackage = await readJson("packages/anthropic/package.json") + const geminiPackage = await readJson("packages/gemini/package.json") const localPackage = await readJson("packages/local/package.json") const openaiPackage = await readJson("packages/openai/package.json") const postgresPackage = await readJson("packages/postgres/package.json") const sqlitePackage = await readJson("packages/sqlite/package.json") + const xaiPackage = await readJson("packages/xai/package.json") const publicPackage = await readJson("packages/agent-memory/package.json") const publicIndex = await read("packages/agent-memory/src/index.ts") @@ -64,16 +76,36 @@ test("package structure separates core runtime, adapters, and public package", a assert.equal(publicPackage.name, "agent-memory") assert.equal(publicPackage.main, "./dist/index.js") assert.equal(publicPackage.types, "./dist/index.d.ts") + assert.equal(publicPackage.dependencies["@agent-memory/anthropic"], "workspace:*") assert.equal(publicPackage.dependencies["@agent-memory/core"], "workspace:*") + assert.equal(publicPackage.dependencies["@agent-memory/gemini"], "workspace:*") assert.equal(publicPackage.dependencies["@agent-memory/local"], "workspace:*") assert.equal(publicPackage.dependencies["@agent-memory/openai"], "workspace:*") assert.equal(publicPackage.dependencies["@agent-memory/postgres"], "workspace:*") assert.equal(publicPackage.dependencies["@agent-memory/sqlite"], "workspace:*") + assert.equal(publicPackage.dependencies["@agent-memory/xai"], "workspace:*") assert.match(publicIndex, /export \* from "@agent-memory\/core"/) + assert.match(publicIndex, /export \{ anthropic \} from "@agent-memory\/anthropic"/) + assert.match(publicIndex, /export \{ gemini \} from "@agent-memory\/gemini"/) assert.match(publicIndex, /export \{ localMemory \} from "@agent-memory\/local"/) assert.match(publicIndex, /export \{ openAICompatible, openai \} from "@agent-memory\/openai"/) assert.match(publicIndex, /export \{ postgresMemory \} from "@agent-memory\/postgres"/) assert.match(publicIndex, /export \{ sqliteMemory \} from "@agent-memory\/sqlite"/) + assert.match(publicIndex, /export \{ xai \} from "@agent-memory\/xai"/) + + assert.equal(anthropicPackage.name, "@agent-memory/anthropic") + assert.equal(anthropicPackage.main, "./dist/index.js") + assert.equal(anthropicPackage.types, "./dist/index.d.ts") + assert.equal(anthropicPackage.dependencies["@agent-memory/core"], "workspace:*") + assert.match(anthropicPackage.dependencies["@anthropic-ai/sdk"], /^\^/) + assert.deepEqual(anthropicPackage.files, ["dist", "README.md", "package.json"]) + + assert.equal(geminiPackage.name, "@agent-memory/gemini") + assert.equal(geminiPackage.main, "./dist/index.js") + assert.equal(geminiPackage.types, "./dist/index.d.ts") + assert.equal(geminiPackage.dependencies["@agent-memory/core"], "workspace:*") + assert.match(geminiPackage.dependencies["@google/genai"], /^\^/) + assert.deepEqual(geminiPackage.files, ["dist", "README.md", "package.json"]) assert.equal(localPackage.name, "@agent-memory/local") assert.equal(localPackage.main, "./dist/index.js") @@ -100,18 +132,28 @@ test("package structure separates core runtime, adapters, and public package", a assert.equal(sqlitePackage.dependencies["@agent-memory/core"], "workspace:*") assert.match(sqlitePackage.dependencies["sql.js"], /^\^/) assert.deepEqual(sqlitePackage.files, ["dist", "README.md", "package.json"]) + + assert.equal(xaiPackage.name, "@agent-memory/xai") + assert.equal(xaiPackage.main, "./dist/index.js") + assert.equal(xaiPackage.types, "./dist/index.d.ts") + assert.equal(xaiPackage.dependencies["@agent-memory/core"], "workspace:*") + assert.match(xaiPackage.dependencies.openai, /^\^/) + assert.deepEqual(xaiPackage.files, ["dist", "README.md", "package.json"]) }) test("root scripts build TypeScript before test and package checks", async () => { const packageJson = await readJson("package.json") - assert.equal(packageJson.scripts.build, "pnpm --filter @agent-memory/core build && pnpm --filter @agent-memory/local build && pnpm --filter @agent-memory/sqlite build && pnpm --filter @agent-memory/postgres build && pnpm --filter @agent-memory/openai build && pnpm --filter agent-memory build") + assert.equal(packageJson.scripts.build, "pnpm --filter @agent-memory/core build && pnpm --filter @agent-memory/local build && pnpm --filter @agent-memory/sqlite build && pnpm --filter @agent-memory/postgres build && pnpm --filter @agent-memory/openai build && pnpm --filter @agent-memory/anthropic build && pnpm --filter @agent-memory/gemini build && pnpm --filter @agent-memory/xai build && pnpm --filter agent-memory build") assert.match(packageJson.scripts.test, /pnpm build/) assert.match(packageJson.scripts["pack:check"], /@agent-memory\/core/) assert.match(packageJson.scripts["pack:check"], /@agent-memory\/local/) assert.match(packageJson.scripts["pack:check"], /@agent-memory\/sqlite/) assert.match(packageJson.scripts["pack:check"], /@agent-memory\/postgres/) assert.match(packageJson.scripts["pack:check"], /@agent-memory\/openai/) + assert.match(packageJson.scripts["pack:check"], /@agent-memory\/anthropic/) + assert.match(packageJson.scripts["pack:check"], /@agent-memory\/gemini/) + assert.match(packageJson.scripts["pack:check"], /@agent-memory\/xai/) assert.match(packageJson.devDependencies.typescript, /^\^/) }) From a7f4bf959e246d319600f63ff399f22e1eb87f74 Mon Sep 17 00:00:00 2001 From: gharibyan Date: Thu, 11 Jun 2026 11:16:03 +0400 Subject: [PATCH 2/2] Fix README hygiene test --- README.md | 2 -- test/repository-hygiene.test.mjs | 1 - 2 files changed, 3 deletions(-) diff --git a/README.md b/README.md index 5a67249..10d81c9 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ TypeScript SDK for building AI agents with automatic, scoped, persistent memory. `agent-memory` wraps model calls with memory recall and learning so apps can keep useful user, thread, and operation context without manually stuffing long chat histories into every prompt. -Repository: [github.com/gharibyan/agent-memory](https://github.com/gharibyan/agent-memory) - ## Install ```sh diff --git a/test/repository-hygiene.test.mjs b/test/repository-hygiene.test.mjs index 36f0c26..4856f07 100644 --- a/test/repository-hygiene.test.mjs +++ b/test/repository-hygiene.test.mjs @@ -29,7 +29,6 @@ test("root README and MIT license are present", async () => { const license = await read("LICENSE") assert.match(readme, /^# agent-memory/m) - assert.match(readme, /github\.com\/gharibyan\/agent-memory/) assert.match(readme, /MIT License/) assert.match(license, /^MIT License/m) assert.match(license, /Gharibyan/)