The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .env.example
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── banner.png
├── components.json
├── eslint.config.js
├── next.config.js
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── prettier.config.js
├── prisma
    ├── migrations
    │   ├── 20250608171639_users
    │   │   └── migration.sql
    │   ├── 20250608175206_chats
    │   │   └── migration.sql
    │   ├── 20250608210029_streams
    │   │   └── migration.sql
    │   ├── 20250612033842_files
    │   │   └── migration.sql
    │   ├── 20250613013025_images
    │   │   └── migration.sql
    │   ├── 20250615144652_chat_branching
    │   │   └── migration.sql
    │   ├── 20250615224432_features
    │   │   └── migration.sql
    │   ├── 20250616064236_workbenches
    │   │   └── migration.sql
    │   ├── 20250714211437_add_starring
    │   │   └── migration.sql
    │   └── migration_lock.toml
    ├── schema.prisma
    └── seed.ts
├── public
    ├── icons
    │   ├── anthropic.png
    │   ├── deepseek.png
    │   ├── discord.png
    │   ├── e2b.png
    │   ├── google.png
    │   ├── lucide.png
    │   ├── mem0.png
    │   ├── meta-llama.png
    │   ├── motion.png
    │   ├── next.png
    │   ├── nextauth.png
    │   ├── openai.png
    │   ├── perplexity.png
    │   ├── postgres.png
    │   ├── prisma.png
    │   ├── qwen.png
    │   ├── radix.png
    │   ├── react.png
    │   ├── recharts.png
    │   ├── redis.png
    │   ├── shadcn.png
    │   ├── t3.png
    │   ├── tRPC.png
    │   ├── tailwind.png
    │   ├── tanstack.png
    │   ├── vercel.png
    │   ├── x-ai.png
    │   ├── xai.png
    │   └── zod.png
    └── logo
    │   ├── dark.svg
    │   └── light.svg
├── src
    ├── ai
    │   ├── generate.ts
    │   ├── models
    │   │   ├── all.ts
    │   │   ├── anthropic.ts
    │   │   ├── deepseek.ts
    │   │   ├── google.ts
    │   │   ├── index.ts
    │   │   ├── llama.ts
    │   │   ├── openai.ts
    │   │   ├── perplexity.ts
    │   │   ├── qwen.ts
    │   │   └── xai.ts
    │   ├── registry.ts
    │   └── types.ts
    ├── app
    │   ├── [id]
    │   │   └── page.tsx
    │   ├── _components
    │   │   ├── auth
    │   │   │   └── auth-buttons.tsx
    │   │   ├── chat
    │   │   │   ├── chat.tsx
    │   │   │   ├── index.tsx
    │   │   │   ├── input
    │   │   │   │   ├── index.tsx
    │   │   │   │   ├── model-select
    │   │   │   │   │   ├── index.tsx
    │   │   │   │   │   ├── native-search-toggle.tsx
    │   │   │   │   │   ├── use-model-select.ts
    │   │   │   │   │   └── utils.tsx
    │   │   │   │   └── tools.tsx
    │   │   │   ├── layout.tsx
    │   │   │   ├── messages
    │   │   │   │   ├── greeting.tsx
    │   │   │   │   ├── index.tsx
    │   │   │   │   ├── message-actions.tsx
    │   │   │   │   ├── message-editor.tsx
    │   │   │   │   ├── message-reasoning.tsx
    │   │   │   │   ├── message-tool.tsx
    │   │   │   │   └── message.tsx
    │   │   │   ├── preview-attachment.tsx
    │   │   │   └── starter-prompts.tsx
    │   │   ├── landing-page
    │   │   │   ├── auth-modal.tsx
    │   │   │   ├── dependencies
    │   │   │   │   ├── data.tsx
    │   │   │   │   ├── index.tsx
    │   │   │   │   └── types.ts
    │   │   │   ├── hero
    │   │   │   │   ├── data.tsx
    │   │   │   │   ├── index.tsx
    │   │   │   │   ├── message.tsx
    │   │   │   │   ├── message
    │   │   │   │   │   ├── code-execution.tsx
    │   │   │   │   │   ├── memory.tsx
    │   │   │   │   │   ├── recent-ai-news.tsx
    │   │   │   │   │   ├── repository.tsx
    │   │   │   │   │   └── top-repositories.tsx
    │   │   │   │   ├── motion-container.tsx
    │   │   │   │   ├── toolkit-demo-list.tsx
    │   │   │   │   └── types.ts
    │   │   │   ├── index.tsx
    │   │   │   ├── navbar
    │   │   │   │   └── index.tsx
    │   │   │   ├── toolkit-creation
    │   │   │   │   ├── data.ts
    │   │   │   │   └── index.tsx
    │   │   │   └── workbenches
    │   │   │   │   ├── card.tsx
    │   │   │   │   ├── data.tsx
    │   │   │   │   ├── graphic.tsx
    │   │   │   │   ├── index.tsx
    │   │   │   │   └── types.ts
    │   │   ├── navbar
    │   │   │   ├── account-button
    │   │   │   │   ├── authenticated.tsx
    │   │   │   │   ├── index.tsx
    │   │   │   │   ├── provider-icon.tsx
    │   │   │   │   └── unauthenticated.tsx
    │   │   │   ├── color-mode-toggle.tsx
    │   │   │   └── index.tsx
    │   │   ├── sidebar
    │   │   │   ├── chats
    │   │   │   │   ├── index.tsx
    │   │   │   │   └── item.tsx
    │   │   │   ├── index.tsx
    │   │   │   ├── main.tsx
    │   │   │   ├── user.tsx
    │   │   │   └── workbench-select.tsx
    │   │   └── welcome-dialog.tsx
    │   ├── _contexts
    │   │   ├── chat-context.tsx
    │   │   └── theme.tsx
    │   ├── _hooks
    │   │   ├── use-auto-resume.tsx
    │   │   ├── use-chat-visibility.ts
    │   │   ├── use-delete-chat.ts
    │   │   ├── use-delete-messages.tsx
    │   │   ├── use-messages.tsx
    │   │   ├── use-scroll-to-bottom.tsx
    │   │   └── use-star-chat.ts
    │   ├── account
    │   │   ├── components
    │   │   │   ├── header.tsx
    │   │   │   └── tabs
    │   │   │   │   ├── attachments
    │   │   │   │       ├── attachment.tsx
    │   │   │   │       ├── index.tsx
    │   │   │   │       └── table.tsx
    │   │   │   │   ├── connected-accounts
    │   │   │   │       ├── connect-disconnect.tsx
    │   │   │   │       └── index.tsx
    │   │   │   │   ├── images
    │   │   │   │       ├── index.tsx
    │   │   │   │       └── table.tsx
    │   │   │   │   ├── index.tsx
    │   │   │   │   └── memories
    │   │   │   │       ├── index.tsx
    │   │   │   │       └── table.tsx
    │   │   └── page.tsx
    │   ├── admin
    │   │   ├── _components
    │   │   │   └── admin-panel.tsx
    │   │   └── page.tsx
    │   ├── api
    │   │   ├── auth
    │   │   │   └── [...nextauth]
    │   │   │   │   └── route.ts
    │   │   ├── chat
    │   │   │   ├── route.ts
    │   │   │   └── schema.ts
    │   │   ├── files
    │   │   │   └── upload
    │   │   │   │   └── route.ts
    │   │   ├── mcp
    │   │   │   └── [server]
    │   │   │   │   └── [transport]
    │   │   │   │       └── route.ts
    │   │   └── trpc
    │   │   │   └── [trpc]
    │   │   │       └── route.ts
    │   ├── favicon.ico
    │   ├── icon.png
    │   ├── layout.tsx
    │   ├── login
    │   │   ├── login-form.tsx
    │   │   └── page.tsx
    │   ├── opengraph-image.png
    │   ├── page.tsx
    │   ├── twitter-image.png
    │   └── workbench
    │   │   ├── [id]
    │   │       ├── [chatId]
    │   │       │   └── page.tsx
    │   │       ├── _components
    │   │       │   └── header.tsx
    │   │       ├── edit
    │   │       │   ├── _components
    │   │       │   │   └── edit-workbench-form.tsx
    │   │       │   └── page.tsx
    │   │       ├── layout.tsx
    │   │       └── page.tsx
    │   │   └── new
    │   │       ├── _components
    │   │           ├── index.ts
    │   │           └── new-workbench-form.tsx
    │   │       └── page.tsx
    ├── components
    │   ├── magicui
    │   │   ├── animated-beam.tsx
    │   │   ├── animated-list.tsx
    │   │   ├── animated-shiny-text.tsx
    │   │   └── marquee.tsx
    │   ├── toolkit
    │   │   ├── toolkit-configure.tsx
    │   │   ├── toolkit-icons.tsx
    │   │   ├── toolkit-list.tsx
    │   │   └── types.ts
    │   └── ui
    │   │   ├── accordion.tsx
    │   │   ├── alert-dialog.tsx
    │   │   ├── avatar.tsx
    │   │   ├── badge.tsx
    │   │   ├── button.tsx
    │   │   ├── card.tsx
    │   │   ├── checkbox.tsx
    │   │   ├── code-block.tsx
    │   │   ├── dialog.tsx
    │   │   ├── dropdown-menu.tsx
    │   │   ├── info-tooltip.tsx
    │   │   ├── input.tsx
    │   │   ├── label.tsx
    │   │   ├── logo.tsx
    │   │   ├── markdown.tsx
    │   │   ├── model-icon.tsx
    │   │   ├── popover.tsx
    │   │   ├── search-type-icon.tsx
    │   │   ├── separator.tsx
    │   │   ├── sheet.tsx
    │   │   ├── sidebar.tsx
    │   │   ├── skeleton.tsx
    │   │   ├── slider.tsx
    │   │   ├── sonner.tsx
    │   │   ├── stack.tsx
    │   │   ├── switch.tsx
    │   │   ├── table.tsx
    │   │   ├── tabs.tsx
    │   │   ├── textarea.tsx
    │   │   └── tooltip.tsx
    ├── env.js
    ├── hooks
    │   └── use-mobile.ts
    ├── lib
    │   ├── constants.ts
    │   ├── cookies
    │   │   ├── client.ts
    │   │   ├── keys.ts
    │   │   ├── server.ts
    │   │   └── types.ts
    │   ├── errors.ts
    │   ├── fetch.ts
    │   └── utils.ts
    ├── server
    │   ├── api
    │   │   ├── root.ts
    │   │   ├── routers
    │   │   │   ├── accounts.ts
    │   │   │   ├── chats.ts
    │   │   │   ├── features.ts
    │   │   │   ├── files.ts
    │   │   │   ├── images.ts
    │   │   │   ├── index.ts
    │   │   │   ├── memories.ts
    │   │   │   ├── messages.ts
    │   │   │   ├── streams.ts
    │   │   │   ├── users.ts
    │   │   │   └── workbenches.ts
    │   │   └── trpc.ts
    │   ├── auth
    │   │   ├── config.ts
    │   │   ├── index.ts
    │   │   └── providers.ts
    │   └── db.ts
    ├── styles
    │   └── globals.css
    ├── toolkits
    │   ├── README.md
    │   ├── create-tool.ts
    │   ├── create-toolkit.ts
    │   ├── toolkit-groups.ts
    │   ├── toolkits
    │   │   ├── client.ts
    │   │   ├── e2b
    │   │   │   ├── base.ts
    │   │   │   ├── client.tsx
    │   │   │   ├── server.ts
    │   │   │   └── tools
    │   │   │   │   ├── run_code
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   └── tools.ts
    │   │   ├── exa
    │   │   │   ├── base.ts
    │   │   │   ├── client.tsx
    │   │   │   ├── components
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── result-item.tsx
    │   │   │   │   ├── results-list.tsx
    │   │   │   │   └── tool-call-display.tsx
    │   │   │   ├── server.ts
    │   │   │   └── tools
    │   │   │   │   ├── company_research
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── competitor_finder
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── crawling
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── github_search
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── linkedin_search
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── research_paper_search
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── search
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── tools.ts
    │   │   │   │   └── wikipedia_search
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   ├── github
    │   │   │   ├── base.ts
    │   │   │   ├── client.tsx
    │   │   │   ├── components
    │   │   │   │   ├── activity-chart.tsx
    │   │   │   │   └── user-avatar.tsx
    │   │   │   ├── lib
    │   │   │   │   ├── commits.ts
    │   │   │   │   └── prs.ts
    │   │   │   ├── server.ts
    │   │   │   └── tools
    │   │   │   │   ├── client.ts
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── repo
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── search
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   └── server.ts
    │   │   ├── google-calendar
    │   │   │   ├── base.ts
    │   │   │   ├── client.tsx
    │   │   │   ├── components
    │   │   │   │   ├── calendar-card.tsx
    │   │   │   │   ├── event-card.tsx
    │   │   │   │   └── tool-call.tsx
    │   │   │   ├── server.ts
    │   │   │   └── tools
    │   │   │   │   ├── client.ts
    │   │   │   │   ├── get-calendar
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── get-event
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── list-calendars
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── list-events
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── search-events
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   └── server.ts
    │   │   ├── google-drive
    │   │   │   ├── base.ts
    │   │   │   ├── client.tsx
    │   │   │   ├── components
    │   │   │   │   ├── file-card.tsx
    │   │   │   │   └── tool-call.tsx
    │   │   │   ├── server.ts
    │   │   │   └── tools
    │   │   │   │   ├── client.ts
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── read-file
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── search-files
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   └── server.ts
    │   │   ├── image
    │   │   │   ├── base.ts
    │   │   │   ├── client.tsx
    │   │   │   ├── server.ts
    │   │   │   └── tools
    │   │   │   │   ├── generate
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   └── tools.ts
    │   │   ├── mem0
    │   │   │   ├── base.ts
    │   │   │   ├── client.tsx
    │   │   │   ├── components
    │   │   │   │   └── tool-call-display.tsx
    │   │   │   ├── server.ts
    │   │   │   └── tools
    │   │   │   │   ├── add_memory
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── search_memories
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   └── tools.ts
    │   │   ├── notion
    │   │   │   ├── base.ts
    │   │   │   ├── client.tsx
    │   │   │   ├── components
    │   │   │   │   ├── block.tsx
    │   │   │   │   ├── database.tsx
    │   │   │   │   ├── icon.tsx
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── page.tsx
    │   │   │   │   └── tool-call-display.tsx
    │   │   │   ├── lib
    │   │   │   │   └── title.ts
    │   │   │   ├── server.ts
    │   │   │   └── tools
    │   │   │   │   ├── blocks
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── client.ts
    │   │   │   │   ├── databases
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── index.ts
    │   │   │   │   ├── pages
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   │   │   ├── server.ts
    │   │   │   │   └── users
    │   │   │   │       ├── base.ts
    │   │   │   │       ├── client.tsx
    │   │   │   │       └── server.ts
    │   │   ├── server.ts
    │   │   ├── shared.ts
    │   │   └── toolkit-types.ts
    │   └── types.ts
    └── trpc
    │   ├── query-client.ts
    │   ├── react.tsx
    │   └── server.ts
├── start-database.sh
└── tsconfig.json


/.env.example:
--------------------------------------------------------------------------------
 1 | # This file will be committed to version control, so make sure not to have any
 2 | # secrets in it. If you are cloning this repo, create a copy of this file named
 3 | # ".env" and populate it with your secrets.
 4 | 
 5 | # When adding additional environment variables, the schema in "/src/env.js"
 6 | # should be updated accordingly.
 7 | 
 8 | APP_URL="http://localhost:3000"
 9 | 
10 | # Next Auth
11 | # You can generate a new secret on the command line with:
12 | # npx auth secret
13 | # https://next-auth.js.org/configuration/options#secret
14 | AUTH_SECRET=""
15 | 
16 | # Authentication Providers
17 | # Adding credentials for a given auth provider will automatically include it in the sign-in options
18 | # Dynamically sets the provider options for next-auth
19 | 
20 | # You must include at least one of these
21 | 
22 | # Next Auth Discord Provider
23 | AUTH_DISCORD_ID=""
24 | AUTH_DISCORD_SECRET=""
25 | 
26 | # Google auth credentials -- https://developers.google.com/identity/protocols/oauth2
27 | AUTH_GOOGLE_ID=""
28 | AUTH_GOOGLE_SECRET=""
29 | 
30 | # Github auth credentials
31 | AUTH_GITHUB_ID=""
32 | AUTH_GITHUB_SECRET=""
33 | 
34 | # Twitter auth credentials
35 | AUTH_TWITTER_ID=""
36 | AUTH_TWITTER_SECRET=""
37 | 
38 | # Notion auth credentials
39 | AUTH_NOTION_ID=""
40 | AUTH_NOTION_SECRET=""
41 | 
42 | # Prisma
43 | # https://www.prisma.io/docs/reference/database-reference/connection-urls#env
44 | DATABASE_URL="postgresql://postgres:password@localhost:5432/open-chat"
45 | 
46 | 
47 | # You must provide this key to run the app
48 | 
49 | OPENROUTER_API_KEY=""
50 | 
51 | # Tool Providers (optional)
52 | 
53 | EXA_API_KEY="" # set this to access the Web Search Toolkit
54 | E2B_API_KEY="" # set this to access the Code Interpreter Toolkit
55 | MEM0_API_KEY="" # set this to access the Memory Toolkit
56 | 
57 | # Optional data storage
58 | 
59 | REDIS_URL="" # You must set this to have resumable streams
60 | BLOB_READ_WRITE_TOKEN="" # You must set this to allow attachment uploads


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
 2 | 
 3 | # dependencies
 4 | /node_modules
 5 | /.pnp
 6 | .pnp.js
 7 | 
 8 | # testing
 9 | /coverage
10 | 
11 | # database
12 | /prisma/db.sqlite
13 | /prisma/db.sqlite-journal
14 | db.sqlite
15 | 
16 | # next.js
17 | /.next/
18 | /out/
19 | next-env.d.ts
20 | 
21 | # production
22 | /build
23 | 
24 | # misc
25 | .DS_Store
26 | *.pem
27 | 
28 | # debug
29 | npm-debug.log*
30 | yarn-debug.log*
31 | yarn-error.log*
32 | .pnpm-debug.log*
33 | 
34 | # local env files
35 | # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
36 | .env
37 | .env*.local
38 | 
39 | # vercel
40 | .vercel
41 | 
42 | # typescript
43 | *.tsbuildinfo
44 | 
45 | # idea files
46 | .idea


--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | public-hoist-pattern[]=*eslint*
2 | public-hoist-pattern[]=*prettier*


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2025 Jason Hedman
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/banner.png


--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "$schema": "https://ui.shadcn.com/schema.json",
 3 |   "style": "new-york",
 4 |   "rsc": true,
 5 |   "tsx": true,
 6 |   "tailwind": {
 7 |     "config": "",
 8 |     "css": "src/styles/globals.css",
 9 |     "baseColor": "neutral",
10 |     "cssVariables": true,
11 |     "prefix": ""
12 |   },
13 |   "aliases": {
14 |     "components": "@/components",
15 |     "utils": "@/lib/utils",
16 |     "ui": "@/components/ui",
17 |     "lib": "@/lib",
18 |     "hooks": "@/hooks"
19 |   },
20 |   "iconLibrary": "lucide"
21 | }


--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
 1 | import { FlatCompat } from "@eslint/eslintrc";
 2 | import tseslint from "typescript-eslint";
 3 | 
 4 | const compat = new FlatCompat({
 5 |   baseDirectory: import.meta.dirname,
 6 | });
 7 | 
 8 | export default tseslint.config(
 9 |   {
10 |     ignores: [".next"],
11 |   },
12 |   ...compat.extends("next/core-web-vitals"),
13 |   {
14 |     files: ["**/*.ts", "**/*.tsx"],
15 |     extends: [
16 |       ...tseslint.configs.recommended,
17 |       ...tseslint.configs.recommendedTypeChecked,
18 |       ...tseslint.configs.stylisticTypeChecked,
19 |     ],
20 |     rules: {
21 |       "@typescript-eslint/array-type": "off",
22 |       "@typescript-eslint/consistent-type-definitions": "off",
23 |       "@typescript-eslint/consistent-type-imports": [
24 |         "warn",
25 |         { prefer: "type-imports", fixStyle: "inline-type-imports" },
26 |       ],
27 |       "@typescript-eslint/no-unused-vars": [
28 |         "warn",
29 |         { argsIgnorePattern: "^_" },
30 |       ],
31 |       "@typescript-eslint/require-await": "off",
32 |       "@typescript-eslint/no-misused-promises": [
33 |         "error",
34 |         { checksVoidReturn: { attributes: false } },
35 |       ],
36 |     },
37 |   },
38 |   {
39 |     linterOptions: {
40 |       reportUnusedDisableDirectives: true,
41 |     },
42 |     languageOptions: {
43 |       parserOptions: {
44 |         projectService: true,
45 |       },
46 |     },
47 |   },
48 | );
49 | 


--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
 3 |  * for Docker builds.
 4 |  */
 5 | import "./src/env.js";
 6 | 
 7 | /** @type {import("next").NextConfig} */
 8 | const config = {
 9 |   images: {
10 |     remotePatterns: [
11 |       {
12 |         protocol: "https",
13 |         hostname: "3kjwme0xuhfnyrkn.public.blob.vercel-storage.com",
14 |       },
15 |     ],
16 |   },
17 | };
18 | 
19 | export default config;
20 | 


--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 |   plugins: {
3 |     "@tailwindcss/postcss": {},
4 |   },
5 | };
6 | 


--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */
2 | export default {
3 |   plugins: ["prettier-plugin-tailwindcss"],
4 | };
5 | 


--------------------------------------------------------------------------------
/prisma/migrations/20250608175206_chats/migration.sql:
--------------------------------------------------------------------------------
 1 | -- CreateEnum
 2 | CREATE TYPE "Visibility" AS ENUM ('public', 'private');
 3 | 
 4 | -- CreateTable
 5 | CREATE TABLE "Chat" (
 6 |     "id" TEXT NOT NULL,
 7 |     "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 8 |     "title" TEXT NOT NULL,
 9 |     "userId" TEXT NOT NULL,
10 |     "visibility" "Visibility" NOT NULL DEFAULT 'private',
11 | 
12 |     CONSTRAINT "Chat_pkey" PRIMARY KEY ("id")
13 | );
14 | 
15 | -- CreateTable
16 | CREATE TABLE "Message" (
17 |     "id" TEXT NOT NULL,
18 |     "chatId" TEXT NOT NULL,
19 |     "role" TEXT NOT NULL,
20 |     "parts" JSONB NOT NULL,
21 |     "attachments" JSONB NOT NULL,
22 |     "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
23 | 
24 |     CONSTRAINT "Message_pkey" PRIMARY KEY ("id")
25 | );
26 | 
27 | -- AddForeignKey
28 | ALTER TABLE "Chat" ADD CONSTRAINT "Chat_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
29 | 
30 | -- AddForeignKey
31 | ALTER TABLE "Message" ADD CONSTRAINT "Message_chatId_fkey" FOREIGN KEY ("chatId") REFERENCES "Chat"("id") ON DELETE CASCADE ON UPDATE CASCADE;
32 | 


--------------------------------------------------------------------------------
/prisma/migrations/20250608210029_streams/migration.sql:
--------------------------------------------------------------------------------
 1 | -- CreateTable
 2 | CREATE TABLE "Stream" (
 3 |     "id" TEXT NOT NULL,
 4 |     "chatId" TEXT NOT NULL,
 5 |     "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 6 | 
 7 |     CONSTRAINT "Stream_pkey" PRIMARY KEY ("id")
 8 | );
 9 | 
10 | -- CreateIndex
11 | CREATE INDEX "Stream_chatId_idx" ON "Stream"("chatId");
12 | 
13 | -- AddForeignKey
14 | ALTER TABLE "Stream" ADD CONSTRAINT "Stream_chatId_fkey" FOREIGN KEY ("chatId") REFERENCES "Chat"("id") ON DELETE CASCADE ON UPDATE CASCADE;
15 | 


--------------------------------------------------------------------------------
/prisma/migrations/20250612033842_files/migration.sql:
--------------------------------------------------------------------------------
 1 | /*
 2 |   Warnings:
 3 | 
 4 |   - The `attachments` column on the `Message` table would be dropped and recreated. This will lead to data loss if there is data in the column.
 5 | 
 6 | */
 7 | -- AlterTable
 8 | ALTER TABLE "Message" DROP COLUMN "attachments",
 9 | ADD COLUMN     "attachments" JSONB[];
10 | 
11 | -- CreateTable
12 | CREATE TABLE "File" (
13 |     "id" TEXT NOT NULL,
14 |     "userId" TEXT NOT NULL,
15 |     "name" TEXT NOT NULL,
16 |     "contentType" TEXT NOT NULL,
17 |     "url" TEXT NOT NULL,
18 |     "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
19 | 
20 |     CONSTRAINT "File_pkey" PRIMARY KEY ("id")
21 | );
22 | 
23 | -- AddForeignKey
24 | ALTER TABLE "File" ADD CONSTRAINT "File_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
25 | 


--------------------------------------------------------------------------------
/prisma/migrations/20250613013025_images/migration.sql:
--------------------------------------------------------------------------------
 1 | -- CreateTable
 2 | CREATE TABLE "Image" (
 3 |     "id" TEXT NOT NULL,
 4 |     "userId" TEXT NOT NULL,
 5 |     "contentType" TEXT NOT NULL,
 6 |     "url" TEXT NOT NULL,
 7 |     "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 8 | 
 9 |     CONSTRAINT "Image_pkey" PRIMARY KEY ("id")
10 | );
11 | 
12 | -- AddForeignKey
13 | ALTER TABLE "Image" ADD CONSTRAINT "Image_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
14 | 


--------------------------------------------------------------------------------
/prisma/migrations/20250615144652_chat_branching/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Chat" ADD COLUMN     "parentChatId" TEXT;
3 | 
4 | -- AlterTable
5 | ALTER TABLE "Message" ADD COLUMN     "modelId" TEXT NOT NULL DEFAULT 'openai:gpt-4o';
6 | 
7 | -- AddForeignKey
8 | ALTER TABLE "Chat" ADD CONSTRAINT "Chat_parentChatId_fkey" FOREIGN KEY ("parentChatId") REFERENCES "Chat"("id") ON DELETE CASCADE ON UPDATE CASCADE;
9 | 


--------------------------------------------------------------------------------
/prisma/migrations/20250615224432_features/migration.sql:
--------------------------------------------------------------------------------
 1 | -- CreateTable
 2 | CREATE TABLE "Feature" (
 3 |     "id" TEXT NOT NULL,
 4 |     "name" TEXT NOT NULL,
 5 |     "description" TEXT,
 6 |     "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
 7 |     "updatedAt" TIMESTAMP(3) NOT NULL,
 8 | 
 9 |     CONSTRAINT "Feature_pkey" PRIMARY KEY ("id")
10 | );
11 | 
12 | -- CreateTable
13 | CREATE TABLE "UserFeature" (
14 |     "id" TEXT NOT NULL,
15 |     "userId" TEXT NOT NULL,
16 |     "featureId" TEXT NOT NULL,
17 |     "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
18 | 
19 |     CONSTRAINT "UserFeature_pkey" PRIMARY KEY ("id")
20 | );
21 | 
22 | -- CreateIndex
23 | CREATE UNIQUE INDEX "Feature_name_key" ON "Feature"("name");
24 | 
25 | -- CreateIndex
26 | CREATE UNIQUE INDEX "UserFeature_userId_featureId_key" ON "UserFeature"("userId", "featureId");
27 | 
28 | -- AddForeignKey
29 | ALTER TABLE "UserFeature" ADD CONSTRAINT "UserFeature_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
30 | 
31 | -- AddForeignKey
32 | ALTER TABLE "UserFeature" ADD CONSTRAINT "UserFeature_featureId_fkey" FOREIGN KEY ("featureId") REFERENCES "Feature"("id") ON DELETE CASCADE ON UPDATE CASCADE;
33 | 


--------------------------------------------------------------------------------
/prisma/migrations/20250616064236_workbenches/migration.sql:
--------------------------------------------------------------------------------
 1 | -- AlterTable
 2 | ALTER TABLE "Chat" ADD COLUMN     "workbenchId" TEXT;
 3 | 
 4 | -- CreateTable
 5 | CREATE TABLE "Workbench" (
 6 |     "id" TEXT NOT NULL,
 7 |     "name" TEXT NOT NULL,
 8 |     "systemPrompt" TEXT NOT NULL,
 9 |     "toolkitIds" TEXT[],
10 |     "userId" TEXT NOT NULL,
11 |     "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
12 |     "updatedAt" TIMESTAMP(3) NOT NULL,
13 | 
14 |     CONSTRAINT "Workbench_pkey" PRIMARY KEY ("id")
15 | );
16 | 
17 | -- AddForeignKey
18 | ALTER TABLE "Chat" ADD CONSTRAINT "Chat_workbenchId_fkey" FOREIGN KEY ("workbenchId") REFERENCES "Workbench"("id") ON DELETE SET NULL ON UPDATE CASCADE;
19 | 
20 | -- AddForeignKey
21 | ALTER TABLE "Workbench" ADD CONSTRAINT "Workbench_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
22 | 


