Full Code of idosal/git-mcp for AI

main ed56be437f10 cached
127 files
1.9 MB
634.2k tokens
876 symbols
1 requests
Download .txt
Showing preview only (2,036K chars total). Download the full file or copy to clipboard to get everything.
Repository: idosal/git-mcp
Branch: main
Commit: ed56be437f10
Files: 127
Total size: 1.9 MB

Directory structure:
gitextract_khbd3ulr/

├── .github/
│   ├── CONTRIBUTING.md
│   └── workflows/
│       ├── e2e-tests.yml
│       └── run-tests.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierrc
├── .react-router/
│   └── types/
│       ├── +register.ts
│       ├── +virtual.d.ts
│       └── app/
│           ├── +types/
│           │   └── root.ts
│           └── routes/
│               └── +types/
│                   ├── $.ts
│                   ├── _index.ts
│                   └── api.chat.ts
├── LICENSE
├── README.md
├── SECURITY.md
├── app/
│   ├── app.css
│   ├── chat/
│   │   ├── ai/
│   │   │   ├── providers.server.ts
│   │   │   └── providers.shared.ts
│   │   ├── components/
│   │   │   ├── api-key-manager.tsx
│   │   │   ├── api-keys-provider.tsx
│   │   │   ├── chat-sidebar.tsx
│   │   │   ├── chat.tsx
│   │   │   ├── copy-button.tsx
│   │   │   ├── icons.tsx
│   │   │   ├── input.tsx
│   │   │   ├── markdown.tsx
│   │   │   ├── mcp-server-manager.tsx
│   │   │   ├── message.tsx
│   │   │   ├── messages.tsx
│   │   │   ├── model-picker.tsx
│   │   │   ├── project-overview.tsx
│   │   │   ├── suggested-prompts.tsx
│   │   │   ├── textarea.tsx
│   │   │   ├── theme-provider.tsx
│   │   │   ├── theme-toggle.tsx
│   │   │   ├── tool-invocation.tsx
│   │   │   └── ui/
│   │   │       ├── accordion.tsx
│   │   │       ├── avatar.tsx
│   │   │       ├── badge.tsx
│   │   │       ├── button.tsx
│   │   │       ├── dialog.tsx
│   │   │       ├── dropdown-menu.tsx
│   │   │       ├── input.tsx
│   │   │       ├── label.tsx
│   │   │       ├── popover.tsx
│   │   │       ├── scroll-area.tsx
│   │   │       ├── select.tsx
│   │   │       ├── separator.tsx
│   │   │       ├── sheet.tsx
│   │   │       ├── sidebar.tsx
│   │   │       ├── skeleton.tsx
│   │   │       ├── sonner.tsx
│   │   │       ├── text-morph.tsx
│   │   │       ├── textarea.tsx
│   │   │       └── tooltip.tsx
│   │   ├── hooks/
│   │   │   └── use-mobile.ts
│   │   └── lib/
│   │       ├── constants.ts
│   │       ├── context/
│   │       │   └── mcp-context.tsx
│   │       ├── db/
│   │       │   └── schema.ts
│   │       ├── hooks/
│   │       │   ├── use-copy.ts
│   │       │   ├── use-local-storage.ts
│   │       │   └── use-scroll-to-bottom.tsx
│   │       ├── user-id.ts
│   │       └── utils.ts
│   ├── components/
│   │   ├── .client/
│   │   │   ├── chatPage.client.tsx
│   │   │   └── chatPage.css
│   │   ├── chatPage.tsx
│   │   └── content.tsx
│   ├── entry.server.tsx
│   ├── globals.css
│   ├── root.tsx
│   ├── routes/
│   │   ├── $.tsx
│   │   ├── _index.tsx
│   │   └── api.chat.ts
│   └── routes.ts
├── biome.json
├── components.json
├── package.json
├── playwright.config.ts
├── postcss.config.mjs
├── react-router.config.ts
├── src/
│   ├── api/
│   │   ├── test-setup.ts
│   │   ├── tools/
│   │   │   ├── commonTools.test.ts
│   │   │   ├── commonTools.ts
│   │   │   ├── index.test.ts
│   │   │   ├── index.ts
│   │   │   └── repoHandlers/
│   │   │       ├── DefaultRepoHandler.ts
│   │   │       ├── GenericRepoHandler.ts
│   │   │       ├── ReactRouterRepoHandler.ts
│   │   │       ├── RepoHandler.ts
│   │   │       ├── ThreejsRepoHandler.ts
│   │   │       ├── generic/
│   │   │       │   ├── generic.test.ts
│   │   │       │   └── static-mapping.json
│   │   │       ├── handlers.test.ts
│   │   │       ├── handlers.ts
│   │   │       ├── test/
│   │   │       │   └── utils.ts
│   │   │       └── threejs/
│   │   │           ├── __snapshots__/
│   │   │           │   └── utils.test.ts.snap
│   │   │           ├── utils.test.ts
│   │   │           └── utils.ts
│   │   └── utils/
│   │       ├── ViewCounterDO.ts
│   │       ├── badge.ts
│   │       ├── cache.ts
│   │       ├── github.ts
│   │       ├── githubClient.ts
│   │       ├── helpers.ts
│   │       ├── r2.ts
│   │       ├── robotsTxt.ts
│   │       └── vectorStore.ts
│   ├── index.ts
│   ├── shared/
│   │   ├── nameUtils.ts
│   │   ├── repoData.test.ts
│   │   ├── repoData.ts
│   │   └── urlUtils.ts
│   ├── test/
│   │   ├── ViewCounterDO.test.ts
│   │   └── badge.test.ts
│   └── utils.ts
├── static/
│   └── README.md
├── tailwind.config.js
├── tests/
│   ├── e2e/
│   │   └── inspection.spec.ts
│   └── global-setup.ts
├── tsconfig.cloudflare.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
├── vitest.config.ts
├── worker-configuration.d.ts
└── wrangler.jsonc

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing to GitMCP

First of all, thank you for your interest in contributing to GitMCP! We appreciate the time and effort you're willing to invest in improving the project. This document provides guidelines and information to make the contribution process as smooth as possible.

## Table of Contents

