├── .npmrc ├── examples ├── dexa-mfm │ ├── .gitignore │ ├── wrangler.example.toml │ ├── src │ │ ├── utils.ts │ │ ├── types.ts │ │ ├── index.ts │ │ └── routes.ts │ ├── tsconfig.json │ ├── readme.md │ └── package.json ├── dexa-lex-fridman │ ├── .gitignore │ ├── wrangler.example.toml │ ├── tsconfig.json │ ├── src │ │ ├── utils.ts │ │ ├── types.ts │ │ ├── index.ts │ │ └── routes.ts │ ├── package.json │ └── readme.md └── ascii-art │ ├── wrangler.toml │ ├── tsconfig.json │ ├── package.json │ ├── src │ ├── index.ts │ └── routes.ts │ └── readme.md ├── .github ├── funding.yml └── workflows │ └── test.yml ├── pnpm-workspace.yaml ├── .prettierignore ├── media ├── social.png ├── love-opt.jpg ├── advice-for-youth-opt.jpg ├── poker-and-physics-opt.jpg ├── plugin-ascii-art-demo-opt.jpg └── elon-musk-philosophy-on-life-opt.jpg ├── .husky └── pre-commit ├── packages └── chatgpt-plugin │ ├── src │ ├── index.ts │ ├── types.ts │ ├── utils.ts │ └── ai-plugin.ts │ ├── tsconfig.json │ ├── tsup.config.ts │ ├── package.json │ └── readme.md ├── lerna.json ├── tsconfig.json ├── tsconfig.base.json ├── .prettierrc.cjs ├── .gitignore ├── license ├── package.json └── readme.md /.npmrc: -------------------------------------------------------------------------------- 1 | enable-pre-post-scripts=true 2 | -------------------------------------------------------------------------------- /examples/dexa-mfm/.gitignore: -------------------------------------------------------------------------------- 1 | wrangler.toml -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | github: [transitive-bullshit] 2 | -------------------------------------------------------------------------------- /examples/dexa-lex-fridman/.gitignore: -------------------------------------------------------------------------------- 1 | wrangler.toml -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | - 'examples/*' 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .snapshots/ 2 | build/ 3 | dist/ 4 | node_modules/ 5 | .next/ 6 | .vercel/ -------------------------------------------------------------------------------- /media/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/chatgpt-plugin-ts/main/media/social.png -------------------------------------------------------------------------------- /media/love-opt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/chatgpt-plugin-ts/main/media/love-opt.jpg -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run pre-commit 5 | -------------------------------------------------------------------------------- /media/advice-for-youth-opt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/chatgpt-plugin-ts/main/media/advice-for-youth-opt.jpg -------------------------------------------------------------------------------- /media/poker-and-physics-opt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/chatgpt-plugin-ts/main/media/poker-and-physics-opt.jpg -------------------------------------------------------------------------------- /packages/chatgpt-plugin/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ai-plugin' 2 | export * from './types' 3 | export * from './utils' 4 | -------------------------------------------------------------------------------- /media/plugin-ascii-art-demo-opt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/chatgpt-plugin-ts/main/media/plugin-ascii-art-demo-opt.jpg -------------------------------------------------------------------------------- /examples/ascii-art/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "chatgpt-plugin-ascii-art" 2 | main = "src/index.ts" 3 | compatibility_date = "2023-04-04" 4 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "useWorkspaces": true, 4 | "version": "0.1.2" 5 | } 6 | -------------------------------------------------------------------------------- /media/elon-musk-philosophy-on-life-opt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altryne/chatgpt-plugin-ts/main/media/elon-musk-philosophy-on-life-opt.jpg -------------------------------------------------------------------------------- /examples/dexa-lex-fridman/wrangler.example.toml: -------------------------------------------------------------------------------- 1 | name = "chatgpt-plugin-dexa-lex-fridman" 2 | main = "src/index.ts" 3 | compatibility_date = "2023-04-04" 4 | 5 | [vars] 6 | DEXA_API_BASE_URL="TODO" -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true 4 | }, 5 | "files": [], 6 | "include": [], 7 | "references": [{ "path": "./packages/chatgpt-plugin/tsconfig.json" }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/chatgpt-plugin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "build", 6 | "rootDir": "src", 7 | "tsBuildInfoFile": "build/.tsbuildinfo", 8 | "emitDeclarationOnly": true 9 | }, 10 | "include": ["src/**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/chatgpt-plugin/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['src/**/*.ts'], 5 | outDir: 'build', 6 | target: 'node14', 7 | platform: 'node', 8 | format: ['esm'], 9 | splitting: false, 10 | sourcemap: true, 11 | minify: false, 12 | shims: false 13 | }) 14 | -------------------------------------------------------------------------------- /examples/dexa-mfm/wrangler.example.toml: -------------------------------------------------------------------------------- 1 | name = "chatgpt-plugin-dexa-mfm" 2 | main = "src/index.ts" 3 | compatibility_date = "2023-04-04" 4 | 5 | [vars] 6 | vars = { DEXA_API_BASE_URL="TODO", ENVIRONMENT="dev" } 7 | 8 | [env.staging] 9 | vars = { DEXA_API_BASE_URL="TODO", ENVIRONMENT = "staging" } 10 | 11 | [env.production] 12 | vars = { DEXA_API_BASE_URL="TODO", ENVIRONMENT = "production" } 13 | -------------------------------------------------------------------------------- /examples/ascii-art/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "build", 6 | "tsBuildInfoFile": "build/.tsbuildinfo", 7 | "emitDeclarationOnly": true, 8 | "types": ["@cloudflare/workers-types"] 9 | }, 10 | "include": ["src/**/*.ts"], 11 | "references": [{ "path": "../../packages/chatgpt-plugin/tsconfig.json" }] 12 | } 13 | -------------------------------------------------------------------------------- /examples/dexa-lex-fridman/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "build", 6 | "tsBuildInfoFile": "build/.tsbuildinfo", 7 | "emitDeclarationOnly": true, 8 | "types": ["@cloudflare/workers-types"] 9 | }, 10 | "include": ["src/**/*.ts", "package.json"], 11 | "references": [{ "path": "../../packages/chatgpt-plugin/tsconfig.json" }] 12 | } 13 | -------------------------------------------------------------------------------- /examples/dexa-mfm/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function pick(obj: T, ...keys: string[]): U { 2 | return Object.fromEntries( 3 | keys.filter((key) => key in obj).map((key) => [key, obj[key]]) 4 | ) as U 5 | } 6 | 7 | export function omit(obj: T, ...keys: string[]): U { 8 | return Object.fromEntries( 9 | Object.entries(obj).filter(([key]) => !keys.includes(key)) 10 | ) as U 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2021", 5 | "lib": ["esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "strict": false, 9 | "forceConsistentCasingInFileNames": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/dexa-lex-fridman/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function pick(obj: T, ...keys: string[]): U { 2 | return Object.fromEntries( 3 | keys.filter((key) => key in obj).map((key) => [key, obj[key]]) 4 | ) as U 5 | } 6 | 7 | export function omit(obj: T, ...keys: string[]): U { 8 | return Object.fromEntries( 9 | Object.entries(obj).filter(([key]) => !keys.includes(key)) 10 | ) as U 11 | } 12 | -------------------------------------------------------------------------------- /examples/dexa-mfm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base", 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "build", 6 | "tsBuildInfoFile": "build/.tsbuildinfo", 7 | "emitDeclarationOnly": true, 8 | "types": ["@cloudflare/workers-types"] 9 | }, 10 | "include": ["src/**/*.ts", "package.json"], 11 | "exclude": ["node_modules"], 12 | "references": [{ "path": "../../packages/chatgpt-plugin/tsconfig.json" }] 13 | } 14 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('@trivago/prettier-plugin-sort-imports')], 3 | singleQuote: true, 4 | jsxSingleQuote: true, 5 | semi: false, 6 | useTabs: false, 7 | tabWidth: 2, 8 | bracketSpacing: true, 9 | bracketSameLine: false, 10 | arrowParens: 'always', 11 | trailingComma: 'none', 12 | importOrder: ['^node:.*', '', '^(@/(.*)$)', '^[./]'], 13 | importOrderSeparation: true, 14 | importOrderSortSpecifiers: true, 15 | importOrderGroupNamespaceSpecifiers: true 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | *.swp 4 | .idea 5 | 6 | # dependencies 7 | node_modules/ 8 | .pnp/ 9 | .pnp.js 10 | 11 | # testing 12 | /coverage 13 | 14 | # next.js 15 | .next/ 16 | out/ 17 | 18 | # production 19 | build/ 20 | 21 | # misc 22 | .DS_Store 23 | *.pem 24 | 25 | # debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | .pnpm-debug.log* 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | next-env.d.ts 40 | 41 | # local env files 42 | .env 43 | .env.local 44 | .env.build 45 | .env.development.local 46 | .env.test.local 47 | .env.production.local 48 | -------------------------------------------------------------------------------- /examples/dexa-mfm/src/types.ts: -------------------------------------------------------------------------------- 1 | import z from 'zod' 2 | 3 | export const DexaSearchRequestBodySchema = z.object({ 4 | query: z.string().min(1).max(500), 5 | topK: z.number().min(1).max(100).default(10) 6 | }) 7 | 8 | export type DexaSearchRequestBody = z.infer 9 | 10 | export const DexaSearchResultSchema = z.object({ 11 | content: z.string(), 12 | episodeTitle: z.string(), 13 | chapterTitle: z.string(), 14 | citationUrl: z.string() 15 | }) 16 | 17 | export const DexaSearchResponseBodySchema = z.object({ 18 | results: z.array(DexaSearchResultSchema) 19 | }) 20 | 21 | export type DexaSearchResult = z.infer 22 | export type DexaSearchResponseBody = z.infer< 23 | typeof DexaSearchResponseBodySchema 24 | > 25 | -------------------------------------------------------------------------------- /examples/dexa-lex-fridman/src/types.ts: -------------------------------------------------------------------------------- 1 | import z from 'zod' 2 | 3 | export const DexaSearchRequestBodySchema = z.object({ 4 | query: z.string().min(1).max(500), 5 | topK: z.number().min(1).max(100).default(10) 6 | }) 7 | 8 | export type DexaSearchRequestBody = z.infer 9 | 10 | export const DexaSearchResultSchema = z.object({ 11 | content: z.string(), 12 | episodeTitle: z.string(), 13 | chapterTitle: z.string(), 14 | citationUrl: z.string() 15 | }) 16 | 17 | export const DexaSearchResponseBodySchema = z.object({ 18 | results: z.array(DexaSearchResultSchema) 19 | }) 20 | 21 | export type DexaSearchResult = z.infer 22 | export type DexaSearchResponseBody = z.infer< 23 | typeof DexaSearchResponseBodySchema 24 | > 25 | -------------------------------------------------------------------------------- /examples/ascii-art/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-plugin-example-ascii-art", 3 | "version": "0.1.2", 4 | "private": true, 5 | "description": "Example ChatGPT Plugin which renders text as ASCII art. Built using Cloudflare workers.", 6 | "author": "Travis Fischer ", 7 | "repository": "transitive-bullshit/chatgpt-plugin-ts", 8 | "license": "MIT", 9 | "type": "module", 10 | "engines": { 11 | "node": ">=14" 12 | }, 13 | "scripts": { 14 | "dev": "wrangler dev", 15 | "deploy": "wrangler publish" 16 | }, 17 | "dependencies": { 18 | "@cloudflare/itty-router-openapi": "^0.0.15", 19 | "chatgpt-plugin": "workspace:../../packages/chatgpt-plugin", 20 | "figlet": "^1.5.2" 21 | }, 22 | "keywords": [ 23 | "openai", 24 | "chatgpt", 25 | "plugin", 26 | "openapi", 27 | "worker" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/chatgpt-plugin/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface AIPluginManifest { 2 | schema_version: string 3 | name_for_model: string 4 | name_for_human: string 5 | description_for_model: string 6 | description_for_human: string 7 | auth: AIPluginAuth 8 | api: AIPluginAPI 9 | logo_url: string 10 | contact_email: string 11 | legal_info_url: string 12 | } 13 | 14 | export interface AIPluginAPI { 15 | type: string 16 | url: string 17 | has_user_authentication: boolean 18 | } 19 | 20 | export interface AIPluginAuth { 21 | type: string | 'none' 22 | authorization_type?: string 23 | authorization_url?: string 24 | client_url?: string 25 | scope?: string 26 | authorization_content_type?: string 27 | verification_tokens?: AIPluginVerificationTokens 28 | instructions?: string 29 | } 30 | 31 | export interface AIPluginVerificationTokens { 32 | openai: string 33 | } 34 | -------------------------------------------------------------------------------- /packages/chatgpt-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-plugin", 3 | "version": "0.1.2", 4 | "description": "Types and utilities for building ChatGPT plugins with TypeScript.", 5 | "author": "Travis Fischer ", 6 | "repository": "transitive-bullshit/chatgpt-plugin-ts", 7 | "license": "MIT", 8 | "type": "module", 9 | "source": "./src/index.ts", 10 | "types": "./build/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "import": "./build/index.js", 14 | "types": "./build/index.d.ts", 15 | "default": "./build/index.js" 16 | } 17 | }, 18 | "files": [ 19 | "build" 20 | ], 21 | "engines": { 22 | "node": ">=14" 23 | }, 24 | "scripts": { 25 | "build": "tsup" 26 | }, 27 | "keywords": [ 28 | "chatgpt", 29 | "plugin", 30 | "ai", 31 | "well-known", 32 | "openai", 33 | "openapi" 34 | ], 35 | "dependencies": { 36 | "@sindresorhus/slugify": "^2.2.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/dexa-mfm/readme.md: -------------------------------------------------------------------------------- 1 | # ChatGPT Plugin Example - Dexa MFM Podcast 2 | 3 | > TODO: WIP 4 | 5 | [![Build Status](https://github.com/transitive-bullshit/chatgpt-plugin-ts/actions/workflows/test.yml/badge.svg)](https://github.com/transitive-bullshit/chatgpt-plugin-ts/actions/workflows/test.yml) [![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/transitive-bullshit/chatgpt-plugin-ts/blob/main/license) [![Prettier Code Formatting](https://img.shields.io/badge/code_style-prettier-brightgreen.svg)](https://prettier.io) 6 | 7 | - [Intro](#intro) 8 | - [License](#license) 9 | 10 | ## Intro 11 | 12 | TODO: WIP 13 | 14 | ## License 15 | 16 | MIT © [Travis Fischer](https://transitivebullsh.it) 17 | 18 | If you found this project interesting, please consider [sponsoring me](https://github.com/sponsors/transitive-bullshit) or following me on twitter twitter 19 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Travis Fischer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/chatgpt-plugin/src/utils.ts: -------------------------------------------------------------------------------- 1 | function ipToInt(ip: string) { 2 | return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet), 0) 3 | } 4 | 5 | function isIpInCIDR(ip: string, cidr: string) { 6 | const [cidrIp, prefixLength] = cidr.split('/') 7 | const mask = -1 << (32 - parseInt(prefixLength)) 8 | const ipInt = ipToInt(ip) 9 | const cidrIpInt = ipToInt(cidrIp) 10 | 11 | const networkAddress = cidrIpInt & mask 12 | const broadcastAddress = networkAddress | ~mask 13 | 14 | return ipInt >= networkAddress && ipInt <= broadcastAddress 15 | } 16 | 17 | /** 18 | * Validates that the given IP address is in the range of IP addresses 19 | * documented by OpenAI's production ChatGPT Plugin docs. 20 | * 21 | * Credit to [Steven Tey](https://gist.github.com/steven-tey/994ae6be0da254ebbdf28d06623874ec) for the original implementation. 22 | * 23 | * @see https://platform.openai.com/docs/plugins/production/ip-egress-ranges 24 | */ 25 | export function isValidChatGPTIPAddress(ip: string) { 26 | if (!ip) return false 27 | 28 | // verify both CIDR blocks 29 | return ( 30 | isIpInCIDR(ip, '23.102.140.112/28') || isIpInCIDR(ip, '23.98.142.176/28') 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /examples/dexa-lex-fridman/src/index.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIRouter } from '@cloudflare/itty-router-openapi' 2 | import { defineAIPluginManifest } from 'chatgpt-plugin' 3 | 4 | import * as routes from './routes' 5 | import pkg from '../package.json' 6 | 7 | export interface Env { 8 | DEXA_API_BASE_URL: string 9 | } 10 | 11 | const router = OpenAPIRouter({ 12 | schema: { 13 | info: { 14 | title: pkg.aiPlugin.name, 15 | version: pkg.version 16 | } 17 | } 18 | }) 19 | 20 | router.get('/search', routes.DexaSearch) 21 | 22 | router.get('/.well-known/ai-plugin.json', (request: Request) => { 23 | const host = request.headers.get('host') 24 | const pluginManifest = defineAIPluginManifest( 25 | { 26 | description_for_human: pkg.description, 27 | name_for_human: pkg.aiPlugin.name, 28 | ...pkg.aiPlugin 29 | }, 30 | { openAPIUrl: `https://${host}/openapi.json` } 31 | ) 32 | 33 | return new Response(JSON.stringify(pluginManifest, null, 2), { 34 | headers: { 35 | 'content-type': 'application/json;charset=UTF-8' 36 | } 37 | }) 38 | }) 39 | 40 | // 404 for everything else 41 | router.all('*', () => new Response('Not Found.', { status: 404 })) 42 | 43 | export default { 44 | fetch: (request: Request, env: Env, ctx: ExecutionContext) => 45 | router.handle(request, env, ctx) 46 | } 47 | -------------------------------------------------------------------------------- /examples/dexa-mfm/src/index.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIRouter } from '@cloudflare/itty-router-openapi' 2 | import { defineAIPluginManifest } from 'chatgpt-plugin' 3 | 4 | import * as routes from './routes' 5 | import pkg from '../package.json' 6 | 7 | export interface Env { 8 | DEXA_API_BASE_URL: string 9 | ENVIRONMENT: string 10 | } 11 | 12 | const router = OpenAPIRouter({ 13 | schema: { 14 | info: { 15 | title: pkg.aiPlugin.name, 16 | version: pkg.version 17 | } 18 | } 19 | }) 20 | 21 | router.get('/search', routes.DexaSearch) 22 | 23 | router.get('/.well-known/ai-plugin.json', (request: Request) => { 24 | const host = request.headers.get('host') 25 | const pluginManifest = defineAIPluginManifest( 26 | { 27 | description_for_human: pkg.description, 28 | name_for_human: pkg.aiPlugin.name, 29 | ...pkg.aiPlugin 30 | }, 31 | { openAPIUrl: `https://${host}/openapi.json` } 32 | ) 33 | 34 | return new Response(JSON.stringify(pluginManifest, null, 2), { 35 | headers: { 36 | 'content-type': 'application/json;charset=UTF-8' 37 | } 38 | }) 39 | }) 40 | 41 | // 404 for everything else 42 | router.all('*', () => new Response('Not Found.', { status: 404 })) 43 | 44 | export default { 45 | fetch: (request: Request, env: Env, ctx: ExecutionContext) => 46 | router.handle(request, env, ctx) 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Test Node.js ${{ matrix.node-version }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node-version: 13 | - 19 14 | - 18 15 | - 16 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | 21 | - name: Install Node.js 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | 26 | - name: Install pnpm 27 | uses: pnpm/action-setup@v2 28 | id: pnpm-install 29 | with: 30 | version: 8 31 | run_install: false 32 | 33 | - name: Get pnpm store directory 34 | id: pnpm-cache 35 | shell: bash 36 | run: | 37 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 38 | 39 | - uses: actions/cache@v3 40 | name: Setup pnpm cache 41 | with: 42 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 43 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 44 | restore-keys: | 45 | ${{ runner.os }}-pnpm-store- 46 | 47 | - name: Install dependencies 48 | run: pnpm install --frozen-lockfile 49 | 50 | - name: Run test 51 | run: pnpm run test 52 | -------------------------------------------------------------------------------- /examples/dexa-mfm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-plugin-example-dexa-mfm", 3 | "version": "0.1.2", 4 | "private": true, 5 | "description": "Search across all of the My First Million Podcast – powered by Dexa AI.", 6 | "author": "Travis Fischer ", 7 | "repository": "transitive-bullshit/chatgpt-plugin-ts", 8 | "license": "MIT", 9 | "type": "module", 10 | "engines": { 11 | "node": ">=14" 12 | }, 13 | "scripts": { 14 | "dev": "wrangler dev", 15 | "deploy": "wrangler publish" 16 | }, 17 | "dependencies": { 18 | "@cloudflare/itty-router-openapi": "^0.0.15", 19 | "chatgpt-plugin": "workspace:../../packages/chatgpt-plugin", 20 | "zod": "^3.21.4" 21 | }, 22 | "aiPlugin": { 23 | "name": "MFM Podcast", 24 | "description_for_model": "Plugin for searching transcripts from the My First Million (MFM) Podcast by Sam Parr (@TheSamParr) and Shaan Puri (@ShaanVP). Use it whenever a user asks about business ideas, getting rich, investing, building wealth, entrepreneurship, company building, fitness, e-commerce, side hustles, or the MFM Podcast.", 25 | "logo_url": "https://assets.standardresume.co/image/upload/w_500,h_500,c_fill,q_auto,f_auto,dpr_2/dexa/accounts/mfm", 26 | "contact_email": "team@dexa.ai", 27 | "legal_info_url": "https://dexa.ai" 28 | }, 29 | "keywords": [ 30 | "openai", 31 | "chatgpt", 32 | "plugin", 33 | "openapi", 34 | "my first million", 35 | "mfm", 36 | "podcast", 37 | "search" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-plugin-ts", 3 | "private": true, 4 | "description": "Examples and resources for creating ChatGPT plugins in TypeScript.", 5 | "author": "Travis Fischer ", 6 | "repository": "transitive-bullshit/chatgpt-plugin-ts", 7 | "license": "MIT", 8 | "type": "module", 9 | "engines": { 10 | "node": ">=14" 11 | }, 12 | "workspaces": [ 13 | "packages/*", 14 | "examples/*" 15 | ], 16 | "scripts": { 17 | "build": "run-s build:*", 18 | "build:tsup": "lerna run build --no-private", 19 | "build:tsc": "tsc --build", 20 | "deploy": "lerna run deploy", 21 | "clean": "del packages/*/build", 22 | "prebuild": "run-s clean", 23 | "prepare": "husky install", 24 | "pre-commit": "lint-staged", 25 | "pretest": "run-s build", 26 | "test": "run-p test:*", 27 | "test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check" 28 | }, 29 | "devDependencies": { 30 | "@cloudflare/workers-types": "^4.20230321.0", 31 | "@trivago/prettier-plugin-sort-imports": "^4.1.1", 32 | "@types/node": "^18.15.11", 33 | "del-cli": "^5.0.0", 34 | "husky": "^8.0.3", 35 | "lerna": "^6.6.1", 36 | "lint-staged": "^13.2.0", 37 | "npm-run-all": "^4.1.5", 38 | "prettier": "^2.8.7", 39 | "tsup": "^6.7.0", 40 | "tsx": "^3.12.6", 41 | "typescript": "^5.0.3", 42 | "wrangler": "^2.14.0" 43 | }, 44 | "lint-staged": { 45 | "*.{ts,tsx}": [ 46 | "prettier --write" 47 | ] 48 | }, 49 | "keywords": [ 50 | "chatgpt", 51 | "plugin", 52 | "ai", 53 | "well-known", 54 | "openai", 55 | "openapi", 56 | "cloudflare", 57 | "cf", 58 | "worker" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /examples/ascii-art/src/index.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIRouter } from '@cloudflare/itty-router-openapi' 2 | import { defineAIPluginManifest } from 'chatgpt-plugin' 3 | 4 | import * as routes from './routes' 5 | 6 | const router = OpenAPIRouter({ 7 | schema: { 8 | info: { 9 | title: 'ChatGPT ASCII Art Plugin', 10 | version: '1.0' 11 | } 12 | } 13 | }) 14 | 15 | router.get('/render', routes.ASCIIArtRender) 16 | 17 | router.get('/.well-known/ai-plugin.json', (request: Request) => { 18 | const host = request.headers.get('host') 19 | const pluginManifest = defineAIPluginManifest({ 20 | schema_version: 'v1', 21 | name_for_model: 'asciiArt0', 22 | name_for_human: 'ASCII Art', 23 | description_for_model: 24 | 'Plugin for rendering text as ASCII art. Use it whenever a user asks to convert text into ASCII character art. Output is a string that should be rendered as a markdown code block (no programming language).', 25 | description_for_human: 'Convert any text to ASCII art.', 26 | auth: { 27 | type: 'none' 28 | }, 29 | api: { 30 | type: 'openapi', 31 | url: `https://${host}/openapi.json`, 32 | has_user_authentication: false 33 | }, 34 | logo_url: 'https://transitivebullsh.it/.well-known/logo.png', 35 | contact_email: 'travis@transitivebullsh.it', 36 | legal_info_url: 'https://transitivebullsh.it/about' 37 | }) 38 | 39 | return new Response(JSON.stringify(pluginManifest, null, 2), { 40 | headers: { 41 | 'content-type': 'application/json;charset=UTF-8' 42 | } 43 | }) 44 | }) 45 | 46 | // 404 for everything else 47 | router.all('*', () => new Response('Not Found.', { status: 404 })) 48 | 49 | export default { 50 | fetch: (request: Request) => router.handle(request) 51 | } 52 | -------------------------------------------------------------------------------- /examples/ascii-art/readme.md: -------------------------------------------------------------------------------- 1 | 3 | 4 | # ChatGPT Plugin Example - ASCII Art 5 | 6 | > Example ChatGPT Plugin which renders text as ASCII art. Built using Cloudflare workers. 7 | 8 | [![Build Status](https://github.com/transitive-bullshit/chatgpt-plugin-ts/actions/workflows/test.yml/badge.svg)](https://github.com/transitive-bullshit/chatgpt-plugin-ts/actions/workflows/test.yml) [![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/transitive-bullshit/chatgpt-plugin-ts/blob/main/license) [![Prettier Code Formatting](https://img.shields.io/badge/code_style-prettier-brightgreen.svg)](https://prettier.io) 9 | 10 | - [Intro](#intro) 11 | - [Stack](#stack) 12 | - [Examples](#examples) 13 | - [License](#license) 14 | 15 | ## Intro 16 | 17 | This is a simple ChatGPT Plugin which renders text as ASCII art. 18 | 19 | It contains a single API route. 20 | 21 | [Live manifest file](https://chatgpt-plugin-ascii-art.transitive-bullshit.workers.dev/.well-known/ai-plugin.json) 22 | 23 | ## Stack 24 | 25 | - [Cloudflare Workers](https://workers.cloudflare.com/) 26 | - [TypeScript](https://www.typescriptlang.org/) 27 | - [@cloudflare/itty-router-openapi](https://github.com/cloudflare/itty-router-openapi) 28 | 29 | ## Examples 30 | 31 |

32 | ASCII art using the Poison font 33 |

34 | 35 | ## License 36 | 37 | MIT © [Travis Fischer](https://transitivebullsh.it) 38 | 39 | If you found this project interesting, please consider [sponsoring me](https://github.com/sponsors/transitive-bullshit) or following me on twitter twitter 40 | -------------------------------------------------------------------------------- /examples/dexa-lex-fridman/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-plugin-example-dexa-lex-fridman", 3 | "version": "0.1.2", 4 | "private": true, 5 | "description": "Search across all of the Lex Fridman Podcast episodes – powered by Dexa AI.", 6 | "author": "Travis Fischer ", 7 | "repository": "transitive-bullshit/chatgpt-plugin-ts", 8 | "license": "MIT", 9 | "type": "module", 10 | "engines": { 11 | "node": ">=14" 12 | }, 13 | "scripts": { 14 | "dev": "wrangler dev", 15 | "deploy": "wrangler publish" 16 | }, 17 | "dependencies": { 18 | "@cloudflare/itty-router-openapi": "^0.0.15", 19 | "chatgpt-plugin": "workspace:../../packages/chatgpt-plugin", 20 | "zod": "^3.21.4" 21 | }, 22 | "aiPlugin": { 23 | "name": "Dexa Lex Fridman", 24 | "description_for_model": "Plugin for searching transcripts from the Lex Fridman Podcast. Use it whenever a user asks about Lex Fridman, one of his guests, or the topics he covers on his podcast. Key topics include: science, math, physics, AI, AGI, judo, poker, evolution, programming, consciousness, technology, intelligence, deep learning, bitcoin, startups, and philosophy. Some example guests that you should use this plugin for include: Sam Altman, Jordan Peterson, Elon Musk, Joe Rogan, David Fravor, Andrew Bustamante, Ben Shapiro, Coffeezilla, Sam Harris, Donald Hoffman, Michael Saylor, Kanye, Vitalik Buterin, Mark Zuckerberb, MrBeast, Michio Kaku, Andrew Huberman, Stephen Wolfram, Ray Dalio, Grimes, Magnus Carlsen, Andrej Karpathy, Chamath Palihapitiya, Tim Urban, Noam Chomsky, Nick Rubin, Demis Hassabis, Jocko Willink, Georges St-Pierre, George Holtz, Chris Voss, Ray Kurzweil, and many more.", 25 | "logo_url": "https://assets.standardresume.co/image/upload/c_fill,w_392,h_392,f_auto,q_auto/dexa/accounts/lex", 26 | "contact_email": "team@dexa.ai", 27 | "legal_info_url": "https://dexa.ai" 28 | }, 29 | "keywords": [ 30 | "openai", 31 | "chatgpt", 32 | "plugin", 33 | "openapi", 34 | "cloudflare", 35 | "lex fridman", 36 | "podcast", 37 | "search" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /packages/chatgpt-plugin/src/ai-plugin.ts: -------------------------------------------------------------------------------- 1 | import slugify from '@sindresorhus/slugify' 2 | 3 | import * as types from './types' 4 | 5 | const preferredKeysOrder: (keyof types.AIPluginManifest)[] = [ 6 | 'schema_version', 7 | 'name_for_model', 8 | 'name_for_human', 9 | 'description_for_model', 10 | 'description_for_human', 11 | 'auth', 12 | 'api', 13 | 'logo_url', 14 | 'contact_email', 15 | 'legal_info_url' 16 | ] 17 | 18 | const preferredKeysOrderMap = preferredKeysOrder.reduce( 19 | (acc, key, i) => ({ ...acc, [key]: i }), 20 | {} as Record 21 | ) 22 | 23 | // TODO: better typing and validation 24 | export function defineAIPluginManifest( 25 | partialPluginManifest: Partial, 26 | opts: { openAPIUrl?: string } = {} 27 | ): types.AIPluginManifest { 28 | const { openAPIUrl } = opts 29 | 30 | const nameForModel = 31 | partialPluginManifest.name_for_model || 32 | slugify( 33 | partialPluginManifest.name_for_human || 34 | (partialPluginManifest as any).name || 35 | 'chatgpt test plugin', 36 | { separator: '_' } 37 | ) 38 | const nameForHuman = 39 | partialPluginManifest.name_for_human || 40 | (partialPluginManifest as any).name || 41 | 'chatgpt test plugin' 42 | 43 | const pluginManifest = { 44 | schema_version: 'v1', 45 | auth: { 46 | type: 'none' 47 | }, 48 | api: openAPIUrl 49 | ? { 50 | type: 'openapi', 51 | url: openAPIUrl, 52 | has_user_authentication: false 53 | } 54 | : undefined, 55 | ...partialPluginManifest, 56 | name_for_model: nameForModel, 57 | name_for_human: nameForHuman 58 | } as types.AIPluginManifest 59 | 60 | // ensure the manifest keys are always in a determinstic order 61 | const pluginManifestSorted = Object.fromEntries( 62 | Object.entries(pluginManifest).sort((a, b) => { 63 | const kA = preferredKeysOrderMap[a[0]] ?? Number.POSITIVE_INFINITY 64 | const kB = preferredKeysOrderMap[b[0]] ?? Number.POSITIVE_INFINITY 65 | 66 | return kA - kB 67 | }) 68 | ) as types.AIPluginManifest 69 | 70 | return pluginManifestSorted 71 | } 72 | -------------------------------------------------------------------------------- /packages/chatgpt-plugin/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ChatGPT Plugin TS 3 | 4 | 5 |

ChatGPT Plugin TS

6 | 7 |

8 | Types and utilities for building ChatGPT Plugins with TypeScript. 9 |

10 | 11 |

12 | chatgpt-plugin NPM package 13 | Build Status 14 | MIT License 15 | Prettier Code Formatting 16 |

17 | 18 | - [Intro](#intro) 19 | - [Install](#install) 20 | - [Usage](#usage) 21 | - [License](#license) 22 | 23 | ## Intro 24 | 25 | Currently, this package only contains types for `.well-known/ai-plugin.json` manifest files. We're actively working on adding more utilities to validate plugin manifests and OpenAPI specs. 26 | 27 | We'll also be consolidating best practices as we develop more plugins. 28 | 29 | The goals of this package are to: 30 | 31 | - Help developers build ChatGPT Plugins with TS 32 | - Distill best practices for building ChatGPT Plugins with TS 33 | - Be agnostic to the underlying server framework (e.g. itty-router, Express, Fastify, etc) 34 | - Be agnostic to the underlying hosting provider (e.g. Cloudflare, Vercel, AWS, etc) 35 | 36 | Framework-specific and hosting provider-specific examples can be found in the [example plugins](../../plugins) folder. 37 | 38 | See the [main readme](https://github.com/transitive-bullshit/chatgpt-plugin-ts) for more details. 39 | 40 | ## Install 41 | 42 | ```bash 43 | npm install chatgpt-plugin 44 | # or 45 | yarn add chatgpt-plugin 46 | # or 47 | pnpm install chatgpt-plugin 48 | ``` 49 | 50 | ## Usage 51 | 52 | ```ts 53 | import { 54 | type AIPluginManifest, 55 | defineAIPluginManifest, 56 | isValidChatGPTIPAddress 57 | } from 'chatgpt-plugin' 58 | 59 | // see the example plugins for more details 60 | ``` 61 | 62 | ## License 63 | 64 | MIT © [Travis Fischer](https://transitivebullsh.it) 65 | 66 | If you found this project interesting, please consider [sponsoring me](https://github.com/sponsors/transitive-bullshit) or following me on twitter twitter 67 | -------------------------------------------------------------------------------- /examples/ascii-art/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Enumeration, 3 | OpenAPIRoute, 4 | Query, 5 | Str 6 | } from '@cloudflare/itty-router-openapi' 7 | import figlet from 'figlet' 8 | 9 | figlet.defaults({ 10 | fontPath: 'https://www.unpkg.com/figlet@1.5.2/fonts' 11 | }) 12 | 13 | export class ASCIIArtRender extends OpenAPIRoute { 14 | static schema = { 15 | tags: ['figlet'], 16 | summary: 17 | 'Renders text as ASCII art. Output is a string that should be rendered a markdown code block. Do not specify a programming language for the code block; it is plain text.', 18 | parameters: { 19 | input: Query( 20 | new Str({ 21 | description: 'Input text to convert', 22 | example: 'boo' 23 | }), 24 | { 25 | required: true 26 | } 27 | ), 28 | font: Query(Enumeration, { 29 | description: 'Which ASCII art font to render', 30 | default: 'Ghost', 31 | required: false, 32 | enumCaseSensitive: true, 33 | values: { 34 | Standard: 'Standard', 35 | Ghost: 'Ghost', 36 | '3D Diagonal': '3D Diagonal', 37 | Graffiti: 'Graffiti', 38 | 'Dancing Font': 'Dancing Font', 39 | 'Big Money-ne': 'Big Money-ne', 40 | 'Big Money-nw': 'Big Money-nw', 41 | 'Big Money-se': 'Big Money-se', 42 | 'Big Money-sw': 'Big Money-sw', 43 | Big: 'Big', 44 | Bulbhead: 'Bulbhead', 45 | Doom: 'Doom', 46 | Isometric1: 'Isometric1', 47 | Slant: 'Slant', 48 | 'Sub-Zero': 'Sub-Zero', 49 | 'ANSI Regular': 'ANSI Regular', 50 | 'ANSI Shadow': 'ANSI Shadow', 51 | Bloody: 'Bloody', 52 | Alligator: 'Alligator', 53 | Alligator2: 'Alligator2', 54 | Ivrit: 'Ivrit', 55 | 'Larry 3D': 'Larry 3D', 56 | NScript: 'NScript', 57 | Poison: 'Poison', 58 | 'Small Poison': 'Small Poison' 59 | } 60 | }) 61 | }, 62 | responses: { 63 | '200': { 64 | schema: new Str({ 65 | example: ` 66 | _ _ _ _ __ __ _ _ _ _ 67 | | | | | ___| | | ___ \ \ / /__ _ __| | __| | | | 68 | | |_| |/ _ \ | |/ _ \ \ \ /\ / / _ \| '__| |/ _\` | | | 69 | | _ | __/ | | (_) | \ V V / (_) | | | | (_| |_|_| 70 | |_| |_|\___|_|_|\___/ \_/\_/ \___/|_| |_|\__,_(_|_) 71 | ` 72 | }) 73 | } 74 | } 75 | } 76 | 77 | async handle(request: Request, data: Record) { 78 | const url = new URL(request.url) 79 | const input = url.searchParams.get('input') || 'hello' 80 | const font: any = url.searchParams.get('font') || 'Ghost' 81 | 82 | const output: string = await new Promise((resolve, reject) => { 83 | figlet.text( 84 | input, 85 | { 86 | font 87 | }, 88 | (err, data) => { 89 | if (err) { 90 | reject(err) 91 | } else { 92 | resolve(data) 93 | } 94 | } 95 | ) 96 | }) 97 | 98 | return new Response(output) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /examples/dexa-lex-fridman/readme.md: -------------------------------------------------------------------------------- 1 | 3 | 4 | # ChatGPT Plugin Example - Dexa Lex Fridman Podcast 5 | 6 | > Example ChatGPT Plugin to search across all of the [Lex Fridman Podcast](https://lexfridman.com/podcast/) episodes – powered by [Dexa](https://dexa.ai) 🔥 7 | 8 | [![Build Status](https://github.com/transitive-bullshit/chatgpt-plugin-ts/actions/workflows/test.yml/badge.svg)](https://github.com/transitive-bullshit/chatgpt-plugin-ts/actions/workflows/test.yml) [![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/transitive-bullshit/chatgpt-plugin-ts/blob/main/license) [![Prettier Code Formatting](https://img.shields.io/badge/code_style-prettier-brightgreen.svg)](https://prettier.io) 9 | 10 | - [Intro](#intro) 11 | - [Stack](#stack) 12 | - [Dexa](#dexa) 13 | - [Lex Fridman Stats](#lex-fridman-stats) 14 | - [Examples](#examples) 15 | - [License](#license) 16 | 17 | ## Intro 18 | 19 | This is a ChatGPT retrieval plugin which gives ChatGPT access to all of the transcriptions across 360+ episodes of the [Lex Fridman Podcast](https://lexfridman.com/podcast/). 20 | 21 | It contains a single API route. 22 | 23 | More details can be found in the [twitter launch thread](https://twitter.com/transitive_bs/status/1643990888417464332). 24 | 25 | ## Stack 26 | 27 | - [Dexa API](https://dexa.ai/) (currently in private beta) 28 | - [Cloudflare Workers](https://workers.cloudflare.com/) 29 | - [TypeScript](https://www.typescriptlang.org/) 30 | - [@cloudflare/itty-router-openapi](https://github.com/cloudflare/itty-router-openapi) 31 | 32 | Here's an example [live manifest file](https://chatgpt-plugin-dexa-lex-fridman.transitive-bullshit.workers.dev/.well-known/ai-plugin.json). API endpoints are protected so only OpenAI can access them. 33 | 34 | ## Dexa 35 | 36 | [Dexa](https://dexa.ai) has already done all the hard work of aggregating, transcribing, processing, and indexing the Lex Fridman Podcast (and many other podcasts!). 37 | 38 | Under the hood, they're doing **a lot** of really awesome, AI-powered data processing: 39 | 40 | - Transcriptions with speaker labels (diarization) for attribution (using [Assembly](https://assemblyai.com)) 41 | - Automatic post-processing for common transcription errors 42 | - Advanced chunking based on metadata, topic detection, and sentence structure 43 | - Metadata extraction and enrichment with support for photos of speakers 44 | - Heirarchical clustering and summarization 45 | 46 | ### Lex Fridman Stats 47 | 48 | Lex Fridman Podcast stats from [Dexa](https://dexa.ai/) as of April 5, 2023: 49 | 50 | - Total number of episodes: 364 51 | - Total characters transcribed: 45,005,793 52 | - Total audio length: ~36 days 53 | 54 | ## Examples 55 | 56 |

57 | What advice does Lex's podcast have for young people? 58 |     59 | 60 | What is Elon Musk's philosophy on life? 61 |

62 | 63 |

64 | What do poker and physics have in common? 65 |     66 | 67 | What do Lex's guests think about love? 68 |

69 | 70 | ## License 71 | 72 | MIT © [Travis Fischer](https://transitivebullsh.it) 73 | 74 | If you found this project interesting, please consider [sponsoring me](https://github.com/sponsors/transitive-bullshit) or following me on twitter twitter 75 | -------------------------------------------------------------------------------- /examples/dexa-mfm/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIRoute, Query, Str } from '@cloudflare/itty-router-openapi' 2 | import { isValidChatGPTIPAddress } from 'chatgpt-plugin' 3 | 4 | import * as types from './types' 5 | import { omit } from './utils' 6 | 7 | export class DexaSearch extends OpenAPIRoute { 8 | static schema = { 9 | tags: ['dexa'], 10 | summary: 11 | 'Searches the MFM podcast for any topic and returns the most relevant results as conversation transcripts. Multiple conversation transcripts can be combined to form a summary. Always cite your sources when using this API via the citationUrl.', 12 | 13 | parameters: { 14 | query: Query( 15 | new Str({ 16 | description: 'Search query', 17 | example: 'side hustle' 18 | }), 19 | { 20 | required: true 21 | } 22 | ) 23 | }, 24 | responses: { 25 | '200': { 26 | schema: { 27 | results: [ 28 | { 29 | content: new Str({ 30 | description: 31 | 'The main content of this conversation transcript with speaker labels' 32 | }), 33 | episodeTitle: new Str({ 34 | description: 35 | 'Title of the podcast episode this conversation is from' 36 | }), 37 | chapterTitle: new Str({ 38 | description: 'Title of the chapter this conversation is from' 39 | }), 40 | // peopleNames: [ 41 | // new Str({ 42 | // description: 43 | // 'Names of the person (or people) present in the conversation' 44 | // }) 45 | // ], 46 | citationUrl: new Str({ 47 | description: 48 | 'URL citation linking to the source of this conversation. Use this URL to cite this conversation in answers.' 49 | }) 50 | } 51 | ] 52 | } 53 | } 54 | } 55 | } 56 | 57 | async handle(request: Request, env: any, _ctx, data: Record) { 58 | const dexaApiBaseUrl = env.DEXA_API_BASE_URL 59 | if (!dexaApiBaseUrl) { 60 | return new Response('DEXA_API_BASE_URL not set', { status: 500 }) 61 | } 62 | 63 | const ip = request.headers.get('Cf-Connecting-Ip') 64 | if (!ip) { 65 | console.warn('search error missing IP address') 66 | return new Response('invalid source IP', { status: 500 }) 67 | } 68 | 69 | if (env.ENVIRONMENT === 'production' && !isValidChatGPTIPAddress(ip)) { 70 | // console.warn('search error invalid IP address', ip) 71 | return new Response(`Forbidden`, { status: 403 }) 72 | } 73 | 74 | const openaiUserLocaleInfo = request.headers.get( 75 | 'openai-subdivision-1-iso-code' 76 | ) 77 | const { query } = data 78 | console.log() 79 | console.log() 80 | console.log('>>> search', `${query} (${openaiUserLocaleInfo}, ${ip})`) 81 | console.log() 82 | 83 | const url = `${dexaApiBaseUrl}/api/query-mfm` 84 | const body = types.DexaSearchRequestBodySchema.parse({ 85 | query, 86 | // NOTE: I tried testing with returning 10 results, but ChatGPT would frequently 87 | // stop generating it's response in the middle of an answer, so I'm guessing the 88 | // returned results were too long and ChatGPT was hitting the max token limit 89 | // abruptly. I haven't been able to reproduce this but for `topK: 5` so far. 90 | topK: 5 91 | }) 92 | 93 | const { results }: types.DexaSearchResponseBody = await fetch(url, { 94 | method: 'POST', 95 | body: JSON.stringify(body), 96 | headers: { 97 | 'content-type': 'application/json' 98 | } 99 | }).then((res) => { 100 | if (!res.ok) { 101 | throw new Error(`Dexa API error: ${res.statusText}`) 102 | } 103 | 104 | return res.json() 105 | }) 106 | 107 | console.log( 108 | `search results for query "${query}"`, 109 | results.map((r) => ({ 110 | ...omit(r, 'content') 111 | })) 112 | ) 113 | console.log() 114 | console.log() 115 | console.log('<<< search', `${query} (${openaiUserLocaleInfo}, ${ip})`) 116 | 117 | const responseBody = { 118 | results 119 | } 120 | 121 | return new Response(JSON.stringify(responseBody, null, 2), { 122 | headers: { 123 | 'content-type': 'application/json;charset=UTF-8' 124 | } 125 | }) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /examples/dexa-lex-fridman/src/routes.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIRoute, Query, Str } from '@cloudflare/itty-router-openapi' 2 | import { isValidChatGPTIPAddress } from 'chatgpt-plugin' 3 | 4 | import * as types from './types' 5 | import { omit } from './utils' 6 | 7 | export class DexaSearch extends OpenAPIRoute { 8 | static schema = { 9 | tags: ['dexa'], 10 | summary: 11 | 'Searches the Lex Fridman podcast for any topic and returns the most relevant results as conversation transcripts. Multiple conversation transcripts can be combined to form a summary. Always cite your sources when using this API using the citationUrl.', 12 | parameters: { 13 | query: Query( 14 | new Str({ 15 | description: 'Search query', 16 | example: 'elon musk tesla' 17 | }), 18 | { 19 | required: true 20 | } 21 | ) 22 | }, 23 | responses: { 24 | '200': { 25 | schema: { 26 | results: [ 27 | { 28 | content: new Str({ 29 | description: 30 | 'The main content of this conversation transcript with speaker labels' 31 | }), 32 | episodeTitle: new Str({ 33 | description: 34 | 'Title of the podcast episode this conversation is from' 35 | }), 36 | chapterTitle: new Str({ 37 | description: 'Title of the chapter this conversation is from' 38 | }), 39 | // peopleNames: [ 40 | // new Str({ 41 | // description: 42 | // 'Names of the person (or people) present in the conversation' 43 | // }) 44 | // ], 45 | citationUrl: new Str({ 46 | description: 47 | 'URL citation linking to the source of this conversation. Use this URL to cite this conversation in answers.', 48 | example: 49 | 'https://dexa.ai/lex/episodes/doc_358?sectionSid=sec_5319&chunkSid=chunk_9725' 50 | }) 51 | } 52 | ] 53 | } 54 | } 55 | } 56 | } 57 | 58 | async handle(request: Request, env: any, _ctx, data: Record) { 59 | const dexaApiBaseUrl = env.DEXA_API_BASE_URL 60 | if (!dexaApiBaseUrl) { 61 | return new Response('DEXA_API_BASE_URL not set', { status: 500 }) 62 | } 63 | 64 | const ip = request.headers.get('Cf-Connecting-Ip') 65 | if (!ip) { 66 | console.warn('search error missing IP address') 67 | return new Response('invalid source IP', { status: 500 }) 68 | } 69 | 70 | if (!isValidChatGPTIPAddress(ip)) { 71 | // console.warn('search error invalid IP address', ip) 72 | return new Response(`Forbidden`, { status: 403 }) 73 | } 74 | 75 | const openaiUserLocaleInfo = request.headers.get( 76 | 'openai-subdivision-1-iso-code' 77 | ) 78 | const { query } = data 79 | console.log() 80 | console.log() 81 | console.log('>>> search', `${query} (${openaiUserLocaleInfo}, ${ip})`) 82 | console.log() 83 | 84 | const url = `${dexaApiBaseUrl}/api/query` 85 | const body = types.DexaSearchRequestBodySchema.parse({ 86 | query, 87 | // NOTE: I tried testing with returning 10 results, but ChatGPT would frequently 88 | // stop generating it's response in the middle of an answer, so I'm guessing the 89 | // returned results were too long and ChatGPT was hitting the max token limit 90 | // abruptly. I haven't been able to reproduce this but for `topK: 5` so far. 91 | topK: 5 92 | }) 93 | 94 | const { results }: types.DexaSearchResponseBody = await fetch(url, { 95 | method: 'POST', 96 | body: JSON.stringify(body), 97 | headers: { 98 | 'content-type': 'application/json' 99 | } 100 | }).then((res) => { 101 | if (!res.ok) { 102 | throw new Error(`Dexa API error: ${res.statusText}`) 103 | } 104 | 105 | return res.json() 106 | }) 107 | 108 | console.log( 109 | `search results for query "${query}"`, 110 | results.map((r) => ({ 111 | ...omit(r, 'content') 112 | })) 113 | ) 114 | console.log() 115 | console.log() 116 | console.log('<<< search', `${query} (${openaiUserLocaleInfo}, ${ip})`) 117 | 118 | const responseBody = { 119 | results 120 | } 121 | 122 | return new Response(JSON.stringify(responseBody, null, 2), { 123 | headers: { 124 | 'content-type': 'application/json;charset=UTF-8' 125 | } 126 | }) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | ChatGPT Plugin TS 3 | 4 | 5 |

ChatGPT Plugin TS

6 | 7 |

8 | Examples and resources for creating ChatGPT plugins in TypeScript. 9 |

10 | 11 |

12 | chatgpt-plugin NPM package 13 | Build Status 14 | MIT License 15 | Prettier Code Formatting 16 |

17 | 18 | - [Intro](#intro) 19 | - [Install](#install) 20 | - [Example Plugins](#example-plugins) 21 | - [Dexa Lex Fridman Plugin](#dexa-lex-fridman-plugin) 22 | - [ASCII Art Plugin](#ascii-art-plugin) 23 | - [Notes](#notes) 24 | - [License](#license) 25 | 26 | ## Intro 27 | 28 | This repo contains the [chatgpt-plugin NPM package](./packages/chatgpt-plugin), with TS types and utilities for building ChatGPT Plugins with TypeScript. 29 | 30 | It also contains several high quality example plugins that you can use as a template for building your own plugins. The goal is to add more examples using different OpenAPI frameworks and hosting providers over time. Currently, all of the examples use Cloudflare Workers, but I'll add an example using Vercel serverless functions soon. 31 | 32 | If there's something missing that you'd like to see, please [open an issue](https://github.com/transitive-bullshit/chatgpt-plugin-ts/issues/new) or join our [ChatGPT Hackers community](https://www.chatgpthackers.dev/) on Discord, with over 8000 developers who are building cool stuff with AI! 33 | 34 | ## Install 35 | 36 | ```bash 37 | npm install chatgpt-plugin 38 | # or 39 | yarn add chatgpt-plugin 40 | # or 41 | pnpm install chatgpt-plugin 42 | ``` 43 | 44 | ## Config 45 | Start by adding your aiPlugin configuration into the `package.json` file like so: 46 | (see full instructions on [OpenAi docs](https://platform.openai.com/docs/plugins/getting-started/plugin-manifest)) 47 | ```json 48 | "aiPlugin": { 49 | "name": "plugin_name", 50 | "description_for_model": "description_for_model", 51 | "logo_url": "hosted logo url", 52 | "contact_email": "your email", 53 | "legal_info_url": "legal info link" 54 | } 55 | ``` 56 | 57 | 58 | ## Example Plugins 59 | 60 | TS code for all example plugins can be found in the [examples](/examples) directory. 61 | 62 | ### Dexa Lex Fridman Plugin 63 | 64 | Example ChatGPT retrieval plugin to search across all of the [Lex Fridman Podcast](https://lexfridman.com/podcast/) episodes – powered by [Dexa AI](https://dexa.ai). 65 | 66 | - [source code](/examples/dexa-lex-fridman) 67 | - [launch tweet](https://twitter.com/transitive_bs/status/1643990888417464332) 68 | - built using CF workers 69 | 70 |

71 | What advice does Lex's podcast have for young people? 72 |     73 | 74 | What is Elon Musk's philosophy on life? 75 |

76 | 77 |

78 | What do poker and physics have in common? 79 |     80 | 81 | What do Lex's guests think about love? 82 |

83 | 84 | ### ASCII Art Plugin 85 | 86 | This is a really simple example plugin that converts text to ASCII art. It's a great template to start building your own plugins. 87 | 88 | - [source code](/examples/ascii-art/) 89 | - [launch tweet](https://twitter.com/transitive_bs/status/1643144204900597760) 90 | - built using CF workers 91 | 92 |

93 | ASCII art using the Poison font 94 |

95 | 96 | ## Notes 97 | 98 | - `name_for_human` 99 | - 30 character max 100 | - `name_for_model` 101 | - 50 character max 102 | - `description_for_human` 103 | - 120 character max 104 | - `description_for_model` 105 | - 8000 character max 106 | - Max decreases over time 107 | - API response body length 108 | - 100k character limit 109 | - Decreases over time 110 | - Subject to limitations 111 | - TODO: `defineConfig` function to help validate `ai-plugin.json` configs? 112 | 113 | ## License 114 | 115 | MIT © [Travis Fischer](https://transitivebullsh.it) 116 | 117 | If you found this project interesting, please consider [sponsoring me](https://github.com/sponsors/transitive-bullshit) or following me on twitter twitter 118 | --------------------------------------------------------------------------------