--------------------------------------------------------------------------------
/prisma/migrations/20250714211437_add_starring/migration.sql:
--------------------------------------------------------------------------------
1 | -- AlterTable
2 | ALTER TABLE "Chat" ADD COLUMN     "starred" BOOLEAN NOT NULL DEFAULT false;
3 | 


--------------------------------------------------------------------------------
/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (e.g., Git)
3 | provider = "postgresql"
4 | 


--------------------------------------------------------------------------------
/public/icons/anthropic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/anthropic.png


--------------------------------------------------------------------------------
/public/icons/deepseek.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/deepseek.png


--------------------------------------------------------------------------------
/public/icons/discord.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/discord.png


--------------------------------------------------------------------------------
/public/icons/e2b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/e2b.png


--------------------------------------------------------------------------------
/public/icons/google.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/google.png


--------------------------------------------------------------------------------
/public/icons/lucide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/lucide.png


--------------------------------------------------------------------------------
/public/icons/mem0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/mem0.png


--------------------------------------------------------------------------------
/public/icons/meta-llama.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/meta-llama.png


--------------------------------------------------------------------------------
/public/icons/motion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/motion.png


--------------------------------------------------------------------------------
/public/icons/next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/next.png


--------------------------------------------------------------------------------
/public/icons/nextauth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/nextauth.png


--------------------------------------------------------------------------------
/public/icons/openai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/openai.png


--------------------------------------------------------------------------------
/public/icons/perplexity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/perplexity.png


--------------------------------------------------------------------------------
/public/icons/postgres.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/postgres.png


--------------------------------------------------------------------------------
/public/icons/prisma.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/prisma.png


--------------------------------------------------------------------------------
/public/icons/qwen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/qwen.png


--------------------------------------------------------------------------------
/public/icons/radix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/radix.png


--------------------------------------------------------------------------------
/public/icons/react.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/react.png


--------------------------------------------------------------------------------
/public/icons/recharts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/recharts.png


--------------------------------------------------------------------------------
/public/icons/redis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/redis.png


--------------------------------------------------------------------------------
/public/icons/shadcn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/shadcn.png


--------------------------------------------------------------------------------
/public/icons/t3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/t3.png


--------------------------------------------------------------------------------
/public/icons/tRPC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/tRPC.png


--------------------------------------------------------------------------------
/public/icons/tailwind.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/tailwind.png


--------------------------------------------------------------------------------
/public/icons/tanstack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/tanstack.png


--------------------------------------------------------------------------------
/public/icons/vercel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/vercel.png


--------------------------------------------------------------------------------
/public/icons/x-ai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/x-ai.png


--------------------------------------------------------------------------------
/public/icons/xai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/xai.png


--------------------------------------------------------------------------------
/public/icons/zod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/public/icons/zod.png


--------------------------------------------------------------------------------
/src/ai/generate.ts:
--------------------------------------------------------------------------------
 1 | import { generateText as generateTextAi, streamText as streamTextAi } from "ai";
 2 | 
 3 | import { openrouter } from "@openrouter/ai-sdk-provider";
 4 | 
 5 | export const generateText = (
 6 |   model: `${string}/${string}`,
 7 |   params: Omit<Parameters<typeof generateTextAi>[0], "model">,
 8 | ) => {
 9 |   return generateTextAi({
10 |     model: openrouter(model),
11 |     ...params,
12 |   });
13 | };
14 | 
15 | export const streamText = (
16 |   model: `${string}/${string}`,
17 |   params: Omit<Parameters<typeof streamTextAi>[0], "model">,
18 | ) => {
19 |   return streamTextAi({
20 |     model: openrouter(model),
21 |     ...params,
22 |   });
23 | };
24 | 


--------------------------------------------------------------------------------
/src/ai/models/all.ts:
--------------------------------------------------------------------------------
 1 | // for type inference
 2 | 
 3 | import { openAiImageModels, openAiLanguageModels } from "./openai";
 4 | import { xaiImageModels, xaiLanguageModels } from "./xai";
 5 | import { perplexityModels } from "./perplexity";
 6 | import { googleModels } from "./google";
 7 | import { anthropicModels } from "./anthropic";
 8 | import { llamaModels } from "./llama";
 9 | import { qwenModels } from "./qwen";
10 | import { deepseekModels } from "./deepseek";
11 | 
12 | export const allLanguageModels = [
13 |   ...anthropicModels,
14 |   ...googleModels,
15 |   ...openAiLanguageModels,
16 |   ...xaiLanguageModels,
17 |   ...perplexityModels,
18 |   ...llamaModels,
19 |   ...qwenModels,
20 |   ...deepseekModels,
21 | ];
22 | 
23 | export const allImageModels = [...openAiImageModels, ...xaiImageModels];
24 | 


--------------------------------------------------------------------------------
/src/ai/models/deepseek.ts:
--------------------------------------------------------------------------------
 1 | import { type LanguageModel, LanguageModelCapability } from "@/ai/types";
 2 | 
 3 | const deepseekModelData: Omit<LanguageModel, "provider">[] = [
 4 |   {
 5 |     name: "DeepSeek R1",
 6 |     modelId: "deepseek-r1-0528",
 7 |     description:
 8 |       "DeepSeek R1 is a large language model optimized for various natural language processing tasks",
 9 |     capabilities: [],
10 |     bestFor: ["General purpose", "Text generation", "Conversation"],
11 |     contextLength: 131072,
12 |   },
13 |   {
14 |     name: "DeepSeek 3",
15 |     modelId: "deepseek-chat-v3-0324",
16 |     description:
17 |       "DeepSeek 3 is the latest generation of DeepSeek models with enhanced capabilities",
18 |     capabilities: [LanguageModelCapability.ToolCalling],
19 |     bestFor: ["Advanced reasoning", "Code generation", "Creative writing"],
20 |     contextLength: 131072,
21 |   },
22 | ];
23 | 
24 | export const deepseekModels: LanguageModel[] = deepseekModelData.map(
25 |   (model) => ({
26 |     ...model,
27 |     provider: "deepseek",
28 |   }),
29 | );
30 | 


--------------------------------------------------------------------------------
/src/ai/models/index.ts:
--------------------------------------------------------------------------------
 1 | import { env } from "@/env";
 2 | 
 3 | import { type LanguageModel } from "@/ai/types";
 4 | 
 5 | import { anthropicModels } from "./anthropic";
 6 | import { googleModels } from "./google";
 7 | import { openAiLanguageModels, openAiImageModels } from "./openai";
 8 | import { xaiImageModels, xaiLanguageModels } from "./xai";
 9 | import { perplexityModels } from "./perplexity";
10 | import { llamaModels } from "./llama";
11 | import { qwenModels } from "./qwen";
12 | import { deepseekModels } from "./deepseek";
13 | 
14 | export const languageModels: LanguageModel[] = [
15 |   ...anthropicModels,
16 |   ...googleModels,
17 |   ...openAiLanguageModels,
18 |   ...xaiLanguageModels,
19 |   ...perplexityModels,
20 |   ...llamaModels,
21 |   ...qwenModels,
22 |   ...deepseekModels,
23 | ];
24 | 
25 | export const imageModels = [
26 |   ...("OPENAI_API_KEY" in env ? openAiImageModels : []),
27 |   ...("XAI_API_KEY" in env ? xaiImageModels : []),
28 | ];
29 | 


--------------------------------------------------------------------------------
/src/ai/models/perplexity.ts:
--------------------------------------------------------------------------------
 1 | import { LanguageModelCapability, type LanguageModel } from "@/ai/types";
 2 | 
 3 | const perplexityModelData: Omit<LanguageModel, "provider">[] = [
 4 |   {
 5 |     name: "Sonar Pro",
 6 |     modelId: "sonar-pro",
 7 |     description: "Next-generation Sonar with enhanced reasoning",
 8 |     capabilities: [LanguageModelCapability.WebSearch],
 9 |     bestFor: ["Complex reasoning", "Advanced analysis", "Research"],
10 |     contextLength: 2000000,
11 |   },
12 |   {
13 |     name: "Sonar",
14 |     modelId: "sonar",
15 |     description: "Fast version of Sonar for quick responses",
16 |     capabilities: [LanguageModelCapability.WebSearch],
17 |     bestFor: ["Quick tasks", "Real-time responses", "Efficient processing"],
18 |     contextLength: 1000000,
19 |   },
20 | ];
21 | 
22 | export const perplexityModels: LanguageModel[] = perplexityModelData.map(
23 |   (model) => ({
24 |     ...model,
25 |     provider: "perplexity",
26 |   }),
27 | );
28 | 


--------------------------------------------------------------------------------
/src/ai/models/qwen.ts:
--------------------------------------------------------------------------------
 1 | import { type LanguageModel, LanguageModelCapability } from "@/ai/types";
 2 | 
 3 | const qwenModelData: Omit<LanguageModel, "provider">[] = [
 4 |   {
 5 |     name: "Qwen QwQ 32B",
 6 |     modelId: "qwq-32b",
 7 |     description:
 8 |       "Qwen QWQ 32B is a large language model optimized for various natural language processing tasks",
 9 |     capabilities: [LanguageModelCapability.ToolCalling],
10 |     bestFor: ["General purpose", "Text generation", "Conversation"],
11 |     contextLength: 131072,
12 |   },
13 |   {
14 |     name: "Qwen 3 32B",
15 |     modelId: "qwen3-32b",
16 |     description:
17 |       "Qwen 3 32B is the latest generation of Qwen models with enhanced capabilities",
18 |     capabilities: [LanguageModelCapability.ToolCalling],
19 |     bestFor: ["Advanced reasoning", "Code generation", "Creative writing"],
20 |     contextLength: 131072,
21 |   },
22 | ];
23 | 
24 | export const qwenModels: LanguageModel[] = qwenModelData.map((model) => ({
25 |   ...model,
26 |   provider: "qwen",
27 | }));
28 | 


--------------------------------------------------------------------------------
/src/ai/registry.ts:
--------------------------------------------------------------------------------
 1 | import { createProviderRegistry } from "ai";
 2 | 
 3 | import { openai } from "@ai-sdk/openai";
 4 | import { xai } from "@ai-sdk/xai";
 5 | 
 6 | export const registry = createProviderRegistry({
 7 |   openai,
 8 |   xai,
 9 | });
10 | 


--------------------------------------------------------------------------------
/src/ai/types.ts:
--------------------------------------------------------------------------------
 1 | import type { ProviderMetadata } from "ai";
 2 | 
 3 | export enum LanguageModelCapability {
 4 |   Vision = "vision",
 5 |   WebSearch = "web-search",
 6 |   Reasoning = "reasoning",
 7 |   Pdf = "pdf",
 8 |   ToolCalling = "tool-calling",
 9 | }
10 | 
11 | export type LanguageModel = {
12 |   name: string;
13 |   provider: string;
14 |   modelId: string;
15 |   description?: string;
16 |   capabilities?: LanguageModelCapability[];
17 |   bestFor?: string[];
18 |   contextLength?: number;
19 |   isNew?: boolean;
20 |   providerOptions?: ProviderMetadata;
21 | };
22 | 
23 | export enum SearchOptions {
24 |   Native = "Native",
25 |   OpenAiResponses = "OpenAI Responses",
26 |   Exa = "Exa Search",
27 | }
28 | 
29 | export type ImageModelProvider = "openai" | "xai";
30 | 
31 | export type ImageModel = {
32 |   name: string;
33 |   provider: ImageModelProvider;
34 |   modelId: string;
35 |   description?: string;
36 | };
37 | 


--------------------------------------------------------------------------------
/src/app/[id]/page.tsx:
--------------------------------------------------------------------------------
 1 | import { notFound, redirect } from "next/navigation";
 2 | 
 3 | import { Chat } from "@/app/_components/chat";
 4 | import { api } from "@/trpc/server";
 5 | 
 6 | import { auth } from "@/server/auth";
 7 | 
 8 | export default async function Page(props: { params: Promise<{ id: string }> }) {
 9 |   const params = await props.params;
10 |   const { id } = params;
11 | 
12 |   const session = await auth();
13 | 
14 |   if (!session) {
15 |     redirect(`/login?redirect=/${id}`);
16 |   }
17 | 
18 |   const chat = await api.chats.getChat(id);
19 | 
20 |   if (!chat) {
21 |     notFound();
22 |   }
23 | 
24 |   return (
25 |     <>
26 |       <Chat
27 |         id={chat.id}
28 |         initialVisibilityType={chat.visibility}
29 |         isReadonly={session?.user?.id !== chat.userId}
30 |         isNew={false}
31 |       />
32 |     </>
33 |   );
34 | }
35 | 


--------------------------------------------------------------------------------
/src/app/_components/auth/auth-buttons.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { Button } from "@/components/ui/button";
 4 | import { AuthProviderIcon } from "../navbar/account-button/provider-icon";
 5 | import { signIn } from "next-auth/react";
 6 | 
 7 | interface AuthButtonsProps {
 8 |   providers: {
 9 |     name: string;
10 |     id: string;
11 |   }[];
12 |   redirect?: string;
13 | }
14 | 
15 | export const AuthButtons = ({ providers, redirect }: AuthButtonsProps) => {
16 |   return (
17 |     <div className="flex flex-col gap-2">
18 |       {providers.map((provider) => (
19 |         <Button
20 |           key={provider.id}
21 |           variant="outline"
22 |           className="w-full"
23 |           onClick={() => signIn(provider.id, { redirectTo: redirect })}
24 |         >
25 |           <AuthProviderIcon provider={provider.name} />
26 |           Sign in with {provider.name}
27 |         </Button>
28 |       ))}
29 |     </div>
30 |   );
31 | };
32 | 


--------------------------------------------------------------------------------
/src/app/_components/chat/input/model-select/native-search-toggle.tsx:
--------------------------------------------------------------------------------
 1 | import { LanguageModelCapability } from "@/ai/types";
 2 | import { useChatContext } from "@/app/_contexts/chat-context";
 3 | import {
 4 |   Tooltip,
 5 |   TooltipContent,
 6 |   TooltipTrigger,
 7 | } from "@/components/ui/tooltip";
 8 | import { cn } from "@/lib/utils";
 9 | import { Globe } from "lucide-react";
10 | 
11 | export const NativeSearchToggle = () => {
12 |   const { useNativeSearch, setUseNativeSearch, selectedChatModel } =
13 |     useChatContext();
14 | 
15 |   if (
16 |     !selectedChatModel?.capabilities?.includes(
17 |       LanguageModelCapability.WebSearch,
18 |     )
19 |   ) {
20 |     return null;
21 |   }
22 | 
23 |   return (
24 |     <Tooltip>
25 |       <TooltipTrigger asChild>
26 |         <div
27 |           onClick={(event) => {
28 |             event.preventDefault();
29 |             event.stopPropagation();
30 |             setUseNativeSearch(!useNativeSearch);
31 |           }}
32 |           onMouseDown={(event) => {
33 |             event.preventDefault();
34 |             event.stopPropagation();
35 |           }}
36 |           className={cn(
37 |             "size-fit cursor-pointer rounded-full bg-transparent p-1",
38 |             useNativeSearch && "bg-primary/10 text-primary",
39 |           )}
40 |           data-native-search-toggle="true"
41 |         >
42 |           <Globe className="size-4" />
43 |         </div>
44 |       </TooltipTrigger>
45 |       <TooltipContent>
46 |         {useNativeSearch ? (
47 |           <p>Native search enabled</p>
48 |         ) : (
49 |           <p>Native search disabled</p>
50 |         )}
51 |       </TooltipContent>
52 |     </Tooltip>
53 |   );
54 | };
55 | 


--------------------------------------------------------------------------------
/src/app/_components/chat/input/model-select/utils.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { Eye, Search, Sparkles, File, Wrench } from "lucide-react";
 4 | 
 5 | import { LanguageModelCapability } from "@/ai/types";
 6 | 
 7 | export const capabilityIcons: Record<
 8 |   LanguageModelCapability,
 9 |   React.ComponentType<{ className?: string }>
10 | > = {
11 |   [LanguageModelCapability.Vision]: Eye,
12 |   [LanguageModelCapability.WebSearch]: Search,
13 |   [LanguageModelCapability.Reasoning]: Sparkles,
14 |   [LanguageModelCapability.Pdf]: File,
15 |   [LanguageModelCapability.ToolCalling]: Wrench,
16 | };
17 | 
18 | export const capabilityLabels: Record<LanguageModelCapability, string> = {
19 |   [LanguageModelCapability.Vision]: "Vision",
20 |   [LanguageModelCapability.WebSearch]: "Web Search",
21 |   [LanguageModelCapability.Reasoning]: "Reasoning",
22 |   [LanguageModelCapability.Pdf]: "PDF",
23 |   [LanguageModelCapability.ToolCalling]: "Tool Calling",
24 | };
25 | 
26 | export const capabilityColors: Record<LanguageModelCapability, string> = {
27 |   [LanguageModelCapability.Vision]: "bg-green-100 text-green-800",
28 |   [LanguageModelCapability.WebSearch]: "bg-yellow-100 text-yellow-800",
29 |   [LanguageModelCapability.Reasoning]: "bg-orange-100 text-orange-800",
30 |   [LanguageModelCapability.Pdf]: "bg-gray-200 text-gray-800",
31 |   [LanguageModelCapability.ToolCalling]: "bg-blue-100 text-blue-800",
32 | };
33 | 
34 | export const formatContextLength = (length?: number) => {
35 |   if (!length) return null;
36 |   if (length >= 1000000) return `${(length / 1000000).toFixed(1)}M tokens`;
37 |   if (length >= 1000) return `${(length / 1000).toFixed(0)}K tokens`;
38 |   return `${length} tokens`;
39 | };
40 | 
41 | export const modelProviderNames: Record<string, string> = {
42 |   openai: "OpenAI",
43 |   google: "Google",
44 |   anthropic: "Anthropic",
45 |   perplexity: "Perplexity",
46 |   "x-ai": "xAI",
47 |   "meta-llama": "Llama",
48 |   qwen: "Qwen",
49 |   deepseek: "DeepSeek",
50 | };
51 | 


--------------------------------------------------------------------------------
/src/app/_components/chat/layout.tsx:
--------------------------------------------------------------------------------
 1 | interface Props {
 2 |   children: React.ReactNode;
 3 | }
 4 | 
 5 | export const ChatLayout: React.FC<Props> = ({ children }) => {
 6 |   return (
 7 |     <div className="flex h-0 flex-1 flex-col overflow-hidden">{children}</div>
 8 |   );
 9 | };
10 | 


--------------------------------------------------------------------------------
/src/app/_components/chat/messages/greeting.tsx:
--------------------------------------------------------------------------------
 1 | import { motion } from "motion/react";
 2 | 
 3 | export const Greeting = () => {
 4 |   return (
 5 |     <div
 6 |       key="overview"
 7 |       className="mx-auto flex size-full max-w-3xl flex-col justify-center px-8 md:mt-20"
 8 |     >
 9 |       <motion.div
10 |         initial={{ opacity: 0, y: 10 }}
11 |         animate={{ opacity: 1, y: 0 }}
12 |         exit={{ opacity: 0, y: 10 }}
13 |         transition={{ delay: 0.5 }}
14 |         className="text-2xl font-semibold"
15 |       >
16 |         Hello there!
17 |       </motion.div>
18 |       <motion.div
19 |         initial={{ opacity: 0, y: 10 }}
20 |         animate={{ opacity: 1, y: 0 }}
21 |         exit={{ opacity: 0, y: 10 }}
22 |         transition={{ delay: 0.6 }}
23 |         className="text-2xl text-zinc-500"
24 |       >
25 |         How can I help you today?
26 |       </motion.div>
27 |     </div>
28 |   );
29 | };
30 | 


--------------------------------------------------------------------------------
/src/app/_components/landing-page/auth-modal.tsx:
--------------------------------------------------------------------------------
 1 | import {
 2 |   Dialog,
 3 |   DialogContent,
 4 |   DialogHeader,
 5 |   DialogTitle,
 6 |   DialogDescription,
 7 |   DialogTrigger,
 8 | } from "@/components/ui/dialog";
 9 | import { AuthButtons } from "../auth/auth-buttons";
10 | import { providers } from "@/server/auth/providers";
11 | import { Logo } from "@/components/ui/logo";
12 | import { VStack } from "@/components/ui/stack";
13 | 
14 | interface AuthModalProps {
15 |   children: React.ReactNode;
16 | }
17 | 
18 | export const AuthModal = ({ children }: AuthModalProps) => {
19 |   return (
20 |     <Dialog>
21 |       <DialogTrigger asChild>{children}</DialogTrigger>
22 |       <DialogContent showCloseButton={false} className="gap-6">
23 |         <DialogHeader className="items-center gap-2">
24 |           <Logo className="size-16" />
25 |           <VStack>
26 |             <DialogTitle className="text-primary text-xl">
27 |               Sign in to Toolkit
28 |             </DialogTitle>
29 |             <DialogDescription className="hidden">
30 |               Sign in to your account to get started with Toolkit.
31 |             </DialogDescription>
32 |           </VStack>
33 |         </DialogHeader>
34 |         <AuthButtons
35 |           providers={providers.map((provider) => ({
36 |             name: provider.name,
37 |             id: provider.id,
38 |           }))}
39 |         />
40 |       </DialogContent>
41 |     </Dialog>
42 |   );
43 | };
44 | 


--------------------------------------------------------------------------------
/src/app/_components/landing-page/dependencies/types.ts:
--------------------------------------------------------------------------------
1 | export interface Dependency {
2 |   name: string;
3 |   icon: React.ReactNode | null;
4 | }
5 | 


--------------------------------------------------------------------------------
/src/app/_components/landing-page/hero/message/memory.tsx:
--------------------------------------------------------------------------------
 1 | import { mem0AddMemoryToolConfigClient } from "@/toolkits/toolkits/mem0/tools/add_memory/client";
 2 | 
 3 | export const MemoryCalling: React.FC = () => {
 4 |   return (
 5 |     <mem0AddMemoryToolConfigClient.CallComponent
 6 |       args={{
 7 |         content:
 8 |           "User is building an AI chatbot and wants to learn how to use the vercel/ai toolkit.",
 9 |       }}
10 |       isPartial={false}
11 |     />
12 |   );
13 | };
14 | 
15 | export const MemoryResult: React.FC = () => {
16 |   return (
17 |     <mem0AddMemoryToolConfigClient.ResultComponent
18 |       result={{
19 |         content:
20 |           "User is building an AI chatbot and wants to learn how to use the vercel/ai toolkit.",
21 |         success: true,
22 |       }}
23 |       args={{
24 |         content:
25 |           "User is building an AI chatbot and wants to learn how to use the vercel/ai toolkit.",
26 |       }}
27 |       append={() => void 0}
28 |     />
29 |   );
30 | };
31 | 


--------------------------------------------------------------------------------
/src/app/_components/landing-page/hero/message/top-repositories.tsx:
--------------------------------------------------------------------------------
 1 | import { githubSearchReposToolConfigClient } from "@/toolkits/toolkits/github/tools/client";
 2 | 
 3 | export const TopRepositoriesCalling: React.FC = () => {
 4 |   return (
 5 |     <githubSearchReposToolConfigClient.CallComponent
 6 |       args={{
 7 |         query: "org:vercel ai in:name,description,topics",
 8 |         per_page: 5,
 9 |         page: 1,
10 |       }}
11 |       isPartial={false}
12 |     />
13 |   );
14 | };
15 | 
16 | export const TopRepositoriesResult: React.FC = () => {
17 |   return (
18 |     <githubSearchReposToolConfigClient.ResultComponent
19 |       result={{
20 |         repositories: [
21 |           {
22 |             stars: 14997,
23 |             language: "TypeScript",
24 |             full_name: "vercel/ai",
25 |             description:
26 |               "The AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and agents ",
27 |           },
28 |           {
29 |             stars: 16639,
30 |             language: "TypeScript",
31 |             full_name: "vercel/ai-chatbot",
32 |             description:
33 |               "A full-featured, hackable Next.js AI chatbot built by Vercel",
34 |           },
35 |           {
36 |             stars: 1280,
37 |             language: "TypeScript",
38 |             full_name: "vercel/modelfusion",
39 |             description: "The TypeScript library for building AI applications.",
40 |           },
41 |         ],
42 |       }}
43 |       args={{ query: "org:vercel", per_page: 5, page: 1 }}
44 |       append={() => void 0}
45 |     />
46 |   );
47 | };
48 | 


--------------------------------------------------------------------------------
/src/app/_components/landing-page/hero/motion-container.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { motion } from "motion/react";
 4 | 
 5 | export const MotionContainer = ({
 6 |   children,
 7 |   initial = { opacity: 0, y: 30 },
 8 |   animate = { opacity: 1, y: 0 },
 9 |   transition = { duration: 0.6 },
10 |   className,
11 | }: {
12 |   children: React.ReactNode;
13 |   initial?: {
14 |     opacity: number;
15 |     y: number;
16 |   };
17 |   animate?: {
18 |     opacity: number;
19 |     y: number;
20 |   };
21 |   transition?: {
22 |     duration: number;
23 |   };
24 |   className?: string;
25 | }) => {
26 |   return (
27 |     <motion.div
28 |       initial={initial}
29 |       animate={animate}
30 |       transition={transition}
31 |       className={className}
32 |     >
33 |       {children}
34 |     </motion.div>
35 |   );
36 | };
37 | 


--------------------------------------------------------------------------------
/src/app/_components/landing-page/hero/types.ts:
--------------------------------------------------------------------------------
 1 | import type { Toolkits } from "@/toolkits/toolkits/shared";
 2 | 
 3 | export type Message = {
 4 |   id: string;
 5 | } & (
 6 |   | {
 7 |       type: "user";
 8 |       content: string;
 9 |     }
10 |   | {
11 |       type: "assistant";
12 |       content: string;
13 |     }
14 |   | {
15 |       type: "tool";
16 |       callComponent: React.ReactNode;
17 |       resultComponent: React.ReactNode;
18 |       toolkit: Toolkits;
19 |     }
20 | );
21 | 


--------------------------------------------------------------------------------
/src/app/_components/landing-page/index.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { HeroSection } from "./hero";
 3 | import { ToolkitCreationSection } from "./toolkit-creation";
 4 | import { WorkbenchSection } from "./workbenches";
 5 | import { DependenciesSection } from "./dependencies";
 6 | import { Navbar } from "./navbar";
 7 | 
 8 | export const LandingPage: React.FC = () => {
 9 |   return (
10 |     <div className="h-fit min-h-screen">
11 |       <Navbar />
12 |       <HeroSection />
13 |       <DependenciesSection />
14 |       <ToolkitCreationSection />
15 |       <WorkbenchSection />
16 |     </div>
17 |   );
18 | };
19 | 
20 | export default LandingPage;
21 | 


--------------------------------------------------------------------------------
/src/app/_components/landing-page/navbar/index.tsx:
--------------------------------------------------------------------------------
 1 | import { Logo } from "@/components/ui/logo";
 2 | import { HStack } from "@/components/ui/stack";
 3 | import { ColorModeToggle } from "../../navbar/color-mode-toggle";
 4 | import { Button } from "@/components/ui/button";
 5 | import { AuthModal } from "../auth-modal";
 6 | 
 7 | export const Navbar = () => {
 8 |   return (
 9 |     <HStack className="bg-background fixed top-0 z-50 w-full border-b py-2">
10 |       <HStack className="container mx-auto justify-between px-2">
11 |         <HStack>
12 |           <Logo className="size-6" />
13 |           <h1 className="shimmer-text overflow-hidden text-lg font-bold whitespace-nowrap">
14 |             Toolkit.dev
15 |           </h1>
16 |         </HStack>
17 |         <HStack>
18 |           <AuthModal>
19 |             <Button className="user-message">Try it Out</Button>
20 |           </AuthModal>
21 |           <ColorModeToggle />
22 |         </HStack>
23 |       </HStack>
24 |     </HStack>
25 |   );
26 | };
27 | 


--------------------------------------------------------------------------------
/src/app/_components/landing-page/workbenches/data.tsx:
--------------------------------------------------------------------------------
 1 | import { Toolkits } from "@/toolkits/toolkits/shared";
 2 | import type { WorkbenchExample } from "./types";
 3 | import { BarChart3, Code, Briefcase } from "lucide-react";
 4 | 
 5 | export const workbenchExamples: WorkbenchExample[] = [
 6 |   {
 7 |     title: "Research Assistant",
 8 |     description: "Comprehensive research and documentation workbench",
 9 |     systemPrompt:
10 |       "You are a research assistant that helps users gather, analyze, and document information from multiple sources.",
11 |     toolkits: [
12 |       Toolkits.Exa,
13 |       Toolkits.Github,
14 |       Toolkits.Image,
15 |       Toolkits.Notion,
16 |       Toolkits.Memory,
17 |     ],
18 |     icon: <BarChart3 className="h-5 w-5" />,
19 |     color: "blue",
20 |   },
21 |   {
22 |     title: "Data Analyst",
23 |     description: "Data processing and visualization workbench",
24 |     systemPrompt:
25 |       "You are a data analyst specializing in extracting insights from various data sources and creating compelling visualizations.",
26 |     toolkits: [
27 |       Toolkits.Notion,
28 |       Toolkits.GoogleDrive,
29 |       Toolkits.E2B,
30 |       Toolkits.Image,
31 |     ],
32 |     icon: <Code className="h-5 w-5" />,
33 |     color: "green",
34 |   },
35 |   {
36 |     title: "Project Manager",
37 |     description: "Team coordination and project management workbench",
38 |     systemPrompt:
39 |       "You are a project manager focused on coordinating teams, scheduling meetings, and tracking project progress.",
40 |     toolkits: [
41 |       Toolkits.GoogleCalendar,
42 |       Toolkits.Notion,
43 |       Toolkits.GoogleDrive,
44 |       Toolkits.Memory,
45 |     ],
46 |     icon: <Briefcase className="h-5 w-5" />,
47 |     color: "purple",
48 |   },
49 | ];
50 | 


