From b7e5ea196f2ee56afa955cb3e0f85a7514838151 Mon Sep 17 00:00:00 2001 From: Nikos Douvlis Date: Tue, 11 Feb 2025 02:42:04 +0200 Subject: [PATCH] feat(agent-toolkit): Introduce @clerk/agent-toolkit package --- packages/agent-toolkit/LICENSE | 21 ++ packages/agent-toolkit/README.md | 230 +++++++++++++++ packages/agent-toolkit/package.json | 66 +++++ packages/agent-toolkit/src/ai-sdk/adapter.ts | 12 + packages/agent-toolkit/src/ai-sdk/index.ts | 43 +++ packages/agent-toolkit/src/global.d.ts | 6 + .../agent-toolkit/src/langchain/adapter.ts | 13 + packages/agent-toolkit/src/langchain/index.ts | 48 ++++ .../agent-toolkit/src/lib/clerk-client.ts | 22 ++ packages/agent-toolkit/src/lib/clerk-tool.ts | 55 ++++ packages/agent-toolkit/src/lib/constants.ts | 5 + .../src/lib/inject-session-claims.ts | 18 ++ packages/agent-toolkit/src/lib/tools/index.ts | 15 + .../agent-toolkit/src/lib/tools/user-tools.ts | 52 ++++ packages/agent-toolkit/src/lib/types.ts | 26 ++ packages/agent-toolkit/src/lib/utils.ts | 66 +++++ packages/agent-toolkit/tsconfig.json | 19 ++ packages/agent-toolkit/tsconfig.test.json | 6 + packages/agent-toolkit/tsup.config.ts | 23 ++ packages/backend/src/api/resources/User.ts | 10 +- pnpm-lock.yaml | 263 +++++++++++++++++- 21 files changed, 1010 insertions(+), 9 deletions(-) create mode 100644 packages/agent-toolkit/LICENSE create mode 100644 packages/agent-toolkit/README.md create mode 100644 packages/agent-toolkit/package.json create mode 100644 packages/agent-toolkit/src/ai-sdk/adapter.ts create mode 100644 packages/agent-toolkit/src/ai-sdk/index.ts create mode 100644 packages/agent-toolkit/src/global.d.ts create mode 100644 packages/agent-toolkit/src/langchain/adapter.ts create mode 100644 packages/agent-toolkit/src/langchain/index.ts create mode 100644 packages/agent-toolkit/src/lib/clerk-client.ts create mode 100644 packages/agent-toolkit/src/lib/clerk-tool.ts create mode 100644 packages/agent-toolkit/src/lib/constants.ts create mode 100644 packages/agent-toolkit/src/lib/inject-session-claims.ts create mode 100644 packages/agent-toolkit/src/lib/tools/index.ts create mode 100644 packages/agent-toolkit/src/lib/tools/user-tools.ts create mode 100644 packages/agent-toolkit/src/lib/types.ts create mode 100644 packages/agent-toolkit/src/lib/utils.ts create mode 100644 packages/agent-toolkit/tsconfig.json create mode 100644 packages/agent-toolkit/tsconfig.test.json create mode 100644 packages/agent-toolkit/tsup.config.ts diff --git a/packages/agent-toolkit/LICENSE b/packages/agent-toolkit/LICENSE new file mode 100644 index 00000000000..66914b6af7c --- /dev/null +++ b/packages/agent-toolkit/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Clerk, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/agent-toolkit/README.md b/packages/agent-toolkit/README.md new file mode 100644 index 00000000000..dddab065a8c --- /dev/null +++ b/packages/agent-toolkit/README.md @@ -0,0 +1,230 @@ +

+ + + + + + +
+

@clerk/agent-toolkit

+