- [Getting Started](#getting-started)
- [Local Development](#local-development)
  - [Prerequisites](#prerequisites)
  - [Setting Up Your Development Environment](#setting-up-your-development-environment)
  - [Running the Project Locally](#running-the-project-locally)
  - [Testing](#testing)
  - [Code Formatting](#code-formatting)
  - [Development Workflow](#development-workflow)
  - [Project Structure](#project-structure)
- [How to Contribute](#how-to-contribute)
  - [Reporting Bugs](#reporting-bugs)
  - [Suggesting Enhancements](#suggesting-enhancements)
  - [Submitting Pull Requests](#submitting-pull-requests)
- [Style Guidelines](#style-guidelines)
  - [Code Style](#code-style)
  - [Commit Messages](#commit-messages)
- [Additional Resources](#additional-resources)

## Getting Started

1. Fork the repository and clone it to your local machine.
2. Set up the development environment.
3. Explore the codebase, run tests, and verify that everything works as expected.

## Local Development

### Prerequisites

Before you start working on GitMCP, make sure you have the following installed:

- [Node.js](https://nodejs.org/) (version 18 or higher recommended)
- [pnpm](https://pnpm.io/) (version 8.15.7 or higher)
- Git

### Setting Up Your Development Environment

1. Clone your forked repository:
   ```bash
   git clone https://github.com/your-username/git-mcp.git
   cd git-mcp
   ```

2. Install dependencies:
   ```bash
   pnpm install
   ```

3. Set up environment variables:
   - Create a `.env.local` file in the root directory
   - Add any necessary environment variables (ask project maintainers if you need access to specific API keys)

### Running the Project Locally

To start the development server:

```bash
pnpm vercel dev
```

This will start the Next.js development server, typically at http://localhost:3000.

For running with the MCP Inspector (useful for debugging MCP endpoints):

```bash
pnpm run inspector
```

### Testing

To run tests:

```bash
pnpm test
```

GitMCP uses Vitest as the testing framework. When adding new features, please include appropriate tests.

### Code Formatting

GitMCP uses Prettier for code formatting and lint-staged to ensure code is properly formatted before committing. Pre-commit hooks are set up with Husky to run these checks automatically.

To manually format your code:

```bash
pnpm prettier --write .
```

### Development Workflow

1. Create a new branch for your feature/bugfix
2. Make your changes
3. Add tests for your changes when applicable
4. Run the tests to ensure they pass
5. Commit your changes following the commit message guidelines
6. Push your branch and open a pull request

### Project Structure

- `api/`: Contains the server-side code and MCP implementation
  - `tools/`: MCP tools implementation
  - `utils/`: Utility functions for the API
- `app/`: Next.js app directory with React components
- `pages/`: Additional Next.js pages
- `public/`: Static assets
- `shared/`: Shared utilities used across the codebase

## How to Contribute

### Reporting Bugs

If you encounter a bug or issue while using GitMCP, please open a new issue on the [GitHub Issues](https://github.com/idosal/git-mcp/issues) page. Provide a clear and concise description of the problem, steps to reproduce it, and any relevant error messages or logs.

### Suggesting Enhancements

We welcome ideas for improvements and new features. To suggest an enhancement, open a new issue on the [GitHub Issues](https://github.com/idosal/git-mcp/issues) page. Describe the enhancement in detail, explain the use case, and outline the benefits it would bring to the project.

### Submitting Pull Requests

1. Create a new branch for your feature or bugfix. Use a descriptive name like `feature/your-feature-name` or `fix/your-bugfix-name`.
2. Make your changes, following the [Style Guidelines](#style-guidelines) below.
3. Test your changes and ensure that they don't introduce new issues or break existing functionality.
4. Commit your changes, following the [commit message guidelines](#commit-messages).
5. Push your branch to your fork on GitHub.
6. Open a new pull request against the `main` branch of the Wolverine repository. Include a clear and concise description of your changes, referencing any related issues.

## Style Guidelines

### Code Style

GitMCP uses [ESLint](https://eslint.org/) as its code style guide. Please ensure that your code follows these guidelines. 

### Commit Messages

Write clear and concise commit messages that briefly describe the changes made in each commit. Use the imperative mood and start with a capitalized verb, e.g., "Add new feature" or "Fix bug in function".

## Additional Resources

- [GitHub Help](https://help.github.com/)
- [GitHub Pull Request Documentation](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests)
- [ESLint Style Guide](https://eslint.org/)

Thank you once again for your interest in contributing to GitMCP. We look forward to collaborating with you and creating an even better project together!



================================================
FILE: .github/workflows/e2e-tests.yml
================================================
name: E2E Tests

on:
  pull_request:
    paths:
      - 'src/**'
      - 'tests/e2e/**'
      - 'package.json'
      - 'pnpm-lock.yaml'
      - 'wrangler.jsonc'
      - '.github/workflows/e2e-tests.yml'

jobs:
  e2e_test:
    runs-on: ubuntu-latest
    timeout-minutes: 15 # Adjust timeout as needed
    permissions:
      contents: read
      pull-requests: write # Required to post the preview URL comment back to the PR

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20' # Match your project's Node.js version requirement

      - name: Setup PNPM
        uses: pnpm/action-setup@v4

      - name: Get pnpm store directory
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV

      - name: Setup pnpm cache
        uses: actions/cache@v4
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Install Playwright Browsers
        run: npx playwright install chromium --with-deps

      - name: Create .dev.vars with test environment
        run: echo "ENVIRONMENT=test" > .dev.vars

      - name: Run Playwright tests
        run: pnpm run test:e2e

================================================
FILE: .github/workflows/run-tests.yml
================================================
name: Run Tests

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4

    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'

    - name: Setup pnpm
      uses: pnpm/action-setup@v3
      with:
        version: '10.7.1'

    - name: Get pnpm store directory
      id: pnpm-cache
      run: |
        echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT

    - name: Setup pnpm cache
      uses: actions/cache@v4
      with:
        path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
        key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
        restore-keys: |
          ${{ runner.os }}-pnpm-store-

    - name: Install dependencies
      run: pnpm install

    - name: Run tests
      run: pnpm test


================================================
FILE: .gitignore
================================================
node_modules
build

.nx
.idea
.vscode
.zed
# Logs

logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)

report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# Runtime data

pids
_.pid
_.seed
\*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover

lib-cov

# Coverage directory used by tools like istanbul

coverage
\*.lcov

# nyc test coverage

.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)

.grunt

# Bower dependency directory (https://bower.io/)

bower_components

# node-waf configuration

.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)

build/Release

# Dependency directories

node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)

web_modules/

# TypeScript cache

\*.tsbuildinfo

# Optional npm cache directory

.npm

# Optional eslint cache

.eslintcache

# Optional stylelint cache

.stylelintcache

# Microbundle cache

.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history

.node_repl_history

# Output of 'npm pack'

\*.tgz

# Yarn Integrity file

.yarn-integrity

# dotenv environment variable files

.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)

.cache
.parcel-cache

# Next.js build output

.next
out

# Nuxt.js build / generate output

.nuxt
dist

# Gatsby files

.cache/

# Comment in the public line in if your project uses Gatsby and not Next.js

# https://nextjs.org/blog/next-9-1#public-directory-support

# public

# vuepress build output

.vuepress/dist

# vuepress v2.x temp and cache directory

.temp
.cache

# Docusaurus cache and generated files

.docusaurus

# Serverless directories

.serverless/

# FuseBox cache

.fusebox/

# DynamoDB Local files

.dynamodb/

# TernJS port file

.tern-port

# Stores VSCode versions used for testing VSCode extensions

.vscode-test

# yarn v2

.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*

# wrangler project

.dev.vars
.wrangler/

# playwright

playwright-results/
playwright-report/
test-results/

================================================
FILE: .husky/pre-commit
================================================
pnpm lint-staged


================================================
FILE: .prettierrc
================================================
{
  "semi": true,
  "trailingComma": "all",
  "singleQuote": false,
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false,
  "bracketSpacing": true,
  "arrowParens": "always",
  "endOfLine": "lf"
}


================================================
FILE: .react-router/types/+register.ts
================================================
import "react-router";

declare module "react-router" {
  interface Register {
    params: Params;
  }
}

type Params = {
  "/": {};
  "/api/chat": {};
  "/*": {
    "*": string;
  };
};

================================================
FILE: .react-router/types/+virtual.d.ts
================================================
declare module "virtual:react-router/server-build" {
  import { ServerBuild } from "react-router";
  export const assets: ServerBuild["assets"];
  export const assetsBuildDirectory: ServerBuild["assetsBuildDirectory"];
  export const basename: ServerBuild["basename"];
  export const entry: ServerBuild["entry"];
  export const future: ServerBuild["future"];
  export const isSpaMode: ServerBuild["isSpaMode"];
  export const prerender: ServerBuild["prerender"];
  export const publicPath: ServerBuild["publicPath"];
  export const routes: ServerBuild["routes"];
  export const ssr: ServerBuild["ssr"];
  export const unstable_getCriticalCss: ServerBuild["unstable_getCriticalCss"];
}

================================================
FILE: .react-router/types/app/+types/root.ts
================================================
// React Router generated types for route:
// root.tsx

import type * as T from "react-router/route-module"



type Module = typeof import("../root.js")

export type Info = {
  parents: [],
  id: "root"
  file: "root.tsx"
  path: ""
  params: {} & { [key: string]: string | undefined }
  module: Module
  loaderData: T.CreateLoaderData<Module>
  actionData: T.CreateActionData<Module>
}

export namespace Route {
  export type LinkDescriptors = T.LinkDescriptors
  export type LinksFunction = () => LinkDescriptors

  export type MetaArgs = T.CreateMetaArgs<Info>
  export type MetaDescriptors = T.MetaDescriptors
  export type MetaFunction = (args: MetaArgs) => MetaDescriptors

  export type HeadersArgs = T.HeadersArgs
  export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit

  export type unstable_MiddlewareFunction = T.CreateServerMiddlewareFunction<Info>
  export type unstable_ClientMiddlewareFunction = T.CreateClientMiddlewareFunction<Info>
  export type LoaderArgs = T.CreateServerLoaderArgs<Info>
  export type ClientLoaderArgs = T.CreateClientLoaderArgs<Info>
  export type ActionArgs = T.CreateServerActionArgs<Info>
  export type ClientActionArgs = T.CreateClientActionArgs<Info>

  export type HydrateFallbackProps = T.CreateHydrateFallbackProps<Info>
  export type ComponentProps = T.CreateComponentProps<Info>
  export type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Info>
}

================================================
FILE: .react-router/types/app/routes/+types/$.ts
================================================
// React Router generated types for route:
// routes/$.tsx

import type * as T from "react-router/route-module"

import type { Info as Parent0 } from "../../+types/root.js"

type Module = typeof import("../$.js")

export type Info = {
  parents: [Parent0],
  id: "routes/$"
  file: "routes/$.tsx"
  path: "*"
  params: {"*": string} & { [key: string]: string | undefined }
  module: Module
  loaderData: T.CreateLoaderData<Module>
  actionData: T.CreateActionData<Module>
}

export namespace Route {
  export type LinkDescriptors = T.LinkDescriptors
  export type LinksFunction = () => LinkDescriptors

  export type MetaArgs = T.CreateMetaArgs<Info>
  export type MetaDescriptors = T.MetaDescriptors
  export type MetaFunction = (args: MetaArgs) => MetaDescriptors

  export type HeadersArgs = T.HeadersArgs
  export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit

  export type unstable_MiddlewareFunction = T.CreateServerMiddlewareFunction<Info>
  export type unstable_ClientMiddlewareFunction = T.CreateClientMiddlewareFunction<Info>
  export type LoaderArgs = T.CreateServerLoaderArgs<Info>
  export type ClientLoaderArgs = T.CreateClientLoaderArgs<Info>
  export type ActionArgs = T.CreateServerActionArgs<Info>
  export type ClientActionArgs = T.CreateClientActionArgs<Info>

  export type HydrateFallbackProps = T.CreateHydrateFallbackProps<Info>
  export type ComponentProps = T.CreateComponentProps<Info>
  export type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Info>
}

================================================
FILE: .react-router/types/app/routes/+types/_index.ts
================================================
// React Router generated types for route:
// routes/_index.tsx

import type * as T from "react-router/route-module"

import type { Info as Parent0 } from "../../+types/root.js"

type Module = typeof import("../_index.js")

export type Info = {
  parents: [Parent0],
  id: "routes/_index"
  file: "routes/_index.tsx"
  path: "undefined"
  params: {} & { [key: string]: string | undefined }
  module: Module
  loaderData: T.CreateLoaderData<Module>
  actionData: T.CreateActionData<Module>
}

export namespace Route {
  export type LinkDescriptors = T.LinkDescriptors
  export type LinksFunction = () => LinkDescriptors

  export type MetaArgs = T.CreateMetaArgs<Info>
  export type MetaDescriptors = T.MetaDescriptors
  export type MetaFunction = (args: MetaArgs) => MetaDescriptors

  export type HeadersArgs = T.HeadersArgs
  export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit

  export type unstable_MiddlewareFunction = T.CreateServerMiddlewareFunction<Info>
  export type unstable_ClientMiddlewareFunction = T.CreateClientMiddlewareFunction<Info>
  export type LoaderArgs = T.CreateServerLoaderArgs<Info>
  export type ClientLoaderArgs = T.CreateClientLoaderArgs<Info>
  export type ActionArgs = T.CreateServerActionArgs<Info>
  export type ClientActionArgs = T.CreateClientActionArgs<Info>

  export type HydrateFallbackProps = T.CreateHydrateFallbackProps<Info>
  export type ComponentProps = T.CreateComponentProps<Info>
  export type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Info>
}

================================================
FILE: .react-router/types/app/routes/+types/api.chat.ts
================================================
// React Router generated types for route:
// routes/api.chat.ts

import type * as T from "react-router/route-module"

import type { Info as Parent0 } from "../../+types/root.js"

type Module = typeof import("../api.chat.js")

export type Info = {
  parents: [Parent0],
  id: "routes/api.chat"
  file: "routes/api.chat.ts"
  path: "api/chat"
  params: {} & { [key: string]: string | undefined }
  module: Module
  loaderData: T.CreateLoaderData<Module>
  actionData: T.CreateActionData<Module>
}

export namespace Route {
  export type LinkDescriptors = T.LinkDescriptors
  export type LinksFunction = () => LinkDescriptors

  export type MetaArgs = T.CreateMetaArgs<Info>
  export type MetaDescriptors = T.MetaDescriptors
  export type MetaFunction = (args: MetaArgs) => MetaDescriptors

  export type HeadersArgs = T.HeadersArgs
  export type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit

  export type unstable_MiddlewareFunction = T.CreateServerMiddlewareFunction<Info>
  export type unstable_ClientMiddlewareFunction = T.CreateClientMiddlewareFunction<Info>
  export type LoaderArgs = T.CreateServerLoaderArgs<Info>
  export type ClientLoaderArgs = T.CreateClientLoaderArgs<Info>
  export type ActionArgs = T.CreateServerActionArgs<Info>
  export type ClientActionArgs = T.CreateClientActionArgs<Info>

  export type HydrateFallbackProps = T.CreateHydrateFallbackProps<Info>
  export type ComponentProps = T.CreateComponentProps<Info>
  export type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Info>
}

================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2025 GitMCP Authors (Ido Salomon and Liad Yosef)

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# GitMCP

<p align="center">
  <img width="884" alt="image" src="https://github.com/user-attachments/assets/2bf3e3df-556c-49c6-ab7b-36c279d53bba" />
</p>

<p align="center">
  <a href="#-what-is-gitmcp">What is GitMCP</a> •
  <a href="#-features">Features</a> •
  <a href="#-getting-started">Getting Started</a> •
  <a href="#-how-it-works">How It Works</a> •
  <a href="#-badge">Badge</a> •
  <a href="#-examples">Examples</a> •
  <a href="#-faq">FAQ</a> •
  <a href="#-privacy">Privacy</a> •
  <a href="#-contributing">Contributing</a> •
  <a href="#-license">License</a>
</p>
<div align="center">

[![GitMCP](https://img.shields.io/endpoint?url=https://gitmcp.io/badge/idosal/git-mcp)](https://gitmcp.io/idosal/git-mcp)
[![Twitter Follow](https://img.shields.io/twitter/follow/idosal1?style=social)](https://twitter.com/idosal1)
[![Twitter Follow](https://img.shields.io/twitter/follow/liadyosef?style=social)](https://twitter.com/liadyosef)
</div>

<div align="center">
  <a href="https://www.pulsemcp.com/servers/idosal-git-mcp"><img src="https://www.pulsemcp.com/badge/top-pick/idosal-git-mcp" width="400" alt="Pulse MCP Badge"></a>
</div>

## 🤔 What is GitMCP?
**Stop vibe-hallucinating and start vibe-coding!**

[GitMCP](https://gitmcp.io) is a free, open-source, remote [Model Context Protocol (MCP)](https://docs.anthropic.com/en/docs/agents-and-tools/mcp) server that transforms **any** GitHub project (repositories or GitHub pages) into a documentation hub. It enables AI tools like Cursor to access up-to-date documentation and code, even if the LLM has never encountered them, thereby eliminating code hallucinations seamlessly.

GitMCP supports **two flavors** -

*   **Specific Repository (`gitmcp.io/{owner}/{repo}` or `{owner}.gitmcp.io/{repo}`):** Use these when you primarily work with a select number of libraries. This ensures your AI assistant always targets the correct project, enhancing security and relevance by preventing access to unintended repositories.
*   **Generic Server (`gitmcp.io/docs`):** Use this for maximum flexibility when you need to switch between different repositories frequently. The AI assistant will prompt you (or decide based on context) which repository to access for each request. Be mindful that this relies on correctly identifying the target repository each time.

**With GitMCP:**

*   AI assistants access the *latest* documentation and code directly from the source.
*   Get accurate API usage and reliable code examples.
*   Work effectively even with niche, new, or rapidly changing libraries.
*   Significantly reduced hallucinations and improved code correctness.

For example, this side-by-side comparison shows the result for the same one-shot prompt in Cursor when creating a [three.js](https://github.com/mrdoob/three.js) scene -

https://github.com/user-attachments/assets/fbf1b4a7-f9f0-4c0e-831c-4d64faae2c45

## ✨ Features

- 😎 **Latest Documentation on ANY GitHub Project**: Grant your AI assistant seamless access to the GitHub project's documentation and code. The built-in smart search capabilities help find exactly what the AI needs without using too many tokens!
- 🧠 **No More Hallucinations**: With GitMCP, your AI assistant can provide accurate and relevant answers to your questions.
- ☁️ **Zero Setup**: GitMCP runs in the cloud. Simply add the chosen GitMCP URL as an MCP server in your IDE — no downloads, installations, signups, or changes are required.
- 💬 **Embedded Chat**: Start quickly by chatting directly with the repository's documentation through our in-browser chat!
- ✅ **Open, Free, and Private**: GitMCP is open-source and completely free to use. It doesn't collect personal information or store queries. You can even self-host it!

<video src="https://github.com/user-attachments/assets/2c3afaf9-6c08-436e-9efd-db8710554430"></video>

## 🚀 Getting Started

Using GitMCP is easy! Simply follow these steps:

### Step 1: Choose the type of server you want

Choose one of these URL formats depending on what you want to connect to:

- For GitHub repositories: `gitmcp.io/{owner}/{repo}`
- For GitHub Pages sites: `{owner}.gitmcp.io/{repo}`
- For a generic tool that supports any repository (dynamic): `gitmcp.io/docs`

Replace `{owner}` with the GitHub username or organization name, and `{repo}` with the repository name.

For your convenience, you can also use the conversion tool on the landing page to format the GitHub URL into an MCP URL!

### Step 2: Connect your AI assistant

Select your AI assistant from the options below and follow the configuration instructions:

#### Connecting Cursor

Update your Cursor configuration file at `~/.cursor/mcp.json`:
   ```json
   {
     "mcpServers": {
       "gitmcp": {
         "url": "https://gitmcp.io/{owner}/{repo}"
       }
     }
   }
   ```

#### Connecting Claude Desktop

1. In Claude Desktop, go to Settings > Developer > Edit Config
2. Replace the configuration with:
   ```json
   {
     "mcpServers": {
       "gitmcp": {
         "command": "npx",
         "args": [
           "mcp-remote",
           "https://gitmcp.io/{owner}/{repo}"
         ]
       }
     }
   }
   ```

#### Connecting Windsurf

Update your Windsurf configuration file at `~/.codeium/windsurf/mcp_config.json`:
   ```json
   {
     "mcpServers": {
       "gitmcp": {
         "serverUrl": "https://gitmcp.io/{owner}/{repo}"
       }
     }
   }
   ```

#### Connecting VSCode

Update your VSCode configuration file at `.vscode/mcp.json`:
   ```json
   {
     "servers": {
       "gitmcp": {
         "type": "sse",
         "url": "https://gitmcp.io/{owner}/{repo}"
       }
     }
   }
   ```

#### Connecting Cline

Update your Cline configuration file at `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`:
   ```json
   {
     "mcpServers": {
       "gitmcp": {
         "url": "https://gitmcp.io/{owner}/{repo}",
         "disabled": false,
         "autoApprove": []
       }
     }
   }
   ```

#### Connecting Highlight AI

1. Open Highlight AI and click the plugins icon (@ symbol) in the sidebar
2. Click **Installed Plugins** at the top of the sidebar
3. Select **Custom Plugin**
4. Click **Add a plugin using a custom SSE URL**

Plugin name: `gitmcp`
SSE URL: `https://gitmcp.io/{owner}/{repo}`

For more details on adding custom MCP servers to HighlightAI, refer to [the documentation](https://docs.highlightai.com/plugins/custom).

#### Connecting Augment Code

1. Open Augment Code settings
2. Navigate to the MCP section
3. Add a new MCP server with the following details:

Name the MCP server: `git-mcp Docs`

Use this command:
```bash
npx mcp-remote https://gitmcp.io/{owner}/{repo}
```

Or use the following configuration:
```json
{
  "mcpServers": {
    "git-mcp Docs": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://gitmcp.io/{owner}/{repo}"
      ]
    }
  }
}
```

#### Connecting Msty AI
1. Open Msty Studio
2. Go to Tools > Import Tools from JSON Clipboard
3. Paste the following configuration:

```json
{
  "mcpServers": {
    "git-mcp Docs": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://gitmcp.io/{owner}/{repo}"
      ]
    }
  }
}
```

For more details on configuring MCP servers in Augment Code, visit [the Augment Code documentation](https://docs.augmentcode.com/setup-augment/mcp).

> **Note:** Remember to replace `{owner}` and `{repo}` with the actual GitHub username/organization and repository name. You can also use the dynamic endpoint `https://gitmcp.io/docs` to allow your AI to access any repository on demand.

## ⚙ How It Works

GitMCP connects your AI assistant to GitHub repositories using the Model Context Protocol (MCP), a standard that lets AI tools request additional information from external sources.

What happens when you use GitMCP:

1. **You provide the GitMCP URL** to your AI assistant (e.g., `gitmcp.io/microsoft/typescript`). GitMCP exposes tools like documentation fetching, smart search, code search, etc.
2. **Prompt the AI assistant** on documentation/code-related questions.
3. **Your AI sends requests** to GitMCP to use its tools (with your approval).
4. **GitMCP executes the AI's request** and returns the requested data.
5. **Your AI receives the information** and generates a more accurate, grounded response without hallucinations.

### Supported Documentation

GitMCP currently supports the following documents (in order of priority):
1. [llms.txt](https://llmstxt.org)
2. AI-optimized version of the project's documentation
3. `README.md`/root

## 💡 Examples

Here are some examples of how to use GitMCP with different AI assistants and repositories:

### Example 1: Using Windsurf with a specific repository

For the GitHub repository `https://github.com/microsoft/playwright-mcp`, add `https://gitmcp.io/microsoft/playwright-mcp` as an MCP server to Windsurf.

**Prompt to Claude:**
> "How do I use the Playwright MCP"

Windsurf will pull the relevant documentation from GitMCP to implement the memory feature correctly.

### Example 2: Using Cursor with a GitHub Pages site

For the GitHub Pages site `langchain-ai.github.io/langgraph`, add `https://langchain-ai.gitmcp.io/langgraph` as an MCP server to Cursor.

**Prompt to Cursor:**
> "Add memory to my LangGraph agent"

Cursor will pull the relevant documentation and code from GitMCP to correctly implement the memory feature.

### Example 3: Using Claude Desktop with the dynamic endpoint

You don't have to pick specific repositories. The generic `gitmcp.io/docs` endpoint allows AI to pick the GitHub project on the fly!

**Prompt to any AI assistant:**
> "I want to learn about the OpenAI Whisper speech recognition model. Explain how it works."

Claude will pull the data from GitMCP and answer the question.

## 🛠️ Tools

GitMCP provides AI assistants with several valuable tools to help them access, understand, and query GitHub repositories.

### `fetch_<repo-name>_documentation`

This tool gets the primary documentation from a GitHub repository. It works by retrieving relevant documentation (e.g., `llms.txt`). This gives the AI a good overview of what the project is about

**When it's useful:** For general questions about a project's purpose, features, or how to get started

### `search_<repo-name>_documentation`

This tool lets the AI search through a repository's documentation by providing a specific search query. Instead of loading all the documentation (which could be very large), it uses intelligent search to find just the relevant parts.

**When it's useful:** For specific questions about particular features, functions, or concepts within a project

### `fetch_url_content`

This tool helps the AI get information from links mentioned in the documentation. It retrieves the content from those links and converts it to a format the AI can easily read.

**When it's useful:** When documentation references external information that would help answer your question

### `search_<repo-name>_code`

This tool searches through the actual code in the repository using GitHub's code search. It helps AI find specific code examples or implementation details.

**When it's useful:** When you want examples of how something is implemented or need technical details not covered in documentation

> **Note:** When using the dynamic endpoint (`gitmcp.io/docs`), these tools are named slightly differently (`fetch_generic_documentation`, `search_generic_code`, and `search_generic_documentation`) and need additional information about which repository to access.

## 📊 Badge

GitMCP has a badge for your repository's README. It allows users to quickly access your documentation through their IDE or browser (using the embedded chat). It also showcases how many times your documentation has been accessed through GitMCP.

Example (`idosal/git-mcp`): [![GitMCP](https://img.shields.io/endpoint?url=https://gitmcp.io/badge/idosal/git-mcp)](https://gitmcp.io/idosal/git-mcp)

### Adding the Badge to Your Repository

Add the following to your `README.md`:

```markdown
[![GitMCP](https://img.shields.io/endpoint?url=https://gitmcp.io/badge/OWNER/REPO)](https://gitmcp.io/OWNER/REPO)
```

Replace `OWNER` with your GitHub username or organization, and `REPO` with your repository name.

### How We Count Views

Increment for each tool call on the specific repository.

### Customizing the Badge

You can customize the badge's appearance with parameters:

| Parameter | Description | Default | Example |
|-----------|-------------|---------|---------|
| `color` | Color for the badge value | `aquamarine` | `?color=green` |
| `label` | Badge label | `GitMCP` | `Documentation`

Please reach out if you need help!

## ❓ FAQ

### What is the Model Context Protocol?

The [Model Context Protocol](https://modelcontextprotocol.io/introduction) is a standard that allows AI assistants to request and receive additional context from external sources in a structured manner, enhancing their understanding and performance.

### Does GitMCP work with any AI assistant?

Yes, GitMCP is compatible with any AI assistant supporting the Model Context Protocol, including tools like Cursor, VSCode, Claude, etc.

### Is GitMCP compatible with all GitHub projects?

Absolutely! GitMCP works with any public GitHub repository without requiring any modifications. It prioritizes the `llms.txt` file and falls back to `README.md` or other pages if the former is unavailable. Future updates aim to support additional documentation methods and even generate content dynamically.

### Does GitMCP cost money?

No, GitMCP is a free service to the community with no associated costs.

## 🔒 Privacy

GitMCP is deeply committed to its users' privacy. The service doesn't have access to or store any personally identifiable information as it doesn't require authentication. In addition, it doesn't store any queries sent by the agents. Moreover, as GitMCP is an open-source project, it can be deployed independently in your environment.

GitMCP only accesses content that is already publicly available and only when queried by a user. GitMCP does not automatically scrape repositories. Before accessing any GitHub Pages site, the code checks for `robots.txt` rules and follows the directives set by site owners, allowing them to opt out. Please note that GitMCP doesn't permanently store data regarding the GitHub projects or their content.

## 👥 Contributing

We welcome contributions, feedback, and ideas! Please review our [contribution](https://github.com/idosal/git-mcp/blob/main/.github/CONTRIBUTING.md) guidelines.

### Local Development Setup

1. **Clone the repository**
   ```bash
   git clone https://github.com/idosal/git-mcp.git
   cd git-mcp
   ```

2. **Install dependencies**
   ```bash
   pnpm install
   ```

3. **Run locally for development**
   ```bash
   npm run dev
   # or
   pnpm dev
   ```

#### Using MCP Inspector for Testing

1. Install the MCP Inspector tool:
   ```bash
   npx @modelcontextprotocol/inspector
   ```

2. In the inspector interface:
   - Set Transport Type to `SSE`
   - Enter your GitMCP URL (e.g., `http://localhost:5173/docs`)
   - Click "Connect"

## 📄 License

This project is licensed under the [Apache License 2.0](LICENSE).

## Disclaimer

GitMCP is provided "as is" without warranty of any kind. While we strive to ensure the reliability and security of our service, we are not responsible for any damages or issues that may arise from its use. GitHub projects accessed through GitMCP are subject to their respective owners' terms and conditions. GitMCP is not affiliated with GitHub or any of the mentioned AI tools.


## Star History

[![Star History Chart](https://api.star-history.com/svg?repos=idosal/git-mcp&type=Timeline)](https://www.star-history.com/#idosal/git-mcp&Timeline)


================================================
FILE: SECURITY.md
================================================
# Security Policy

GitMCP is committed to maintaining the security and privacy of its users. We welcome vulnerability disclosures, suggestions, and feedback that can improve it.

## Reporting a Vulnerability

Please report vulnerabilities using this private [form](https://forms.gle/mMoVv69XFyPqDUTX8). All vulnerabilities will be patched as soon as possible.


================================================
FILE: app/app.css
================================================
@import "tailwindcss";

@theme {
  --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif,
    "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}

html,
body {
  @apply bg-white dark:bg-gray-950;

  @media (prefers-color-scheme: dark) {
    color-scheme: dark;
  }
}


================================================
FILE: app/chat/ai/providers.server.ts
================================================
import { createOpenAI } from "@ai-sdk/openai";
import { createGroq } from "@ai-sdk/groq";
import { createAnthropic } from "@ai-sdk/anthropic";
import { createXai } from "@ai-sdk/xai";

import {
  customProvider,
  wrapLanguageModel,
  extractReasoningMiddleware,
  type LanguageModel,
} from "ai";
import type { modelID, StorageKey } from "./providers.shared";

const middleware = extractReasoningMiddleware({
  tagName: "think",
});

export const getModel = (
  env: CloudflareEnvironment,
  apiKeys: Partial<Record<StorageKey, string>>,
) => {
  // Helper to get API keys from environment variables first, then localStorage
  const getApiKey = (key: StorageKey): string | undefined => {
    // Check for environment variables first
    if (env[key]) {
      return env[key] || undefined;
    }

    // Check for API keys in localStorage
    if (apiKeys[key]) {
      return apiKeys[key] || undefined;
    }

    return undefined;
  };

  // Create provider instances with API keys from env/user/localStorage
  const openaiClient = createOpenAI({
    apiKey: getApiKey("OPENAI_API_KEY"),
  });

  const anthropicClient = createAnthropic({
    apiKey: getApiKey("ANTHROPIC_API_KEY"),
  });

  const groqClient = createGroq({
    apiKey: getApiKey("GROQ_API_KEY"),
  });

  const xaiClient = createXai({
    apiKey: getApiKey("XAI_API_KEY"),
  });

  const languageModels: Record<modelID, LanguageModel> = {
    "gpt-4.1-mini": openaiClient("gpt-4.1-mini"),
    "claude-3-7-sonnet": anthropicClient("claude-3-7-sonnet-20250219"),
    "qwen-qwq": wrapLanguageModel({
      model: groqClient("qwen-qwq-32b"),
      middleware,
    }),
    "grok-3-mini": xaiClient("grok-3-mini-latest"),
  };

  const model = customProvider({
    languageModels,
  });

  return model;
};


================================================
FILE: app/chat/ai/providers.shared.ts
================================================
export interface ModelInfo {
  provider: string;
  name: string;
  description: string;
  apiVersion: string;
  capabilities: string[];
}

export type StorageKey =
  | "OPENAI_API_KEY"
  | "ANTHROPIC_API_KEY"
  | "GROQ_API_KEY"
  | "XAI_API_KEY";

export type modelID =
  | "gpt-4.1-mini"
  | "claude-3-7-sonnet"
  | "qwen-qwq"
  | "grok-3-mini";

export const modelDetails: Record<modelID, ModelInfo> = {
  "gpt-4.1-mini": {
    provider: "OpenAI",
    name: "GPT-4.1 Mini",
    description:
      "Compact version of OpenAI's GPT-4.1 with good balance of capabilities, including vision.",
    apiVersion: "gpt-4.1-mini",
    capabilities: ["Balance", "Creative", "Vision"],
  },
  "claude-3-7-sonnet": {
    provider: "Anthropic",
    name: "Claude 3.7 Sonnet",
    description:
      "Latest version of Anthropic's Claude 3.7 Sonnet with strong reasoning and coding capabilities.",
    apiVersion: "claude-3-7-sonnet-20250219",
    capabilities: ["Reasoning", "Efficient", "Agentic"],
  },
  "qwen-qwq": {
    provider: "Groq",
    name: "Qwen QWQ",
    description:
      "Latest version of Alibaba's Qwen QWQ with strong reasoning and coding capabilities.",
    apiVersion: "qwen-qwq",
    capabilities: ["Reasoning", "Efficient", "Agentic"],
  },
  "grok-3-mini": {
    provider: "XAI",
    name: "Grok 3 Mini",
    description:
      "Latest version of XAI's Grok 3 Mini with strong reasoning and coding capabilities.",
    apiVersion: "grok-3-mini-latest",
    capabilities: ["Reasoning", "Efficient", "Agentic"],
  },
};

export const MODELS = Object.keys(modelDetails);

export const defaultModel: modelID = "qwen-qwq";


================================================
FILE: app/chat/components/api-key-manager.tsx
================================================
import { useState, useCallback } from "react";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "~/chat/components/ui/dialog";
import { Button } from "~/chat/components/ui/button";
import { Input } from "~/chat/components/ui/input";
import { Label } from "~/chat/components/ui/label";
import { toast } from "sonner";
import { STORAGE_KEYS } from "~/chat/lib/constants";
import type { StorageKey } from "../ai/providers.shared";
import { useApiKeys } from "./api-keys-provider";

// API key configuration
export interface ApiKeyConfig {
  name: string;
  key: string;
  storageKey: StorageKey;
  label: string;
  placeholder: string;
}

// Available API keys configuration
export const API_KEYS_CONFIG: readonly ApiKeyConfig[] = [
  {
    name: "OpenAI",
    key: "openai",
    storageKey: "OPENAI_API_KEY",
    label: "OpenAI API Key",
    placeholder: "sk-...",
  },
  {
    name: "Anthropic",
    key: "anthropic",
    storageKey: "ANTHROPIC_API_KEY",
    label: "Anthropic API Key",
    placeholder: "sk-ant-...",
  },
  {
    name: "Groq",
    key: "groq",
    storageKey: "GROQ_API_KEY",
    label: "Groq API Key",
    placeholder: "gsk_...",
  },
  {
    name: "XAI",
    key: "xai",
    storageKey: "XAI_API_KEY",
    label: "XAI API Key",
    placeholder: "xai-...",
  },
] as const;

interface ApiKeyManagerProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
}

export function ApiKeyManager({ open, onOpenChange }: ApiKeyManagerProps) {
  // State to store API keys
  const { apiKeys, setApiKeys } = useApiKeys();
  const [localApiKeys, setLocalApiKeys] =
    useState<Partial<Record<StorageKey, string>>>(apiKeys);

  // Update API key in state
  const handleApiKeyChange = useCallback(
    (storageKey: StorageKey, value: string) => {
      setLocalApiKeys((prev) => ({
        ...prev,
        [storageKey]: value,
      }));
    },
    [],
  );

  // Save API keys to localStorage
  const handleSaveApiKeys = useCallback(() => {
    try {
      setApiKeys(localApiKeys);
      localStorage.setItem(STORAGE_KEYS.API_KEYS, JSON.stringify(localApiKeys));

      toast.success("API keys saved successfully");
      onOpenChange(false);
    } catch (error) {
      console.error("Error saving API keys:", error);
      toast.error("Failed to save API keys");
    }
  }, [localApiKeys, setApiKeys, onOpenChange]);

  // Clear all API keys
  const handleClearApiKeys = useCallback(() => {
    try {
      setLocalApiKeys({});
      setApiKeys({});
      toast.success("All API keys cleared");
    } catch (error) {
      console.error("Error clearing API keys:", error);
      toast.error("Failed to clear API keys");
    }
  }, [setApiKeys, localApiKeys]);

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-[500px]">
        <DialogHeader>
          <DialogTitle>API Key Settings</DialogTitle>
          <DialogDescription>
            Enter your own API keys for different AI providers. Keys are stored
            securely in your browser&apos;s local storage.
          </DialogDescription>
        </DialogHeader>

        <div className="grid gap-4 py-4">
          {API_KEYS_CONFIG.map((config) => (
            <div key={config.storageKey} className="grid gap-2">
              <Label htmlFor={config.storageKey}>{config.label}</Label>
              <Input
                id={config.storageKey}
                type="password"
                value={localApiKeys[config.storageKey] || ""}
                onChange={(e) =>
                  handleApiKeyChange(config.storageKey, e.target.value)
                }
                placeholder={config.placeholder}
              />
            </div>
          ))}
        </div>

        <DialogFooter className="flex justify-between sm:justify-between">
          <Button variant="destructive" onClick={handleClearApiKeys}>
            Clear All Keys
          </Button>
          <div className="flex gap-2">
            <Button variant="outline" onClick={() => onOpenChange(false)}>
              Cancel
            </Button>
            <Button onClick={handleSaveApiKeys}>Save Keys</Button>
          </div>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}


================================================
FILE: app/chat/components/api-keys-provider.tsx
================================================
import { createContext, useContext } from "react";
import type { StorageKey } from "../ai/providers.shared";
import { STORAGE_KEYS } from "../lib/constants";
import { useLocalStorage } from "../lib/hooks/use-local-storage";

const ApiKeysContext = createContext<{
  apiKeys: Partial<Record<StorageKey, string>>;
  setApiKeys: (apiKeys: Partial<Record<StorageKey, string>>) => void;
}>({
  apiKeys: {},
  setApiKeys: () => {},
});

export function ApiKeysProvider({ children }: { children: React.ReactNode }) {
  const [apiKeys, setApiKeys] = useLocalStorage<
    Partial<Record<StorageKey, string>>
  >(STORAGE_KEYS.API_KEYS, {});

  return (
    <ApiKeysContext.Provider value={{ apiKeys, setApiKeys }}>
      {children}
    </ApiKeysContext.Provider>
  );
}

export function useApiKeys() {
  return useContext(ApiKeysContext);
}


================================================
FILE: app/chat/components/chat-sidebar.tsx
================================================
"use client";

import { useState } from "react";
import {
  ServerIcon,
  Settings,
  Sparkles,
  ChevronsUpDown,
  Github,
  Key,
} from "lucide-react";
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupContent,
  SidebarHeader,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuBadge,
  useSidebar,
} from "~/chat/components/ui/sidebar";
import { Badge } from "~/chat/components/ui/badge";
import { MCPServerManager } from "./mcp-server-manager";
import { ApiKeyManager } from "./api-key-manager";
import { ThemeToggle } from "./theme-toggle";
import { cn } from "~/chat/lib/utils";

import { useMCP } from "~/chat/lib/context/mcp-context";

export function ChatSidebar() {
  const [mcpSettingsOpen, setMcpSettingsOpen] = useState(false);
  const [apiKeySettingsOpen, setApiKeySettingsOpen] = useState(false);
  const { state } = useSidebar();
  const isCollapsed = state === "collapsed";

  // Get MCP server data from context
  const {
    mcpServers,
    setMcpServers,
    selectedMcpServers,
    setSelectedMcpServers,
  } = useMCP();

  // Get active MCP servers status
  const activeServersCount = selectedMcpServers.length;

  return (
    <Sidebar
      className="shadow-sm bg-background/80 dark:bg-background/40 ocean:bg-background/40 backdrop-blur-md"
      collapsible="icon"
    >
      <SidebarHeader className="p-4 border-b border-border/40">
        <div className="flex items-center justify-start">
          <div
            className={`flex items-center gap-2 ${
              isCollapsed ? "justify-center w-full" : ""
            }`}
          >
            <div
              className={`relative rounded-full bg-primary/70 flex items-center justify-center ${
                isCollapsed ? "size-5 p-3" : "size-6"
              }`}
            >
              <img
                src="/img/icon_cropped.png"
                alt="GitMCP Logo"
                width={24}
                height={24}
                className="absolute transform scale-75"
              />
            </div>
            {!isCollapsed && (
              <div className="font-semibold text-lg text-foreground/90">
                GitMCP Chat
              </div>
            )}
          </div>
        </div>
      </SidebarHeader>

      <SidebarContent className="flex flex-col h-[calc(100vh-8rem)]">
        <SidebarGroup className="flex-shrink-0 pl-0 h-full">
          <SidebarGroupContent className="h-full flex flex-col">
            <SidebarMenu className="h-full flex flex-col">
              <SidebarMenuItem>
                <SidebarMenuButton
                  onClick={() => setMcpSettingsOpen(true)}
                  className={cn(
                    "w-full flex items-center gap-2 transition-all",
                    "hover:bg-secondary/50 active:bg-secondary/70 cursor-pointer",
                  )}
                  tooltip={isCollapsed ? "GitMCP Servers" : undefined}
                >
                  <ServerIcon
                    className={cn(
                      "h-4 w-4 flex-shrink-0",
                      activeServersCount > 0
                        ? "text-primary"
                        : "text-muted-foreground",
                    )}
                  />
                  {!isCollapsed && (
                    <span className="flex-grow text-sm text-foreground/80">
                      GitMCP Servers
                    </span>
                  )}
                  {activeServersCount > 0 && !isCollapsed ? (
                    <Badge
                      variant="secondary"
                      className="ml-auto text-[10px] px-1.5 py-0 h-5 bg-secondary/80"
                    >
                      {activeServersCount}
                    </Badge>
                  ) : activeServersCount > 0 && isCollapsed ? (
                    <SidebarMenuBadge className="bg-secondary/80 text-secondary-foreground">
                      {activeServersCount}
                    </SidebarMenuBadge>
                  ) : null}
                </SidebarMenuButton>
              </SidebarMenuItem>
              <div className="border-b border-border/40 w-full" />
              <SidebarMenuItem>
                <SidebarMenuButton
                  onClick={() => setApiKeySettingsOpen(true)}
                  className="w-full flex items-center gap-2 transition-all hover:bg-secondary/50 active:bg-secondary/70 cursor-pointer"
                >
                  <Key className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
                  <span className="flex-grow text-sm text-foreground/80">
                    API Keys
                  </span>
                </SidebarMenuButton>
              </SidebarMenuItem>
              <SidebarMenuItem className="mt-auto">
                <SidebarMenuButton
                  onClick={() =>
                    window.open("https://git.new/gitmcp", "_blank")
                  }
                  className="w-full flex items-center gap-2 transition-all hover:bg-secondary/50 active:bg-secondary/70 cursor-pointer"
                >
                  <Github className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
                  <span className="flex-grow text-sm text-foreground/80">
                    GitHub
                  </span>
                </SidebarMenuButton>
              </SidebarMenuItem>
              <SidebarMenuItem>
                <SidebarMenuButton
                  asComponent="div"
                  className="w-full p-2 flex items-center justify-between gap-2 transition-all hover:bg-secondary/50 active:bg-secondary/70 cursor-default"
                >
                  <div className="flex items-center gap-2">
                    <Sparkles className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
                    <span className="text-sm text-foreground/80">Theme</span>
                  </div>
                  <ThemeToggle className="h-6 w-6" />
                </SidebarMenuButton>
              </SidebarMenuItem>
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
      <SidebarFooter className="p-4 border-t border-border/40 mt-auto">
        <div className="flex flex-col gap-4 items-center">
          {!isCollapsed && (
            <p className="text-xs text-muted-foreground">
              Built with{" "}
              <a
                target="_blank"
                rel="noopener noreferrer"
                href="https://git.new/s-mcp"
                className="text-primary hover:text-primary/80"
              >
                Scira Chat
              </a>
            </p>
          )}
        </div>
        <MCPServerManager
          servers={mcpServers}
          onServersChange={setMcpServers}
          selectedServers={selectedMcpServers}
          onSelectedServersChange={setSelectedMcpServers}
          open={mcpSettingsOpen}
          onOpenChange={setMcpSettingsOpen}
        />

        <ApiKeyManager
          open={apiKeySettingsOpen}
          onOpenChange={setApiKeySettingsOpen}
        />
      </SidebarFooter>
    </Sidebar>
  );
}


================================================
FILE: app/chat/components/chat.tsx
================================================
"use client";

import { defaultModel, type modelID } from "~/chat/ai/providers.shared";
import { useChat } from "@ai-sdk/react";
import { Textarea } from "./textarea";
import { ProjectOverview } from "./project-overview";
import { Messages } from "./messages";
import { toast } from "sonner";
import { useLocalStorage } from "~/chat/lib/hooks/use-local-storage";
import { useMCP } from "~/chat/lib/context/mcp-context";
import { useCallback } from "react";
import { useApiKeys } from "./api-keys-provider";

const CHAT_API_URL = "https://chat-api-worker.idosalomon.workers.dev/api/chat";

export default function Chat() {
  const [selectedModel, setSelectedModel] = useLocalStorage<modelID>(
    "selectedModel",
    defaultModel,
  );

  const { apiKeys } = useApiKeys();

  // Get MCP server data from context
  const { mcpServersForApi } = useMCP();

  const { messages, input, handleInputChange, handleSubmit, status, stop } =
    useChat({
      api: CHAT_API_URL,
      maxSteps: 20,
      body: {
        selectedModel,
        mcpServers: mcpServersForApi,
        apiKeys,
      },
      experimental_throttle: 500,
      onError: (error) => {
        toast.error(
          error.message.length > 0
            ? error.message
            : "An error occurred, please try again later.",
          { position: "top-center", richColors: true },
        );
      },
    });

  // Custom submit handler
  const handleFormSubmit = useCallback(
    (e: React.FormEvent<HTMLFormElement>) => {
      e.preventDefault();
      handleSubmit(e);
    },
    [input, handleSubmit],
  );

  const isLoading = status === "streaming" || status === "submitted";

  return (
    <div className="h-dvh flex flex-col justify-center w-full max-w-3xl mx-auto px-4 sm:px-6 md:py-4">
      {messages.length === 0 ? (
        <div className="max-w-xl mx-auto w-full">
          <ProjectOverview />
          <form onSubmit={handleFormSubmit} className="mt-4 w-full mx-auto">
            <Textarea
              selectedModel={selectedModel}
              setSelectedModel={setSelectedModel}
              handleInputChange={handleInputChange}
              input={input}
              isLoading={isLoading}
              status={status}
              stop={stop}
            />
          </form>
        </div>
      ) : (
        <>
          <div className="flex-1 overflow-y-auto min-h-0 pb-2">
            <Messages
              messages={messages}
              isLoading={isLoading}
              status={status}
            />
          </div>
          <form
            onSubmit={handleFormSubmit}
            className="mt-2 w-full mx-auto mb-4 sm:mb-auto"
          >
            <Textarea
              selectedModel={selectedModel}
              setSelectedModel={setSelectedModel}
              handleInputChange={handleInputChange}
              input={input}
              isLoading={isLoading}
              status={status}
              stop={stop}
            />
          </form>
        </>
      )}
    </div>
  );
}


================================================
FILE: app/chat/components/copy-button.tsx
================================================
import { CheckIcon, CopyIcon } from "lucide-react";
import { cn } from "~/chat/lib/utils";
import { useCopy } from "~/chat/lib/hooks/use-copy";
import { Button } from "./ui/button";

interface CopyButtonProps {
  text: string;
  className?: string;
}

export function CopyButton({ text, className }: CopyButtonProps) {
  const { copied, copy } = useCopy();

  return (
    <Button
      variant="ghost"
      size="sm"
      className={cn(
        "transition-opacity opacity-0 group-hover/message:opacity-100 gap-1.5",
        className,
      )}
      onClick={() => copy(text)}
      title="Copy to clipboard"
    >
      {copied ? (
        <>
          <CheckIcon className="h-4 w-4" />
          <span className="text-xs">Copied!</span>
        </>
      ) : (
        <>
          <CopyIcon className="h-4 w-4" />
          <span className="text-xs">Copy</span>
        </>
      )}
    </Button>
  );
}


================================================
FILE: app/chat/components/icons.tsx
================================================
import type { SVGProps } from "react";

export const VercelIcon = ({ size = 17 }) => {
  return (
    <svg
      height={size}
      strokeLinejoin="round"
      viewBox="0 0 16 16"
      width={size}
      style={{ color: "currentcolor" }}
    >
      <title>Vercel Icon</title>
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M8 1L16 15H0L8 1Z"
        fill="currentColor"
      />
    </svg>
  );
};

export const SpinnerIcon = ({ size = 16 }: { size?: number }) => (
  <svg
    height={size}
    strokeLinejoin="round"
    viewBox="0 0 16 16"
    width={size}
    style={{ color: "currentcolor" }}
  >
    <title>Spinner Icon</title>
    <g clipPath="url(#clip0_2393_1490)">
      <path d="M8 0V4" stroke="currentColor" strokeWidth="1.5" />
      <path
        opacity="0.5"
        d="M8 16V12"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        opacity="0.9"
        d="M3.29773 1.52783L5.64887 4.7639"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        opacity="0.1"
        d="M12.7023 1.52783L10.3511 4.7639"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        opacity="0.4"
        d="M12.7023 14.472L10.3511 11.236"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        opacity="0.6"
        d="M3.29773 14.472L5.64887 11.236"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        opacity="0.2"
        d="M15.6085 5.52783L11.8043 6.7639"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        opacity="0.7"
        d="M0.391602 10.472L4.19583 9.23598"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        opacity="0.3"
        d="M15.6085 10.4722L11.8043 9.2361"
        stroke="currentColor"
        strokeWidth="1.5"
      />
      <path
        opacity="0.8"
        d="M0.391602 5.52783L4.19583 6.7639"
        stroke="currentColor"
        strokeWidth="1.5"
      />
    </g>
    <defs>
      <clipPath id="clip0_2393_1490">
        <rect width="16" height="16" fill="white" />
      </clipPath>
    </defs>
  </svg>
);

export const Github = (props: SVGProps<SVGSVGElement>) => (
  <svg
    viewBox="0 0 256 250"
    width="1em"
    height="1em"
    fill="currentColor"
    xmlns="http://www.w3.org/2000/svg"
    preserveAspectRatio="xMidYMid"
    {...props}
  >
    <title>GitHub Icon</title>
    <path d="M128.001 0C57.317 0 0 57.307 0 128.001c0 56.554 36.676 104.535 87.535 121.46 6.397 1.185 8.746-2.777 8.746-6.158 0-3.052-.12-13.135-.174-23.83-35.61 7.742-43.124-15.103-43.124-15.103-5.823-14.795-14.213-18.73-14.213-18.73-11.613-7.944.876-7.78.876-7.78 12.853.902 19.621 13.19 19.621 13.19 11.417 19.568 29.945 13.911 37.249 10.64 1.149-8.272 4.466-13.92 8.127-17.116-28.431-3.236-58.318-14.212-58.318-63.258 0-13.975 5-25.394 13.188-34.358-1.329-3.224-5.71-16.242 1.24-33.874 0 0 10.749-3.44 35.21 13.121 10.21-2.836 21.16-4.258 32.038-4.307 10.878.049 21.837 1.47 32.066 4.307 24.431-16.56 35.165-13.12 35.165-13.12 6.967 17.63 2.584 30.65 1.255 33.873 8.207 8.964 13.173 20.383 13.173 34.358 0 49.163-29.944 59.988-58.447 63.157 4.591 3.972 8.682 11.762 8.682 23.704 0 17.126-.148 30.91-.148 35.126 0 3.407 2.304 7.398 8.792 6.14C219.37 232.5 256 184.537 256 128.002 256 57.307 198.691 0 128.001 0Zm-80.06 182.34c-.282.636-1.283.827-2.194.39-.929-.417-1.45-1.284-1.15-1.922.276-.655 1.279-.838 2.205-.399.93.418 1.46 1.293 1.139 1.931Zm6.296 5.618c-.61.566-1.804.303-2.614-.591-.837-.892-.994-2.086-.375-2.66.63-.566 1.787-.301 2.626.591.838.903 1 2.088.363 2.66Zm4.32 7.188c-.785.545-2.067.034-2.86-1.104-.784-1.138-.784-2.503.017-3.05.795-.547 2.058-.055 2.861 1.075.782 1.157.782 2.522-.019 3.08Zm7.304 8.325c-.701.774-2.196.566-3.29-.49-1.119-1.032-1.43-2.496-.726-3.27.71-.776 2.213-.558 3.315.49 1.11 1.03 1.45 2.505.701 3.27Zm9.442 2.81c-.31 1.003-1.75 1.459-3.199 1.033-1.448-.439-2.395-1.613-2.103-2.626.301-1.01 1.747-1.484 3.207-1.028 1.446.436 2.396 1.602 2.095 2.622Zm10.744 1.193c.036 1.055-1.193 1.93-2.715 1.95-1.53.034-2.769-.82-2.786-1.86 0-1.065 1.202-1.932 2.733-1.958 1.522-.03 2.768.818 2.768 1.868Zm10.555-.405c.182 1.03-.875 2.088-2.387 2.37-1.485.271-2.861-.365-3.05-1.386-.184-1.056.893-2.114 2.376-2.387 1.514-.263 2.868.356 3.061 1.403Z" />
  </svg>
);

export const XAiIcon = ({ size = 16 }) => {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      height={size}
      version="1.1"
      viewBox="0 0 438.7 481.4"
    >
      <title>xAI Icon</title>
      <path d="M355.5,155.1l8.3,326.4h66.6l8.3-445.2-83.2,118.8ZM438.7,0h-101.6l-159.4,227.6,50.8,72.5L438.7,0ZM0,481.4h101.6l50.8-72.5-50.8-72.5L0,481.4ZM0,155.1l228.5,326.4h101.6L101.6,155.1H0Z" />
    </svg>
  );
};


================================================
FILE: app/chat/components/input.tsx
================================================
import { ArrowUp } from "lucide-react";
import { Input as ShadcnInput } from "./ui/input";

interface InputProps {
  input: string;
  handleInputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
  isLoading: boolean;
  status: string;
  stop: () => void;
}

export const Input = ({
  input,
  handleInputChange,
  isLoading,
  status,
  stop,
}: InputProps) => {
  return (
    <div className="relative w-full">
      <ShadcnInput
        className="bg-secondary py-6 w-full rounded-xl pr-12"
        value={input}
        autoFocus
        placeholder={"Say something..."}
        onChange={handleInputChange}
      />
      {status === "streaming" || status === "submitted" ? (
        <button
          type="button"
          onClick={stop}
          className="cursor-pointer absolute right-2 top-1/2 -translate-y-1/2 rounded-full p-2 bg-black hover:bg-zinc-800 disabled:bg-zinc-300 disabled:cursor-not-allowed transition-colors"
        >
          <div className="animate-spin h-4 w-4">
            <svg className="h-4 w-4 text-white" viewBox="0 0 24 24">
              <circle
                className="opacity-25"
                cx="12"
                cy="12"
                r="10"
                stroke="currentColor"
                strokeWidth="4"
                fill="none"
              />
              <path
                className="opacity-75"
                fill="currentColor"
                d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
              />
            </svg>
          </div>
        </button>
      ) : (
        <button
          type="submit"
          disabled={isLoading || !input.trim()}
          className="absolute right-2 top-1/2 -translate-y-1/2 rounded-full p-2 bg-black hover:bg-zinc-800 disabled:bg-zinc-300 disabled:cursor-not-allowed transition-colors"
        >
          <ArrowUp className="h-4 w-4 text-white" />
        </button>
      )}
    </div>
  );
};


================================================
FILE: app/chat/components/markdown.tsx
================================================
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { memo } from "react";
import ReactMarkdown, { type Components } from "react-markdown";
import remarkGfm from "remark-gfm";
import { cn } from "~/chat/lib/utils";

const components: Partial<Components> = {
  pre: ({ children, ...props }) => (
    <pre
      className="overflow-x-auto rounded-lg bg-zinc-100 dark:bg-zinc-800/50 black:bg-zinc-800/50 ocean:bg-zinc-800/50 p-2.5 my-1.5 text-sm"
      {...props}
    >
      {children}
    </pre>
  ),
  code: ({
    children,
    className,
    ...props
  }: React.HTMLProps<HTMLElement> & { className?: string }) => {
    const match = /language-(\w+)/.exec(className || "");
    const isInline = !match && !className;

    if (isInline) {
      return (
        <code
          className="px-1 py-0.5 rounded-md bg-zinc-100 dark:bg-zinc-800/50 black:bg-zinc-800/50 ocean:bg-zinc-800/50 text-zinc-700 dark:text-zinc-300 black:text-zinc-300 ocean:text-zinc-300 text-[0.9em] font-mono"
          {...props}
        >
          {children}
        </code>
      );
    }
    return (
      <code className={cn("block font-mono text-sm", className)} {...props}>
        {children}
      </code>
    );
  },
  ol: ({ node, children, ...props }) => (
    <ol
      className="list-decimal list-outside ml-4 space-y-0.5 my-1.5"
      {...props}
    >
      {children}
    </ol>
  ),
  ul: ({ node, children, ...props }) => (
    <ul className="list-disc list-outside ml-4 space-y-0.5 my-1.5" {...props}>
      {children}
    </ul>
  ),
  li: ({ node, children, ...props }) => (
    <li className="leading-normal" {...props}>
      {children}
    </li>
  ),
  p: ({ node, children, ...props }) => (
    <p className="leading-relaxed my-1" {...props}>
      {children}
    </p>
  ),
  strong: ({ node, children, ...props }) => (
    <strong className="font-semibold" {...props}>
      {children}
    </strong>
  ),
  em: ({ node, children, ...props }) => (
    <em className="italic" {...props}>
      {children}
    </em>
  ),
  blockquote: ({ node, children, ...props }) => (
    <blockquote
      className="border-l-2 border-zinc-200 dark:border-zinc-700 black:border-zinc-700 ocean:border-zinc-700 pl-3 my-1.5 italic text-zinc-600 dark:text-zinc-400 black:text-zinc-400 ocean:text-zinc-400"
      {...props}
    >
      {children}
    </blockquote>
  ),
  a: ({ node, children, ...props }) => (
    <a
      className="text-blue-500 hover:underline hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 black:text-blue-400 black:hover:text-blue-300 ocean:text-blue-400 ocean:hover:text-blue-300 transition-colors"
      target="_blank"
      rel="noreferrer"
      {...props}
    >
      {children}
    </a>
  ),
  h1: ({ node, children, ...props }) => (
    <h1
      className="text-2xl font-semibold mt-3 mb-1.5 text-zinc-800 dark:text-zinc-200 black:text-zinc-200 ocean:text-zinc-200"
      {...props}
    >
      {children}
    </h1>
  ),
  h2: ({ node, children, ...props }) => (
    <h2
      className="text-xl font-semibold mt-2.5 mb-1.5 text-zinc-800 dark:text-zinc-200 black:text-zinc-200 ocean:text-zinc-200"
      {...props}
    >
      {children}
    </h2>
  ),
  h3: ({ node, children, ...props }) => (
    <h3
      className="text-lg font-semibold mt-2 mb-1 text-zinc-800 dark:text-zinc-200 black:text-zinc-200 ocean:text-zinc-200"
      {...props}
    >
      {children}
    </h3>
  ),
  h4: ({ node, children, ...props }) => (
    <h4
      className="text-base font-semibold mt-2 mb-1 text-zinc-800 dark:text-zinc-200 black:text-zinc-200 ocean:text-zinc-200"
      {...props}
    >
      {children}
    </h4>
  ),
  h5: ({ node, children, ...props }) => (
    <h5
      className="text-sm font-semibold mt-2 mb-1 text-zinc-800 dark:text-zinc-200 black:text-zinc-200 ocean:text-zinc-200"
      {...props}
    >
      {children}
    </h5>
  ),
  h6: ({ node, children, ...props }) => (
    <h6
      className="text-xs font-semibold mt-2 mb-0.5 text-zinc-800 dark:text-zinc-200 black:text-zinc-200 ocean:text-zinc-200"
      {...props}
    >
      {children}
    </h6>
  ),
  table: ({ node, children, ...props }) => (
    <div className="my-1.5 overflow-x-auto">
      <table
        className="min-w-full divide-y divide-zinc-200 dark:divide-zinc-700 black:divide-zinc-700 ocean:divide-zinc-700"
        {...props}
      >
        {children}
      </table>
    </div>
  ),
  thead: ({ node, children, ...props }) => (
    <thead
      className="bg-zinc-50 dark:bg-zinc-800/50 black:bg-zinc-800/50 ocean:bg-zinc-800/50"
      {...props}
    >
      {children}
    </thead>
  ),
  tbody: ({ node, children, ...props }) => (
    <tbody
      className="divide-y divide-zinc-200 dark:divide-zinc-700 black:divide-zinc-700 ocean:divide-zinc-700 bg-white dark:bg-transparent black:bg-transparent ocean:bg-transparent"
      {...props}
    >
      {children}
    </tbody>
  ),
  tr: ({ node, children, ...props }) => (
    <tr
      className="transition-colors hover:bg-zinc-50 dark:hover:bg-zinc-800/30 black:hover:bg-zinc-800/30 ocean:hover:bg-zinc-800/30"
      {...props}
    >
      {children}
    </tr>
  ),
  th: ({ node, children, ...props }) => (
    <th
      className="px-3 py-1.5 text-left text-xs font-medium text-zinc-500 dark:text-zinc-400 black:text-zinc-400 ocean:text-zinc-400 uppercase tracking-wider"
      {...props}
    >
      {children}
    </th>
  ),
  td: ({ node, children, ...props }) => (
    <td className="px-3 py-1.5 text-sm" {...props}>
      {children}
    </td>
  ),
  hr: ({ node, ...props }) => (
    <hr
      className="my-1.5 border-zinc-200 dark:border-zinc-700 black:border-zinc-700 ocean:border-zinc-700"
      {...props}
    />
  ),
};

const remarkPlugins = [remarkGfm];

const NonMemoizedMarkdown = ({ children }: { children: string }) => {
  return (
    <ReactMarkdown remarkPlugins={remarkPlugins} components={components}>
      {children}
    </ReactMarkdown>
  );
};

export const Markdown = memo(
  NonMemoizedMarkdown,
  (prevProps, nextProps) => prevProps.children === nextProps.children,
);


================================================
FILE: app/chat/components/mcp-server-manager.tsx
================================================
"use client";

import { useState } from "react";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "./ui/dialog";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
import {
  PlusCircle,
  ServerIcon,
  Globe,
  ExternalLink,
  Trash2,
  CheckCircle,
  Edit2,
} from "lucide-react";
import { toast } from "sonner";
import type { MCPServer } from "~/chat/lib/context/mcp-context";
import { getRepoData } from "~/chat/lib/utils";

// Default template for a new MCP server
const INITIAL_NEW_SERVER: Omit<MCPServer, "id"> = {
  name: "",
  url: "",
  type: "sse",
  command: "node",
  args: [],
  env: [],
  headers: [],
};

interface MCPServerManagerProps {
  servers: MCPServer[];
  onServersChange: (servers: MCPServer[]) => void;
  selectedServers: string[];
  onSelectedServersChange: (serverIds: string[]) => void;
  open: boolean;
  onOpenChange: (open: boolean) => void;
}

export const MCPServerManager = ({
  servers,
  onServersChange,
  selectedServers,
  onSelectedServersChange,
  open,
  onOpenChange,
}: MCPServerManagerProps) => {
  const [newServer, setNewServer] =
    useState<Omit<MCPServer, "id">>(INITIAL_NEW_SERVER);
  const [view, setView] = useState<"list" | "add">("list");
  const [editingServerId, setEditingServerId] = useState<string | null>(null);

  const resetAndClose = () => {
    setView("list");
    setNewServer(INITIAL_NEW_SERVER);
    onOpenChange(false);
  };

  const generateServerFromUrl = (url: string): MCPServer | null => {
    if (!url) {
      toast.error("Server URL is required");
      return null;
    }

    const { owner, repo } = getRepoData(url);

    if (!owner || (owner != "docs" && !repo)) {
      toast.error("Invalid server URL");
      return null;
    }

    const newUrl = ["https://gitmcp.io", owner, repo].filter(Boolean).join("/");

    const name = repo ? `${repo} Docs` : "MCP Docs";

    const id = crypto.randomUUID();

    return { id, name, url: newUrl, type: "sse" } as const;
  };

  const addServer = () => {
    const newServerToAdd = generateServerFromUrl(newServer.url);
    if (!newServerToAdd) {
      return;
    }
    const updatedServers = [...servers, newServerToAdd];
    onServersChange(updatedServers);

    toast.success(`Added MCP server: ${newServerToAdd.name}`);
    setView("list");
    setNewServer(INITIAL_NEW_SERVER);
  };

  const removeServer = (id: string, e: React.MouseEvent) => {
    e.stopPropagation();
    const updatedServers = servers.filter((server) => server.id !== id);
    onServersChange(updatedServers);

    // If the removed server was selected, remove it from selected servers
    if (selectedServers.includes(id)) {
      onSelectedServersChange(
        selectedServers.filter((serverId) => serverId !== id),
      );
    }

    toast.success("Server removed");
  };

  const toggleServer = (id: string) => {
    if (selectedServers.includes(id)) {
      // Remove from selected servers
      onSelectedServersChange(
        selectedServers.filter((serverId) => serverId !== id),
      );
      const server = servers.find((s) => s.id === id);
      if (server) {
        toast.success(`Disabled MCP server: ${server.name}`);
      }
    } else {
      // Add to selected servers
      onSelectedServersChange([...selectedServers, id]);
      const server = servers.find((s) => s.id === id);
      if (server) {
        toast.success(`Enabled MCP server: ${server.name}`);
      }
    }
  };

  const clearAllServers = () => {
    if (selectedServers.length > 0) {
      onSelectedServersChange([]);
      toast.success("All MCP servers disabled");
      resetAndClose();
    }
  };

  // Editing support
  const startEditing = (server: MCPServer) => {
    setEditingServerId(server.id);
    setNewServer({
      name: server.name,
      url: server.url,
      type: server.type,
      command: server.command,
      args: server.args,
      env: server.env,
      headers: server.headers,
    });
    setView("add");
  };

  const handleFormCancel = () => {
    if (view === "add") {
      setView("list");
      setEditingServerId(null);
      setNewServer(INITIAL_NEW_SERVER);
    } else {
      resetAndClose();
    }
  };

  const updateServer = () => {
    const newServerToUpdate = generateServerFromUrl(newServer.url);
    if (!newServerToUpdate) {
      return;
    }
    const updated = servers.map((s) =>
      s.id === editingServerId
        ? { ...newServerToUpdate, id: editingServerId! }
        : s,
    );
    onServersChange(updated);
    toast.success(`Updated MCP server: ${newServerToUpdate.name}`);
    setView("list");
    setEditingServerId(null);
    setNewServer(INITIAL_NEW_SERVER);
  };

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-[480px] max-h-[85vh] overflow-hidden flex flex-col">
        <DialogHeader>
          <DialogTitle className="flex items-center gap-2">
            <ServerIcon className="h-5 w-5 text-primary" />
            {view == "list"
              ? "MCP Server Configuration"
              : editingServerId
                ? "Edit GitMCP Server"
                : "Add New GitMCP Server"}
          </DialogTitle>
          <DialogDescription>
            {selectedServers.length > 0 && (
              <span className="block mt-1 text-xs font-medium text-primary">
                {selectedServers.length} server
                {selectedServers.length !== 1 ? "s" : ""} currently active
              </span>
            )}
          </DialogDescription>
        </DialogHeader>

        {view === "list" ? (
          <div className="flex-1 overflow-hidden flex flex-col">
            {servers.length > 0 ? (
              <div className="flex-1 overflow-hidden flex flex-col">
                <div className="flex-1 overflow-hidden flex flex-col">
                  <div className="flex items-center justify-between mb-3">
                    <h3 className="text-sm font-medium">Available Servers</h3>
                    <span className="text-xs text-muted-foreground">
                      Select multiple servers to combine their tools
                    </span>
                  </div>
                  <div className="overflow-y-auto pr-1 flex-1 gap-2.5 flex flex-col pb-16">
                    {servers
                      .sort((a, b) => {
                        const aActive = selectedServers.includes(a.id);
                        const bActive = selectedServers.includes(b.id);
                        if (aActive && !bActive) return -1;
                        if (!aActive && bActive) return 1;
                        return 0;
                      })
                      .map((server) => {
                        const isActive = selectedServers.includes(server.id);
                        return (
                          <McpServerListItem
                            key={server.id}
                            server={server}
                            isActive={isActive}
                            removeServer={removeServer}
                            startEditing={startEditing}
                            toggleServer={toggleServer}
                          />
                        );
                      })}
                  </div>
                </div>
              </div>
            ) : (
              <div className="flex-1 py-8 pb-16 flex flex-col items-center justify-center space-y-4">
                <div className="rounded-full p-3 bg-primary/10">
                  <ServerIcon className="h-7 w-7 text-primary" />
                </div>
                <div className="text-center space-y-1">
                  <h3 className="text-base font-medium">
                    No MCP Servers Added
                  </h3>
                  <p className="text-sm text-muted-foreground max-w-[300px]">
                    Add your first MCP server to access additional AI tools
                  </p>
                </div>
                <div className="flex items-center gap-1.5 text-xs text-muted-foreground mt-4">
                  <a
                    href="https://modelcontextprotocol.io"
                    target="_blank"
                    rel="noopener noreferrer"
                    className="flex items-center gap-1 hover:text-primary transition-colors"
                  >
                    Learn about MCP
                    <ExternalLink className="h-3 w-3" />
                  </a>
                </div>
              </div>
            )}
          </div>
        ) : (
          <div className="space-y-4 overflow-y-auto px-1 py-0.5 mb-14 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden">
            <div className="space-y-4">
              <div className="grid gap-1.5">
                <Label htmlFor="url" className="pb-1">
                  Server or Repository URL
                </Label>
                <Input
                  id="url"
                  value={newServer.url}
                  onChange={(e) =>
                    setNewServer({ ...newServer, url: e.target.value })
                  }
                  placeholder="https://gitmcp.io/microsoft/playwright-mcp"
                  className="relative z-0 placeholder:text-muted-foreground/60"
                />
                <p className="text-xs text-muted-foreground/80">
                  A gitmcp.io server, a github.com repository, or a github.io
                  pages site
                </p>
              </div>
            </div>
          </div>
        )}

        {/* Persistent fixed footer with buttons */}
        <div className="absolute bottom-0 left-0 right-0 p-4 bg-background border-t border-border flex justify-between z-10">
          {view === "list" ? (
            <>
              <span></span>
              <Button
                onClick={() => setView("add")}
                size="sm"
                className="gap-1.5"
              >
                <PlusCircle className="h-3.5 w-3.5" />
                Add Server
              </Button>
            </>
          ) : (
            <>
              <Button variant="outline" onClick={handleFormCancel}>
                Cancel
              </Button>
              <Button
                onClick={editingServerId ? updateServer : addServer}
                disabled={!newServer.url}
              >
                {editingServerId ? "Save Changes" : "Add Server"}
              </Button>
            </>
          )}
        </div>
      </DialogContent>
    </Dialog>
  );
};

function McpServerListItem({
  server,
  isActive,
  removeServer,
  startEditing,
  toggleServer,
}: {
  server: MCPServer;
  isActive: boolean;
  removeServer: (id: string, e: React.MouseEvent) => void;
  startEditing: (server: MCPServer) => void;
  toggleServer: (id: string) => void;
}) {
  return (
    <div
      key={server.id}
      className={`
relative flex flex-col p-3.5 rounded-xl transition-colors
border ${
        isActive
          ? "border-primary bg-primary/10"
          : "border-border hover:border-primary/30 hover:bg-primary/5"
      }
`}
    >
      {/* Server Header with Type Badge and Delete Button */}
      <div className="flex items-center justify-between mb-2">
        <div className="flex items-center gap-2">
          <Globe
            className={`h-4 w-4 ${
              isActive ? "text-primary" : "text-muted-foreground"
            } flex-shrink-0`}
          />

          <h4 className="text-sm font-medium truncate max-w-[220px]">
            {server.name}
          </h4>
        </div>
        <div className="flex items-center gap-2">
          <span className="text-xs px-2 py-0.5 rounded-full bg-secondary text-secondary-foreground">
            {server.type.toUpperCase()}
          </span>
          {!server.isFixed && (
            <>
              <button
                onClick={(e) => removeServer(server.id, e)}
                className="p-1 rounded-full hover:bg-muted/70"
                aria-label="Remove server"
              >
                <Trash2 className="h-3.5 w-3.5 text-muted-foreground" />
              </button>
              <button
                onClick={() => startEditing(server)}
                className="p-1 rounded-full hover:bg-muted/50"
                aria-label="Edit server"
              >
                <Edit2 className="h-3.5 w-3.5 text-muted-foreground" />
              </button>
            </>
          )}
        </div>
      </div>

      {/* Server Details */}
      <p className="text-xs text-muted-foreground mb-2.5 truncate">
        {server.type === "sse"
          ? server.url
          : `${server.command} ${server.args?.join(" ")}`}
      </p>

      {/* Action Button */}
      {!server.isFixed && (
        <Button
          size="sm"
          className="w-full gap-1.5 hover:text-black hover:dark:text-white hover:ocean:text-white rounded-lg"
          variant={isActive ? "default" : "outline"}
          onClick={() => toggleServer(server.id)}
        >
          {isActive && <CheckCircle className="h-3.5 w-3.5" />}
          {isActive ? "Active" : "Enable Server"}
        </Button>
      )}
    </div>
  );
}


================================================
FILE: app/chat/components/message.tsx
================================================
"use client";

import type { Message as TMessage } from "ai";
import { AnimatePresence, motion } from "motion/react";
import { memo, useCallback, useEffect, useState } from "react";
import equal from "fast-deep-equal";
import { Markdown } from "./markdown";
import { cn } from "~/chat/lib/utils";
import {
  ChevronDownIcon,
  ChevronUpIcon,
  LightbulbIcon,
  BrainIcon,
} from "lucide-react";
import { SpinnerIcon } from "./icons";
import { ToolInvocation } from "./tool-invocation";
import { CopyButton } from "./copy-button";

interface ReasoningPart {
  type: "reasoning";
  reasoning: string;
  details: Array<{ type: "text"; text: string }>;
}

interface ReasoningMessagePartProps {
  part: ReasoningPart;
  isReasoning: boolean;
}

export function ReasoningMessagePart({
  part,
  isReasoning,
}: ReasoningMessagePartProps) {
  const [isExpanded, setIsExpanded] = useState(false);

  const memoizedSetIsExpanded = useCallback((value: boolean) => {
    setIsExpanded(value);
  }, []);

  useEffect(() => {
    memoizedSetIsExpanded(isReasoning);
  }, [isReasoning, memoizedSetIsExpanded]);

  return (
    <div className="flex flex-col mb-2 group">
      {isReasoning ? (
        <div
          className={cn(
            "flex items-center gap-2.5 rounded-full py-1.5 px-3",
            "bg-indigo-50/50 dark:bg-indigo-900/10 ocean:bg-indigo-900/10 text-indigo-700 dark:text-indigo-300 ocean:text-indigo-300",
            "border border-indigo-200/50 dark:border-indigo-700/20 ocean:border-indigo-700/20 w-fit",
          )}
        >
          <div className="animate-spin h-3.5 w-3.5">
            <SpinnerIcon />
          </div>
          <div className="text-xs font-medium tracking-tight">Thinking...</div>
        </div>
      ) : (
        <button
          onClick={() => setIsExpanded(!isExpanded)}
          className={cn(
            "flex items-center justify-between w-full",
            "rounded-md py-2 px-3 mb-0.5",
            "bg-muted/50 border border-border/60 hover:border-border/80",
            "transition-all duration-150 cursor-pointer",
            isExpanded ? "bg-muted border-primary/20" : "",
          )}
        >
          <div className="flex items-center gap-2.5">
            <div
              className={cn(
                "flex items-center justify-center w-6 h-6 rounded-full",
                "bg-amber-50 dark:bg-amber-900/20 ocean:bg-amber-900/20",
                "text-amber-600 dark:text-amber-400 ocean:text-amber-400 ring-1 ring-amber-200 dark:ring-amber-700/30 ocean:ring-amber-700/30",
              )}
            >
              <LightbulbIcon className="h-3.5 w-3.5" />
            </div>
            <div className="text-sm font-medium text-foreground flex items-center gap-1.5">
              Reasoning
              <span className="text-xs text-muted-foreground font-normal">
                (click to {isExpanded ? "hide" : "view"})
              </span>
            </div>
          </div>
          <div
            className={cn(
              "flex items-center justify-center",
              "rounded-full p-0.5 w-5 h-5",
              "text-muted-foreground hover:text-foreground",
              "bg-background/80 border border-border/50",
              "transition-colors",
            )}
          >
            {isExpanded ? (
              <ChevronDownIcon className="h-3 w-3" />
            ) : (
              <ChevronUpIcon className="h-3 w-3" />
            )}
          </div>
        </button>
      )}

      <AnimatePresence initial={false}>
        {isExpanded && (
          <motion.div
            key="reasoning"
            className={cn(
              "text-sm text-muted-foreground flex flex-col gap-2",
              "pl-3.5 ml-0.5 mt-1",
              "border-l border-amber-200/50 dark:border-amber-700/30 ocean:border-amber-700/30",
            )}
            initial={{ height: 0, opacity: 0 }}
            animate={{ height: "auto", opacity: 1 }}
            exit={{ height: 0, opacity: 0 }}
            transition={{ duration: 0.2, ease: "easeInOut" }}
          >
            <div className="text-xs text-muted-foreground/70 pl-1 font-medium">
              The assistant&apos;s thought process:
            </div>
            {part.details.map((detail, detailIndex) =>
              detail.type === "text" ? (
                <div
                  key={detailIndex}
                  className="px-2 py-1.5 bg-muted/10 rounded-md border border-border/30"
                >
                  <Markdown>{detail.text}</Markdown>
                </div>
              ) : (
                "<redacted>"
              ),
            )}
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}

const PurePreviewMessage = ({
  message,
  isLatestMessage,
  status,
}: {
  message: TMessage;
  isLoading: boolean;
  status: "error" | "submitted" | "streaming" | "ready";
  isLatestMessage: boolean;
}) => {
  // Create a string with all text parts for copy functionality
  const getMessageText = () => {
    if (!message.parts) return "";
    return message.parts
      .filter((part) => part.type === "text")
      .map((part) => (part.type === "text" ? part.text : ""))
      .join("\n\n");
  };

  // Only show copy button if the message is from the assistant and not currently streaming
  const shouldShowCopyButton =
    message.role === "assistant" &&
    (!isLatestMessage || status !== "streaming");

  return (
    <AnimatePresence key={message.id}>
      <motion.div
        className={cn(
          "w-full mx-auto px-4 group/message",
          message.role === "assistant" ? "mb-8" : "mb-6",
        )}
        initial={{ y: 5, opacity: 0 }}
        animate={{ y: 0, opacity: 1 }}
        key={`message-${message.id}`}
        data-role={message.role}
      >
        <div
          className={cn(
            "flex gap-4 w-full group-data-[role=user]/message:ml-auto group-data-[role=user]/message:max-w-2xl",
            "group-data-[role=user]/message:w-fit",
          )}
        >
          <div className="flex flex-col w-full space-y-3">
            {message.parts?.map((part, i) => {
              switch (part.type) {
                case "text":
                  return (
                    <motion.div
                      initial={{ y: 5, opacity: 0 }}
                      animate={{ y: 0, opacity: 1 }}
                      key={`message-${message.id}-part-${i}`}
                      className="flex flex-row gap-2 items-start w-full"
                    >
                      <div
                        className={cn("flex flex-col gap-3 w-full", {
                          "bg-secondary text-secondary-foreground px-4 py-3 rounded-2xl":
                            message.role === "user",
                        })}
                      >
                        <Markdown>{part.text}</Markdown>
                      </div>
                    </motion.div>
                  );
                case "tool-invocation":
                  const { toolName, state, args } = part.toolInvocation;
                  const result =
                    "result" in part.toolInvocation
                      ? part.toolInvocation.result
                      : null;

                  return (
                    <ToolInvocation
                      key={`message-${message.id}-part-${i}`}
                      toolName={toolName}
                      state={state}
                      args={args}
                      result={result}
                      isLatestMessage={isLatestMessage}
                      status={status}
                    />
                  );
                case "reasoning":
                  return (
                    <ReasoningMessagePart
                      key={`message-${message.id}-${i}`}
                      // @ts-expect-error part
                      part={part}
                      isReasoning={
                        (message.parts &&
                          status === "streaming" &&
                          i === message.parts.length - 1) ??
                        false
                      }
                    />
                  );
                default:
                  return null;
              }
            })}
            {shouldShowCopyButton && (
              <div className="flex justify-start mt-2">
                <CopyButton text={getMessageText()} />
              </div>
            )}
          </div>
        </div>
      </motion.div>
    </AnimatePresence>
  );
};

export const Message = memo(PurePreviewMessage, (prevProps, nextProps) => {
  if (prevProps.status !== nextProps.status) return false;
  if (prevProps.message.annotations !== nextProps.message.annotations)
    return false;
  if (!equal(prevProps.message.parts, nextProps.message.parts)) return false;
  return true;
});


================================================
FILE: app/chat/components/messages.tsx
================================================
import type { Message as TMessage } from "ai";
import { Message } from "./message";
import { useScrollToBottom } from "~/chat/lib/hooks/use-scroll-to-bottom";

export const Messages = ({
  messages,
  isLoading,
  status,
}: {
  messages: TMessage[];
  isLoading: boolean;
  status: "error" | "submitted" | "streaming" | "ready";
}) => {
  const [containerRef, endRef] = useScrollToBottom();

  return (
    <div className="h-full overflow-y-auto no-scrollbar" ref={containerRef}>
      <div className="max-w-lg sm:max-w-3xl mx-auto py-4">
        {messages.map((m, i) => (
          <Message
            key={i}
            isLatestMessage={i === messages.length - 1}
            isLoading={isLoading}
            message={m}
            status={status}
          />
        ))}
        <div className="h-1" ref={endRef} />
      </div>
    </div>
  );
};


================================================
FILE: app/chat/components/model-picker.tsx
================================================
"use client";
import {
  MODELS,
  modelDetails,
  type modelID,
  defaultModel,
} from "~/chat/ai/providers.shared";
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "./ui/select";
import { cn } from "~/chat/lib/utils";
import {
  Sparkles,
  Zap,
  Info,
  Bolt,
  Code,
  Brain,
  Lightbulb,
  Image,
  Gauge,
  Rocket,
  Bot,
} from "lucide-react";
import { useState, useEffect } from "react";

interface ModelPickerProps {
  selectedModel: modelID;
  setSelectedModel: (model: modelID) => void;
}

export const ModelPicker = ({
  selectedModel,
  setSelectedModel,
}: ModelPickerProps) => {
  const [hoveredModel, setHoveredModel] = useState<modelID | null>(null);

  // Ensure we always have a valid model ID
  const validModelId = MODELS.includes(selectedModel)
    ? selectedModel
    : defaultModel;

  // If the selected model is invalid, update it to the default
  useEffect(() => {
    if (selectedModel !== validModelId) {
      setSelectedModel(validModelId as modelID);
    }
  }, [selectedModel, validModelId, setSelectedModel]);

  // Function to get the appropriate icon for each provider
  const getProviderIcon = (provider: string) => {
    switch (provider.toLowerCase()) {
      case "anthropic":
        return <Sparkles className="h-3 w-3 text-orange-600" />;
      case "openai":
        return <Zap className="h-3 w-3 text-green-500" />;
      case "google":
        return <Zap className="h-3 w-3 text-red-500" />;
      case "groq":
        return <Sparkles className="h-3 w-3 text-blue-500" />;
      case "xai":
        return <Sparkles className="h-3 w-3 text-yellow-500" />;
      default:
        return <Info className="h-3 w-3 text-blue-500" />;
    }
  };

  // Function to get capability icon
  const getCapabilityIcon = (capability: string) => {
    switch (capability.toLowerCase()) {
      case "code":
        return <Code className="h-2.5 w-2.5" />;
      case "reasoning":
        return <Brain className="h-2.5 w-2.5" />;
      case "research":
        return <Lightbulb className="h-2.5 w-2.5" />;
      case "vision":
        return <Image className="h-2.5 w-2.5" />;
      case "fast":
      case "rapid":
        return <Bolt className="h-2.5 w-2.5" />;
      case "efficient":
      case "compact":
        return <Gauge className="h-2.5 w-2.5" />;
      case "creative":
      case "balance":
        return <Rocket className="h-2.5 w-2.5" />;
      case "agentic":
        return <Bot className="h-2.5 w-2.5" />;
      default:
        return <Info className="h-2.5 w-2.5" />;
    }
  };

  // Get capability badge color
  const getCapabilityColor = (capability: string) => {
    switch (capability.toLowerCase()) {
      case "code":
        return "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300 ocean:bg-blue-900/30 ocean:text-blue-300";
      case "reasoning":
      case "research":
        return "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300 ocean:bg-purple-900/30 ocean:text-purple-300";
      case "vision":
        return "bg-indigo-100 text-indigo-800 dark:bg-indigo-900/30 dark:text-indigo-300 ocean:bg-indigo-900/30 ocean:text-indigo-300";
      case "fast":
      case "rapid":
        return "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300 ocean:bg-amber-900/30 ocean:text-amber-300";
      case "efficient":
      case "compact":
        return "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-300 ocean:bg-emerald-900/30 ocean:text-emerald-300";
      case "creative":
      case "balance":
        return "bg-rose-100 text-rose-800 dark:bg-rose-900/30 dark:text-rose-300 ocean:bg-rose-900/30 ocean:text-rose-300";
      case "agentic":
        return "bg-cyan-100 text-cyan-800 dark:bg-cyan-900/30 dark:text-cyan-300 ocean:bg-cyan-900/30 ocean:text-cyan-300";
      default:
        return "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300 ocean:bg-gray-800 ocean:text-gray-300";
    }
  };

  // Get current model details to display
  const displayModelId = hoveredModel || validModelId;
  const currentModelDetails = modelDetails[displayModelId];

  // Handle model change
  const handleModelChange = (modelId: string) => {
    if (MODELS.includes(modelId)) {
      const typedModelId = modelId as modelID;
      setSelectedModel(typedModelId);
    }
  };

  return (
    <div className="absolute bottom-2 left-2 z-10">
      <Select
        value={validModelId}
        onValueChange={handleModelChange}
        defaultValue={validModelId}
      >
        <SelectTrigger className="max-w-[200px] sm:max-w-fit sm:w-56 px-2 sm:px-3 h-8 sm:h-9 rounded-full group border-primary/20 bg-primary/5 hover:bg-primary/10 dark:bg-primary/10 dark:hover:bg-primary/20 ocean:bg-primary/10 ocean:hover:bg-primary/20 transition-all duration-200 ring-offset-background focus:ring-2 focus:ring-primary/30 focus:ring-offset-2">
          <SelectValue
            placeholder="Select model"
            className="text-xs font-medium flex items-center gap-1 sm:gap-2 text-primary dark:text-primary-foreground ocean:text-primary-foreground"
          >
            <div className="flex items-center gap-1 sm:gap-2">
              {getProviderIcon(modelDetails[validModelId].provider)}
              <span className="font-medium truncate">
                {modelDetails[validModelId].name}
              </span>
            </div>
          </SelectValue>
        </SelectTrigger>
        <SelectContent
          align="start"
          className="bg-background/95 dark:bg-muted/95 ocean:bg-muted/95 backdrop-blur-sm border-border/80 rounded-lg overflow-hidden p-0 w-[280px] sm:w-[350px] md:w-[515px]"
        >
          <div className="grid grid-cols-1 sm:grid-cols-[120px_1fr] md:grid-cols-[200px_1fr] items-start">
            {/* Model selector column */}
            <div className="sm:border-r border-border/40 bg-muted/20 p-0 pr-1">
              <SelectGroup className="space-y-1">
                {MODELS.map((id) => {
                  const modelId = id as modelID;
                  return (
                    <SelectItem
                      key={id}
                      value={id}
                      onMouseEnter={() => setHoveredModel(modelId)}
                      onMouseLeave={() => setHoveredModel(null)}
                      className={cn(
                        "!px-2 sm:!px-3 py-1.5 sm:py-2 cursor-pointer rounded-md text-xs transition-colors duration-150",
                        "hover:bg-primary/5 hover:text-primary-foreground",
                        "focus:bg-primary/10 focus:text-primary focus:outline-none",
                        "data-[highlighted]:bg-primary/10 data-[highlighted]:text-primary",
                        validModelId === id &&
                          "!bg-primary/15 !text-primary font-medium",
                      )}
                    >
                      <div className="flex flex-col gap-0.5">
                        <div className="flex items-center gap-1.5">
                          {getProviderIcon(modelDetails[modelId].provider)}
                          <span className="font-medium truncate">
                            {modelDetails[modelId].name}
                          </span>
                        </div>
                        <span className="text-[10px] sm:text-xs text-muted-foreground">
                          {modelDetails[modelId].provider}
                        </span>
                      </div>
                    </SelectItem>
                  );
                })}
              </SelectGroup>
            </div>

            {/* Model details column - hidden on smallest screens, visible on sm+ */}
            <div className="sm:block hidden p-2 sm:p-3 md:p-4 flex-col">
              <div>
                <div className="flex items-center gap-2 mb-1">
                  {getProviderIcon(currentModelDetails.provider)}
                  <h3 className="text-sm font-semibold">
                    {currentModelDetails.name}
                  </h3>
                </div>
                <div className="text-xs text-muted-foreground mb-1">
                  Provider:{" "}
                  <span className="font-medium">
                    {currentModelDetails.provider}
                  </span>
                </div>

                {/* Capability badges */}
                <div className="flex flex-wrap gap-1 mt-2 mb-3">
                  {currentModelDetails.capabilities.map((capability) => (
                    <span
                      key={capability}
                      className={cn(
                        "inline-flex items-center gap-1 text-[9px] px-1.5 py-0.5 rounded-full font-medium",
                        getCapabilityColor(capability),
                      )}
                    >
                      {getCapabilityIcon(capability)}
                      <span>{capability}</span>
                    </span>
                  ))}
                </div>

                <div className="text-xs text-foreground/90 leading-relaxed mb-3 hidden md:block">
                  {currentModelDetails.description}
                </div>
              </div>

              <div className="bg-muted/40 rounded-md p-2 hidden md:block">
                <div className="text-[10px] text-muted-foreground flex justify-between items-center">
                  <span>API Version:</span>
                  <code className="bg-background/80 px-2 py-0.5 rounded text-[10px] font-mono">
                    {currentModelDetails.apiVersion}
                  </code>
                </div>
              </div>
            </div>

            {/* Condensed model details for mobile only */}
            <div className="p-3 sm:hidden border-t border-border/30">
              <div className="flex flex-wrap gap-1 mb-2">
                {currentModelDetails.capabilities
                  .slice(0, 4)
                  .map((capability) => (
                    <span
                      key={capability}
                      className={cn(
                        "inline-flex items-center gap-1 text-[9px] px-1.5 py-0.5 rounded-full font-medium",
                        getCapabilityColor(capability),
                      )}
                    >
                      {getCapabilityIcon(capability)}
                      <span>{capability}</span>
                    </span>
                  ))}
                {currentModelDetails.capabilities.length > 4 && (
                  <span className="text-[10px] text-muted-foreground">
                    +{currentModelDetails.capabilities.length - 4} more
                  </span>
                )}
              </div>
            </div>
          </div>
        </SelectContent>
      </Select>
    </div>
  );
};


================================================
FILE: app/chat/components/project-overview.tsx
================================================
import { useMCP } from "~/chat/lib/context/mcp-context";
export const ProjectOverview = () => {
  const { owner, repo, serverNameText } = useMCP();
  return (
    <div className="flex flex-col items-center justify-end">
      <h1 className="text-4xl font-semibold mb-4 text-center">
        {`Chat with ${serverNameText}`}
      </h1>
      <p className="text-foreground/80 text-lg mb-4 text-center">
        Ask questions about {repo ? `${owner}/${repo}` : "any GitHub repo"}{" "}
        documentation.
      </p>
    </div>
  );
};


================================================
FILE: app/chat/components/suggested-prompts.tsx
================================================
"use client";

import { motion } from "motion/react";
import { Button } from "./ui/button";
import { memo } from "react";

interface SuggestedPromptsProps {
  sendMessage: (input: string) => void;
}

function PureSuggestedPrompts({ sendMessage }: SuggestedPromptsProps) {
  const suggestedActions = [
    {
      title: "What are the advantages",
      label: "of using Next.js?",
      action: "What are the advantages of using Next.js?",
    },
    {
      title: "What is the weather",
      label: "in San Francisco?",
      action: "What is the weather in San Francisco?",
    },
  ];

  return (
    <div
      data-testid="suggested-actions"
      className="grid sm:grid-cols-2 gap-2 w-full"
    >
      {suggestedActions.map((suggestedAction, index) => (
        <motion.div
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: 20 }}
          transition={{ delay: 0.05 * index }}
          key={`suggested-action-${suggestedAction.title}-${index}`}
          className={index > 1 ? "hidden sm:block" : "block"}
        >
          <Button
            variant="ghost"
            onClick={async () => {
              sendMessage(suggestedAction.action);
            }}
            className="text-left border rounded-xl px-4 py-3.5 text-sm flex-1 gap-1 sm:flex-col w-full h-auto justify-start items-start"
          >
            <span className="font-medium">{suggestedAction.title}</span>
            <span className="text-muted-foreground">
              {suggestedAction.label}
            </span>
          </Button>
        </motion.div>
      ))}
    </div>
  );
}

export const SuggestedPrompts = memo(PureSuggestedPrompts, () => true);


================================================
FILE: app/chat/components/textarea.tsx
================================================
import type { modelID } from "~/chat/ai/providers.shared";
import { Textarea as ShadcnTextarea } from "~/chat/components/ui/textarea";
import { ArrowUp, Loader2 } from "lucide-react";
import { ModelPicker } from "./model-picker";

interface InputProps {
  input: string;
  handleInputChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
  isLoading: boolean;
  status: string;
  stop: () => void;
  selectedModel: modelID;
  setSelectedModel: (model: modelID) => void;
}

export const Textarea = ({
  input,
  handleInputChange,
  isLoading,
  status,
  stop,
  selectedModel,
  setSelectedModel,
}: InputProps) => {
  const isStreaming = status === "streaming" || status === "submitted";

  return (
    <div className="relative w-full">
      <ShadcnTextarea
        className="resize-none bg-background/50 dark:bg-muted/50 ocean:bg-muted/50 backdrop-blur-sm w-full rounded-2xl pr-12 pt-4 pb-16 border-input focus-visible:ring-ring placeholder:text-muted-foreground/80"
        value={input}
        autoFocus
        placeholder="Send a message..."
        onChange={handleInputChange}
        onKeyDown={(e) => {
          if (e.key === "Enter" && !e.shiftKey && !isLoading && input.trim()) {
            e.preventDefault();
            e.currentTarget.form?.requestSubmit();
          }
        }}
      />
      <ModelPicker
        setSelectedModel={setSelectedModel}
        selectedModel={selectedModel}
      />

      <button
        type={isStreaming ? "button" : "submit"}
        onClick={isStreaming ? stop : undefined}
        disabled={
          (!isStreaming && !input.trim()) ||
          (isStreaming && status === "submitted")
        }
        className="absolute right-2 bottom-2 rounded-full p-2 bg-primary hover:bg-primary/90 disabled:bg-muted disabled:cursor-not-allowed transition-all duration-200"
      >
        {isStreaming ? (
          <Loader2 className="h-4 w-4 text-primary-foreground animate-spin" />
        ) : (
          <ArrowUp className="h-4 w-4 text-primary-foreground" />
        )}
      </button>
    </div>
  );
};


================================================
FILE: app/chat/components/theme-provider.tsx
================================================
"use client";

import { ThemeProvider as NextThemesProvider } from "next-themes";
import type { ThemeProviderProps } from "next-themes";

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}


================================================
FILE: app/chat/components/theme-toggle.tsx
================================================
"use client";

import * as React from "react";
import {
  CircleDashed,
  Flame,
  Sun,
  Moon,
  WavesIcon,
  SunsetIcon,
} from "lucide-react";
import { useTheme } from "next-themes";
import { Button } from "./ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "./ui/dropdown-menu";
import { cn } from "~/chat/lib/utils";

export function ThemeToggle({
  className,
  ...props
}: React.ComponentProps<typeof Button>) {
  const { setTheme } = useTheme();

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild={true}>
        <Button
          variant="ghost"
          size="icon"
          className={cn(`rounded-md h-8 w-8`, className)}
          {...props}
        >
          <Flame className="h-4 w-4 rotate-0 scale-100 transition-all hover:text-sidebar-accent" />
          <span className="sr-only">Toggle theme</span>
        </Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent align="end">
        <DropdownMenuItem onSelect={() => setTheme("dark")}>
          <Moon className="mr-2 h-4 w-4" />
          <span>Dark</span>
        </DropdownMenuItem>
        <DropdownMenuItem onSelect={() => setTheme("light")}>
          <Sun className="mr-2 h-4 w-4" />
          <span>Light</span>
        </DropdownMenuItem>
        <DropdownMenuItem onSelect={() => setTheme("black")}>
          <CircleDashed className="mr-2 h-4 w-4" />
          <span>Black</span>
        </DropdownMenuItem>
        {/* sunset theme */}
        <DropdownMenuItem onSelect={() => setTheme("sunset")}>
          <SunsetIcon className="mr-2 h-4 w-4" />
          <span>Sunset</span>
        </DropdownMenuItem>
        <DropdownMenuItem onSelect={() => setTheme("ocean")}>
          <WavesIcon className="mr-2 h-4 w-4" />
          <span>Ocean</span>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}


================================================
FILE: app/chat/components/tool-invocation.tsx
================================================
"use client";

import { useState } from "react";
import { motion, AnimatePresence } from "motion/react";
import {
  ChevronDownIcon,
  ChevronUpIcon,
  Loader2,
  CheckCircle2,
  TerminalSquare,
  Code,
  ArrowRight,
  Circle,
} from "lucide-react";
import { cn } from "~/chat/lib/utils";

interface ToolInvocationProps {
  toolName: string;
  state: string;
  args: any;
  result: any;
  isLatestMessage: boolean;
  status: string;
}

export function ToolInvocation({
  toolName,
  state,
  args,
  result,
  isLatestMessage,
  status,
}: ToolInvocationProps) {
  const [isExpanded, setIsExpanded] = useState(false);

  const variants = {
    collapsed: {
      height: 0,
      opacity: 0,
    },
    expanded: {
      height: "auto",
      opacity: 1,
    },
  };

  const getStatusIcon = () => {
    if (state === "call") {
      if (isLatestMessage && status !== "ready") {
        return <Loader2 className="animate-spin h-3.5 w-3.5 text-primary/70" />;
      }
      return (
        <Circle className="h-3.5 w-3.5 fill-muted-foreground/10 text-muted-foreground/70" />
      );
    }
    return <CheckCircle2 size={14} className="text-primary/90" />;
  };

  const getStatusClass = () => {
    if (state === "call") {
      if (isLatestMessage && status !== "ready") {
        return "text-primary";
      }
      return "text-muted-foreground";
    }
    return "text-primary";
  };

  const formatContent = (content: any): string => {
    try {
      if (typeof content === "string") {
        try {
          const parsed = JSON.parse(content);
          return JSON.stringify(parsed, null, 2);
        } catch {
          return content;
        }
      }
      return JSON.stringify(content, null, 2);
    } catch {
      return String(content);
    }
  };

  return (
    <div
      className={cn(
        "flex flex-col mb-2 rounded-md border border-border/50 overflow-hidden",
        "bg-gradient-to-b from-background to-muted/30 backdrop-blur-sm",
        "transition-all duration-200 hover:border-border/80 group",
      )}
    >
      <div
        className={cn(
          "flex items-center gap-2.5 px-3 py-2 cursor-pointer transition-colors",
          "hover:bg-muted/20",
        )}
        onClick={() => setIsExpanded(!isExpanded)}
      >
        <div className="flex items-center justify-center rounded-full w-5 h-5 bg-primary/5 text-primary">
          <TerminalSquare className="h-3.5 w-3.5" />
        </div>
        <div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground flex-1">
          <span className="text-foreground font-semibold tracking-tight">
            {toolName}
          </span>
          <ArrowRight className="h-3 w-3 text-muted-foreground/50" />
          <span className={cn("font-medium", getStatusClass())}>
            {state === "call"
              ? isLatestMessage && status !== "ready"
                ? "Running"
                : "Waiting"
              : "Completed"}
          </span>
        </div>
        <div className="flex items-center gap-2 opacity-70 group-hover:opacity-100 transition-opacity">
          {getStatusIcon()}
          <div className="bg-muted/30 rounded-full p-0.5 border border-border/30">
            {isExpanded ? (
              <ChevronUpIcon className="h-3 w-3 text-foreground/70" />
            ) : (
              <ChevronDownIcon className="h-3 w-3 text-foreground/70" />
            )}
          </div>
        </div>
      </div>

      <AnimatePresence initial={false}>
        {isExpanded && (
          <motion.div
            initial="collapsed"
            animate="expanded"
            exit="collapsed"
            variants={variants}
            transition={{ duration: 0.2 }}
            className="space-y-2 px-3 pb-3"
          >
            {!!args && (
              <div className="space-y-1.5">
                <div className="flex items-center gap-1.5 text-xs text-muted-foreground/70 pt-1.5">
                  <Code className="h-3 w-3" />
                  <span className="font-medium">Arguments</span>
                </div>
                <pre
                  className={cn(
                    "text-xs font-mono p-2.5 rounded-md overflow-x-auto",
                    "border border-border/40 bg-muted/10",
                  )}
                >
                  {formatContent(args)}
                </pre>
              </div>
            )}

            {!!result && (
              <div className="space-y-1.5">
                <div className="flex items-center gap-1.5 text-xs text-muted-foreground/70">
                  <ArrowRight className="h-3 w-3" />
                  <span className="font-medium">Result</span>
                </div>
                <pre
                  className={cn(
                    "text-xs font-mono p-2.5 rounded-md overflow-x-auto max-h-[300px] overflow-y-auto",
                    "border border-border/40 bg-muted/10",
                  )}
                >
                  {formatContent(result)}
                </pre>
              </div>
            )}
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}


================================================
FILE: app/chat/components/ui/accordion.tsx
================================================
"use client";

import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "lucide-react";

import { cn } from "~/chat/lib/utils";

function Accordion({
  ...props
}: React.ComponentProps<typeof AccordionPrimitive.Root>) {
  return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
}

function AccordionItem({
  className,
  ...props
}: React.ComponentProps<typeof AccordionPrimitive.Item>) {
  return (
    <AccordionPrimitive.Item
      data-slot="accordion-item"
      className={cn("mb-1", className)}
      {...props}
    />
  );
}

function AccordionTrigger({
  className,
  children,
  ...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
  return (
    <AccordionPrimitive.Header className="flex">
      <AccordionPrimitive.Trigger
        data-slot="accordion-trigger"
        className={cn(
          "focus-visible:ring-ring/30 flex flex-1 items-center justify-between py-3 text-left text-sm font-medium transition-all outline-none focus-visible:ring-2 rounded-md disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
          className,
        )}
        {...props}
      >
        {children}
        <ChevronDownIcon className="text-muted-foreground/70 size-3.5 shrink-0 transition-transform duration-200" />
      </AccordionPrimitive.Trigger>
    </AccordionPrimitive.Header>
  );
}

function AccordionContent({
  className,
  children,
  ...props
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
  return (
    <AccordionPrimitive.Content
      data-slot="accordion-content"
      className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
      {...props}
    >
      <div className={cn("py-2 pl-1", className)}>{children}</div>
    </AccordionPrimitive.Content>
  );
}

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };


================================================
FILE: app/chat/components/ui/avatar.tsx
================================================
"use client";

import * as React from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar";

import { cn } from "~/chat/lib/utils";

function Avatar({
  className,
  ...props
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
  return (
    <AvatarPrimitive.Root
      data-slot="avatar"
      className={cn(
        "relative flex size-8 shrink-0 overflow-hidden rounded-full",
        className,
      )}
      {...props}
    />
  );
}

function AvatarImage({
  className,
  ...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
  return (
    <AvatarPrimitive.Image
      data-slot="avatar-image"
      className={cn("aspect-square size-full", className)}
      {...props}
    />
  );
}

function AvatarFallback({
  className,
  ...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
  return (
    <AvatarPrimitive.Fallback
      data-slot="avatar-fallback"
      className={cn(
        "bg-muted flex size-full items-center justify-center rounded-full",
        className,
      )}
      {...props}
    />
  );
}

export { Avatar, AvatarImage, AvatarFallback };


================================================
FILE: app/chat/components/ui/badge.tsx
================================================
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "~/chat/lib/utils";

const badgeVariants = cva(
  "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 ocean:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
  {
    variants: {
      variant: {
        default:
          "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
        secondary:
          "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
        destructive:
          "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 ocean:focus-visible:ring-destructive/40 dark:bg-destructive/60 ocean:bg-destructive/60",
        outline:
          "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  },
);

function Badge({
  className,
  variant,
  asChild = false,
  ...props
}: React.ComponentProps<"span"> &
  VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
  const Comp = asChild ? Slot : "span";

  return (
    <Comp
      data-slot="badge"
      className={cn(badgeVariants({ variant }), className)}
      {...props}
    />
  );
}

export { Badge, badgeVariants };


================================================
FILE: app/chat/components/ui/button.tsx
================================================
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "~/chat/lib/utils";

const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 ocean:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
  {
    variants: {
      variant: {
        default:
          "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
        destructive:
          "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 ocean:focus-visible:ring-destructive/40 dark:bg-destructive/60 ocean:bg-destructive/60",
        outline:
          "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 ocean:bg-input/30 dark:border-input ocean:border-input dark:hover:bg-input/50 ocean:hover:bg-input/50",
        secondary:
          "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
        ghost:
          "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 ocean:hover:bg-accent/50",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2 has-[>svg]:px-3",
        sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
        lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
        icon: "size-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  },
);

function Button({
  className,
  variant,
  size,
  asChild = false,
  ...props
}: React.ComponentProps<"button"> &
  VariantProps<typeof buttonVariants> & {
    asChild?: boolean;
  }) {
  const Comp = asChild ? Slot : "button";

  return (
    <Comp
      data-slot="button"
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  );
}

export { Button, buttonVariants };


================================================
FILE: app/chat/components/ui/dialog.tsx
================================================
"use client";

import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";

import { cn } from "~/chat/lib/utils";

function Dialog({
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
  return <DialogPrimitive.Root data-slot="dialog" {...props} />;
}

function DialogTrigger({
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
  return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
}

function DialogPortal({
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
  return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
}

function DialogClose({
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
  return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
}

function DialogOverlay({
  className,
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
  return (
    <DialogPrimitive.Overlay
      data-slot="dialog-overlay"
      className={cn(
        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
        className,
      )}
      {...props}
    />
  );
}

function DialogContent({
  className,
  children,
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Content>) {
  return (
    <DialogPortal data-slot="dialog-portal">
      <DialogOverlay />
      <DialogPrimitive.Content
        data-slot="dialog-content"
        className={cn(
          "bg-background 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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
          className,
        )}
        {...props}
      >
        {children}
        <DialogPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
          <XIcon />
          <span className="sr-only">Close</span>
        </DialogPrimitive.Close>
      </DialogPrimitive.Content>
    </DialogPortal>
  );
}

function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="dialog-header"
      className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
      {...props}
    />
  );
}

function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="dialog-footer"
      className={cn(
        "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
        className,
      )}
      {...props}
    />
  );
}

function DialogTitle({
  className,
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
  return (
    <DialogPrimitive.Title
      data-slot="dialog-title"
      className={cn("text-lg leading-none font-semibold", className)}
      {...props}
    />
  );
}

function DialogDescription({
  className,
  ...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
  return (
    <DialogPrimitive.Description
      data-slot="dialog-description"
      className={cn("text-muted-foreground text-sm", className)}
      {...props}
    />
  );
}

export {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogOverlay,
  DialogPortal,
  DialogTitle,
  DialogTrigger,
};


================================================
FILE: app/chat/components/ui/dropdown-menu.tsx
================================================
"use client";

import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";

import { cn } from "~/chat/lib/utils";

function DropdownMenu({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
  return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
}

function DropdownMenuPortal({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
  return (
    <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
  );
}

function DropdownMenuTrigger({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
  return (
    <DropdownMenuPrimitive.Trigger
      data-slot="dropdown-menu-trigger"
      {...props}
    />
  );
}

function DropdownMenuContent({
  className,
  sideOffset = 4,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
  return (
    <DropdownMenuPrimitive.Portal>
      <DropdownMenuPrimitive.Content
        data-slot="dropdown-menu-content"
        sideOffset={sideOffset}
        className={cn(
          "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 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
          className,
        )}
        {...props}
      />
    </DropdownMenuPrimitive.Portal>
  );
}

function DropdownMenuGroup({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
  return (
    <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
  );
}

function DropdownMenuItem({
  className,
  inset,
  variant = "default",
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
  inset?: boolean;
  variant?: "default" | "destructive";
}) {
  return (
    <DropdownMenuPrimitive.Item
      data-slot="dropdown-menu-item"
      data-inset={inset}
      data-variant={variant}
      className={cn(
        "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 ocean:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
      )}
      {...props}
    />
  );
}

function DropdownMenuCheckboxItem({
  className,
  children,
  checked,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
  return (
    <DropdownMenuPrimitive.CheckboxItem
      data-slot="dropdown-menu-checkbox-item"
      className={cn(
        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
      )}
      checked={checked}
      {...props}
    >
      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
        <DropdownMenuPrimitive.ItemIndicator>
          <CheckIcon className="size-4" />
        </DropdownMenuPrimitive.ItemIndicator>
      </span>
      {children}
    </DropdownMenuPrimitive.CheckboxItem>
  );
}

function DropdownMenuRadioGroup({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
  return (
    <DropdownMenuPrimitive.RadioGroup
      data-slot="dropdown-menu-radio-group"
      {...props}
    />
  );
}

function DropdownMenuRadioItem({
  className,
  children,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
  return (
    <DropdownMenuPrimitive.RadioItem
      data-slot="dropdown-menu-radio-item"
      className={cn(
        "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
      )}
      {...props}
    >
      <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
        <DropdownMenuPrimitive.ItemIndicator>
          <CircleIcon className="size-2 fill-current" />
        </DropdownMenuPrimitive.ItemIndicator>
      </span>
      {children}
    </DropdownMenuPrimitive.RadioItem>
  );
}

function DropdownMenuLabel({
  className,
  inset,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
  inset?: boolean;
}) {
  return (
    <DropdownMenuPrimitive.Label
      data-slot="dropdown-menu-label"
      data-inset={inset}
      className={cn(
        "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
        className,
      )}
      {...props}
    />
  );
}

function DropdownMenuSeparator({
  className,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
  return (
    <DropdownMenuPrimitive.Separator
      data-slot="dropdown-menu-separator"
      className={cn("bg-border -mx-1 my-1 h-px", className)}
      {...props}
    />
  );
}

function DropdownMenuShortcut({
  className,
  ...props
}: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="dropdown-menu-shortcut"
      className={cn(
        "text-muted-foreground ml-auto text-xs tracking-widest",
        className,
      )}
      {...props}
    />
  );
}

function DropdownMenuSub({
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
  return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
}

function DropdownMenuSubTrigger({
  className,
  inset,
  children,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
  inset?: boolean;
}) {
  return (
    <DropdownMenuPrimitive.SubTrigger
      data-slot="dropdown-menu-sub-trigger"
      data-inset={inset}
      className={cn(
        "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
        className,
      )}
      {...props}
    >
      {children}
      <ChevronRightIcon className="ml-auto size-4" />
    </DropdownMenuPrimitive.SubTrigger>
  );
}

function DropdownMenuSubContent({
  className,
  ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
  return (
    <DropdownMenuPrimitive.SubContent
      data-slot="dropdown-menu-sub-content"
      className={cn(
        "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 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
        className,
      )}
      {...props}
    />
  );
}

export {
  DropdownMenu,
  DropdownMenuPortal,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuItem,
  DropdownMenuCheckboxItem,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubTrigger,
  DropdownMenuSubContent,
};


================================================
FILE: app/chat/components/ui/input.tsx
================================================
import * as React from "react";

import { cn } from "~/chat/lib/utils";

function Input({ className, type, ...props }: React.ComponentProps<"input">) {
  return (
    <input
      type={type}
      data-slot="input"
      className={cn(
        "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 ocean: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",
        "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
        "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 ocean:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
        className,
      )}
      {...props}
    />
  );
}

export { Input };


================================================
FILE: app/chat/components/ui/label.tsx
================================================
"use client";

import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";

import { cn } from "~/chat/lib/utils";

function Label({
  className,
  ...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
  return (
    <LabelPrimitive.Root
      data-slot="label"
      className={cn(
        "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
        className,
      )}
      {...props}
    />
  );
}

export { Label };


================================================
FILE: app/chat/components/ui/popover.tsx
================================================
"use client";

import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";

import { cn } from "~/chat/lib/utils";

function Popover({
  ...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
  return <PopoverPrimitive.Root data-slot="popover" {...props} />;
}

function PopoverTrigger({
  ...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
  return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
}

function PopoverContent({
  className,
  align = "center",
  sideOffset = 4,
  ...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
  return (
    <PopoverPrimitive.Portal>
      <PopoverPrimitive.Content
        data-slot="popover-content"
        align={align}
        sideOffset={sideOffset}
        className={cn(
          "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",
          className,
        )}
        {...props}
      />
    </PopoverPrimitive.Portal>
  );
}

function PopoverAnchor({
  ...props
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
  return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
}

export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };


================================================
FILE: app/chat/components/ui/scroll-area.tsx
================================================
"use client";

import * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";

import { cn } from "~/chat/lib/utils";

function ScrollArea({
  className,
  children,
  ...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
  return (
    <ScrollAreaPrimitive.Root
      data-slot="scroll-area"
      className={cn("relative", className)}
      {...props}
    >
      <ScrollAreaPrimitive.Viewport
        data-slot="scroll-area-viewport"
        className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
      >
        {children}
      </ScrollAreaPrimitive.Viewport>
      <ScrollBar />
      <ScrollAreaPrimitive.Corner />
    </ScrollAreaPrimitive.Root>
  );
}

function ScrollBar({
  className,
  orientation = "vertical",
  ...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
  return (
    <ScrollAreaPrimitive.ScrollAreaScrollbar
      data-slot="scroll-area-scrollbar"
      orientation={orientation}
      className={cn(
        "flex touch-none p-px transition-colors select-none",
        orientation === "vertical" &&
          "h-full w-2.5 border-l border-l-transparent",
        orientation === "horizontal" &&
          "h-2.5 flex-col border-t border-t-transparent",
        className,
      )}
      {...props}
    >
      <ScrollAreaPrimitive.ScrollAreaThumb
        data-slot="scroll-area-thumb"
        className="bg-border relative flex-1 rounded-full"
      />
    </ScrollAreaPrimitive.ScrollAreaScrollbar>
  );
}

export { ScrollArea, ScrollBar };


================================================
FILE: app/chat/components/ui/select.tsx
================================================
"use client";

import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";

import { cn } from "~/chat/lib/utils";

function Select({
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
  return <SelectPrimitive.Root data-slot="select" {...props} />;
}

function SelectGroup({
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
  return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}

function SelectValue({
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
  return <SelectPrimitive.Value data-slot="select-value" {...props} />;
}

function SelectTrigger({
  className,
  children,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger>) {
  return (
    <SelectPrimitive.Trigger
      data-slot="select-trigger"
      className={cn(
        "text-muted-foreground data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]: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 h-9 w-fit items-center justify-between gap-2 rounded-md bg-transparent px-3 py-2 text-sm whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-secondary data-[state=open]:bg-secondary disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className,
      )}
      {...props}
    >
      {children}
      <SelectPrimitive.Icon asChild>
        <ChevronDownIcon className="size-4 opacity-50" />
      </SelectPrimitive.Icon>
    </SelectPrimitive.Trigger>
  );
}

function SelectContent({
  className,
  children,
  position = "popper",
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
  return (
    <SelectPrimitive.Portal>
      <SelectPrimitive.Content
        data-slot="select-content"
        className={cn(
          "bg-secondary text-secondary-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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
          position === "popper" &&
            "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
          className,
        )}
        position={position}
        {...props}
      >
        <SelectScrollUpButton />
        <SelectPrimitive.Viewport
          className={cn(
            "p-1",
            position === "popper" &&
              "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
          )}
        >
          {children}
        </SelectPrimitive.Viewport>
        <SelectScrollDownButton />
      </SelectPrimitive.Content>
    </SelectPrimitive.Portal>
  );
}

function SelectLabel({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
  return (
    <SelectPrimitive.Label
      data-slot="select-label"
      className={cn("px-2 py-1.5 text-sm font-medium", className)}
      {...props}
    />
  );
}

function SelectItem({
  className,
  children,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
  return (
    <SelectPrimitive.Item
      data-slot="select-item"
      className={cn(
        "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
        className,
      )}
      {...props}
    >
      <span className="absolute right-2 flex size-3.5 items-center justify-center">
        <SelectPrimitive.ItemIndicator>
          <CheckIcon className="size-4" />
        </SelectPrimitive.ItemIndicator>
      </span>
      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
    </SelectPrimitive.Item>
  );
}

function SelectSeparator({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
  return (
    <SelectPrimitive.Separator
      data-slot="select-separator"
      className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
      {...props}
    />
  );
}

function SelectScrollUpButton({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
  return (
    <SelectPrimitive.ScrollUpButton
      data-slot="select-scroll-up-button"
      className={cn(
        "flex cursor-default items-center justify-center py-1",
        className,
      )}
      {...props}
    >
      <ChevronUpIcon className="size-4" />
    </SelectPrimitive.ScrollUpButton>
  );
}

function SelectScrollDownButton({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
  return (
    <SelectPrimitive.ScrollDownButton
      data-slot="select-scroll-down-button"
      className={cn(
        "flex cursor-default items-center justify-center py-1",
        className,
      )}
      {...props}
    >
      <ChevronDownIcon className="size-4" />
    </SelectPrimitive.ScrollDownButton>
  );
}

export {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectScrollDownButton,
  SelectScrollUpButton,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
};


================================================
FILE: app/chat/components/ui/separator.tsx
================================================
"use client";

import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";

import { cn } from "~/chat/lib/utils";

function Separator({
  className,
  orientation = "horizontal",
  decorative = true,
  ...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
  return (
    <SeparatorPrimitive.Root
      data-slot="separator-root"
      decorative={decorative}
      orientation={orientation}
      className={cn(
        "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
        className,
      )}
      {...props}
    />
  );
}

export { Separator };


================================================
FILE: app/chat/components/ui/sheet.tsx
================================================
"use client";

import * as React from "react";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { XIcon } from "lucide-react";

import { cn } from "~/chat/lib/utils";

function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
  return <SheetPrimitive.Root data-slot="sheet" {...props} />;
}

function SheetTrigger({
  ...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
  return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
}

function SheetClose({
  ...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
  return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
}

function SheetPortal({
  ...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
  return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
}

function SheetOverlay({
  className,
  ...props
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
  return (
    <SheetPrimitive.Overlay
      data-slot="sheet-overlay"
      className={cn(
        "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
        className,
      )}
      {...props}
    />
  );
}

function SheetContent({
  className,
  children,
  side = "right",
  ...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
  side?: "top" | "right" | "bottom" | "left";
}) {
  return (
    <SheetPortal>
      <SheetOverlay />
      <SheetPrimitive.Content
        data-slot="sheet-content"
        className={cn(
          "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
          side === "right" &&
            "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
          side === "left" &&
            "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
          side === "top" &&
            "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b",
          side === "bottom" &&
            "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t",
          className,
        )}
        {...props}
      >
        {children}
        <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
          <XIcon className="size-4" />
          <span className="sr-only">Close</span>
        </SheetPrimitive.Close>
      </SheetPrimitive.Content>
    </SheetPortal>
  );
}

function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="sheet-header"
      className={cn("flex flex-col gap-1.5 p-4", className)}
      {...props}
    />
  );
}

function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="sheet-footer"
      className={cn("mt-auto flex flex-col gap-2 p-4", className)}
      {...props}
    />
  );
}

function SheetTitle({
  className,
  ...props
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
  return (
    <SheetPrimitive.Title
      data-slot="sheet-title"
      className={cn("text-foreground font-semibold", className)}
      {...props}
    />
  );
}

function SheetDescription({
  className,
  ...props
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
  return (
    <SheetPrimitive.Description
      data-slot="sheet-description"
      className={cn("text-muted-foreground text-sm", className)}
      {...props}
    />
  );
}

export {
  Sheet,
  SheetTrigger,
  SheetClose,
  SheetContent,
  SheetHeader,
  SheetFooter,
  SheetTitle,
  SheetDescription,
};


================================================
FILE: app/chat/components/ui/sidebar.tsx
================================================
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority";
import { PanelLeftIcon } from "lucide-react";

import { useIsMobile } from "~/chat/hooks/use-mobile";
import { cn } from "~/chat/lib/utils";
import { Button } from "~/chat/components/ui/button";
import { Input } from "~/chat/components/ui/input";
import { Separator } from "~/chat/components/ui/separator";
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
} from "~/chat/components/ui/sheet";
import { Skeleton } from "~/chat/components/ui/skeleton";
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "~/chat/components/ui/tooltip";

const SIDEBAR_COOKIE_NAME = "sidebar_state";
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = "13rem";
const SIDEBAR_WIDTH_MOBILE = "15rem";
const SIDEBAR_WIDTH_ICON = "3rem";
const SIDEBAR_KEYBOARD_SHORTCUT = "b";

type SidebarContextProps = {
  state: "expanded" | "collapsed";
  open: boolean;
  setOpen: (open: boolean) => void;
  openMobile: boolean;
  setOpenMobile: (open: boolean) => void;
  isMobile: boolean;
  toggleSidebar: () => void;
};

const SidebarContext = React.createContext<SidebarContextProps | null>(null);

function useSidebar() {
  const context = React.useContext(SidebarContext);
  if (!context) {
    throw new Error("useSidebar must be used within a SidebarProvider.");
  }

  return context;
}

function SidebarProvider({
  defaultOpen = true,
  open: openProp,
  onOpenChange: setOpenProp,
  className,
  style,
  children,
  ...props
}: React.ComponentProps<"div"> & {
  defaultOpen?: boolean;
  open?: boolean;
  onOpenChange?: (open: boolean) => void;
}) {
  const isMobile = useIsMobile();
  const [openMobile, setOpenMobile] = React.useState(false);

  // This is the internal state of the sidebar.
  // We use openProp and setOpenProp for control from outside the component.
  const [_open, _setOpen] = React.useState(defaultOpen);
  const open = openProp ?? _open;
  const setOpen = React.useCallback(
    (value: boolean | ((value: boolean) => boolean)) => {
      const openState = typeof value === "function" ? value(open) : value;
      if (setOpenProp) {
        setOpenProp(openState);
      } else {
        _setOpen(openState);
      }

      // This sets the cookie to keep the sidebar state.
      document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
    },
    [setOpenProp, open],
  );

  // Helper to toggle the sidebar.
  const toggleSidebar = React.useCallback(() => {
    return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
  }, [isMobile, setOpen, setOpenMobile]);

  // Adds a keyboard shortcut to toggle the sidebar.
  React.useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (
        event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
        (event.metaKey || event.ctrlKey)
      ) {
        event.preventDefault();
        toggleSidebar();
      }
    };

    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, [toggleSidebar]);

  // We add a state so that we can do data-state="expanded" or "collapsed".
  // This makes it easier to style the sidebar with Tailwind classes.
  const state = open ? "expanded" : "collapsed";

  const contextValue = React.useMemo<SidebarContextProps>(
    () => ({
      state,
      open,
      setOpen,
      isMobile,
      openMobile,
      setOpenMobile,
      toggleSidebar,
    }),
    [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
  );

  return (
    <SidebarContext.Provider value={contextValue}>
      <TooltipProvider delayDuration={0}>
        <div
          data-slot="sidebar-wrapper"
          style={
            {
              "--sidebar-width": SIDEBAR_WIDTH,
              "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
              ...style,
            } as React.CSSProperties
          }
          className={cn(
            "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
            className,
          )}
          {...props}
        >
          {children}
        </div>
      </TooltipProvider>
    </SidebarContext.Provider>
  );
}

function Sidebar({
  side = "left",
  variant = "sidebar",
  collapsible = "offcanvas",
  className,
  children,
  ...props
}: React.ComponentProps<"div"> & {
  side?: "left" | "right";
  variant?: "sidebar" | "floating" | "inset";
  collapsible?: "offcanvas" | "icon" | "none";
}) {
  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();

  if (collapsible === "none") {
    return (
      <div
        data-slot="sidebar"
        className={cn(
          "bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
          className,
        )}
        {...props}
      >
        {children}
      </div>
    );
  }

  if (isMobile) {
    return (
      <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
        <SheetContent
          data-sidebar="sidebar"
          data-slot="sidebar"
          data-mobile="true"
          className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
          style={
            {
              "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
            } as React.CSSProperties
          }
          side={side}
        >
          <SheetHeader className="sr-only">
            <SheetTitle>Sidebar</SheetTitle>
            <SheetDescription>Displays the mobile sidebar.</SheetDescription>
          </SheetHeader>
          <div className="flex h-full w-full flex-col">{children}</div>
        </SheetContent>
      </Sheet>
    );
  }

  return (
    <div
      className="group peer text-sidebar-foreground hidden md:block"
      data-state={state}
      data-collapsible={state === "collapsed" ? collapsible : ""}
      data-variant={variant}
      data-side={side}
      data-slot="sidebar"
    >
      {/* This is what handles the sidebar gap on desktop */}
      <div
        data-slot="sidebar-gap"
        className={cn(
          "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
          "group-data-[collapsible=offcanvas]:w-0",
          "group-data-[side=right]:rotate-180",
          variant === "floating" || variant === "inset"
            ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
            : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
        )}
      />
      <div
        data-slot="sidebar-container"
        className={cn(
          "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
          side === "left"
            ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
            : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
          // Adjust the padding for floating and inset variants.
          variant === "floating" || variant === "inset"
            ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
            : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
          className,
        )}
        {...props}
      >
        <div
          data-sidebar="sidebar"
          data-slot="sidebar-inner"
          className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
        >
          {children}
        </div>
      </div>
    </div>
  );
}

function SidebarTrigger({
  className,
  onClick,
  ...props
}: React.ComponentProps<typeof Button>) {
  const { toggleSidebar } = useSidebar();

  return (
    <Button
      data-sidebar="trigger"
      data-slot="sidebar-trigger"
      variant="ghost"
      size="icon"
      className={cn("size-7", className)}
      onClick={(event) => {
        onClick?.(event);
        toggleSidebar();
      }}
      {...props}
    >
      <PanelLeftIcon />
      <span className="sr-only">Toggle Sidebar</span>
    </Button>
  );
}

function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
  const { toggleSidebar } = useSidebar();

  return (
    <button
      data-sidebar="rail"
      data-slot="sidebar-rail"
      aria-label="Toggle Sidebar"
      tabIndex={-1}
      onClick={toggleSidebar}
      title="Toggle Sidebar"
      className={cn(
        "hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex",
        "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
        "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
        "hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full",
        "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
        "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
        className,
      )}
      {...props}
    />
  );
}

function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
  return (
    <main
      data-slot="sidebar-inset"
      className={cn(
        "bg-background relative flex w-full flex-1 flex-col",
        "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
        className,
      )}
      {...props}
    />
  );
}

function SidebarInput({
  className,
  ...props
}: React.ComponentProps<typeof Input>) {
  return (
    <Input
      data-slot="sidebar-input"
      data-sidebar="input"
      className={cn("bg-background h-8 w-full shadow-none", className)}
      {...props}
    />
  );
}

function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="sidebar-header"
      data-sidebar="header"
      className={cn("flex flex-col gap-2 p-2", className)}
      {...props}
    />
  );
}

function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="sidebar-footer"
      data-sidebar="footer"
      className={cn("flex flex-col gap-2 p-2", className)}
      {...props}
    />
  );
}

function SidebarSeparator({
  className,
  ...props
}: React.ComponentProps<typeof Separator>) {
  return (
    <Separator
      data-slot="sidebar-separator"
      data-sidebar="separator"
      className={cn("bg-sidebar-border mx-2 w-auto", className)}
      {...props}
    />
  );
}

function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="sidebar-content"
      data-sidebar="content"
      className={cn(
        "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
        className,
      )}
      {...props}
    />
  );
}

function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="sidebar-group"
      data-sidebar="group"
      className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
      {...props}
    />
  );
}

function SidebarGroupLabel({
  className,
  asChild = false,
  ...props
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
  const Comp = asChild ? Slot : "div";

  return (
    <Comp
      data-slot="sidebar-group-label"
      data-sidebar="group-label"
      className={cn(
        "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
        "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
        className,
      )}
      {...props}
    />
  );
}

function SidebarGroupAction({
  className,
  asChild = false,
  ...props
}: React.ComponentProps<"button"> & { asChild?: boolean }) {
  const Comp = asChild ? Slot : "button";

  return (
    <Comp
      data-slot="sidebar-group-action"
      data-sidebar="group-action"
      className={cn(
        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
        // Increases the hit area of the button on mobile.
        "after:absolute after:-inset-2 md:after:hidden",
        "group-data-[collapsible=icon]:hidden",
        className,
      )}
      {...props}
    />
  );
}

function SidebarGroupContent({
  className,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="sidebar-group-content"
      data-sidebar="group-content"
      className={cn("w-full text-sm", className)}
      {...props}
    />
  );
}

function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
  return (
    <ul
      data-slot="sidebar-menu"
      data-sidebar="menu"
      className={cn("flex w-full min-w-0 flex-col gap-1", className)}
      {...props}
    />
  );
}

function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
  return (
    <li
      data-slot="sidebar-menu-item"
      data-sidebar="menu-item"
      className={cn("group/menu-item relative", className)}
      {...props}
    />
  );
}

const sidebarMenuButtonVariants = cva(
  "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
  {
    variants: {
      variant: {
        default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
        outline:
          "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
      },
      size: {
        default: "h-8 text-sm",
        sm: "h-7 text-xs",
        lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  },
);

function SidebarMenuButton({
  asChild = false,
  isActive = false,
  variant = "default",
  size = "default",
  tooltip,
  className,
  asComponent = "button",
  ...props
}: React.ComponentProps<"button"> & {
  asChild?: boolean;
  isActive?: boolean;
  asComponent?: "button" | "span" | "a" | "div";
  tooltip?: string | React.ComponentProps<typeof TooltipContent>;
} & VariantProps<typeof sidebarMenuButtonVariants>) {
  const Comp = asChild ? Slot : (asComponent as React.ElementType);
  const { isMobile, state } = useSidebar();

  const button = (
    <Comp
      data-slot="sidebar-menu-button"
      data-sidebar="menu-button"
      data-size={size}
      data-active={isActive}
      className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
      {...props}
    />
  );

  if (!tooltip) {
    return button;
  }

  if (typeof tooltip === "string") {
    tooltip = {
      children: tooltip,
    };
  }

  return (
    <Tooltip>
      <TooltipTrigger asChild>{button}</TooltipTrigger>
      <TooltipContent
        side="right"
        align="center"
        hidden={state !== "collapsed" || isMobile}
        {...tooltip}
      />
    </Tooltip>
  );
}

function SidebarMenuAction({
  className,
  asChild = false,
  showOnHover = false,
  ...props
}: React.ComponentProps<"button"> & {
  asChild?: boolean;
  showOnHover?: boolean;
}) {
  const Comp = asChild ? Slot : "button";

  return (
    <Comp
      data-slot="sidebar-menu-action"
      data-sidebar="menu-action"
      className={cn(
        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
        // Increases the hit area of the button on mobile.
        "after:absolute after:-inset-2 md:after:hidden",
        "peer-data-[size=sm]/menu-button:top-1",
        "peer-data-[size=default]/menu-button:top-1.5",
        "peer-data-[size=lg]/menu-button:top-2.5",
        "group-data-[collapsible=icon]:hidden",
        showOnHover &&
          "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
        className,
      )}
      {...props}
    />
  );
}

function SidebarMenuBadge({
  className,
  ...props
}: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="sidebar-menu-badge"
      data-sidebar="menu-badge"
      className={cn(
        "text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none",
        "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
        "peer-data-[size=sm]/menu-button:top-1",
        "peer-data-[size=default]/menu-button:top-1.5",
        "peer-data-[size=lg]/menu-button:top-2.5",
        "group-data-[collapsible=icon]:hidden",
        className,
      )}
      {...props}
    />
  );
}

function SidebarMenuSkeleton({
  className,
  showIcon = false,
  ...props
}: React.ComponentProps<"div"> & {
  showIcon?: boolean;
}) {
  // Random width between 50 to 90%.
  const width = React.useMemo(() => {
    return `${Math.floor(Math.random() * 40) + 50}%`;
  }, []);

  return (
    <div
      data-slot="sidebar-menu-skeleton"
      data-sidebar="menu-skeleton"
      className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
      {...props}
    >
      {showIcon && (
        <Skeleton
          className="size-4 rounded-md"
          data-sidebar="menu-skeleton-icon"
        />
      )}
      <Skeleton
        className="h-4 max-w-(--skeleton-width) flex-1"
        data-sidebar="menu-skeleton-text"
        style={
          {
            "--skeleton-width": width,
          } as React.CSSProperties
        }
      />
    </div>
  );
}

function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
  return (
    <ul
      data-slot="sidebar-menu-sub"
      data-sidebar="menu-sub"
      className={cn(
        "border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5",
        "group-data-[collapsible=icon]:hidden",
        className,
      )}
      {...props}
    />
  );
}

function SidebarMenuSubItem({
  className,
  ...props
}: React.ComponentProps<"li">) {
  return (
    <li
      data-slot="sidebar-menu-sub-item"
      data-sidebar="menu-sub-item"
      className={cn("group/menu-sub-item relative", className)}
      {...props}
    />
  );
}

function SidebarMenuSubButton({
  asChild = false,
  size = "md",
  isActive = false,
  className,
  ...props
}: React.ComponentProps<"a"> & {
  asChild?: boolean;
  size?: "sm" | "md";
  isActive?: boolean;
}) {
  const Comp = asChild ? Slot : "a";

  return (
    <Comp
      data-slot="sidebar-menu-sub-button"
      data-sidebar="menu-sub-button"
      data-size={size}
      data-active={isActive}
      className={cn(
        "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
        "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
        size === "sm" && "text-xs",
        size === "md" && "text-sm",
        "group-data-[collapsible=icon]:hidden",
        className,
      )}
      {...props}
    />
  );
}

export {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupAction,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInput,
  SidebarInset,
  SidebarMenu,
  SidebarMenuAction,
  SidebarMenuBadge,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSkeleton,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  SidebarProvider,
  SidebarRail,
  SidebarSeparator,
  SidebarTrigger,
  useSidebar,
};


================================================
FILE: app/chat/components/ui/skeleton.tsx
================================================
import { cn } from "~/chat/lib/utils";

function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="skeleton"
      className={cn("bg-accent animate-pulse rounded-md", className)}
      {...props}
    />
  );
}

export { Skeleton };


================================================
FILE: app/chat/components/ui/sonner.tsx
================================================
"use client";

import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";

const Toaster = ({ ...props }: ToasterProps) => {
  const { theme = "system" } = useTheme();

  return (
    <Sonner
      theme={theme as ToasterProps["theme"]}
      className="toaster group"
      style={
        {
          "--normal-bg": "var(--popover)",
          "--normal-text": "var(--popover-foreground)",
          "--normal-border": "var(--border)",
        } as React.CSSProperties
      }
      {...props}
    />
  );
};

export { Toaster };


================================================
FILE: app/chat/components/ui/text-morph.tsx
================================================
import { cn } from "~/chat/lib/utils";
import {
  AnimatePresence,
  motion,
  type Transition,
  type Variants,
} from "motion/react";
import { useMemo, useId } from "react";

export type TextMorphProps = {
  children: string;
  as?: React.ElementType;
  className?: string;
  style?: React.CSSProperties;
  variants?: Variants;
  transition?: Transition;
};

export function TextMorph({
  children,
  as: Component = "p",
  className,
  style,
  variants,
  transition,
}: TextMorphProps) {
  const uniqueId = useId();

  const characters = useMemo(() => {
    const charCounts: Record<string, number> = {};

    return children.split("").map((char) => {
      const lowerChar = char.toLowerCase();
      charCounts[lowerChar] = (charCounts[lowerChar] || 0) + 1;

      return {
        id: `${uniqueId}-${lowerChar}${charCounts[lowerChar]}`,
        label: char === " " ? "\u00A0" : char,
      };
    });
  }, [children, uniqueId]);

  const defaultVariants: Variants = {
    initial: { opacity: 0 },
    animate: { opacity: 1 },
    exit: { opacity: 0 },
  };

  const defaultTransition: Transition = {
    type: "spring",
    stiffness: 280,
    damping: 18,
    mass: 0.3,
  };

  return (
    <Component className={cn(className)} aria-label={children} style={style}>
      <AnimatePresence mode="popLayout" initial={false}>
        {characters.map((character) => (
          <motion.span
            key={character.id}
            layoutId={character.id}
            className="inline-block"
            aria-hidden="true"
            initial="initial"
            animate="animate"
            exit="exit"
            variants={variants || defaultVariants}
            transition={transition || defaultTransition}
          >
            {character.label}
          </motion.span>
        ))}
      </AnimatePresence>
    </Component>
  );
}


================================================
FILE: app/chat/components/ui/textarea.tsx
================================================
import * as React from "react";

import { cn } from "~/chat/lib/utils";

function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
  return (
    <textarea
      data-slot="textarea"
      className={cn(
        "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 ocean: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",
        className,
      )}
      {...props}
    />
  );
}

export { Textarea };


================================================
FILE: app/chat/components/ui/tooltip.tsx
================================================
"use client";

import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";

import { cn } from "~/chat/lib/utils";

function TooltipProvider({
  delayDuration = 0,
  ...props
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
  return (
    <TooltipPrimitive.Provider
      data-slot="tooltip-provider"
      delayDuration={delayDuration}
      {...props}
    />
  );
}

function Tooltip({
  ...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
  return (
    <TooltipProvider>
      <TooltipPrimitive.Root data-slot="tooltip" {...props} />
    </TooltipProvider>
  );
}

function TooltipTrigger({
  ...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
  return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
}

function TooltipContent({
  className,
  sideOffset = 0,
  children,
  ...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
  return (
    <TooltipPrimitive.Portal>
      <TooltipPrimitive.Content
        data-slot="tooltip-content"
        sideOffset={sideOffset}
        className={cn(
          "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
          className,
        )}
        {...props}
      >
        {children}
        <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
      </TooltipPrimitive.Content>
    </TooltipPrimitive.Portal>
  );
}

export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };


================================================
FILE: app/chat/hooks/use-mobile.ts
================================================
import * as React from "react";

const MOBILE_BREAKPOINT = 768;

export function useIsMobile() {
  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
    undefined,
  );

  React.useEffect(() => {
    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
    const onChange = () => {
      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
    };
    mql.addEventListener("change", onChange);
    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
    return () => mql.removeEventListener("change", onChange);
  }, []);

  return !!isMobile;
}


================================================
FILE: app/chat/lib/constants.ts
================================================
/**
 * Constants used throughout the application
 */

// Local storage keys
export const STORAGE_KEYS = {
  MCP_SERVERS: "mcp-servers",
  SELECTED_MCP_SERVERS: "selected-mcp-servers",
  SIDEBAR_STATE: "sidebar-state",
  API_KEYS: "api-keys",
};


================================================
FILE: app/chat/lib/context/mcp-context.tsx
================================================
"use client";

import React, {
  createContext,
  useContext,
  useEffect,
  useMemo,
  useState,
} from "react";
import { useLocalStorage } from "~/chat/lib/hooks/use-local-storage";
import { STORAGE_KEYS } from "~/chat/lib/constants";

// Define types for MCP server
export interface KeyValuePair {
  key: string;
  value: string;
}

export interface MCPServer {
  id: string;
  name: string;
  url: string;
  type: "sse" | "stdio";
  command?: string;
  args?: string[];
  env?: KeyValuePair[];
  headers?: KeyValuePair[];
  description?: string;
  isFixed?: boolean;
}

// Type for processed MCP server config for API
export interface MCPServerApi {
  type: "sse" | "stdio";
  url: string;
  command?: string;
  args?: string[];
  env?: KeyValuePair[];
  headers?: KeyValuePair[];
}

interface MCPContextType {
  mcpServers: MCPServer[];
  setMcpServers: (servers: MCPServer[]) => void;
  selectedMcpServers: string[];
  setSelectedMcpServers: (serverIds: string[]) => void;
  mcpServersForApi: MCPServerApi[];
  owner: string;
  repo: string | null;
  serverNameText: string;
}

const MCPContext = createContext<MCPContextType | undefined>(undefined);

export function MCPProvider(props: {
  children: React.ReactNode;
  owner: string;
  repo: string | null;
  persist?: boolean;
}) {
  const { children, owner, repo, persist = false } = props;
  const [mcpServersFromLocalStorage, setMcpServersFromLocalStorage] =
    useLocalStorage<MCPServer[]>(STORAGE_KEYS.MCP_SERVERS, []);
  const [
    selectedMcpServersFromLocalStorage,
    setSelectedMcpServersFromLocalStorage,
  ] = useLocalStorage<string[]>(STORAGE_KEYS.SELECTED_MCP_SERVERS, []);

  const [mcpServersFromState, setMcpServersFromState] = useState<MCPServer[]>(
    [],
  );
  const [selectedMcpServersFromState, setSelectedMcpServersFromState] =
    useState<string[]>([]);

  const { serverNameText, serverUrl, serverName } = useMemo(() => {
    const repoName = repo || "";
    if (owner == "cloudflare" && repoName == "docs") {
      return {
        serverNameText: "Cloudflare docs",
        serverUrl: "https://docs.mcp.cloudflare.com/sse",
        serverName: "Cloudflare Docs",
      };
    }
    return {
      serverNameText: repoName ? `${repoName} docs` : "Github docs",
      serverUrl: ["https://gitmcp.io", owner, repo].filter(Boolean).join("/"),
      serverName: repoName ? `${repoName} Docs` : "MCP Docs",
    };
  }, [owner, repo]);

  const gitMcpServer = useMemo<MCPServer>(() => {
    return {
      id: ["gitMcp", owner, repo].filter(Boolean).join("-"),
      name: serverName,
      url: serverUrl,
      type: "sse",
      isFixed: true,
    };
  }, [owner, repo]);

  const {
    mcpServers,
    setMcpServers,
    selectedMcpServers,
    setSelectedMcpServers,
  } = useMemo(() => {
    if (persist) {
      return {
        mcpServers: [
          gitMcpServer,
          ...mcpServersFromLocalStorage.filter(
            (server) => server.id !== gitMcpServer.id,
          ),
        ],
        setMcpServers: setMcpServersFromLocalStorage,
        selectedMcpServers: [
          gitMcpServer.id,
          ...selectedMcpServersFromLocalStorage.filter(
            (id) => id !== gitMcpServer.id,
          ),
        ],
        setSelectedMcpServers: setSelectedMcpServersFromLocalStorage,
      };
    }
    return {
      mcpServers: [
        gitMcpServer,
        ...mcpServersFromState.filter(
          (server) => server.id !== gitMcpServer.id,
        ),
      ],
      setMcpServers: setMcpServersFromState,
      selectedMcpServers: [
        gitMcpServer.id,
        ...selectedMcpServersFromState.filter((id) => id !== gitMcpServer.id),
      ],
      setSelectedMcpServers: setSelectedMcpServersFromState,
    };
  }, [
    persist,
    mcpServersFromLocalStorage,
    setMcpServersFromLocalStorage,
    mcpServersFromState,
    setMcpServersFromState,
    selectedMcpServersFromState,
    setSelectedMcpServersFromState,
    selectedMcpServersFromLocalStorage,
    setSelectedMcpServersFromLocalStorage,
    gitMcpServer,
  ]);

  const [mcpServersForApi, setMcpServersForApi] = useState<MCPServerApi[]>([]);

  // Process MCP servers for API consumption whenever server data changes
  useEffect(() => {
    if (!selectedMcpServers.length) {
      setMcpServersForApi([]);
      return;
    }

    const processedServers: MCPServerApi[] = selectedMcpServers
      .map((id) => mcpServers.find((server) => server.id === id))
      .filter((server): server is MCPServer => Boolean(server))
      .map((server) => ({
        type: server.type,
        url: server.url,
        command: server.command,
        args: server.args,
        env: server.env,
        headers: server.headers,
      }));

    setMcpServersForApi(processedServers);
  }, [mcpServers, selectedMcpServers]);

  return (
    <MCPContext.Provider
      value={{
        mcpServers,
        setMcpServers,
        selectedMcpServers,
        setSelectedMcpServers,
        mcpServersForApi,
        owner,
        repo,
        serverNameText,
      }}
    >
      {children}
    </MCPContext.Provider>
  );
}

export function useMCP() {
  const context = useContext(MCPContext);
  if (context === undefined) {
    throw new Error("useMCP must be used within an MCPProvider");
  }
  return context;
}


================================================
FILE: app/chat/lib/db/schema.ts
================================================
// Message role enum type
export enum MessageRole {
  USER = "user",
  ASSISTANT = "assistant",
  TOOL = "tool",
}

// Types for structured message content
export type MessagePart = {
  type: string;
  text?: string;
  toolCallId?: string;
  toolName?: string;
  args?: any;
  result?: any;
  [key: string]: any;
};

export type Attachment = {
  type: string;
  [key: string]: any;
};

expo
Download .txt
gitextract_khbd3ulr/

├── .github/
│   ├── CONTRIBUTING.md
│   └── workflows/
│       ├── e2e-tests.yml
│       └── run-tests.yml
├── .gitignore
├── .husky/
│   └── pre-commit
├── .prettierrc
├── .react-router/
│   └── types/
│       ├── +register.ts
│       ├── +virtual.d.ts
│       └── app/
│           ├── +types/
│           │   └── root.ts
│           └── routes/
│               └── +types/
│                   ├── $.ts
│                   ├── _index.ts
│                   └── api.chat.ts
├── LICENSE
├── README.md
├── SECURITY.md
├── app/
│   ├── app.css
│   ├── chat/
│   │   ├── ai/
│   │   │   ├── providers.server.ts
│   │   │   └── providers.shared.ts
│   │   ├── components/
│   │   │   ├── api-key-manager.tsx
│   │   │   ├── api-keys-provider.tsx
│   │   │   ├── chat-sidebar.tsx
│   │   │   ├── chat.tsx
│   │   │   ├── copy-button.tsx
│   │   │   ├── icons.tsx
│   │   │   ├── input.tsx
│   │   │   ├── markdown.tsx
│   │   │   ├── mcp-server-manager.tsx
│   │   │   ├── message.tsx
│   │   │   ├── messages.tsx
│   │   │   ├── model-picker.tsx
│   │   │   ├── project-overview.tsx
│   │   │   ├── suggested-prompts.tsx
│   │   │   ├── textarea.tsx
│   │   │   ├── theme-provider.tsx
│   │   │   ├── theme-toggle.tsx
│   │   │   ├── tool-invocation.tsx
│   │   │   └── ui/
│   │   │       ├── accordion.tsx
│   │   │       ├── avatar.tsx
│   │   │       ├── badge.tsx
│   │   │       ├── button.tsx
│   │   │       ├── dialog.tsx
│   │   │       ├── dropdown-menu.tsx
│   │   │       ├── input.tsx
│   │   │       ├── label.tsx
│   │   │       ├── popover.tsx
│   │   │       ├── scroll-area.tsx
│   │   │       ├── select.tsx
│   │   │       ├── separator.tsx
│   │   │       ├── sheet.tsx
│   │   │       ├── sidebar.tsx
│   │   │       ├── skeleton.tsx
│   │   │       ├── sonner.tsx
│   │   │       ├── text-morph.tsx
│   │   │       ├── textarea.tsx
│   │   │       └── tooltip.tsx
│   │   ├── hooks/
│   │   │   └── use-mobile.ts
│   │   └── lib/
│   │       ├── constants.ts
│   │       ├── context/
│   │       │   └── mcp-context.tsx
│   │       ├── db/
│   │       │   └── schema.ts
│   │       ├── hooks/
│   │       │   ├── use-copy.ts
│   │       │   ├── use-local-storage.ts
│   │       │   └── use-scroll-to-bottom.tsx
│   │       ├── user-id.ts
│   │       └── utils.ts
│   ├── components/
│   │   ├── .client/
│   │   │   ├── chatPage.client.tsx
│   │   │   └── chatPage.css
│   │   ├── chatPage.tsx
│   │   └── content.tsx
│   ├── entry.server.tsx
│   ├── globals.css
│   ├── root.tsx
│   ├── routes/
│   │   ├── $.tsx
│   │   ├── _index.tsx
│   │   └── api.chat.ts
│   └── routes.ts
├── biome.json
├── components.json
├── package.json
├── playwright.config.ts
├── postcss.config.mjs
├── react-router.config.ts
├── src/
│   ├── api/
│   │   ├── test-setup.ts
│   │   ├── tools/
│   │   │   ├── commonTools.test.ts
│   │   │   ├── commonTools.ts
│   │   │   ├── index.test.ts
│   │   │   ├── index.ts
│   │   │   └── repoHandlers/
│   │   │       ├── DefaultRepoHandler.ts
│   │   │       ├── GenericRepoHandler.ts
│   │   │       ├── ReactRouterRepoHandler.ts
│   │   │       ├── RepoHandler.ts
│   │   │       ├── ThreejsRepoHandler.ts
│   │   │       ├── generic/
│   │   │       │   ├── generic.test.ts
│   │   │       │   └── static-mapping.json
│   │   │       ├── handlers.test.ts
│   │   │       ├── handlers.ts
│   │   │       ├── test/
│   │   │       │   └── utils.ts
│   │   │       └── threejs/
│   │   │           ├── __snapshots__/
│   │   │           │   └── utils.test.ts.snap
│   │   │           ├── utils.test.ts
│   │   │           └── utils.ts
│   │   └── utils/
│   │       ├── ViewCounterDO.ts
│   │       ├── badge.ts
│   │       ├── cache.ts
│   │       ├── github.ts
│   │       ├── githubClient.ts
│   │       ├── helpers.ts
│   │       ├── r2.ts
│   │       ├── robotsTxt.ts
│   │       └── vectorStore.ts
│   ├── index.ts
│   ├── shared/
│   │   ├── nameUtils.ts
│   │   ├── repoData.test.ts
│   │   ├── repoData.ts
│   │   └── urlUtils.ts
│   ├── test/
│   │   ├── ViewCounterDO.test.ts
│   │   └── badge.test.ts
│   └── utils.ts
├── static/
│   └── README.md
├── tailwind.config.js
├── tests/
│   ├── e2e/
│   │   └── inspection.spec.ts
│   └── global-setup.ts
├── tsconfig.cloudflare.json
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.ts
├── vitest.config.ts
├── worker-configuration.d.ts
└── wrangler.jsonc
Download .txt
SYMBOL INDEX (876 symbols across 82 files)

FILE: .react-router/types/+register.ts
  type Register (line 4) | interface Register {
  type Params (line 9) | type Params = {

FILE: .react-router/types/app/+types/root.ts
  type Module (line 8) | type Module = typeof import("../root.js")
  type Info (line 10) | type Info = {
  type LinkDescriptors (line 22) | type LinkDescriptors = T.LinkDescriptors
  type LinksFunction (line 23) | type LinksFunction = () => LinkDescriptors
  type MetaArgs (line 25) | type MetaArgs = T.CreateMetaArgs<Info>
  type MetaDescriptors (line 26) | type MetaDescriptors = T.MetaDescriptors
  type MetaFunction (line 27) | type MetaFunction = (args: MetaArgs) => MetaDescriptors
  type HeadersArgs (line 29) | type HeadersArgs = T.HeadersArgs
  type HeadersFunction (line 30) | type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit
  type unstable_MiddlewareFunction (line 32) | type unstable_MiddlewareFunction = T.CreateServerMiddlewareFunction<Info>
  type unstable_ClientMiddlewareFunction (line 33) | type unstable_ClientMiddlewareFunction = T.CreateClientMiddlewareFunctio...
  type LoaderArgs (line 34) | type LoaderArgs = T.CreateServerLoaderArgs<Info>
  type ClientLoaderArgs (line 35) | type ClientLoaderArgs = T.CreateClientLoaderArgs<Info>
  type ActionArgs (line 36) | type ActionArgs = T.CreateServerActionArgs<Info>
  type ClientActionArgs (line 37) | type ClientActionArgs = T.CreateClientActionArgs<Info>
  type HydrateFallbackProps (line 39) | type HydrateFallbackProps = T.CreateHydrateFallbackProps<Info>
  type ComponentProps (line 40) | type ComponentProps = T.CreateComponentProps<Info>
  type ErrorBoundaryProps (line 41) | type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Info>

FILE: .react-router/types/app/routes/+types/$.ts
  type Module (line 8) | type Module = typeof import("../$.js")
  type Info (line 10) | type Info = {
  type LinkDescriptors (line 22) | type LinkDescriptors = T.LinkDescriptors
  type LinksFunction (line 23) | type LinksFunction = () => LinkDescriptors
  type MetaArgs (line 25) | type MetaArgs = T.CreateMetaArgs<Info>
  type MetaDescriptors (line 26) | type MetaDescriptors = T.MetaDescriptors
  type MetaFunction (line 27) | type MetaFunction = (args: MetaArgs) => MetaDescriptors
  type HeadersArgs (line 29) | type HeadersArgs = T.HeadersArgs
  type HeadersFunction (line 30) | type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit
  type unstable_MiddlewareFunction (line 32) | type unstable_MiddlewareFunction = T.CreateServerMiddlewareFunction<Info>
  type unstable_ClientMiddlewareFunction (line 33) | type unstable_ClientMiddlewareFunction = T.CreateClientMiddlewareFunctio...
  type LoaderArgs (line 34) | type LoaderArgs = T.CreateServerLoaderArgs<Info>
  type ClientLoaderArgs (line 35) | type ClientLoaderArgs = T.CreateClientLoaderArgs<Info>
  type ActionArgs (line 36) | type ActionArgs = T.CreateServerActionArgs<Info>
  type ClientActionArgs (line 37) | type ClientActionArgs = T.CreateClientActionArgs<Info>
  type HydrateFallbackProps (line 39) | type HydrateFallbackProps = T.CreateHydrateFallbackProps<Info>
  type ComponentProps (line 40) | type ComponentProps = T.CreateComponentProps<Info>
  type ErrorBoundaryProps (line 41) | type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Info>

FILE: .react-router/types/app/routes/+types/_index.ts
  type Module (line 8) | type Module = typeof import("../_index.js")
  type Info (line 10) | type Info = {
  type LinkDescriptors (line 22) | type LinkDescriptors = T.LinkDescriptors
  type LinksFunction (line 23) | type LinksFunction = () => LinkDescriptors
  type MetaArgs (line 25) | type MetaArgs = T.CreateMetaArgs<Info>
  type MetaDescriptors (line 26) | type MetaDescriptors = T.MetaDescriptors
  type MetaFunction (line 27) | type MetaFunction = (args: MetaArgs) => MetaDescriptors
  type HeadersArgs (line 29) | type HeadersArgs = T.HeadersArgs
  type HeadersFunction (line 30) | type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit
  type unstable_MiddlewareFunction (line 32) | type unstable_MiddlewareFunction = T.CreateServerMiddlewareFunction<Info>
  type unstable_ClientMiddlewareFunction (line 33) | type unstable_ClientMiddlewareFunction = T.CreateClientMiddlewareFunctio...
  type LoaderArgs (line 34) | type LoaderArgs = T.CreateServerLoaderArgs<Info>
  type ClientLoaderArgs (line 35) | type ClientLoaderArgs = T.CreateClientLoaderArgs<Info>
  type ActionArgs (line 36) | type ActionArgs = T.CreateServerActionArgs<Info>
  type ClientActionArgs (line 37) | type ClientActionArgs = T.CreateClientActionArgs<Info>
  type HydrateFallbackProps (line 39) | type HydrateFallbackProps = T.CreateHydrateFallbackProps<Info>
  type ComponentProps (line 40) | type ComponentProps = T.CreateComponentProps<Info>
  type ErrorBoundaryProps (line 41) | type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Info>

FILE: .react-router/types/app/routes/+types/api.chat.ts
  type Module (line 8) | type Module = typeof import("../api.chat.js")
  type Info (line 10) | type Info = {
  type LinkDescriptors (line 22) | type LinkDescriptors = T.LinkDescriptors
  type LinksFunction (line 23) | type LinksFunction = () => LinkDescriptors
  type MetaArgs (line 25) | type MetaArgs = T.CreateMetaArgs<Info>
  type MetaDescriptors (line 26) | type MetaDescriptors = T.MetaDescriptors
  type MetaFunction (line 27) | type MetaFunction = (args: MetaArgs) => MetaDescriptors
  type HeadersArgs (line 29) | type HeadersArgs = T.HeadersArgs
  type HeadersFunction (line 30) | type HeadersFunction = (args: HeadersArgs) => Headers | HeadersInit
  type unstable_MiddlewareFunction (line 32) | type unstable_MiddlewareFunction = T.CreateServerMiddlewareFunction<Info>
  type unstable_ClientMiddlewareFunction (line 33) | type unstable_ClientMiddlewareFunction = T.CreateClientMiddlewareFunctio...
  type LoaderArgs (line 34) | type LoaderArgs = T.CreateServerLoaderArgs<Info>
  type ClientLoaderArgs (line 35) | type ClientLoaderArgs = T.CreateClientLoaderArgs<Info>
  type ActionArgs (line 36) | type ActionArgs = T.CreateServerActionArgs<Info>
  type ClientActionArgs (line 37) | type ClientActionArgs = T.CreateClientActionArgs<Info>
  type HydrateFallbackProps (line 39) | type HydrateFallbackProps = T.CreateHydrateFallbackProps<Info>
  type ComponentProps (line 40) | type ComponentProps = T.CreateComponentProps<Info>
  type ErrorBoundaryProps (line 41) | type ErrorBoundaryProps = T.CreateErrorBoundaryProps<Info>

FILE: app/chat/ai/providers.shared.ts
  type ModelInfo (line 1) | interface ModelInfo {
  type StorageKey (line 9) | type StorageKey =
  type modelID (line 15) | type modelID =
  constant MODELS (line 56) | const MODELS = Object.keys(modelDetails);

FILE: app/chat/components/api-key-manager.tsx
  type ApiKeyConfig (line 19) | interface ApiKeyConfig {
  constant API_KEYS_CONFIG (line 28) | const API_KEYS_CONFIG: readonly ApiKeyConfig[] = [
  type ApiKeyManagerProps (line 59) | interface ApiKeyManagerProps {
  function ApiKeyManager (line 64) | function ApiKeyManager({ open, onOpenChange }: ApiKeyManagerProps) {

FILE: app/chat/components/api-keys-provider.tsx
  function ApiKeysProvider (line 14) | function ApiKeysProvider({ children }: { children: React.ReactNode }) {
  function useApiKeys (line 26) | function useApiKeys() {

FILE: app/chat/components/chat-sidebar.tsx
  function ChatSidebar (line 33) | function ChatSidebar() {

FILE: app/chat/components/chat.tsx
  constant CHAT_API_URL (line 14) | const CHAT_API_URL = "https://chat-api-worker.idosalomon.workers.dev/api...
  function Chat (line 16) | function Chat() {

FILE: app/chat/components/copy-button.tsx
  type CopyButtonProps (line 6) | interface CopyButtonProps {
  function CopyButton (line 11) | function CopyButton({ text, className }: CopyButtonProps) {

FILE: app/chat/components/input.tsx
  type InputProps (line 4) | interface InputProps {

FILE: app/chat/components/mcp-server-manager.tsx
  constant INITIAL_NEW_SERVER (line 28) | const INITIAL_NEW_SERVER: Omit<MCPServer, "id"> = {
  type MCPServerManagerProps (line 38) | interface MCPServerManagerProps {
  function McpServerListItem (line 329) | function McpServerListItem({

FILE: app/chat/components/message.tsx
  type ReasoningPart (line 19) | interface ReasoningPart {
  type ReasoningMessagePartProps (line 25) | interface ReasoningMessagePartProps {
  function ReasoningMessagePart (line 30) | function ReasoningMessagePart({

FILE: app/chat/components/model-picker.tsx
  type ModelPickerProps (line 32) | interface ModelPickerProps {

FILE: app/chat/components/suggested-prompts.tsx
  type SuggestedPromptsProps (line 7) | interface SuggestedPromptsProps {
  function PureSuggestedPrompts (line 11) | function PureSuggestedPrompts({ sendMessage }: SuggestedPromptsProps) {

FILE: app/chat/components/textarea.tsx
  type InputProps (line 6) | interface InputProps {

FILE: app/chat/components/theme-provider.tsx
  function ThemeProvider (line 6) | function ThemeProvider({ children, ...props }: ThemeProviderProps) {

FILE: app/chat/components/theme-toggle.tsx
  function ThemeToggle (line 22) | function ThemeToggle({

FILE: app/chat/components/tool-invocation.tsx
  type ToolInvocationProps (line 17) | interface ToolInvocationProps {
  function ToolInvocation (line 26) | function ToolInvocation({

FILE: app/chat/components/ui/accordion.tsx
  function Accordion (line 9) | function Accordion({
  function AccordionItem (line 15) | function AccordionItem({
  function AccordionTrigger (line 28) | function AccordionTrigger({
  function AccordionContent (line 50) | function AccordionContent({

FILE: app/chat/components/ui/avatar.tsx
  function Avatar (line 8) | function Avatar({
  function AvatarImage (line 24) | function AvatarImage({
  function AvatarFallback (line 37) | function AvatarFallback({

FILE: app/chat/components/ui/badge.tsx
  function Badge (line 28) | function Badge({

FILE: app/chat/components/ui/button.tsx
  function Button (line 38) | function Button({

FILE: app/chat/components/ui/dialog.tsx
  function Dialog (line 9) | function Dialog({
  function DialogTrigger (line 15) | function DialogTrigger({
  function DialogPortal (line 21) | function DialogPortal({
  function DialogClose (line 27) | function DialogClose({
  function DialogOverlay (line 33) | function DialogOverlay({
  function DialogContent (line 49) | function DialogContent({
  function DialogHeader (line 75) | function DialogHeader({ className, ...props }: React.ComponentProps<"div...
  function DialogFooter (line 85) | function DialogFooter({ className, ...props }: React.ComponentProps<"div...
  function DialogTitle (line 98) | function DialogTitle({
  function DialogDescription (line 111) | function DialogDescription({

FILE: app/chat/components/ui/dropdown-menu.tsx
  function DropdownMenu (line 9) | function DropdownMenu({
  function DropdownMenuPortal (line 15) | function DropdownMenuPortal({
  function DropdownMenuTrigger (line 23) | function DropdownMenuTrigger({
  function DropdownMenuContent (line 34) | function DropdownMenuContent({
  function DropdownMenuGroup (line 54) | function DropdownMenuGroup({
  function DropdownMenuItem (line 62) | function DropdownMenuItem({
  function DropdownMenuCheckboxItem (line 85) | function DropdownMenuCheckboxItem({
  function DropdownMenuRadioGroup (line 111) | function DropdownMenuRadioGroup({
  function DropdownMenuRadioItem (line 122) | function DropdownMenuRadioItem({
  function DropdownMenuLabel (line 146) | function DropdownMenuLabel({
  function DropdownMenuSeparator (line 166) | function DropdownMenuSeparator({
  function DropdownMenuShortcut (line 179) | function DropdownMenuShortcut({
  function DropdownMenuSub (line 195) | function DropdownMenuSub({
  function DropdownMenuSubTrigger (line 201) | function DropdownMenuSubTrigger({
  function DropdownMenuSubContent (line 225) | function DropdownMenuSubContent({

FILE: app/chat/components/ui/input.tsx
  function Input (line 5) | function Input({ className, type, ...props }: React.ComponentProps<"inpu...

FILE: app/chat/components/ui/label.tsx
  function Label (line 8) | function Label({

FILE: app/chat/components/ui/popover.tsx
  function Popover (line 8) | function Popover({
  function PopoverTrigger (line 14) | function PopoverTrigger({
  function PopoverContent (line 20) | function PopoverContent({
  function PopoverAnchor (line 42) | function PopoverAnchor({

FILE: app/chat/components/ui/scroll-area.tsx
  function ScrollArea (line 8) | function ScrollArea({
  function ScrollBar (line 31) | function ScrollBar({

FILE: app/chat/components/ui/select.tsx
  function Select (line 9) | function Select({
  function SelectGroup (line 15) | function SelectGroup({
  function SelectValue (line 21) | function SelectValue({
  function SelectTrigger (line 27) | function SelectTrigger({
  function SelectContent (line 49) | function SelectContent({
  function SelectLabel (line 84) | function SelectLabel({
  function SelectItem (line 97) | function SelectItem({
  function SelectSeparator (line 121) | function SelectSeparator({
  function SelectScrollUpButton (line 134) | function SelectScrollUpButton({
  function SelectScrollDownButton (line 152) | function SelectScrollDownButton({

FILE: app/chat/components/ui/separator.tsx
  function Separator (line 8) | function Separator({

FILE: app/chat/components/ui/sheet.tsx
  function Sheet (line 9) | function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive....
  function SheetTrigger (line 13) | function SheetTrigger({
  function SheetClose (line 19) | function SheetClose({
  function SheetPortal (line 25) | function SheetPortal({
  function SheetOverlay (line 31) | function SheetOverlay({
  function SheetContent (line 47) | function SheetContent({
  function SheetHeader (line 84) | function SheetHeader({ className, ...props }: React.ComponentProps<"div"...
  function SheetFooter (line 94) | function SheetFooter({ className, ...props }: React.ComponentProps<"div"...
  function SheetTitle (line 104) | function SheetTitle({
  function SheetDescription (line 117) | function SheetDescription({

FILE: app/chat/components/ui/sidebar.tsx
  constant SIDEBAR_COOKIE_NAME (line 26) | const SIDEBAR_COOKIE_NAME = "sidebar_state";
  constant SIDEBAR_COOKIE_MAX_AGE (line 27) | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
  constant SIDEBAR_WIDTH (line 28) | const SIDEBAR_WIDTH = "13rem";
  constant SIDEBAR_WIDTH_MOBILE (line 29) | const SIDEBAR_WIDTH_MOBILE = "15rem";
  constant SIDEBAR_WIDTH_ICON (line 30) | const SIDEBAR_WIDTH_ICON = "3rem";
  constant SIDEBAR_KEYBOARD_SHORTCUT (line 31) | const SIDEBAR_KEYBOARD_SHORTCUT = "b";
  type SidebarContextProps (line 33) | type SidebarContextProps = {
  function useSidebar (line 45) | function useSidebar() {
  function SidebarProvider (line 54) | function SidebarProvider({
  function Sidebar (line 152) | function Sidebar({
  function SidebarTrigger (line 254) | function SidebarTrigger({
  function SidebarRail (line 280) | function SidebarRail({ className, ...props }: React.ComponentProps<"butt...
  function SidebarInset (line 305) | function SidebarInset({ className, ...props }: React.ComponentProps<"mai...
  function SidebarInput (line 319) | function SidebarInput({
  function SidebarHeader (line 333) | function SidebarHeader({ className, ...props }: React.ComponentProps<"di...
  function SidebarFooter (line 344) | function SidebarFooter({ className, ...props }: React.ComponentProps<"di...
  function SidebarSeparator (line 355) | function SidebarSeparator({
  function SidebarContent (line 369) | function SidebarContent({ className, ...props }: React.ComponentProps<"d...
  function SidebarGroup (line 383) | function SidebarGroup({ className, ...props }: React.ComponentProps<"div...
  function SidebarGroupLabel (line 394) | function SidebarGroupLabel({
  function SidebarGroupAction (line 415) | function SidebarGroupAction({
  function SidebarGroupContent (line 438) | function SidebarGroupContent({
  function SidebarMenu (line 452) | function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
  function SidebarMenuItem (line 463) | function SidebarMenuItem({ className, ...props }: React.ComponentProps<"...
  function SidebarMenuButton (line 496) | function SidebarMenuButton({
  function SidebarMenuAction (line 548) | function SidebarMenuAction({
  function SidebarMenuBadge (line 580) | function SidebarMenuBadge({
  function SidebarMenuSkeleton (line 602) | function SidebarMenuSkeleton({
  function SidebarMenuSub (line 640) | function SidebarMenuSub({ className, ...props }: React.ComponentProps<"u...
  function SidebarMenuSubItem (line 655) | function SidebarMenuSubItem({
  function SidebarMenuSubButton (line 669) | function SidebarMenuSubButton({

FILE: app/chat/components/ui/skeleton.tsx
  function Skeleton (line 3) | function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

FILE: app/chat/components/ui/text-morph.tsx
  type TextMorphProps (line 10) | type TextMorphProps = {
  function TextMorph (line 19) | function TextMorph({

FILE: app/chat/components/ui/textarea.tsx
  function Textarea (line 5) | function Textarea({ className, ...props }: React.ComponentProps<"textare...

FILE: app/chat/components/ui/tooltip.tsx
  function TooltipProvider (line 8) | function TooltipProvider({
  function Tooltip (line 21) | function Tooltip({
  function TooltipTrigger (line 31) | function TooltipTrigger({
  function TooltipContent (line 37) | function TooltipContent({

FILE: app/chat/hooks/use-mobile.ts
  constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768;
  function useIsMobile (line 5) | function useIsMobile() {

FILE: app/chat/lib/constants.ts
  constant STORAGE_KEYS (line 6) | const STORAGE_KEYS = {

FILE: app/chat/lib/context/mcp-context.tsx
  type KeyValuePair (line 14) | interface KeyValuePair {
  type MCPServer (line 19) | interface MCPServer {
  type MCPServerApi (line 33) | interface MCPServerApi {
  type MCPContextType (line 42) | interface MCPContextType {
  function MCPProvider (line 55) | function MCPProvider(props: {
  function useMCP (line 194) | function useMCP() {

FILE: app/chat/lib/db/schema.ts
  type MessageRole (line 2) | enum MessageRole {
  type MessagePart (line 9) | type MessagePart = {
  type Attachment (line 19) | type Attachment = {
  type Chat (line 24) | type Chat = {
  type Message (line 31) | type Message = {

FILE: app/chat/lib/hooks/use-copy.ts
  function useCopy (line 3) | function useCopy(timeout = 2000) {

FILE: app/chat/lib/hooks/use-local-storage.ts
  type SetValue (line 3) | type SetValue<T> = T | ((val: T) => T);
  function useLocalStorage (line 11) | function useLocalStorage<T>(key: string, initialValue: T) {
  function parseJSON (line 64) | function parseJSON<T>(value: string): T {
  function useLocalStorageValue (line 79) | function useLocalStorageValue<T>(key: string, defaultValue: T): T {

FILE: app/chat/lib/hooks/use-scroll-to-bottom.tsx
  function useScrollToBottom (line 3) | function useScrollToBottom(): [

FILE: app/chat/lib/user-id.ts
  constant USER_ID_KEY (line 3) | const USER_ID_KEY = "ai-chat-user-id";
  function getUserId (line 5) | function getUserId(): string {
  function updateUserId (line 20) | function updateUserId(newUserId: string): void {

FILE: app/chat/lib/utils.ts
  function cn (line 4) | function cn(...inputs: ClassValue[]) {
  function getRepoData (line 8) | function getRepoData(url: string): {

FILE: app/components/.client/chatPage.client.tsx
  function ChatPage (line 14) | function ChatPage({

FILE: app/components/chatPage.tsx
  function ChatPageServer (line 3) | function ChatPageServer({

FILE: app/components/content.tsx
  function Content (line 6) | function Content({
  function CodeExample (line 701) | function CodeExample({

FILE: app/entry.server.tsx
  function handleRequest (line 6) | async function handleRequest(

FILE: app/root.tsx
  function App (line 4) | function App() {

FILE: app/routes/$.tsx
  function HydrateFallback (line 43) | function HydrateFallback() {
  function ContentPage (line 47) | function ContentPage({
  function isChatPage (line 61) | function isChatPage({

FILE: app/routes/_index.tsx
  function Home (line 5) | function Home() {
  function Example (line 598) | function Example({
  function Divider (line 630) | function Divider({ text, simple }: { text?: string; simple?: boolean }) {
  function Carousel (line 652) | function Carousel() {

FILE: app/routes/api.chat.ts
  type KeyValuePair (line 11) | interface KeyValuePair {
  type MCPServerConfig (line 16) | interface MCPServerConfig {
  function action (line 25) | async function action({

FILE: src/api/test-setup.ts
  type TestSetupBindings (line 1) | interface TestSetupBindings extends Env {
  function handleR2TestSetup (line 24) | async function handleR2TestSetup(

FILE: src/api/tools/commonTools.ts
  type FetchDocumentationResult (line 19) | type FetchDocumentationResult = {
  function fetchDocumentation (line 25) | async function fetchDocumentation({
  function enqueueDocumentationProcessing (line 299) | async function enqueueDocumentationProcessing(
  function searchRepositoryDocumentation (line 340) | async function searchRepositoryDocumentation({
  function searchRepositoryDocumentationAutoRag (line 393) | async function searchRepositoryDocumentationAutoRag({
  function searchRepositoryDocumentationNaive (line 506) | async function searchRepositoryDocumentationNaive({
  function searchRepositoryCode (line 578) | async function searchRepositoryCode({
  function fetchUrlContent (line 711) | async function fetchUrlContent({ url, env }: { url: string; env: Env }) {
  constant LIMIT (line 783) | const LIMIT = 51;
  function enforceToolNameLengthLimit (line 792) | function enforceToolNameLengthLimit(
  function generateSearchToolName (line 855) | function generateSearchToolName({ urlType, repo }: RepoData): string {
  function generateSearchToolDescription (line 878) | function generateSearchToolDescription({
  function generateFetchToolDescription (line 907) | function generateFetchToolDescription({
  function generateFetchToolName (line 935) | function generateFetchToolName({
  function generateCodeSearchToolName (line 963) | function generateCodeSearchToolName({
  function generateCodeSearchToolDescription (line 988) | function generateCodeSearchToolDescription({
  function listAllSubfolders (line 1001) | async function listAllSubfolders(bucket: R2Bucket, startPrefix: string) {

FILE: src/api/tools/index.ts
  function getMcpTools (line 8) | function getMcpTools(

FILE: src/api/tools/repoHandlers/DefaultRepoHandler.ts
  class DefaultRepoHandler (line 18) | class DefaultRepoHandler implements RepoHandler {
    method getTools (line 20) | getTools(repoData: RepoData, env: any, ctx: any): Array<Tool> {
    method fetchDocumentation (line 95) | async fetchDocumentation({
    method searchRepositoryDocumentation (line 110) | async searchRepositoryDocumentation({
  function getDefaultRepoHandler (line 134) | function getDefaultRepoHandler(): DefaultRepoHandler {

FILE: src/api/tools/repoHandlers/GenericRepoHandler.ts
  class GenericRepoHandler (line 15) | class GenericRepoHandler implements RepoHandler {
    method getTools (line 17) | getTools(_: RepoData, env: any, ctx: any): Array<Tool> {
    method fetchDocumentation (line 178) | async fetchDocumentation({
    method searchRepositoryDocumentation (line 193) | async searchRepositoryDocumentation({
  function getGenericRepoHandler (line 212) | function getGenericRepoHandler(): GenericRepoHandler {
  function fetchRepoMapping (line 219) | async function fetchRepoMapping(): Promise<
  function fetchRepoMappingByRepoName (line 225) | async function fetchRepoMappingByRepoName(): Promise<

FILE: src/api/tools/repoHandlers/ReactRouterRepoHandler.ts
  class ReactRouterRepoHandler (line 13) | class ReactRouterRepoHandler implements RepoHandler {
    method getTools (line 15) | getTools(repoData: RepoData, env: any, ctx: any): Array<Tool> {
    method fetchDocumentation (line 51) | async fetchDocumentation({
    method searchRepositoryDocumentation (line 66) | async searchRepositoryDocumentation({
  function getReactRouterRepoHandler (line 90) | function getReactRouterRepoHandler(): ReactRouterRepoHandler {

FILE: src/api/tools/repoHandlers/RepoHandler.ts
  type Tool (line 4) | interface Tool {
  type RepoHandler (line 12) | interface RepoHandler {

FILE: src/api/tools/repoHandlers/ThreejsRepoHandler.ts
  constant GET_REFERENCE_DOCS_LIST_TOOL_NAME (line 12) | const GET_REFERENCE_DOCS_LIST_TOOL_NAME = "get_threejs_reference_docs_li...
  constant GET_SPECIFIC_DOCS_CONTENT_TOOL_NAME (line 13) | const GET_SPECIFIC_DOCS_CONTENT_TOOL_NAME = "get_threejs_specific_docs_c...
  class ThreejsRepoHandler (line 15) | class ThreejsRepoHandler implements RepoHandler {
    method getTools (line 17) | getTools(repoData: RepoData, env: any, ctx: any): Array<Tool> {
    method fetchDocumentation (line 111) | async fetchDocumentation({
    method searchRepositoryDocumentation (line 129) | async searchRepositoryDocumentation({
  function getThreejsRepoHandler (line 155) | function getThreejsRepoHandler(): ThreejsRepoHandler {
  function noopFallbackSearch (line 162) | async function noopFallbackSearch({

FILE: src/api/tools/repoHandlers/handlers.ts
  function getHandlerByRepoData (line 16) | function getHandlerByRepoData(repoData: RepoData): RepoHandler {
  type RepoKey (line 37) | type RepoKey = `${string}/${string}`;
  type UrlTypeRepoKey (line 38) | type UrlTypeRepoKey = `${UrlType}::${RepoKey}`;
  type AllRepoKey (line 39) | type AllRepoKey = `all::${RepoKey}`;
  type MapRepoKey (line 40) | type MapRepoKey = UrlTypeRepoKey | AllRepoKey;
  type RepoHandlerMap (line 42) | type RepoHandlerMap = {

FILE: src/api/tools/repoHandlers/test/utils.ts
  class MockMcp (line 3) | class MockMcp {
    method tool (line 12) | tool(
    method getTool (line 21) | getTool(name: string) {
    method getTools (line 25) | getTools() {

FILE: src/api/tools/repoHandlers/threejs/utils.test.ts
  constant THREEJS_DOCS_REF_URL (line 4) | const THREEJS_DOCS_REF_URL = "https://threejs.org/docs/list.json";
  constant THREEJS_MANUAL_REF_URL (line 5) | const THREEJS_MANUAL_REF_URL = "https://threejs.org/manual/list.json";

FILE: src/api/tools/repoHandlers/threejs/utils.ts
  constant THREEJS_BASE_URL (line 3) | const THREEJS_BASE_URL = "https://threejs.org";
  constant THREEJS_DOCS_BASE_URL (line 4) | const THREEJS_DOCS_BASE_URL = `${THREEJS_BASE_URL}/docs`;
  constant THREEJS_MANUAL_BASE_URL (line 5) | const THREEJS_MANUAL_BASE_URL = `${THREEJS_BASE_URL}/manual`;
  constant THREEJS_DOCS_REF_URL (line 6) | const THREEJS_DOCS_REF_URL = `${THREEJS_DOCS_BASE_URL}/list.json`;
  constant THREEJS_MANUAL_REF_URL (line 7) | const THREEJS_MANUAL_REF_URL = `${THREEJS_MANUAL_BASE_URL}/list.json`;
  function getByKey (line 12) | async function getByKey(
  function getListFlatCache (line 44) | async function getListFlatCache(env: Env) {
  function getReferenceDocsList (line 73) | async function getReferenceDocsList({ env }: { env: Env }): Promise<{
  function getReferenceDocsListAsMarkdown (line 100) | async function getReferenceDocsListAsMarkdown({
  function getReferenceDocsContent (line 167) | async function getReferenceDocsContent({
  function fetchThreeJsUrlsAsMarkdown (line 200) | async function fetchThreeJsUrlsAsMarkdown(

FILE: src/api/utils/ViewCounterDO.ts
  class ViewCounterDO (line 6) | class ViewCounterDO {
    method constructor (line 15) | constructor(state: DurableObjectState) {
    method initialize (line 26) | private async initialize() {
    method setupAlarm (line 41) | private setupAlarm() {
    method alarm (line 57) | async alarm() {
    method flushBuffer (line 66) | private async flushBuffer() {
    method bufferIncrement (line 100) | private bufferIncrement(key: string, amount: number = 1): number {
    method fetch (line 124) | async fetch(request: Request): Promise<Response> {

FILE: src/api/utils/badge.ts
  function getViewCounterStub (line 14) | function getViewCounterStub(
  function incrementRepoViewCount (line 41) | async function incrementRepoViewCount(
  function getRepoViewCount (line 97) | async function getRepoViewCount(
  function withViewTracking (line 160) | function withViewTracking<T, R>(
  function getEscapedLogo (line 203) | function getEscapedLogo(): string {
  function generateBadgeResponse (line 215) | function generateBadgeResponse(

FILE: src/api/utils/cache.ts
  type FormatOptions (line 5) | type FormatOptions = {
  constant BASE_TTL (line 11) | const BASE_TTL = 60 * 60 * 12;
  constant JITTER_FACTOR (line 12) | const JITTER_FACTOR = 0.2;
  function getCacheTTL (line 15) | function getCacheTTL(): number {
  function getRepoFilePathCacheKey (line 27) | function getRepoFilePathCacheKey(owner: string, repo: string): string {
  function getIsIndexedCacheKey (line 37) | function getIsIndexedCacheKey(owner: string, repo: string): string {
  function getFetchDocCacheKey (line 47) | function getFetchDocCacheKey(owner: string, repo: string): string {
  function getUrlContentCacheKey (line 57) | function getUrlContentCacheKey(url: string, format: string): string {
  function getFromCache (line 67) | async function getFromCache(key: string, env: Env): Promise<any> {
  function setInCache (line 89) | async function setInCache(
  function getCachedFilePath (line 118) | async function getCachedFilePath(
  function cacheFilePath (line 142) | async function cacheFilePath(
  function getRobotsTxtCacheKey (line 166) | function getRobotsTxtCacheKey(domain: string): string {
  function getCachedRobotsTxt (line 176) | async function getCachedRobotsTxt(
  function cacheRobotsTxt (line 196) | async function cacheRobotsTxt(
  function getIsIndexedFromCache (line 216) | async function getIsIndexedFromCache(
  function cacheIsIndexed (line 238) | async function cacheIsIndexed(
  function fetchUrlContent (line 259) | async function fetchUrlContent<T extends keyof FormatOptions>({
  function getCachedFetchDocResult (line 308) | async function getCachedFetchDocResult(
  function cacheFetchDocResult (line 331) | async function cacheFetchDocResult(

FILE: src/api/utils/github.ts
  function fetchFileFromGitHub (line 18) | async function fetchFileFromGitHub(
  type GitHubFile (line 29) | interface GitHubFile {
  function searchGitHubRepo (line 35) | async function searchGitHubRepo(
  function constructGithubUrl (line 90) | function constructGithubUrl(
  function getRepoBranch (line 110) | async function getRepoBranch(

FILE: src/api/utils/githubClient.ts
  constant DEFAULT_DELAY (line 9) | const DEFAULT_DELAY = 1000;
  constant MAX_RETRIES (line 11) | const MAX_RETRIES = 3;
  function extractRepoContextFromUrl (line 17) | function extractRepoContextFromUrl(url: string): string {
  type RateLimitInfo (line 55) | interface RateLimitInfo {
  function updateRateLimitFromHeaders (line 71) | function updateRateLimitFromHeaders(headers: Headers): void {
  function respectRateLimits (line 96) | async function respectRateLimits(): Promise<void> {
  function githubApiRequest (line 125) | async function githubApiRequest(
  function searchCode (line 245) | async function searchCode(
  function searchFileByName (line 277) | async function searchFileByName(
  function fetchRawFile (line 305) | async function fetchRawFile(

FILE: src/api/utils/helpers.ts
  function formatSearchResults (line 8) | function formatSearchResults(
  function fetchFile (line 83) | async function fetchFile(url: string): Promise<string | null> {

FILE: src/api/utils/r2.ts
  function fetchFileFromR2 (line 1) | async function fetchFileFromR2(

FILE: src/api/utils/robotsTxt.ts
  type RobotsRule (line 9) | interface RobotsRule {
  function parseRobotsTxt (line 20) | function parseRobotsTxt(content: string): RobotsRule[] {
  function isPathAllowed (line 75) | function isPathAllowed(rules: RobotsRule[], path: string): boolean {
  function checkRobotsTxt (line 124) | async function checkRobotsTxt(
  function fetchFileWithRobotsTxtCheck (line 173) | async function fetchFileWithRobotsTxtCheck(

FILE: src/api/utils/vectorStore.ts
  type Dict (line 2) | type Dict = { [key: string]: any };
  constant VECTOR_TTL (line 5) | const VECTOR_TTL = 60 * 60 * 24 * 1 * 1000;
  type VectorizeVector (line 8) | interface VectorizeVector {
  type VectorizeMatch (line 14) | interface VectorizeMatch {
  type VectorizeMatches (line 20) | interface VectorizeMatches {
  type Vectorize (line 25) | interface Vectorize {
  function getRepoNamespace (line 49) | function getRepoNamespace(owner: string, repo: string): string {
  function getVectorId (line 62) | function getVectorId(
  function getEmbeddings (line 78) | async function getEmbeddings(text: string): Promise<number[]> {
  function extractKeywords (line 136) | function extractKeywords(text: string): Array<{ term: string; score: num...
  function normalizeVector (line 192) | function normalizeVector(vector: Float32Array): void {
  function simpleHash (line 211) | function simpleHash(str: string): number {
  function chunkReadme (line 269) | function chunkReadme(text: string, fileName?: string): string[] {
  function chunkDocumentation (line 510) | function chunkDocumentation(text: string, fileName?: string): string[] {
  function chunkText (line 561) | function chunkText(
  type VectorMetadata (line 645) | interface VectorMetadata {
  function storeDocumentationVectors (line 663) | async function storeDocumentationVectors(
  function calculateKeywordMatchScore (line 752) | function calculateKeywordMatchScore(text: string, query: string): number {
  function searchDocumentation (line 849) | async function searchDocumentation(
  function chunkStructuredDocs (line 932) | function chunkStructuredDocs(text: string): string[] {

FILE: src/index.ts
  type CloudflareEnvironment (line 16) | interface CloudflareEnvironment extends Env {
  type AppLoadContext (line 25) | interface AppLoadContext {
  function handleBadgeRequest (line 53) | async function handleBadgeRequest(
  class MyMCP (line 65) | class MyMCP extends McpAgent {
    method init (line 71) | async init() {
  method fetch (line 114) | async fetch(request: Request, env: any, ctx: any) {

FILE: src/shared/nameUtils.ts
  function generateServerName (line 6) | function generateServerName(repo: string | null | undefined): string {

FILE: src/shared/repoData.ts
  type UrlType (line 1) | type UrlType = "subdomain" | "github" | "unknown";
  type MinimalRepoData (line 2) | type MinimalRepoData = {
  type RepoData (line 7) | type RepoData = MinimalRepoData & {
  type RequestData (line 11) | type RequestData = {
  type LogData (line 15) | type LogData = RepoData & RequestData;
  function getRepoData (line 17) | function getRepoData(requestData: RequestData): RepoData {
  function log (line 111) | function log(...args: any[]) {
  constant HOST_TEMP_URL (line 115) | const HOST_TEMP_URL = "remote-mcp-server-cf.idosalomon.workers.dev";
  function getRepoDataFromUrl (line 117) | function getRepoDataFromUrl(url: string): MinimalRepoData {

FILE: src/shared/urlUtils.ts
  function removeLeadingUnderscore (line 1) | function removeLeadingUnderscore(url: string) {

FILE: tests/global-setup.ts
  function globalSetup (line 8) | async function globalSetup(config: FullConfig) {

FILE: worker-configuration.d.ts
  type Env (line 5) | interface Env {
  type Env (line 19) | interface Env extends Cloudflare.Env {}
  type StringifyValues (line 20) | type StringifyValues<EnvType extends Record<string, unknown>> = {
  type ProcessEnv (line 24) | interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "GITHU...
  class DOMException (line 50) | class DOMException extends Error {
  type WorkerGlobalScopeEventMap (line 90) | type WorkerGlobalScopeEventMap = {
  type Console (line 101) | interface Console {
  type BufferSource (line 142) | type BufferSource = ArrayBufferView | ArrayBuffer;
  type TypedArray (line 143) | type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Arra...
  class CompileError (line 145) | class CompileError extends Error {
  class RuntimeError (line 148) | class RuntimeError extends Error {
  type ValueType (line 151) | type ValueType = "anyfunc" | "externref" | "f32" | "f64" | "i32" | "i64"...
  type GlobalDescriptor (line 152) | interface GlobalDescriptor {
  class Global (line 156) | class Global {
  type ImportValue (line 161) | type ImportValue = ExportValue | number;
  type ModuleImports (line 162) | type ModuleImports = Record<string, ImportValue>;
  type Imports (line 163) | type Imports = Record<string, ModuleImports>;
  type ExportValue (line 164) | type ExportValue = Function | Global | Memory | Table;
  type Exports (line 165) | type Exports = Record<string, ExportValue>;
  class Instance (line 166) | class Instance {
  type MemoryDescriptor (line 170) | interface MemoryDescriptor {
  class Memory (line 175) | class Memory {
  type ImportExportKind (line 180) | type ImportExportKind = "function" | "global" | "memory" | "table";
  type ModuleExportDescriptor (line 181) | interface ModuleExportDescriptor {
  type ModuleImportDescriptor (line 185) | interface ModuleImportDescriptor {
  type TableKind (line 195) | type TableKind = "anyfunc" | "externref";
  type TableDescriptor (line 196) | interface TableDescriptor {
  class Table (line 201) | class Table {
  type ServiceWorkerGlobalScope (line 217) | interface ServiceWorkerGlobalScope extends WorkerGlobalScope {
  type TestController (line 355) | interface TestController {
  type ExecutionContext (line 357) | interface ExecutionContext {
  type ExportedHandlerFetchHandler (line 362) | type ExportedHandlerFetchHandler<Env = unknown, CfHostMetadata = unknown...
  type ExportedHandlerTailHandler (line 363) | type ExportedHandlerTailHandler<Env = unknown> = (events: TraceItem[], e...
  type ExportedHandlerTraceHandler (line 364) | type ExportedHandlerTraceHandler<Env = unknown> = (traces: TraceItem[], ...
  type ExportedHandlerTailStreamHandler (line 365) | type ExportedHandlerTailStreamHandler<Env = unknown> = (event: TailStrea...
  type ExportedHandlerScheduledHandler (line 366) | type ExportedHandlerScheduledHandler<Env = unknown> = (controller: Sched...
  type ExportedHandlerQueueHandler (line 367) | type ExportedHandlerQueueHandler<Env = unknown, Message = unknown> = (ba...
  type ExportedHandlerTestHandler (line 368) | type ExportedHandlerTestHandler<Env = unknown> = (controller: TestContro...
  type ExportedHandler (line 369) | interface ExportedHandler<Env = unknown, QueueHandlerMessage = unknown, ...
  type StructuredSerializeOptions (line 379) | interface StructuredSerializeOptions {
  type Performance (line 400) | interface Performance {
  type AlarmInvocationInfo (line 406) | interface AlarmInvocationInfo {
  type Cloudflare (line 410) | interface Cloudflare {
  type DurableObject (line 413) | interface DurableObject {
  type DurableObjectStub (line 420) | type DurableObjectStub<T extends Rpc.DurableObjectBranded | undefined = ...
  type DurableObjectId (line 424) | interface DurableObjectId {
  type DurableObjectNamespace (line 429) | interface DurableObjectNamespace<T extends Rpc.DurableObjectBranded | un...
  type DurableObjectJurisdiction (line 436) | type DurableObjectJurisdiction = "eu" | "fedramp";
  type DurableObjectNamespaceNewUniqueIdOptions (line 437) | interface DurableObjectNamespaceNewUniqueIdOptions {
  type DurableObjectLocationHint (line 440) | type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeu...
  type DurableObjectNamespaceGetDurableObjectOptions (line 441) | interface DurableObjectNamespaceGetDurableObjectOptions {
  type DurableObjectState (line 444) | interface DurableObjectState {
  type DurableObjectTransaction (line 460) | interface DurableObjectTransaction {
  type DurableObjectStorage (line 473) | interface DurableObjectStorage {
  type DurableObjectListOptions (line 493) | interface DurableObjectListOptions {
  type DurableObjectGetOptions (line 503) | interface DurableObjectGetOptions {
  type DurableObjectGetAlarmOptions (line 507) | interface DurableObjectGetAlarmOptions {
  type DurableObjectPutOptions (line 510) | interface DurableObjectPutOptions {
  type DurableObjectSetAlarmOptions (line 515) | interface DurableObjectSetAlarmOptions {
  class WebSocketRequestResponsePair (line 519) | class WebSocketRequestResponsePair {
  type AnalyticsEngineDataset (line 524) | interface AnalyticsEngineDataset {
  type AnalyticsEngineDataPoint (line 527) | interface AnalyticsEngineDataPoint {
  class Event (line 537) | class Event {
  type EventInit (line 652) | interface EventInit {
  type EventListener (line 657) | type EventListener<EventType extends Event = Event> = (event: EventType)...
  type EventListenerObject (line 658) | interface EventListenerObject<EventType extends Event = Event> {
  type EventListenerOrEventListenerObject (line 661) | type EventListenerOrEventListenerObject<EventType extends Event = Event>...
  class EventTarget (line 667) | class EventTarget<EventMap extends Record<string, Event> = Record<string...
  type EventTargetEventListenerOptions (line 700) | interface EventTargetEventListenerOptions {
  type EventTargetAddEventListenerOptions (line 703) | interface EventTargetAddEventListenerOptions {
  type EventTargetHandlerObject (line 709) | interface EventTargetHandlerObject {
  class AbortController (line 717) | class AbortController {
  type Scheduler (line 759) | interface Scheduler {
  type SchedulerWaitOptions (line 762) | interface SchedulerWaitOptions {
  class CustomEvent (line 775) | class CustomEvent<T = any> extends Event {
  type CustomEventCustomEventInit (line 784) | interface CustomEventCustomEventInit {
  class Blob (line 795) | class Blob {
  type BlobOptions (line 812) | interface BlobOptions {
  class File (line 820) | class File extends Blob {
  type FileOptions (line 827) | interface FileOptions {
  type CacheQueryOptions (line 854) | interface CacheQueryOptions {
  type CryptoKeyPair (line 931) | interface CryptoKeyPair {
  type JsonWebKey (line 935) | interface JsonWebKey {
  type RsaOtherPrimesInfo (line 955) | interface RsaOtherPrimesInfo {
  type SubtleCryptoDeriveKeyAlgorithm (line 960) | interface SubtleCryptoDeriveKeyAlgorithm {
  type SubtleCryptoEncryptAlgorithm (line 968) | interface SubtleCryptoEncryptAlgorithm {
  type SubtleCryptoGenerateKeyAlgorithm (line 977) | interface SubtleCryptoGenerateKeyAlgorithm {
  type SubtleCryptoHashAlgorithm (line 985) | interface SubtleCryptoHashAlgorithm {
  type SubtleCryptoImportKeyAlgorithm (line 988) | interface SubtleCryptoImportKeyAlgorithm {
  type SubtleCryptoSignAlgorithm (line 995) | interface SubtleCryptoSignAlgorithm {
  type CryptoKeyKeyAlgorithm (line 1001) | interface CryptoKeyKeyAlgorithm {
  type CryptoKeyAesKeyAlgorithm (line 1004) | interface CryptoKeyAesKeyAlgorithm {
  type CryptoKeyHmacKeyAlgorithm (line 1008) | interface CryptoKeyHmacKeyAlgorithm {
  type CryptoKeyRsaKeyAlgorithm (line 1013) | interface CryptoKeyRsaKeyAlgorithm {
  type CryptoKeyEllipticKeyAlgorithm (line 1019) | interface CryptoKeyEllipticKeyAlgorithm {
  type CryptoKeyArbitraryKeyAlgorithm (line 1023) | interface CryptoKeyArbitraryKeyAlgorithm {
  class DigestStream (line 1029) | class DigestStream extends WritableStream<ArrayBuffer | ArrayBufferView> {
  class TextDecoder (line 1039) | class TextDecoder {
  class TextEncoder (line 1066) | class TextEncoder {
  type TextDecoderConstructorOptions (line 1082) | interface TextDecoderConstructorOptions {
  type TextDecoderDecodeOptions (line 1086) | interface TextDecoderDecodeOptions {
  type TextEncoderEncodeIntoResult (line 1089) | interface TextEncoderEncodeIntoResult {
  class ErrorEvent (line 1098) | class ErrorEvent extends Event {
  type ErrorEventErrorEventInit (line 1111) | interface ErrorEventErrorEventInit {
  class FormData (line 1123) | class FormData {
  type ContentOptions (line 1156) | interface ContentOptions {
  class HTMLRewriter (line 1159) | class HTMLRewriter {
  type HTMLRewriterElementContentHandlers (line 1165) | interface HTMLRewriterElementContentHandlers {
  type HTMLRewriterDocumentContentHandlers (line 1170) | interface HTMLRewriterDocumentContentHandlers {
  type Doctype (line 1176) | interface Doctype {
  type Element (line 1181) | interface Element {
  type EndTag (line 1200) | interface EndTag {
  type Comment (line 1206) | interface Comment {
  type Text (line 1214) | interface Text {
  type DocumentEnd (line 1223) | interface DocumentEnd {
  type HeadersInit (line 1238) | type HeadersInit = Headers | Iterable<Iterable<string>> | Record<string,...
  class Headers (line 1244) | class Headers {
  type BodyInit (line 1274) | type BodyInit = ReadableStream<Uint8Array> | string | ArrayBuffer | Arra...
  type Response (line 1310) | interface Response extends Body {
  type ResponseInit (line 1330) | interface ResponseInit {
  type RequestInfo (line 1338) | type RequestInfo<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetad...
  type Request (line 1353) | interface Request<CfHostMetadata = unknown, Cf = CfProperties<CfHostMeta...
  type RequestInit (line 1407) | interface RequestInit<Cf = CfProperties> {
  type Service (line 1426) | type Service<T extends Rpc.WorkerEntrypointBranded | undefined = undefin...
  type Fetcher (line 1427) | type Fetcher<T extends Rpc.EntrypointBranded | undefined = undefined, Re...
  type KVNamespaceListKey (line 1431) | interface KVNamespaceListKey<Metadata, Key extends string = string> {
  type KVNamespaceListResult (line 1436) | type KVNamespaceListResult<Metadata, Key extends string = string> = {
  type KVNamespace (line 1446) | interface KVNamespace<Key extends string = string> {
  type KVNamespaceListOptions (line 1479) | interface KVNamespaceListOptions {
  type KVNamespaceGetOptions (line 1484) | interface KVNamespaceGetOptions<Type> {
  type KVNamespacePutOptions (line 1488) | interface KVNamespacePutOptions {
  type KVNamespaceGetWithMetadataResult (line 1493) | interface KVNamespaceGetWithMetadataResult<Value, Metadata> {
  type QueueContentType (line 1498) | type QueueContentType = "text" | "bytes" | "json" | "v8";
  type Queue (line 1499) | interface Queue<Body = unknown> {
  type QueueSendOptions (line 1503) | interface QueueSendOptions {
  type QueueSendBatchOptions (line 1507) | interface QueueSendBatchOptions {
  type MessageSendRequest (line 1510) | interface MessageSendRequest<Body = unknown> {
  type QueueRetryOptions (line 1515) | interface QueueRetryOptions {
  type Message (line 1518) | interface Message<Body = unknown> {
  type QueueEvent (line 1526) | interface QueueEvent<Body = unknown> extends ExtendableEvent {
  type MessageBatch (line 1532) | interface MessageBatch<Body = unknown> {
  type R2Error (line 1538) | interface R2Error extends Error {
  type R2ListOptions (line 1545) | interface R2ListOptions {
  type R2MultipartUpload (line 1568) | interface R2MultipartUpload {
  type R2UploadedPart (line 1575) | interface R2UploadedPart {
  type R2ObjectBody (line 1594) | interface R2ObjectBody extends R2Object {
  type R2Range (line 1602) | type R2Range = {
  type R2Conditional (line 1611) | interface R2Conditional {
  type R2GetOptions (line 1618) | interface R2GetOptions {
  type R2PutOptions (line 1623) | interface R2PutOptions {
  type R2MultipartOptions (line 1635) | interface R2MultipartOptions {
  type R2Checksums (line 1641) | interface R2Checksums {
  type R2StringChecksums (line 1649) | interface R2StringChecksums {
  type R2HTTPMetadata (line 1656) | interface R2HTTPMetadata {
  type R2Objects (line 1664) | type R2Objects = {
  type R2UploadPartOptions (line 1673) | interface R2UploadPartOptions {
  type ScheduledController (line 1681) | interface ScheduledController {
  type QueuingStrategy (line 1686) | interface QueuingStrategy<T = any> {
  type UnderlyingSink (line 1690) | interface UnderlyingSink<W = any> {
  type UnderlyingByteSource (line 1697) | interface UnderlyingByteSource {
  type UnderlyingSource (line 1704) | interface UnderlyingSource<R = any> {
  type Transformer (line 1711) | interface Transformer<I = any, O = any> {
  type StreamPipeOptions (line 1720) | interface StreamPipeOptions {
  type ReadableStreamReadResult (line 1743) | type ReadableStreamReadResult<R = any> = {
  type ReadableStream (line 1755) | interface ReadableStream<R = any> {
  class ReadableStreamDefaultReader (line 1787) | class ReadableStreamDefaultReader<R = any> {
  class ReadableStreamBYOBReader (line 1797) | class ReadableStreamBYOBReader {
  type ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions (line 1807) | interface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions {
  type ReadableStreamGetReaderOptions (line 1810) | interface ReadableStreamGetReaderOptions {
  type ReadableWritablePair (line 1874) | interface ReadableWritablePair<R = any, W = any> {
  class WritableStream (line 1888) | class WritableStream<W = any> {
  class WritableStreamDefaultWriter (line 1904) | class WritableStreamDefaultWriter<W = any> {
  class TransformStream (line 1922) | class TransformStream<I = any, O = any> {
  class FixedLengthStream (line 1929) | class FixedLengthStream extends IdentityTransformStream {
  class IdentityTransformStream (line 1932) | class IdentityTransformStream extends TransformStream<ArrayBuffer | Arra...
  type IdentityTransformStreamQueuingStrategy (line 1935) | interface IdentityTransformStreamQueuingStrategy {
  type ReadableStreamValuesOptions (line 1938) | interface ReadableStreamValuesOptions {
  class CompressionStream (line 1942) | class CompressionStream extends TransformStream<ArrayBuffer | ArrayBuffe...
  class DecompressionStream (line 1946) | class DecompressionStream extends TransformStream<ArrayBuffer | ArrayBuf...
  class TextEncoderStream (line 1950) | class TextEncoderStream extends TransformStream<string, Uint8Array> {
  class TextDecoderStream (line 1955) | class TextDecoderStream extends TransformStream<ArrayBuffer | ArrayBuffe...
  type TextDecoderStreamTextDecoderStreamInit (line 1961) | interface TextDecoderStreamTextDecoderStreamInit {
  class ByteLengthQueuingStrategy (line 1970) | class ByteLengthQueuingStrategy implements QueuingStrategy<ArrayBufferVi...
  class CountQueuingStrategy (line 1982) | class CountQueuingStrategy implements QueuingStrategy {
  type QueuingStrategyInit (line 1989) | interface QueuingStrategyInit {
  type ScriptVersion (line 1997) | interface ScriptVersion {
  type TraceItem (line 2006) | interface TraceItem {
  type TraceItemAlarmEventInfo (line 2023) | interface TraceItemAlarmEventInfo {
  type TraceItemCustomEventInfo (line 2026) | interface TraceItemCustomEventInfo {
  type TraceItemScheduledEventInfo (line 2028) | interface TraceItemScheduledEventInfo {
  type TraceItemQueueEventInfo (line 2032) | interface TraceItemQueueEventInfo {
  type TraceItemEmailEventInfo (line 2036) | interface TraceItemEmailEventInfo {
  type TraceItemTailEventInfo (line 2041) | interface TraceItemTailEventInfo {
  type TraceItemTailEventInfoTailItem (line 2044) | interface TraceItemTailEventInfoTailItem {
  type TraceItemFetchEventInfo (line 2047) | interface TraceItemFetchEventInfo {
  type TraceItemFetchEventInfoRequest (line 2051) | interface TraceItemFetchEventInfoRequest {
  type TraceItemFetchEventInfoResponse (line 2058) | interface TraceItemFetchEventInfoResponse {
  type TraceItemJsRpcEventInfo (line 2061) | interface TraceItemJsRpcEventInfo {
  type TraceItemHibernatableWebSocketEventInfo (line 2064) | interface TraceItemHibernatableWebSocketEventInfo {
  type TraceItemHibernatableWebSocketEventInfoMessage (line 2067) | interface TraceItemHibernatableWebSocketEventInfoMessage {
  type TraceItemHibernatableWebSocketEventInfoClose (line 2070) | interface TraceItemHibernatableWebSocketEventInfoClose {
  type TraceItemHibernatableWebSocketEventInfoError (line 2075) | interface TraceItemHibernatableWebSocketEventInfoError {
  type TraceLog (line 2078) | interface TraceLog {
  type TraceException (line 2083) | interface TraceException {
  type TraceDiagnosticChannelEvent (line 2089) | interface TraceDiagnosticChannelEvent {
  type TraceMetrics (line 2094) | interface TraceMetrics {
  type UnsafeTraceMetrics (line 2098) | interface UnsafeTraceMetrics {
  class URL (line 2106) | class URL {
  class URLSearchParams (line 2166) | class URLSearchParams {
  class URLPattern (line 2225) | class URLPattern {
  type URLPatternInit (line 2238) | interface URLPatternInit {
  type URLPatternComponentResult (line 2249) | interface URLPatternComponentResult {
  type URLPatternResult (line 2253) | interface URLPatternResult {
  type URLPatternOptions (line 2264) | interface URLPatternOptions {
  class CloseEvent (line 2272) | class CloseEvent extends Event {
  type CloseEventInit (line 2293) | interface CloseEventInit {
  class MessageEvent (line 2303) | class MessageEvent extends Event {
  type MessageEventInit (line 2312) | interface MessageEventInit {
  type WebSocketEventMap (line 2315) | type WebSocketEventMap = {
  type WebSocket (line 2343) | interface WebSocket extends EventTarget<WebSocketEventMap> {
  type SqlStorage (line 2390) | interface SqlStorage {
  type SqlStorageValue (line 2398) | type SqlStorageValue = ArrayBuffer | string | number | null;
  type Socket (line 2415) | interface Socket {
  type SocketOptions (line 2425) | interface SocketOptions {
  type SocketAddress (line 2430) | interface SocketAddress {
  type TlsOptions (line 2434) | interface TlsOptions {
  type SocketInfo (line 2437) | interface SocketInfo {
  class EventSource (line 2442) | class EventSource extends EventTarget {
  type EventSourceEventSourceInit (line 2485) | interface EventSourceEventSourceInit {
  type Container (line 2489) | interface Container {
  type ContainerStartupOptions (line 2497) | interface ContainerStartupOptions {
  type AiImageClassificationInput (line 2502) | type AiImageClassificationInput = {
  type AiImageClassificationOutput (line 2505) | type AiImageClassificationOutput = {
  type AiImageToTextInput (line 2513) | type AiImageToTextInput = {
  type AiImageToTextOutput (line 2527) | type AiImageToTextOutput = {
  type AiImageTextToTextInput (line 2534) | type AiImageTextToTextInput = {
  type AiImageTextToTextOutput (line 2549) | type AiImageTextToTextOutput = {
  type AiObjectDetectionInput (line 2556) | type AiObjectDetectionInput = {
  type AiObjectDetectionOutput (line 2559) | type AiObjectDetectionOutput = {
  type AiSentenceSimilarityInput (line 2567) | type AiSentenceSimilarityInput = {
  type AiSentenceSimilarityOutput (line 2571) | type AiSentenceSimilarityOutput = number[];
  type AiAutomaticSpeechRecognitionInput (line 2576) | type AiAutomaticSpeechRecognitionInput = {
  type AiAutomaticSpeechRecognitionOutput (line 2579) | type AiAutomaticSpeechRecognitionOutput = {
  type AiSummarizationInput (line 2592) | type AiSummarizationInput = {
  type AiSummarizationOutput (line 2596) | type AiSummarizationOutput = {
  type AiTextClassificationInput (line 2603) | type AiTextClassificationInput = {
  type AiTextClassificationOutput (line 2606) | type AiTextClassificationOutput = {
  type AiTextEmbeddingsInput (line 2614) | type AiTextEmbeddingsInput = {
  type AiTextEmbeddingsOutput (line 2617) | type AiTextEmbeddingsOutput = {
  type RoleScopedChatInput (line 2625) | type RoleScopedChatInput = {
  type AiTextGenerationToolLegacyInput (line 2630) | type AiTextGenerationToolLegacyInput = {
  type AiTextGenerationToolInput (line 2644) | type AiTextGenerationToolInput = {
  type AiTextGenerationFunctionsInput (line 2661) | type AiTextGenerationFunctionsInput = {
  type AiTextGenerationResponseFormat (line 2665) | type AiTextGenerationResponseFormat = {
  type AiTextGenerationInput (line 2669) | type AiTextGenerationInput = {
  type AiTextGenerationOutput (line 2686) | type AiTextGenerationOutput = {
  type AiTextToSpeechInput (line 2697) | type AiTextToSpeechInput = {
  type AiTextToSpeechOutput (line 2701) | type AiTextToSpeechOutput = Uint8Array | {
  type AiTextToImageInput (line 2708) | type AiTextToImageInput = {
  type AiTextToImageOutput (line 2721) | type AiTextToImageOutput = ReadableStream<Uint8Array>;
  type AiTranslationInput (line 2726) | type AiTranslationInput = {
  type AiTranslationOutput (line 2731) | type AiTranslationOutput = {
  type Ai_Cf_Openai_Whisper_Input (line 2738) | type Ai_Cf_Openai_Whisper_Input = string | {
  type Ai_Cf_Openai_Whisper_Output (line 2744) | interface Ai_Cf_Openai_Whisper_Output {
  type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input (line 2767) | type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input = string | {
  type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output (line 2806) | interface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output {
  type Ai_Cf_Openai_Whisper_Tiny_En_Input (line 2813) | type Ai_Cf_Openai_Whisper_Tiny_En_Input = string | {
  type Ai_Cf_Openai_Whisper_Tiny_En_Output (line 2819) | interface Ai_Cf_Openai_Whisper_Tiny_En_Output {
  type Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input (line 2842) | interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input {
  type Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output (line 2868) | interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output {
  type Ai_Cf_Baai_Bge_M3_Input (line 2948) | type Ai_Cf_Baai_Bge_M3_Input = BGEM3InputQueryAndContexts | BGEM3InputEm...
  type BGEM3InputQueryAndContexts (line 2949) | interface BGEM3InputQueryAndContexts {
  type BGEM3InputEmbedding (line 2968) | interface BGEM3InputEmbedding {
  type Ai_Cf_Baai_Bge_M3_Output (line 2975) | type Ai_Cf_Baai_Bge_M3_Output = BGEM3OuputQuery | BGEM3OutputEmbeddingFo...
  type BGEM3OuputQuery (line 2976) | interface BGEM3OuputQuery {
  type BGEM3OutputEmbeddingForContexts (line 2988) | interface BGEM3OutputEmbeddingForContexts {
  type BGEM3OuputEmbedding (line 2996) | interface BGEM3OuputEmbedding {
  type Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input (line 3011) | interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input {
  type Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output (line 3021) | interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output {
  type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input (line 3031) | type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input = Prompt | Messages;
  type Prompt (line 3032) | interface Prompt {
  type Messages (line 3083) | interface Messages {
  type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output (line 3226) | type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = {
  type Ai_Cf_Meta_Llama_Guard_3_8B_Input (line 3249) | interface Ai_Cf_Meta_Llama_Guard_3_8B_Input {
  type Ai_Cf_Meta_Llama_Guard_3_8B_Output (line 3281) | interface Ai_Cf_Meta_Llama_Guard_3_8B_Output {
  type Ai_Cf_Baai_Bge_Reranker_Base_Input (line 3314) | interface Ai_Cf_Baai_Bge_Reranker_Base_Input {
  type Ai_Cf_Baai_Bge_Reranker_Base_Output (line 3332) | interface Ai_Cf_Baai_Bge_Reranker_Base_Output {
  type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input (line 3348) | type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input = Ai_Cf_Meta_Llama_...
  type Ai_Cf_Meta_Llama_4_Prompt (line 3349) | interface Ai_Cf_Meta_Llama_4_Prompt {
  type Ai_Cf_Meta_Llama_4_Messages (line 3399) | interface Ai_Cf_Meta_Llama_4_Messages {
  type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output (line 3574) | type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = {
  type AiModels (line 3614) | interface AiModels {
  type AiOptions (line 3682) | type AiOptions = {
  type ConversionResponse (line 3688) | type ConversionResponse = {
  type AiModelsSearchParams (line 3695) | type AiModelsSearchParams = {
  type AiModelsSearchObject (line 3704) | type AiModelsSearchObject = {
  type InferenceUpstreamError (line 3720) | interface InferenceUpstreamError extends Error {
  type AiInternalError (line 3722) | interface AiInternalError extends Error {
  type AiModelListType (line 3724) | type AiModelListType = Record<string, any>;
  type GatewayRetries (line 3748) | type GatewayRetries = {
  type GatewayOptions (line 3753) | type GatewayOptions = {
  type AiGatewayPatchLog (line 3764) | type AiGatewayPatchLog = {
  type AiGatewayLog (line 3769) | type AiGatewayLog = {
  type AIGatewayProviders (line 3796) | type AIGatewayProviders = 'workers-ai' | 'anthropic' | 'aws-bedrock' | '...
  type AIGatewayHeaders (line 3797) | type AIGatewayHeaders = {
  type AIGatewayUniversalRequest (line 3818) | type AIGatewayUniversalRequest = {
  type AiGatewayInternalError (line 3824) | interface AiGatewayInternalError extends Error {
  type AiGatewayLogNotFound (line 3826) | interface AiGatewayLogNotFound extends Error {
  type AutoRAGInternalError (line 3837) | interface AutoRAGInternalError extends Error {
  type AutoRAGNotFoundError (line 3839) | interface AutoRAGNotFoundError extends Error {
  type AutoRAGUnauthorizedError (line 3841) | interface AutoRAGUnauthorizedError extends Error {
  type ComparisonFilter (line 3843) | type ComparisonFilter = {
  type CompoundFilter (line 3848) | type CompoundFilter = {
  type AutoRagSearchRequest (line 3852) | type AutoRagSearchRequest = {
  type AutoRagAiSearchRequest (line 3862) | type AutoRagAiSearchRequest = AutoRagSearchRequest & {
  type AutoRagAiSearchRequestStreaming (line 3865) | type AutoRagAiSearchRequestStreaming = Omit<AutoRagAiSearchRequest, 'str...
  type AutoRagSearchResponse (line 3868) | type AutoRagSearchResponse = {
  type AutoRagAiSearchResponse (line 3884) | type AutoRagAiSearchResponse = AutoRagSearchResponse & {
  type BasicImageTransformations (line 3893) | interface BasicImageTransformations {
  type BasicImageTransformationsGravityCoordinates (line 3954) | interface BasicImageTransformationsGravityCoordinates {
  type RequestInitCfProperties (line 3968) | interface RequestInitCfProperties extends Record<string, unknown> {
  type RequestInitCfPropertiesImageDraw (line 4019) | interface RequestInitCfPropertiesImageDraw extends BasicImageTransformat...
  type RequestInitCfPropertiesImage (line 4056) | interface RequestInitCfPropertiesImage extends BasicImageTransformations {
  type RequestInitCfPropertiesImageMinify (line 4207) | interface RequestInitCfPropertiesImageMinify {
  type RequestInitCfPropertiesR2 (line 4212) | interface RequestInitCfPropertiesR2 {
  type IncomingRequestCfProperties (line 4221) | type IncomingRequestCfProperties<HostMetadata = unknown> = IncomingReque...
  type IncomingRequestCfPropertiesBase (line 4222) | interface IncomingRequestCfPropertiesBase extends Record<string, unknown> {
  type IncomingRequestCfPropertiesBotManagementBase (line 4300) | interface IncomingRequestCfPropertiesBotManagementBase {
  type IncomingRequestCfPropertiesBotManagement (line 4327) | interface IncomingRequestCfPropertiesBotManagement {
  type IncomingRequestCfPropertiesBotManagementEnterprise (line 4339) | interface IncomingRequestCfPropertiesBotManagementEnterprise extends Inc...
  type IncomingRequestCfPropertiesCloudflareForSaaSEnterprise (line 4351) | interface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise<HostMet...
  type IncomingRequestCfPropertiesCloudflareAccessOrApiShield (line 4360) | interface IncomingRequestCfPropertiesCloudflareAccessOrApiShield {
  type IncomingRequestCfPropertiesExportedAuthenticatorMetadata (line 4380) | interface IncomingRequestCfPropertiesExportedAuthenticatorMetadata {
  type IncomingRequestCfPropertiesGeographicInformation (line 4409) | interface IncomingRequestCfPropertiesGeographicInformation {
  type IncomingRequestCfPropertiesTLSClientAuth (line 4486) | interface IncomingRequestCfPropertiesTLSClientAuth {
  type IncomingRequestCfPropertiesTLSClientAuthPlaceholder (line 4579) | interface IncomingRequestCfPropertiesTLSClientAuthPlaceholder {
  type CertVerificationStatus (line 4599) | type CertVerificationStatus =
  type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus (line 4617) | type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus = 0 /** Unkno...
  type Iso3166Alpha2Code (line 4619) | type Iso3166Alpha2Code = "AD" | "AE" | "AF" | "AG" | "AI" | "AL" | "AM" ...
  type ContinentCode (line 4621) | type ContinentCode = "AF" | "AN" | "AS" | "EU" | "NA" | "OC" | "SA";
  type CfProperties (line 4622) | type CfProperties<HostMetadata = unknown> = IncomingRequestCfProperties<...
  type D1Meta (line 4623) | interface D1Meta {
  type D1Response (line 4646) | interface D1Response {
  type D1Result (line 4651) | type D1Result<T = unknown> = D1Response & {
  type D1ExecResult (line 4654) | interface D1ExecResult {
  type D1SessionConstraint (line 4658) | type D1SessionConstraint =
  type D1SessionBookmark (line 4667) | type D1SessionBookmark = string;
  type Disposable (line 4716) | interface Disposable {
  type EmailMessage (line 4721) | interface EmailMessage {
  type ForwardableEmailMessage (line 4734) | interface ForwardableEmailMessage extends EmailMessage {
  type SendEmail (line 4770) | interface SendEmail {
  type EmailExportedHandler (line 4776) | type EmailExportedHandler<Env = unknown> = (message: ForwardableEmailMes...
  type Hyperdrive (line 4784) | interface Hyperdrive {
  type ImageInfoResponse (line 4834) | type ImageInfoResponse = {
  type ImageTransform (line 4842) | type ImageTransform = {
  type ImageDrawOptions (line 4883) | type ImageDrawOptions = {
  type ImageOutputOptions (line 4891) | type ImageOutputOptions = {
  type ImagesBinding (line 4896) | interface ImagesBinding {
  type ImageTransformer (line 4910) | interface ImageTransformer {
  type ImageTransformationResult (line 4931) | interface ImageTransformationResult {
  type ImagesError (line 4945) | interface ImagesError extends Error {
  type Params (line 4950) | type Params<P extends string = any> = Record<P, string | string[]>;
  type EventContext (line 4951) | type EventContext<Env, P extends string, Data> = {
  type PagesFunction (line 4965) | type PagesFunction<Env = unknown, Params extends string = any, Data exte...
  type EventPluginContext (line 4966) | type EventPluginContext<Env, P extends string, Data, PluginArgs> = {
  type PagesPluginFunction (line 4981) | type PagesPluginFunction<Env = unknown, Params extends string = any, Dat...
  type PipelineRecord (line 5002) | type PipelineRecord = Record<string, unknown>;
  type PipelineBatchMetadata (line 5003) | type PipelineBatchMetadata = {
  type Pipeline (line 5007) | interface Pipeline<T extends PipelineRecord = PipelineRecord> {
  type PubSubMessage (line 5020) | interface PubSubMessage {
  type JsonWebKeyWithKid (line 5046) | interface JsonWebKeyWithKid extends JsonWebKey {
  type RateLimitOptions (line 5050) | interface RateLimitOptions {
  type RateLimitOutcome (line 5053) | interface RateLimitOutcome {
  type RateLimit (line 5056) | interface RateLimit {
  type RpcTargetBranded (line 5077) | interface RpcTargetBranded {
  type WorkerEntrypointBranded (line 5080) | interface WorkerEntrypointBranded {
  type DurableObjectBranded (line 5083) | interface DurableObjectBranded {
  type WorkflowEntrypointBranded (line 5086) | interface WorkflowEntrypointBranded {
  type EntrypointBranded (line 5089) | type EntrypointBranded = WorkerEntrypointBranded | DurableObjectBranded ...
  type Stubable (line 5091) | type Stubable = RpcTargetBranded | ((...args: any[]) => any);
  type Serializable (line 5096) | type Serializable<T> =
  type StubBase (line 5109) | interface StubBase<T extends Stubable> extends Disposable {
  type Stub (line 5113) | type Stub<T extends Stubable> = Provider<T> & StubBase<T>;
  type BaseType (line 5115) | type BaseType = void | undefined | null | boolean | number | bigint | st...
  type Stubify (line 5118) | type Stubify<T> = T extends Stubable ? Stub<T> : T extends Map<infer K, ...
  type Unstubify (line 5127) | type Unstubify<T> = T extends StubBase<infer V> ? V : T extends Map<infe...
  type UnstubifyAll (line 5132) | type UnstubifyAll<A extends any[]> = {
  type MaybeProvider (line 5137) | type MaybeProvider<T> = T extends object ? Provider<T> : unknown;
  type MaybeDisposable (line 5138) | type MaybeDisposable<T> = T extends object ? Disposable : unknown;
  type Result (line 5147) | type Result<R> = R extends Stubable ? Promise<Stub<R>> & Provider<R> : R...
  type MethodOrProperty (line 5153) | type MethodOrProperty<V> = V extends (...args: infer P) => infer R ? (.....
  type MaybeCallableProvider (line 5156) | type MaybeCallableProvider<T> = T extends (...args: any[]) => any ? Meth...
  type Provider (line 5160) | type Provider<T extends object, Reserved extends string = never> = Maybe...
  type Env (line 5165) | interface Env {
  type RpcStub (line 5169) | type RpcStub<T extends Rpc.Stubable> = Rpc.Stub<T>;
  type WorkflowDurationLabel (line 5200) | type WorkflowDurationLabel = 'second' | 'minute' | 'hour' | 'day' | 'wee...
  type WorkflowSleepDuration (line 5201) | type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ...
  type WorkflowDelayDuration (line 5202) | type WorkflowDelayDuration = WorkflowSleepDuration;
  type WorkflowTimeoutDuration (line 5203) | type WorkflowTimeoutDuration = WorkflowSleepDuration;
  type WorkflowBackoff (line 5204) | type WorkflowBackoff = 'constant' | 'linear' | 'exponential';
  type WorkflowStepConfig (line 5205) | type WorkflowStepConfig = {
  type WorkflowEvent (line 5213) | type WorkflowEvent<T> = {
  type WorkflowStepEvent (line 5218) | type WorkflowStepEvent<T> = {
  type SecretsStoreSecret (line 5242) | interface SecretsStoreSecret {
  type Header (line 5254) | interface Header {
  type FetchEventInfo (line 5258) | interface FetchEventInfo {
  type JsRpcEventInfo (line 5265) | interface JsRpcEventInfo {
  type ScheduledEventInfo (line 5269) | interface ScheduledEventInfo {
  type AlarmEventInfo (line 5274) | interface AlarmEventInfo {
  type QueueEventInfo (line 5278) | interface QueueEventInfo {
  type EmailEventInfo (line 5283) | interface EmailEventInfo {
  type TraceEventInfo (line 5289) | interface TraceEventInfo {
  type HibernatableWebSocketEventInfoMessage (line 5293) | interface HibernatableWebSocketEventInfoMessage {
  type HibernatableWebSocketEventInfoError (line 5296) | interface HibernatableWebSocketEventInfoError {
  type HibernatableWebSocketEventInfoClose (line 5299) | interface HibernatableWebSocketEventInfoClose {
  type HibernatableWebSocketEventInfo (line 5304) | interface HibernatableWebSocketEventInfo {
  type Resume (line 5308) | interface Resume {
  type CustomEventInfo (line 5312) | interface CustomEventInfo {
  type FetchResponseInfo (line 5315) | interface FetchResponseInfo {
  type EventOutcome (line 5319) | type EventOutcome = "ok" | "canceled" | "exception" | "unknown" | "killS...
  type ScriptVersion (line 5320) | interface ScriptVersion {
  type Trigger (line 5325) | interface Trigger {
  type Onset (line 5330) | interface Onset {
  type Outcome (line 5340) | interface Outcome {
  type Hibernate (line 5346) | interface Hibernate {
  type SpanOpen (line 5349) | interface SpanOpen {
  type SpanClose (line 5354) | interface SpanClose {
  type DiagnosticChannelEvent (line 5358) | interface DiagnosticChannelEvent {
  type Exception (line 5363) | interface Exception {
  type Log (line 5369) | interface Log {
  type Return (line 5374) | interface Return {
  type Link (line 5378) | interface Link {
  type Attribute (line 5385) | interface Attribute {
  type Mark (line 5390) | type Mark = DiagnosticChannelEvent | Exception | Log | Return | Link | A...
  type TailEvent (line 5391) | interface TailEvent {
  type TailEventHandler (line 5399) | type TailEventHandler = (event: TailEvent) => void | Promise<void>;
  type TailEventHandlerName (line 5400) | type TailEventHandlerName = "onset" | "outcome" | "hibernate" | "spanOpe...
  type TailEventHandlerObject (line 5401) | type TailEventHandlerObject = Record<TailEventHandlerName, TailEventHand...
  type TailEventHandlerType (line 5402) | type TailEventHandlerType = TailEventHandler | TailEventHandlerObject;
  type VectorizeVectorMetadataValue (line 5410) | type VectorizeVectorMetadataValue = string | number | boolean | string[];
  type VectorizeVectorMetadata (line 5414) | type VectorizeVectorMetadata = VectorizeVectorMetadataValue | Record<str...
  type VectorFloatArray (line 5415) | type VectorFloatArray = Float32Array | Float64Array;
  type VectorizeError (line 5416) | interface VectorizeError {
  type VectorizeVectorMetadataFilterOp (line 5425) | type VectorizeVectorMetadataFilterOp = "$eq" | "$ne";
  type VectorizeVectorMetadataFilter (line 5429) | type VectorizeVectorMetadataFilter = {
  type VectorizeDistanceMetric (line 5438) | type VectorizeDistanceMetric = "euclidean" | "cosine" | "dot-product";
  type VectorizeMetadataRetrievalLevel (line 5448) | type VectorizeMetadataRetrievalLevel = "all" | "indexed" | "none";
  type VectorizeQueryOptions (line 5449) | interface VectorizeQueryOptions {
  type VectorizeIndexConfig (line 5459) | type VectorizeIndexConfig = {
  type VectorizeIndexDetails (line 5471) | interface VectorizeIndexDetails {
  type VectorizeIndexInfo (line 5486) | interface VectorizeIndexInfo {
  type VectorizeVector (line 5499) | interface VectorizeVector {
  type VectorizeMatch (line 5512) | type VectorizeMatch = Pick<Partial<VectorizeVector>, "values"> & Omit<Ve...
  type VectorizeMatches (line 5519) | interface VectorizeMatches {
  type VectorizeVectorMutation (line 5530) | interface VectorizeVectorMutation {
  type VectorizeAsyncMutation (line 5540) | interface VectorizeAsyncMutation {
  type WorkerVersionMetadata (line 5642) | type WorkerVersionMetadata = {
  type DynamicDispatchLimits (line 5650) | interface DynamicDispatchLimits {
  type DynamicDispatchOptions (line 5660) | interface DynamicDispatchOptions {
  type DispatchNamespace (line 5672) | interface DispatchNamespace {
  class NonRetryableError (line 5689) | class NonRetryableError extends Error {
  type WorkflowInstanceCreateOptions (line 5714) | interface WorkflowInstanceCreateOptions<PARAMS = unknown> {
  type InstanceStatus (line 5724) | type InstanceStatus = {
  type WorkflowError (line 5733) | interface WorkflowError {
Condensed preview — 127 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,169K chars).
[
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 5357,
    "preview": "# Contributing to GitMCP\n\nFirst of all, thank you for your interest in contributing to GitMCP! We appreciate the time an"
  },
  {
    "path": ".github/workflows/e2e-tests.yml",
    "chars": 1481,
    "preview": "name: E2E Tests\n\non:\n  pull_request:\n    paths:\n      - 'src/**'\n      - 'tests/e2e/**'\n      - 'package.json'\n      - '"
  },
  {
    "path": ".github/workflows/run-tests.yml",
    "chars": 887,
    "preview": "name: Run Tests\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  test:\n    runs-on: u"
  },
  {
    "path": ".gitignore",
    "chars": 2241,
    "preview": "node_modules\nbuild\n\n.nx\n.idea\n.vscode\n.zed\n# Logs\n\nlogs\n_.log\nnpm-debug.log_\nyarn-debug.log*\nyarn-error.log*\nlerna-debug"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 17,
    "preview": "pnpm lint-staged\n"
  },
  {
    "path": ".prettierrc",
    "chars": 200,
    "preview": "{\n  \"semi\": true,\n  \"trailingComma\": \"all\",\n  \"singleQuote\": false,\n  \"printWidth\": 80,\n  \"tabWidth\": 2,\n  \"useTabs\": fa"
  },
  {
    "path": ".react-router/types/+register.ts",
    "chars": 186,
    "preview": "import \"react-router\";\n\ndeclare module \"react-router\" {\n  interface Register {\n    params: Params;\n  }\n}\n\ntype Params = "
  },
  {
    "path": ".react-router/types/+virtual.d.ts",
    "chars": 684,
    "preview": "declare module \"virtual:react-router/server-build\" {\n  import { ServerBuild } from \"react-router\";\n  export const assets"
  },
  {
    "path": ".react-router/types/app/+types/root.ts",
    "chars": 1418,
    "preview": "// React Router generated types for route:\n// root.tsx\n\nimport type * as T from \"react-router/route-module\"\n\n\n\ntype Modu"
  },
  {
    "path": ".react-router/types/app/routes/+types/$.ts",
    "chars": 1505,
    "preview": "// React Router generated types for route:\n// routes/$.tsx\n\nimport type * as T from \"react-router/route-module\"\n\nimport "
  },
  {
    "path": ".react-router/types/app/routes/+types/_index.ts",
    "chars": 1522,
    "preview": "// React Router generated types for route:\n// routes/_index.tsx\n\nimport type * as T from \"react-router/route-module\"\n\nim"
  },
  {
    "path": ".react-router/types/app/routes/+types/api.chat.ts",
    "chars": 1527,
    "preview": "// React Router generated types for route:\n// routes/api.chat.ts\n\nimport type * as T from \"react-router/route-module\"\n\ni"
  },
  {
    "path": "LICENSE",
    "chars": 11373,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 15819,
    "preview": "# GitMCP\n\n<p align=\"center\">\n  <img width=\"884\" alt=\"image\" src=\"https://github.com/user-attachments/assets/2bf3e3df-556"
  },
  {
    "path": "SECURITY.md",
    "chars": 360,
    "preview": "# Security Policy\n\nGitMCP is committed to maintaining the security and privacy of its users. We welcome vulnerability di"
  },
  {
    "path": "app/app.css",
    "chars": 300,
    "preview": "@import \"tailwindcss\";\n\n@theme {\n  --font-sans: \"Inter\", ui-sans-serif, system-ui, sans-serif,\n    \"Apple Color Emoji\", "
  },
  {
    "path": "app/chat/ai/providers.server.ts",
    "chars": 1769,
    "preview": "import { createOpenAI } from \"@ai-sdk/openai\";\nimport { createGroq } from \"@ai-sdk/groq\";\nimport { createAnthropic } fro"
  },
  {
    "path": "app/chat/ai/providers.shared.ts",
    "chars": 1630,
    "preview": "export interface ModelInfo {\n  provider: string;\n  name: string;\n  description: string;\n  apiVersion: string;\n  capabili"
  },
  {
    "path": "app/chat/components/api-key-manager.tsx",
    "chars": 4276,
    "preview": "import { useState, useCallback } from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n"
  },
  {
    "path": "app/chat/components/api-keys-provider.tsx",
    "chars": 831,
    "preview": "import { createContext, useContext } from \"react\";\nimport type { StorageKey } from \"../ai/providers.shared\";\nimport { ST"
  },
  {
    "path": "app/chat/components/chat-sidebar.tsx",
    "chars": 7154,
    "preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport {\n  ServerIcon,\n  Settings,\n  Sparkles,\n  ChevronsUpDown,\n  Gith"
  },
  {
    "path": "app/chat/components/chat.tsx",
    "chars": 3025,
    "preview": "\"use client\";\n\nimport { defaultModel, type modelID } from \"~/chat/ai/providers.shared\";\nimport { useChat } from \"@ai-sdk"
  },
  {
    "path": "app/chat/components/copy-button.tsx",
    "chars": 911,
    "preview": "import { CheckIcon, CopyIcon } from \"lucide-react\";\nimport { cn } from \"~/chat/lib/utils\";\nimport { useCopy } from \"~/ch"
  },
  {
    "path": "app/chat/components/icons.tsx",
    "chars": 4796,
    "preview": "import type { SVGProps } from \"react\";\n\nexport const VercelIcon = ({ size = 17 }) => {\n  return (\n    <svg\n      height="
  },
  {
    "path": "app/chat/components/input.tsx",
    "chars": 2004,
    "preview": "import { ArrowUp } from \"lucide-react\";\nimport { Input as ShadcnInput } from \"./ui/input\";\n\ninterface InputProps {\n  inp"
  },
  {
    "path": "app/chat/components/markdown.tsx",
    "chars": 6084,
    "preview": "/* eslint-disable @typescript-eslint/no-unused-vars */\nimport React, { memo } from \"react\";\nimport ReactMarkdown, { type"
  },
  {
    "path": "app/chat/components/mcp-server-manager.tsx",
    "chars": 13242,
    "preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogHeader"
  },
  {
    "path": "app/chat/components/message.tsx",
    "chars": 8843,
    "preview": "\"use client\";\n\nimport type { Message as TMessage } from \"ai\";\nimport { AnimatePresence, motion } from \"motion/react\";\nim"
  },
  {
    "path": "app/chat/components/messages.tsx",
    "chars": 857,
    "preview": "import type { Message as TMessage } from \"ai\";\nimport { Message } from \"./message\";\nimport { useScrollToBottom } from \"~"
  },
  {
    "path": "app/chat/components/model-picker.tsx",
    "chars": 10830,
    "preview": "\"use client\";\nimport {\n  MODELS,\n  modelDetails,\n  type modelID,\n  defaultModel,\n} from \"~/chat/ai/providers.shared\";\nim"
  },
  {
    "path": "app/chat/components/project-overview.tsx",
    "chars": 535,
    "preview": "import { useMCP } from \"~/chat/lib/context/mcp-context\";\nexport const ProjectOverview = () => {\n  const { owner, repo, s"
  },
  {
    "path": "app/chat/components/suggested-prompts.tsx",
    "chars": 1717,
    "preview": "\"use client\";\n\nimport { motion } from \"motion/react\";\nimport { Button } from \"./ui/button\";\nimport { memo } from \"react\""
  },
  {
    "path": "app/chat/components/textarea.tsx",
    "chars": 2077,
    "preview": "import type { modelID } from \"~/chat/ai/providers.shared\";\nimport { Textarea as ShadcnTextarea } from \"~/chat/components"
  },
  {
    "path": "app/chat/components/theme-provider.tsx",
    "chars": 289,
    "preview": "\"use client\";\n\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\";\nimport type { ThemeProviderProps } fro"
  },
  {
    "path": "app/chat/components/theme-toggle.tsx",
    "chars": 1903,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  CircleDashed,\n  Flame,\n  Sun,\n  Moon,\n  WavesIcon,\n  SunsetIco"
  },
  {
    "path": "app/chat/components/tool-invocation.tsx",
    "chars": 5135,
    "preview": "\"use client\";\n\nimport { useState } from \"react\";\nimport { motion, AnimatePresence } from \"motion/react\";\nimport {\n  Chev"
  },
  {
    "path": "app/chat/components/ui/accordion.tsx",
    "chars": 1953,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as AccordionPrimitive from \"@radix-ui/react-accordion\";\nimport {"
  },
  {
    "path": "app/chat/components/ui/avatar.tsx",
    "chars": 1112,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\";\n\nimport { cn }"
  },
  {
    "path": "app/chat/components/ui/badge.tsx",
    "chars": 1748,
    "preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"cla"
  },
  {
    "path": "app/chat/components/ui/button.tsx",
    "chars": 2327,
    "preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"cla"
  },
  {
    "path": "app/chat/components/ui/dialog.tsx",
    "chars": 3837,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon"
  },
  {
    "path": "app/chat/components/ui/dropdown-menu.tsx",
    "chars": 8379,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\ni"
  },
  {
    "path": "app/chat/components/ui/input.tsx",
    "chars": 1034,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"~/chat/lib/utils\";\n\nfunction Input({ className, type, ...props }: R"
  },
  {
    "path": "app/chat/components/ui/label.tsx",
    "chars": 623,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\n\nimport { cn } f"
  },
  {
    "path": "app/chat/components/ui/popover.tsx",
    "chars": 1650,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\";\n\nimport { cn"
  },
  {
    "path": "app/chat/components/ui/scroll-area.tsx",
    "chars": 1658,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as ScrollAreaPrimitive from \"@radix-ui/react-scroll-area\";\n\nimpo"
  },
  {
    "path": "app/chat/components/ui/select.tsx",
    "chars": 6088,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { Check"
  },
  {
    "path": "app/chat/components/ui/separator.tsx",
    "chars": 716,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\";\n\nimport "
  },
  {
    "path": "app/chat/components/ui/sheet.tsx",
    "chars": 4114,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { XIcon "
  },
  {
    "path": "app/chat/components/ui/sidebar.tsx",
    "chars": 21888,
    "preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { type VariantProps, cva } from \"cla"
  },
  {
    "path": "app/chat/components/ui/skeleton.tsx",
    "chars": 284,
    "preview": "import { cn } from \"~/chat/lib/utils\";\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<\"div\">) {\n  retu"
  },
  {
    "path": "app/chat/components/ui/sonner.tsx",
    "chars": 576,
    "preview": "\"use client\";\n\nimport { useTheme } from \"next-themes\";\nimport { Toaster as Sonner, type ToasterProps } from \"sonner\";\n\nc"
  },
  {
    "path": "app/chat/components/ui/text-morph.tsx",
    "chars": 1851,
    "preview": "import { cn } from \"~/chat/lib/utils\";\nimport {\n  AnimatePresence,\n  motion,\n  type Transition,\n  type Variants,\n} from "
  },
  {
    "path": "app/chat/components/ui/textarea.tsx",
    "chars": 791,
    "preview": "import * as React from \"react\";\n\nimport { cn } from \"~/chat/lib/utils\";\n\nfunction Textarea({ className, ...props }: Reac"
  },
  {
    "path": "app/chat/components/ui/tooltip.tsx",
    "chars": 1906,
    "preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\n\nimport { cn"
  },
  {
    "path": "app/chat/hooks/use-mobile.ts",
    "chars": 585,
    "preview": "import * as React from \"react\";\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile() {\n  const [isMobile, setI"
  },
  {
    "path": "app/chat/lib/constants.ts",
    "chars": 245,
    "preview": "/**\n * Constants used throughout the application\n */\n\n// Local storage keys\nexport const STORAGE_KEYS = {\n  MCP_SERVERS:"
  },
  {
    "path": "app/chat/lib/context/mcp-context.tsx",
    "chars": 5283,
    "preview": "\"use client\";\n\nimport React, {\n  createContext,\n  useContext,\n  useEffect,\n  useMemo,\n  useState,\n} from \"react\";\nimport"
  },
  {
    "path": "app/chat/lib/db/schema.ts",
    "chars": 615,
    "preview": "// Message role enum type\nexport enum MessageRole {\n  USER = \"user\",\n  ASSISTANT = \"assistant\",\n  TOOL = \"tool\",\n}\n\n// T"
  },
  {
    "path": "app/chat/lib/hooks/use-copy.ts",
    "chars": 667,
    "preview": "import { useState, useCallback } from \"react\";\n\nexport function useCopy(timeout = 2000) {\n  const [copied, setCopied] = "
  },
  {
    "path": "app/chat/lib/hooks/use-local-storage.ts",
    "chars": 2535,
    "preview": "import { useState, useEffect, useCallback } from \"react\";\n\ntype SetValue<T> = T | ((val: T) => T);\n\n/**\n * Custom hook f"
  },
  {
    "path": "app/chat/lib/hooks/use-scroll-to-bottom.tsx",
    "chars": 2356,
    "preview": "import { useEffect, useRef, type RefObject } from \"react\";\n\nexport function useScrollToBottom(): [\n  RefObject<HTMLDivEl"
  },
  {
    "path": "app/chat/lib/user-id.ts",
    "chars": 556,
    "preview": "import { nanoid } from \"nanoid\";\n\nconst USER_ID_KEY = \"ai-chat-user-id\";\n\nexport function getUserId(): string {\n  // Onl"
  },
  {
    "path": "app/chat/lib/utils.ts",
    "chars": 1344,
    "preview": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: C"
  },
  {
    "path": "app/components/.client/chatPage.client.tsx",
    "chars": 2117,
    "preview": "import Chat from \"~/chat/components/chat\";\nimport { ChatSidebar } from \"~/chat/components/chat-sidebar\";\nimport { Sideba"
  },
  {
    "path": "app/components/.client/chatPage.css",
    "chars": 12964,
    "preview": "@import \"tailwindcss\";\n\n@plugin \"tailwindcss-animate\";\n\n@custom-variant dark (&:is(.dark *));\n@custom-variant sunset (&:"
  },
  {
    "path": "app/components/chatPage.tsx",
    "chars": 479,
    "preview": "import { useState, useEffect } from \"react\";\nimport ChatPageClient from \"./.client/chatPage.client\";\nexport default func"
  },
  {
    "path": "app/components/content.tsx",
    "chars": 28602,
    "preview": "import React, { useState } from \"react\";\nimport { ClipboardCopy, Check, Github } from \"lucide-react\";\nimport type { UrlT"
  },
  {
    "path": "app/entry.server.tsx",
    "chars": 1474,
    "preview": "import type { AppLoadContext, EntryContext } from \"react-router\";\nimport { ServerRouter } from \"react-router\";\nimport { "
  },
  {
    "path": "app/globals.css",
    "chars": 664,
    "preview": "@import \"tailwindcss\";\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n  html {\n    -webkit-fo"
  },
  {
    "path": "app/root.tsx",
    "chars": 2188,
    "preview": "import \"./globals.css\";\nimport { Links, Meta, Outlet, Scripts, ScrollRestoration } from \"react-router\";\n\nexport default "
  },
  {
    "path": "app/routes/$.tsx",
    "chars": 1882,
    "preview": "import { getRepoData } from \"../../src/shared/repoData\";\nimport Content from \"../components/content\";\nimport ChatPageSer"
  },
  {
    "path": "app/routes/_index.tsx",
    "chars": 30534,
    "preview": "import { Github, Code, Globe, Zap } from \"lucide-react\";\nimport { useState, type FormEvent, useEffect } from \"react\";\nim"
  },
  {
    "path": "app/routes/api.chat.ts",
    "chars": 6162,
    "preview": "import { getModel } from \"~/chat/ai/providers.server\";\nimport type { modelID } from \"~/chat/ai/providers.shared\";\nimport"
  },
  {
    "path": "app/routes.ts",
    "chars": 167,
    "preview": "import { type RouteConfig } from \"@react-router/dev/routes\";\nimport { flatRoutes } from \"@react-router/fs-routes\";\n\nexpo"
  },
  {
    "path": "biome.json",
    "chars": 658,
    "preview": "{\n  \"$schema\": \"https://biomejs.dev/schemas/1.6.2/schema.json\",\n  \"organizeImports\": {\n    \"enabled\": true\n  },\n  \"files"
  },
  {
    "path": "components.json",
    "chars": 423,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {"
  },
  {
    "path": "package.json",
    "chars": 3373,
    "preview": "{\n  \"name\": \"git-mcp\",\n  \"version\": \"1.0.0\",\n  \"description\": \"GitMCP is a tool that allows you to get the documentation"
  },
  {
    "path": "playwright.config.ts",
    "chars": 2372,
    "preview": "import { defineConfig, devices } from '@playwright/test';\nimport path from 'path';\nimport { fileURLToPath } from 'url'; "
  },
  {
    "path": "postcss.config.mjs",
    "chars": 114,
    "preview": "/** @type {import('tailwindcss').Config} */\nexport default {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n};\n"
  },
  {
    "path": "react-router.config.ts",
    "chars": 163,
    "preview": "import type { Config } from \"@react-router/dev/config\";\n\nexport default {\n  ssr: true,\n  future: {\n    unstable_viteEnvi"
  },
  {
    "path": "src/api/test-setup.ts",
    "chars": 2534,
    "preview": "export interface TestSetupBindings extends Env {\n  DOCS_BUCKET: R2Bucket; // Ensure this matches CloudflareEnvironment d"
  },
  {
    "path": "src/api/tools/commonTools.test.ts",
    "chars": 1838,
    "preview": "import { describe, it, expect } from \"vitest\";\nimport { enforceToolNameLengthLimit, LIMIT } from \"./commonTools\";\nimport"
  },
  {
    "path": "src/api/tools/commonTools.ts",
    "chars": 29750,
    "preview": "import type { RepoData } from \"../../shared/repoData.js\";\nimport {\n  constructGithubUrl,\n  fetchFileFromGitHub,\n  getRep"
  },
  {
    "path": "src/api/tools/index.test.ts",
    "chars": 6867,
    "preview": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport * as toolsModule from \"./index\";\nimport"
  },
  {
    "path": "src/api/tools/index.ts",
    "chars": 1228,
    "preview": "import { z } from \"zod\";\nimport { getRepoData } from \"../../shared/repoData.js\";\nimport { fetchUrlContent } from \"./comm"
  },
  {
    "path": "src/api/tools/repoHandlers/DefaultRepoHandler.ts",
    "chars": 3664,
    "preview": "import {\n  fetchDocumentation,\n  searchRepositoryDocumentation,\n  searchRepositoryCode,\n  fetchUrlContent,\n  generateFet"
  },
  {
    "path": "src/api/tools/repoHandlers/GenericRepoHandler.ts",
    "chars": 7556,
    "preview": "import type { RepoData } from \"../../../shared/repoData.js\";\nimport type { RepoHandler, Tool } from \"./RepoHandler.js\";\n"
  },
  {
    "path": "src/api/tools/repoHandlers/ReactRouterRepoHandler.ts",
    "chars": 2578,
    "preview": "import {\n  fetchDocumentation,\n  generateSearchToolDescription,\n  generateSearchToolName,\n  searchRepositoryDocumentatio"
  },
  {
    "path": "src/api/tools/repoHandlers/RepoHandler.ts",
    "chars": 928,
    "preview": "import type { RepoData } from \"../../../shared/repoData.js\";\nimport type { ToolAnnotations } from \"@modelcontextprotocol"
  },
  {
    "path": "src/api/tools/repoHandlers/ThreejsRepoHandler.ts",
    "chars": 5439,
    "preview": "import type { RepoHandler, Tool } from \"./RepoHandler.js\";\nimport type { RepoData } from \"../../../shared/repoData.js\";\n"
  },
  {
    "path": "src/api/tools/repoHandlers/generic/generic.test.ts",
    "chars": 2688,
    "preview": "import { describe, it, expect, beforeAll } from \"vitest\";\nimport { MockMcp } from \"../test/utils\";\nimport * as toolsModu"
  },
  {
    "path": "src/api/tools/repoHandlers/generic/static-mapping.json",
    "chars": 865773,
    "preview": "{\n  \"Peewee Async\": {\n    \"title\": \"Peewee Async\",\n    \"repoName\": \"05bit/peewee-async\",\n    \"githubUrl\": \"https://githu"
  },
  {
    "path": "src/api/tools/repoHandlers/handlers.test.ts",
    "chars": 2541,
    "preview": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { getHandlerByRepoData } from \"./handle"
  },
  {
    "path": "src/api/tools/repoHandlers/handlers.ts",
    "chars": 1589,
    "preview": "import type { RepoData, UrlType } from \"../../../shared/repoData.js\";\nimport type { RepoHandler } from \"./RepoHandler.js"
  },
  {
    "path": "src/api/tools/repoHandlers/test/utils.ts",
    "chars": 674,
    "preview": "import type { ZodRawShape } from \"zod\";\n\nexport class MockMcp {\n  #tools: Record<\n    string,\n    {\n      description: s"
  },
  {
    "path": "src/api/tools/repoHandlers/threejs/__snapshots__/utils.test.ts.snap",
    "chars": 559,
    "preview": "// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html\n\nexports[`Threejs Utils > should get the reference docs li"
  },
  {
    "path": "src/api/tools/repoHandlers/threejs/utils.test.ts",
    "chars": 1931,
    "preview": "import { describe, it, expect, vi } from \"vitest\";\nimport { getReferenceDocsContent } from \"./utils.js\";\n\nconst THREEJS_"
  },
  {
    "path": "src/api/tools/repoHandlers/threejs/utils.ts",
    "chars": 7741,
    "preview": "import htmlToMd from \"html-to-md\";\nimport { fetchUrlContent } from \"../../../utils/cache.js\";\nconst THREEJS_BASE_URL = \""
  },
  {
    "path": "src/api/utils/ViewCounterDO.ts",
    "chars": 5328,
    "preview": "/**\n * Durable Object for handling repository view counts\n * This provides atomic operations to avoid race conditions wh"
  },
  {
    "path": "src/api/utils/badge.ts",
    "chars": 411392,
    "preview": "/**\n * Badge utilities for GitMCP documentation tracking\n */\n\nconst badgeCountAllowedRepos = [\"mcp-ui\", \"git-mcp\"];\n\n/**"
  },
  {
    "path": "src/api/utils/cache.ts",
    "chars": 9805,
    "preview": "import type { RobotsRule } from \"./robotsTxt.js\";\nimport type { FetchDocumentationResult } from \"../tools/commonTools.js"
  },
  {
    "path": "src/api/utils/github.ts",
    "chars": 4639,
    "preview": "import { cacheFilePath, getCachedFilePath } from \"./cache.js\";\nimport {\n  searchFileByName,\n  githubApiRequest,\n  fetchR"
  },
  {
    "path": "src/api/utils/githubClient.ts",
    "chars": 9839,
    "preview": "/**\n * GitHub API client with rate limiting support\n * This file contains utilities for making GitHub API requests with "
  },
  {
    "path": "src/api/utils/helpers.ts",
    "chars": 2766,
    "preview": "/**\n * Format search results into a readable text format\n * Ensures each documentation entry is properly separated\n * @p"
  },
  {
    "path": "src/api/utils/r2.ts",
    "chars": 560,
    "preview": "export async function fetchFileFromR2(\n  owner: string,\n  repo: string,\n  filename: string,\n  env: CloudflareEnvironment"
  },
  {
    "path": "src/api/utils/robotsTxt.ts",
    "chars": 5747,
    "preview": "import {\n  cacheRobotsTxt,\n  fetchUrlContent,\n  getCachedRobotsTxt,\n} from \"./cache.js\";\n/**\n * Interface for robots.txt"
  },
  {
    "path": "src/api/utils/vectorStore.ts",
    "chars": 34231,
    "preview": "// Define a generic Dict type since we can't import it directly\ntype Dict = { [key: string]: any };\n\n// TTL for vector e"
  },
  {
    "path": "src/index.ts",
    "chars": 4585,
    "preview": "import { McpAgent } from \"agents/mcp\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { get"
  },
  {
    "path": "src/shared/nameUtils.ts",
    "chars": 282,
    "preview": "/**\n * Generates a consistent server name based on repository name\n * @param repo - The repository name\n * @returns A se"
  },
  {
    "path": "src/shared/repoData.test.ts",
    "chars": 5150,
    "preview": "import { getRepoData, getRepoDataFromUrl, HOST_TEMP_URL } from \"./repoData\";\nimport type { RepoData } from \"./repoData\";"
  },
  {
    "path": "src/shared/repoData.ts",
    "chars": 4359,
    "preview": "export type UrlType = \"subdomain\" | \"github\" | \"unknown\";\nexport type MinimalRepoData = {\n  owner: string | null;\n  repo"
  },
  {
    "path": "src/shared/urlUtils.ts",
    "chars": 305,
    "preview": "export function removeLeadingUnderscore(url: string) {\n  const urlObj = new URL(url);\n  let pathname = urlObj.pathname;\n"
  },
  {
    "path": "src/test/ViewCounterDO.test.ts",
    "chars": 3947,
    "preview": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { ViewCounterDO } from \"../api/utils/ViewCounterDO"
  },
  {
    "path": "src/test/badge.test.ts",
    "chars": 6571,
    "preview": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport {\n  generateBadgeResponse,\n  getRepoViewCount,\n  i"
  },
  {
    "path": "src/utils.ts",
    "chars": 11575,
    "preview": "// Helper to generate the layout\nimport { html, raw } from \"hono/html\";\nimport type { HtmlEscapedString } from \"hono/uti"
  },
  {
    "path": "static/README.md",
    "chars": 3802,
    "preview": "# Remote MCP Server on Cloudflare\n\nLet's get a remote MCP server up-and-running on Cloudflare Workers complete with OAut"
  },
  {
    "path": "tailwind.config.js",
    "chars": 1035,
    "preview": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  content: [\"./app/**/*.{js,ts,jsx,tsx}\"],\n  theme: {\n   "
  },
  {
    "path": "tests/e2e/inspection.spec.ts",
    "chars": 5108,
    "preview": "import { test, expect } from \"@playwright/test\";\n\n// Define the base target dev server URL (the one the Inspector connec"
  },
  {
    "path": "tests/global-setup.ts",
    "chars": 2162,
    "preview": "import { FullConfig } from '@playwright/test';\nimport fetch from 'node-fetch';\n\n// URL of your running worker during tes"
  },
  {
    "path": "tsconfig.cloudflare.json",
    "chars": 662,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"include\": [\n    \".react-router/types/**/*\",\n    \"app/**/*\",\n    \"app/**/.server/**/"
  },
  {
    "path": "tsconfig.json",
    "chars": 320,
    "preview": "{\n  \"files\": [],\n  \"references\": [\n    { \"path\": \"./tsconfig.node.json\" },\n    { \"path\": \"./tsconfig.cloudflare.json\" }\n"
  },
  {
    "path": "tsconfig.node.json",
    "chars": 289,
    "preview": "{\n  \"extends\": \"./tsconfig.json\",\n  \"include\": [\"tailwind.config.ts\", \"vite.config.ts\"],\n  \"compilerOptions\": {\n    \"com"
  },
  {
    "path": "vite.config.ts",
    "chars": 405,
    "preview": "import { reactRouter } from \"@react-router/dev/vite\";\nimport { cloudflare } from \"@cloudflare/vite-plugin\";\nimport tailw"
  },
  {
    "path": "vitest.config.ts",
    "chars": 199,
    "preview": "import { configDefaults, defineConfig } from \"vitest/config\";\n\nexport default defineConfig({\n  test: {\n    exclude: [..."
  },
  {
    "path": "worker-configuration.d.ts",
    "chars": 237856,
    "preview": "/* eslint-disable */\n// Generated by Wrangler by running `wrangler types` (hash: b15bdb1393c2ff62c93af283c69f3ddc)\n// Ru"
  },
  {
    "path": "wrangler.jsonc",
    "chars": 1631,
    "preview": "/**\n * For more details on how to configure Wrangler, refer to:\n * https://developers.cloudflare.com/workers/wrangler/co"
  }
]

About this extraction

This page contains the full source code of the idosal/git-mcp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 127 files (1.9 MB), approximately 634.2k tokens, and a symbol index with 876 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!