--------------------------------------------------------------------------------
/src/app/_components/landing-page/workbenches/index.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import React from "react";
 4 | import { motion } from "motion/react";
 5 | import { WorkbenchCard } from "./card";
 6 | import { workbenchExamples } from "./data";
 7 | 
 8 | export const WorkbenchSection: React.FC = () => {
 9 |   return (
10 |     <section className="from-muted/20 to-background bg-gradient-to-b py-24">
11 |       <div className="container mx-auto px-2 md:px-4">
12 |         <motion.div
13 |           initial={{ opacity: 0, y: 20 }}
14 |           whileInView={{ opacity: 1, y: 0 }}
15 |           transition={{ duration: 0.6 }}
16 |           viewport={{ once: true }}
17 |           className="mb-16 text-center"
18 |         >
19 |           <h2 className="mb-4 text-3xl font-bold md:text-4xl">
20 |             Configure Custom
21 |             <span className="text-primary block">Workbenches</span>
22 |           </h2>
23 |           <p className="text-muted-foreground mx-auto mb-8 max-w-2xl text-lg">
24 |             Workbenches combine multiple toolkits with specialized system
25 |             prompts to create focused AI assistants for specific use cases. Each
26 |             workbench orchestrates toolkit interactions seamlessly.
27 |           </p>
28 |         </motion.div>
29 | 
30 |         <div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
31 |           {workbenchExamples.map((workbench, index) => (
32 |             <WorkbenchCard
33 |               key={workbench.title}
34 |               workbench={workbench}
35 |               delay={index * 0.1}
36 |             />
37 |           ))}
38 |         </div>
39 |       </div>
40 |     </section>
41 |   );
42 | };
43 | 


--------------------------------------------------------------------------------
/src/app/_components/landing-page/workbenches/types.ts:
--------------------------------------------------------------------------------
 1 | import type { Toolkits } from "@/toolkits/toolkits/shared";
 2 | 
 3 | export interface WorkbenchExample {
 4 |   title: string;
 5 |   description: string;
 6 |   systemPrompt: string;
 7 |   toolkits: Toolkits[];
 8 |   icon: React.ReactNode;
 9 |   color: string;
10 | }
11 | 


--------------------------------------------------------------------------------
/src/app/_components/navbar/account-button/authenticated.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { LogOut, User } from "lucide-react";
 4 | import Link from "next/link";
 5 | 
 6 | import { signOut } from "next-auth/react";
 7 | 
 8 | import { Button } from "@/components/ui/button";
 9 | import {
10 |   DropdownMenu,
11 |   DropdownMenuContent,
12 |   DropdownMenuItem,
13 |   DropdownMenuSeparator,
14 |   DropdownMenuTrigger,
15 | } from "@/components/ui/dropdown-menu";
16 | 
17 | import type { Session } from "next-auth";
18 | 
19 | interface Props {
20 |   session: Session;
21 | }
22 | 
23 | export const Authenticated: React.FC<Props> = ({ session }) => {
24 |   return (
25 |     <DropdownMenu>
26 |       <DropdownMenuTrigger asChild>
27 |         <Button variant="outline">
28 |           {session.user.image ? (
29 |             // eslint-disable-next-line @next/next/no-img-element
30 |             <img
31 |               src={session.user.image}
32 |               alt={session.user.name ?? ""}
33 |               className="size-4 rounded-full"
34 |             />
35 |           ) : (
36 |             <User className="size-4" />
37 |           )}
38 |           {session.user.name ?? "Signed In"}
39 |         </Button>
40 |       </DropdownMenuTrigger>
41 |       <DropdownMenuContent align="end">
42 |         <DropdownMenuItem asChild>
43 |           <Link href="/account">
44 |             <User />
45 |             Account
46 |           </Link>
47 |         </DropdownMenuItem>
48 |         <DropdownMenuSeparator />
49 |         <DropdownMenuItem onClick={() => signOut()}>
50 |           <LogOut />
51 |           Sign Out
52 |         </DropdownMenuItem>
53 |       </DropdownMenuContent>
54 |     </DropdownMenu>
55 |   );
56 | };
57 | 


--------------------------------------------------------------------------------
/src/app/_components/navbar/account-button/index.tsx:
--------------------------------------------------------------------------------
 1 | import { Unauthenticated } from "./unauthenticated";
 2 | import { Authenticated } from "./authenticated";
 3 | 
 4 | import { auth } from "@/server/auth";
 5 | import { providers } from "@/server/auth/providers";
 6 | 
 7 | export const AccountButton = async () => {
 8 |   const session = await auth();
 9 | 
10 |   if (!session) {
11 |     return (
12 |       <Unauthenticated
13 |         providers={providers.map((provider) => ({
14 |           name: provider.name,
15 |           id: provider.id,
16 |         }))}
17 |       />
18 |     );
19 |   }
20 | 
21 |   return <Authenticated session={session} />;
22 | };
23 | 


--------------------------------------------------------------------------------
/src/app/_components/navbar/account-button/provider-icon.tsx:
--------------------------------------------------------------------------------
 1 | import Image from "next/image";
 2 | 
 3 | import { cn } from "@/lib/utils";
 4 | import { SiGithub, SiNotion, SiX } from "@icons-pack/react-simple-icons";
 5 | 
 6 | interface Props {
 7 |   provider: string;
 8 |   className?: string;
 9 | }
10 | 
11 | export const AuthProviderIcon: React.FC<Props> = ({ provider, className }) => {
12 |   const Icon =
13 |     {
14 |       Discord: ({ className }: { className?: string }) => (
15 |         <Image
16 |           src="/icons/discord.png"
17 |           alt="Discord"
18 |           className={className}
19 |           width={16}
20 |           height={16}
21 |         />
22 |       ),
23 |       Google: ({ className }: { className?: string }) => (
24 |         <Image
25 |           src="/icons/google.png"
26 |           alt="Google"
27 |           className={className}
28 |           width={16}
29 |           height={16}
30 |         />
31 |       ),
32 |       GitHub: SiGithub,
33 |       Twitter: SiX,
34 |       Notion: SiNotion,
35 |     }[provider] ?? null;
36 | 
37 |   return Icon ? <Icon className={cn("size-4", className)} /> : null;
38 | };
39 | 


--------------------------------------------------------------------------------
/src/app/_components/navbar/account-button/unauthenticated.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { User } from "lucide-react";
 4 | 
 5 | import { signIn } from "next-auth/react";
 6 | 
 7 | import { Button } from "@/components/ui/button";
 8 | import {
 9 |   DropdownMenu,
10 |   DropdownMenuContent,
11 |   DropdownMenuItem,
12 |   DropdownMenuTrigger,
13 | } from "@/components/ui/dropdown-menu";
14 | 
15 | import { AuthProviderIcon } from "./provider-icon";
16 | 
17 | interface Props {
18 |   providers: {
19 |     name: string;
20 |     id: string;
21 |   }[];
22 | }
23 | 
24 | export const Unauthenticated: React.FC<Props> = ({ providers }) => {
25 |   return (
26 |     <DropdownMenu>
27 |       <DropdownMenuTrigger asChild>
28 |         <Button variant="outline">
29 |           <User />
30 |           Sign In
31 |         </Button>
32 |       </DropdownMenuTrigger>
33 |       <DropdownMenuContent>
34 |         {providers.map((provider) => (
35 |           <DropdownMenuItem
36 |             key={provider.id}
37 |             onClick={() => signIn(provider.id, { callbackUrl: "/" })}
38 |           >
39 |             <AuthProviderIcon provider={provider.name} />
40 |             Sign In with {provider.name}
41 |           </DropdownMenuItem>
42 |         ))}
43 |       </DropdownMenuContent>
44 |     </DropdownMenu>
45 |   );
46 | };
47 | 


--------------------------------------------------------------------------------
/src/app/_components/navbar/color-mode-toggle.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { useTheme } from "@/app/_contexts/theme";
 4 | import { Button } from "@/components/ui/button";
 5 | import { Moon, Sun } from "lucide-react";
 6 | 
 7 | export const ColorModeToggle = () => {
 8 |   const { theme, toggleTheme } = useTheme();
 9 | 
10 |   return (
11 |     <Button
12 |       onClick={toggleTheme}
13 |       variant="outline"
14 |       aria-label={`Switch to ${theme === "light" ? "dark" : "light"} mode`}
15 |       size="icon"
16 |     >
17 |       {theme === "light" ? (
18 |         <Moon className="h-5 w-5" />
19 |       ) : (
20 |         <Sun className="h-5 w-5" />
21 |       )}
22 |     </Button>
23 |   );
24 | };
25 | 


--------------------------------------------------------------------------------
/src/app/_components/navbar/index.tsx:
--------------------------------------------------------------------------------
 1 | import Link from "next/link";
 2 | 
 3 | import { AccountButton } from "./account-button";
 4 | import { ColorModeToggle } from "./color-mode-toggle";
 5 | import { HStack } from "@/components/ui/stack";
 6 | import { auth } from "@/server/auth";
 7 | import { SidebarTrigger } from "@/components/ui/sidebar";
 8 | import { Menu } from "lucide-react";
 9 | 
10 | export const Navbar = async () => {
11 |   const session = await auth();
12 | 
13 |   if (!session) return null;
14 | 
15 |   return (
16 |     <HStack className="bg-background sticky top-0 z-10 justify-between p-2 md:hidden">
17 |       <HStack>
18 |         <SidebarTrigger className="hover:bg-accent/50 rounded-lg p-2">
19 |           <Menu className="size-4" />
20 |         </SidebarTrigger>
21 |         <Link href="/">
22 |           <h1 className="overflow-hidden text-lg font-bold whitespace-nowrap">
23 |             Toolkit.dev
24 |           </h1>
25 |         </Link>
26 |       </HStack>
27 |       <HStack>
28 |         <AccountButton />
29 |         <ColorModeToggle />
30 |       </HStack>
31 |     </HStack>
32 |   );
33 | };
34 | 


--------------------------------------------------------------------------------
/src/app/_components/sidebar/main.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import Link from "next/link";
 4 | 
 5 | import { Edit } from "lucide-react";
 6 | 
 7 | import {
 8 |   SidebarGroup,
 9 |   SidebarMenu,
10 |   SidebarMenuButton,
11 |   SidebarMenuItem,
12 | } from "@/components/ui/sidebar";
13 | import { usePathname } from "next/navigation";
14 | 
15 | export const NavMain = () => {
16 |   const pathname = usePathname();
17 | 
18 |   const workbenchId =
19 |     pathname.split("/")[2] === "new" ? undefined : pathname.split("/")[2];
20 | 
21 |   const items = [
22 |     {
23 |       title: "New Chat",
24 |       url: workbenchId ? `/workbench/${workbenchId}` : "/",
25 |       icon: Edit,
26 |     },
27 |   ];
28 | 
29 |   return (
30 |     <SidebarGroup>
31 |       <SidebarMenu>
32 |         {items.map((item) => (
33 |           <SidebarMenuItem key={item.title}>
34 |             <SidebarMenuButton key={item.title} tooltip={item.title} asChild>
35 |               <Link href={item.url}>
36 |                 {item.icon && <item.icon />}
37 |                 <span>{item.title}</span>
38 |               </Link>
39 |             </SidebarMenuButton>
40 |           </SidebarMenuItem>
41 |         ))}
42 |       </SidebarMenu>
43 |     </SidebarGroup>
44 |   );
45 | };
46 | 


--------------------------------------------------------------------------------
/src/app/_hooks/use-auto-resume.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { useEffect } from "react";
 4 | 
 5 | import type { UIMessage } from "ai";
 6 | import type { UseChatHelpers } from "@ai-sdk/react";
 7 | 
 8 | type DataPart = { type: "append-message"; message: string };
 9 | 
10 | export interface UseAutoResumeParams {
11 |   autoResume: boolean;
12 |   initialMessages: UIMessage[];
13 |   experimental_resume: UseChatHelpers["experimental_resume"];
14 |   data: UseChatHelpers["data"];
15 |   setMessages: UseChatHelpers["setMessages"];
16 | }
17 | 
18 | export function useAutoResume({
19 |   autoResume,
20 |   initialMessages,
21 |   experimental_resume,
22 |   data,
23 |   setMessages,
24 | }: UseAutoResumeParams) {
25 |   useEffect(() => {
26 |     if (!autoResume) return;
27 | 
28 |     const mostRecentMessage = initialMessages.at(-1);
29 | 
30 |     if (mostRecentMessage?.role === "user") {
31 |       experimental_resume();
32 |     }
33 | 
34 |     // we intentionally run this once
35 |     // eslint-disable-next-line react-hooks/exhaustive-deps
36 |   }, []);
37 | 
38 |   useEffect(() => {
39 |     if (!data) return;
40 |     if (data.length === 0) return;
41 | 
42 |     const dataPart = data[0] as DataPart;
43 | 
44 |     if (dataPart.type === "append-message") {
45 |       const message = JSON.parse(dataPart.message) as UIMessage;
46 |       setMessages([...initialMessages, message]);
47 |     }
48 |   }, [data, initialMessages, setMessages]);
49 | }
50 | 


--------------------------------------------------------------------------------
/src/app/_hooks/use-chat-visibility.ts:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { api } from "@/trpc/react";
 4 | 
 5 | export const useUpdateChatVisibility = () => {
 6 |   const utils = api.useUtils();
 7 | 
 8 |   return api.chats.updateChatVisibility.useMutation({
 9 |     onSuccess: async () => {
10 |       await utils.chats.getChats.invalidate();
11 |     },
12 |   });
13 | };
14 | 


--------------------------------------------------------------------------------
/src/app/_hooks/use-delete-chat.ts:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { api } from "@/trpc/react";
 4 | 
 5 | export const useDeleteChat = () => {
 6 |   const utils = api.useUtils();
 7 | 
 8 |   return api.chats.deleteChat.useMutation({
 9 |     onSuccess: async () => {
10 |       await utils.chats.getChats.invalidate();
11 |     },
12 |   });
13 | };
14 | 


--------------------------------------------------------------------------------
/src/app/_hooks/use-delete-messages.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { api } from "@/trpc/react";
 4 | 
 5 | export const useDeleteMessagesAfterTimestamp = () => {
 6 |   const utils = api.useUtils();
 7 | 
 8 |   return api.messages.deleteMessagesAfterTimestamp.useMutation({
 9 |     onSuccess: async (_, { chatId }) => {
10 |       await utils.messages.getMessagesForChat.invalidate({ chatId });
11 |     },
12 |   });
13 | };
14 | 


--------------------------------------------------------------------------------
/src/app/_hooks/use-messages.tsx:
--------------------------------------------------------------------------------
 1 | import { useState, useEffect } from "react";
 2 | 
 3 | import type { UseChatHelpers } from "@ai-sdk/react";
 4 | 
 5 | export function useMessages({
 6 |   chatId,
 7 |   status,
 8 |   scrollToBottom,
 9 | }: {
10 |   chatId: string;
11 |   status: UseChatHelpers["status"];
12 |   scrollToBottom: (behavior: ScrollBehavior) => void;
13 | }) {
14 |   const [hasSentMessage, setHasSentMessage] = useState(false);
15 | 
16 |   useEffect(() => {
17 |     if (chatId) {
18 |       scrollToBottom("instant");
19 |       setHasSentMessage(false);
20 |     }
21 |   }, [chatId, scrollToBottom]);
22 | 
23 |   useEffect(() => {
24 |     if (status === "submitted") {
25 |       setHasSentMessage(true);
26 |     }
27 |   }, [status]);
28 | 
29 |   return {
30 |     hasSentMessage,
31 |   };
32 | }
33 | 


--------------------------------------------------------------------------------
/src/app/_hooks/use-scroll-to-bottom.tsx:
--------------------------------------------------------------------------------
 1 | import { useRef, useEffect, useCallback } from "react";
 2 | import { useQuery, useQueryClient } from "@tanstack/react-query";
 3 | 
 4 | type ScrollFlag = ScrollBehavior | false;
 5 | 
 6 | export function useScrollToBottom() {
 7 |   const containerRef = useRef<HTMLDivElement>(null);
 8 |   const endRef = useRef<HTMLDivElement>(null);
 9 |   const queryClient = useQueryClient();
10 | 
11 |   const { data: isAtBottom = false } = useQuery({
12 |     queryKey: ["messages", "is-at-bottom"],
13 |     queryFn: () => false,
14 |     initialData: false,
15 |   });
16 | 
17 |   const { data: scrollBehavior = false } = useQuery({
18 |     queryKey: ["messages", "should-scroll"],
19 |     queryFn: () => false as ScrollFlag,
20 |     initialData: false,
21 |   });
22 | 
23 |   useEffect(() => {
24 |     if (scrollBehavior) {
25 |       endRef.current?.scrollIntoView({ behavior: scrollBehavior });
26 |       queryClient.setQueryData(["messages", "should-scroll"], false);
27 |     }
28 |   }, [queryClient, scrollBehavior]);
29 | 
30 |   const scrollToBottom = useCallback(
31 |     (behavior: ScrollBehavior = "smooth") => {
32 |       queryClient.setQueryData(["messages", "should-scroll"], behavior);
33 |     },
34 |     [queryClient],
35 |   );
36 | 
37 |   function onViewportEnter() {
38 |     queryClient.setQueryData(["messages", "is-at-bottom"], true);
39 |   }
40 | 
41 |   function onViewportLeave() {
42 |     queryClient.setQueryData(["messages", "is-at-bottom"], false);
43 |   }
44 | 
45 |   return {
46 |     containerRef,
47 |     endRef,
48 |     isAtBottom,
49 |     scrollToBottom,
50 |     onViewportEnter,
51 |     onViewportLeave,
52 |   };
53 | }
54 | 


--------------------------------------------------------------------------------
/src/app/_hooks/use-star-chat.ts:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { api } from "@/trpc/react";
 4 | 
 5 | export const useStarChat = () => {
 6 |   const utils = api.useUtils();
 7 | 
 8 |   return api.chats.starChat.useMutation({
 9 |     onSuccess: async () => {
10 |       await utils.chats.getChats.invalidate();
11 |     },
12 |   });
13 | };
14 | 


--------------------------------------------------------------------------------
/src/app/account/components/header.tsx:
--------------------------------------------------------------------------------
 1 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
 2 | import type { User } from "@prisma/client";
 3 | 
 4 | interface Props {
 5 |   user: User;
 6 | }
 7 | 
 8 | export const AccountHeader: React.FC<Props> = ({ user }) => {
 9 |   return (
10 |     <div>
11 |       <div className="flex items-center gap-4">
12 |         <Avatar className="size-16 md:size-20">
13 |           <AvatarImage src={user.image ?? undefined} />
14 |           <AvatarFallback>
15 |             {(user.name ?? user.email)?.charAt(0).toUpperCase() ?? "?"}
16 |           </AvatarFallback>
17 |         </Avatar>
18 |         <div className="flex flex-col gap-1">
19 |           {user.name && <h1 className="text-4xl font-bold">{user.name}</h1>}
20 |           {user.email && (
21 |             <p className="text-muted-foreground text-lg">{user.email}</p>
22 |           )}
23 |           {!user.name && !user.email && (
24 |             <p className="text-muted-foreground text-lg">No name or email</p>
25 |           )}
26 |         </div>
27 |       </div>
28 |     </div>
29 |   );
30 | };
31 | 


--------------------------------------------------------------------------------
/src/app/account/components/tabs/attachments/attachment.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/src/app/account/components/tabs/attachments/attachment.tsx


--------------------------------------------------------------------------------
/src/app/account/components/tabs/attachments/index.tsx:
--------------------------------------------------------------------------------
1 | import { DataTableDemo } from "./table";
2 | 
3 | export const Attachments = async () => {
4 |   return <DataTableDemo />;
5 | };
6 | 


--------------------------------------------------------------------------------
/src/app/account/components/tabs/connected-accounts/connect-disconnect.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import React from "react";
 4 | 
 5 | import { Check, Loader2 } from "lucide-react";
 6 | 
 7 | import { useRouter } from "next/navigation";
 8 | import { toast } from "sonner";
 9 | 
10 | import { Button } from "@/components/ui/button";
11 | 
12 | import { api } from "@/trpc/react";
13 | import { signIn } from "next-auth/react";
14 | 
15 | interface ConnectProps {
16 |   provider: string;
17 | }
18 | 
19 | export const ConnectButton: React.FC<ConnectProps> = ({ provider }) => {
20 |   return (
21 |     <Button
22 |       onClick={() => {
23 |         void signIn(provider, {
24 |           callbackUrl: "/account?tab=connected-accounts",
25 |         });
26 |       }}
27 |     >
28 |       Connect
29 |     </Button>
30 |   );
31 | };
32 | 
33 | interface DisconnectProps {
34 |   accountId: string;
35 | }
36 | 
37 | export const DisconnectButton: React.FC<DisconnectProps> = ({ accountId }) => {
38 |   const router = useRouter();
39 |   const utils = api.useUtils();
40 |   const {
41 |     mutate: deleteAccount,
42 |     isPending,
43 |     isSuccess,
44 |   } = api.accounts.deleteAccount.useMutation({
45 |     onSuccess: async () => {
46 |       await utils.accounts.getAccounts.invalidate();
47 |       router.refresh();
48 |       toast.success("Account disconnected");
49 |     },
50 |   });
51 | 
52 |   return (
53 |     <Button
54 |       variant="outline"
55 |       onClick={() => {
56 |         void deleteAccount(accountId);
57 |       }}
58 |       disabled={isPending}
59 |     >
60 |       Disconnect
61 |       {isPending ? (
62 |         <Loader2 className="animate-spin" />
63 |       ) : isSuccess ? (
64 |         <Check />
65 |       ) : null}
66 |     </Button>
67 |   );
68 | };
69 | 


--------------------------------------------------------------------------------
/src/app/account/components/tabs/connected-accounts/index.tsx:
--------------------------------------------------------------------------------
 1 | import { AuthProviderIcon } from "@/app/_components/navbar/account-button/provider-icon";
 2 | import { Badge } from "@/components/ui/badge";
 3 | import { HStack } from "@/components/ui/stack";
 4 | import { providers } from "@/server/auth/providers";
 5 | import { api } from "@/trpc/server";
 6 | import { ConnectButton, DisconnectButton } from "./connect-disconnect";
 7 | 
 8 | export const ConnectedAccounts = async () => {
 9 |   const accounts = await api.accounts.getAccounts({
10 |     limit: 100,
11 |   });
12 | 
13 |   return (
14 |     <div className="flex flex-col gap-2">
15 |       {providers.map((provider) => {
16 |         const account = accounts?.items.find(
17 |           (account) => account.provider === provider.id,
18 |         );
19 | 
20 |         return (
21 |           <HStack
22 |             key={provider.id}
23 |             className="w-full justify-between rounded-md border px-4 py-2"
24 |           >
25 |             <HStack className="gap-4">
26 |               <AuthProviderIcon provider={provider.name} />
27 |               <HStack className="gap-2">
28 |                 <h2 className="font-medium">{provider.name}</h2>
29 |                 {account && <Badge variant="success">Connected</Badge>}
30 |               </HStack>
31 |             </HStack>
32 |             {account ? (
33 |               <DisconnectButton accountId={account.id} />
34 |             ) : (
35 |               <ConnectButton provider={provider.id} />
36 |             )}
37 |           </HStack>
38 |         );
39 |       })}
40 |     </div>
41 |   );
42 | };
43 | 


--------------------------------------------------------------------------------
/src/app/account/components/tabs/images/index.tsx:
--------------------------------------------------------------------------------
1 | import { ImagesTable } from "./table";
2 | 
3 | export const Images = () => {
4 |   return <ImagesTable />;
5 | };
6 | 


--------------------------------------------------------------------------------
/src/app/account/components/tabs/index.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | 
 3 | import { File, User, Brain, Image as ImageIcon } from "lucide-react";
 4 | 
 5 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
 6 | 
 7 | import { ConnectedAccounts } from "./connected-accounts";
 8 | import { Attachments } from "./attachments";
 9 | import { Memories } from "./memories";
10 | import { Images } from "./images";
11 | import { HStack } from "@/components/ui/stack";
12 | 
13 | const tabs = [
14 |   {
15 |     label: "Connected Accounts",
16 |     value: "connected-accounts",
17 |     component: <ConnectedAccounts />,
18 |     icon: <User />,
19 |   },
20 |   {
21 |     label: "Attachments",
22 |     value: "attachments",
23 |     component: <Attachments />,
24 |     icon: <File />,
25 |   },
26 |   {
27 |     label: "Memories",
28 |     value: "memories",
29 |     component: <Memories />,
30 |     icon: <Brain />,
31 |   },
32 |   {
33 |     label: "Images Generated",
34 |     value: "images",
35 |     component: <Images />,
36 |     icon: <ImageIcon />,
37 |   },
38 | ];
39 | 
40 | interface Props {
41 |   defaultTab?: string;
42 | }
43 | 
44 | export const AccountTabs: React.FC<Props> = ({ defaultTab }) => {
45 |   return (
46 |     <Tabs defaultValue={defaultTab ?? "connected-accounts"}>
47 |       <TabsList>
48 |         {tabs.map((tab) => (
49 |           <TabsTrigger key={tab.value} value={tab.value}>
50 |             <HStack className="gap-2">
51 |               {tab.icon}
52 |               {tab.label}
53 |             </HStack>
54 |           </TabsTrigger>
55 |         ))}
56 |       </TabsList>
57 |       {tabs.map((tab) => (
58 |         <TabsContent key={tab.value} value={tab.value} className="mt-2">
59 |           {tab.component}
60 |         </TabsContent>
61 |       ))}
62 |     </Tabs>
63 |   );
64 | };
65 | 


--------------------------------------------------------------------------------
/src/app/account/components/tabs/memories/index.tsx:
--------------------------------------------------------------------------------
1 | import { MemoriesTable } from "./table";
2 | 
3 | export const Memories = () => {
4 |   return <MemoriesTable />;
5 | };
6 | 


--------------------------------------------------------------------------------
/src/app/account/page.tsx:
--------------------------------------------------------------------------------
 1 | import { auth } from "@/server/auth";
 2 | 
 3 | import { redirect } from "next/navigation";
 4 | 
 5 | import { api } from "@/trpc/server";
 6 | 
 7 | import { AccountHeader } from "./components/header";
 8 | import { AccountTabs } from "./components/tabs";
 9 | 
10 | const AccountPage = async ({
11 |   searchParams,
12 | }: {
13 |   searchParams: Promise<{ tab?: string }>;
14 | }) => {
15 |   const { tab } = await searchParams;
16 | 
17 |   const session = await auth();
18 | 
19 |   if (!session) {
20 |     redirect("/login?redirect=/account");
21 |   }
22 | 
23 |   const user = await api.users.getCurrentUser();
24 | 
25 |   if (!user) {
26 |     redirect("/login?redirect=/account");
27 |   }
28 | 
29 |   return (
30 |     <div className="mx-auto max-w-4xl space-y-4 px-2 py-4 md:space-y-8">
31 |       <AccountHeader user={user} />
32 |       <AccountTabs defaultTab={tab} />
33 |     </div>
34 |   );
35 | };
36 | 
37 | export default AccountPage;
38 | 


--------------------------------------------------------------------------------
/src/app/admin/page.tsx:
--------------------------------------------------------------------------------
 1 | import { redirect, notFound } from "next/navigation";
 2 | import { auth } from "@/server/auth";
 3 | import { createCaller } from "@/server/api/root";
 4 | import { createTRPCContext } from "@/server/api/trpc";
 5 | import { headers } from "next/headers";
 6 | import { AdminPanel } from "./_components/admin-panel";
 7 | 
 8 | export default async function AdminPage() {
 9 |   // Check authentication
10 |   const session = await auth();
11 | 
12 |   if (!session) {
13 |     redirect("/login?redirect=/admin");
14 |   }
15 | 
16 |   // Create TRPC context and caller for server-side calls
17 |   const ctx = await createTRPCContext({ headers: await headers() });
18 |   const caller = createCaller(ctx);
19 | 
20 |   // Check if user has admin access
21 |   try {
22 |     const isAdmin = await caller.features.isAdmin();
23 | 
24 |     if (!isAdmin) {
25 |       notFound();
26 |     }
27 |   } catch (error) {
28 |     console.error(error);
29 |     // If there's an error checking admin status, deny access
30 |     notFound();
31 |   }
32 | 
33 |   // If user is admin, render the admin panel
34 |   return <AdminPanel />;
35 | }
36 | 


--------------------------------------------------------------------------------
/src/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import { handlers } from "@/server/auth";
2 | 
3 | export const { GET, POST } = handlers;
4 | 