+ +
+ +[![Chat on Discord](https://img.shields.io/discord/856971667393609759.svg?logo=discord)](https://clerk.com/discord) +[![Clerk documentation](https://img.shields.io/badge/documentation-clerk-green.svg)](https://clerk.com/docs?utm_source=github&utm_medium=clerk_agent_toolkit) +[![Follow on Twitter](https://img.shields.io/twitter/follow/ClerkDev?style=social)](https://twitter.com/intent/follow?screen_name=ClerkDev) + +[Changelog](https://github.com/clerk/javascript/blob/main/packages/agent-toolkit/CHANGELOG.md) +· +[Report a Bug](https://github.com/clerk/javascript/issues/new?assignees=&labels=needs-triage&projects=&template=BUG_REPORT.yml) +· +[Request a Feature](https://feedback.clerk.com/roadmap) +· +[Get Help](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_agent_toolkit) + +
+ +> ℹ️ Developer Preview +> +> Agent behavior is typically non-deterministic. Ensure you thoroughly test your integration and evaluate your application's performance. Additionally, consider scoping this toolkit's tools to specific users to limit resource access. +> +> If your app's code path is predetermined, it's always preferable to call APIs directly instead of using agents and tool calling. +> +> This SDK is recommended for testing purposes only unless you are confident in the agent's behavior and have implemented necessary security measures such as guardrails and best practices. + +## Table of Contents +1. [Getting Started](#getting-started) +2. [Public APIs](#public-apis) +3. [Use with Vercel's AI SDK](#use-with-vercels-ai-sdk) +4. [Use with Langchain](#use-with-langchain) +5. [Advanced Usage](#advanced-usage) +6. [Support](#support) + +## Getting Started + +Use this SDK to integrate [Clerk](https://clerk.com/?utm_source=github&utm_medium=clerk_agent_toolkit) into your agentic workflows. The Clerk Agent Toolkit enables popular agent frameworks, including Vercel's AI SDK and LangChain, to integrate with Clerk using tools (also known as function calling). + +This package exposes a subset of Clerk's functionality to agent frameworks, allowing you to build powerful agentic systems capable of managing users, user data, organizations, and more. + +## Public APIs + +The Clerk Agent Toolkit package provides two main import paths: + +- `@clerk/agent-toolkit/ai-sdk`: Helpers for integrating with Vercel's AI SDK. +- `@clerk/agent-toolkit/langchain`: Helpers for integrating with Langchain. + +The toolkit offers the same tools and core APIs across frameworks, but their public interfaces may vary slightly to align with each framework's design: + +- `createClerkToolkit(options)`: Instantiates a new Clerk toolkit. +- `toolkit.injectSessionClaims(systemPrompt)`: Injects session claims (userId, sessionId, orgId, etc.) into the system prompt, making them accessible to the AI model. +- `toolkit.users()`: Provides tools for managing users. +- `toolkit.organizations()`: Provides tools for managing organizations. +- `toolkit.allTools()`: Returns all available tools. +- `toolkit.toolMap()`: **(Langchain only)** Returns an object mapping available tools, useful for calling tools by name. + +For more details on each tool, refer to the framework-specific directories or the [Clerk BAPI documentation](https://clerk.com/docs/reference/backend-api). + +### Prerequisites +- `ai-sdk`: `"^3.4.7 || ^4.0.0"`, or `langchain`: `"^0.3.6"` +- A Clerk application +- An API key for an AI model compatible with Langchain + +### Example Repository +- [Clerk AI SDK Example](https://github.com/clerk/agent-toolkit-example) + +## Use with Vercel's AI SDK + +1. Install the Clerk Agent Toolkit package: + + ```shell + npm install @clerk/agent-toolkit + ``` + +2. Set the Clerk secret key as an environment variable in your project. Ensure you also configure any required LLM model keys. + + ``` + CLERK_SECRET_KEY=sk_ + ``` + +3. Import the helper from the `/ai-sdk` path, instantiate a new Clerk `toolkit`, and use it in your agent function: + +```typescript +// Import the helper from the ai-sdk path +import { createClerkToolkit } from "@clerk/agent-toolkit/ai-sdk"; +import { openai } from "@ai-sdk/openai"; +import { streamText } from "ai"; +import { auth } from "@clerk/nextjs/server"; +import { systemPrompt } from "@/lib/ai/prompts"; + +export const maxDuration = 30; + +export async function POST(req: Request) { + const { messages } = await req.json(); + // Optional - get the userId from the request + const { userId } = await auth.protect(); + + // Instantiate a new Clerk toolkit + // Optional - scope the toolkit to a specific user + const toolkit = await createClerkToolkit({ context: { userId } }); + + const result = streamText({ + model: openai("gpt-4o"), + messages, + // Optional - inject session claims into the system prompt + system: toolkit.injectSessionClaims(systemPrompt), + tools: { + // Provide the tools you want to use + ...toolkit.users(), + ...toolkit.organizations(), + }, + }); + + return result.toDataStreamResponse(); +} +``` + +## Use with Langchain + + + +1. Install the Clerk Agent Toolkit package: + + ```shell + npm install @clerk/agent-toolkit + ``` + +2. Set the Clerk secret key as an environment variable: + + ```shell + CLERK_SECRET_KEY=sk_ + ``` + +3. Import the helper from the `/langchain` path, instantiate a new Clerk `toolkit`, and use it in your agent function: + +```typescript +// Import the helper from the langchain path +import { createClerkToolkit } from "@clerk/agent-toolkit/langchain"; +import { ChatOpenAI } from "@langchain/openai"; +import { auth } from "@clerk/nextjs/server"; +import { HumanMessage, SystemMessage } from "@langchain/core/messages"; +import { LangChainAdapter } from "ai"; +import { systemPrompt } from "@/lib/ai/prompts"; + +export const maxDuration = 30; + +export async function POST(req: Request) { + const { prompt } = await req.json(); + const { userId } = await auth.protect(); + + // Instantiate a new Clerk toolkit + // Optional - scope the toolkit to a specific user + const toolkit = await createClerkToolkit({ context: { userId } }); + + const model = new ChatOpenAI({ model: "gpt-4o", temperature: 0 }); + + // Bind the tools you want to use to the model + const modelWithTools = model.bindTools(toolkit.users()); + + const messages = [new SystemMessage(toolkit.injectSessionClaims(systemPrompt)), new HumanMessage(prompt)]; + const aiMessage = await modelWithTools.invoke(messages); + messages.push(aiMessage); + + for (const toolCall of aiMessage.tool_calls || []) { + // Call the selected tool + const selectedTool = toolkit.toolMap()[toolCall.name]; + const toolMessage = await selectedTool.invoke(toolCall); + messages.push(toolMessage); + } + + // To simplify the setup, this example uses the ai-sdk langchain adapter + // to stream the results back to the /langchain page. + // For more details, see: https://sdk.vercel.ai/providers/adapters/langchain + const stream = await modelWithTools.stream(messages); + return LangChainAdapter.toDataStreamResponse(stream); +} + +```` + +## Advanced Usage + +### Using a Custom `clerkClient` + +If you need to set the Clerk secret key dynamically or use different Clerk instances, pass a custom `clerkClient`: + +```typescript +import { createClerkToolkit } from "@clerk/agent-toolkit/ai-sdk"; +import { createClerkClient } from "@clerk/backend"; + +export async function POST(req: Request) { + // Create a new Clerk client + const clerkClient = createClerkClient({ secretKey: "sk_" }); + + // Instantiate a new Clerk toolkit with the custom client + const toolkit = await createClerkToolkit({ clerkClient }); + + // Use the toolkit as usual + const result = streamText({ + model: openai("gpt-4o"), + messages, + tools: toolkit.users() + }); +} +``` + +## Support + +You can get in touch with us in any of the following ways: + +- Join our official community [Discord server](https://clerk.com/discord) +- On [our support page](https://clerk.com/contact/support?utm_source=github&utm_medium=clerk_agent_toolkit) + +## Contributing + +We're open to all community contributions! If you'd like to contribute in any way, please read [our contribution guidelines](https://github.com/clerk/javascript/blob/main/docs/CONTRIBUTING.md) and [code of conduct](https://github.com/clerk/javascript/blob/main/docs/CODE_OF_CONDUCT.md). + +## License + +This project is licensed under the **MIT license**. + +See [LICENSE](https://github.com/clerk/javascript/blob/main/packages/agent-toolkit/LICENSE) for more information. diff --git a/packages/agent-toolkit/package.json b/packages/agent-toolkit/package.json new file mode 100644 index 00000000000..d8a88a0be86 --- /dev/null +++ b/packages/agent-toolkit/package.json @@ -0,0 +1,66 @@ +{ + "name": "@clerk/agent-toolkit", + "version": "0.0.1", + "description": "Clerk Toolkit for AI Agents", + "homepage": "https://clerk.com/", + "bugs": { + "url": "https://github.com/clerk/javascript/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/clerk/javascript.git", + "directory": "packages/agent-toolkit" + }, + "license": "MIT", + "author": "Clerk", + "sideEffects": false, + "files": [ + "dist" + ], + "exports": { + "./langchain": { + "types": "./dist/langchain/index.d.ts", + "require": "./dist/langchain/index.js", + "import": "./dist/langchain/index.mjs" + }, + "./ai-sdk": { + "types": "./dist/ai-sdk/index.d.ts", + "require": "./dist/ai-sdk/index.js", + "import": "./dist/ai-sdk/index.mjs" + } + }, + "scripts": { + "build": "tsup --env.NODE_ENV production", + "clean": "rimraf ./dist", + "dev": "tsup --watch", + "lint": "eslint src", + "lint:attw": "attw --pack .", + "lint:publint": "publint", + "test": "jest", + "test:cache:clear": "jest --clearCache --useStderr" + }, + "dependencies": { + "@clerk/backend": "workspace:^", + "@clerk/shared": "workspace:^", + "@clerk/types": "workspace:^", + "zod": "^3.24.1" + }, + "peerDependencies": { + "@langchain/core": "^0.3.6", + "ai": "^3.4.7 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@langchain/core": { + "optional": true + }, + "ai": { + "optional": true + } + }, + "engines": { + "node": ">=20" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/agent-toolkit/src/ai-sdk/adapter.ts b/packages/agent-toolkit/src/ai-sdk/adapter.ts new file mode 100644 index 00000000000..b9ae77033f7 --- /dev/null +++ b/packages/agent-toolkit/src/ai-sdk/adapter.ts @@ -0,0 +1,12 @@ +import type { Tool } from 'ai'; +import { tool } from 'ai'; + +import type { SdkAdapter } from '../lib/types'; + +export const adapter: SdkAdapter = (clerkClient, context, clerkTool) => { + return tool({ + description: clerkTool.description, + parameters: clerkTool.parameters, + execute: clerkTool.bindRunnable(clerkClient, context), + }); +}; diff --git a/packages/agent-toolkit/src/ai-sdk/index.ts b/packages/agent-toolkit/src/ai-sdk/index.ts new file mode 100644 index 00000000000..7ad9a86bf1c --- /dev/null +++ b/packages/agent-toolkit/src/ai-sdk/index.ts @@ -0,0 +1,43 @@ +import { clerkClient as _clerkClient } from '../lib/clerk-client'; +import { defaultToolkitContext } from '../lib/constants'; +import { injectSessionClaims } from '../lib/inject-session-claims'; +import { flatTools, tools } from '../lib/tools'; +import type { ClerkToolkitBase, CreateClerkToolkitParams } from '../lib/types'; +import { shallowTransform } from '../lib/utils'; +import { adapter } from './adapter'; + +export type ClerkToolkit = ClerkToolkitBase & { + /** + * Returns an object with all the tools from all categories in the Clerk toolkit. + * + * Most LLM providers recommend that for each LLM call, the number of available tools should be kept to a minimum, + * usually around 10-20 tools. This increases the LLM's accuracy when picking the right tool. + * + * As a result, we also recommend to use the fine-grained tool categories, for example, `toolkit.users` instead. + */ + allTools: () => { [key in keyof typeof flatTools]: ReturnType }; +} & { + [key in keyof typeof tools]: () => { [tool in keyof (typeof tools)[key]]: ReturnType }; +}; + +export const createClerkToolkit = async (params: CreateClerkToolkitParams = {}): Promise => { + const clerkClient = params.clerkClient || _clerkClient; + const context = params.context || defaultToolkitContext; + + const adaptedTools = shallowTransform(tools, toolSection => { + return () => + shallowTransform(toolSection, t => { + return adapter(clerkClient, context, t); + }); + }); + + const allTools = () => { + return shallowTransform(flatTools, t => adapter(clerkClient, context, t)); + }; + + return { + ...adaptedTools, + allTools, + injectSessionClaims: injectSessionClaims(context), + }; +}; diff --git a/packages/agent-toolkit/src/global.d.ts b/packages/agent-toolkit/src/global.d.ts new file mode 100644 index 00000000000..1ae75219e34 --- /dev/null +++ b/packages/agent-toolkit/src/global.d.ts @@ -0,0 +1,6 @@ +declare global { + const PACKAGE_NAME: string; + const PACKAGE_VERSION: string; +} + +export {}; diff --git a/packages/agent-toolkit/src/langchain/adapter.ts b/packages/agent-toolkit/src/langchain/adapter.ts new file mode 100644 index 00000000000..85bce7461eb --- /dev/null +++ b/packages/agent-toolkit/src/langchain/adapter.ts @@ -0,0 +1,13 @@ +import type { StructuredTool } from '@langchain/core/tools'; +import { tool } from '@langchain/core/tools'; + +import type { SdkAdapter } from '../lib/types'; + +// https://js.langchain.com/docs/how_to/custom_tools +export const adapter: SdkAdapter = (clerkClient, context, clerkTool) => { + return tool(clerkTool.bindRunnable(clerkClient, context), { + name: clerkTool.name, + description: clerkTool.description, + schema: clerkTool.parameters, + }); +}; diff --git a/packages/agent-toolkit/src/langchain/index.ts b/packages/agent-toolkit/src/langchain/index.ts new file mode 100644 index 00000000000..f2c6e0099e1 --- /dev/null +++ b/packages/agent-toolkit/src/langchain/index.ts @@ -0,0 +1,48 @@ +import { clerkClient as _clerkClient } from '../lib/clerk-client'; +import { defaultToolkitContext } from '../lib/constants'; +import { injectSessionClaims } from '../lib/inject-session-claims'; +import { flatTools, tools } from '../lib/tools'; +import type { ClerkToolkitBase, CreateClerkToolkitParams } from '../lib/types'; +import { shallowTransform } from '../lib/utils'; +import { adapter } from './adapter'; + +export type ClerkToolkit = ClerkToolkitBase & { + /** + * Returns an array containing all tools from all categories in the Clerk toolkit. + * + * Most LLM providers recommend that for each LLM call, the number of available tools should be kept to a minimum, + * usually around 10-20 tools. This increases the LLM's accuracy when picking the right tool. + * + * As a result, we also recommend to use the fine-grained tool categories, for example, `toolkit.users` instead. + */ + allTools: () => Array>; + /** + * Returns an object with all the tools from all categories in the Clerk toolkit. + * Useful when using tool calling with Langchain messages (e.g. `tool_calls`). + */ + toolMap: () => { [key in keyof typeof flatTools]: ReturnType }; +} & { + [key in keyof typeof tools]: () => Array>; +}; + +export const createClerkToolkit = async (params: CreateClerkToolkitParams = {}): Promise => { + const clerkClient = params.clerkClient || _clerkClient; + const context = params.context || defaultToolkitContext; + + const adaptedTools = shallowTransform(tools, toolSection => { + return () => Object.values(toolSection).map(t => adapter(clerkClient, context, t)); + }); + + const allTools = () => { + return Object.values(flatTools).map(t => adapter(clerkClient, context, t)); + }; + + const toolMap = shallowTransform(flatTools, t => adapter(clerkClient, context, t)); + + return { + ...adaptedTools, + allTools, + toolMap: () => toolMap, + injectSessionClaims: injectSessionClaims(context), + }; +}; diff --git a/packages/agent-toolkit/src/lib/clerk-client.ts b/packages/agent-toolkit/src/lib/clerk-client.ts new file mode 100644 index 00000000000..5b709e713ad --- /dev/null +++ b/packages/agent-toolkit/src/lib/clerk-client.ts @@ -0,0 +1,22 @@ +import { createClerkClient } from '@clerk/backend'; +import { apiUrlFromPublishableKey } from '@clerk/shared/apiUrlFromPublishableKey'; + +const API_VERSION = process.env.CLERK_API_VERSION || 'v1'; +const SECRET_KEY = process.env.CLERK_SECRET_KEY || ''; +const PUBLISHABLE_KEY = process.env.CLERK_PUBLISHABLE_KEY || ''; +const API_URL = process.env.CLERK_API_URL || apiUrlFromPublishableKey(PUBLISHABLE_KEY); +const JWT_KEY = process.env.CLERK_JWT_KEY || ''; +const SDK_METADATA = { + name: PACKAGE_NAME, + version: PACKAGE_VERSION, + environment: process.env.NODE_ENV, +}; + +export const clerkClient = createClerkClient({ + secretKey: SECRET_KEY, + apiUrl: API_URL, + apiVersion: API_VERSION, + jwtKey: JWT_KEY, + userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`, + sdkMetadata: SDK_METADATA, +}); diff --git a/packages/agent-toolkit/src/lib/clerk-tool.ts b/packages/agent-toolkit/src/lib/clerk-tool.ts new file mode 100644 index 00000000000..8c82b5c6d82 --- /dev/null +++ b/packages/agent-toolkit/src/lib/clerk-tool.ts @@ -0,0 +1,55 @@ +import type { ClerkClient } from '@clerk/backend'; +import type { ZodObject } from 'zod'; + +import type { ToolkitContext } from './types'; + +export interface ClerkTool { + /** + * The name of the tool. This can be used to reference the tool in the code. + * A descriptive LLM-readable string. + */ + name: string; + /** + * A descriptive prompt explaining the tool's purpose, usage and input parameters. + * Ths is intended to be used by the underlying LLM. + * To avoid duplication, the description can reference the parameters by using the `$parameters` prefix. + */ + description: string; + /** + * The Zod schema for the input parameters of the tool + */ + parameters: ZodObject; + /** + * The actual implementation of the tool. + */ + bindRunnable: (clerkClient: ClerkClient, context: ToolkitContext) => (input: any) => Promise; +} + +const trimLines = (text: string) => + text + .split('\n') + .map(l => l.trim()) + .filter(Boolean) + .join('\n'); + +export const ClerkTool = (params: ClerkTool): ClerkTool => { + const schemaEntries = Object.entries(params.parameters.shape); + const args = + schemaEntries.length === 0 + ? 'Takes no arguments' + : Object.entries(params.parameters.shape) + .map(([key, value]) => { + return `- ${key}: ${(value as any).description || ''}`; + }) + .join('\n'); + + const description = trimLines(` + Tool name: + ${params.name} + Description: + ${params.description}. + Arguments: + ${args} + `); + return { ...params, description }; +}; diff --git a/packages/agent-toolkit/src/lib/constants.ts b/packages/agent-toolkit/src/lib/constants.ts new file mode 100644 index 00000000000..7f23a9bdebe --- /dev/null +++ b/packages/agent-toolkit/src/lib/constants.ts @@ -0,0 +1,5 @@ +import type {ToolkitContext} from "./types"; + +export const defaultToolkitContext: ToolkitContext = { + allowPrivateMetadata: false, +}; diff --git a/packages/agent-toolkit/src/lib/inject-session-claims.ts b/packages/agent-toolkit/src/lib/inject-session-claims.ts new file mode 100644 index 00000000000..db31cf5892e --- /dev/null +++ b/packages/agent-toolkit/src/lib/inject-session-claims.ts @@ -0,0 +1,18 @@ +import type { ToolkitContext } from './types'; + +export const injectSessionClaims = (context: ToolkitContext) => (prompt: string) => { + if (!context.sessionClaims && !context.userId) { + return prompt; + } + + const lines = []; + lines.push(`# Signed-in user info:`); + lines.push( + context.sessionClaims?.sub || context.userId + ? `Your userId is ${context.sessionClaims?.sub || context.userId}` + : '', + ); + lines.push(context.sessionClaims?.sid ? `Your sessionId is ${context.sessionClaims?.sid}` : ''); + lines.push(prompt); + return lines.filter(Boolean).join('\n'); +}; diff --git a/packages/agent-toolkit/src/lib/tools/index.ts b/packages/agent-toolkit/src/lib/tools/index.ts new file mode 100644 index 00000000000..a7714fe5291 --- /dev/null +++ b/packages/agent-toolkit/src/lib/tools/index.ts @@ -0,0 +1,15 @@ +import { users } from './user-tools'; + +export const tools = { + /** + * Tools for interacting with users. + * This is a wrapper around the `clerkClient.users` API. + * For more information, see the [Clerk API documentation](https://clerk.com/docs/reference/backend-api/tag/Users). + */ + users, +} as const; + +// Just to help with types later on +export const flatTools = { + ...users, +}; diff --git a/packages/agent-toolkit/src/lib/tools/user-tools.ts b/packages/agent-toolkit/src/lib/tools/user-tools.ts new file mode 100644 index 00000000000..76676aba762 --- /dev/null +++ b/packages/agent-toolkit/src/lib/tools/user-tools.ts @@ -0,0 +1,52 @@ +import { z } from 'zod'; + +import { ClerkTool } from '../clerk-tool'; +import { prunePrivateData } from '../utils'; + +const getUserId = ClerkTool({ + name: 'getUserId', + description: ` + Get the userId of the current user if signed in, otherwise return null. + `, + parameters: z.object({}), + bindRunnable: (clerkClient, context) => () => { + return Promise.resolve(context.userId || null); + }, +}); + +const getUser = ClerkTool({ + name: 'getUser', + description: ` + Retrieves information about a single User by their userId, if the userId is valid. + `, + parameters: z.object({ + userId: z.string().describe('(string): The userId of the User to retrieve'), + }), + bindRunnable: (clerkClient, context) => async params => { + const res = await clerkClient.users.getUser(context.userId || params.userId); + return prunePrivateData(context, res.raw); + }, +}); + +const updateUserPublicMetadata = ClerkTool({ + name: 'updateUserPublicMetadata', + description: ` + Updates the public metadata associated with the specified user by merging existing values with the provided parameters. + A "deep" merge will be performed - "deep" means that any nested JSON objects will be merged as well. You can remove metadata keys at any level by setting their value to null. + `, + parameters: z.object({ + userId: z.string().describe('(string): The userId of the User to update'), + metadata: z.record(z.string(), z.any()).describe('(Record): The new public metadata to set'), + }), + bindRunnable: (clerkClient, context) => async params => { + const { userId, metadata } = params; + const res = await clerkClient.users.updateUserMetadata(context.userId || userId, { publicMetadata: metadata }); + return prunePrivateData(context, res.raw); + }, +}); + +export const users = { + getUserId, + getUser, + updateUserPublicMetadata, +} as const; diff --git a/packages/agent-toolkit/src/lib/types.ts b/packages/agent-toolkit/src/lib/types.ts new file mode 100644 index 00000000000..1c458013db4 --- /dev/null +++ b/packages/agent-toolkit/src/lib/types.ts @@ -0,0 +1,26 @@ +import type { ClerkClient } from '@clerk/backend'; +import type { SignedInAuthObject } from '@clerk/backend/internal'; + +import type { ClerkTool } from './clerk-tool'; + +export type SdkAdapter = (clerkClient: ClerkClient, context: ToolkitContext, clerkTool: ClerkTool) => T; + +export type ToolkitContext = { + userId?: string; + sessionClaims?: SignedInAuthObject['sessionClaims']; + allowPrivateMetadata?: boolean; +}; + +export type CreateClerkToolkitParams = { + clerkClient?: ClerkClient; + context?: ToolkitContext; +}; + +export type ClerkToolkitBase = { + /** + * Augment the system prompt with data about the current session. + * This usually contains the userId, the sessionId, the organizationId, etc. + * This property uses the data passed to `createClerkToolkit`. + */ + injectSessionClaims: (prompt: string) => string; +}; diff --git a/packages/agent-toolkit/src/lib/utils.ts b/packages/agent-toolkit/src/lib/utils.ts new file mode 100644 index 00000000000..2400130332f --- /dev/null +++ b/packages/agent-toolkit/src/lib/utils.ts @@ -0,0 +1,66 @@ +import type { ToolkitContext } from './types'; + +// A helper type that maps T to a new type with every leaf replaced by R. +type DeepTransform = + T extends Array ? DeepTransform[] : T extends object ? { [K in keyof T]: DeepTransform } : R; + +/** + * Recursively transforms every value in an object (or array) by applying a function. + * + * The result has the same structure as the input object, + but each leaf value is replaced with the return type R of the transform function. + */ +export function deepTransform(input: T, transformFn: (value: any) => R): DeepTransform { + if (Array.isArray(input)) { + // Recursively transform each element of the array. + return input.map(item => deepTransform(item, transformFn)) as DeepTransform; + } else if (input !== null && typeof input === 'object') { + // Recursively transform each property of the object. + const result: any = {}; + for (const key in input) { + if (Object.prototype.hasOwnProperty.call(input, key)) { + result[key] = deepTransform((input as any)[key], transformFn); + } + } + return result as DeepTransform; + } else { + // For non-objects, apply the transform function. + return transformFn(input) as DeepTransform; + } +} + +/** + * A mapped type that replaces every property in T with type R. + */ +type ShallowTransform = { + [K in keyof T]: R; +}; + +/** + * Transforms the top-level values of an object using a transformation function. + * + */ +export function shallowTransform( + input: T, + transformFn: (value: T[keyof T]) => R, +): ShallowTransform { + const result = {} as ShallowTransform; + for (const key in input) { + if (Object.prototype.hasOwnProperty.call(input, key)) { + const typedKey = key as keyof T; + result[typedKey] = transformFn(input[typedKey]); + } + } + return result; +} + +export const prunePrivateData = (context: ToolkitContext, o?: Record | null) => { + if (context.allowPrivateMetadata) { + return o; + } + + if (o && o.private_metadata) { + delete o.private_metadata; + } + return o; +}; diff --git a/packages/agent-toolkit/tsconfig.json b/packages/agent-toolkit/tsconfig.json new file mode 100644 index 00000000000..57b39d50251 --- /dev/null +++ b/packages/agent-toolkit/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "moduleResolution": "NodeNext", + "module": "NodeNext", + "sourceMap": false, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "allowJs": true, + "target": "ES2020", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "resolveJsonModule": true, + "declarationDir": "dist/types" + }, + "include": ["src"] +} diff --git a/packages/agent-toolkit/tsconfig.test.json b/packages/agent-toolkit/tsconfig.test.json new file mode 100644 index 00000000000..5635d6cd1b7 --- /dev/null +++ b/packages/agent-toolkit/tsconfig.test.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": true + } +} diff --git a/packages/agent-toolkit/tsup.config.ts b/packages/agent-toolkit/tsup.config.ts new file mode 100644 index 00000000000..7eb4dadc8e0 --- /dev/null +++ b/packages/agent-toolkit/tsup.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'tsup'; + +import { name, version } from './package.json'; + +export default defineConfig(overrideOptions => { + const isProd = overrideOptions.env?.NODE_ENV === 'production'; + + return { + entry: ['src/ai-sdk/index.ts', 'src/langchain/index.ts'], + dts: true, + // onSuccess: 'tsc', + minify: isProd, + clean: true, + sourcemap: true, + format: ['cjs', 'esm'], + // legacyOutput: true, + define: { + PACKAGE_NAME: `"${name}"`, + PACKAGE_VERSION: `"${version}"`, + __DEV__: `${!isProd}`, + }, + }; +}); diff --git a/packages/backend/src/api/resources/User.ts b/packages/backend/src/api/resources/User.ts index f70b7da959a..5da7b6a97ef 100644 --- a/packages/backend/src/api/resources/User.ts +++ b/packages/backend/src/api/resources/User.ts @@ -6,6 +6,12 @@ import { SamlAccount } from './SamlAccount'; import { Web3Wallet } from './Web3Wallet'; export class User { + private _raw: UserJSON | null = null; + + public get raw(): UserJSON | null { + return this._raw; + } + constructor( readonly id: string, readonly passwordEnabled: boolean, @@ -42,7 +48,7 @@ export class User { ) {} static fromJSON(data: UserJSON): User { - return new User( + const res = new User( data.id, data.password_enabled, data.totp_enabled, @@ -76,6 +82,8 @@ export class User { data.delete_self_enabled, data.legal_accepted_at, ); + res._raw = data; + return res; } get primaryEmailAddress() { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6f58ebff3d5..f109f9a60cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -302,6 +302,27 @@ importers: specifier: catalog:repo version: 8.3.0 + packages/agent-toolkit: + dependencies: + '@clerk/backend': + specifier: workspace:^ + version: link:../backend + '@clerk/shared': + specifier: workspace:^ + version: link:../shared + '@clerk/types': + specifier: workspace:^ + version: link:../types + '@langchain/core': + specifier: ^0.3.6 + version: 0.3.39 + ai: + specifier: ^3.4.7 || ^4.0.0 + version: 4.1.34(react@18.3.1)(zod@3.24.1) + zod: + specifier: ^3.24.1 + version: 3.24.1 + packages/astro: dependencies: '@clerk/backend': @@ -541,7 +562,7 @@ importers: version: 8.2.2 next: specifier: ^14.2.23 - version: 14.2.23(@babel/core@7.26.7)(@playwright/test@1.44.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.23(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.44.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) type-fest: specifier: ^4.33.0 version: 4.33.0 @@ -710,7 +731,7 @@ importers: version: 4.2.2 next: specifier: ^14.2.23 - version: 14.2.23(@babel/core@7.26.7)(@playwright/test@1.44.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.23(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.44.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) packages/nuxt: dependencies: @@ -1150,6 +1171,40 @@ packages: '@adobe/css-tools@4.4.0': resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==} + '@ai-sdk/provider-utils@2.1.6': + resolution: {integrity: sha512-Pfyaj0QZS22qyVn5Iz7IXcJ8nKIKlu2MeSAdKJzTwkAks7zdLaKVB+396Rqcp1bfQnxl7vaduQVMQiXUrgK8Gw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/provider@1.0.7': + resolution: {integrity: sha512-q1PJEZ0qD9rVR+8JFEd01/QM++csMT5UVwYXSN2u54BrVw/D8TZLTeg2FEfKK00DgAx0UtWd8XOhhwITP9BT5g==} + engines: {node: '>=18'} + + '@ai-sdk/react@1.1.11': + resolution: {integrity: sha512-vfjZ7w2M+Me83HTMMrnnrmXotz39UDCMd27YQSrvt2f1YCLPloVpLhP+Y9TLZeFE/QiiRCrPYLDQm6aQJYJ9PQ==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + zod: + optional: true + + '@ai-sdk/ui-utils@1.1.11': + resolution: {integrity: sha512-1SC9W4VZLcJtxHRv4Y0aX20EFeaEP6gUvVqoKLBBtMLOgtcZrv/F/HQRjGavGugiwlS3dsVza4X+E78fiwtlTA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + '@alcalzone/ansi-tokenize@0.1.3': resolution: {integrity: sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==} engines: {node: '>=14.13.1'} @@ -1976,6 +2031,9 @@ packages: '@bundled-es-modules/tough-cookie@0.1.6': resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + '@cfworker/json-schema@4.1.1': + resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} + '@changesets/apply-release-plan@7.0.5': resolution: {integrity: sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==} @@ -3289,6 +3347,10 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@langchain/core@0.3.39': + resolution: {integrity: sha512-muXs4asy1A7qDtcdznxqyBfxf4N6qxofY/S0c95vbsWa0r9YAE2PttHIjcuxSy1q2jUiTkpCcgFEjNJRQRVhEw==} + engines: {node: '>=18'} + '@leichtgewicht/ip-codec@2.0.4': resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==} @@ -3582,6 +3644,10 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} @@ -5385,6 +5451,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/diff-match-patch@1.0.36': + resolution: {integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==} + '@types/diff@6.0.0': resolution: {integrity: sha512-dhVCYGv3ZSbzmQaBSagrv1WJ6rXCdkyTcDyoNu1MD8JohI7pR7k8wdZEm+mvdxRKXyHVwckFzWU1vJc+Z29MlA==} @@ -5574,6 +5643,9 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/webextension-polyfill@0.10.7': resolution: {integrity: sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw==} @@ -6090,6 +6162,18 @@ packages: resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} engines: {node: '>=12'} + ai@4.1.34: + resolution: {integrity: sha512-9IB5duz6VbXvjibqNrvKz6++PwE8Ui5UfbOC9/CtcQN5Z9sudUQErss+maj7ptoPysD2NPjj99e0Hp183Cz5LQ==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.0.0 + peerDependenciesMeta: + react: + optional: true + zod: + optional: true + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -7128,6 +7212,9 @@ packages: resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} engines: {node: ^14.18.0 || >=16.10.0} + console-table-printer@2.12.1: + resolution: {integrity: sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -7723,6 +7810,9 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff-match-patch@1.0.5: + resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==} + diff-sequences@29.6.3: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8253,6 +8343,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + eventsource-parser@3.0.0: + resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==} + engines: {node: '>=18.0.0'} + exec-async@2.2.0: resolution: {integrity: sha512-87OpwcEiMia/DeiKFzaQNBNFeN3XkkpYIh9FyOqq5mS2oKv3CBE67PXoEKcr6nodWdXNogTiQ0jE2NGuoffXPw==} @@ -9979,6 +10073,9 @@ packages: resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} engines: {node: '>=0.10.0'} + js-tiktoken@1.0.18: + resolution: {integrity: sha512-hFYx4xYf6URgcttcGvGuOBJhTxPYZ2R5eIesqCaNRJmYH8sNmsfTeWg4yu//7u1VD/qIUkgKJTpGom9oHXmB4g==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -10107,6 +10204,11 @@ packages: engines: {node: '>=6'} hasBin: true + jsondiffpatch@0.6.0: + resolution: {integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -10172,6 +10274,14 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + langsmith@0.3.7: + resolution: {integrity: sha512-wakN1hxGkm1JR2PpAV7fiT7oC99LKcgxiuUrYGZWPbuj7Y8EPF19F7VNr4B+hA219bfaeWTa4Lxy2YrtPSKnQA==} + peerDependencies: + openai: '*' + peerDependenciesMeta: + openai: + optional: true + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -10987,6 +11097,10 @@ packages: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true + mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true + mute-stream@2.0.0: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -11489,6 +11603,10 @@ packages: resolution: {integrity: sha512-T8BatKGY+k5rU+Q/GTYgrEf2r4xRMevAN5mtXc2aPc4rS1j3s+vWTaO2Wag94neXuCAUAs8cxBL9EeB5EA6diw==} engines: {node: '>=16'} + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + p-queue@8.0.1: resolution: {integrity: sha512-NXzu9aQJTAzbBqOt2hwsR63ea7yvxJc0PwN/zobNAudYfb1B7R08SzB4TsLeSbUCuG467NhnoT0oO6w1qRO+BA==} engines: {node: '>=18'} @@ -11501,6 +11619,10 @@ packages: resolution: {integrity: sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==} engines: {node: '>=16.17'} + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + p-timeout@5.1.0: resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==} engines: {node: '>=12'} @@ -12978,6 +13100,9 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + simple-wcswidth@1.0.1: + resolution: {integrity: sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==} + sirv@1.0.19: resolution: {integrity: sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==} engines: {node: '>= 10'} @@ -13597,6 +13722,10 @@ packages: throttleit@1.0.1: resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==} + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} @@ -14300,6 +14429,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + uuid@7.0.3: resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} hasBin: true @@ -15057,10 +15190,10 @@ packages: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} - zod-to-json-schema@3.23.3: - resolution: {integrity: sha512-TYWChTxKQbRJp5ST22o/Irt9KC5nj7CdBKYB/AosCRdj/wxEMvv4NNaj9XVUHDOIp53ZxArGhnw5HMZziPFjog==} + zod-to-json-schema@3.24.1: + resolution: {integrity: sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==} peerDependencies: - zod: ^3.23.3 + zod: ^3.24.1 zod-to-ts@1.2.0: resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} @@ -15099,6 +15232,37 @@ snapshots: '@adobe/css-tools@4.4.0': {} + '@ai-sdk/provider-utils@2.1.6(zod@3.24.1)': + dependencies: + '@ai-sdk/provider': 1.0.7 + eventsource-parser: 3.0.0 + nanoid: 3.3.8 + secure-json-parse: 2.7.0 + optionalDependencies: + zod: 3.24.1 + + '@ai-sdk/provider@1.0.7': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@1.1.11(react@18.3.1)(zod@3.24.1)': + dependencies: + '@ai-sdk/provider-utils': 2.1.6(zod@3.24.1) + '@ai-sdk/ui-utils': 1.1.11(zod@3.24.1) + swr: 2.2.5(react@18.3.1) + throttleit: 2.1.0 + optionalDependencies: + react: 18.3.1 + zod: 3.24.1 + + '@ai-sdk/ui-utils@1.1.11(zod@3.24.1)': + dependencies: + '@ai-sdk/provider': 1.0.7 + '@ai-sdk/provider-utils': 2.1.6(zod@3.24.1) + zod-to-json-schema: 3.24.1(zod@3.24.1) + optionalDependencies: + zod: 3.24.1 + '@alcalzone/ansi-tokenize@0.1.3': dependencies: ansi-styles: 6.2.1 @@ -16163,6 +16327,8 @@ snapshots: '@types/tough-cookie': 4.0.5 tough-cookie: 4.1.4 + '@cfworker/json-schema@4.1.1': {} + '@changesets/apply-release-plan@7.0.5': dependencies: '@changesets/config': 3.0.3 @@ -17729,6 +17895,23 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@langchain/core@0.3.39': + dependencies: + '@cfworker/json-schema': 4.1.1 + ansi-styles: 5.2.0 + camelcase: 6.3.0 + decamelize: 1.2.0 + js-tiktoken: 1.0.18 + langsmith: 0.3.7 + mustache: 4.2.0 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 10.0.0 + zod: 3.24.1 + zod-to-json-schema: 3.24.1(zod@3.24.1) + transitivePeerDependencies: + - openai + '@leichtgewicht/ip-codec@2.0.4': {} '@manypkg/find-root@1.1.0': @@ -18246,6 +18429,8 @@ snapshots: '@open-draft/until@2.1.0': {} + '@opentelemetry/api@1.9.0': {} + '@oslojs/encoding@1.1.0': {} '@parcel/watcher-android-arm64@2.5.1': @@ -21348,6 +21533,8 @@ snapshots: dependencies: '@types/ms': 0.7.34 + '@types/diff-match-patch@1.0.36': {} + '@types/diff@6.0.0': {} '@types/estree@1.0.5': {} @@ -21553,6 +21740,8 @@ snapshots: '@types/unist@3.0.3': {} + '@types/uuid@10.0.0': {} + '@types/webextension-polyfill@0.10.7': {} '@types/webpack-env@1.18.8': {} @@ -22363,6 +22552,18 @@ snapshots: clean-stack: 4.2.0 indent-string: 5.0.0 + ai@4.1.34(react@18.3.1)(zod@3.24.1): + dependencies: + '@ai-sdk/provider': 1.0.7 + '@ai-sdk/provider-utils': 2.1.6(zod@3.24.1) + '@ai-sdk/react': 1.1.11(react@18.3.1)(zod@3.24.1) + '@ai-sdk/ui-utils': 1.1.11(zod@3.24.1) + '@opentelemetry/api': 1.9.0 + jsondiffpatch: 0.6.0 + optionalDependencies: + react: 18.3.1 + zod: 3.24.1 + ajv-formats@2.1.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -22687,7 +22888,7 @@ snapshots: xxhash-wasm: 1.0.2 yargs-parser: 21.1.1 zod: 3.24.1 - zod-to-json-schema: 3.23.3(zod@3.24.1) + zod-to-json-schema: 3.24.1(zod@3.24.1) zod-to-ts: 1.2.0(typescript@5.6.3)(zod@3.24.1) optionalDependencies: sharp: 0.33.5 @@ -23590,6 +23791,10 @@ snapshots: consola@3.4.0: {} + console-table-printer@2.12.1: + dependencies: + simple-wcswidth: 1.0.1 + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -24212,6 +24417,8 @@ snapshots: didyoumean@1.2.2: {} + diff-match-patch@1.0.5: {} + diff-sequences@29.6.3: {} diff@4.0.2: @@ -24937,6 +25144,8 @@ snapshots: events@3.3.0: {} + eventsource-parser@3.0.0: {} + exec-async@2.2.0: {} execa@1.0.0: @@ -27162,6 +27371,10 @@ snapshots: js-levenshtein@1.1.6: {} + js-tiktoken@1.0.18: + dependencies: + base64-js: 1.5.1 + js-tokens@4.0.0: {} js-tokens@9.0.1: {} @@ -27365,6 +27578,12 @@ snapshots: json5@2.2.3: {} + jsondiffpatch@0.6.0: + dependencies: + '@types/diff-match-patch': 1.0.36 + chalk: 5.4.1 + diff-match-patch: 1.0.5 + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -27441,6 +27660,16 @@ snapshots: kolorist@1.8.0: {} + langsmith@0.3.7: + dependencies: + '@types/uuid': 10.0.0 + chalk: 4.1.2 + console-table-printer: 2.12.1 + p-queue: 6.6.2 + p-retry: 4.6.2 + semver: 7.6.3 + uuid: 10.0.0 + language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -28545,6 +28774,8 @@ snapshots: dns-packet: 5.6.0 thunky: 1.1.0 + mustache@4.2.0: {} + mute-stream@2.0.0: {} mv@2.1.1: @@ -28583,7 +28814,7 @@ snapshots: nested-error-stacks@2.1.1: {} - next@14.2.23(@babel/core@7.26.7)(@playwright/test@1.44.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.2.23(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.44.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.2.23 '@swc/helpers': 0.5.5 @@ -28604,6 +28835,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 14.2.23 '@next/swc-win32-ia32-msvc': 14.2.23 '@next/swc-win32-x64-msvc': 14.2.23 + '@opentelemetry/api': 1.9.0 '@playwright/test': 1.44.1 transitivePeerDependencies: - '@babel/core' @@ -29279,6 +29511,11 @@ snapshots: p-map@6.0.0: {} + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + p-queue@8.0.1: dependencies: eventemitter3: 5.0.1 @@ -29295,6 +29532,10 @@ snapshots: is-network-error: 1.1.0 retry: 0.13.1 + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + p-timeout@5.1.0: {} p-timeout@6.1.3: {} @@ -30976,6 +31217,8 @@ snapshots: is-arrayish: 0.3.2 optional: true + simple-wcswidth@1.0.1: {} + sirv@1.0.19: dependencies: '@polka/url': 1.0.0-next.28 @@ -31696,6 +31939,8 @@ snapshots: throttleit@1.0.1: {} + throttleit@2.1.0: {} + through2@2.0.5: dependencies: readable-stream: 2.3.8 @@ -32373,6 +32618,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@10.0.0: {} + uuid@7.0.3: {} uuid@8.3.2: {} @@ -33322,7 +33569,7 @@ snapshots: compress-commons: 6.0.2 readable-stream: 4.7.0 - zod-to-json-schema@3.23.3(zod@3.24.1): + zod-to-json-schema@3.24.1(zod@3.24.1): dependencies: zod: 3.24.1