├── .gitignore ├── LICENSE ├── README.md ├── simple-sports-stats ├── Dockerfile ├── README.md ├── manifest.json ├── openapi.yaml ├── requirements.txt └── serve.py ├── simple-todo-no-auth ├── Dockerfile ├── README.md ├── manifest.json ├── openapi.yaml ├── requirements.txt └── serve.py └── simple-todo-service-auth ├── Dockerfile ├── README.md ├── manifest.json ├── openapi.yaml ├── requirements.txt └── serve.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Python files 2 | *.pyc 3 | __pycache__/ 4 | 5 | # Virtual environment 6 | venv/ 7 | env/ 8 | env.bak/ 9 | *.egg-info/ 10 | 11 | # Logs 12 | *.log 13 | 14 | # Other 15 | .idea/ 16 | .vscode/ 17 | *.DS_Store 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 breadchris 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatGPT Plugin Examples 2 | This repository contains examples of ChatGPT plugins that demonstrate how to build and deploy plugins on the OpenAI platform. The examples were taken from the [OpenAI Platform documentation](https://platform.openai.com/docs/plugins/examples) and have been added to this repository to make it easier to read and use. 3 | 4 | Each example includes a Dockerfile to help with deployment of the plugin. You can use the Dockerfile to build and run the plugin locally or deploy it to a remote server. 5 | 6 | ## Quick Start 7 | ``` 8 | git clone https://github.com/breadchris/chatgpt-plugin-examples.git 9 | cd chatgpt-plugin-examples/simple-todo-no-auth 10 | python -m venv env 11 | . env/bin/activate 12 | pip install -r requirements.txt 13 | python serve.py 14 | ``` 15 | 16 | ## Examples 17 | Each example is contained in its own directory with its own README.md file. The README.md file provides instructions on how to build and run the plugin using the Dockerfile. 18 | 19 | - [Simple todo list plugin with no auth](simple-todo-no-auth/) 20 | - [Simple todo list plugin with service level auth](simple-todo-service-auth/) 21 | - [Simple sports stats plugin](sports-stats-plugin/) 22 | - Semantic search and retrieval plugin (contained in its [own repo](https://github.com/openai/chatgpt-retrieval-plugin)) 23 | 24 | ## Contributing 25 | If you have a new example that you would like to contribute, please fork this repository and submit a pull request with your changes. We welcome contributions from the community! 26 | 27 | ## License 28 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 29 | -------------------------------------------------------------------------------- /simple-sports-stats/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | 3 | ENV PROJECT_DIR /usr/local/src/webapp 4 | 5 | WORKDIR ${PROJECT_DIR} 6 | 7 | COPY requirements.txt ${PROJECT_DIR}/ 8 | 9 | RUN pip install -r requirements.txt 10 | 11 | COPY . ${PROJECT_DIR} 12 | 13 | EXPOSE 8080 14 | 15 | CMD ["python", "server.py"] 16 | -------------------------------------------------------------------------------- /simple-sports-stats/README.md: -------------------------------------------------------------------------------- 1 | # Simple Sports Stats 2 | 3 | ## Run 4 | ```bash 5 | python -m venv env 6 | . env/bin/activate 7 | pip install -r requirements.txt 8 | python serve.py 9 | ``` 10 | 11 | ## Build and Run with Docker 12 | ```shell 13 | docker build . -t simple-sports-stats 14 | docker run -it --rm -p 8080:8080 simple-sports-stats 15 | ``` 16 | -------------------------------------------------------------------------------- /simple-sports-stats/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema_version": "v1", 3 | "name_for_human": "Sport Stats", 4 | "name_for_model": "sportStats", 5 | "description_for_human": "Get current and historical stats for sport players and games.", 6 | "description_for_model": "Get current and historical stats for sport players and games. Always display results using markdown tables.", 7 | "auth": { 8 | "type": "none" 9 | }, 10 | "api": { 11 | "type": "openapi", 12 | "url": "PLUGIN_HOSTNAME/openapi.yaml", 13 | "is_user_authenticated": false 14 | }, 15 | "logo_url": "PLUGIN_HOSTNAME/logo.png", 16 | "contact_email": "dummy@email.com", 17 | "legal_info_url": "http://www.example.com/legal" 18 | } 19 | -------------------------------------------------------------------------------- /simple-sports-stats/openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.1 2 | info: 3 | title: Sport Stats 4 | description: Get current and historical stats for sport players and games. 5 | version: 'v1' 6 | servers: 7 | - url: PLUGIN_HOSTNAME 8 | paths: 9 | /players: 10 | get: 11 | operationId: getPlayers 12 | summary: Retrieves all players from all seasons whose names match the query string. 13 | parameters: 14 | - in: query 15 | name: query 16 | schema: 17 | type: string 18 | description: Used to filter players based on their name. For example, ?query=davis will return players that have 'davis' in their first or last name. 19 | responses: 20 | "200": 21 | description: OK 22 | /teams: 23 | get: 24 | operationId: getTeams 25 | summary: Retrieves all teams for the current season. 26 | responses: 27 | "200": 28 | description: OK 29 | /games: 30 | get: 31 | operationId: getGames 32 | summary: Retrieves all games that match the filters specified by the args. Display results using markdown tables. 33 | parameters: 34 | - in: query 35 | name: limit 36 | schema: 37 | type: string 38 | description: The max number of results to return. 39 | - in: query 40 | name: seasons 41 | schema: 42 | type: array 43 | items: 44 | type: string 45 | description: Filter by seasons. Seasons are represented by the year they began. For example, 2018 represents season 2018-2019. 46 | - in: query 47 | name: team_ids 48 | schema: 49 | type: array 50 | items: 51 | type: string 52 | description: Filter by team ids. Team ids can be determined using the getTeams function. 53 | - in: query 54 | name: start_date 55 | schema: 56 | type: string 57 | description: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or after this date. 58 | - in: query 59 | name: end_date 60 | schema: 61 | type: string 62 | description: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or before this date. 63 | responses: 64 | "200": 65 | description: OK 66 | /stats: 67 | get: 68 | operationId: getStats 69 | summary: Retrieves stats that match the filters specified by the args. Display results using markdown tables. 70 | parameters: 71 | - in: query 72 | name: limit 73 | schema: 74 | type: string 75 | description: The max number of results to return. 76 | - in: query 77 | name: player_ids 78 | schema: 79 | type: array 80 | items: 81 | type: string 82 | description: Filter by player ids. Player ids can be determined using the getPlayers function. 83 | - in: query 84 | name: game_ids 85 | schema: 86 | type: array 87 | items: 88 | type: string 89 | description: Filter by game ids. Game ids can be determined using the getGames function. 90 | - in: query 91 | name: start_date 92 | schema: 93 | type: string 94 | description: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or after this date. 95 | - in: query 96 | name: end_date 97 | schema: 98 | type: string 99 | description: A single date in 'YYYY-MM-DD' format. This is used to select games that occur on or before this date. 100 | responses: 101 | "200": 102 | description: OK 103 | /season_averages: 104 | get: 105 | operationId: getSeasonAverages 106 | summary: Retrieves regular season averages for the given players. Display results using markdown tables. 107 | parameters: 108 | - in: query 109 | name: season 110 | schema: 111 | type: string 112 | description: Defaults to the current season. A season is represented by the year it began. For example, 2018 represents season 2018-2019. 113 | - in: query 114 | name: player_ids 115 | schema: 116 | type: array 117 | items: 118 | type: string 119 | description: Filter by player ids. Player ids can be determined using the getPlayers function. 120 | responses: 121 | "200": 122 | description: OK 123 | -------------------------------------------------------------------------------- /simple-sports-stats/requirements.txt: -------------------------------------------------------------------------------- 1 | quart 2 | quart_cors 3 | requests 4 | -------------------------------------------------------------------------------- /simple-sports-stats/serve.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | import urllib.parse 4 | 5 | import quart 6 | import quart_cors 7 | from quart import request 8 | 9 | app = quart_cors.cors(quart.Quart(__name__), allow_origin="*") 10 | HOST_URL = "https://example.com" 11 | 12 | @app.get("/players") 13 | async def get_players(): 14 | query = request.args.get("query") 15 | res = requests.get( 16 | f"{HOST_URL}/api/v1/players?search={query}&page=0&per_page=100") 17 | body = res.json() 18 | return quart.Response(response=json.dumps(body), status=200) 19 | 20 | 21 | @app.get("/teams") 22 | async def get_teams(): 23 | res = requests.get( 24 | "{HOST_URL}/api/v1/teams?page=0&per_page=100") 25 | body = res.json() 26 | return quart.Response(response=json.dumps(body), status=200) 27 | 28 | 29 | @app.get("/games") 30 | async def get_games(): 31 | query_params = [("page", "0")] 32 | limit = request.args.get("limit") 33 | query_params.append(("per_page", limit or "100")) 34 | start_date = request.args.get("start_date") 35 | if start_date: 36 | query_params.append(("start_date", start_date)) 37 | end_date = request.args.get("end_date") 38 | 39 | if end_date: 40 | query_params.append(("end_date", end_date)) 41 | seasons = request.args.getlist("seasons") 42 | 43 | for season in seasons: 44 | query_params.append(("seasons[]", str(season))) 45 | team_ids = request.args.getlist("team_ids") 46 | 47 | for team_id in team_ids: 48 | query_params.append(("team_ids[]", str(team_id))) 49 | print(query_params) 50 | 51 | res = requests.get( 52 | f"{HOST_URL}/api/v1/games?{urllib.parse.urlencode(query_params)}") 53 | body = res.json() 54 | return quart.Response(response=json.dumps(body), status=200) 55 | 56 | 57 | @app.get("/stats") 58 | async def get_stats(): 59 | query_params = [("page", "0")] 60 | limit = request.args.get("limit") 61 | query_params.append(("per_page", limit or "100")) 62 | start_date = request.args.get("start_date") 63 | if start_date: 64 | query_params.append(("start_date", start_date)) 65 | end_date = request.args.get("end_date") 66 | 67 | if end_date: 68 | query_params.append(("end_date", end_date)) 69 | player_ids = request.args.getlist("player_ids") 70 | 71 | for player_id in player_ids: 72 | query_params.append(("player_ids[]", str(player_id))) 73 | game_ids = request.args.getlist("game_ids") 74 | 75 | for game_id in game_ids: 76 | query_params.append(("game_ids[]", str(game_id))) 77 | res = requests.get( 78 | f"{HOST_URL}/api/v1/stats?{urllib.parse.urlencode(query_params)}") 79 | body = res.json() 80 | return quart.Response(response=json.dumps(body), status=200) 81 | 82 | 83 | @app.get("/season_averages") 84 | async def get_season_averages(): 85 | query_params = [] 86 | season = request.args.get("season") 87 | if season: 88 | query_params.append(("season", str(season))) 89 | player_ids = request.args.getlist("player_ids") 90 | 91 | for player_id in player_ids: 92 | query_params.append(("player_ids[]", str(player_id))) 93 | res = requests.get( 94 | f"{HOST_URL}/api/v1/season_averages?{urllib.parse.urlencode(query_params)}") 95 | body = res.json() 96 | return quart.Response(response=json.dumps(body), status=200) 97 | 98 | 99 | @app.get("/logo.png") 100 | async def plugin_logo(): 101 | filename = 'logo.png' 102 | return await quart.send_file(filename, mimetype='image/png') 103 | 104 | 105 | @app.get("/.well-known/ai-plugin.json") 106 | async def plugin_manifest(): 107 | host = request.headers['Host'] 108 | with open("manifest.json") as f: 109 | text = f.read() 110 | text = text.replace("PLUGIN_HOSTNAME", f"https://{host}") 111 | return quart.Response(text, mimetype="text/json") 112 | 113 | 114 | @app.get("/openapi.yaml") 115 | async def openapi_spec(): 116 | host = request.headers['Host'] 117 | with open("openapi.yaml") as f: 118 | text = f.read() 119 | text = text.replace("PLUGIN_HOSTNAME", f"https://{host}") 120 | return quart.Response(text, mimetype="text/yaml") 121 | 122 | 123 | def main(): 124 | app.run(debug=True, host="0.0.0.0", port=5001) 125 | 126 | 127 | if __name__ == "__main__": 128 | main() 129 | -------------------------------------------------------------------------------- /simple-todo-no-auth/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | 3 | ENV PROJECT_DIR /usr/local/src/webapp 4 | 5 | WORKDIR ${PROJECT_DIR} 6 | 7 | COPY requirements.txt ${PROJECT_DIR}/ 8 | 9 | RUN pip install -r requirements.txt 10 | 11 | COPY . ${PROJECT_DIR} 12 | 13 | EXPOSE 8080 14 | 15 | CMD ["python", "server.py"] 16 | -------------------------------------------------------------------------------- /simple-todo-no-auth/README.md: -------------------------------------------------------------------------------- 1 | # Simple TODO plugin, No Auth 2 | 3 | ## Run 4 | ```bash 5 | python -m venv env 6 | . env/bin/activate 7 | pip install -r requirements.txt 8 | python serve.py 9 | ``` 10 | 11 | ## Build and Run with Docker 12 | ```shell 13 | docker build . -t simple-todo-plugin-no-auth 14 | docker run -it --rm -p 8080:8080 simple-todo-plugin-no-auth 15 | ``` 16 | -------------------------------------------------------------------------------- /simple-todo-no-auth/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema_version": "v1", 3 | "name_for_human": "TODO Plugin (no auth)", 4 | "name_for_model": "todo", 5 | "description_for_human": "Plugin for managing a TODO list, you can add, remove and view your TODOs.", 6 | "description_for_model": "Plugin for managing a TODO list, you can add, remove and view your TODOs.", 7 | "auth": { 8 | "type": "none" 9 | }, 10 | "api": { 11 | "type": "openapi", 12 | "url": "PLUGIN_HOSTNAME/openapi.yaml", 13 | "is_user_authenticated": false 14 | }, 15 | "logo_url": "PLUGIN_HOSTNAME/logo.png", 16 | "contact_email": "dummy@email.com", 17 | "legal_info_url": "http://www.example.com/legal" 18 | } 19 | -------------------------------------------------------------------------------- /simple-todo-no-auth/openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.1 2 | info: 3 | title: TODO Plugin 4 | description: A plugin that allows the user to create and manage a TODO list using ChatGPT. If you do not know the user's username, ask them first before making queries to the plugin. Otherwise, use the username "global". 5 | version: 'v1' 6 | servers: 7 | - url: PLUGIN_HOSTNAME 8 | paths: 9 | /todos/{username}: 10 | get: 11 | operationId: getTodos 12 | summary: Get the list of todos 13 | parameters: 14 | - in: path 15 | name: username 16 | schema: 17 | type: string 18 | required: true 19 | description: The name of the user. 20 | responses: 21 | "200": 22 | description: OK 23 | content: 24 | application/json: 25 | schema: 26 | $ref: '#/components/schemas/getTodosResponse' 27 | post: 28 | operationId: addTodo 29 | summary: Add a todo to the list 30 | parameters: 31 | - in: path 32 | name: username 33 | schema: 34 | type: string 35 | required: true 36 | description: The name of the user. 37 | requestBody: 38 | required: true 39 | content: 40 | application/json: 41 | schema: 42 | $ref: '#/components/schemas/addTodoRequest' 43 | responses: 44 | "200": 45 | description: OK 46 | delete: 47 | operationId: deleteTodo 48 | summary: Delete a todo from the list 49 | parameters: 50 | - in: path 51 | name: username 52 | schema: 53 | type: string 54 | required: true 55 | description: The name of the user. 56 | requestBody: 57 | required: true 58 | content: 59 | application/json: 60 | schema: 61 | $ref: '#/components/schemas/deleteTodoRequest' 62 | responses: 63 | "200": 64 | description: OK 65 | 66 | components: 67 | schemas: 68 | getTodosResponse: 69 | type: object 70 | properties: 71 | todos: 72 | type: array 73 | items: 74 | type: string 75 | description: The list of todos. 76 | addTodoRequest: 77 | type: object 78 | required: 79 | - todo 80 | properties: 81 | todo: 82 | type: string 83 | description: The todo to add to the list. 84 | required: true 85 | deleteTodoRequest: 86 | type: object 87 | required: 88 | - todo_idx 89 | properties: 90 | todo_idx: 91 | type: integer 92 | description: The index of the todo to delete. 93 | required: true 94 | -------------------------------------------------------------------------------- /simple-todo-no-auth/requirements.txt: -------------------------------------------------------------------------------- 1 | quart 2 | quart_cors 3 | -------------------------------------------------------------------------------- /simple-todo-no-auth/serve.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import quart 4 | import quart_cors 5 | from quart import request 6 | 7 | app = quart_cors.cors(quart.Quart(__name__), allow_origin="*") 8 | 9 | _TODOS = {} 10 | 11 | 12 | @app.post("/todos/") 13 | async def add_todo(username): 14 | request = await quart.request.get_json(force=True) 15 | if username not in _TODOS: 16 | _TODOS[username] = [] 17 | _TODOS[username].append(request["todo"]) 18 | return quart.Response(response='OK', status=200) 19 | 20 | 21 | @app.get("/todos/") 22 | async def get_todos(username): 23 | return quart.Response(response=json.dumps(_TODOS.get(username, [])), status=200) 24 | 25 | 26 | @app.delete("/todos/") 27 | async def delete_todo(username): 28 | request = await quart.request.get_json(force=True) 29 | todo_idx = request["todo_idx"] 30 | # fail silently, it's a simple plugin 31 | if 0 <= todo_idx < len(_TODOS[username]): 32 | _TODOS[username].pop(todo_idx) 33 | return quart.Response(response='OK', status=200) 34 | 35 | 36 | @app.get("/logo.png") 37 | async def plugin_logo(): 38 | filename = 'logo.png' 39 | return await quart.send_file(filename, mimetype='image/png') 40 | 41 | 42 | @app.get("/.well-known/ai-plugin.json") 43 | async def plugin_manifest(): 44 | host = request.headers['Host'] 45 | with open("manifest.json") as f: 46 | text = f.read() 47 | text = text.replace("PLUGIN_HOSTNAME", f"https://{host}") 48 | return quart.Response(text, mimetype="text/json") 49 | 50 | 51 | @app.get("/openapi.yaml") 52 | async def openapi_spec(): 53 | host = request.headers['Host'] 54 | with open("openapi.yaml") as f: 55 | text = f.read() 56 | text = text.replace("PLUGIN_HOSTNAME", f"https://{host}") 57 | return quart.Response(text, mimetype="text/yaml") 58 | 59 | 60 | def main(): 61 | app.run(debug=True, host="0.0.0.0", port=5002) 62 | 63 | 64 | if __name__ == "__main__": 65 | main() 66 | -------------------------------------------------------------------------------- /simple-todo-service-auth/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | 3 | ENV PROJECT_DIR /usr/local/src/webapp 4 | 5 | WORKDIR ${PROJECT_DIR} 6 | 7 | COPY requirements.txt ${PROJECT_DIR}/ 8 | 9 | RUN pip install -r requirements.txt 10 | 11 | COPY . ${PROJECT_DIR} 12 | 13 | EXPOSE 8080 14 | 15 | CMD ["python", "server.py"] 16 | -------------------------------------------------------------------------------- /simple-todo-service-auth/README.md: -------------------------------------------------------------------------------- 1 | # Simple TODO plugin, Service Auth 2 | 3 | ## Run 4 | ```bash 5 | python -m venv env 6 | . env/bin/activate 7 | pip install -r requirements.txt 8 | python serve.py 9 | ``` 10 | 11 | ## Build and Run with Docker 12 | ```shell 13 | docker build . -t simple-todo-plugin-service-auth 14 | docker run -it --rm -p 8080:8080 simple-todo-plugin-service-auth 15 | ``` 16 | -------------------------------------------------------------------------------- /simple-todo-service-auth/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema_version": "v1", 3 | "name_for_human": "TODO Plugin (service http)", 4 | "name_for_model": "todo", 5 | "description_for_human": "Plugin for managing a TODO list, you can add, remove and view your TODOs.", 6 | "description_for_model": "Plugin for managing a TODO list, you can add, remove and view your TODOs.", 7 | "auth": { 8 | "type": "service_http", 9 | "authorization_type": "bearer", 10 | "verification_tokens": { 11 | "openai": "758e9ef7984b415688972d749f8aa58e" 12 | } 13 | }, 14 | "api": { 15 | "type": "openapi", 16 | "url": "https://example.com/openapi.yaml", 17 | "is_user_authenticated": false 18 | }, 19 | "logo_url": "https://example.com/logo.png", 20 | "contact_email": "dummy@email.com", 21 | "legal_info_url": "http://www.example.com/legal" 22 | } 23 | -------------------------------------------------------------------------------- /simple-todo-service-auth/openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.1 2 | info: 3 | title: TODO Plugin 4 | description: A plugin that allows the user to create and manage a TODO list using ChatGPT. If you do not know the user's username, ask them first before making queries to the plugin. Otherwise, use the username "global". 5 | version: 'v1' 6 | servers: 7 | - url: https://example.com 8 | paths: 9 | /todos/{username}: 10 | get: 11 | operationId: getTodos 12 | summary: Get the list of todos 13 | parameters: 14 | - in: path 15 | name: username 16 | schema: 17 | type: string 18 | required: true 19 | description: The name of the user. 20 | responses: 21 | "200": 22 | description: OK 23 | content: 24 | application/json: 25 | schema: 26 | $ref: '#/components/schemas/getTodosResponse' 27 | post: 28 | operationId: addTodo 29 | summary: Add a todo to the list 30 | parameters: 31 | - in: path 32 | name: username 33 | schema: 34 | type: string 35 | required: true 36 | description: The name of the user. 37 | requestBody: 38 | required: true 39 | content: 40 | application/json: 41 | schema: 42 | $ref: '#/components/schemas/addTodoRequest' 43 | responses: 44 | "200": 45 | description: OK 46 | delete: 47 | operationId: deleteTodo 48 | summary: Delete a todo from the list 49 | parameters: 50 | - in: path 51 | name: username 52 | schema: 53 | type: string 54 | required: true 55 | description: The name of the user. 56 | requestBody: 57 | required: true 58 | content: 59 | application/json: 60 | schema: 61 | $ref: '#/components/schemas/deleteTodoRequest' 62 | responses: 63 | "200": 64 | description: OK 65 | 66 | components: 67 | schemas: 68 | getTodosResponse: 69 | type: object 70 | properties: 71 | todos: 72 | type: array 73 | items: 74 | type: string 75 | description: The list of todos. 76 | addTodoRequest: 77 | type: object 78 | required: 79 | - todo 80 | properties: 81 | todo: 82 | type: string 83 | description: The todo to add to the list. 84 | required: true 85 | deleteTodoRequest: 86 | type: object 87 | required: 88 | - todo_idx 89 | properties: 90 | todo_idx: 91 | type: integer 92 | description: The index of the todo to delete. 93 | required: true 94 | -------------------------------------------------------------------------------- /simple-todo-service-auth/requirements.txt: -------------------------------------------------------------------------------- 1 | quart 2 | quart_cors 3 | -------------------------------------------------------------------------------- /simple-todo-service-auth/serve.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import quart 4 | import quart_cors 5 | from quart import request 6 | 7 | app = quart_cors.cors(quart.Quart(__name__), allow_origin="*") 8 | 9 | _SERVICE_AUTH_KEY = "TEST" 10 | _TODOS = {} 11 | 12 | 13 | def assert_auth_header(req): 14 | assert req.headers.get( 15 | "Authorization", None) == f"Bearer {_SERVICE_AUTH_KEY}" 16 | 17 | 18 | @app.post("/todos/") 19 | async def add_todo(username): 20 | assert_auth_header(quart.request) 21 | request = await quart.request.get_json(force=True) 22 | if username not in _TODOS: 23 | _TODOS[username] = [] 24 | _TODOS[username].append(request["todo"]) 25 | return quart.Response(response='OK', status=200) 26 | 27 | 28 | @app.get("/todos/") 29 | async def get_todos(username): 30 | assert_auth_header(quart.request) 31 | return quart.Response(response=json.dumps(_TODOS.get(username, [])), status=200) 32 | 33 | 34 | @app.delete("/todos/") 35 | async def delete_todo(username): 36 | assert_auth_header(quart.request) 37 | request = await quart.request.get_json(force=True) 38 | todo_idx = request["todo_idx"] 39 | # fail silently, it's a simple plugin 40 | if 0 <= todo_idx < len(_TODOS[username]): 41 | _TODOS[username].pop(todo_idx) 42 | return quart.Response(response='OK', status=200) 43 | 44 | 45 | @app.get("/logo.png") 46 | async def plugin_logo(): 47 | filename = 'logo.png' 48 | return await quart.send_file(filename, mimetype='image/png') 49 | 50 | 51 | @app.get("/.well-known/ai-plugin.json") 52 | async def plugin_manifest(): 53 | host = request.headers['Host'] 54 | with open("manifest.json") as f: 55 | text = f.read() 56 | text = text.replace("PLUGIN_HOSTNAME", f"https://{host}") 57 | return quart.Response(text, mimetype="text/json") 58 | 59 | 60 | @app.get("/openapi.yaml") 61 | async def openapi_spec(): 62 | host = request.headers['Host'] 63 | with open("openapi.yaml") as f: 64 | text = f.read() 65 | text = text.replace("PLUGIN_HOSTNAME", f"https://{host}") 66 | return quart.Response(text, mimetype="text/yaml") 67 | 68 | 69 | def main(): 70 | app.run(debug=True, host="0.0.0.0", port=5002) 71 | 72 | 73 | if __name__ == "__main__": 74 | main() 75 | --------------------------------------------------------------------------------