--------------------------------------------------------------------------------
/src/app/api/mcp/[server]/[transport]/route.ts:
--------------------------------------------------------------------------------
 1 | import { serverToolkits } from "@/toolkits/toolkits/server";
 2 | import type { Toolkits } from "@/toolkits/toolkits/shared";
 3 | import { createMcpHandler } from "@vercel/mcp-adapter";
 4 | 
 5 | // Create a wrapper function that can access Next.js route parameters
 6 | async function createHandlerWithParams(
 7 |   request: Request,
 8 |   { params }: { params: Promise<{ server: Toolkits }> },
 9 | ) {
10 |   const { server } = await params;
11 | 
12 |   const serverToolkit = serverToolkits[server];
13 | 
14 |   const handler = createMcpHandler(
15 |     async (mcpServer) => {
16 |       const tools = await serverToolkit.tools({
17 |         model: "openai:gpt-image-1",
18 |       });
19 | 
20 |       Object.entries(tools).forEach(([toolName, tool]) => {
21 |         const { description, inputSchema, callback, message } = tool;
22 |         mcpServer.tool(
23 |           toolName,
24 |           description,
25 |           inputSchema.shape,
26 |           async (args) => {
27 |             const result = await callback(args);
28 |             return {
29 |               content: [
30 |                 {
31 |                   type: "text",
32 |                   text: message
33 |                     ? typeof message === "function"
34 |                       ? message(result)
35 |                       : message
36 |                     : JSON.stringify(result, null, 2),
37 |                 },
38 |               ],
39 |               structuredContent: result,
40 |             };
41 |           },
42 |         );
43 |       });
44 |     },
45 |     {
46 |       // Optional server options
47 |     },
48 |     {
49 |       redisUrl: process.env.REDIS_URL,
50 |       basePath: `/mcp/${server}`,
51 |       sseEndpoint: "/sse",
52 |       maxDuration: 120,
53 |       verboseLogs: true,
54 |     },
55 |   );
56 | 
57 |   return await handler(request);
58 | }
59 | 
60 | export { createHandlerWithParams as GET, createHandlerWithParams as POST };
61 | 


--------------------------------------------------------------------------------
/src/app/api/trpc/[trpc]/route.ts:
--------------------------------------------------------------------------------
 1 | import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
 2 | import { type NextRequest } from "next/server";
 3 | 
 4 | import { env } from "@/env";
 5 | import { appRouter } from "@/server/api/root";
 6 | import { createTRPCContext } from "@/server/api/trpc";
 7 | 
 8 | /**
 9 |  * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
10 |  * handling a HTTP request (e.g. when you make requests from Client Components).
11 |  */
12 | const createContext = async (req: NextRequest) => {
13 |   return createTRPCContext({
14 |     headers: req.headers,
15 |   });
16 | };
17 | 
18 | const handler = (req: NextRequest) =>
19 |   fetchRequestHandler({
20 |     endpoint: "/api/trpc",
21 |     req,
22 |     router: appRouter,
23 |     createContext: () => createContext(req),
24 |     onError:
25 |       env.NODE_ENV === "development"
26 |         ? ({ path, error }) => {
27 |             console.error(
28 |               `❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
29 |             );
30 |           }
31 |         : undefined,
32 |   });
33 | 
34 | export { handler as GET, handler as POST };
35 | 


--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/src/app/favicon.ico


--------------------------------------------------------------------------------
/src/app/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/src/app/icon.png


--------------------------------------------------------------------------------
/src/app/login/login-form.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { cn } from "@/lib/utils";
 4 | import { Card } from "@/components/ui/card";
 5 | 
 6 | import { VStack } from "@/components/ui/stack";
 7 | import { Logo } from "@/components/ui/logo";
 8 | import { AuthButtons } from "../_components/auth/auth-buttons";
 9 | import { useSearchParams } from "next/navigation";
10 | 
11 | interface LoginFormProps {
12 |   providers: {
13 |     name: string;
14 |     id: string;
15 |   }[];
16 | }
17 | 
18 | export function LoginForm({
19 |   providers,
20 |   className,
21 |   ...props
22 | }: LoginFormProps & React.ComponentProps<"div">) {
23 |   const searchParams = useSearchParams();
24 |   const redirect = searchParams.get("redirect");
25 | 
26 |   return (
27 |     <div className={cn("flex flex-col gap-6", className)} {...props}>
28 |       <VStack className="w-full max-w-md gap-4">
29 |         <VStack className="gap-4">
30 |           <Logo className="size-16" />
31 |           <VStack className="gap-1">
32 |             <h1 className="text-primary text-2xl font-bold">
33 |               Welcome to Toolkit
34 |             </h1>
35 |           </VStack>
36 |         </VStack>
37 |         <Card className="w-full gap-4 p-4">
38 |           <p className="text-muted-foreground text-center text-sm">
39 |             Sign in with your preferred account to continue
40 |           </p>
41 |           <AuthButtons providers={providers} redirect={redirect ?? undefined} />
42 |         </Card>
43 |       </VStack>
44 |     </div>
45 |   );
46 | }
47 | 


--------------------------------------------------------------------------------
/src/app/login/page.tsx:
--------------------------------------------------------------------------------
 1 | import { providers } from "@/server/auth/providers";
 2 | 
 3 | import { LoginForm } from "./login-form";
 4 | import { auth } from "@/server/auth";
 5 | import { redirect } from "next/navigation";
 6 | 
 7 | export default async function LoginPage() {
 8 |   const session = await auth();
 9 | 
10 |   if (session) {
11 |     redirect("/");
12 |   }
13 | 
14 |   const mappedProviders = providers.map((provider) => ({
15 |     name: provider.name,
16 |     id: provider.id,
17 |   }));
18 | 
19 |   return (
20 |     <div className="flex flex-1 flex-col items-center justify-center p-4">
21 |       <div className="w-full max-w-md">
22 |         <LoginForm providers={mappedProviders} />
23 |       </div>
24 |     </div>
25 |   );
26 | }
27 | 


--------------------------------------------------------------------------------
/src/app/opengraph-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/src/app/opengraph-image.png


--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
 1 | import { Chat } from "@/app/_components/chat";
 2 | import { auth } from "@/server/auth";
 3 | import { generateUUID } from "@/lib/utils";
 4 | import LandingPage from "./_components/landing-page";
 5 | 
 6 | export default async function Page() {
 7 |   const session = await auth();
 8 | 
 9 |   if (!session) {
10 |     return <LandingPage />;
11 |   }
12 | 
13 |   const id = generateUUID();
14 | 
15 |   return (
16 |     <Chat
17 |       key={id}
18 |       id={id}
19 |       initialVisibilityType="private"
20 |       isReadonly={false}
21 |       isNew={true}
22 |     />
23 |   );
24 | }
25 | 


--------------------------------------------------------------------------------
/src/app/twitter-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/src/app/twitter-image.png


--------------------------------------------------------------------------------
/src/app/workbench/[id]/[chatId]/page.tsx:
--------------------------------------------------------------------------------
 1 | import { notFound } from "next/navigation";
 2 | 
 3 | import { auth } from "@/server/auth";
 4 | import { Chat } from "@/app/_components/chat";
 5 | import { api } from "@/trpc/server";
 6 | 
 7 | export default async function Page(props: {
 8 |   params: Promise<{ id: string; chatId: string }>;
 9 | }) {
10 |   const params = await props.params;
11 |   const { id, chatId } = params;
12 | 
13 |   const session = await auth();
14 | 
15 |   const [chat, workbench] = await Promise.all([
16 |     api.chats.getChat(chatId),
17 |     api.workbenches.getWorkbench(id),
18 |   ]);
19 | 
20 |   if (!chat || !workbench) {
21 |     notFound();
22 |   }
23 | 
24 |   return (
25 |     <Chat
26 |       id={chat.id}
27 |       initialVisibilityType={chat.visibility}
28 |       isReadonly={session?.user?.id !== chat.userId}
29 |       isNew={false}
30 |       workbench={workbench}
31 |     />
32 |   );
33 | }
34 | 


--------------------------------------------------------------------------------
/src/app/workbench/[id]/_components/header.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { ToolkitIcons } from "@/components/toolkit/toolkit-icons";
 4 | import { Button } from "@/components/ui/button";
 5 | import { HStack } from "@/components/ui/stack";
 6 | import type { Toolkits } from "@/toolkits/toolkits/shared";
 7 | import type { Workbench } from "@prisma/client";
 8 | import { Settings, Anvil } from "lucide-react";
 9 | import Link from "next/link";
10 | 
11 | interface WorkbenchHeaderProps {
12 |   workbench: Workbench;
13 | }
14 | 
15 | export function WorkbenchHeader({ workbench }: WorkbenchHeaderProps) {
16 |   return (
17 |     <HStack className="justify-between border-b p-4">
18 |       <HStack>
19 |         <Anvil className="size-5" />
20 |         <h1 className="text-lg font-semibold">{workbench.name}</h1>
21 |         <ToolkitIcons toolkits={workbench.toolkitIds as Toolkits[]} />
22 |       </HStack>
23 |       <Link href={`/workbench/${workbench.id}/edit`}>
24 |         <Button variant="ghost" size="icon" className="size-fit p-1">
25 |           <Settings className="size-4" />
26 |         </Button>
27 |       </Link>
28 |     </HStack>
29 |   );
30 | }
31 | 


--------------------------------------------------------------------------------
/src/app/workbench/[id]/edit/page.tsx:
--------------------------------------------------------------------------------
 1 | import { notFound } from "next/navigation";
 2 | import { api } from "@/trpc/server";
 3 | import { EditWorkbenchForm } from "./_components/edit-workbench-form";
 4 | 
 5 | interface EditWorkbenchPageProps {
 6 |   params: Promise<{ id: string }>;
 7 | }
 8 | 
 9 | export default async function EditWorkbenchPage({
10 |   params,
11 | }: EditWorkbenchPageProps) {
12 |   const { id } = await params;
13 | 
14 |   try {
15 |     const workbench = await api.workbenches.getWorkbench(id);
16 | 
17 |     if (!workbench) {
18 |       notFound();
19 |     }
20 | 
21 |     return <EditWorkbenchForm workbench={workbench} />;
22 |   } catch (error) {
23 |     console.error(error);
24 |     notFound();
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/src/app/workbench/[id]/layout.tsx:
--------------------------------------------------------------------------------
 1 | import { api } from "@/trpc/server";
 2 | import { WorkbenchHeader } from "./_components/header";
 3 | import { notFound, redirect } from "next/navigation";
 4 | import { auth } from "@/server/auth";
 5 | 
 6 | export default async function WorkbenchLayout({
 7 |   children,
 8 |   params,
 9 | }: {
10 |   children: React.ReactNode;
11 |   params: Promise<{ id: string }>;
12 | }) {
13 |   const { id } = await params;
14 | 
15 |   const session = await auth();
16 | 
17 |   if (!session) {
18 |     redirect(`/login?redirect=/workbench/${id}`);
19 |   }
20 | 
21 |   const workbench = await api.workbenches.getWorkbench(id);
22 | 
23 |   if (!workbench) {
24 |     notFound();
25 |   }
26 | 
27 |   return (
28 |     <div className="flex h-0 flex-1 flex-col overflow-hidden">
29 |       <WorkbenchHeader workbench={workbench} />
30 |       <div className="flex h-0 flex-1 flex-col overflow-y-auto">{children}</div>
31 |     </div>
32 |   );
33 | }
34 | 


--------------------------------------------------------------------------------
/src/app/workbench/[id]/page.tsx:
--------------------------------------------------------------------------------
 1 | import { notFound } from "next/navigation";
 2 | 
 3 | import { Chat } from "@/app/_components/chat";
 4 | import { api } from "@/trpc/server";
 5 | import { generateUUID } from "@/lib/utils";
 6 | 
 7 | export default async function WorkbenchPage(props: {
 8 |   params: Promise<{ id: string }>;
 9 | }) {
10 |   const params = await props.params;
11 |   const { id } = params;
12 | 
13 |   try {
14 |     const workbench = await api.workbenches.getWorkbench(id);
15 | 
16 |     if (!workbench) {
17 |       notFound();
18 |     }
19 | 
20 |     const chatId = generateUUID();
21 | 
22 |     return (
23 |       <Chat
24 |         id={chatId}
25 |         isReadonly={false}
26 |         isNew={true}
27 |         initialVisibilityType="private"
28 |         workbench={workbench}
29 |       />
30 |     );
31 |   } catch (error) {
32 |     console.error(error);
33 |     notFound();
34 |   }
35 | }
36 | 


--------------------------------------------------------------------------------
/src/app/workbench/new/_components/index.ts:
--------------------------------------------------------------------------------
1 | export { NewWorkbenchForm } from "./new-workbench-form";
2 | 


--------------------------------------------------------------------------------
/src/app/workbench/new/page.tsx:
--------------------------------------------------------------------------------
 1 | import { auth } from "@/server/auth";
 2 | import { redirect } from "next/navigation";
 3 | import { NewWorkbenchForm } from "./_components";
 4 | 
 5 | export default async function NewWorkbenchPage() {
 6 |   const session = await auth();
 7 | 
 8 |   if (!session) {
 9 |     redirect("/");
10 |   }
11 | 
12 |   return <NewWorkbenchForm />;
13 | }
14 | 


--------------------------------------------------------------------------------
/src/components/magicui/animated-shiny-text.tsx:
--------------------------------------------------------------------------------
 1 | import {
 2 |   type ComponentPropsWithoutRef,
 3 |   type CSSProperties,
 4 |   type FC,
 5 | } from "react";
 6 | 
 7 | import { cn } from "@/lib/utils";
 8 | 
 9 | export interface AnimatedShinyTextProps
10 |   extends ComponentPropsWithoutRef<"span"> {
11 |   shimmerWidth?: number;
12 | }
13 | 
14 | export const AnimatedShinyText: FC<AnimatedShinyTextProps> = ({
15 |   children,
16 |   className,
17 |   shimmerWidth = 100,
18 |   ...props
19 | }) => {
20 |   return (
21 |     <span
22 |       style={
23 |         {
24 |           "--shiny-width": `${shimmerWidth}px`,
25 |         } as CSSProperties
26 |       }
27 |       className={cn(
28 |         "text-neutral-600/70 dark:text-neutral-400/70",
29 | 
30 |         // Shine effect
31 |         "animate-shiny-text [background-size:var(--shiny-width)_100%] bg-clip-text [background-position:0_0] bg-no-repeat [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]",
32 | 
33 |         // Shine gradient
34 |         "bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80",
35 | 
36 |         className,
37 |       )}
38 |       {...props}
39 |     >
40 |       {children}
41 |     </span>
42 |   );
43 | };
44 | 


--------------------------------------------------------------------------------
/src/components/toolkit/toolkit-configure.tsx:
--------------------------------------------------------------------------------
 1 | import { Button } from "@/components/ui/button";
 2 | import { Plus } from "lucide-react";
 3 | import { useState } from "react";
 4 | import type z from "zod";
 5 | import type {
 6 |   Toolkits,
 7 |   ServerToolkitParameters,
 8 | } from "@/toolkits/toolkits/shared";
 9 | import type { ClientToolkit } from "@/toolkits/types";
10 | import type { SelectedToolkit } from "./types";
11 | 
12 | interface ClientToolkitConfigureProps {
13 |   toolkit: ClientToolkit;
14 |   id: Toolkits;
15 |   schema: z.ZodObject<z.ZodRawShape>;
16 |   onAdd: (toolkit: SelectedToolkit) => void;
17 | }
18 | 
19 | export const ClientToolkitConfigure: React.FC<ClientToolkitConfigureProps> = ({
20 |   toolkit,
21 |   id,
22 |   schema,
23 |   onAdd,
24 | }) => {
25 |   const [parameters, setParameters] = useState<
26 |     ServerToolkitParameters[typeof id]
27 |   >({} as ServerToolkitParameters[typeof id]);
28 | 
29 |   const handleSubmit = () => {
30 |     onAdd({ id, toolkit, parameters });
31 |   };
32 | 
33 |   return (
34 |     <div className="space-y-4">
35 |       <div>
36 |         <h4 className="font-medium">{toolkit.name}</h4>
37 |       </div>
38 | 
39 |       <div className="space-y-4">
40 |         {toolkit.form && (
41 |           <toolkit.form parameters={parameters} setParameters={setParameters} />
42 |         )}
43 |       </div>
44 | 
45 |       <Button
46 |         onClick={handleSubmit}
47 |         disabled={!schema.safeParse(parameters).success}
48 |         className="w-full"
49 |       >
50 |         <Plus className="mr-2 size-4" />
51 |         Add {toolkit.name}
52 |       </Button>
53 |     </div>
54 |   );
55 | };
56 | 


--------------------------------------------------------------------------------
/src/components/toolkit/toolkit-icons.tsx:
--------------------------------------------------------------------------------
 1 | import { cn } from "@/lib/utils";
 2 | import { getClientToolkit } from "@/toolkits/toolkits/client";
 3 | import type { Toolkits } from "@/toolkits/toolkits/shared";
 4 | 
 5 | interface Props {
 6 |   toolkits: Toolkits[];
 7 |   orientation?: "horizontal" | "vertical";
 8 |   containerClassName?: string;
 9 |   iconContainerClassName?: string;
10 |   iconClassName?: string;
11 | }
12 | 
13 | export const ToolkitIcons = ({
14 |   toolkits,
15 |   orientation = "horizontal",
16 |   containerClassName,
17 |   iconContainerClassName,
18 |   iconClassName,
19 | }: Props) => {
20 |   if (toolkits.length === 0) return null;
21 | 
22 |   return (
23 |     <div
24 |       className={cn(
25 |         "flex items-center",
26 |         {
27 |           "flex-col pt-2": orientation === "vertical",
28 |           "flex-row pl-2": orientation === "horizontal",
29 |         },
30 |         containerClassName,
31 |       )}
32 |     >
33 |       {toolkits.map((toolkit) => {
34 |         const Icon = getClientToolkit(toolkit).icon;
35 |         return (
36 |           <div
37 |             className={cn(
38 |               "border-primary bg-muted rounded-full border p-1",
39 |               iconContainerClassName,
40 |               {
41 |                 "-mt-2": orientation === "vertical",
42 |                 "-ml-2": orientation === "horizontal",
43 |               },
44 |             )}
45 |             key={toolkit}
46 |           >
47 |             <Icon
48 |               className={cn("text-primary size-3 md:size-4", iconClassName)}
49 |             />
50 |           </div>
51 |         );
52 |       })}
53 |     </div>
54 |   );
55 | };
56 | 


--------------------------------------------------------------------------------
/src/components/toolkit/types.ts:
--------------------------------------------------------------------------------
1 | import type { Toolkits } from "@/toolkits/toolkits/shared";
2 | import type { ClientToolkit } from "@/toolkits/types";
3 | 
4 | export type SelectedToolkit = {
5 |   id: Toolkits;
6 |   toolkit: ClientToolkit;
7 |   parameters: Record<string, unknown>;
8 | };
9 | 


--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar";
 5 | 
 6 | import { cn } from "@/lib/utils";
 7 | 
 8 | function Avatar({
 9 |   className,
10 |   ...props
11 | }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
12 |   return (
13 |     <AvatarPrimitive.Root
14 |       data-slot="avatar"
15 |       className={cn(
16 |         "relative flex size-8 shrink-0 overflow-hidden rounded-full",
17 |         className,
18 |       )}
19 |       {...props}
20 |     />
21 |   );
22 | }
23 | 
24 | function AvatarImage({
25 |   className,
26 |   ...props
27 | }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
28 |   return (
29 |     <AvatarPrimitive.Image
30 |       data-slot="avatar-image"
31 |       className={cn("aspect-square size-full", className)}
32 |       {...props}
33 |     />
34 |   );
35 | }
36 | 
37 | function AvatarFallback({
38 |   className,
39 |   ...props
40 | }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
41 |   return (
42 |     <AvatarPrimitive.Fallback
43 |       data-slot="avatar-fallback"
44 |       className={cn(
45 |         "bg-muted flex size-full items-center justify-center rounded-full",
46 |         className,
47 |       )}
48 |       {...props}
49 |     />
50 |   );
51 | }
52 | 
53 | export { Avatar, AvatarImage, AvatarFallback };
54 | 


--------------------------------------------------------------------------------
/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
 1 | import * as React from "react";
 2 | import { cva, type VariantProps } from "class-variance-authority";
 3 | 
 4 | import { cn } from "@/lib/utils";
 5 | 
 6 | const badgeVariants = cva(
 7 |   "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
 8 |   {
 9 |     variants: {
10 |       variant: {
11 |         default:
12 |           "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 |         secondary:
14 |           "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 |         destructive:
16 |           "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 |         outline: "text-foreground",
18 |         capability:
19 |           "border-transparent bg-muted text-muted-foreground hover:bg-muted/80",
20 |         success: "border-transparent bg-green-500/20 text-green-500",
21 |         primary: "border-transparent bg-primary/30 text-primary",
22 |       },
23 |     },
24 |     defaultVariants: {
25 |       variant: "default",
26 |     },
27 |   },
28 | );
29 | 
30 | export interface BadgeProps
31 |   extends React.HTMLAttributes<HTMLDivElement>,
32 |     VariantProps<typeof badgeVariants> {}
33 | 
34 | function Badge({ className, variant, ...props }: BadgeProps) {
35 |   return (
36 |     <div className={cn(badgeVariants({ variant }), className)} {...props} />
37 |   );
38 | }
39 | 
40 | export { Badge, badgeVariants };
41 | 


--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
 5 | import { CheckIcon } from "lucide-react";
 6 | 
 7 | import { cn } from "@/lib/utils";
 8 | 
 9 | function Checkbox({
10 |   className,
11 |   ...props
12 | }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
13 |   return (
14 |     <CheckboxPrimitive.Root
15 |       data-slot="checkbox"
16 |       className={cn(
17 |         "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
18 |         className,
19 |       )}
20 |       {...props}
21 |     >
22 |       <CheckboxPrimitive.Indicator
23 |         data-slot="checkbox-indicator"
24 |         className="flex items-center justify-center text-current transition-none"
25 |       >
26 |         <CheckIcon className="size-3.5" />
27 |       </CheckboxPrimitive.Indicator>
28 |     </CheckboxPrimitive.Root>
29 |   );
30 | }
31 | 
32 | export { Checkbox };
33 | 


--------------------------------------------------------------------------------
/src/components/ui/info-tooltip.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | 
 3 | import {
 4 |   Tooltip,
 5 |   TooltipContent,
 6 |   TooltipProvider,
 7 |   TooltipTrigger,
 8 | } from "@/components/ui/tooltip";
 9 | import { cn } from "@/lib/utils";
10 | import { Info } from "lucide-react";
11 | 
12 | interface Props {
13 |   content: string;
14 |   className?: string;
15 |   contentClassName?: string;
16 | }
17 | 
18 | export const InfoTooltip: React.FC<Props> = ({
19 |   content,
20 |   className,
21 |   contentClassName,
22 | }) => (
23 |   <TooltipProvider delayDuration={100}>
24 |     <Tooltip>
25 |       <TooltipTrigger>
26 |         <div
27 |           className={cn(
28 |             "text-muted-foreground hover:text-foreground size-5 transition-colors",
29 |             className,
30 |           )}
31 |         >
32 |           <Info className="size-full" />
33 |         </div>
34 |       </TooltipTrigger>
35 |       <TooltipContent
36 |         side="bottom"
37 |         className={cn("max-w-[200px] text-center", contentClassName)}
38 |       >
39 |         {content}
40 |       </TooltipContent>
41 |     </Tooltip>
42 |   </TooltipProvider>
43 | );
44 | 


--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
 1 | import * as React from "react";
 2 | 
 3 | import { cn } from "@/lib/utils";
 4 | 
 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) {
 6 |   return (
 7 |     <input
 8 |       type={type}
 9 |       data-slot="input"
10 |       className={cn(
11 |         "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12 |         "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
13 |         "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14 |         className,
15 |       )}
16 |       {...props}
17 |     />
18 |   );
19 | }
20 | 
21 | export { Input };
22 | 


--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import * as LabelPrimitive from "@radix-ui/react-label";
 5 | import { cva, type VariantProps } from "class-variance-authority";
 6 | 
 7 | import { cn } from "@/lib/utils";
 8 | 
 9 | const labelVariants = cva(
10 |   "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
11 | );
12 | 
13 | const Label = React.forwardRef<
14 |   React.ElementRef<typeof LabelPrimitive.Root>,
15 |   React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16 |     VariantProps<typeof labelVariants>
17 | >(({ className, ...props }, ref) => (
18 |   <LabelPrimitive.Root
19 |     ref={ref}
20 |     className={cn(labelVariants(), className)}
21 |     {...props}
22 |   />
23 | ));
24 | Label.displayName = LabelPrimitive.Root.displayName;
25 | 
26 | export { Label };
27 | 


--------------------------------------------------------------------------------
/src/components/ui/logo.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | 
 3 | import Image from "next/image";
 4 | import { cn } from "@/lib/utils";
 5 | 
 6 | interface Props {
 7 |   className?: string;
 8 | }
 9 | 
10 | export const Logo: React.FC<Props> = ({ className }) => {
11 |   return (
12 |     <>
13 |       <Image
14 |         src="/logo/light.svg"
15 |         alt="Toolkit.dev"
16 |         width={100}
17 |         height={100}
18 |         className={cn(className, "dark:hidden")}
19 |       />
20 |       <Image
21 |         src="/logo/dark.svg"
22 |         alt="Toolkit.dev"
23 |         width={100}
24 |         height={100}
25 |         className={cn(className, "hidden dark:block")}
26 |       />
27 |     </>
28 |   );
29 | };
30 | 


--------------------------------------------------------------------------------
/src/components/ui/model-icon.tsx:
--------------------------------------------------------------------------------
 1 | import Image from "next/image";
 2 | 
 3 | import { cn } from "@/lib/utils";
 4 | 
 5 | interface Props {
 6 |   provider: string;
 7 |   className?: string;
 8 | }
 9 | 
10 | export const ModelProviderIcon: React.FC<Props> = ({ provider, className }) => {
11 |   return (
12 |     <Image
13 |       src={`/icons/${provider}.png`}
14 |       alt={provider}
15 |       width={16}
16 |       height={16}
17 |       className={cn("rounded-full", className ?? "")}
18 |     />
19 |   );
20 | };
21 | 


--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import * as PopoverPrimitive from "@radix-ui/react-popover";
 5 | 
 6 | import { cn } from "@/lib/utils";
 7 | 
 8 | function Popover({
 9 |   ...props
10 | }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
11 |   return <PopoverPrimitive.Root data-slot="popover" {...props} />;
12 | }
13 | 
14 | function PopoverTrigger({
15 |   ...props
16 | }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
17 |   return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
18 | }
19 | 
20 | function PopoverContent({
21 |   className,
22 |   align = "center",
23 |   sideOffset = 4,
24 |   ...props
25 | }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
26 |   return (
27 |     <PopoverPrimitive.Portal>
28 |       <PopoverPrimitive.Content
29 |         data-slot="popover-content"
30 |         align={align}
31 |         sideOffset={sideOffset}
32 |         className={cn(
33 |           "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
34 |           className,
35 |         )}
36 |         {...props}
37 |       />
38 |     </PopoverPrimitive.Portal>
39 |   );
40 | }
41 | 
42 | function PopoverAnchor({
43 |   ...props
44 | }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
45 |   return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
46 | }
47 | 
48 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
49 | 


--------------------------------------------------------------------------------
/src/components/ui/search-type-icon.tsx:
--------------------------------------------------------------------------------
 1 | import { Brain, Sparkles } from "lucide-react";
 2 | 
 3 | import { cn } from "@/lib/utils";
 4 | 
 5 | import { SearchOptions } from "@/ai/types";
 6 | import { SiOpenai } from "@icons-pack/react-simple-icons";
 7 | 
 8 | interface Props {
 9 |   searchOption: SearchOptions;
10 |   className?: string;
11 | }
12 | 
13 | export const SearchTypeIcon: React.FC<Props> = ({
14 |   searchOption,
15 |   className,
16 | }) => {
17 |   const Icon = {
18 |     [SearchOptions.Native]: Brain,
19 |     [SearchOptions.OpenAiResponses]: SiOpenai,
20 |     [SearchOptions.Exa]: Sparkles,
21 |   }[searchOption];
22 | 
23 |   return Icon ? <Icon className={cn("size-4", className)} /> : null;
24 | };
25 | 


--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator";
 5 | 
 6 | import { cn } from "@/lib/utils";
 7 | 
 8 | function Separator({
 9 |   className,
10 |   orientation = "horizontal",
11 |   decorative = true,
12 |   ...props
13 | }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
14 |   return (
15 |     <SeparatorPrimitive.Root
16 |       data-slot="separator"
17 |       decorative={decorative}
18 |       orientation={orientation}
19 |       className={cn(
20 |         "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
21 |         className,
22 |       )}
23 |       {...props}
24 |     />
25 |   );
26 | }
27 | 
28 | export { Separator };
29 | 


--------------------------------------------------------------------------------
/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
 1 | import { cn } from "@/lib/utils";
 2 | 
 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
 4 |   return (
 5 |     <div
 6 |       data-slot="skeleton"
 7 |       className={cn("bg-accent animate-pulse rounded-md", className)}
 8 |       {...props}
 9 |     />
10 |   );
11 | }
12 | 
13 | export { Skeleton };
14 | 


--------------------------------------------------------------------------------
/src/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import { useTheme } from "next-themes";
 4 | import { Toaster as Sonner, type ToasterProps } from "sonner";
 5 | 
 6 | const Toaster = ({ ...props }: ToasterProps) => {
 7 |   const { theme = "system" } = useTheme();
 8 | 
 9 |   return (
10 |     <Sonner
11 |       theme={theme as ToasterProps["theme"]}
12 |       className="toaster group"
13 |       style={
14 |         {
15 |           "--normal-bg": "var(--popover)",
16 |           "--normal-text": "var(--popover-foreground)",
17 |           "--normal-border": "var(--border)",
18 |         } as React.CSSProperties
19 |       }
20 |       {...props}
21 |     />
22 |   );
23 | };
24 | 
25 | export { Toaster };
26 | 


--------------------------------------------------------------------------------
/src/components/ui/stack.tsx:
--------------------------------------------------------------------------------
 1 | import { cn } from "@/lib/utils";
 2 | import React from "react";
 3 | 
 4 | export const HStack: React.FC<React.ComponentProps<"div">> = ({
 5 |   children,
 6 |   className,
 7 |   ...props
 8 | }) => {
 9 |   return (
10 |     <div
11 |       className={cn("flex flex-row items-center gap-2", className)}
12 |       {...props}
13 |     >
14 |       {children}
15 |     </div>
16 |   );
17 | };
18 | 
19 | export const VStack: React.FC<React.ComponentProps<"div">> = ({
20 |   children,
21 |   className,
22 |   ...props
23 | }) => {
24 |   return (
25 |     <div
26 |       className={cn("flex flex-col items-center gap-2", className)}
27 |       {...props}
28 |     >
29 |       {children}
30 |     </div>
31 |   );
32 | };
33 | 


--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
 1 | "use client";
 2 | 
 3 | import * as React from "react";
 4 | import * as SwitchPrimitive from "@radix-ui/react-switch";
 5 | 
 6 | import { cn } from "@/lib/utils";
 7 | 
 8 | function Switch({
 9 |   className,
10 |   ...props
11 | }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
12 |   return (
13 |     <SwitchPrimitive.Root
14 |       data-slot="switch"
15 |       className={cn(
16 |         "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
17 |         className,
18 |       )}
19 |       {...props}
20 |     >
21 |       <SwitchPrimitive.Thumb
22 |         data-slot="switch-thumb"
23 |         className={cn(
24 |           "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0",
25 |         )}
26 |       />
27 |     </SwitchPrimitive.Root>
28 |   );
29 | }
30 | 
31 | export { Switch };
32 | 


--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
 1 | import * as React from "react";
 2 | 
 3 | import { cn } from "@/lib/utils";
 4 | 
 5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
 6 |   return (
 7 |     <textarea
 8 |       data-slot="textarea"
 9 |       className={cn(
10 |         "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
11 |         className,
12 |       )}
13 |       {...props}
14 |     />
15 |   );
16 | }
17 | 
18 | export { Textarea };
19 | 


--------------------------------------------------------------------------------
/src/hooks/use-mobile.ts:
--------------------------------------------------------------------------------
 1 | import * as React from "react";
 2 | 
 3 | const MOBILE_BREAKPOINT = 768;
 4 | 
 5 | export function useIsMobile() {
 6 |   const [isMobile, setIsMobile] = React.useState<boolean>(false);
 7 | 
 8 |   React.useEffect(() => {
 9 |     // Set initial value
10 |     setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
11 | 
12 |     // Create media query
13 |     const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
14 | 
15 |     // Define change handler
16 |     const onChange = (e: MediaQueryListEvent) => {
17 |       setIsMobile(e.matches);
18 |     };
19 | 
20 |     // Add event listener
21 |     mql.addEventListener("change", onChange);
22 | 
23 |     // Cleanup
24 |     return () => mql.removeEventListener("change", onChange);
25 |   }, []);
26 | 
27 |   return isMobile;
28 | }
29 | 


--------------------------------------------------------------------------------
/src/lib/constants.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * Global validation constants for the application
 3 |  */
 4 | 
 5 | export const VALIDATION_LIMITS = {
 6 |   /** Maximum length for message content and text parts */
 7 |   MESSAGE_MAX_LENGTH: 16348,
 8 | 
 9 |   /** Maximum length for file and attachment names */
10 |   FILE_NAME_MAX_LENGTH: 2000,
11 | 
12 |   /** Maximum file size for uploads (in bytes) */
13 |   FILE_MAX_SIZE: 5 * 1024 * 1024, // 5MB
14 | } as const;
15 | 
16 | /**
17 |  * Convenience exports for commonly used limits
18 |  */
19 | export const MESSAGE_MAX_LENGTH = VALIDATION_LIMITS.MESSAGE_MAX_LENGTH;
20 | export const FILE_NAME_MAX_LENGTH = VALIDATION_LIMITS.FILE_NAME_MAX_LENGTH;
21 | export const FILE_MAX_SIZE = VALIDATION_LIMITS.FILE_MAX_SIZE;
22 | 


--------------------------------------------------------------------------------
/src/lib/cookies/client.ts:
--------------------------------------------------------------------------------
 1 | import { setCookie } from "cookies-next/client";
 2 | import type { ImageModel, LanguageModel } from "@/ai/types";
 3 | import { COOKIE_KEYS } from "./keys";
 4 | import type { ClientToolkit } from "@/toolkits/types";
 5 | import type { z } from "zod";
 6 | import type { PersistedToolkit } from "./types";
 7 | 
 8 | // Client-side cookie utilities
 9 | export const clientCookieUtils = {
10 |   // Save model selection
11 |   setSelectedChatModel(model: LanguageModel | undefined): void {
12 |     setCookie(COOKIE_KEYS.SELECTED_CHAT_MODEL, model);
13 |   },
14 | 
15 |   // Save image generation model
16 |   setImageGenerationModel(model: ImageModel | undefined): void {
17 |     setCookie(COOKIE_KEYS.IMAGE_GENERATION_MODEL, model);
18 |   },
19 | 
20 |   // Save native search preference
21 |   setUseNativeSearch(enabled: boolean): void {
22 |     setCookie(COOKIE_KEYS.USE_NATIVE_SEARCH, enabled);
23 |   },
24 | 
25 |   // Save toolkits
26 |   setToolkits(
27 |     toolkits: Array<{
28 |       id: string;
29 |       toolkit: ClientToolkit;
30 |       parameters: z.infer<ClientToolkit["parameters"]>;
31 |     }>,
32 |   ): void {
33 |     const persistedToolkits: PersistedToolkit[] = toolkits.map((t) => ({
34 |       id: t.id,
35 |       parameters: t.parameters,
36 |     }));
37 |     setCookie(COOKIE_KEYS.TOOLKITS, persistedToolkits);
38 |   },
39 | };
40 | 


--------------------------------------------------------------------------------
/src/lib/cookies/keys.ts:
--------------------------------------------------------------------------------
1 | export const COOKIE_KEYS = {
2 |   SELECTED_CHAT_MODEL: "open-chat-selected-model",
3 |   IMAGE_GENERATION_MODEL: "open-chat-image-model",
4 |   USE_NATIVE_SEARCH: "open-chat-native-search",
5 |   TOOLKITS: "open-chat-toolkits",
6 | } as const;
7 | 


--------------------------------------------------------------------------------
/src/lib/cookies/server.ts:
--------------------------------------------------------------------------------
 1 | import { cookies } from "next/headers";
 2 | import type { ChatPreferences } from "./types";
 3 | import { COOKIE_KEYS } from "./keys";
 4 | 
 5 | // Helper to safely parse JSON from cookie value
 6 | const safeParseJson = <T>(value: string | null | undefined, fallback: T): T => {
 7 |   if (!value) return fallback;
 8 |   try {
 9 |     return JSON.parse(decodeURIComponent(value)) as T;
10 |   } catch {
11 |     return fallback;
12 |   }
13 | };
14 | 
15 | // Server-side cookie utilities
16 | export const serverCookieUtils = {
17 |   // Get all preferences from server-side cookies
18 |   async getPreferences(): Promise<ChatPreferences> {
19 |     try {
20 |       const cookieStore = await cookies();
21 | 
22 |       return {
23 |         selectedChatModel: safeParseJson(
24 |           cookieStore.get(COOKIE_KEYS.SELECTED_CHAT_MODEL)?.value,
25 |           undefined,
26 |         ),
27 |         imageGenerationModel: safeParseJson(
28 |           cookieStore.get(COOKIE_KEYS.IMAGE_GENERATION_MODEL)?.value,
29 |           undefined,
30 |         ),
31 |         useNativeSearch: safeParseJson(
32 |           cookieStore.get(COOKIE_KEYS.USE_NATIVE_SEARCH)?.value,
33 |           false,
34 |         ),
35 |         toolkits: safeParseJson(
36 |           cookieStore.get(COOKIE_KEYS.TOOLKITS)?.value,
37 |           [],
38 |         ),
39 |       };
40 |     } catch (error) {
41 |       console.warn("Failed to read cookies:", error);
42 |       return {};
43 |     }
44 |   },
45 | };
46 | 


--------------------------------------------------------------------------------
/src/lib/cookies/types.ts:
--------------------------------------------------------------------------------
 1 | import type { ImageModel, LanguageModel } from "@/ai/types";
 2 | 
 3 | export interface PersistedToolkit {
 4 |   id: string;
 5 |   parameters: Record<string, unknown>;
 6 | }
 7 | 
 8 | export interface ChatPreferences {
 9 |   selectedChatModel?: LanguageModel;
10 |   imageGenerationModel?: ImageModel;
11 |   useNativeSearch?: boolean;
12 |   toolkits?: PersistedToolkit[];
13 | }
14 | 


--------------------------------------------------------------------------------
/src/lib/fetch.ts:
--------------------------------------------------------------------------------
 1 | import { ChatSDKError, type ErrorCode } from "@/lib/errors";
 2 | 
 3 | export async function fetchWithErrorHandlers(
 4 |   input: RequestInfo | URL,
 5 |   init?: RequestInit,
 6 | ) {
 7 |   try {
 8 |     const response = await fetch(input, init);
 9 | 
10 |     if (!response.ok) {
11 |       const { code, cause } = (await response.json()) as {
12 |         code: ErrorCode;
13 |         cause: string;
14 |       };
15 |       throw new ChatSDKError(code, cause);
16 |     }
17 | 
18 |     return response;
19 |   } catch (error: unknown) {
20 |     if (typeof navigator !== "undefined" && !navigator.onLine) {
21 |       throw new ChatSDKError("offline:chat");
22 |     }
23 | 
24 |     throw error;
25 |   }
26 | }
27 | 


--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
 1 | import { clsx, type ClassValue } from "clsx";
 2 | import { twMerge } from "tailwind-merge";
 3 | 
 4 | export function cn(...inputs: ClassValue[]) {
 5 |   return twMerge(clsx(inputs));
 6 | }
 7 | 
 8 | export function generateUUID(): string {
 9 |   return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
10 |     const r = (Math.random() * 16) | 0;
11 |     const v = c === "x" ? r : (r & 0x3) | 0x8;
12 |     return v.toString(16);
13 |   });
14 | }
15 | 
16 | export function sanitizeText(text: string) {
17 |   return text.replace("<has_function_call>", "");
18 | }
19 | 


--------------------------------------------------------------------------------
/src/server/api/root.ts:
--------------------------------------------------------------------------------
 1 | import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc";
 2 | 
 3 | import {
 4 |   chatsRouter,
 5 |   filesRouter,
 6 |   messagesRouter,
 7 |   streamsRouter,
 8 |   usersRouter,
 9 |   accountsRouter,
10 |   imagesRouter,
11 |   memoriesRouter,
12 |   featuresRouter,
13 |   workbenchesRouter,
14 | } from "./routers";
15 | 
16 | /**
17 |  * This is the primary router for your server.
18 |  *
19 |  * All routers added in /api/routers should be manually added here.
20 |  */
21 | export const appRouter = createTRPCRouter({
22 |   chats: chatsRouter,
23 |   messages: messagesRouter,
24 |   streams: streamsRouter,
25 |   files: filesRouter,
26 |   users: usersRouter,
27 |   accounts: accountsRouter,
28 |   images: imagesRouter,
29 |   memories: memoriesRouter,
30 |   features: featuresRouter,
31 |   workbenches: workbenchesRouter,
32 | });
33 | 
34 | // export type definition of API
35 | export type AppRouter = typeof appRouter;
36 | 
37 | /**
38 |  * Create a server-side caller for the tRPC API.
39 |  * @example
40 |  * const trpc = createCaller(createContext);
41 |  * const res = await trpc.post.all();
42 |  *       ^? Post[]
43 |  */
44 | export const createCaller = createCallerFactory(appRouter);
45 | 


--------------------------------------------------------------------------------
/src/server/api/routers/index.ts:
--------------------------------------------------------------------------------
 1 | export { chatsRouter } from "./chats";
 2 | export { messagesRouter } from "./messages";
 3 | export { streamsRouter } from "./streams";
 4 | export { filesRouter } from "./files";
 5 | export { usersRouter } from "./users";
 6 | export { accountsRouter } from "./accounts";
 7 | export { imagesRouter } from "./images";
 8 | export { memoriesRouter } from "./memories";
 9 | export { featuresRouter } from "./features";
10 | export { workbenchesRouter } from "./workbenches";
11 | 


--------------------------------------------------------------------------------
/src/server/api/routers/streams.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | 
 3 | import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
 4 | 
 5 | export const streamsRouter = createTRPCRouter({
 6 |   createStreamId: protectedProcedure
 7 |     .input(
 8 |       z.object({
 9 |         streamId: z.string(),
10 |         chatId: z.string(),
11 |       }),
12 |     )
13 |     .mutation(async ({ ctx, input }) => {
14 |       return await ctx.db.stream.create({
15 |         data: {
16 |           id: input.streamId,
17 |           chatId: input.chatId,
18 |           createdAt: new Date(),
19 |         },
20 |       });
21 |     }),
22 | 
23 |   getStreamIdsByChatId: protectedProcedure
24 |     .input(
25 |       z.object({
26 |         chatId: z.string(),
27 |       }),
28 |     )
29 |     .query(async ({ ctx, input }) => {
30 |       const streams = await ctx.db.stream.findMany({
31 |         where: {
32 |           chatId: input.chatId,
33 |         },
34 |         orderBy: {
35 |           createdAt: "asc",
36 |         },
37 |         select: {
38 |           id: true,
39 |         },
40 |       });
41 | 
42 |       return streams.map((stream) => stream.id);
43 |     }),
44 | });
45 | 


--------------------------------------------------------------------------------
/src/server/api/routers/users.ts:
--------------------------------------------------------------------------------
 1 | import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
 2 | import { z } from "zod";
 3 | 
 4 | export const usersRouter = createTRPCRouter({
 5 |   getCurrentUser: protectedProcedure.query(async ({ ctx }) => {
 6 |     return ctx.db.user.findUnique({
 7 |       where: {
 8 |         id: ctx.session.user.id,
 9 |       },
10 |       select: {
11 |         id: true,
12 |         name: true,
13 |         email: true,
14 |         image: true,
15 |         emailVerified: true,
16 |       },
17 |     });
18 |   }),
19 | 
20 |   updateUser: protectedProcedure
21 |     .input(
22 |       z.object({
23 |         name: z.string().optional(),
24 |         email: z.string().email().optional(),
25 |         image: z.string().optional(),
26 |       }),
27 |     )
28 |     .mutation(async ({ ctx, input }) => {
29 |       return ctx.db.user.update({
30 |         where: {
31 |           id: ctx.session.user.id,
32 |         },
33 |         data: input,
34 |       });
35 |     }),
36 | 
37 |   deleteUser: protectedProcedure.mutation(async ({ ctx }) => {
38 |     return ctx.db.user.delete({
39 |       where: {
40 |         id: ctx.session.user.id,
41 |       },
42 |     });
43 |   }),
44 | });
45 | 


--------------------------------------------------------------------------------
/src/server/auth/index.ts:
--------------------------------------------------------------------------------
 1 | import { cache } from "react";
 2 | 
 3 | import NextAuth from "next-auth";
 4 | 
 5 | import { authConfig } from "./config";
 6 | 
 7 | const { auth: uncachedAuth, handlers, signIn, signOut } = NextAuth(authConfig);
 8 | 
 9 | const auth = cache(uncachedAuth);
10 | 
11 | export { auth, handlers, signIn, signOut };
12 | 


--------------------------------------------------------------------------------
/src/server/db.ts:
--------------------------------------------------------------------------------
 1 | import { PrismaClient } from "@prisma/client";
 2 | 
 3 | import { env } from "@/env";
 4 | 
 5 | const createPrismaClient = () =>
 6 |   new PrismaClient({
 7 |     log:
 8 |       env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
 9 |   });
10 | 
11 | const globalForPrisma = globalThis as unknown as {
12 |   prisma: ReturnType<typeof createPrismaClient> | undefined;
13 | };
14 | 
15 | export const db = globalForPrisma.prisma ?? createPrismaClient();
16 | 
17 | if (env.NODE_ENV !== "production") globalForPrisma.prisma = db;
18 | 


--------------------------------------------------------------------------------
/src/toolkits/create-tool.ts:
--------------------------------------------------------------------------------
 1 | import type { ZodObject, ZodRawShape } from "zod";
 2 | import type {
 3 |   BaseTool,
 4 |   ClientTool,
 5 |   ClientToolConfig,
 6 |   ServerTool,
 7 |   ServerToolConfig,
 8 | } from "./types";
 9 | 
10 | export const createBaseTool = <
11 |   Args extends ZodRawShape,
12 |   Output extends ZodRawShape,
13 | >({
14 |   description,
15 |   inputSchema,
16 |   outputSchema,
17 | }: {
18 |   description: string;
19 |   inputSchema: ZodObject<Args>;
20 |   outputSchema: ZodObject<Output>;
21 | }): BaseTool<Args, Output> => {
22 |   return { description, inputSchema, outputSchema };
23 | };
24 | 
25 | export const createServerTool = <
26 |   Args extends ZodRawShape,
27 |   Result extends ZodRawShape,
28 | >(
29 |   tool: BaseTool<Args, Result>,
30 |   config: ServerToolConfig<Args, Result>,
31 | ): ServerTool<Args, Result> => {
32 |   return {
33 |     ...tool,
34 |     ...config,
35 |   };
36 | };
37 | 
38 | export const createClientTool = <
39 |   Args extends ZodRawShape,
40 |   Result extends ZodRawShape,
41 | >(
42 |   tool: BaseTool<Args, Result>,
43 |   config: ClientToolConfig<Args, Result>,
44 | ): ClientTool<Args, Result> => {
45 |   return {
46 |     ...tool,
47 |     ...config,
48 |   };
49 | };
50 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkit-groups.ts:
--------------------------------------------------------------------------------
 1 | import { Logo } from "@/components/ui/logo";
 2 | import { ToolkitGroups, type ToolkitGroup } from "./types";
 3 | import { BookCopy, Database } from "lucide-react";
 4 | 
 5 | export const toolkitGroups: ToolkitGroup[] = [
 6 |   {
 7 |     id: ToolkitGroups.Native,
 8 |     name: "Native Tools",
 9 |     icon: Logo,
10 |   },
11 |   {
12 |     id: ToolkitGroups.DataSource,
13 |     name: "Data Sources",
14 |     icon: Database,
15 |   },
16 |   {
17 |     id: ToolkitGroups.KnowledgeBase,
18 |     name: "Knowledge Base",
19 |     icon: BookCopy,
20 |   },
21 | ];
22 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/client.ts:
--------------------------------------------------------------------------------
 1 | import type { ClientToolkit } from "../types";
 2 | import {
 3 |   Toolkits,
 4 |   type ServerToolkitNames,
 5 |   type ServerToolkitParameters,
 6 | } from "./shared";
 7 | import { exaClientToolkit } from "./exa/client";
 8 | import { imageClientToolkit } from "./image/client";
 9 | import { githubClientToolkit } from "./github/client";
10 | import { googleCalendarClientToolkit } from "./google-calendar/client";
11 | import { googleDriveClientToolkit } from "./google-drive/client";
12 | import { mem0ClientToolkit } from "./mem0/client";
13 | import { notionClientToolkit } from "./notion/client";
14 | import { e2bClientToolkit } from "./e2b/client";
15 | 
16 | type ClientToolkits = {
17 |   [K in Toolkits]: ClientToolkit<
18 |     ServerToolkitNames[K],
19 |     ServerToolkitParameters[K]
20 |   >;
21 | };
22 | 
23 | export const clientToolkits: ClientToolkits = {
24 |   [Toolkits.E2B]: e2bClientToolkit,
25 |   [Toolkits.Memory]: mem0ClientToolkit,
26 |   [Toolkits.Image]: imageClientToolkit,
27 |   [Toolkits.Exa]: exaClientToolkit,
28 |   [Toolkits.Github]: githubClientToolkit,
29 |   [Toolkits.GoogleCalendar]: googleCalendarClientToolkit,
30 |   [Toolkits.Notion]: notionClientToolkit,
31 |   [Toolkits.GoogleDrive]: googleDriveClientToolkit,
32 | };
33 | 
34 | export function getClientToolkit<T extends Toolkits>(
35 |   server: T,
36 | ): ClientToolkit<ServerToolkitNames[T], ServerToolkitParameters[T]> {
37 |   return clientToolkits[server];
38 | }
39 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/e2b/base.ts:
--------------------------------------------------------------------------------
 1 | import type { ToolkitConfig } from "@/toolkits/types";
 2 | import { baseRunCodeTool } from "./tools/run_code/base";
 3 | import { E2BTools } from "./tools/tools";
 4 | import { z } from "zod";
 5 | 
 6 | export const e2bParameters = z.object({});
 7 | 
 8 | export const baseE2BToolkitConfig: ToolkitConfig<
 9 |   E2BTools,
10 |   typeof e2bParameters.shape
11 | > = {
12 |   tools: {
13 |     [E2BTools.RunCode]: baseRunCodeTool,
14 |   },
15 |   parameters: e2bParameters,
16 | };
17 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/e2b/client.tsx:
--------------------------------------------------------------------------------
 1 | import { Terminal } from "lucide-react";
 2 | 
 3 | import { E2BTools } from "./tools/tools";
 4 | import { createClientToolkit } from "@/toolkits/create-toolkit";
 5 | import { e2bRunCodeToolConfigClient } from "./tools/run_code/client";
 6 | import { baseE2BToolkitConfig } from "./base";
 7 | import { ToolkitGroups } from "@/toolkits/types";
 8 | 
 9 | export const e2bClientToolkit = createClientToolkit(
10 |   baseE2BToolkitConfig,
11 |   {
12 |     name: "Code Interpreter",
13 |     description: "Execute python code in a secure environment",
14 |     icon: Terminal,
15 |     form: null,
16 |     type: ToolkitGroups.Native,
17 |   },
18 |   {
19 |     [E2BTools.RunCode]: e2bRunCodeToolConfigClient,
20 |   },
21 | );
22 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/e2b/server.ts:
--------------------------------------------------------------------------------
 1 | import { createServerToolkit } from "@/toolkits/create-toolkit";
 2 | import { baseE2BToolkitConfig } from "./base";
 3 | import { e2bRunCodeToolConfigServer } from "./tools/run_code/server";
 4 | import { E2BTools } from "./tools/tools";
 5 | 
 6 | export const e2bToolkitServer = createServerToolkit(
 7 |   baseE2BToolkitConfig,
 8 |   `You have access to the E2B toolkit for secure code execution and development environments. This toolkit provides:
 9 | 
10 | - **Run Code**: Execute Python code in isolated, secure cloud environments.`,
11 |   async () => {
12 |     return {
13 |       [E2BTools.RunCode]: e2bRunCodeToolConfigServer(),
14 |     };
15 |   },
16 | );
17 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/e2b/tools/run_code/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | import type { Result, Logs } from "@e2b/code-interpreter";
 5 | 
 6 | export const baseRunCodeTool = createBaseTool({
 7 |   description:
 8 |     "Run Python code in a secure sandbox environment using E2B. Supports Jupyter Notebook syntax and returns execution results and logs.",
 9 |   inputSchema: z.object({
10 |     code: z.string().describe("The Python code to execute in the sandbox"),
11 |   }),
12 |   outputSchema: z.object({
13 |     results: z
14 |       .array(z.custom<Result>())
15 |       .describe("The execution results from running the code"),
16 |     logs: z.custom<Logs>().describe("Execution logs"),
17 |   }),
18 | });
19 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/e2b/tools/tools.ts:
--------------------------------------------------------------------------------
1 | export enum E2BTools {
2 |   RunCode = "run-code",
3 | }
4 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/base.ts:
--------------------------------------------------------------------------------
 1 | import type { ToolkitConfig } from "@/toolkits/types";
 2 | import { baseSearchTool } from "./tools/search/base";
 3 | import { baseResearchPaperSearchTool } from "./tools/research_paper_search/base";
 4 | import { baseCompanyResearchTool } from "./tools/company_research/base";
 5 | import { baseCrawlingTool } from "./tools/crawling/base";
 6 | import { baseCompetitorFinderTool } from "./tools/competitor_finder/base";
 7 | import { baseLinkedinSearchTool } from "./tools/linkedin_search/base";
 8 | import { baseWikipediaSearchTool } from "./tools/wikipedia_search/base";
 9 | import { baseGithubSearchTool } from "./tools/github_search/base";
10 | import { ExaTools } from "./tools/tools";
11 | import { z } from "zod";
12 | 
13 | export const exaParameters = z.object({});
14 | 
15 | export const baseExaToolkitConfig: ToolkitConfig<
16 |   ExaTools,
17 |   typeof exaParameters.shape
18 | > = {
19 |   tools: {
20 |     [ExaTools.Search]: baseSearchTool,
21 |     [ExaTools.ResearchPaperSearch]: baseResearchPaperSearchTool,
22 |     [ExaTools.CompanyResearch]: baseCompanyResearchTool,
23 |     [ExaTools.Crawling]: baseCrawlingTool,
24 |     [ExaTools.CompetitorFinder]: baseCompetitorFinderTool,
25 |     [ExaTools.LinkedinSearch]: baseLinkedinSearchTool,
26 |     [ExaTools.WikipediaSearch]: baseWikipediaSearchTool,
27 |     [ExaTools.GithubSearch]: baseGithubSearchTool,
28 |   },
29 |   parameters: exaParameters,
30 | };
31 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/client.tsx:
--------------------------------------------------------------------------------
 1 | import { Search } from "lucide-react";
 2 | 
 3 | import { ExaTools } from "./tools/tools";
 4 | import { createClientToolkit } from "@/toolkits/create-toolkit";
 5 | import { exaSearchToolConfigClient } from "./tools/search/client";
 6 | import { exaResearchPaperSearchToolConfigClient } from "./tools/research_paper_search/client";
 7 | import { exaCompanyResearchToolConfigClient } from "./tools/company_research/client";
 8 | import { exaCrawlingToolConfigClient } from "./tools/crawling/client";
 9 | import { exaCompetitorFinderToolConfigClient } from "./tools/competitor_finder/client";
10 | import { exaLinkedinSearchToolConfigClient } from "./tools/linkedin_search/client";
11 | import { exaWikipediaSearchToolConfigClient } from "./tools/wikipedia_search/client";
12 | import { exaGithubSearchToolConfigClient } from "./tools/github_search/client";
13 | import { baseExaToolkitConfig } from "./base";
14 | import { ToolkitGroups } from "@/toolkits/types";
15 | 
16 | export const exaClientToolkit = createClientToolkit(
17 |   baseExaToolkitConfig,
18 |   {
19 |     name: "Web Search",
20 |     description: "Find articles, research papers, companies, and more",
21 |     icon: Search,
22 |     form: null,
23 |     type: ToolkitGroups.DataSource,
24 |   },
25 |   {
26 |     [ExaTools.Search]: exaSearchToolConfigClient,
27 |     [ExaTools.ResearchPaperSearch]: exaResearchPaperSearchToolConfigClient,
28 |     [ExaTools.CompanyResearch]: exaCompanyResearchToolConfigClient,
29 |     [ExaTools.Crawling]: exaCrawlingToolConfigClient,
30 |     [ExaTools.CompetitorFinder]: exaCompetitorFinderToolConfigClient,
31 |     [ExaTools.LinkedinSearch]: exaLinkedinSearchToolConfigClient,
32 |     [ExaTools.WikipediaSearch]: exaWikipediaSearchToolConfigClient,
33 |     [ExaTools.GithubSearch]: exaGithubSearchToolConfigClient,
34 |   },
35 | );
36 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/components/index.ts:
--------------------------------------------------------------------------------
1 | export { ToolCallDisplay } from "./tool-call-display";
2 | export { ResultItem, type ResultData } from "./result-item";
3 | export { ResultsList } from "./results-list";
4 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/components/results-list.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { ResultItem, type ResultData } from "./result-item";
 3 | 
 4 | interface ResultsListProps {
 5 |   results: ResultData[];
 6 |   title: string;
 7 |   emptyMessage: string;
 8 |   linkText?: string;
 9 | }
10 | 
11 | export const ResultsList: React.FC<ResultsListProps> = ({
12 |   results,
13 |   title,
14 |   emptyMessage,
15 |   linkText,
16 | }) => {
17 |   if (!results.length) {
18 |     return <div className="text-gray-500">{emptyMessage}</div>;
19 |   }
20 | 
21 |   return (
22 |     <div className="">
23 |       <h1 className="text-muted-foreground text-sm font-medium">{title}</h1>
24 |       <div className="flex flex-col">
25 |         {results.map((result, index) => (
26 |           <ResultItem
27 |             key={index}
28 |             result={result}
29 |             index={index}
30 |             linkText={linkText}
31 |           />
32 |         ))}
33 |       </div>
34 |     </div>
35 |   );
36 | };
37 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/components/tool-call-display.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { HStack, VStack } from "@/components/ui/stack";
 3 | 
 4 | interface ToolCallDisplayProps {
 5 |   icon: React.ComponentType<{ className?: string }>;
 6 |   label: string;
 7 |   value: string;
 8 | }
 9 | 
10 | export const ToolCallDisplay: React.FC<ToolCallDisplayProps> = ({
11 |   icon: Icon,
12 |   label,
13 |   value,
14 | }) => {
15 |   return (
16 |     <HStack className="gap-2">
17 |       <Icon className="text-muted-foreground size-4" />
18 |       <VStack className="items-start gap-0">
19 |         <span className="text-muted-foreground/80 text-xs font-medium">
20 |           {label}
21 |         </span>
22 |         <span className="text-sm">&quot;{value}&quot;</span>
23 |       </VStack>
24 |     </HStack>
25 |   );
26 | };
27 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/company_research/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const baseCompanyResearchTool = createBaseTool({
 5 |   description: "Research companies and gather detailed business information",
 6 |   inputSchema: z.object({
 7 |     company: z.string().min(1).max(100),
 8 |   }),
 9 |   outputSchema: z.object({
10 |     results: z.array(
11 |       z.object({
12 |         title: z.string().nullable(),
13 |         url: z.string().url(),
14 |         content: z.string(),
15 |         publishedDate: z.string().optional(),
16 |         image: z.string().optional(),
17 |         favicon: z.string().optional(),
18 |         score: z.number().optional(),
19 |         author: z.string().optional(),
20 |       }),
21 |     ),
22 |   }),
23 | });
24 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/company_research/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { Building } from "lucide-react";
 3 | import type { baseCompanyResearchTool } from "./base";
 4 | import type { ClientToolConfig } from "@/toolkits/types";
 5 | import { ToolCallDisplay, ResultsList } from "../../components";
 6 | 
 7 | export const exaCompanyResearchToolConfigClient: ClientToolConfig<
 8 |   typeof baseCompanyResearchTool.inputSchema.shape,
 9 |   typeof baseCompanyResearchTool.outputSchema.shape
10 | > = {
11 |   CallComponent: ({ args }) => {
12 |     return (
13 |       <ToolCallDisplay
14 |         icon={Building}
15 |         label="Company Research"
16 |         value={args.company ?? "..."}
17 |       />
18 |     );
19 |   },
20 |   ResultComponent: ({ result }) => {
21 |     return (
22 |       <ResultsList
23 |         results={result.results}
24 |         title="Company Research Results"
25 |         emptyMessage="No company information found"
26 |         linkText="Read more →"
27 |       />
28 |     );
29 |   },
30 | };
31 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/company_research/server.ts:
--------------------------------------------------------------------------------
 1 | import { type baseCompanyResearchTool } from "./base";
 2 | import { env } from "@/env";
 3 | import { Exa } from "exa-js";
 4 | import type { ServerToolConfig } from "@/toolkits/types";
 5 | 
 6 | export const exaCompanyResearchToolConfigServer: ServerToolConfig<
 7 |   typeof baseCompanyResearchTool.inputSchema.shape,
 8 |   typeof baseCompanyResearchTool.outputSchema.shape
 9 | > = {
10 |   callback: async ({ company }) => {
11 |     if (!env.EXA_API_KEY) {
12 |       throw new Error("EXA_API_KEY is not set");
13 |     }
14 | 
15 |     const exa = new Exa(env.EXA_API_KEY);
16 | 
17 |     const { results } = await exa.searchAndContents(
18 |       `${company} company profile business information`,
19 |       {
20 |         livecrawl: "always",
21 |         numResults: 5,
22 |         type: "neural",
23 |         useAutoprompt: true,
24 |       },
25 |     );
26 | 
27 |     return {
28 |       results: results.map((result) => ({
29 |         title: result.title,
30 |         url: result.url,
31 |         content: result.text.slice(0, 1500),
32 |         publishedDate: result.publishedDate,
33 |         image: result.image,
34 |         favicon: result.favicon,
35 |         score: result.score,
36 |         author: result.author,
37 |       })),
38 |     };
39 |   },
40 |   message:
41 |     "The user is shown comprehensive company research results. Provide a summary of the key business information found and ask if they need more specific details.",
42 | };
43 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/competitor_finder/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const baseCompetitorFinderTool = createBaseTool({
 5 |   description: "Find competitors of a company",
 6 |   inputSchema: z.object({
 7 |     company: z.string().min(1).max(100),
 8 |   }),
 9 |   outputSchema: z.object({
10 |     results: z.array(
11 |       z.object({
12 |         title: z.string().nullable(),
13 |         url: z.string().url(),
14 |         content: z.string(),
15 |         publishedDate: z.string().optional(),
16 |         image: z.string().optional(),
17 |         favicon: z.string().optional(),
18 |         score: z.number().optional(),
19 |         author: z.string().optional(),
20 |       }),
21 |     ),
22 |   }),
23 | });
24 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/competitor_finder/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { Users } from "lucide-react";
 3 | import type { baseCompetitorFinderTool } from "./base";
 4 | import type { ClientToolConfig } from "@/toolkits/types";
 5 | import { ToolCallDisplay, ResultsList } from "../../components";
 6 | 
 7 | export const exaCompetitorFinderToolConfigClient: ClientToolConfig<
 8 |   typeof baseCompetitorFinderTool.inputSchema.shape,
 9 |   typeof baseCompetitorFinderTool.outputSchema.shape
10 | > = {
11 |   CallComponent: ({ args }) => {
12 |     return (
13 |       <ToolCallDisplay
14 |         icon={Users}
15 |         label="Competitor Analysis"
16 |         value={args.company ?? "..."}
17 |       />
18 |     );
19 |   },
20 |   ResultComponent: ({ result }) => {
21 |     return (
22 |       <ResultsList
23 |         results={result.results}
24 |         title="Competitors Found"
25 |         emptyMessage="No competitors found"
26 |         linkText="Learn more →"
27 |       />
28 |     );
29 |   },
30 | };
31 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/competitor_finder/server.ts:
--------------------------------------------------------------------------------
 1 | import { type baseCompetitorFinderTool } from "./base";
 2 | import { env } from "@/env";
 3 | import { Exa } from "exa-js";
 4 | import type { ServerToolConfig } from "@/toolkits/types";
 5 | 
 6 | export const exaCompetitorFinderToolConfigServer: ServerToolConfig<
 7 |   typeof baseCompetitorFinderTool.inputSchema.shape,
 8 |   typeof baseCompetitorFinderTool.outputSchema.shape
 9 | > = {
10 |   callback: async ({ company }) => {
11 |     if (!env.EXA_API_KEY) {
12 |       throw new Error("EXA_API_KEY is not set");
13 |     }
14 | 
15 |     const exa = new Exa(env.EXA_API_KEY);
16 | 
17 |     const { results } = await exa.searchAndContents(
18 |       `${company} competitors alternative companies similar services`,
19 |       {
20 |         livecrawl: "always",
21 |         numResults: 5,
22 |         type: "neural",
23 |         useAutoprompt: true,
24 |       },
25 |     );
26 | 
27 |     return {
28 |       results: results.map((result) => ({
29 |         title: result.title,
30 |         url: result.url,
31 |         content: result.text.slice(0, 1000),
32 |         publishedDate: result.publishedDate,
33 |         image: result.image,
34 |         favicon: result.favicon,
35 |         score: result.score,
36 |         author: result.author,
37 |       })),
38 |     };
39 |   },
40 |   message:
41 |     "The user is shown competitor analysis results. Provide a summary of the main competitors found and their key differentiators.",
42 | };
43 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/crawling/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const baseCrawlingTool = createBaseTool({
 5 |   description: "Extract content from specific URLs",
 6 |   inputSchema: z.object({
 7 |     urls: z.array(z.string()),
 8 |   }),
 9 |   outputSchema: z.object({
10 |     results: z.array(
11 |       z.object({
12 |         title: z.string().nullable(),
13 |         url: z.string().url(),
14 |         content: z.string(),
15 |         publishedDate: z.string().optional(),
16 |         image: z.string().optional(),
17 |         favicon: z.string().optional(),
18 |         score: z.number().optional(),
19 |         author: z.string().optional(),
20 |       }),
21 |     ),
22 |   }),
23 | });
24 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/crawling/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { Link } from "lucide-react";
 3 | import type { baseCrawlingTool } from "./base";
 4 | import type { ClientToolConfig } from "@/toolkits/types";
 5 | import { ToolCallDisplay, ResultsList } from "../../components";
 6 | 
 7 | export const exaCrawlingToolConfigClient: ClientToolConfig<
 8 |   typeof baseCrawlingTool.inputSchema.shape,
 9 |   typeof baseCrawlingTool.outputSchema.shape
10 | > = {
11 |   CallComponent: ({ args }) => {
12 |     return (
13 |       <ToolCallDisplay
14 |         icon={Link}
15 |         label="Crawling URLs"
16 |         value={args.urls?.join(", ") ?? "..."}
17 |       />
18 |     );
19 |   },
20 |   ResultComponent: ({ result }) => {
21 |     return (
22 |       <ResultsList
23 |         results={result.results}
24 |         title="Extracted Content"
25 |         emptyMessage="No content extracted"
26 |         linkText="View source →"
27 |       />
28 |     );
29 |   },
30 | };
31 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/crawling/server.ts:
--------------------------------------------------------------------------------
 1 | import { type baseCrawlingTool } from "./base";
 2 | import { env } from "@/env";
 3 | import { Exa } from "exa-js";
 4 | import type { ServerToolConfig } from "@/toolkits/types";
 5 | 
 6 | export const exaCrawlingToolConfigServer: ServerToolConfig<
 7 |   typeof baseCrawlingTool.inputSchema.shape,
 8 |   typeof baseCrawlingTool.outputSchema.shape
 9 | > = {
10 |   callback: async ({ urls }) => {
11 |     if (!env.EXA_API_KEY) {
12 |       throw new Error("EXA_API_KEY is not set");
13 |     }
14 | 
15 |     const exa = new Exa(env.EXA_API_KEY);
16 | 
17 |     const { results } = await exa.getContents(urls);
18 | 
19 |     return {
20 |       results: results.map((result) => ({
21 |         title: result.title,
22 |         url: result.url,
23 |         content: result.text,
24 |         publishedDate: result.publishedDate,
25 |         image: result.image,
26 |         favicon: result.favicon,
27 |         score: result.score,
28 |         author: result.author,
29 |       })),
30 |     };
31 |   },
32 |   message:
33 |     "The user is shown the extracted content from the URL. Provide a brief summary of what was found.",
34 | };
35 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/github_search/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const baseGithubSearchTool = createBaseTool({
 5 |   description: "Search GitHub repositories and accounts",
 6 |   inputSchema: z.object({
 7 |     query: z.string().min(1).max(100),
 8 |   }),
 9 |   outputSchema: z.object({
10 |     results: z.array(
11 |       z.object({
12 |         title: z.string().nullable(),
13 |         url: z.string().url(),
14 |         content: z.string(),
15 |         publishedDate: z.string().optional(),
16 |         image: z.string().optional(),
17 |         favicon: z.string().optional(),
18 |         score: z.number().optional(),
19 |         author: z.string().optional(),
20 |       }),
21 |     ),
22 |   }),
23 | });
24 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/github_search/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import type { baseGithubSearchTool } from "./base";
 3 | import type { ClientToolConfig } from "@/toolkits/types";
 4 | import { ToolCallDisplay, ResultsList } from "../../components";
 5 | import { SiGithub } from "@icons-pack/react-simple-icons";
 6 | 
 7 | export const exaGithubSearchToolConfigClient: ClientToolConfig<
 8 |   typeof baseGithubSearchTool.inputSchema.shape,
 9 |   typeof baseGithubSearchTool.outputSchema.shape
10 | > = {
11 |   CallComponent: ({ args }) => {
12 |     return (
13 |       <ToolCallDisplay
14 |         icon={SiGithub}
15 |         label="GitHub Search"
16 |         value={args.query ?? "..."}
17 |       />
18 |     );
19 |   },
20 |   ResultComponent: ({ result }) => {
21 |     return (
22 |       <ResultsList
23 |         results={result.results}
24 |         title="GitHub Repositories"
25 |         emptyMessage="No GitHub repositories found"
26 |         linkText="View repository →"
27 |       />
28 |     );
29 |   },
30 | };
31 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/github_search/server.ts:
--------------------------------------------------------------------------------
 1 | import { type baseGithubSearchTool } from "./base";
 2 | import { env } from "@/env";
 3 | import { Exa } from "exa-js";
 4 | import type { ServerToolConfig } from "@/toolkits/types";
 5 | 
 6 | export const exaGithubSearchToolConfigServer: ServerToolConfig<
 7 |   typeof baseGithubSearchTool.inputSchema.shape,
 8 |   typeof baseGithubSearchTool.outputSchema.shape
 9 | > = {
10 |   callback: async ({ query }) => {
11 |     if (!env.EXA_API_KEY) {
12 |       throw new Error("EXA_API_KEY is not set");
13 |     }
14 | 
15 |     const exa = new Exa(env.EXA_API_KEY);
16 | 
17 |     const { results } = await exa.searchAndContents(query, {
18 |       livecrawl: "always",
19 |       numResults: 5,
20 |       includeDomains: ["github.com"],
21 |     });
22 | 
23 |     return {
24 |       results: results.map((result) => ({
25 |         title: result.title,
26 |         url: result.url,
27 |         content: result.text.slice(0, 1000),
28 |         publishedDate: result.publishedDate,
29 |         image: result.image,
30 |         favicon: result.favicon,
31 |         score: result.score,
32 |         author: result.author,
33 |       })),
34 |     };
35 |   },
36 |   message:
37 |     "The user is shown GitHub repositories and accounts. Provide a summary of the development projects and code found.",
38 | };
39 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/linkedin_search/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const baseLinkedinSearchTool = createBaseTool({
 5 |   description: "Search LinkedIn for companies and people",
 6 |   inputSchema: z.object({
 7 |     query: z.string().min(1).max(100),
 8 |   }),
 9 |   outputSchema: z.object({
10 |     results: z.array(
11 |       z.object({
12 |         title: z.string().nullable(),
13 |         url: z.string().url(),
14 |         content: z.string(),
15 |         publishedDate: z.string().optional(),
16 |         image: z.string().optional(),
17 |         favicon: z.string().optional(),
18 |         score: z.number().optional(),
19 |         author: z.string().optional(),
20 |       }),
21 |     ),
22 |   }),
23 | });
24 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/linkedin_search/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { Linkedin } from "lucide-react";
 3 | import type { baseLinkedinSearchTool } from "./base";
 4 | import type { ClientToolConfig } from "@/toolkits/types";
 5 | import { ToolCallDisplay, ResultsList } from "../../components";
 6 | 
 7 | export const exaLinkedinSearchToolConfigClient: ClientToolConfig<
 8 |   typeof baseLinkedinSearchTool.inputSchema.shape,
 9 |   typeof baseLinkedinSearchTool.outputSchema.shape
10 | > = {
11 |   CallComponent: ({ args }) => {
12 |     return (
13 |       <ToolCallDisplay
14 |         icon={Linkedin}
15 |         label="LinkedIn Search"
16 |         value={args.query ?? "..."}
17 |       />
18 |     );
19 |   },
20 |   ResultComponent: ({ result }) => {
21 |     return (
22 |       <ResultsList
23 |         results={result.results}
24 |         title="LinkedIn Results"
25 |         emptyMessage="No LinkedIn profiles found"
26 |         linkText="View profile →"
27 |       />
28 |     );
29 |   },
30 | };
31 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/linkedin_search/server.ts:
--------------------------------------------------------------------------------
 1 | import { type baseLinkedinSearchTool } from "./base";
 2 | import { env } from "@/env";
 3 | import { Exa } from "exa-js";
 4 | import type { ServerToolConfig } from "@/toolkits/types";
 5 | 
 6 | export const exaLinkedinSearchToolConfigServer: ServerToolConfig<
 7 |   typeof baseLinkedinSearchTool.inputSchema.shape,
 8 |   typeof baseLinkedinSearchTool.outputSchema.shape
 9 | > = {
10 |   callback: async ({ query }) => {
11 |     if (!env.EXA_API_KEY) {
12 |       throw new Error("EXA_API_KEY is not set");
13 |     }
14 | 
15 |     const exa = new Exa(env.EXA_API_KEY);
16 | 
17 |     const { results } = await exa.searchAndContents(query, {
18 |       livecrawl: "always",
19 |       numResults: 3,
20 |       includeDomains: ["linkedin.com"],
21 |     });
22 | 
23 |     return {
24 |       results: results.map((result) => ({
25 |         title: result.title,
26 |         url: result.url,
27 |         content: result.text.slice(0, 1000),
28 |         publishedDate: result.publishedDate,
29 |         image: result.image,
30 |         favicon: result.favicon,
31 |         score: result.score,
32 |         author: result.author,
33 |       })),
34 |     };
35 |   },
36 |   message:
37 |     "The user is shown LinkedIn profiles and company pages. Provide a summary of the professional information found.",
38 | };
39 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/research_paper_search/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const baseResearchPaperSearchTool = createBaseTool({
 5 |   description: "Search for research papers and academic content",
 6 |   inputSchema: z.object({
 7 |     query: z.string().min(1).max(100),
 8 |   }),
 9 |   outputSchema: z.object({
10 |     results: z.array(
11 |       z.object({
12 |         title: z.string().nullable(),
13 |         url: z.string().url(),
14 |         content: z.string(),
15 |         publishedDate: z.string().optional(),
16 |         image: z.string().optional(),
17 |         favicon: z.string().optional(),
18 |         score: z.number().optional(),
19 |         author: z.string().optional(),
20 |       }),
21 |     ),
22 |   }),
23 | });
24 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/research_paper_search/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { BookOpen } from "lucide-react";
 3 | 
 4 | import type { baseResearchPaperSearchTool } from "./base";
 5 | import type { ClientToolConfig } from "@/toolkits/types";
 6 | import { ToolCallDisplay, ResultsList } from "../../components";
 7 | 
 8 | export const exaResearchPaperSearchToolConfigClient: ClientToolConfig<
 9 |   typeof baseResearchPaperSearchTool.inputSchema.shape,
10 |   typeof baseResearchPaperSearchTool.outputSchema.shape
11 | > = {
12 |   CallComponent: ({ args }) => {
13 |     return (
14 |       <ToolCallDisplay
15 |         icon={BookOpen}
16 |         label="Research Paper Search"
17 |         value={args.query ?? "..."}
18 |       />
19 |     );
20 |   },
21 |   ResultComponent: ({ result }) => {
22 |     return (
23 |       <ResultsList
24 |         results={result.results}
25 |         title="Research Papers"
26 |         emptyMessage="No research papers found"
27 |         linkText="Read full paper →"
28 |       />
29 |     );
30 |   },
31 | };
32 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/research_paper_search/server.ts:
--------------------------------------------------------------------------------
 1 | import { type baseResearchPaperSearchTool } from "./base";
 2 | import { env } from "@/env";
 3 | import { Exa } from "exa-js";
 4 | import type { ServerToolConfig } from "@/toolkits/types";
 5 | 
 6 | export const exaResearchPaperSearchToolConfigServer: ServerToolConfig<
 7 |   typeof baseResearchPaperSearchTool.inputSchema.shape,
 8 |   typeof baseResearchPaperSearchTool.outputSchema.shape
 9 | > = {
10 |   callback: async ({ query }) => {
11 |     if (!env.EXA_API_KEY) {
12 |       throw new Error("EXA_API_KEY is not set");
13 |     }
14 | 
15 |     const exa = new Exa(env.EXA_API_KEY);
16 | 
17 |     const { results } = await exa.searchAndContents(query, {
18 |       livecrawl: "always",
19 |       numResults: 3,
20 |       includeDomains: [
21 |         "arxiv.org",
22 |         "scholar.google.com",
23 |         "pubmed.ncbi.nlm.nih.gov",
24 |         "researchgate.net",
25 |         "ieee.org",
26 |         "acm.org",
27 |       ],
28 |     });
29 | 
30 |     return {
31 |       results: results.map((result) => ({
32 |         title: result.title,
33 |         url: result.url,
34 |         content: result.text.slice(0, 1000),
35 |         publishedDate: result.publishedDate,
36 |         image: result.image,
37 |         favicon: result.favicon,
38 |         score: result.score,
39 |         author: result.author,
40 |       })),
41 |     };
42 |   },
43 |   message:
44 |     "The user is shown research papers in three cards. Do not list the sources again. Just give a 1-2 sentence summary response to their question and ask what else they would like to know.",
45 | };
46 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/search/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const baseSearchTool = createBaseTool({
 5 |   description: "Search the web for up-to-date information",
 6 |   inputSchema: z.object({
 7 |     query: z.string().min(1).max(100),
 8 |   }),
 9 |   outputSchema: z.object({
10 |     results: z.array(
11 |       z.object({
12 |         title: z.string().nullable(),
13 |         url: z.string().url(),
14 |         content: z.string(),
15 |         publishedDate: z.string().optional(),
16 |         image: z.string().optional(),
17 |         favicon: z.string().optional(),
18 |         score: z.number().optional(),
19 |         author: z.string().optional(),
20 |       }),
21 |     ),
22 |   }),
23 | });
24 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/search/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { Search } from "lucide-react";
 3 | 
 4 | import type { baseSearchTool } from "./base";
 5 | import type { ClientToolConfig } from "@/toolkits/types";
 6 | import { ToolCallDisplay, ResultsList } from "../../components";
 7 | 
 8 | export const exaSearchToolConfigClient: ClientToolConfig<
 9 |   typeof baseSearchTool.inputSchema.shape,
10 |   typeof baseSearchTool.outputSchema.shape
11 | > = {
12 |   CallComponent: ({ args }) => {
13 |     return (
14 |       <ToolCallDisplay
15 |         icon={Search}
16 |         label="Search Query"
17 |         value={args.query ?? "..."}
18 |       />
19 |     );
20 |   },
21 |   ResultComponent: ({ result }) => {
22 |     return (
23 |       <ResultsList
24 |         results={result.results}
25 |         title="Search Results"
26 |         emptyMessage="No results found"
27 |         linkText="Read full article →"
28 |       />
29 |     );
30 |   },
31 | };
32 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/search/server.ts:
--------------------------------------------------------------------------------
 1 | import { type baseSearchTool } from "./base";
 2 | import { env } from "@/env";
 3 | import { Exa } from "exa-js";
 4 | import type { ServerToolConfig } from "@/toolkits/types";
 5 | 
 6 | export const exaSearchToolConfigServer: ServerToolConfig<
 7 |   typeof baseSearchTool.inputSchema.shape,
 8 |   typeof baseSearchTool.outputSchema.shape
 9 | > = {
10 |   callback: async ({ query }) => {
11 |     if (!env.EXA_API_KEY) {
12 |       throw new Error("EXA_API_KEY is not set");
13 |     }
14 | 
15 |     const exa = new Exa(env.EXA_API_KEY);
16 | 
17 |     const { results } = await exa.searchAndContents(query, {
18 |       livecrawl: "always",
19 |       numResults: 3,
20 |     });
21 | 
22 |     return {
23 |       results: results.map((result) => ({
24 |         title: result.title,
25 |         url: result.url,
26 |         content: result.text.slice(0, 1000),
27 |         publishedDate: result.publishedDate,
28 |         image: result.image,
29 |         favicon: result.favicon,
30 |         score: result.score,
31 |         author: result.author,
32 |       })),
33 |     };
34 |   },
35 |   message:
36 |     "The user is shown the article in three cards. Do not list the sources again. Just give a 1-2 sentence summary response to their question and ask what else they would like to know.",
37 | };
38 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/tools.ts:
--------------------------------------------------------------------------------
 1 | export enum ExaTools {
 2 |   Search = "search",
 3 |   ResearchPaperSearch = "research-paper-search",
 4 |   CompanyResearch = "company-research",
 5 |   Crawling = "crawling",
 6 |   CompetitorFinder = "competitor-finder",
 7 |   LinkedinSearch = "linkedin-search",
 8 |   WikipediaSearch = "wikipedia-search-exa",
 9 |   GithubSearch = "github-search",
10 | }
11 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/wikipedia_search/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const baseWikipediaSearchTool = createBaseTool({
 5 |   description: "Search Wikipedia articles on specific topics",
 6 |   inputSchema: z.object({
 7 |     query: z.string().min(1).max(100),
 8 |   }),
 9 |   outputSchema: z.object({
10 |     results: z.array(
11 |       z.object({
12 |         title: z.string().nullable(),
13 |         url: z.string().url(),
14 |         content: z.string(),
15 |         publishedDate: z.string().optional(),
16 |         image: z.string().optional(),
17 |         favicon: z.string().optional(),
18 |         score: z.number().optional(),
19 |         author: z.string().optional(),
20 |       }),
21 |     ),
22 |   }),
23 | });
24 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/wikipedia_search/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { Globe } from "lucide-react";
 3 | import type { baseWikipediaSearchTool } from "./base";
 4 | import type { ClientToolConfig } from "@/toolkits/types";
 5 | import { ToolCallDisplay, ResultsList } from "../../components";
 6 | 
 7 | export const exaWikipediaSearchToolConfigClient: ClientToolConfig<
 8 |   typeof baseWikipediaSearchTool.inputSchema.shape,
 9 |   typeof baseWikipediaSearchTool.outputSchema.shape
10 | > = {
11 |   CallComponent: ({ args }) => {
12 |     return (
13 |       <ToolCallDisplay
14 |         icon={Globe}
15 |         label="Wikipedia Search"
16 |         value={args.query ?? "..."}
17 |       />
18 |     );
19 |   },
20 |   ResultComponent: ({ result }) => {
21 |     return (
22 |       <ResultsList
23 |         results={result.results}
24 |         title="Wikipedia Articles"
25 |         emptyMessage="No Wikipedia articles found"
26 |         linkText="Read article →"
27 |       />
28 |     );
29 |   },
30 | };
31 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/exa/tools/wikipedia_search/server.ts:
--------------------------------------------------------------------------------
 1 | import { type baseWikipediaSearchTool } from "./base";
 2 | import { env } from "@/env";
 3 | import { Exa } from "exa-js";
 4 | import type { ServerToolConfig } from "@/toolkits/types";
 5 | 
 6 | export const exaWikipediaSearchToolConfigServer: ServerToolConfig<
 7 |   typeof baseWikipediaSearchTool.inputSchema.shape,
 8 |   typeof baseWikipediaSearchTool.outputSchema.shape
 9 | > = {
10 |   callback: async ({ query }) => {
11 |     if (!env.EXA_API_KEY) {
12 |       throw new Error("EXA_API_KEY is not set");
13 |     }
14 | 
15 |     const exa = new Exa(env.EXA_API_KEY);
16 | 
17 |     const { results } = await exa.searchAndContents(query, {
18 |       livecrawl: "always",
19 |       numResults: 3,
20 |       includeDomains: ["wikipedia.org"],
21 |     });
22 | 
23 |     return {
24 |       results: results.map((result) => ({
25 |         title: result.title,
26 |         url: result.url,
27 |         content: result.text.slice(0, 1500),
28 |         publishedDate: result.publishedDate,
29 |         image: result.image,
30 |         favicon: result.favicon,
31 |         score: result.score,
32 |         author: result.author,
33 |       })),
34 |     };
35 |   },
36 |   message:
37 |     "The user is shown Wikipedia articles. Provide a concise summary of the encyclopedia information found.",
38 | };
39 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/github/base.ts:
--------------------------------------------------------------------------------
 1 | import type { ToolkitConfig } from "@/toolkits/types";
 2 | import { z } from "zod";
 3 | import { GithubTools } from "./tools";
 4 | import {
 5 |   searchRepositoriesTool,
 6 |   repoInfoTool,
 7 |   searchCodeTool,
 8 |   searchUsersTool,
 9 | } from "./tools";
10 | 
11 | export const githubParameters = z.object({});
12 | 
13 | export const baseGithubToolkitConfig: ToolkitConfig<
14 |   GithubTools,
15 |   typeof githubParameters.shape
16 | > = {
17 |   tools: {
18 |     [GithubTools.SearchRepos]: searchRepositoriesTool,
19 |     [GithubTools.RepoInfo]: repoInfoTool,
20 |     [GithubTools.SearchCode]: searchCodeTool,
21 |     [GithubTools.SearchUsers]: searchUsersTool,
22 |   },
23 |   parameters: githubParameters,
24 | };
25 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/github/components/user-avatar.tsx:
--------------------------------------------------------------------------------
 1 | import { memo } from "react";
 2 | 
 3 | import { SiGithub } from "@icons-pack/react-simple-icons";
 4 | 
 5 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
 6 | 
 7 | import { cn } from "@/lib/utils";
 8 | 
 9 | interface Props {
10 |   login: string;
11 |   className?: string;
12 |   style?: React.CSSProperties;
13 | }
14 | 
15 | export const GithubAvatar = memo(function GithubAvatar({
16 |   login,
17 |   className,
18 |   style,
19 | }: Props) {
20 |   return (
21 |     <Avatar className={cn("size-10 rounded-full", className)} style={style}>
22 |       <AvatarImage
23 |         src={`https://github.com/${login}.png`}
24 |         alt={login}
25 |         className="object-cover"
26 |       />
27 |       <AvatarFallback className="bg-muted dark:bg-muted p-1">
28 |         <SiGithub className="size-full opacity-60" />
29 |       </AvatarFallback>
30 |     </Avatar>
31 |   );
32 | });
33 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/github/lib/commits.ts:
--------------------------------------------------------------------------------
 1 | import { type Octokit } from "octokit";
 2 | 
 3 | interface CommitParams {
 4 |   since?: string;
 5 |   until?: string;
 6 |   author?: string;
 7 |   sha?: string;
 8 | }
 9 | 
10 | export const getAllCommits = async (
11 |   octokit: Octokit,
12 |   owner: string,
13 |   repo: string,
14 |   additionalParams?: CommitParams,
15 | ) => {
16 |   const firstPage = await octokit.rest.repos.listCommits({
17 |     ...additionalParams,
18 |     owner,
19 |     repo,
20 |     per_page: 100,
21 |     page: 1,
22 |   });
23 | 
24 |   const lastPageMatch = firstPage.headers.link?.match(/<([^>]+)>; rel="last"/);
25 |   const lastPageUrl = lastPageMatch?.[1];
26 |   const totalPages = lastPageUrl
27 |     ? parseInt(new URL(lastPageUrl).searchParams.get("page") ?? "1")
28 |     : 1;
29 | 
30 |   const remainingPages =
31 |     totalPages > 1
32 |       ? await Promise.all(
33 |           Array.from({ length: totalPages - 1 }, (_, i) =>
34 |             octokit.rest.repos.listCommits({
35 |               owner,
36 |               repo,
37 |               ...additionalParams,
38 |               per_page: 100,
39 |               page: i + 2,
40 |             }),
41 |           ),
42 |         )
43 |       : [];
44 | 
45 |   return [firstPage.data, ...remainingPages.map((page) => page.data)].flat();
46 | };
47 | 
48 | export const getTotalCommits = async (
49 |   octokit: Octokit,
50 |   owner: string,
51 |   repo: string,
52 |   additionalParams?: CommitParams,
53 | ) => {
54 |   const firstPage = await octokit.rest.repos.listCommits({
55 |     owner,
56 |     repo,
57 |     per_page: 1,
58 |     ...additionalParams,
59 |   });
60 | 
61 |   const lastPageMatch = firstPage.headers.link?.match(/<([^>]+)>; rel="last"/);
62 |   const lastPageUrl = lastPageMatch?.[1];
63 |   const totalPages = lastPageUrl
64 |     ? parseInt(new URL(lastPageUrl).searchParams.get("page") ?? "1")
65 |     : 1;
66 | 
67 |   return totalPages;
68 | };
69 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/github/lib/prs.ts:
--------------------------------------------------------------------------------
 1 | import { type Octokit } from "octokit";
 2 | 
 3 | export const getTotalPrs = async (octokit: Octokit, query: string) => {
 4 |   const {
 5 |     data: { total_count },
 6 |   } = await octokit.rest.search.issuesAndPullRequests({
 7 |     q: query,
 8 |     per_page: 1,
 9 |   });
10 | 
11 |   return total_count;
12 | };
13 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/github/tools/client.ts:
--------------------------------------------------------------------------------
1 | export * from "./search/client";
2 | export * from "./repo/client";
3 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/github/tools/index.ts:
--------------------------------------------------------------------------------
 1 | export enum GithubTools {
 2 |   SearchRepos = "search-repos",
 3 |   SearchCode = "search-code",
 4 |   SearchUsers = "search-users",
 5 |   RepoInfo = "repo-info",
 6 | }
 7 | 
 8 | export * from "./search/base";
 9 | export * from "./repo/base";
10 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/github/tools/server.ts:
--------------------------------------------------------------------------------
1 | export * from "./repo/server";
2 | export * from "./search/server";
3 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/base.ts:
--------------------------------------------------------------------------------
 1 | import type { ToolkitConfig } from "@/toolkits/types";
 2 | import { z } from "zod";
 3 | import { GoogleCalendarTools } from "./tools";
 4 | import {
 5 |   listCalendarsTool,
 6 |   getCalendarTool,
 7 |   listEventsTool,
 8 |   getEventTool,
 9 |   searchEventsTool,
10 | } from "./tools";
11 | 
12 | export const googleCalendarParameters = z.object({});
13 | 
14 | export const baseGoogleCalendarToolkitConfig: ToolkitConfig<
15 |   GoogleCalendarTools,
16 |   typeof googleCalendarParameters.shape
17 | > = {
18 |   tools: {
19 |     [GoogleCalendarTools.ListCalendars]: listCalendarsTool,
20 |     [GoogleCalendarTools.GetCalendar]: getCalendarTool,
21 |     [GoogleCalendarTools.ListEvents]: listEventsTool,
22 |     [GoogleCalendarTools.GetEvent]: getEventTool,
23 |     [GoogleCalendarTools.SearchEvents]: searchEventsTool,
24 |   },
25 |   parameters: googleCalendarParameters,
26 | };
27 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/components/calendar-card.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { Badge } from "@/components/ui/badge";
 3 | import { HStack, VStack } from "@/components/ui/stack";
 4 | import { Calendar, Clock, Shield } from "lucide-react";
 5 | 
 6 | interface CalendarCardProps {
 7 |   calendar: {
 8 |     id: string;
 9 |     summary: string;
10 |     description?: string;
11 |     timeZone: string;
12 |     accessRole: string;
13 |     primary?: boolean;
14 |     selected?: boolean;
15 |   };
16 |   showDetails?: boolean;
17 | }
18 | 
19 | export const CalendarCard: React.FC<CalendarCardProps> = ({
20 |   calendar,
21 |   showDetails = false,
22 | }) => {
23 |   return (
24 |     <VStack className="w-full items-start gap-1 border-b pb-2 last:border-b-0 last:pb-0">
25 |       <HStack className="w-full items-center gap-2">
26 |         <Calendar className="text-primary size-4" />
27 |         <VStack className="flex-1 items-start gap-0">
28 |           <h3 className="text-sm font-medium">{calendar.summary}</h3>
29 |           {calendar.description && (
30 |             <p className="text-muted-foreground line-clamp-2 text-xs">
31 |               {calendar.description}
32 |             </p>
33 |           )}
34 |         </VStack>
35 |         {calendar.primary && (
36 |           <Badge variant="default" className="text-xs">
37 |             Primary
38 |           </Badge>
39 |         )}
40 |       </HStack>
41 | 
42 |       {showDetails && (
43 |         <HStack className="text-muted-foreground items-center gap-4 text-xs">
44 |           <HStack className="items-center gap-1">
45 |             <Clock className="size-3" />
46 |             <span>{calendar.timeZone}</span>
47 |           </HStack>
48 |           <HStack className="items-center gap-1">
49 |             <Shield className="size-3" />
50 |             <span>{calendar.accessRole}</span>
51 |           </HStack>
52 |         </HStack>
53 |       )}
54 |     </VStack>
55 |   );
56 | };
57 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/components/tool-call.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { HStack, VStack } from "@/components/ui/stack";
 3 | import { Search } from "lucide-react";
 4 | 
 5 | interface ToolCallComponentProps {
 6 |   action: string;
 7 |   primaryText: string;
 8 |   secondaryText?: string;
 9 |   icon?: React.ComponentType<{ className?: string }>;
10 | }
11 | 
12 | export const ToolCallComponent: React.FC<ToolCallComponentProps> = ({
13 |   action,
14 |   primaryText,
15 |   secondaryText,
16 |   icon: Icon = Search,
17 | }) => {
18 |   return (
19 |     <HStack className="gap-2">
20 |       <Icon className="text-muted-foreground size-4" />
21 |       <VStack className="items-start gap-0">
22 |         <span className="text-muted-foreground text-xs font-medium">
23 |           {action}
24 |         </span>
25 |         <span className="text-sm">{primaryText}</span>
26 |         {secondaryText && (
27 |           <span className="text-muted-foreground text-xs">{secondaryText}</span>
28 |         )}
29 |       </VStack>
30 |     </HStack>
31 |   );
32 | };
33 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/tools/client.ts:
--------------------------------------------------------------------------------
1 | export { googleCalendarListCalendarsToolConfigClient } from "./list-calendars/client";
2 | export { googleCalendarGetCalendarToolConfigClient } from "./get-calendar/client";
3 | export { googleCalendarListEventsToolConfigClient } from "./list-events/client";
4 | export { googleCalendarGetEventToolConfigClient } from "./get-event/client";
5 | export { googleCalendarSearchEventsToolConfigClient } from "./search-events/client";
6 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/tools/get-calendar/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const getCalendarTool = createBaseTool({
 5 |   description: "Get details of a specific calendar by ID",
 6 |   inputSchema: z.object({
 7 |     calendarId: z.string().describe("The ID of the calendar to retrieve"),
 8 |   }),
 9 |   outputSchema: z.object({
10 |     id: z.string().describe("The calendar ID"),
11 |     summary: z.string().describe("The calendar title"),
12 |     description: z.string().optional().describe("The calendar description"),
13 |     timeZone: z.string().describe("The time zone of the calendar"),
14 |     colorId: z.string().optional().describe("The color ID of the calendar"),
15 |     backgroundColor: z
16 |       .string()
17 |       .optional()
18 |       .describe("The background color of the calendar"),
19 |     foregroundColor: z
20 |       .string()
21 |       .optional()
22 |       .describe("The foreground color of the calendar"),
23 |     selected: z
24 |       .boolean()
25 |       .optional()
26 |       .describe("Whether the calendar is selected"),
27 |     accessRole: z.string().describe("The access role for the calendar"),
28 |     primary: z
29 |       .boolean()
30 |       .optional()
31 |       .describe("Whether this is the primary calendar"),
32 |     location: z.string().optional().describe("The location of the calendar"),
33 |   }),
34 | });
35 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/tools/get-calendar/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { type getCalendarTool } from "./base";
 3 | import type { ClientToolConfig } from "@/toolkits/types";
 4 | import { VStack } from "@/components/ui/stack";
 5 | import { CalendarCard } from "../../components/calendar-card";
 6 | import { ToolCallComponent } from "../../components/tool-call";
 7 | 
 8 | export const googleCalendarGetCalendarToolConfigClient: ClientToolConfig<
 9 |   typeof getCalendarTool.inputSchema.shape,
10 |   typeof getCalendarTool.outputSchema.shape
11 | > = {
12 |   CallComponent: ({ args }) => {
13 |     return (
14 |       <ToolCallComponent
15 |         action="Fetching Calendar"
16 |         primaryText={args.calendarId ?? ""}
17 |       />
18 |     );
19 |   },
20 |   ResultComponent: ({ result }) => {
21 |     return (
22 |       <VStack className="items-start gap-2">
23 |         <h3 className="text-sm font-medium">Calendar Details</h3>
24 |         <CalendarCard calendar={result} showDetails={true} />
25 |       </VStack>
26 |     );
27 |   },
28 | };
29 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/tools/get-calendar/server.ts:
--------------------------------------------------------------------------------
 1 | import { type getCalendarTool } from "./base";
 2 | import { google } from "googleapis";
 3 | import type { ServerToolConfig } from "@/toolkits/types";
 4 | 
 5 | export const googleCalendarGetCalendarToolConfigServer = (
 6 |   accessToken: string,
 7 | ): ServerToolConfig<
 8 |   typeof getCalendarTool.inputSchema.shape,
 9 |   typeof getCalendarTool.outputSchema.shape
10 | > => {
11 |   return {
12 |     callback: async ({ calendarId }) => {
13 |       const auth = new google.auth.OAuth2();
14 |       auth.setCredentials({ access_token: accessToken });
15 | 
16 |       const calendar = google.calendar({ version: "v3", auth });
17 | 
18 |       const response = await calendar.calendars.get({
19 |         calendarId,
20 |       });
21 | 
22 |       const cal = response.data;
23 | 
24 |       return {
25 |         id: cal.id!,
26 |         summary: cal.summary!,
27 |         description: cal.description ?? undefined,
28 |         timeZone: cal.timeZone!,
29 |         colorId: cal.etag ?? undefined,
30 |         backgroundColor: cal.etag ?? undefined,
31 |         foregroundColor: cal.etag ?? undefined,
32 |         selected: undefined,
33 |         accessRole: "owner",
34 |         primary: false,
35 |         location: cal.location ?? undefined,
36 |       };
37 |     },
38 |   };
39 | };
40 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/tools/get-event/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { type getEventTool } from "./base";
 3 | import type { ClientToolConfig } from "@/toolkits/types";
 4 | import { VStack } from "@/components/ui/stack";
 5 | import { EventCard } from "../../components/event-card";
 6 | import { ToolCallComponent } from "../../components/tool-call";
 7 | 
 8 | export const googleCalendarGetEventToolConfigClient: ClientToolConfig<
 9 |   typeof getEventTool.inputSchema.shape,
10 |   typeof getEventTool.outputSchema.shape
11 | > = {
12 |   CallComponent: ({ args }) => {
13 |     return (
14 |       <ToolCallComponent
15 |         action="Fetching Event"
16 |         primaryText={args.eventId ?? ""}
17 |         secondaryText={`From calendar: ${args.calendarId}`}
18 |       />
19 |     );
20 |   },
21 |   ResultComponent: ({ result }) => {
22 |     return (
23 |       <VStack className="items-start gap-2">
24 |         <h3 className="text-sm font-medium">Event Details</h3>
25 |         <EventCard event={result} showDetails={true} />
26 |       </VStack>
27 |     );
28 |   },
29 | };
30 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/tools/index.ts:
--------------------------------------------------------------------------------
 1 | export enum GoogleCalendarTools {
 2 |   ListCalendars = "list-calendars",
 3 |   GetCalendar = "get-calendar",
 4 |   ListEvents = "list-events",
 5 |   GetEvent = "get-event",
 6 |   SearchEvents = "search-events",
 7 | }
 8 | 
 9 | export { listCalendarsTool } from "./list-calendars/base";
10 | export { getCalendarTool } from "./get-calendar/base";
11 | export { listEventsTool } from "./list-events/base";
12 | export { getEventTool } from "./get-event/base";
13 | export { searchEventsTool } from "./search-events/base";
14 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/tools/list-calendars/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const listCalendarsTool = createBaseTool({
 5 |   description: "List all calendars for the authenticated user",
 6 |   inputSchema: z.object({
 7 |     maxResults: z
 8 |       .number()
 9 |       .describe("Maximum number of calendars to return (default: 100)"),
10 |     pageToken: z
11 |       .string()
12 |       .describe("Token for pagination (use empty string for first page)"),
13 |   }),
14 |   outputSchema: z.object({
15 |     calendars: z.array(
16 |       z.object({
17 |         id: z.string().describe("The calendar ID"),
18 |         summary: z.string().describe("The calendar title"),
19 |         description: z.string().optional().describe("The calendar description"),
20 |         timeZone: z.string().describe("The time zone of the calendar"),
21 |         colorId: z.string().optional().describe("The color ID of the calendar"),
22 |         backgroundColor: z
23 |           .string()
24 |           .optional()
25 |           .describe("The background color of the calendar"),
26 |         foregroundColor: z
27 |           .string()
28 |           .optional()
29 |           .describe("The foreground color of the calendar"),
30 |         selected: z
31 |           .boolean()
32 |           .optional()
33 |           .describe("Whether the calendar is selected"),
34 |         accessRole: z.string().describe("The access role for the calendar"),
35 |         primary: z
36 |           .boolean()
37 |           .optional()
38 |           .describe("Whether this is the primary calendar"),
39 |       }),
40 |     ),
41 |     nextPageToken: z
42 |       .string()
43 |       .optional()
44 |       .describe("Token for next page of results"),
45 |   }),
46 | });
47 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/tools/list-calendars/server.ts:
--------------------------------------------------------------------------------
 1 | import { type listCalendarsTool } from "./base";
 2 | import { google } from "googleapis";
 3 | import type { ServerToolConfig } from "@/toolkits/types";
 4 | 
 5 | export const googleCalendarListCalendarsToolConfigServer = (
 6 |   accessToken: string,
 7 | ): ServerToolConfig<
 8 |   typeof listCalendarsTool.inputSchema.shape,
 9 |   typeof listCalendarsTool.outputSchema.shape
10 | > => {
11 |   return {
12 |     callback: async ({ maxResults, pageToken }) => {
13 |       const auth = new google.auth.OAuth2();
14 |       auth.setCredentials({ access_token: accessToken });
15 | 
16 |       const calendar = google.calendar({ version: "v3", auth });
17 | 
18 |       const response = await calendar.calendarList.list({
19 |         maxResults: maxResults,
20 |         pageToken: pageToken,
21 |       });
22 | 
23 |       const calendars =
24 |         response.data.items?.map((cal) => ({
25 |           id: cal.id!,
26 |           summary: cal.summary!,
27 |           description: cal.description ?? undefined,
28 |           timeZone: cal.timeZone!,
29 |           colorId: cal.colorId ?? undefined,
30 |           backgroundColor: cal.backgroundColor ?? undefined,
31 |           foregroundColor: cal.foregroundColor ?? undefined,
32 |           selected: cal.selected ?? undefined,
33 |           accessRole: cal.accessRole!,
34 |           primary: cal.primary ?? undefined,
35 |         })) ?? [];
36 | 
37 |       return {
38 |         calendars,
39 |         nextPageToken: response.data.nextPageToken ?? undefined,
40 |       };
41 |     },
42 |   };
43 | };
44 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/tools/search-events/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { type searchEventsTool } from "./base";
 3 | import type { ClientToolConfig } from "@/toolkits/types";
 4 | import { HStack, VStack } from "@/components/ui/stack";
 5 | import { EventCard } from "../../components/event-card";
 6 | import { ToolCallComponent } from "../../components/tool-call";
 7 | 
 8 | export const googleCalendarSearchEventsToolConfigClient: ClientToolConfig<
 9 |   typeof searchEventsTool.inputSchema.shape,
10 |   typeof searchEventsTool.outputSchema.shape
11 | > = {
12 |   CallComponent: ({ args }) => {
13 |     return (
14 |       <ToolCallComponent
15 |         action="Searching Events"
16 |         primaryText={`"${args.query}" (${args.maxResults ?? 5} events)`}
17 |         secondaryText={`In calendar: ${args.calendarId}`}
18 |       />
19 |     );
20 |   },
21 |   ResultComponent: ({ result }) => {
22 |     const { events, timeZone } = result;
23 | 
24 |     if (events.length === 0) {
25 |       return (
26 |         <p className="text-muted-foreground text-sm">
27 |           No events found matching your search
28 |         </p>
29 |       );
30 |     }
31 | 
32 |     return (
33 |       <VStack className="items-start gap-2">
34 |         <HStack className="items-center justify-between">
35 |           <h3 className="text-sm font-medium">
36 |             Search Results ({events.length})
37 |           </h3>
38 |           {timeZone && (
39 |             <span className="text-muted-foreground text-xs">{timeZone}</span>
40 |           )}
41 |         </HStack>
42 | 
43 |         <div className="flex w-full flex-col gap-2">
44 |           {events.map((event) => (
45 |             <EventCard key={event.id} event={event} showDetails={true} />
46 |           ))}
47 |         </div>
48 |       </VStack>
49 |     );
50 |   },
51 | };
52 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-calendar/tools/server.ts:
--------------------------------------------------------------------------------
1 | export { googleCalendarListCalendarsToolConfigServer } from "./list-calendars/server";
2 | export { googleCalendarGetCalendarToolConfigServer } from "./get-calendar/server";
3 | export { googleCalendarListEventsToolConfigServer } from "./list-events/server";
4 | export { googleCalendarGetEventToolConfigServer } from "./get-event/server";
5 | export { googleCalendarSearchEventsToolConfigServer } from "./search-events/server";
6 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-drive/base.ts:
--------------------------------------------------------------------------------
 1 | import type { ToolkitConfig } from "@/toolkits/types";
 2 | import { z } from "zod";
 3 | import { GoogleDriveTools } from "./tools";
 4 | import { searchFilesTool } from "./tools/search-files/base";
 5 | import { readFileTool } from "./tools/read-file/base";
 6 | 
 7 | export const googleDriveParameters = z.object({});
 8 | 
 9 | export const baseGoogleDriveToolkitConfig: ToolkitConfig<
10 |   GoogleDriveTools,
11 |   typeof googleDriveParameters.shape
12 | > = {
13 |   tools: {
14 |     [GoogleDriveTools.SearchFiles]: searchFilesTool,
15 |     [GoogleDriveTools.ReadFile]: readFileTool,
16 |   },
17 |   parameters: googleDriveParameters,
18 | };
19 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-drive/components/tool-call.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { HStack, VStack } from "@/components/ui/stack";
 3 | import { Search } from "lucide-react";
 4 | 
 5 | interface ToolCallComponentProps {
 6 |   action: string;
 7 |   primaryText: string;
 8 |   secondaryText?: string;
 9 |   icon?: React.ComponentType<{ className?: string }>;
10 | }
11 | 
12 | export const ToolCallComponent: React.FC<ToolCallComponentProps> = ({
13 |   action,
14 |   primaryText,
15 |   secondaryText,
16 |   icon: Icon = Search,
17 | }) => {
18 |   return (
19 |     <HStack className="gap-2">
20 |       <Icon className="text-muted-foreground size-4" />
21 |       <VStack className="items-start gap-0">
22 |         <span className="text-muted-foreground text-xs font-medium">
23 |           {action}
24 |         </span>
25 |         <span className="text-sm">{primaryText}</span>
26 |         {secondaryText && (
27 |           <span className="text-muted-foreground text-xs">{secondaryText}</span>
28 |         )}
29 |       </VStack>
30 |     </HStack>
31 |   );
32 | };
33 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-drive/tools/client.ts:
--------------------------------------------------------------------------------
1 | export { googleDriveSearchFilesToolConfigClient } from "./search-files/client";
2 | export { googleDriveReadFileToolConfigClient } from "./read-file/client";
3 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-drive/tools/index.ts:
--------------------------------------------------------------------------------
1 | export enum GoogleDriveTools {
2 |   SearchFiles = "search-files",
3 |   ReadFile = "read-file",
4 | }
5 | 
6 | export { searchFilesTool } from "./search-files/base";
7 | export { readFileTool } from "./read-file/base";
8 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-drive/tools/read-file/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const readFileTool = createBaseTool({
 5 |   description: "Read the contents of a file from Google Drive",
 6 |   inputSchema: z.object({
 7 |     fileId: z.string().describe("ID of the file to read"),
 8 |     exportFormat: z
 9 |       .string()
10 |       .describe(
11 |         "Export format for Google Workspace files (e.g., 'text/plain', 'text/csv', 'text/markdown') (leave blank for auto-detection)",
12 |       ),
13 |   }),
14 |   outputSchema: z.object({
15 |     content: z.string().describe("File content as text"),
16 |     mimeType: z.string().describe("MIME type of the content"),
17 |     fileName: z.string().describe("Name of the file"),
18 |     size: z.number().optional().describe("Size of the content in bytes"),
19 |     encoding: z.string().optional().describe("Content encoding used"),
20 |   }),
21 | });
22 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/google-drive/tools/server.ts:
--------------------------------------------------------------------------------
1 | export { googleDriveSearchFilesToolConfigServer } from "./search-files/server";
2 | export { googleDriveReadFileToolConfigServer } from "./read-file/server";
3 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/image/base.ts:
--------------------------------------------------------------------------------
 1 | import type { ToolkitConfig } from "@/toolkits/types";
 2 | import { ImageTools } from "./tools/tools";
 3 | import { baseGenerateTool } from "./tools/generate/base";
 4 | import { z } from "zod";
 5 | import { allImageModels } from "@/ai/models/all";
 6 | import type { ImageModelProvider } from "@/ai/types";
 7 | 
 8 | export const imageParameters = z.object({
 9 |   model: z.enum(
10 |     allImageModels.map((model) => `${model.provider}:${model.modelId}`) as [
11 |       `${ImageModelProvider}:${string}`,
12 |       ...`${ImageModelProvider}:${string}`[],
13 |     ],
14 |   ),
15 | });
16 | 
17 | export const baseImageToolkitConfig: ToolkitConfig<
18 |   ImageTools,
19 |   typeof imageParameters.shape
20 | > = {
21 |   tools: {
22 |     [ImageTools.Generate]: baseGenerateTool,
23 |   },
24 |   parameters: imageParameters,
25 | };
26 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/image/server.ts:
--------------------------------------------------------------------------------
 1 | import { createServerToolkit } from "@/toolkits/create-toolkit";
 2 | import { baseImageToolkitConfig } from "./base";
 3 | import { generateToolConfigServer } from "./tools/generate/server";
 4 | import { ImageTools } from "./tools/tools";
 5 | 
 6 | export const imageToolkitServer = createServerToolkit(
 7 |   baseImageToolkitConfig,
 8 |   `You have access to the Image toolkit for AI-powered image generation. This toolkit contains:
 9 | 
10 | - **Generate**: Create high-quality images from text descriptions using advanced AI models
11 | 
12 | **Usage Guidelines:**
13 | 1. Use detailed, descriptive prompts for better image quality
14 | 2. Include style specifications (e.g., "photorealistic", "digital art", "cartoon style")
15 | 3. Specify composition details (e.g., "close-up", "wide shot", "bird's eye view")
16 | 4. Consider lighting and mood descriptions for enhanced results
17 | 5. For multiple related images, maintain consistent style and theme across generations
18 | 
19 | This tool is perfect for creating visual content, illustrations, concept art, and any visual materials needed for your projects.`,
20 |   async (parameters) => {
21 |     return {
22 |       [ImageTools.Generate]: generateToolConfigServer(parameters),
23 |     };
24 |   },
25 | );
26 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/image/tools/generate/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | const inputSchema = z.object({
 5 |   prompt: z.string().min(1).max(100).describe("The image generation prompt"),
 6 | });
 7 | 
 8 | const outputSchema = z.object({
 9 |   url: z.string().describe("The URL of the generated image"),
10 | });
11 | 
12 | export const baseGenerateTool = createBaseTool({
13 |   description: "Generate an image",
14 |   inputSchema,
15 |   outputSchema,
16 | });
17 | 
18 | export type GenerateToolArgs = z.infer<typeof inputSchema>;
19 | export type GenerateToolResult = z.infer<typeof outputSchema>;
20 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/image/tools/generate/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | 
 3 | import Image from "next/image";
 4 | 
 5 | import type { baseGenerateTool } from "./base";
 6 | 
 7 | import type { ClientToolConfig } from "@/toolkits/types";
 8 | 
 9 | export const generateToolConfigClient: ClientToolConfig<
10 |   typeof baseGenerateTool.inputSchema.shape,
11 |   typeof baseGenerateTool.outputSchema.shape
12 | > = {
13 |   CallComponent: ({ args }) => {
14 |     return <span className="opacity/60 text-sm font-light">{args.prompt}</span>;
15 |   },
16 |   ResultComponent: ({ result }) => {
17 |     return (
18 |       <Image src={result.url} alt="Generated Image" width={500} height={500} />
19 |     );
20 |   },
21 | };
22 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/image/tools/generate/server.ts:
--------------------------------------------------------------------------------
 1 | import { experimental_generateImage as generateImage } from "ai";
 2 | 
 3 | import { type baseGenerateTool } from "./base";
 4 | import type { ServerToolConfig } from "@/toolkits/types";
 5 | import { put } from "@vercel/blob";
 6 | import { api } from "@/trpc/server";
 7 | import type { imageParameters } from "../../base";
 8 | import type z from "zod";
 9 | import { registry } from "@/ai/registry";
10 | 
11 | export const generateToolConfigServer = (
12 |   parameters: z.infer<typeof imageParameters>,
13 | ): ServerToolConfig<
14 |   typeof baseGenerateTool.inputSchema.shape,
15 |   typeof baseGenerateTool.outputSchema.shape
16 | > => {
17 |   return {
18 |     callback: async ({ prompt }) => {
19 |       const { image } = await generateImage({
20 |         model: registry.imageModel(parameters.model),
21 |         prompt,
22 |       });
23 | 
24 |       if (!image) {
25 |         console.error("No image generated");
26 |         throw new Error("No image generated");
27 |       }
28 | 
29 |       const imageId = crypto.randomUUID();
30 | 
31 |       const file = new File(
32 |         [image.uint8Array],
33 |         `images/${imageId}.${image.mimeType.split("/")[1]}`,
34 |         {
35 |           type: image.mimeType,
36 |         },
37 |       );
38 | 
39 |       const { url: imageUrl } = await put(file.name, file, {
40 |         access: "public",
41 |       });
42 | 
43 |       await api.images.createImage({
44 |         url: imageUrl,
45 |         contentType: image.mimeType,
46 |       });
47 | 
48 |       return { url: imageUrl };
49 |     },
50 |   };
51 | };
52 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/image/tools/tools.ts:
--------------------------------------------------------------------------------
1 | export enum ImageTools {
2 |   Generate = "generate",
3 | }
4 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/mem0/base.ts:
--------------------------------------------------------------------------------
 1 | import type { ToolkitConfig } from "@/toolkits/types";
 2 | import { baseAddMemoryTool } from "./tools/add_memory/base";
 3 | import { baseSearchMemoriesTool } from "./tools/search_memories/base";
 4 | import { Mem0Tools } from "./tools/tools";
 5 | import { z } from "zod";
 6 | 
 7 | export const mem0Parameters = z.object({});
 8 | 
 9 | export const baseMem0ToolkitConfig: ToolkitConfig<
10 |   Mem0Tools,
11 |   typeof mem0Parameters.shape
12 | > = {
13 |   tools: {
14 |     [Mem0Tools.AddMemory]: baseAddMemoryTool,
15 |     [Mem0Tools.SearchMemories]: baseSearchMemoriesTool,
16 |   },
17 |   parameters: mem0Parameters,
18 | };
19 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/mem0/client.tsx:
--------------------------------------------------------------------------------
 1 | import { Brain } from "lucide-react";
 2 | 
 3 | import { Mem0Tools } from "./tools/tools";
 4 | import { createClientToolkit } from "@/toolkits/create-toolkit";
 5 | import { mem0AddMemoryToolConfigClient } from "./tools/add_memory/client";
 6 | import { mem0SearchMemoriesToolConfigClient } from "./tools/search_memories/client";
 7 | import { baseMem0ToolkitConfig } from "./base";
 8 | import { ToolkitGroups } from "@/toolkits/types";
 9 | 
10 | export const mem0ClientToolkit = createClientToolkit(
11 |   baseMem0ToolkitConfig,
12 |   {
13 |     name: "Memory",
14 |     description: "Personalize your experience with each conversation.",
15 |     icon: ({ className }) => <Brain className={className} />,
16 |     form: null,
17 |     type: ToolkitGroups.Native,
18 |   },
19 |   {
20 |     [Mem0Tools.AddMemory]: mem0AddMemoryToolConfigClient,
21 |     [Mem0Tools.SearchMemories]: mem0SearchMemoriesToolConfigClient,
22 |   },
23 | );
24 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/mem0/components/tool-call-display.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { type LucideIcon } from "lucide-react";
 3 | import { HStack } from "@/components/ui/stack";
 4 | 
 5 | interface ToolCallDisplayProps {
 6 |   icon: LucideIcon;
 7 |   label: string;
 8 |   value: string;
 9 | }
10 | 
11 | export function ToolCallDisplay({
12 |   icon: Icon,
13 |   label,
14 |   value,
15 | }: ToolCallDisplayProps) {
16 |   return (
17 |     <HStack className="text-muted-foreground flex items-center">
18 |       <Icon className="size-4 shrink-0" />
19 |       <span className="text-sm">
20 |         <strong>{label}:</strong> {value}
21 |       </span>
22 |     </HStack>
23 |   );
24 | }
25 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/mem0/tools/add_memory/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const baseAddMemoryTool = createBaseTool({
 5 |   description:
 6 |     "Add a new memory. This method is called every time the user informs anything about themselves, their preferences, or anything that has any relevant information which can be useful in the future conversation. This can also be called when the user asks you to remember something.",
 7 |   inputSchema: z.object({
 8 |     content: z.string().describe("The content to store in memory"),
 9 |   }),
10 |   outputSchema: z.object({
11 |     success: z.boolean(),
12 |     content: z.string(),
13 |   }),
14 | });
15 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/mem0/tools/add_memory/client.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { Plus } from "lucide-react";
 3 | 
 4 | import type { baseAddMemoryTool } from "./base";
 5 | import type { ClientToolConfig } from "@/toolkits/types";
 6 | import { ToolCallDisplay } from "../../components/tool-call-display";
 7 | import { HStack, VStack } from "@/components/ui/stack";
 8 | 
 9 | export const mem0AddMemoryToolConfigClient: ClientToolConfig<
10 |   typeof baseAddMemoryTool.inputSchema.shape,
11 |   typeof baseAddMemoryTool.outputSchema.shape
12 | > = {
13 |   CallComponent: ({ args }) => {
14 |     return (
15 |       <div className="space-y-2">
16 |         <ToolCallDisplay
17 |           icon={Plus}
18 |           label="Adding Memory"
19 |           value={args.content ?? "..."}
20 |         />
21 |       </div>
22 |     );
23 |   },
24 |   ResultComponent: ({ result }) => {
25 |     return (
26 |       <div className="flex items-center gap-2">
27 |         {result.success ? (
28 |           <HStack className="text-primary flex items-center gap-2">
29 |             <Plus className="size-4 shrink-0" />
30 |             <VStack className="items-start gap-0">
31 |               <span className="text-xs font-medium">Memory Added</span>
32 |               <span className="text-muted-foreground text-sm">
33 |                 {result.content}
34 |               </span>
35 |             </VStack>
36 |           </HStack>
37 |         ) : (
38 |           <HStack className="text-destructive flex items-center gap-2">
39 |             <Plus className="size-4" />
40 |             <VStack className="items-start gap-0">
41 |               <span className="text-xs font-medium">Failed to add memory</span>
42 |               <span className="text-muted-foreground text-sm">
43 |                 {result.content}
44 |               </span>
45 |             </VStack>
46 |           </HStack>
47 |         )}
48 |       </div>
49 |     );
50 |   },
51 | };
52 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/mem0/tools/add_memory/server.ts:
--------------------------------------------------------------------------------
 1 | import { type baseAddMemoryTool } from "./base";
 2 | import { env } from "@/env";
 3 | import { MemoryClient, type Message } from "mem0ai";
 4 | import type { ServerToolConfig } from "@/toolkits/types";
 5 | 
 6 | export const mem0AddMemoryToolConfigServer = (
 7 |   userId: string,
 8 | ): ServerToolConfig<
 9 |   typeof baseAddMemoryTool.inputSchema.shape,
10 |   typeof baseAddMemoryTool.outputSchema.shape
11 | > => ({
12 |   callback: async ({ content }) => {
13 |     if (!env.MEM0_API_KEY) {
14 |       throw new Error("MEM0_API_KEY is not set");
15 |     }
16 | 
17 |     const memoryClient = new MemoryClient({ apiKey: env.MEM0_API_KEY });
18 | 
19 |     try {
20 |       const messages = [
21 |         { role: "assistant", content: "Memory storage system" },
22 |         { role: "user", content },
23 |       ] as Message[];
24 | 
25 |       await memoryClient.add(messages, { user_id: userId });
26 | 
27 |       return {
28 |         success: true,
29 |         content,
30 |       };
31 |     } catch (error) {
32 |       console.error("Error adding memory:", error);
33 |       return {
34 |         success: false,
35 |         content: `Failed to add memory: ${error instanceof Error ? error.message : String(error)}`,
36 |       };
37 |     }
38 |   },
39 |   message:
40 |     "Memory has been successfully stored and will be used to provide more personalized responses in future conversations.",
41 | });
42 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/mem0/tools/search_memories/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | 
 4 | export const baseSearchMemoriesTool = createBaseTool({
 5 |   description:
 6 |     "Search through stored memories. This method is called ANYTIME the user asks anything.",
 7 |   inputSchema: z.object({
 8 |     query: z
 9 |       .string()
10 |       .describe(
11 |         "The search query. This is the query that the user has asked for. Example: 'What did I tell you about the weather last week?' or 'What did I tell you about my friend John?'",
12 |       ),
13 |   }),
14 |   outputSchema: z.object({
15 |     memories: z.array(
16 |       z.object({
17 |         memory: z.string(),
18 |         score: z.number(),
19 |         metadata: z.record(z.any()).optional(),
20 |       }),
21 |     ),
22 |     query: z.string(),
23 |   }),
24 | });
25 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/mem0/tools/search_memories/server.ts:
--------------------------------------------------------------------------------
 1 | import { type baseSearchMemoriesTool } from "./base";
 2 | import { env } from "@/env";
 3 | import { MemoryClient } from "mem0ai";
 4 | import type { ServerToolConfig } from "@/toolkits/types";
 5 | 
 6 | export const mem0SearchMemoriesToolConfigServer = (
 7 |   userId: string,
 8 | ): ServerToolConfig<
 9 |   typeof baseSearchMemoriesTool.inputSchema.shape,
10 |   typeof baseSearchMemoriesTool.outputSchema.shape
11 | > => ({
12 |   callback: async ({ query }) => {
13 |     if (!env.MEM0_API_KEY) {
14 |       throw new Error("MEM0_API_KEY is not set");
15 |     }
16 | 
17 |     const memoryClient = new MemoryClient({ apiKey: env.MEM0_API_KEY });
18 | 
19 |     try {
20 |       const results = await memoryClient.search(query, { user_id: userId });
21 | 
22 |       return {
23 |         memories: results.map((result) => ({
24 |           memory: result.memory ?? "",
25 |           score: result.score ?? 0,
26 |           metadata: result.metadata as object,
27 |         })),
28 |         query,
29 |       };
30 |     } catch (error) {
31 |       console.error("Error searching memories:", error);
32 |       return {
33 |         memories: [],
34 |         query,
35 |       };
36 |     }
37 |   },
38 |   message:
39 |     "These are the relevant memories found. Use this information to provide contextual and personalized responses to the user.",
40 | });
41 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/mem0/tools/tools.ts:
--------------------------------------------------------------------------------
1 | export enum Mem0Tools {
2 |   AddMemory = "add-memory",
3 |   SearchMemories = "search-memories",
4 | }
5 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/base.ts:
--------------------------------------------------------------------------------
 1 | import type { ToolkitConfig } from "@/toolkits/types";
 2 | import { z } from "zod";
 3 | import { NotionTools } from "./tools";
 4 | import {
 5 |   listDatabasesTool,
 6 |   queryDatabaseTool,
 7 |   createDatabaseTool,
 8 |   getPageTool,
 9 |   searchPagesTool,
10 |   createPageTool,
11 |   getBlocksTool,
12 |   appendBlocksTool,
13 |   listUsersTool,
14 | } from "./tools";
15 | 
16 | export const notionParameters = z.object({});
17 | 
18 | export const baseNotionToolkitConfig: ToolkitConfig<
19 |   NotionTools,
20 |   typeof notionParameters.shape
21 | > = {
22 |   tools: {
23 |     [NotionTools.ListDatabases]: listDatabasesTool,
24 |     [NotionTools.QueryDatabase]: queryDatabaseTool,
25 |     [NotionTools.CreateDatabase]: createDatabaseTool,
26 |     [NotionTools.GetPage]: getPageTool,
27 |     [NotionTools.SearchPages]: searchPagesTool,
28 |     [NotionTools.CreatePage]: createPageTool,
29 |     [NotionTools.GetBlocks]: getBlocksTool,
30 |     [NotionTools.AppendBlocks]: appendBlocksTool,
31 |     [NotionTools.ListUsers]: listUsersTool,
32 |   },
33 |   parameters: notionParameters,
34 | };
35 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/components/database.tsx:
--------------------------------------------------------------------------------
 1 | import { HStack, VStack } from "@/components/ui/stack";
 2 | import type { DatabaseObjectResponse } from "@notionhq/client/build/src/api-endpoints";
 3 | import { NotionPageIcon } from "./icon";
 4 | import { Database } from "lucide-react";
 5 | 
 6 | export const NotionDatabase: React.FC<{
 7 |   database: DatabaseObjectResponse;
 8 |   onClick?: () => void;
 9 | }> = ({ database, onClick }) => {
10 |   return (
11 |     <HStack
12 |       key={database.id}
13 |       className="group w-full cursor-pointer justify-between border-b py-2 last:border-b-0 last:pb-0"
14 |       onClick={() => {
15 |         onClick?.();
16 |       }}
17 |     >
18 |       <HStack>
19 |         <NotionPageIcon
20 |           icon={database.icon}
21 |           defaultIcon={<Database className="size-4 shrink-0" />}
22 |         />
23 |         <VStack className="group flex w-full cursor-pointer items-start gap-0">
24 |           <h3 className="group-hover:text-primary line-clamp-2 transition-colors">
25 |             {database.title?.[0]?.plain_text ?? "Untitled Database"}
26 |           </h3>
27 |           {database.description?.[0]?.plain_text && (
28 |             <p className="text-muted-foreground text-xs">
29 |               {database.description?.[0]?.plain_text ?? "No description"}
30 |             </p>
31 |           )}
32 |         </VStack>
33 |       </HStack>
34 |       <p className="text-muted-foreground/60 text-xs">
35 |         Updated {new Date(database.last_edited_time).toLocaleDateString()}
36 |       </p>
37 |     </HStack>
38 |   );
39 | };
40 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/components/icon.tsx:
--------------------------------------------------------------------------------
 1 | import type { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
 2 | import { FileText } from "lucide-react";
 3 | 
 4 | interface Props {
 5 |   icon: PageObjectResponse["icon"];
 6 |   defaultIcon?: React.ReactNode;
 7 | }
 8 | 
 9 | export const NotionPageIcon: React.FC<Props> = ({
10 |   icon,
11 |   defaultIcon = <FileText className="size-4" />,
12 | }) => {
13 |   if (!icon) return defaultIcon;
14 | 
15 |   if (icon.type === "external") {
16 |     return (
17 |       // eslint-disable-next-line @next/next/no-img-element
18 |       <img src={icon.external.url} alt={icon.external.url} className="size-4" />
19 |     );
20 |   }
21 | 
22 |   if (icon.type === "file") {
23 |     // eslint-disable-next-line @next/next/no-img-element
24 |     return <img src={icon.file.url} alt={icon.file.url} className="size-4" />;
25 |   }
26 | 
27 |   if (icon.type === "emoji") {
28 |     return <span className="size-4 text-sm">{icon.emoji}</span>;
29 |   }
30 | 
31 |   if (icon.type === "custom_emoji") {
32 |     return (
33 |       // eslint-disable-next-line @next/next/no-img-element
34 |       <img
35 |         src={icon.custom_emoji.url}
36 |         alt={icon.custom_emoji.url}
37 |         className="size-4"
38 |       />
39 |     );
40 |   }
41 | 
42 |   return null;
43 | };
44 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/components/index.ts:
--------------------------------------------------------------------------------
1 | export { ToolCallDisplay } from "./tool-call-display";
2 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/components/page.tsx:
--------------------------------------------------------------------------------
 1 | import { HStack } from "@/components/ui/stack";
 2 | import type { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
 3 | import { getTitle } from "../lib/title";
 4 | import { NotionPageIcon } from "./icon";
 5 | import Link from "next/link";
 6 | import { ExternalLink } from "lucide-react";
 7 | 
 8 | export const NotionPage: React.FC<{
 9 |   page: PageObjectResponse;
10 |   onClick?: () => void;
11 | }> = ({ page, onClick }) => {
12 |   return (
13 |     <HStack
14 |       className="group w-full cursor-pointer justify-between border-b py-2 last:border-b-0 last:pb-0"
15 |       onClick={onClick}
16 |     >
17 |       <HStack>
18 |         <NotionPageIcon icon={page.icon} />
19 |         <HStack>
20 |           <h3 className="group-hover:text-primary line-clamp-2 transition-colors">
21 |             {getTitle(page)}
22 |           </h3>
23 |           <Link
24 |             href={page.url}
25 |             target="_blank"
26 |             className="text-xs"
27 |             onClick={(e) => e.stopPropagation()}
28 |           >
29 |             <ExternalLink className="hover:text-primary size-3" />
30 |           </Link>
31 |         </HStack>
32 |       </HStack>
33 | 
34 |       <span className="text-muted-foreground/60 text-xs">
35 |         Created {new Date(page.created_time).toLocaleDateString()}
36 |       </span>
37 |     </HStack>
38 |   );
39 | };
40 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/components/tool-call-display.tsx:
--------------------------------------------------------------------------------
 1 | import React from "react";
 2 | import { HStack, VStack } from "@/components/ui/stack";
 3 | 
 4 | interface ToolCallDisplayProps {
 5 |   icon: React.ComponentType<{ className?: string }>;
 6 |   label: string;
 7 |   value: string;
 8 | }
 9 | 
10 | export const ToolCallDisplay: React.FC<ToolCallDisplayProps> = ({
11 |   icon: Icon,
12 |   label,
13 |   value,
14 | }) => {
15 |   return (
16 |     <HStack className="gap-2">
17 |       <Icon className="text-muted-foreground size-4" />
18 |       <VStack className="items-start gap-0">
19 |         <span className="text-muted-foreground/80 text-xs font-medium">
20 |           {label}
21 |         </span>
22 |         <span className="text-sm">{value}</span>
23 |       </VStack>
24 |     </HStack>
25 |   );
26 | };
27 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/lib/title.ts:
--------------------------------------------------------------------------------
 1 | import type { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
 2 | 
 3 | export const getTitle = (page: PageObjectResponse) => {
 4 |   if (
 5 |     "title" in page.properties &&
 6 |     page.properties.title?.type === "title" &&
 7 |     page.properties.title.title?.[0]?.plain_text
 8 |   ) {
 9 |     return page.properties.title.title[0].plain_text;
10 |   }
11 | 
12 |   const urlTitle =
13 |     page.url.split("/").pop()?.split("-").slice(0, -1).join(" ") ?? "";
14 | 
15 |   if (urlTitle) {
16 |     return urlTitle;
17 |   }
18 | 
19 |   return "Untitled Page";
20 | };
21 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/tools/blocks/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | import type { BlockObjectResponse } from "@notionhq/client/build/src/api-endpoints";
 4 | 
 5 | export const getBlocksTool = createBaseTool({
 6 |   description: "Retrieve block content from a page or block",
 7 |   inputSchema: z.object({
 8 |     block_id: z
 9 |       .string()
10 |       .describe("The ID of the block or page to retrieve children from"),
11 |     start_cursor: z
12 |       .string()
13 |       .describe("Pagination cursor to start from (blank for first page)"),
14 |     page_size: z
15 |       .number()
16 |       .max(100)
17 |       .describe("Number of results per page (max 100, default 10)"),
18 |   }),
19 |   outputSchema: z.object({
20 |     results: z.array(z.custom<BlockObjectResponse>()),
21 |     has_more: z.boolean(),
22 |     next_cursor: z.string().optional(),
23 |   }),
24 | });
25 | 
26 | export const appendBlocksTool = createBaseTool({
27 |   description: "Append new blocks to a page or existing block",
28 |   inputSchema: z.object({
29 |     block_id: z
30 |       .string()
31 |       .describe("The ID of the parent block or page to append blocks to"),
32 |     content: z
33 |       .string()
34 |       .describe("Markdown content to convert and append as blocks"),
35 |     block_type: z
36 |       .enum([
37 |         "paragraph",
38 |         "heading_1",
39 |         "heading_2",
40 |         "heading_3",
41 |         "bulleted_list_item",
42 |         "numbered_list_item",
43 |         "to_do",
44 |         "code",
45 |       ])
46 |       .describe("Type of block to create (defaults to paragraph)"),
47 |   }),
48 |   outputSchema: z.object({
49 |     results: z.array(z.custom<BlockObjectResponse>()),
50 |   }),
51 | });
52 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/tools/client.ts:
--------------------------------------------------------------------------------
 1 | export {
 2 |   notionListDatabasesToolConfigClient,
 3 |   notionQueryDatabaseToolConfigClient,
 4 |   notionCreateDatabaseToolConfigClient,
 5 | } from "./databases/client";
 6 | 
 7 | export {
 8 |   notionGetPageToolConfigClient,
 9 |   notionSearchPagesToolConfigClient,
10 |   notionCreatePageToolConfigClient,
11 | } from "./pages/client";
12 | 
13 | export {
14 |   notionGetBlocksToolConfigClient,
15 |   notionAppendBlocksToolConfigClient,
16 | } from "./blocks/client";
17 | 
18 | export { notionListUsersToolConfigClient } from "./users/client";
19 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/tools/index.ts:
--------------------------------------------------------------------------------
 1 | export enum NotionTools {
 2 |   ListDatabases = "list-databases",
 3 |   QueryDatabase = "query-database",
 4 |   CreateDatabase = "create-database",
 5 |   GetPage = "get-page",
 6 |   SearchPages = "search-pages",
 7 |   CreatePage = "create-page",
 8 |   GetBlocks = "get-blocks",
 9 |   AppendBlocks = "append-blocks",
10 |   ListUsers = "list-users",
11 | }
12 | 
13 | export {
14 |   listDatabasesTool,
15 |   queryDatabaseTool,
16 |   createDatabaseTool,
17 | } from "./databases/base";
18 | export { getPageTool, searchPagesTool, createPageTool } from "./pages/base";
19 | export { getBlocksTool, appendBlocksTool } from "./blocks/base";
20 | export { listUsersTool } from "./users/base";
21 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/tools/server.ts:
--------------------------------------------------------------------------------
 1 | export {
 2 |   notionListDatabasesToolConfigServer,
 3 |   notionQueryDatabaseToolConfigServer,
 4 |   notionCreateDatabaseToolConfigServer,
 5 | } from "./databases/server";
 6 | 
 7 | export {
 8 |   notionGetPageToolConfigServer,
 9 |   notionSearchPagesToolConfigServer,
10 |   notionCreatePageToolConfigServer,
11 | } from "./pages/server";
12 | 
13 | export {
14 |   notionGetBlocksToolConfigServer,
15 |   notionAppendBlocksToolConfigServer,
16 | } from "./blocks/server";
17 | 
18 | export { notionListUsersToolConfigServer } from "./users/server";
19 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/tools/users/base.ts:
--------------------------------------------------------------------------------
 1 | import { z } from "zod";
 2 | import { createBaseTool } from "@/toolkits/create-tool";
 3 | import type { UserObjectResponse } from "@notionhq/client/build/src/api-endpoints";
 4 | 
 5 | export const listUsersTool = createBaseTool({
 6 |   description: "List all users in the workspace",
 7 |   inputSchema: z.object({
 8 |     start_cursor: z
 9 |       .string()
10 |       .describe("Pagination cursor to start from (blank for first page)"),
11 |     page_size: z
12 |       .number()
13 |       .max(100)
14 |       .describe("Number of results per page (max 100, default 10)"),
15 |   }),
16 |   outputSchema: z.object({
17 |     results: z.array(z.custom<UserObjectResponse>()),
18 |     has_more: z.boolean(),
19 |     next_cursor: z.string().optional(),
20 |   }),
21 | });
22 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/notion/tools/users/server.ts:
--------------------------------------------------------------------------------
 1 | import type { Client } from "@notionhq/client";
 2 | import type { ServerToolConfig } from "@/toolkits/types";
 3 | import type { listUsersTool } from "./base";
 4 | 
 5 | export const notionListUsersToolConfigServer = (
 6 |   notion: Client,
 7 | ): ServerToolConfig<
 8 |   typeof listUsersTool.inputSchema.shape,
 9 |   typeof listUsersTool.outputSchema.shape
10 | > => {
11 |   return {
12 |     callback: async ({ start_cursor, page_size = 100 }) => {
13 |       try {
14 |         const response = await notion.users.list({
15 |           start_cursor: start_cursor || undefined,
16 |           page_size,
17 |         });
18 | 
19 |         return {
20 |           results: response.results,
21 |           has_more: response.has_more,
22 |           next_cursor: response.next_cursor ?? undefined,
23 |         };
24 |       } catch (error) {
25 |         console.error("Notion API error:", error);
26 |         throw new Error("Failed to retrieve users from Notion");
27 |       }
28 |     },
29 |     message:
30 |       "Successfully retrieved workspace users. The user is shown the responses in the UI. Do not reiterate them. If you called this tool because the user asked a question, answer the question.",
31 |   };
32 | };
33 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/server.ts:
--------------------------------------------------------------------------------
 1 | import type { ServerToolkit } from "../types";
 2 | import { exaToolkitServer } from "./exa/server";
 3 | import { githubToolkitServer } from "./github/server";
 4 | import { googleCalendarToolkitServer } from "./google-calendar/server";
 5 | import { googleDriveToolkitServer } from "./google-drive/server";
 6 | import { imageToolkitServer } from "./image/server";
 7 | import { mem0ToolkitServer } from "./mem0/server";
 8 | import { notionToolkitServer } from "./notion/server";
 9 | import { e2bToolkitServer } from "./e2b/server";
10 | import {
11 |   Toolkits,
12 |   type ServerToolkitNames,
13 |   type ServerToolkitParameters,
14 | } from "./shared";
15 | 
16 | type ServerToolkits = {
17 |   [K in Toolkits]: ServerToolkit<
18 |     ServerToolkitNames[K],
19 |     ServerToolkitParameters[K]
20 |   >;
21 | };
22 | 
23 | export const serverToolkits: ServerToolkits = {
24 |   [Toolkits.Exa]: exaToolkitServer,
25 |   [Toolkits.Image]: imageToolkitServer,
26 |   [Toolkits.Github]: githubToolkitServer,
27 |   [Toolkits.GoogleCalendar]: googleCalendarToolkitServer,
28 |   [Toolkits.GoogleDrive]: googleDriveToolkitServer,
29 |   [Toolkits.Memory]: mem0ToolkitServer,
30 |   [Toolkits.Notion]: notionToolkitServer,
31 |   [Toolkits.E2B]: e2bToolkitServer,
32 | };
33 | 
34 | export function getServerToolkit<T extends Toolkits>(
35 |   server: T,
36 | ): ServerToolkit<ServerToolkitNames[T], ServerToolkitParameters[T]> {
37 |   return serverToolkits[server];
38 | }
39 | 


--------------------------------------------------------------------------------
/src/toolkits/toolkits/toolkit-types.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonhedman/toolkit.dev/6bb4636d55ffcdf152fa21e668c65ca4a5266a73/src/toolkits/toolkits/toolkit-types.ts


--------------------------------------------------------------------------------
/src/trpc/query-client.ts:
--------------------------------------------------------------------------------
 1 | import {
 2 |   defaultShouldDehydrateQuery,
 3 |   QueryClient,
 4 | } from "@tanstack/react-query";
 5 | import SuperJSON from "superjson";
 6 | 
 7 | export const createQueryClient = () =>
 8 |   new QueryClient({
 9 |     defaultOptions: {
10 |       queries: {
11 |         // With SSR, we usually want to set some default staleTime
12 |         // above 0 to avoid refetching immediately on the client
13 |         staleTime: 30 * 1000,
14 |       },
15 |       dehydrate: {
16 |         serializeData: SuperJSON.serialize,
17 |         shouldDehydrateQuery: (query) =>
18 |           defaultShouldDehydrateQuery(query) ||
19 |           query.state.status === "pending",
20 |       },
21 |       hydrate: {
22 |         deserializeData: SuperJSON.deserialize,
23 |       },
24 |     },
25 |   });
26 | 


--------------------------------------------------------------------------------
/src/trpc/server.ts:
--------------------------------------------------------------------------------
 1 | import "server-only";
 2 | 
 3 | import { createHydrationHelpers } from "@trpc/react-query/rsc";
 4 | import { headers } from "next/headers";
 5 | import { cache } from "react";
 6 | 
 7 | import { createCaller, type AppRouter } from "@/server/api/root";
 8 | import { createTRPCContext } from "@/server/api/trpc";
 9 | import { createQueryClient } from "./query-client";
10 | 
11 | /**
12 |  * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
13 |  * handling a tRPC call from a React Server Component.
14 |  */
15 | const createContext = cache(async () => {
16 |   const heads = new Headers(await headers());
17 |   heads.set("x-trpc-source", "rsc");
18 | 
19 |   return createTRPCContext({
20 |     headers: heads,
21 |   });
22 | });
23 | 
24 | const getQueryClient = cache(createQueryClient);
25 | const caller = createCaller(createContext);
26 | 
27 | export const { trpc: api, HydrateClient } = createHydrationHelpers<AppRouter>(
28 |   caller,
29 |   getQueryClient,
30 | );
31 | 


--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     /* Base Options: */
 4 |     "esModuleInterop": true,
 5 |     "skipLibCheck": true,
 6 |     "target": "es2022",
 7 |     "allowJs": true,
 8 |     "resolveJsonModule": true,
 9 |     "moduleDetection": "force",
10 |     "isolatedModules": true,
11 |     "verbatimModuleSyntax": true,
12 | 
13 |     /* Strictness */
14 |     "strict": true,
15 |     "noUncheckedIndexedAccess": true,
16 |     "checkJs": true,
17 | 
18 |     /* Bundled projects */
19 |     "lib": ["dom", "dom.iterable", "ES2022"],
20 |     "noEmit": true,
21 |     "module": "ESNext",
22 |     "moduleResolution": "Bundler",
23 |     "jsx": "preserve",
24 |     "plugins": [{ "name": "next" }],
25 |     "incremental": true,
26 | 
27 |     /* Path Aliases */
28 |     "baseUrl": ".",
29 |     "paths": {
30 |       "@/*": ["./src/*"]
31 |     }
32 |   },
33 |   "include": [
34 |     "next-env.d.ts",
35 |     "**/*.ts",
36 |     "**/*.tsx",
37 |     "**/*.cjs",
38 |     "**/*.js",
39 |     ".next/types/**/*.ts"
40 |   ],
41 |   "exclude": ["node_modules"]
42 | }
43 | 


--------------------------------------------------------------------------------