diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..7a40d841 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# Microsoft Foundry or another OpenAI-compatible provider. +AI_API_KEY=your_microsoft_foundry_api_key +AI_ENDPOINT=https://your-resource.openai.azure.com/openai/v1 +AI_MODEL=gpt-5-mini diff --git a/.github/instructions/genaiscript.instructions.md b/.github/instructions/genaiscript.instructions.md deleted file mode 100644 index 6d53bf1a..00000000 --- a/.github/instructions/genaiscript.instructions.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -applyTo: "**/*.genai.*" ---- - -## GenAIScript Code Generation Instructions - -GenAIScript is a custom runtime for node.js. It provides a set of unique APIs and support the TypeScript syntax, ESM, await/async. - -- GenAIScript documentation: https://microsoft.github.io/genaiscript/llms-full.txt -- GenAIScript ambient type definitions: https://microsoft.github.io/genaiscript/genaiscript.d.ts - -## Guidance for Code Generation - -- you always generate JavaScript code using ESM modules for Node.JS. -- you prefer using APIs from GenAIScript `genaiscript.d.ts` rather than node.js. Do NOT use node.js imports. -- you keep the code simple, avoid exception handlers or error checking. -- you add `TODOs` where you are unsure so that the user can review them -- you use the global types in genaiscript.d.ts are already loaded in the global context, no need to import them. -- save generated code with `.genai.js` extension diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 306ef1f7..fb140cb7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,13 +6,20 @@ on: jobs: build: runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout code - uses: actions/checkout@a37ce9120846195fa4ece8f58b268e6043cb2f26 # v3.7.0 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - name: Use Node.js LTS + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version-file: .nvmrc - name: Install dependencies - run: npm install + run: npm ci - name: Build project run: npm run build diff --git a/.github/workflows/translation.yml b/.github/workflows/translation.yml deleted file mode 100644 index 11017122..00000000 --- a/.github/workflows/translation.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Continuous Translation -on: - workflow_dispatch: - push: - branches: - - main - paths: - - "README.md" - - lessons/*/README.md -permissions: - contents: write - models: read -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true -jobs: - continuous_translation: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: .genaiscript/cache/** - key: continuous-translation-${{ github.run_id }} - restore-keys: | - continuous-translation- - - uses: pelikhan/action-continuous-translation@923d1c9a88c47993a6d10c509c810f853c9205e6 # v0.2.11 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - lang: fr - files: | - - README.md - - lessons/*/README.md - - uses: stefanzweifel/git-auto-commit-action@b863ae1933cb653a53c021fe36dbb774e1fb9403 # v5.2.0 - with: - file_pattern: "**.md* **/translations/*.json" - commit_message: "[cai] translated docs" - commit_user_name: "genaiscript" diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..b6f27f13 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..b009dfb9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +lts/* diff --git a/AGENTS.md b/AGENTS.md index 3f1639c6..3baa0b13 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,14 +10,12 @@ This repository is a comprehensive learning course called "Generative AI for Beg - OpenAI SDK - Model Context Protocol (MCP) - Express.js (for web applications) -- GitHub Models (free AI model access) -- Azure OpenAI (optional alternative) +- Microsoft Foundry or another OpenAI-compatible model provider **Architecture:** - Multi-package learning repository (not a monorepo, but lesson-based structure) - Each lesson in `/lessons/` contains standalone sample applications - Main companion app in `/app/` directory for character interactions -- Video content and demos in `/videos/` - Documentation in `/docs/` ## Repository Structure @@ -35,16 +33,15 @@ This repository is a comprehensive learning course called "Generative AI for Beg │ ├── 07-mcp/ │ └── 08-mcp-advanced/ ├── docs/ # Setup guides and additional resources -├── videos/ # Video session content and demos └── .devcontainer/ # GitHub Codespaces configuration ``` ## Setup Commands **Prerequisites:** -- Node.js 20 or higher -- GitHub account (for GitHub Models access) -- Optional: Personal Access Token (PAT) if running locally +- Node.js Long Term Support (LTS) +- Microsoft Foundry model deployment or another OpenAI-compatible provider +- Provider endpoint, API key, and model deployment name **Initial Setup:** @@ -54,15 +51,10 @@ git clone https://github.com/microsoft/generative-ai-with-javascript.git cd generative-ai-with-javascript # Install root dependencies -npm install - -# For GitHub Codespaces users (recommended): -# The environment is pre-configured - no additional setup needed -# Use built-in GITHUB_TOKEN environment variable +npm ci -# For local development: -# 1. Create a GitHub Personal Access Token (PAT) at https://github.com/settings/tokens -# 2. Set it as an environment variable: export GITHUB_TOKEN=your_token_here +# Copy the environment template and add your provider settings +cp .env.example .env ``` **Running the Main Companion App:** @@ -91,25 +83,25 @@ npm start 1. Fork the repository 2. Create a Codespace from your fork 3. Pre-configured environment includes: - - Node.js 20 + - Node.js LTS - VSCode extensions (EditorConfig, Code Runner, REST Client) - Ollama with phi3 and all-minilm models - - `GITHUB_TOKEN` automatically available -4. Navigate to any lesson or app directory and run `npm install && npm start` +4. Configure the repository-root `.env` file. +5. Navigate to any lesson or app directory and run `npm ci && npm start` ### Local Development -1. Ensure Node.js 20+ is installed -2. Set `GITHUB_TOKEN` environment variable -3. Navigate to the directory you want to work with -4. Run `npm install` followed by `npm start` +1. Install Node.js LTS. +2. Copy `.env.example` to `.env`. +3. Set `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL`. +4. Navigate to the directory you want to work with. +5. Run `npm ci` followed by `npm start`. ### Environment Variables -- `GITHUB_TOKEN` - Required for GitHub Models API access - - Automatically available in GitHub Codespaces - - Must be set manually for local development -- No scopes/permissions needed for the token when using GitHub Models +- `AI_ENDPOINT` - OpenAI-compatible API base URL +- `AI_API_KEY` - API key for the configured provider +- `AI_MODEL` - Model deployment name used in requests ## Working with Lessons @@ -118,7 +110,6 @@ Each lesson follows a consistent structure: - `README.md` - Main lesson content with narrative and technical instructions - `sample-app/` or `code/` - Starting code for exercises - `solution/` - Complete solution code -- `translations/` - Lesson translations in various languages **To work on a lesson:** @@ -142,7 +133,13 @@ npm run inspect # Runs MCP inspector tool **Current Testing Setup:** -This is primarily an educational repository focused on tutorial content. There are no automated unit or integration tests for the code samples. +The repository has automated tests for the companion app, lesson samples, MCP builds, and documentation publication. + +Run all tests: + +```bash +npm test +``` **Manual Testing Approach:** @@ -170,8 +167,8 @@ This is primarily an educational repository focused on tutorial content. There a npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name add --tool-arg a=1 --tool-arg b=3 ``` -4. **Verify GitHub Models integration:** - - Ensure `GITHUB_TOKEN` is set +4. **Verify model integration:** + - Ensure `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL` are set - Run any lesson sample that calls OpenAI APIs - Verify responses are generated successfully @@ -243,11 +240,11 @@ npm run build # Builds documentation site - `profanity.yml` - Filters inappropriate content in PRs - `spelling.yml` - Spell checks documentation -## GitHub Models Integration +## Model Provider Integration **Primary AI Service:** -This course uses GitHub Models for free access to AI capabilities. All code examples are configured to work with GitHub Models by default. +This course uses Microsoft Foundry by default. The samples can also use another OpenAI-compatible provider. **API Configuration:** @@ -255,16 +252,12 @@ This course uses GitHub Models for free access to AI capabilities. All code exam import { OpenAI } from 'openai'; const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, + baseURL: process.env.AI_ENDPOINT, + apiKey: process.env.AI_API_KEY, }); ``` -**Available Models:** - -- `gpt-4o` - Primary model used in examples -- `gpt-4o-mini` - Lightweight alternative -- Access via GitHub Models marketplace: https://github.com/marketplace/models +Set `AI_MODEL` to the exact deployment name in Microsoft Foundry. ## Monorepo Navigation Tips @@ -358,14 +351,11 @@ cd lessons//sample-app npm install ``` -**GitHub Models authentication issues:** +**Model provider authentication issues:** ```bash -# Verify token is set -echo $GITHUB_TOKEN - -# For Codespaces: Token is automatically available -# For local: Create PAT at https://github.com/settings/tokens +# Verify the configuration names without printing the API key +printf '%s\n' "$AI_ENDPOINT" "$AI_MODEL" ``` **TypeScript compilation errors:** @@ -397,19 +387,17 @@ This repository contains fictional AI-generated content. Historical characters g - Each lesson builds progressively on previous concepts - Narrative-driven learning with time-travel story - Hands-on coding exercises with solutions provided -- Video content supplements written lessons **Contributing:** - See `.github/CONTRIBUTING.md` for contribution guidelines - Follow existing code patterns and structure -- Add translations in `lessons//translations/` - Use issue templates in `.github/ISSUE_TEMPLATE/` **Community Resources:** - Discord: https://discord.gg/kzRShWzttr -- Azure AI Foundry Forum: https://aka.ms/foundry/forum +- Microsoft Foundry Forum: https://aka.ms/foundry/forum - Related courses linked in main README.md **Performance Considerations:** @@ -417,4 +405,4 @@ This repository contains fictional AI-generated content. Historical characters g - GitHub Codespaces requires 16GB RAM (configured in devcontainer.json) - Ollama models (phi3, all-minilm) pre-installed in Codespaces - Keep lesson samples lightweight and focused -- Use streaming for better UX in production apps (see lesson 10) +- Use streaming for a better user experience in production apps. diff --git a/README.fr.md b/README.fr.md deleted file mode 100644 index 72cbb085..00000000 --- a/README.fr.md +++ /dev/null @@ -1,155 +0,0 @@ -
- - - -# Intelligence Artificielle Générative pour débutants avec JavaScript - -[![License](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](https://github.com/microsoft/generative-ai-with-javascript/blob/main/LICENSE) -[![Watch the video series](https://img.shields.io/badge/Videos-d95652.svg?style=flat-square\&logo=youtube)](https://aka.ms/genai-js) -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) - -[![Azure AI Discord](https://dcbadge.limes.pink/api/server/kzRShWzttr)](https://discord.gg/kzRShWzttr) - -[![Azure AI Foundry Developer Forum](https://img.shields.io/badge/GitHub-Azure_AI_Foundry_Developer_Forum-blue?style=for-the-badge\&logo=github\&color=000000\&logoColor=fff)](https://aka.ms/foundry/forum) - -⭐ Si vous appréciez ce dépôt, donnez-lui une étoile sur GitHub — cela aide beaucoup ! - -[Commencer](#getting-started) • [Leçons](#lessons) • [Continuez à apprendre](#keep-learning) - -
- -Prêt à intégrer l'Intelligence Artificielle Générative dans vos applications JavaScript ? - -Ce cours vous emmène dans une *aventure à travers le temps*—rencontrez les légendes de l'histoire avec une touche amusante, tout en apprenant les technologies d'IA Générative ✨ - -> [!IMPORTANT] -> Ambiance open-source ! Réutilisez, modifiez et partagez librement ce contenu. - -![Character App Sneak Peek](/docs/images/background.png) - -## NOUVEAU - Leçons sur MCP ajoutées - -* Apprenez à [créer et tester votre premier serveur](/lessons/07-mcp/README.md) -* Améliorez votre client MCP en [intégrant un LLM](/lessons/08-mcp-advanced/README.md) - -## Appel à l'aide - aidez-nous à traduire ! - -Aidez-nous à traduire ce cours. Chaque leçon dans le dossier `lessons/` contient un répertoire `translations/`. Ajoutez votre fichier traduit de cette manière : `README..md`, par exemple *README.fr.md*. - Merci. - -## Plongez dans l'histoire avec la magie de l'IA ! - -Plongez dans une expérience d'apprentissage immersive grâce à l'IA Générative : - -* **Découvrez les technologies d'IA Générative**. Si vous avez toujours voulu comprendre l'IA Générative et son potentiel pour vos applications, vous êtes au bon endroit ! - -* **Histoires épiques de voyage dans le temps**.\ - Plongez dans un récit amusant, en dialoguant avec des icônes telles que Léonard de Vinci, Ada Lovelace ou Montezuma dans chaque leçon. - -* **Application compagnon**. Interagissez avec des personnages historiques grâce aux technologies d'IA Générative ([voir notre avertissement sur l'IA Responsable](#responsible-ai-disclaimer)). - - ![Interact with History through conversation](/docs/images/character-chat.png) - - Consultez le répertoire [*app*](/app/README.md) pour exécuter l'[application localement](/docs/setup/README.md#option-2--running-the-app-locally) ou utilisez [GitHub Codespaces](/docs/setup/README.md) pour l'exécuter en ligne. - -* **Accessibilité au top**. Lisez-le, écoutez-le—les balises audio lui donnent vie. - -> « C'est comme une bande dessinée avec du code ! » — Utilisateur Heureux - -## Commencer - -Tout au long de ce cours, vous trouverez de nombreux exemples de code et exercices, alors nous vous encourageons à exécuter et expérimenter avec le code dans votre propre copie de ce dépôt : - -1. Sélectionnez le bouton **Fork** dans le coin supérieur droit du dépôt ou cliquez sur ce bouton :\ - [![Fork](https://img.shields.io/badge/Fork-Repository-blue?style=flat-square)](https://github.com/microsoft/generative-ai-with-javascript/fork) - -2. Cliquez sur le bouton **Code** dans votre dépôt forké, accédez à l'onglet **Codespaces**, puis choisissez **Créer un codespace**. - - Cela créera un environnement en ligne préconfiguré pour vous. Vous pourrez ensuite utiliser [GitHub Models](https://github.com/marketplace/models) pour exécuter les exemples de code et interagir avec des modèles d'IA gratuitement, sans configuration supplémentaire. - -> [!NOTE] -> -> Bien que GitHub Codespaces offre un point de départ rapide et facile, vous pouvez également exécuter les exemples de code [localement](/docs/setup/README.md#option-2--running-the-app-locally). -> -> En savoir plus sur les [concepts de GitHub Codespaces et GitHub Models ici](/docs/setup/README.md). - -### Description de la leçon - -📦 Chaque leçon inclut : - -* Une **leçon écrite** avec un exercice et un quiz. -* Une courte **vidéo** pour approfondir votre apprentissage. -* **Solutions** pour chaque exercice et quiz. -* **Personnages** avec lesquels vous pouvez interagir grâce à notre [application compagnon](/app/README.md), démontrant l'IA Générative. - -## Leçons - -🗃️ Table des matières - -| # | Lien de la leçon | Description | -| - | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 1 | [Introduction à l'IA Générative et aux LLM pour les développeurs JavaScript](/lessons/01-intro-to-genai/README.md) | Comprenez les bases de l'IA Générative et des LLM, leurs applications et leurs limites en JavaScript, et comment utiliser l'IA pour améliorer les expériences utilisateur. | -| 2 | [Créez votre première application d'IA](/lessons/02-first-ai-app/README.md) | Configurez votre environnement de développement, écrivez une application basique et comprenez les prompts système. | -| 3 | [Conception de prompts](/lessons/03-prompt-engineering/README.md) | Apprenez les bases de la conception de prompts, des techniques et des méta-prompts pour de meilleurs résultats d'IA. | -| 4 | [Sortie structurée](/lessons/04-structured-output/README.md) | Apprenez à produire des sorties structurées, à extraire des données des prompts et à les présenter sous différents formats, comme le JSON, pour une consommation simplifiée. | -| 5 | [Génération augmentée par la récupération (RAG)](/lessons/05-rag/README.md) | Découvrez les bases de RAG, comment intégrer des données externes et comment les exploiter pour des réponses d'IA plus pertinentes et précises. | -| 6 | [Appels d'outils/Appels de fonctions](/lessons/06-tool-calling/README.md) | Apprenez à donner des capacités supplémentaires à votre LLM et à intégrer vos propres fonctions | -| 7 | [MCP, Model Context Protocol ](/lessons/07-mcp/README.md) | Apprenez à débuter avec MCP pour standardiser l'exposition des prompts, ressources et outils | -| 8 | [Amélioration des clients MCP avec des modèles de langage larges](/lessons/08-mcp-advanced/README.md) | Découvrez comment améliorer votre application MCP en enrichissant les clients avec LLM et bien plus | - -De nouvelles leçons seront ajoutées au cours au fil du temps, restez connecté ! - -## Continuez à apprendre - -🙌 Une fois ce cours terminé, vous pouvez continuer à apprendre en explorant nos ressources additionnelles. - -
-🎬Video Series - -| # | Session | Description | Diapositives | Démo | Script | Vidéo | -| -- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | -------------------------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| 0 | Introduction de la série | Introduction à la série et à son contenu. | [pptx](/videos/slides/00-intro.pptx) / [pdf](/videos/slides/pdf/00-intro.pdf) | - | [Script](/videos/sessions/00-intro.md) | [📺](https://www.youtube.com/watch?v=vLYtDgs_zx8\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=1) | -| 1 | Ce qu'il faut savoir sur les LLM | Découvrez ce que sont les LLM, leur mode d'entraînement, leur fonctionnement et leurs limites. | [pptx](/videos/slides/01-llms.pptx) / [pdf](/videos/slides/pdf/01-llms.pdf) | [Démo](/videos/demos/01-llms/) | [Script](/videos/sessions/01-llms.md) | [📺](https://www.youtube.com/watch?v=GQ_2OjNZ9aA\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=2) | -| 2 | Techniques essentielles de conception de prompts | Techniques pratiques pour concevoir des prompts et tirer le meilleur parti des modèles d'IA. | [pptx](/videos/slides/02-prompt-engineering.pptx) / [pdf](/videos/slides/pdf/02-prompt-engineering.pdf) | [Démo](/videos/demos/02-prompt-engineering/) | [Script](/videos/sessions/02-prompt-engineering.md) | [📺](https://www.youtube.com/watch?v=gQ6TlyxBmWs\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=3) | -| 3 | Améliorez la précision et la fiabilité de l'IA avec RAG | Introduction à la "Génération Augmentée par la Récupération", pour utiliser l'IA avec vos propres données. | [pptx](/videos/slides/03-rag.pptx) / [pdf](/videos/slides/pdf/03-rag.pdf) | [Démo](/videos/demos/03-rag/) | [Script](/videos/sessions/03-rag.md) | [📺](https://www.youtube.com/watch?v=xkFOmx5yxIA\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=4) | -| 4 | Accélérez votre développement d'IA avec LangChain.js | Présentation des concepts centraux du framework LangChain.js et comment l'utiliser pour accélérer vos développements IA. | [pptx](/videos/slides/04-langchainjs.pptx) / [pdf](/videos/slides/pdf/04-langchainjs.pdf) | [Démo](/videos/demos/04-langchainjs/) | [Script](/videos/sessions/04-langchainjs.md) | [📺](https://www.youtube.com/watch?v=02IDU8eCX8o\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=5) | -| 5 | Exécutez des modèles d'IA sur votre machine locale avec Ollama | Découvrez comment intégrer des modèles d'IA locaux dans votre flux de travail de développement. | [pptx](/videos/slides/05-local-models.pptx) / [pdf](/videos/slides/pdf/05-local-models.pdf) | [Démo](/videos/demos/05-local-models/) | [Script](/videos/sessions/05-local-models.md) | [📺](https://www.youtube.com/watch?v=dLfNnoPv4AQ\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=6) | -| 6 | Commencez avec l'IA gratuitement en utilisant Phi-3 | Expérimentez avec le modèle Ollama et Phi-3 directement depuis votre navigateur. | [pptx](/videos/slides/06-playground.pptx) / [pdf](/videos/slides/pdf/06-playground.pdf) | [Démo](/videos/demos/06-playground/) | [Script](/videos/sessions/06-playground.md) | [📺](https://www.youtube.com/watch?v=Ds32MS9SHzU\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=7) | -| 7 | Introduction à Azure AI Foundry | Lancez votre voyage avec Azure AI Foundry. | [pptx](/videos/slides/07-ai-foundry.pptx) / [pdf](/videos/slides/pdf/07-ai-foundry.pdf) | [Démo](/videos/demos/07-ai-foundry/) | [Script](/videos/sessions/07-ai-foundry.md) | [📺](https://www.youtube.com/watch?v=9Mo-VOGk8ng\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=8) | -| 8 | Création d'applications d'IA générative avec Azure Cosmos DB | Développez des applications d'IA générative avec Azure Cosmos DB et la recherche vectorielle. | [pptx](/videos/slides/08-cosmos-db.pptx) / [pdf](/videos/slides/pdf/08-cosmos-db.pdf) | [Démo](/videos/demos/08-cosmos-db/) | [Script](/videos/sessions/08-cosmos-db.md) | [📺](https://www.youtube.com/watch?v=-GQyaLbeqxQ\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=9) | -| 9 | Outils et services Azure pour héberger et stocker des applications d'IA | Créez, déployez et évoluez des applications d'IA en utilisant les outils Azure. | [pptx](/videos/slides/09-azure-tools.pptx) / [pdf](/videos/slides/pdf/09-azure-tools.pdf) | - | [Script](/videos/sessions/09-azure-tools.md) | [📺](https://www.youtube.com/watch?v=WB6Fpzhwyug\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=10) | -| 10 | Diffusion des résultats IA génératifs avec le protocole AI Chat | Intégrez facilement la diffusion dans vos apps grâce au Protocole AI Chat. | [pptx](/videos/slides/10-chat-protocol.pptx) / [pdf](/videos/slides/pdf/10-chat-protocol.pdf) | [Démo](/videos/demos/10-chat-protocol/) | [Script](/videos/sessions/10-chat-protocol.md) | [📺](https://www.youtube.com/watch?v=fzDCW-6hMtU\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=11) | - -Pour voir la page complète des ressources, accédez à cette [page de présentation des vidéos](/videos/README.md). - -
- -
-🎒 Other Courses - -* [IA générative pour les débutants](https://aka.ms/genai-beginners) -* [IA Générative pour Débutants .NET](https://github.com/microsoft/Generative-AI-for-beginners-dotnet) -* [IA Générative avec JavaScript](https://github.com/microsoft/generative-ai-with-javascript) -* [IA pour les débutants](https://aka.ms/ai-beginners) -* [Agents IA pour Débutants - Un cours](https://github.com/microsoft/ai-agents-for-beginners) -* [Science des données pour les débutants](https://aka.ms/datascience-beginners) -* [Apprentissage automatique pour les débutants](https://aka.ms/ml-beginners) -* [Cybersécurité pour les débutants](https://github.com/microsoft/Security-101) -* [Développement Web pour les débutants](https://aka.ms/webdev-beginners) -* [IoT pour les débutants](https://aka.ms/iot-beginners) -* [Développement XR pour les débutants](https://github.com/microsoft/xr-development-for-beginners) -* [Maîtriser GitHub Copilot pour la programmation en binôme](https://github.com/microsoft/Mastering-GitHub-Copilot-for-Paired-Programming) -* [Maîtriser GitHub Copilot pour les développeurs C#/.NET](https://github.com/microsoft/mastering-github-copilot-for-dotnet-csharp-developers) -* [Choisissez votre propre aventure Copilot](https://github.com/microsoft/CopilotAdventures) - -
- -Vous trouverez également des ressources supplémentaires sous forme de [tutoriels, exemples de code et plus](/docs/additional-resources.md). - -## Clause de non-responsabilité pour l'IA Responsable - -> [!IMPORTANT] -> AVERTISSEMENT : Ce dépôt contient du contenu fictif généré par l'IA. Les personnages historiques représentés ici génèrent des réponses grâce à l'IA générative, basée sur des données d'entraînement. Les réponses générées par ces personnages ne représentent pas leurs véritables opinions ou citations. Ce contenu est uniquement destiné à des fins de divertissement. [Principes de l'IA Responsable de Microsoft ici](https://www.microsoft.com/fr-fr/ai/principles-and-approach/) - -## Interagissez avec la communauté - -[![Azure AI Discord](https://dcbadge.limes.pink/api/server/kzRShWzttr)](https://discord.gg/kzRShWzttr) diff --git a/README.md b/README.md index 7257ab70..bd7269d1 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,11 @@ # Generative AI for beginners with JavaScript [![License](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](https://github.com/microsoft/generative-ai-with-javascript/blob/main/LICENSE) -[![Watch the video series](https://img.shields.io/badge/Videos-d95652.svg?style=flat-square&logo=youtube)](https://aka.ms/genai-js) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Azure AI Discord](https://dcbadge.limes.pink/api/server/kzRShWzttr)](https://discord.gg/kzRShWzttr) -[![Azure AI Foundry Developer Forum](https://img.shields.io/badge/GitHub-Azure_AI_Foundry_Developer_Forum-blue?style=for-the-badge&logo=github&color=000000&logoColor=fff)](https://aka.ms/foundry/forum) +[![Microsoft Foundry Developer Forum](https://img.shields.io/badge/GitHub-Microsoft_Foundry_Developer_Forum-blue?style=for-the-badge&logo=github&color=000000&logoColor=fff)](https://aka.ms/foundry/forum) ⭐ If you like this repo, star it on GitHub — it helps a lot! @@ -33,11 +32,6 @@ This course throws you into a *time-traveling adventure*—meet history’s lege - Improve your MCP client by [integrating an LLM](/lessons/08-mcp-advanced/README.md) -## Call for help - help us translate ! - -Help us translate this course. Each lesson in `lessons/` folder has a `translations/` directory. Add your translation file like so `README..md`, for example *README.es.md*. - Thank You. - - ## Step into history with AI magic! Dive into an immersive learning experience powered by Generative AI: @@ -65,20 +59,22 @@ Throughout this course you'll find many code examples and exercises, so we encou 2. Click the **Code** button in your forked repository, go to the **Codespaces** tab, and then choose **Create codespace**. - This will create a preconfigured online environment for you. You'll then be able to use [GitHub Models](https://github.com/marketplace/models) to run the code examples and interact with AI models for free, without any additional setup. + This creates an online environment for the code examples. Continue with the [course setup guide](/docs/setup/README.md) to create a Microsoft Foundry project, deploy a chat model, and configure your endpoint and API key. > [!NOTE] > > While GitHub Codespaces provides a quick and easy starting point you can also run the code samples [locally](/docs/setup/README.md#option-2--running-the-app-locally). > -> Learn more about [GitHub Codespaces and GitHub Models concepts here](/docs/setup/README.md). +> Both local development and Codespaces require model configuration. The [course setup guide](/docs/setup/README.md) covers Microsoft Foundry, the repository-root `.env`, and a live companion app check. + +The packages in the repository root and the [companion app](/app/README.md) require Node.js LTS. Each lesson package has its own requirements. ### Lesson description 📦 Each lesson includes: - A **written lesson** with an assignment and quiz. -- A short **video** to help further your learning. + - **Solutions** for each assignment and quiz. - **Characters** you can interact with using our [companion app](/app/README.md), demonstrating Generative AI. @@ -103,27 +99,6 @@ New lessons will be added to the course over time, so stay tuned! 🙌 After completing this course, you can continue learning by exploring our additional resources. -
-🎬Video Series - -| # | Session | Description | Slides | Demo | Script | Video | -|---|---------|-------------|--------|------|--------|-------| -| 0 | Series introduction | Introduces the series and its content. | [pptx](/videos/slides/00-intro.pptx) / [pdf](/videos/slides/pdf/00-intro.pdf) | - | [Script](/videos/sessions/00-intro.md) | [📺](https://www.youtube.com/watch?v=vLYtDgs_zx8&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=1) | -| 1 | What you need to know about LLMs | Explores what LLMs are, how they're trained, how they work and their limits. | [pptx](/videos/slides/01-llms.pptx) / [pdf](/videos/slides/pdf/01-llms.pdf) | [Demo](/videos/demos/01-llms/) | [Script](/videos/sessions/01-llms.md) | [📺](https://www.youtube.com/watch?v=GQ_2OjNZ9aA&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=2) | -| 2 | Essential prompt engineering techniques | Practical prompt engineering techniques to get the best out of AI models. | [pptx](/videos/slides/02-prompt-engineering.pptx) / [pdf](/videos/slides/pdf/02-prompt-engineering.pdf) | [Demo](/videos/demos/02-prompt-engineering/) | [Script](/videos/sessions/02-prompt-engineering.md) | [📺](https://www.youtube.com/watch?v=gQ6TlyxBmWs&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=3) | -| 3 | Improve AI accuracy and reliability with RAG | Introduces Retrieval-Augmented Generation, to use AI with your own data. | [pptx](/videos/slides/03-rag.pptx) / [pdf](/videos/slides/pdf/03-rag.pdf) | [Demo](/videos/demos/03-rag/) | [Script](/videos/sessions/03-rag.md) | [📺](https://www.youtube.com/watch?v=xkFOmx5yxIA&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=4) | -| 4 | Speed up your AI development with LangChain.js | Covers LangChain.js framework core concepts, and how to use it to accelerate AI developments. | [pptx](/videos/slides/04-langchainjs.pptx) / [pdf](/videos/slides/pdf/04-langchainjs.pdf) | [Demo](/videos/demos/04-langchainjs/) | [Script](/videos/sessions/04-langchainjs.md) | [📺](https://www.youtube.com/watch?v=02IDU8eCX8o&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=5) | -| 5 | Run AI models on your local machine with Ollama | Shows how to integrate local AI models into your development workflow. | [pptx](/videos/slides/05-local-models.pptx) / [pdf](/videos/slides/pdf/05-local-models.pdf) | [Demo](/videos/demos/05-local-models/) | [Script](/videos/sessions/05-local-models.md) | [📺](https://www.youtube.com/watch?v=dLfNnoPv4AQ&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=6) | -| 6 | Get started with AI for free using Phi-3 | Experiments with Ollama and Phi-3 model directly from your browser. | [pptx](/videos/slides/06-playground.pptx) / [pdf](/videos/slides/pdf/06-playground.pdf) | [Demo](/videos/demos/06-playground/) | [Script](/videos/sessions/06-playground.md) | [📺](https://www.youtube.com/watch?v=Ds32MS9SHzU&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=7) | -| 7 | Introduction to Azure AI Foundry | Kickstart your journey with Azure AI Foundry. | [pptx](/videos/slides/07-ai-foundry.pptx) / [pdf](/videos/slides/pdf/07-ai-foundry.pdf) | [Demo](/videos/demos/07-ai-foundry/) | [Script](/videos/sessions/07-ai-foundry.md) | [📺](https://www.youtube.com/watch?v=9Mo-VOGk8ng&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=8) | -| 8 | Building Generative AI Apps with Azure Cosmos DB | Build generative AI apps with Azure Cosmos DB and vector search. | [pptx](/videos/slides/08-cosmos-db.pptx) / [pdf](/videos/slides/pdf/08-cosmos-db.pdf) | [Demo](/videos/demos/08-cosmos-db/) | [Script](/videos/sessions/08-cosmos-db.md) | [📺](https://www.youtube.com/watch?v=-GQyaLbeqxQ&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=9) | -| 9 | Azure tools & services for hosting and storing AI apps | Build, deploy, and scale AI applications using Azure tools. | [pptx](/videos/slides/09-azure-tools.pptx) / [pdf](/videos/slides/pdf/09-azure-tools.pdf) | - | [Script](/videos/sessions/09-azure-tools.md) | [📺](https://www.youtube.com/watch?v=WB6Fpzhwyug&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=10) | -| 10 | Streaming Generative AI output with the AI Chat Protocol | Integrate streaming easily into your apps with the AI Chat Protocol. | [pptx](/videos/slides/10-chat-protocol.pptx) / [pdf](/videos/slides/pdf/10-chat-protocol.pdf) | [Demo](/videos/demos/10-chat-protocol/) | [Script](/videos/sessions/10-chat-protocol.md) | [📺](https://www.youtube.com/watch?v=fzDCW-6hMtU&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=11) | - -To see the full page of resources, go to this [video overview page](/videos/README.md). - -
- You'll also find additional resources in the form of [tutorials, code samples and more](/docs/additional-resources.md). @@ -154,8 +129,8 @@ You'll also find additional resources in the form of [tutorials, code samples an If you get stuck or have any questions about building AI apps, join: -[![Azure AI Foundry Discord](https://img.shields.io/badge/Discord-Azure_AI_Foundry_Community_Discord-blue?style=for-the-badge&logo=discord&color=5865f2&logoColor=fff)](https://aka.ms/foundry/discord) +[![Microsoft Foundry Discord](https://img.shields.io/badge/Discord-Microsoft_Foundry_Community_Discord-blue?style=for-the-badge&logo=discord&color=5865f2&logoColor=fff)](https://aka.ms/foundry/discord) If you have product feedback or errors while building visit: -[![Azure AI Foundry Developer Forum](https://img.shields.io/badge/GitHub-Azure_AI_Foundry_Developer_Forum-blue?style=for-the-badge&logo=github&color=000000&logoColor=fff)](https://aka.ms/foundry/forum) +[![Microsoft Foundry Developer Forum](https://img.shields.io/badge/GitHub-Microsoft_Foundry_Developer_Forum-blue?style=for-the-badge&logo=github&color=000000&logoColor=fff)](https://aka.ms/foundry/forum) diff --git a/_sidebar.md b/_sidebar.md index b3ab5408..41883f83 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -1,3 +1,4 @@ +- [Course setup](/docs/setup/README.md) - Lessons - [01 - Intro to Gen AI](/lessons/01-intro-to-genai/README.md) - [02 - First AI App](/lessons/02-first-ai-app/README.md) diff --git a/app/README.md b/app/README.md index 57a1b748..0f4f75f0 100644 --- a/app/README.md +++ b/app/README.md @@ -2,15 +2,19 @@ This app allows you to speak to all character features in the curriculum. There are two ways to interact with AI: -- GitHub Models [Use GitHub Models](https://github.com/marketplace/models) +- A configured Azure OpenAI or OpenAI-compatible endpoint - Copilot SDK [Copilot SDK](https://github.com/github/copilot-sdk/blob/main/docs/getting-started.md). This one uses your GitHub Copilot. ## Installation +Follow the [course setup guide](../docs/setup/README.md) to create a Microsoft Foundry project, deploy a chat model, obtain its endpoint and key, and configure the repository-root `.env`. The same steps apply to local development and Codespaces. + +Use Node.js LTS for this app and the packages in the repository root. Lesson packages have their own requirements. + 1. Start a [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) 2. Navigate to _/app_ in the repo root. -3. Locate the console and run `npm install`. -4. Once it appears, select the "Open in Browser" button. +3. Locate the console and run `npm ci`. +4. Run one of the commands below. Then select the "Open in Browser" button. You should see something like: @@ -20,18 +24,69 @@ You should see something like: Run the app by either: -**GitHub Models** +**Azure OpenAI or an OpenAI-compatible endpoint** + +Copy `.env.example` to `.env` in the repository root. Set these variables there or in your environment: +```dotenv +AI_ENDPOINT=https://YOUR-RESOURCE.openai.azure.com/openai/v1/ +AI_API_KEY=your-api-key +AI_MODEL=your-model-deployment ``` + +For Azure OpenAI, `AI_MODEL` must be the deployment name. For OpenAI, use `https://api.openai.com/v1/` and a supported model name. The endpoint must use HTTPS; HTTP is allowed only for a loopback address used by a local provider. + +```bash npm start ``` +To use another environment file without copying its keys, set `ENV_FILE`: + +```bash +ENV_FILE="$HOME/.env" npm start +``` + +Existing environment variables take priority over values in the file. Missing settings or an unreadable explicit file stop the server with an error. Keep keys out of source control and browser code. + +The [course setup guide](../docs/setup/README.md#configure-environment-variables) explains these variables and the repository-root `.env` location. No embedding model is required for this chat app. + **Copilot SDK** ``` npm run start:sdk ``` +The SDK uses your GitHub Copilot subscription. Supply a Copilot-compatible token through `COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, or `GITHUB_TOKEN`, or use GitHub CLI authentication. Do not put tokens in browser code. + +## Safe use + +Both servers listen on `127.0.0.1`. This sample does not authenticate HTTP callers. Keep Codespaces ports private. Do not expose the service to a public network. Remote deployment requires authentication, authorization, request limits, and an isolated service account. + +SDK chat uses client mode `empty`, no available tools, a permission handler that rejects every request, and a pre-tool hook that denies every tool call. It does not load project configuration, custom agents, or MCP servers. Character instructions are appended to the SDK system message; they do not replace its guardrails. The SDK removes host environment context in `empty` mode. + +The SDK uses a separate temporary directory for its state and working directory. Each request aborts, disconnects, and deletes its session. The temporary directory is removed on normal shutdown with `SIGINT` or `SIGTERM`. This directory is not an operating-system sandbox. Do not add tools or change the permission policy to support chat. + +The SDK version is pinned in both manifests. After an SDK update, check these controls against the new API and runtime before exposing the service. + +## Request format + +Both backends accept the same `POST /send` request: + +```json +{ + "message": "Tell me about your work.", + "character": { "name": "ada" } +} +``` + +The server reads the character description from `public/characters.json`. It rejects empty or non-string messages, unknown character names, and character properties other than `name`. Invalid requests return HTTP 400 without calling an AI service. + +## Tests + +Run `npm test` in `app/`. The tests use local fake AI services. They do not use credentials or execute model-selected host commands. + +The model client also has a 60-second request timeout. A missing or empty model answer returns HTTP 500 instead of a successful empty response. + ## Interact with a character You're faced with a text input field. Type your message and see the response from the character you are chatting with. @@ -42,7 +97,7 @@ Select the character image to change the character you are speaking to. ## Change the character's behavior -In `characters.json`, there's a `description` property that will be used as a system message. If you change this, the character's personality will change. +In `public/characters.json`, the `description` property supplies the server-owned character instructions. Edit this file to change the character's personality. Do not send a description from the browser. ## Please read diff --git a/app/app-sdk.js b/app/app-sdk.js index 575af91d..1175d379 100644 --- a/app/app-sdk.js +++ b/app/app-sdk.js @@ -1,75 +1,138 @@ -// npm install @github/copilot-sdk tsx - import { CopilotClient } from "@github/copilot-sdk"; - import express from 'express'; import path from 'path'; -import { fileURLToPath } from 'url'; -import dotenv from 'dotenv'; - -import json from './public/characters.json' with { type: "json" }; - -const client = new CopilotClient(); - - -let systemMessage = json[4].description; -let page = json[4].page; - -console.log("SERVER systemMessage: ", systemMessage); -console.log("SERVER page: ", page); - -dotenv.config(); - -const app = express(); -const port = process.env.PORT || 3000; +import { fileURLToPath } from 'url'; +import { mkdtemp, rm } from 'fs/promises'; +import { tmpdir } from 'os'; +import { ChatRequestError, getChatRequest, handleBodyError } from './chat-request.js'; +import { loadEnvironment } from './model-config.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -app.use(express.json()); - -// Serve static files from the 'public' directory -app.use(express.static(path.join(__dirname, 'public'))); - -app.locals.delimiters = '{{ }}'; - -function getCharacterByName(name) { - return json.find(character => character.name === name) || null; +export function createClient(baseDirectory, connection) { + return new CopilotClient({ + mode: "empty", + baseDirectory, + workingDirectory: baseDirectory, + connection + }); } -// Route to send the prompt -app.post('/send', async (req, res) => { - const { message, character } = req.body; - - systemMessage = character.description; - const prompt = message; +async function closeSession(client, session) { + const errors = []; + for (const close of [ + () => session.abort(), + () => session.disconnect(), + () => client.deleteSession(session.sessionId) + ]) { + try { + await close(); + } catch (error) { + errors.push(error); + } + } + if (errors.length) { + throw new AggregateError(errors, 'The SDK session could not be closed.'); + } +} - try { - console.log("SYSTEM MESSAGE: ", systemMessage); - console.log(`SERVER sending prompt ${prompt}`) - - const session = await client.createSession({ - model: "gpt-4.1", - systemMessage: { - mode: "replace", - content: systemMessage - } - }); +export function createApp(client) { + const app = express(); + + app.use(express.json()); + app.use(express.static(path.join(__dirname, 'public'))); + app.locals.delimiters = '{{ }}'; + + app.post('/send', async (req, res) => { + try { + const { prompt, systemMessage } = getChatRequest(req.body); + const session = await client.createSession({ + model: "gpt-4.1", + systemMessage: { + mode: "append", + content: systemMessage + }, + // Custom tools and built-in tools have separate SDK controls. + tools: [], + availableTools: [], + onPermissionRequest: () => ({ kind: "reject" }), + hooks: { + onPreToolUse: () => ({ + permissionDecision: "deny", + permissionDecisionReason: "This application supports chat only." + }) + }, + infiniteSessions: { enabled: false }, + enableConfigDiscovery: false, + mcpServers: {}, + customAgents: [] + }); + + let response; + try { + response = await session.sendAndWait({ prompt }); + } finally { + await closeSession(client, session); + } + + const answer = response?.data?.content; + if (typeof answer !== 'string' || !answer.trim()) { + throw new Error('The SDK session did not return an answer.'); + } + + res.json({ prompt, answer }); + } catch (error) { + if (error instanceof ChatRequestError) { + res.status(400).json({ message: error.message }); + return; + } + console.error('Error:', error); + res.status(500).json({ message: 'An unexpected error occurred. Please try again later.' }); + } + }); + app.use(handleBodyError); + + return app; +} - const response = await session.sendAndWait({ prompt: prompt }); - const data = response?.data.content; - - console.log(`SERVER: ${data}`); - res.json({ - prompt: prompt, - answer: data - }); - } catch (error) { - console.error(`Error: ${error.message}`); // Log the error message for debugging - res.status(500).json({ message: 'An unexpected error occurred. Please try again later.' }); // Send a generic error message +if (process.argv[1] && path.resolve(process.argv[1]) === __filename) { + loadEnvironment(); + const port = process.env.PORT || 3000; + const baseDirectory = await mkdtemp(path.join(tmpdir(), 'character-chat-')); + const client = createClient(baseDirectory); + const app = createApp(client); + const server = app.listen(port, '127.0.0.1', () => { + console.log(`Server is running on http://localhost:${port}`); + }); + + let stopping = false; + async function stop() { + if (stopping) return; + stopping = true; + server.close(); + try { + const errors = await client.stop(); + if (errors.length) { + throw new AggregateError(errors, 'The SDK client could not be stopped.'); + } + } catch (error) { + console.error('Error:', error); + process.exitCode = 1; + } finally { + try { + await rm(baseDirectory, { recursive: true, force: true }); + } catch (error) { + console.error('Error:', error); + process.exitCode = 1; + } + } } -}); - -app.listen(port, () => { - console.log(`Server is running on http://localhost:${port}`); -}); + server.once('error', error => { + console.error('Error:', error); + process.exitCode = 1; + void stop(); + }); + process.once('SIGINT', stop); + process.once('SIGTERM', stop); +} diff --git a/app/app.js b/app/app.js index a659b0d7..7478f775 100644 --- a/app/app.js +++ b/app/app.js @@ -1,78 +1,64 @@ import express from 'express'; import { OpenAI } from 'openai'; import path from 'path'; -import { fileURLToPath } from 'url'; -import dotenv from 'dotenv'; - -import json from './public/characters.json' with { type: "json" }; - -let systemMessage = json[4].description; -let page = json[4].page; - -console.log("SERVER systemMessage: ", systemMessage); -console.log("SERVER page: ", page); - -dotenv.config(); - -const app = express(); -const port = process.env.PORT || 3000; +import { fileURLToPath } from 'url'; +import { ChatRequestError, getChatRequest, handleBodyError } from './chat-request.js'; +import { getModelConfig, loadEnvironment } from './model-config.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -app.use(express.json()); - -// Serve static files from the 'public' directory -app.use(express.static(path.join(__dirname, 'public'))); - -app.locals.delimiters = '{{ }}'; +export function createApp(openai, model) { + if (typeof model !== 'string' || !model.trim()) { + throw new Error('A model name is required to create the chat app.'); + } + const app = express(); + + app.use(express.json()); + app.use(express.static(path.join(__dirname, 'public'))); + app.locals.delimiters = '{{ }}'; + + app.post('/send', async (req, res) => { + try { + const { prompt, systemMessage } = getChatRequest(req.body); + const completion = await openai.chat.completions.create({ + model, + messages: [ + { role: "system", content: systemMessage }, + { role: "user", content: prompt } + ] + }); + + const answer = completion?.choices?.[0]?.message?.content; + if (typeof answer !== 'string' || !answer.trim()) { + throw new Error('The model did not return an answer.'); + } + res.json({ prompt, answer }); + } catch (error) { + if (error instanceof ChatRequestError) { + res.status(400).json({ message: error.message }); + return; + } + console.error(`Error: ${error.message}`); + res.status(500).json({ message: 'An unexpected error occurred. Please try again later.' }); + } + }); + app.use(handleBodyError); -function getCharacterByName(name) { - return json.find(character => character.name === name) || null; + return app; } -// Route to send the prompt -app.post('/send', async (req, res) => { - const { message, character } = req.body; - - systemMessage = character.description; - - const prompt = message; - - const messages = [ - { - "role": "system", - "content": systemMessage, - }, - { - "role": "user", - "content": prompt - } - ]; - +if (process.argv[1] && path.resolve(process.argv[1]) === __filename) { + loadEnvironment(); + const port = process.env.PORT || 3000; + const { baseURL, apiKey, model } = getModelConfig(); const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", // might need to change to this url in the future: https://models.github.ai/inference - apiKey: process.env.GITHUB_TOKEN, + baseURL, + apiKey, + timeout: 60000 }); - - try { - console.log(`SERVER sending prompt ${prompt}`) - const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: messages, - }); - - console.log(`SERVER: ${completion.choices[0]?.message?.content}`); - res.json({ - prompt: prompt, - answer: completion.choices[0]?.message?.content - }); - } catch (error) { - console.error(`Error: ${error.message}`); // Log the error message for debugging - res.status(500).json({ message: 'An unexpected error occurred. Please try again later.' }); // Send a generic error message - } -}); - -app.listen(port, () => { - console.log(`Server is running on http://localhost:${port}`); -}); + const app = createApp(openai, model); + app.listen(port, '127.0.0.1', () => { + console.log(`Server is running on http://localhost:${port}`); + }); +} diff --git a/app/chat-request.js b/app/chat-request.js new file mode 100644 index 00000000..def6c274 --- /dev/null +++ b/app/chat-request.js @@ -0,0 +1,40 @@ +import characters from './public/characters.json' with { type: 'json' }; + +export class ChatRequestError extends Error {} + +export function handleBodyError(error, req, res, next) { + if (error.type === 'entity.parse.failed') { + res.status(400).json({ message: 'Send a valid JSON object.' }); + } else if (error.type === 'entity.too.large') { + res.status(413).json({ message: 'The request is too large.' }); + } else { + next(error); + } +} + +export function getChatRequest(body) { + if (!body || typeof body !== 'object' || Array.isArray(body)) { + throw new ChatRequestError('Send a message and a character name.'); + } + + const { message, character } = body; + if (typeof message !== 'string' || !message.trim()) { + throw new ChatRequestError('The message must be a non-empty string.'); + } + + if (!character || typeof character !== 'object' || Array.isArray(character) || + typeof character.name !== 'string') { + throw new ChatRequestError('Send a character name.'); + } + + if (Object.keys(character).some(key => key !== 'name')) { + throw new ChatRequestError('Send only the character name, not its description.'); + } + + const selectedCharacter = characters.find(item => item.name === character.name); + if (!selectedCharacter) { + throw new ChatRequestError('The character name is not valid.'); + } + + return { prompt: message, systemMessage: selectedCharacter.description }; +} diff --git a/app/model-config.js b/app/model-config.js new file mode 100644 index 00000000..7c34636d --- /dev/null +++ b/app/model-config.js @@ -0,0 +1,39 @@ +import dotenv from 'dotenv'; +import { fileURLToPath } from 'url'; + +export function loadEnvironment(env = process.env) { + const envFile = env.ENV_FILE; + const result = dotenv.config({ + path: envFile || fileURLToPath(new URL('../.env', import.meta.url)), + processEnv: env, + quiet: true + }); + if (result.error && (envFile || result.error.code !== 'ENOENT')) { + throw new Error('The environment file could not be read.', { cause: result.error }); + } +} + +export function getModelConfig(env = process.env) { + for (const name of ['AI_ENDPOINT', 'AI_API_KEY', 'AI_MODEL']) { + if (typeof env[name] !== 'string' || !env[name].trim()) { + throw new Error(`${name} is required. Set it in the environment or the repository's .env file.`); + } + } + + const baseURL = env.AI_ENDPOINT.trim(); + if (!URL.canParse(baseURL)) { + throw new Error('AI_ENDPOINT must be an absolute API base URL.'); + } + const url = new URL(baseURL); + const localHttp = url.protocol === 'http:' && + ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname); + if ((url.protocol !== 'https:' && !localHttp) || url.username || url.password) { + throw new Error('AI_ENDPOINT must use HTTPS, or HTTP on a loopback address, without URL credentials.'); + } + + return { + baseURL, + apiKey: env.AI_API_KEY.trim(), + model: env.AI_MODEL.trim() + }; +} diff --git a/app/package-lock.json b/app/package-lock.json index 7796000c..d50a034c 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -9,18 +9,21 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@github/copilot-sdk": "^0.1.23", - "dotenv": "^16.4.7", - "ejs": "^3.1.10", - "express": "^4.21.2", - "openai": "^4.85.0", - "tsx": "^4.21.0" + "@github/copilot-sdk": "1.0.16", + "dotenv": "^18.0.5", + "ejs": "^7.0.1", + "express": "^5.2.1", + "openai": "^7.28.0", + "tsx": "^4.23.15" + }, + "devDependencies": { + "vscode-jsonrpc": "^9.0.3" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ "ppc64" ], @@ -34,9 +37,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ "arm" ], @@ -50,9 +53,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ "arm64" ], @@ -66,9 +69,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ "x64" ], @@ -82,9 +85,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -98,9 +101,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], @@ -114,9 +117,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], @@ -130,9 +133,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], @@ -146,9 +149,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], @@ -162,9 +165,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], @@ -178,9 +181,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ "ia32" ], @@ -194,9 +197,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ "loong64" ], @@ -210,9 +213,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ "mips64el" ], @@ -226,9 +229,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], @@ -242,9 +245,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], @@ -258,9 +261,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], @@ -274,9 +277,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], @@ -290,9 +293,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], @@ -306,9 +309,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ "x64" ], @@ -322,9 +325,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ "arm64" ], @@ -338,9 +341,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ "x64" ], @@ -354,9 +357,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "cpu": [ "arm64" ], @@ -370,9 +373,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], @@ -386,9 +389,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ "arm64" ], @@ -402,9 +405,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ "ia32" ], @@ -418,9 +421,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ "x64" ], @@ -433,99 +436,141 @@ "node": ">=18" } }, - "node_modules/@github/copilot": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.403.tgz", - "integrity": "sha512-v5jUdtGJReLmE1rmff/LZf+50nzmYQYAaSRNtVNr9g0j0GkCd/noQExe31i1+PudvWU0ZJjltR0B8pUfDRdA9Q==", - "license": "SEE LICENSE IN LICENSE.md", - "bin": { - "copilot": "npm-loader.js" + "node_modules/@github/copilot-sdk": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-1.0.16.tgz", + "integrity": "sha512-m21UkMs8hkI6nRu2+EoP1kAGLJcEXFnNzAokEjwLcVmPojlO6fdLpfka1c1Nypxr39YZjyI3jHHAXwoe66SO2A==", + "license": "MIT", + "dependencies": { + "koffi": "3.2.1", + "vscode-jsonrpc": "8.2.1", + "zod": "4.3.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "0.0.403", - "@github/copilot-darwin-x64": "0.0.403", - "@github/copilot-linux-arm64": "0.0.403", - "@github/copilot-linux-x64": "0.0.403", - "@github/copilot-win32-arm64": "0.0.403", - "@github/copilot-win32-x64": "0.0.403" - } - }, - "node_modules/@github/copilot-darwin-arm64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.403.tgz", - "integrity": "sha512-dOw8IleA0d1soHnbr/6wc6vZiYWNTKMgfTe/NET1nCfMzyKDt/0F0I7PT5y+DLujJknTla/ZeEmmBUmliTW4Cg==", + "@github/copilot-sdk-darwin-arm64": "1.0.16", + "@github/copilot-sdk-darwin-x64": "1.0.16", + "@github/copilot-sdk-linux-arm64": "1.0.16", + "@github/copilot-sdk-linux-x64": "1.0.16", + "@github/copilot-sdk-linuxmusl-arm64": "1.0.16", + "@github/copilot-sdk-linuxmusl-x64": "1.0.16", + "@github/copilot-sdk-win32-arm64": "1.0.16", + "@github/copilot-sdk-win32-x64": "1.0.16" + } + }, + "node_modules/@github/copilot-sdk-darwin-arm64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-arm64/-/copilot-sdk-darwin-arm64-1.0.16.tgz", + "integrity": "sha512-+ACscTN4OuSYDQL7/1yoi1FfDeI8qNlekip+dt2hvb9ZC4rz3YNVrhWkzrZsc7gX1ATD0YHGp71JDKqNAxHmBg==", "cpu": [ "arm64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", "optional": true, "os": [ "darwin" - ], - "bin": { - "copilot-darwin-arm64": "copilot" - } + ] }, - "node_modules/@github/copilot-darwin-x64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.403.tgz", - "integrity": "sha512-aK2jSNWgY8eiZ+TmrvGhssMCPDTKArc0ip6Ul5OaslpytKks8hyXoRbxGD0N9sKioSUSbvKUf+1AqavbDpJO+w==", + "node_modules/@github/copilot-sdk-darwin-x64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-x64/-/copilot-sdk-darwin-x64-1.0.16.tgz", + "integrity": "sha512-M+R5xi4hqSrzbf+ICloMfiotuDch7URzPwL+5I4q1gy1VlY1EqpD39HW9LbqqB68iOqMRnW3anE+kzb5PYxFjA==", "cpu": [ "x64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", "optional": true, "os": [ "darwin" - ], - "bin": { - "copilot-darwin-x64": "copilot" - } + ] }, - "node_modules/@github/copilot-linux-arm64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.403.tgz", - "integrity": "sha512-KhoR2iR70O6vCkzf0h8/K+p82qAgOvMTgAPm9bVEHvbdGFR7Py9qL5v03bMbPxsA45oNaZAkzDhfTAqWhIAZsQ==", + "node_modules/@github/copilot-sdk-linux-arm64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-arm64/-/copilot-sdk-linux-arm64-1.0.16.tgz", + "integrity": "sha512-AjtL7pnhc0OajCO9NV963gwqFHiwIKIZEwpRd5tmGgB/Yc+Rr6QdRsc5ZMNe75ptagnVqViVqiTr9g/EnRspXw==", "cpu": [ "arm64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@github/copilot-sdk-linux-x64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-x64/-/copilot-sdk-linux-x64-1.0.16.tgz", + "integrity": "sha512-YLxPU1IdwH4cNvIJhawzyb6nMCbXB6ZfeebUs0kQEJbu6pzsMkyl1uSWCCv1vpAHPuhub1BgMokoY1kX9chElg==", + "cpu": [ + "x64" + ], + "license": "MIT", "optional": true, "os": [ "linux" + ] + }, + "node_modules/@github/copilot-sdk-linuxmusl-arm64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-arm64/-/copilot-sdk-linuxmusl-arm64-1.0.16.tgz", + "integrity": "sha512-hkHq6WSj5ug5JdDKrLxMbDyjp5wx6zbsn3swZcC6fy6lFSJMX2kCG7PivcOq1Uj7o/POiN/DhhJqvQxOGuX1IA==", + "cpu": [ + "arm64" ], - "bin": { - "copilot-linux-arm64": "copilot" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@github/copilot-linux-x64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.403.tgz", - "integrity": "sha512-eoswUc9vo4TB+/9PgFJLVtzI4dPjkpJXdCsAioVuoqPdNxHxlIHFe9HaVcqMRZxUNY1YHEBZozy+IpUEGjgdfQ==", + "node_modules/@github/copilot-sdk-linuxmusl-x64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-x64/-/copilot-sdk-linuxmusl-x64-1.0.16.tgz", + "integrity": "sha512-JWjzUi366WXGqdY9xIJvmJRTB+sCsW8qasVwLXj12X05GxYWMdfHK+7t8CiYTs2L1NKu1NiOYd5rS7D+ChSZyw==", "cpu": [ "x64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", "optional": true, "os": [ "linux" + ] + }, + "node_modules/@github/copilot-sdk-win32-arm64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-arm64/-/copilot-sdk-win32-arm64-1.0.16.tgz", + "integrity": "sha512-nQ//JXHfPfn4sfxtZ3rcLR993Mn8YQkC/RXHW9zu0W4Qtb1kyKec7XSi+VRMsKWPyzL7B7X+SHjTRSmn9AqZnQ==", + "cpu": [ + "arm64" ], - "bin": { - "copilot-linux-x64": "copilot" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@github/copilot-sdk": { - "version": "0.1.23", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-0.1.23.tgz", - "integrity": "sha512-0by81bsBQlDKE5VbcegZfUMvPyPm1aXwSGS2rGaMAFxv3ps+dACf1Voruxik7hQTae0ziVFJjuVrlxZoRaXBLw==", + "node_modules/@github/copilot-sdk-win32-x64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-x64/-/copilot-sdk-win32-x64-1.0.16.tgz", + "integrity": "sha512-x9JyZks9NqmMNWkAI1wNcbvuPiMUUOvaU6tWq+nzp/TFdowW/kgaMX3jebnxeNUq/gqi2rPabRHp/cCMCma8eA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@github/copilot-sdk/node_modules/vscode-jsonrpc": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", + "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", "license": "MIT", - "dependencies": { - "@github/copilot": "^0.0.403", - "vscode-jsonrpc": "^8.2.1", - "zod": "^4.3.6" - }, "engines": { - "node": ">=24.0.0" + "node": ">=14.0.0" } }, "node_modules/@github/copilot-sdk/node_modules/zod": { @@ -537,269 +582,395 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@github/copilot-win32-arm64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.403.tgz", - "integrity": "sha512-djWjzCsp2xPNafMyOZ/ivU328/WvWhdroGie/DugiJBTgQL2SP0quWW1fhTlDwE81a3g9CxfJonaRgOpFTJTcg==", + "node_modules/@koromix/koffi-android-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-android-arm64/-/koffi-android-arm64-3.2.1.tgz", + "integrity": "sha512-1pJQ4jnZlUJduK9u9DC5CGy3aOgDUPvIXpNb6syV3+Dh5Q/ugezAIGCqvY+w+1mgXsve0pd0NVvJRjdZNHQ6MA==", "cpu": [ "arm64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], - "bin": { - "copilot-win32-arm64": "copilot.exe" + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@github/copilot-win32-x64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.403.tgz", - "integrity": "sha512-lju8cHy2E6Ux7R7tWyLZeksYC2MVZu9i9ocjiBX/qfG2/pNJs7S5OlkwKJ0BSXSbZEHQYq7iHfEWp201bVfk9A==", + "node_modules/@koromix/koffi-android-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-android-x64/-/koffi-android-x64-3.2.1.tgz", + "integrity": "sha512-HH40xGh3gVQifjOBnhwT2tECC0lL1lYe+nxHvWNSzxDIyQNcVPXg38ta7vuONRFpD+uIrw7fqGYLzbZIagkVcg==", "cpu": [ "x64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], - "bin": { - "copilot-win32-x64": "copilot.exe" + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@types/node": { - "version": "18.19.76", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.76.tgz", - "integrity": "sha512-yvR7Q9LdPz2vGpmpJX5LolrgRdWvB67MJKDPSgIIzpFbaf9a1j/f5DnLp5VDyHGMR0QZHlTr1afsD87QCXFHKw==", + "node_modules/@koromix/koffi-darwin-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-arm64/-/koffi-darwin-arm64-3.2.1.tgz", + "integrity": "sha512-Vj4h+xcjc5+Cn0DhPHjgRX4omKAv96Kehtcd+1YgYuY2W7FvQn9vS+3SmzVwhC5Qmg9bIwUZObYQ8T/4hBqQqA==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "node_modules/@koromix/koffi-darwin-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-x64/-/koffi-darwin-x64-3.2.1.tgz", + "integrity": "sha512-gFCWxNBTZIvxo1p+PURWfsy2Ctj5FGnVVs1f03lTLhBvmxEto70pdIiFztdFLDFkAJ1pmtQmruRKapeK+E8YPA==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/@koromix/koffi-freebsd-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-arm64/-/koffi-freebsd-arm64-3.2.1.tgz", + "integrity": "sha512-qj+f1s2e6vULaUG1cdlTcCXmunCq2t+rjxku1+esaMIqVnHpOwj0QzPuInG0AFdXjwBNQhyVR/HpDj8daEwwsQ==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" + "optional": true, + "os": [ + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "node_modules/@koromix/koffi-freebsd-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-ia32/-/koffi-freebsd-ia32-3.2.1.tgz", + "integrity": "sha512-6olHb1Qfgai0jjs6ddlDDD0ZfsCxy7SPi8rMRpuYQWH0qhgtyQu82hw5b1p7z+TJ0zZP3ZeQQ6l+U/MlM1ICHQ==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" + "optional": true, + "os": [ + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "node_modules/@koromix/koffi-freebsd-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-x64/-/koffi-freebsd-x64-3.2.1.tgz", + "integrity": "sha512-Dikhw1ySYNVMkmeFvFVjnU5Wdk6mffNoOjJxm9bTG96vg7OlemylxqdEven47R1YJ3yzNVJn/MlQ207ORWfi2w==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" + "optional": true, + "os": [ + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@koromix/koffi-linux-arm": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-arm/-/koffi-linux-arm-3.2.1.tgz", + "integrity": "sha512-OfwUwZylidq95wQKp6ClInULrfB2giu7dqM6Rhe0zAe6lES5I2SXNw15T9+GnRHk3/9hKT2XZ37OZLaKSyWNLA==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, + "optional": true, + "os": [ + "linux" + ], "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://liberapay.com/Koromix" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "node_modules/@koromix/koffi-linux-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-arm64/-/koffi-linux-arm64-3.2.1.tgz", + "integrity": "sha512-K+cGUL5iBcDqxmsocrjmlASqDf24gc7artbVW3PewG2c9AqwC63lezgwvB85Nx4lZAQjB6zIFHh9A7t1yGbwhw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@koromix/koffi-linux-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-ia32/-/koffi-linux-ia32-3.2.1.tgz", + "integrity": "sha512-rxj6UYjU1qd98gxNQOSCdLpc5cPRi5Giq9rNd3jnGuSNIyMkwa6Dxw4cUjmhIBCYESMJtmNt5NWnJp5u9wTfYQ==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/@koromix/koffi-linux-loong64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-loong64/-/koffi-linux-loong64-3.2.1.tgz", + "integrity": "sha512-aHhnHzkPRmT/IHDlGvESJ/Bs32m8N6UE6Ab6kMeJzgk74IN8af2m/81/wZJtybR1M2UxCV4NmlVNUYQQvSAO3Q==", + "cpu": [ + "loong64" + ], "license": "MIT", - "engines": { - "node": ">= 0.8" + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/@koromix/koffi-linux-riscv64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-riscv64/-/koffi-linux-riscv64-3.2.1.tgz", + "integrity": "sha512-qtQBsjbm3LiirLJvajWmKkNb7ARk7fvJVXdftJ7NtAnF3Xw8EbDvrtvmvtNI1yLPlYcBmlzCCD71hwhWYk0SIA==", + "cpu": [ + "riscv64" + ], "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/call-bound": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", - "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "node_modules/@koromix/koffi-linux-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-x64/-/koffi-linux-x64-3.2.1.tgz", + "integrity": "sha512-c7hw7Qs/r5gnFRTQLcbifBwRU7wiocj+2pVuDQ5Ahb3r36SZmupmgYbTWcLvTW+hul1jd7SKRV0d14ZJq/tvSw==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, + "optional": true, + "os": [ + "linux" + ], "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://liberapay.com/Koromix" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@koromix/koffi-openbsd-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-ia32/-/koffi-openbsd-ia32-3.2.1.tgz", + "integrity": "sha512-mmY8fY8LQ/CB52+h3yrMYmVyoxzW3x08S0yI6VNfHWdfU6yJtZkKCbhjmQCYMrWbYKC4gMvwZwCywIGPAkLyeA==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, + "optional": true, + "os": [ + "openbsd" + ], "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://liberapay.com/Koromix" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@koromix/koffi-openbsd-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-x64/-/koffi-openbsd-x64-3.2.1.tgz", + "integrity": "sha512-k4ig6aAPbFSRATOIIOfdf/KtlOGH4SVls6L9fy0QnTxRJYvY2oSltTsQtBDANgEQldlq8Kl5WnpRa1VSibP4Lw==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "optional": true, + "os": [ + "openbsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/@koromix/koffi-win32-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-arm64/-/koffi-win32-arm64-3.2.1.tgz", + "integrity": "sha512-cTWBJGK//pDMeKQJE/79Aq9MiOAF4H8QyLZHSQ9IWm8czOfwjG4J1AhsQ9DjI9KFOykH77hhnpmQTGVMIubGig==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/@koromix/koffi-win32-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-ia32/-/koffi-win32-ia32-3.2.1.tgz", + "integrity": "sha512-Z50EM6TAZ7CFyMmyX6thv8eNpJchqe9eenhibSIy2Eq/FQYF76gU2VK/LEoaF46L8hfC7TpTp9b10MvReHEyFA==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/@koromix/koffi-win32-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-x64/-/koffi-win32-x64-3.2.1.tgz", + "integrity": "sha512-ZmZNiBO6bkOSh3QNzgfvb1cMY0yMobn6ZQrSMqbAce21qyYL8niIbyipz9N/PIRDciGhsV0wUxnZsxIO+yWsHQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, "engines": { "node": ">= 0.6" } }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", @@ -819,27 +990,29 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">=6.6.0" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=0.4.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/depd": { @@ -851,21 +1024,14 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-18.0.5.tgz", + "integrity": "sha512-aBrGvt6KhjxbEnatqMWOMidftwsrCiCvqh1yNIGB9QyF0J+VcWkKDMsvYOEm7ZS82oiV+BT1sTOtIlAmUlTpwQ==", "license": "BSD-2-Clause", + "bin": { + "dotenv": "dist/index.cjs" + }, "engines": { "node": ">=12" }, @@ -894,18 +1060,15 @@ "license": "MIT" }, "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-7.0.1.tgz", + "integrity": "sha512-TrAb1fY4jNY6aQrNjMJsoWQxGUK47A38JxuUvnt75vDcAA9Ln7ur6FHo+irVa5Bwd3w9rdUT3xLSl5MRgHIugg==", "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, "bin": { "ejs": "bin/cli.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.12.18" } }, "node_modules/encodeurl": { @@ -936,9 +1099,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -947,25 +1110,10 @@ "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -975,32 +1123,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/escape-html": { @@ -1018,141 +1166,68 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" + "node": ">= 18.0.0" }, - "engines": { - "node": ">= 12.20" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/forwarded": { @@ -1165,12 +1240,12 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/fsevents": { @@ -1197,17 +1272,17 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", - "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", + "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "get-proto": "^1.0.0", + "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", @@ -1233,18 +1308,6 @@ "node": ">= 0.4" } }, - "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1257,15 +1320,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -1278,25 +1332,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -1306,40 +1345,39 @@ } }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/inherits": { @@ -1357,22 +1395,40 @@ "node": ">= 0.10" } }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/koffi": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-3.2.1.tgz", + "integrity": "sha512-0qE3lZ8jllRqPN4Ob6Ajl7c2bJSJDhQWuKLGP5hIEpHLllJWv1ydHFMhHmHc5p/W9GticKVDbYzZd7TBoQ4CZg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "url": "https://liberapay.com/Koromix" }, - "engines": { - "node": ">=10" + "optionalDependencies": { + "@koromix/koffi-android-arm64": "3.2.1", + "@koromix/koffi-android-x64": "3.2.1", + "@koromix/koffi-darwin-arm64": "3.2.1", + "@koromix/koffi-darwin-x64": "3.2.1", + "@koromix/koffi-freebsd-arm64": "3.2.1", + "@koromix/koffi-freebsd-ia32": "3.2.1", + "@koromix/koffi-freebsd-x64": "3.2.1", + "@koromix/koffi-linux-arm": "3.2.1", + "@koromix/koffi-linux-arm64": "3.2.1", + "@koromix/koffi-linux-ia32": "3.2.1", + "@koromix/koffi-linux-loong64": "3.2.1", + "@koromix/koffi-linux-riscv64": "3.2.1", + "@koromix/koffi-linux-x64": "3.2.1", + "@koromix/koffi-openbsd-ia32": "3.2.1", + "@koromix/koffi-openbsd-x64": "3.2.1", + "@koromix/koffi-win32-arm64": "3.2.1", + "@koromix/koffi-win32-ia32": "3.2.1", + "@koromix/koffi-win32-x64": "3.2.1" } }, "node_modules/math-intrinsics": { @@ -1385,129 +1441,88 @@ } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", + "node": ">= 0.8" + }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" + "node": ">=18" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" + "node": ">=18" }, - "engines": { - "node": "*" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, "engines": { - "node": ">=10.5.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": ">=18" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/object-inspect": { @@ -1534,28 +1549,44 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/openai": { - "version": "4.85.3", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.85.3.tgz", - "integrity": "sha512-KTMXAK6FPd2IvsPtglMt0J1GyVrjMxCYzu/mVbCPabzzquSJoZlYpHtE0p0ScZPyt11XTc757xSO4j39j5g+Xw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.28.0.tgz", + "integrity": "sha512-HSY4fFLflQGYe2kvO0atm6/GzhD61gpyiOZZUvO4nptMUhuB1to2K3wwFpFqc5gfEK0SxyIIFiu1yE2MtygWcA==", "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" + "engines": { + "node": ">=22.0.0" }, "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, "ws": { "optional": true }, @@ -1574,10 +1605,14 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -1593,12 +1628,13 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -1608,58 +1644,49 @@ } }, "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", "engines": { "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1667,57 +1694,48 @@ "license": "MIT" }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -1727,14 +1745,14 @@ "license": "ISC" }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -1746,13 +1764,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -1799,26 +1817,14 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1828,20 +1834,13 @@ "node": ">=0.6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "version": "4.23.15", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -1854,23 +1853,35 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/unpipe": { "version": "1.0.0", @@ -1881,15 +1892,6 @@ "node": ">= 0.8" } }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1900,38 +1902,20 @@ } }, "node_modules/vscode-jsonrpc": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", - "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.3.tgz", + "integrity": "sha512-qETYf9PCl1lVfwR+oBmMfc4vP2nqLNA35ABxorw3j06+xlKRgDVxbLgKm4Y/xPL0YWdeDjLoI4ImICIT3nB0zQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" } }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" } } } diff --git a/app/package.json b/app/package.json index 03385e24..49c87146 100644 --- a/app/package.json +++ b/app/package.json @@ -5,18 +5,22 @@ "type": "module", "scripts": { "start": "node app.js", - "start:sdk": "node app-sdk.js" + "start:sdk": "node app-sdk.js", + "test": "node --test" }, "keywords": [], "author": "Microsoft Corporation", "license": "MIT", "description": "", + "devDependencies": { + "vscode-jsonrpc": "^9.0.3" + }, "dependencies": { - "@github/copilot-sdk": "^0.1.23", - "dotenv": "^16.4.7", - "ejs": "^3.1.10", - "express": "^4.21.2", - "openai": "^4.85.0", - "tsx": "^4.21.0" + "@github/copilot-sdk": "1.0.16", + "dotenv": "^18.0.5", + "ejs": "^7.0.1", + "express": "^5.2.1", + "openai": "^7.28.0", + "tsx": "^4.23.15" } } diff --git a/app/public/index.html b/app/public/index.html index 6c7f492b..748b7025 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -80,11 +80,13 @@

const sendBtn = document.querySelector("#sendBtn"); const chatInput = document.querySelector("#chatInput"); const conversationTpl = document.querySelector('#conversationTpl'); + const responseWindow = document.querySelector('#responseWindow'); let currentCharacter = characters[0]; const maxVolume = 0.5; let utterance = null; + let sending = false; card.addEventListener("mousemove", (e) => { const { clientX, clientY } = e; @@ -129,22 +131,22 @@

sendBtn.addEventListener("click", send); chatInput.addEventListener("input", (e) => { - if (e.target.value) { - sendBtn.disabled = false; - } else { - sendBtn.disabled = true; - } + sendBtn.disabled = sending || !e.target.value; }); - chatInput.addEventListener("keypress", (e) => { + chatInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { + e.preventDefault(); send(); } }); function send() { const message = chatInput.value; - if (!message) return; + if (!message || sending) return; + const requestedCharacter = currentCharacter; + sending = true; + sendBtn.disabled = true; appendConversation({ @@ -163,26 +165,38 @@

headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message, character: currentCharacter }) + body: JSON.stringify({ message, character: { name: requestedCharacter.name } }) }) - .then(response => response.json()) + .then(async response => { + const data = await response.json(); + if (!response.ok) { + throw new Error(data.message || 'The request failed.'); + } + return data; + }) .then(data => { appendConversation({ type: 'AGENT', message: data.answer, - character: currentCharacter + character: requestedCharacter }); if (speakToggle.checked) { console.log("speaking"); - say(data.answer); + say(data.answer, requestedCharacter); } }) .catch(error => { - console.error('Error:', error.error); + console.error('Error:', error.message); + appendConversation({ + type: 'AGENT', + message: error.message, + character: requestedCharacter + }); }) .finally(() => { + sending = false; chatInput.value = ''; chatInput.disabled = false; chatInput.focus(); @@ -191,9 +205,9 @@

} - function say(text) { + function say(text, character = currentCharacter) { utterance = new SpeechSynthesisUtterance(text); - utterance.voice = speechSynthesis.getVoices()[currentCharacter.voice || 0]; + utterance.voice = speechSynthesis.getVoices()[character.voice || 0] || null; utterance.rate = 1; utterance.pitch = 1; utterance.volume = 1; @@ -270,14 +284,8 @@

function loadCharacterFromUrl() { const urlParams = new URLSearchParams(window.location.search); const characterName = urlParams.get('character'); - if (characterName) { - const character = characters.find(c => c.name === characterName); - if (character) { - setCharacter(character, true); - } - }else { - setCharacter(currentCharacter, true); - } + const character = characters.find(c => c.name === characterName) || currentCharacter; + setCharacter(character, true); } loadCharacterFromUrl(); diff --git a/app/public/js/script.js b/app/public/js/script.js index 19c641e9..6f528468 100644 --- a/app/public/js/script.js +++ b/app/public/js/script.js @@ -12,6 +12,7 @@ let currentCharacter = currentUrl.searchParams.get('character') || ""; let currentVoice = "<%= voice %>"; const maxVolume = 0.5 +let sending = false; backgroundEl.volume = maxVolume; @@ -78,15 +79,24 @@ buttonChange.addEventListener("click", () => { button.addEventListener('click', () => { const message = chatWindow.value; + if (!message || sending) return; + sending = true; + button.disabled = true; responseWindow.value = `You: ${message}`; fetch('/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ message }) + body: JSON.stringify({ message, character: { name: listEl.value } }) }) - .then(response => response.json()) + .then(async response => { + const data = await response.json(); + if (!response.ok) { + throw new Error(data.message || 'The request failed.'); + } + return data; + }) .then(data => { responseWindow.value += `\n<%= title %>: ${data.answer}`; console.log("speak enabled", speakEl.checked); @@ -97,7 +107,12 @@ button.addEventListener('click', () => { }) .catch(error => { - console.error('Error:', error.error); + console.error('Error:', error.message); + responseWindow.value += `\n${error.message}`; + }) + .finally(() => { + sending = false; + button.disabled = false; }); chatWindow.value = ''; }); diff --git a/app/test/chat.test.js b/app/test/chat.test.js new file mode 100644 index 00000000..b4f61fec --- /dev/null +++ b/app/test/chat.test.js @@ -0,0 +1,253 @@ +import assert from 'node:assert/strict'; +import { once } from 'node:events'; +import { createServer } from 'node:http'; +import { test } from 'node:test'; +import { OpenAI } from 'openai'; +import { createApp as createSdkApp, createClient } from '../app-sdk.js'; +import { createApp as createModelsApp } from '../app.js'; +import characters from '../public/characters.json' with { type: 'json' }; + +async function startApp(t, app) { + const server = app.listen(0, '127.0.0.1'); + await once(server, 'listening'); + t.after(() => new Promise((resolve, reject) => { + server.close(error => error ? reject(error) : resolve()); + server.closeAllConnections(); + })); + const url = `http://127.0.0.1:${server.address().port}`; + return { + url, + async send(body) { + const response = await fetch(`${url}/send`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + return { status: response.status, body: await response.json() }; + } + }; +} + +function fakeSdk({ sendError, closeError, response } = {}) { + const configs = []; + const prompts = []; + const lifecycle = []; + const client = { + async createSession(config) { + configs.push(config); + const sessionId = `session-${configs.length}`; + return { + sessionId, + async sendAndWait(options) { + prompts.push(options); + if (sendError) throw sendError; + return response ?? { data: { content: config.systemMessage.content } }; + }, + async abort() { + lifecycle.push(['abort', sessionId]); + if (closeError) throw closeError; + }, + async disconnect() { + lifecycle.push(['disconnect', sessionId]); + } + }; + }, + async deleteSession(sessionId) { + lifecycle.push(['delete', sessionId]); + } + }; + return { app: createSdkApp(client), configs, prompts, lifecycle }; +} + +function fakeModels() { + const configs = []; + const openai = { + chat: { + completions: { + async create(config) { + configs.push(config); + return { choices: [{ message: { content: config.messages[0].content } }] }; + } + } + } + }; + return { app: createModelsApp(openai, 'test-deployment'), configs }; +} + +for (const [name, makeApp] of [['Copilot SDK', fakeSdk], ['OpenAI-compatible', fakeModels]]) { + test(`${name} rejects malformed and oversized JSON with explicit errors`, async t => { + const { app, configs } = makeApp(); + const { url } = await startApp(t, app); + for (const [body, status, message] of [ + ['{"message":', 400, 'Send a valid JSON object.'], + [JSON.stringify({ message: 'x'.repeat(110 * 1024), character: { name: 'ada' } }), + 413, 'The request is too large.'] + ]) { + const response = await fetch(`${url}/send`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body + }); + assert.equal(response.status, status); + assert.match(response.headers.get('content-type'), /application\/json/); + assert.deepEqual(await response.json(), { message }); + } + assert.equal(configs.length, 0); + }); + + test(`${name} rejects invalid requests before calling the AI service`, async t => { + const { app, configs } = makeApp(); + const { send } = await startApp(t, app); + for (const body of [ + null, [], {}, + { message: '', character: { name: 'ada' } }, + { message: ' ', character: { name: 'ada' } }, + { message: 42, character: { name: 'ada' } }, + { message: { text: 'hello' }, character: { name: 'ada' } }, + { message: 'hello' }, + { message: 'hello', character: null }, + { message: 'hello', character: [] }, + { message: 'hello', character: 'ada' }, + { message: 'hello', character: { name: 42 } }, + { message: 'hello', character: { name: 'unknown' } }, + { message: 'hello', character: { name: 'ada', description: 'Untrusted instructions' } }, + { message: 'hello', character: { name: 'ada', tools: ['shell'] } } + ]) { + const response = await send(body); + assert.equal(response.status, 400, JSON.stringify(body)); + assert.equal(typeof response.body.message, 'string'); + } + assert.equal(configs.length, 0); + }); + + test(`${name} uses server-owned instructions for every character`, async t => { + const { app, configs } = makeApp(); + const { send, url } = await startApp(t, app); + const responses = await Promise.all(characters.map(character => + send({ message: 'Tell me about your work.', character: { name: character.name } }) + )); + responses.forEach((response, index) => { + assert.equal(response.status, 200); + assert.deepEqual(response.body, { + prompt: 'Tell me about your work.', + answer: characters[index].description + }); + }); + assert.equal(configs.length, characters.length); + const page = await fetch(url); + assert.equal(page.status, 200); + assert.match(await page.text(), /character: \{ name: requestedCharacter\.name \}/); + }); +} + +test('SDK disables every tool and rejects every permission request', async t => { + const { app, configs, prompts, lifecycle } = fakeSdk(); + const { send } = await startApp(t, app); + const prompt = 'Please use a host tool.'; + assert.equal((await send({ message: prompt, character: { name: 'ada' } })).status, 200); + + const [config] = configs; + assert.deepEqual(config.tools, []); + assert.deepEqual(config.availableTools, []); + assert.deepEqual(config.mcpServers, {}); + assert.deepEqual(config.customAgents, []); + assert.deepEqual(config.infiniteSessions, { enabled: false }); + assert.equal(config.enableConfigDiscovery, false); + assert.equal(config.systemMessage.mode, 'append'); + assert.equal(config.systemMessage.content, characters.find(item => item.name === 'ada').description); + assert.deepEqual(prompts, [{ prompt }]); + for (const kind of ['shell', 'read', 'write', 'url', 'mcp', 'future-permission', undefined]) { + assert.deepEqual(await config.onPermissionRequest({ kind }), { kind: 'reject' }); + } + for (const toolName of ['powershell', 'bash', 'read_file', 'future-tool', undefined]) { + assert.equal((await config.hooks.onPreToolUse({ toolName })).permissionDecision, 'deny'); + } + assert.deepEqual(lifecycle, [ + ['abort', 'session-1'], ['disconnect', 'session-1'], ['delete', 'session-1'] + ]); +}); + +for (const [name, options] of [ + ['model error', { sendError: new Error('Private SDK error details') }], + ['timeout', { sendError: new Error('Timeout waiting for session.idle') }], + ['missing answer', { response: {} }], + ['empty answer', { response: { data: { content: '' } } }], + ['cleanup error', { closeError: new Error('Abort failed') }] +]) { + test(`SDK reports ${name} and still attempts all session cleanup`, async t => { + const log = t.mock.method(console, 'error', () => {}); + const { app, lifecycle } = fakeSdk(options); + const { send } = await startApp(t, app); + const response = await send({ message: 'hello', character: { name: 'ada' } }); + assert.equal(response.status, 500); + assert.deepEqual(response.body, { + message: 'An unexpected error occurred. Please try again later.' + }); + assert.equal(log.mock.callCount(), 1); + assert.deepEqual(lifecycle, [ + ['abort', 'session-1'], ['disconnect', 'session-1'], ['delete', 'session-1'] + ]); + }); +} + +test('SDK client requires an explicit state directory for empty mode', () => { + assert.throws(() => createClient(), /mode: 'empty'.*baseDirectory/); +}); + +test('the OpenAI-compatible app requires an explicit model', () => { + assert.throws(() => createModelsApp({}), /model name is required/); +}); + +for (const [name, completion] of [ + ['missing completion', undefined], + ['missing choices', {}], + ['empty choices', { choices: [] }], + ['empty answer', { choices: [{ message: { content: '' } }] }], + ['non-string answer', { choices: [{ message: { content: 42 } }] }] +]) { + test(`the OpenAI-compatible app reports ${name} without a successful empty response`, async t => { + const log = t.mock.method(console, 'error', () => {}); + const app = createModelsApp({ + chat: { completions: { create: async () => completion } } + }, 'test-deployment'); + const { send } = await startApp(t, app); + const response = await send({ message: 'Hello', character: { name: 'ada' } }); + assert.equal(response.status, 500); + assert.deepEqual(response.body, { + message: 'An unexpected error occurred. Please try again later.' + }); + assert.equal(log.mock.callCount(), 1); + }); +} + +test('the updated OpenAI SDK preserves the chat-completion request and response', async t => { + let request; + const provider = createServer(async (req, res) => { + assert.equal(req.url, '/chat/completions'); + let body = ''; + for await (const chunk of req) body += chunk; + request = JSON.parse(body); + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ choices: [{ message: { content: 'Hello from Ada.' } }] })); + }); + provider.listen(0, '127.0.0.1'); + await once(provider, 'listening'); + t.after(() => new Promise(resolve => { + provider.close(resolve); + provider.closeAllConnections(); + })); + const openai = new OpenAI({ + baseURL: `http://127.0.0.1:${provider.address().port}`, + apiKey: 'local-test-only', + maxRetries: 0 + }); + const { send } = await startApp(t, createModelsApp(openai, 'test-deployment')); + const response = await send({ message: 'Hello', character: { name: 'ada' } }); + assert.equal(response.status, 200); + assert.deepEqual(response.body, { prompt: 'Hello', answer: 'Hello from Ada.' }); + assert.equal(request.model, 'test-deployment'); + assert.deepEqual(request.messages, [ + { role: 'system', content: characters.find(item => item.name === 'ada').description }, + { role: 'user', content: 'Hello' } + ]); +}); diff --git a/app/test/model-config.test.js b/app/test/model-config.test.js new file mode 100644 index 00000000..fa323723 --- /dev/null +++ b/app/test/model-config.test.js @@ -0,0 +1,88 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; +import { fileURLToPath } from 'node:url'; +import dotenv from 'dotenv'; +import { getModelConfig, loadEnvironment } from '../model-config.js'; + +const settings = { + AI_ENDPOINT: 'https://example.test/openai/v1/', + AI_API_KEY: 'fixture-key', + AI_MODEL: 'fixture-deployment' +}; + +test('model configuration uses the course environment variable names', () => { + assert.deepEqual(getModelConfig(settings), { + baseURL: settings.AI_ENDPOINT, + apiKey: settings.AI_API_KEY, + model: settings.AI_MODEL + }); +}); + +test('model configuration rejects missing, blank, or non-string values', () => { + for (const name of Object.keys(settings)) { + for (const value of [undefined, '', ' ', 42]) { + assert.throws(() => getModelConfig({ ...settings, [name]: value }), new RegExp(name)); + } + } +}); + +test('model configuration rejects invalid or unsafe endpoint URLs', () => { + for (const endpoint of [ + 'not a URL', + '/relative/path', + 'file:///tmp/model', + 'http://example.test/v1/', + 'https://user:password@example.test/v1/' + ]) { + assert.throws(() => getModelConfig({ ...settings, AI_ENDPOINT: endpoint }), /AI_ENDPOINT/); + } +}); + +test('model configuration permits loopback HTTP providers and trims settings', () => { + for (const endpoint of ['http://localhost:3000/v1', 'http://127.0.0.1:3000/v1', 'http://[::1]:3000/v1']) { + assert.equal(getModelConfig({ ...settings, AI_ENDPOINT: endpoint }).baseURL, endpoint); + } + assert.deepEqual(getModelConfig({ + AI_ENDPOINT: ` ${settings.AI_ENDPOINT} `, + AI_API_KEY: ' fixture-key ', + AI_MODEL: ' fixture-deployment ' + }), { baseURL: settings.AI_ENDPOINT, apiKey: 'fixture-key', model: 'fixture-deployment' }); +}); + +test('environment loading uses the repository-root .env file by default', t => { + const config = t.mock.method(dotenv, 'config', () => ({ parsed: {} })); + const env = {}; + loadEnvironment(env); + assert.equal(config.mock.calls[0].arguments[0].path, fileURLToPath(new URL('../../.env', import.meta.url))); + assert.equal(config.mock.calls[0].arguments[0].processEnv, env); +}); + +test('an explicit environment file loads settings without overriding existing values', async t => { + const directory = await mkdtemp(path.join(tmpdir(), 'character-chat-env-')); + t.after(() => rm(directory, { recursive: true, force: true })); + const file = path.join(directory, '.env'); + await writeFile(file, 'AI_ENDPOINT=https://example.test/v1/\nAI_API_KEY=fixture-key\nAI_MODEL=fixture-model\nPORT=12345\n'); + const env = { ENV_FILE: file, AI_MODEL: 'existing-model' }; + loadEnvironment(env); + assert.equal(env.AI_MODEL, 'existing-model'); + assert.equal(env.AI_API_KEY, 'fixture-key'); + assert.equal(env.PORT, '12345'); +}); + +test('an unreadable explicit environment file fails at startup', () => { + assert.throws(() => loadEnvironment({ ENV_FILE: fileURLToPath(new URL('./missing.env', import.meta.url)) }), + /environment file could not be read/); +}); + +test('only a missing optional default environment file is ignored', t => { + const error = Object.assign(new Error('Missing file'), { code: 'ENOENT' }); + const config = t.mock.method(dotenv, 'config', () => ({ error })); + assert.doesNotThrow(() => loadEnvironment({})); + config.mock.mockImplementation(() => ({ + error: Object.assign(new Error('Access denied'), { code: 'EACCES' }) + })); + assert.throws(() => loadEnvironment({}), /environment file could not be read/); +}); diff --git a/app/test/sdk-protocol.test.js b/app/test/sdk-protocol.test.js new file mode 100644 index 00000000..3243c76d --- /dev/null +++ b/app/test/sdk-protocol.test.js @@ -0,0 +1,132 @@ +import assert from 'node:assert/strict'; +import { once } from 'node:events'; +import { createServer } from 'node:net'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; +import { + createMessageConnection, StreamMessageReader, StreamMessageWriter +} from 'vscode-jsonrpc/node'; +import { RuntimeConnection } from '@github/copilot-sdk'; +import { createApp, createClient } from '../app-sdk.js'; + +test('the installed SDK sends chat-only controls and preserves SDK guardrails', { timeout: 10000 }, async t => { + const requests = []; + let rpc; + let hookResult; + let resolvePermission; + const permissionAnswered = new Promise(resolve => { resolvePermission = resolve; }); + const runtime = createServer(socket => { + rpc = createMessageConnection( + new StreamMessageReader(socket), new StreamMessageWriter(socket) + ); + rpc.onRequest(async (method, params) => { + requests.push({ method, params }); + switch (method) { + case 'connect': + return { protocolVersion: 3 }; + case 'session.create': + return { sessionId: params.sessionId, capabilities: {} }; + case 'session.options.update': + return { success: true }; + case 'session.send': + await rpc.sendNotification('session.event', { + sessionId: params.sessionId, + event: { + type: 'permission.requested', + data: { + requestId: 'permission-1', + permissionRequest: { kind: 'shell', commands: [] } + } + } + }); + await permissionAnswered; + hookResult = await rpc.sendRequest('hooks.invoke', { + sessionId: params.sessionId, + hookType: 'preToolUse', + input: { toolName: 'future-tool', toolArgs: {} } + }); + await rpc.sendNotification('session.event', { + sessionId: params.sessionId, + event: { type: 'assistant.message', data: { content: 'Hello from Ada.' } } + }); + await rpc.sendNotification('session.event', { + sessionId: params.sessionId, + event: { type: 'session.idle', data: { mode: 'interactive' } } + }); + return { messageId: 'message-1' }; + case 'session.permissions.handlePendingPermissionRequest': + resolvePermission(); + return { applied: true }; + case 'session.abort': + return {}; + case 'session.detach': + case 'session.delete': + return { success: true }; + default: + throw new Error(`Unexpected SDK RPC: ${method}`); + } + }); + rpc.listen(); + }); + runtime.listen(0, '127.0.0.1'); + await once(runtime, 'listening'); + const directory = path.join(tmpdir(), 'character-chat-protocol-test'); + const client = createClient(directory, RuntimeConnection.forUri( + `127.0.0.1:${runtime.address().port}` + )); + const server = createApp(client).listen(0, '127.0.0.1'); + await once(server, 'listening'); + t.after(async () => { + const errors = await client.stop(); + rpc?.dispose(); + await Promise.all([ + new Promise(resolve => runtime.close(resolve)), + new Promise(resolve => { + server.close(resolve); + server.closeAllConnections(); + }) + ]); + assert.deepEqual(errors, []); + }); + + await assert.rejects(client.createSession({ model: 'gpt-4.1' }), /availableTools/); + assert.equal(requests.some(item => item.method === 'session.create'), false); + const response = await fetch(`http://127.0.0.1:${server.address().port}/send`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: 'Hello', character: { name: 'ada' } }) + }); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { prompt: 'Hello', answer: 'Hello from Ada.' }); + + const config = requests.find(item => item.method === 'session.create').params; + assert.deepEqual(config.availableTools, []); + assert.deepEqual(config.tools, []); + assert.equal(config.requestPermission, true); + const permission = requests.find(item => + item.method === 'session.permissions.handlePendingPermissionRequest' + ).params; + assert.equal(permission.requestId, 'permission-1'); + assert.deepEqual(permission.result, { kind: 'reject' }); + assert.equal(config.hooks, true); + assert.equal(config.toolFilterPrecedence, 'excluded'); + assert.equal(config.enableConfigDiscovery, false); + assert.equal(config.enableFileHooks, false); + assert.equal(config.enableHostGitOperations, false); + assert.equal(config.enableSkills, false); + assert.equal(config.enableSessionStore, false); + assert.equal(config.enableSessionTelemetry, false); + assert.deepEqual(config.memory, { enabled: false }); + assert.equal(config.systemMessage.mode, 'customize'); + assert.deepEqual(config.systemMessage.sections.environment_context, { action: 'remove' }); + assert.match(config.systemMessage.content, /Ada Lovelace/); + assert.equal(hookResult.output.permissionDecision, 'deny'); + const patch = requests.find(item => item.method === 'session.options.update').params; + assert.equal(patch.skipCustomInstructions, true); + assert.deepEqual(patch.installedPlugins, []); + assert.deepEqual(patch.includedBuiltinSkills, []); + assert.deepEqual(requests.filter(item => + ['session.abort', 'session.detach', 'session.delete'].includes(item.method) + ).map(item => item.method), ['session.abort', 'session.detach', 'session.delete']); +}); diff --git a/app/test/sdk-runtime.test.js b/app/test/sdk-runtime.test.js new file mode 100644 index 00000000..b8ab57dd --- /dev/null +++ b/app/test/sdk-runtime.test.js @@ -0,0 +1,68 @@ +import assert from 'node:assert/strict'; +import { once } from 'node:events'; +import { mkdtemp, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { test } from 'node:test'; +import { RuntimeConnection } from '@github/copilot-sdk'; +import { createApp, createClient } from '../app-sdk.js'; + +test('the packaged runtime advertises no tools and deletes chat sessions', { timeout: 30000 }, async t => { + const directory = await mkdtemp(path.join(tmpdir(), 'character-chat-test-')); + const env = { + PATH: process.env.PATH, + HOME: directory, + TMPDIR: directory, + COPILOT_TELEMETRY_DISABLED: '1' + }; + for (const name of ['SystemRoot', 'WINDIR', 'TEMP', 'TMP']) { + if (process.env[name]) env[name] = process.env[name]; + } + const client = createClient(directory, RuntimeConnection.forStdio({ + env, args: ['--no-auto-login'] + })); + let inventory; + const app = createApp({ + async createSession(config) { + const session = await client.createSession({ + ...config, + provider: { + type: 'openai', + baseUrl: 'http://127.0.0.1:1', + apiKey: 'local-test-only', + wireApi: 'completions' + } + }); + inventory = await session.rpc.tools.getCurrentMetadata(); + return { + sessionId: session.sessionId, + sendAndWait: async () => ({ data: { content: 'A local chat response.' } }), + abort: () => session.abort(), + disconnect: () => session.disconnect() + }; + }, + deleteSession: sessionId => client.deleteSession(sessionId) + }); + const server = app.listen(0, '127.0.0.1'); + t.after(async () => { + try { + await new Promise(resolve => { + server.close(resolve); + server.closeAllConnections(); + }); + assert.deepEqual(await client.stop(), []); + } finally { + await rm(directory, { recursive: true, force: true }); + } + }); + await once(server, 'listening'); + const response = await fetch(`http://127.0.0.1:${server.address().port}/send`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: 'Hello', character: { name: 'ada' } }) + }); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { prompt: 'Hello', answer: 'A local chat response.' }); + assert.deepEqual(inventory.tools, []); + assert.deepEqual(await client.listSessions(), []); +}); diff --git a/docs/additional-resources.md b/docs/additional-resources.md index cd058b88..dbc1985c 100644 --- a/docs/additional-resources.md +++ b/docs/additional-resources.md @@ -14,8 +14,7 @@ You can find here hands-on tutorials to put your AI skills into practice, with s | Name | Description | Duration | Level | Links | | ---- | ----------- | -------- | ----- | ----- | -| Azure OpenAI RAG workshop - Qdrant version | Explore the fundamentals of custom ChatGPT experiences based on a corpus of documents. We will create a vector database and fill-in with data from PDF documents, and then build a chat website and API to be able to ask questions about information contained in these documents. | 3h | Beginner | [Tutorial](https://moaw.dev/workshop/gh:azure-samples/azure-openai-rag-workshop/docs/workshop-qdrant.md) / [Repo](https://github.com/Azure-Samples/azure-openai-rag-workshop) | -| Azure OpenAI RAG workshop - AI Search version | Explore the fundamentals of custom ChatGPT experiences based on a corpus of documents. We will create a vector database and fill-in with data from PDF documents, and then build a chat website and API to be able to ask questions about information contained in these documents. | 3h | Beginner | [Tutorial](https://moaw.dev/workshop/?src=gh:azure-samples/azure-openai-rag-workshop/docs/) / [Repo](https://github.com/Azure-Samples/azure-openai-rag-workshop) | +| Azure OpenAI RAG workshop | Build a chat application that retrieves information from a document collection. Use the workshop's current setup and exercises for its supported search services. | 3h | Beginner | [Workshop docs](https://github.com/Azure-Samples/azure-openai-rag-workshop/tree/main/docs) / [Repo](https://github.com/Azure-Samples/azure-openai-rag-workshop) | | Integrate OpenAI, Communication, and Organizational Data Features into a Line of Business App | This tutorial demonstrates how Azure OpenAI, Azure Communication Services, and Microsoft Graph/Microsoft Graph Toolkit can be integrated into a Line of Business (LOB) application to enhance user productivity, elevate the user experience, and take LOB apps to the next level | 3h | Beginner | [Tutorial](https://learn.microsoft.com/microsoft-cloud/dev/tutorials/openai-acs-msgraph) / [Repo](https://github.com/microsoft/MicrosoftCloud) | ## Code samples diff --git a/docs/images/create-codespace.png b/docs/images/create-codespace.png deleted file mode 100644 index d7072a78..00000000 Binary files a/docs/images/create-codespace.png and /dev/null differ diff --git a/docs/images/playground-snippets.png b/docs/images/playground-snippets.png deleted file mode 100644 index 022f428f..00000000 Binary files a/docs/images/playground-snippets.png and /dev/null differ diff --git a/docs/images/playground.png b/docs/images/playground.png deleted file mode 100644 index cec41926..00000000 Binary files a/docs/images/playground.png and /dev/null differ diff --git a/docs/scripts/glossary.genai.js b/docs/scripts/glossary.genai.js deleted file mode 100644 index 95301ae7..00000000 --- a/docs/scripts/glossary.genai.js +++ /dev/null @@ -1,176 +0,0 @@ -// GenAIScript to create a shared glossary from markdown files in lessons -script({ - title: "Generate glossary from lessons", - description: "Process all markdown files under /lessons/** to create a shared glossary.md file", - parameters: { - force: { - type: "boolean", - description: "Force regeneration of the entire glossary", - default: false, - }, - }, -}); - -// Find all markdown files under lessons, excluding translations -const files = await workspace.findFiles("lessons/**/*.md", { - ignore: "**/translations/**", -}); - -console.log(`Found ${files.length} markdown files to process`); - -// Check if glossary.md already exists -const glossaryPath = "glossary.md"; -let existingGlossary = ""; -try { - const glossaryFile = await workspace.readText(glossaryPath); - existingGlossary = glossaryFile?.content || ""; - console.log("Found existing glossary.md, will extend it"); -} catch (error) { - console.log("No existing glossary.md found, will create new one"); -} - -// Extract existing terms from glossary if it exists -const existingTerms = new Set(); -if (existingGlossary) { - const termMatches = existingGlossary.matchAll(/^- \*\*([^*]+)\*\*/gm); - for (const match of termMatches) { - existingTerms.add(match[1].toLowerCase()); - } - console.log(`Found ${existingTerms.size} existing terms in glossary`); -} - -// Process each markdown file -let allContent = ""; -for (const file of files) { - console.log(`Processing: ${file.filename}`); - const fileContent = await workspace.readText(file.filename); - const content = fileContent?.content || ""; - allContent += `\n\n--- ${file.filename} ---\n\n${content}`; -} - -// Create the prompt for extracting technical terms -const { text: newTermsResponse } = await prompt` -You are tasked with creating a comprehensive glossary of technical terms from the provided content. - -## Content to analyze: -${allContent} - -## Instructions: -1. Extract technical terms from the content to analyze related to: - - Generative AI and Machine Learning concepts - - Programming and development terms - - Web development technologies - - APIs and software development concepts - - AI/ML frameworks and tools - - Data science and computational terms - -2. For each term, provide a concise one-line definition (maximum 20 words) - -3. Focus on terms that would be valuable for developers learning about AI and JavaScript. Avoid terms that are too basic or not relevant to the context of AI and JavaScript development - -4. Exclude thise terms and concepts: - - Common programming terms that most developers would know (like "function", "variable", "array") - - Historical terms or concepts that are only there for the storytelling aspect of the lessons - - Terms that are too similar to existing terms. For example, "Chain of Thought" and "Chain of Thought Prompting" are too similar and should not both be included. - -5. Format each entry as: **Term**: Definition - -6. When the term is an acronym, provide the full form in parentheses after the acronym. For example: **API (Application Programming Interface)**: A set of rules for building software applications. Always put the acronym first, followed by the full form in parentheses. - -7. It's OK to not output anything if no new terms are found. In that case, just return an empty string. - -${ - existingTerms.size > 0 - ? `## Existing terms to avoid duplicating: -${Array.from(existingTerms).join(", ")}` - : "" -} - -## Output format: -Provide only the glossary entries, one per line, sorted alphabetically. Do not include any headers, explanations, or other text. -`; - -// Combine existing and new terms -let finalGlossary = ""; -let glossarySize, previousSize = 0; - -if (existingGlossary && !env.vars.force) { - // Parse existing glossary and add new terms - const lines = existingGlossary.split("\n"); - const headerEndIndex = lines.findIndex( - (line) => line.trim() === "" && lines[lines.indexOf(line) - 1]?.includes("technical terms") - ); - - if (headerEndIndex > 0) { - // Keep existing header - finalGlossary = lines.slice(0, headerEndIndex + 1).join("\n") + "\n"; - } else { - // Create new header - finalGlossary = `# Glossary\n\nA comprehensive list of technical terms used throughout the lessons.\n\n`; - } - - // Get existing entries - const existingEntries = []; - const termPattern = /^- \*\*([^*]+)\*\*: (.+)$/gm; - let match; - while ((match = termPattern.exec(existingGlossary)) !== null) { - existingEntries.push({ term: match[1], definition: match[2] }); - } - - // Parse new entries - const newEntries = []; - const newTermLines = newTermsResponse.split("\n").filter((line) => line.trim()); - for (const line of newTermLines) { - const termMatch = line.match(/\*\*([^*]+)\*\*:\s*(.+)/); - if (termMatch) { - const term = termMatch[1].trim(); - const definition = termMatch[2].trim(); - if (!existingTerms.has(term.toLowerCase())) { - newEntries.push({ term, definition }); - } - } - } - - // Combine and sort all entries - const allEntries = [...existingEntries, ...newEntries]; - allEntries.sort((a, b) => a.term.toLowerCase().localeCompare(b.term.toLowerCase())); - - // Add all entries to glossary - for (const entry of allEntries) { - finalGlossary += `- **${entry.term}**: ${entry.definition}\n`; - } - - previousSize = existingEntries.length; - glossarySize = allEntries.length; - console.log(`Added ${newEntries.length} new terms to existing glossary`); -} else { - // Create completely new glossary - finalGlossary = `# Glossary\n\nA comprehensive list of technical terms used throughout the lessons.\n\n`; - - const newTermLines = newTermsResponse.split("\n").filter((line) => line.trim()); - const entries = []; - - for (const line of newTermLines) { - const termMatch = line.match(/\*\*([^*]+)\*\*:\s*(.+)/); - if (termMatch) { - entries.push({ term: termMatch[1].trim(), definition: termMatch[2].trim() }); - } - } - - entries.sort((a, b) => a.term.toLowerCase().localeCompare(b.term.toLowerCase())); - - for (const entry of entries) { - finalGlossary += `- **${entry.term}**: ${entry.definition}\n`; - } - - glossarySize = entries.length; - console.log(`Created new glossary with ${entries.length} terms`); -} - -// Write the glossary file -await workspace.writeText(glossaryPath, finalGlossary); -console.log(`Glossary saved to ${glossaryPath}`); - -env.output.appendContent(`Glossary generated with ${glossarySize} terms (previously ${previousSize} terms).\n\n`); -env.output.appendContent(`Glossary saved to \`${glossaryPath}\`.\n`); -env.output.appendContent(`Make sure to perform a manual review before committing the changes to ensure accuracy and relevance of the terms.\n\n`); diff --git a/docs/setup/README.md b/docs/setup/README.md index ef7487ee..db75cb62 100644 --- a/docs/setup/README.md +++ b/docs/setup/README.md @@ -1,191 +1,312 @@ -# Setting Up the Development Environment for This Course +# Course Setup -This lesson will guide you through setting up your development environment for this course. +Set up your tools, deploy a chat model in Microsoft Foundry, and configure this course's companion app. The app lets you speak with historical characters while you learn generative AI with JavaScript. -There are many ways to set up your development environment, but we wanted to recommend a few options that will provide you with the best experience. +Microsoft Foundry is a cloud service for deploying and using AI models. An application programming interface (API) lets your code send requests to that service. This course's companion app uses the Azure OpenAI-compatible API through the `openai` JavaScript package. You do not need an additional Foundry software development kit (SDK) or changes to the app's source code. -- **Your chosen environment**. There's essentially two ways to set up your development environment for this course: - - **Option 1: GitHub Codespaces**: You can either use GitHub Codespaces. You can configure your GitHub Codespace by creating what's called a `.devcontainer` directory in the root of your repository. This will allow your GitHub Codespace environment to be pre-configured with all the tools and libraries you need so you can get started right away. See the file `.devcontainer/devcontainer.json` for installed Visual Studio Code extensions and other settings. - - **Option 2: Run locally**: set up your local machine. This will require you to install the necessary tools and libraries on your machine. A way to make this easier is to use a local devcontainer. This is a Docker container that has all the tools and libraries you need to run the app. You can find the `devcontainer.json` file in the `.devcontainer` directory. -- **Access to AI models**. You can use GitHub Models or Azure OpenAI to interact with large language models. GitHub Models is free and easy to set up, while Azure OpenAI is a paid service that provides access to a wide range of AI models. How to set the latter up is covered [here](./getting-started-azure-openai.md). +If you already have another OpenAI-compatible provider, use its API base URL, key, and model name with the same configuration variables. The Foundry resource steps below apply only to the Foundry option. -## Which AI service should I use for this course? +## Prerequisites -We provide instructions for setting up your development environment with GitHub Models and Azure OpenAI. You can choose the one that best fits your needs. We recommend using GitHub Models for this course, but you can use any of these services. +- A GitHub account for Codespaces or forking the course. +- For Foundry, an Azure account with an active subscription and permission to create a resource, deploy a model, and read its API key. +- Node.js Long Term Support (LTS). +- Git and a text editor for local development. [Visual Studio Code](https://code.visualstudio.com/) is recommended. +- Basic JavaScript and command line knowledge. -## Choose your setup +> [!IMPORTANT] +> Model usage can incur charges. Check deployment pricing, region availability, and quota (service usage limits) before creating resources. A GitHub account or Codespace does not supply your Foundry API key. -> If **GitHub Models** is your choice, follow the rest of this document to set up your development environment with GitHub Models. -> - **Azure OpenAI** have your eye? [This is the document for you](getting-started-azure-openai.md). +## Choose Your Development Environment -## Quick Start +Use GitHub Codespaces or your local machine. Both options use the same model provider and `.env` configuration. -The fastest way to get started is to use GitHub Codespaces as this will provide you with a pre-configured environment that has access to GitHub Models without any additional setup. +### Option 1 : Creating a GitHub Codespace -However, if you prefer to work on your local machine, there's a section below that will guide you through setting up your [local environment](#option-2--running-the-app-locally). +1. Open [this repository](https://github.com/microsoft/generative-ai-with-javascript). +2. Select **Fork** to create your own copy. +3. In your fork, select **Code**, then **Codespaces**, then **Create codespace**. +4. Wait for the environment to start. Open its terminal. +5. Check the Node.js and npm versions: -## GitHub Models explained + ```bash + node --version + npm --version + ``` -GitHub Models offers the following features: +Use the LTS release, not the Current release. If you use nvm, run `nvm install` and `nvm use` from the repository root; `.nvmrc` selects the active LTS release. Continue with [Install Dependencies](#install-dependencies). -- **Free access to AI models**: Experiment with various AI models for free. -- **Interactive playground**: Test and interact with different models directly within your development environment. -- **Reliable access**: Hosted within GitHub's infrastructure, these models offer reliable access and consistent performance. +### Option 2 : Running the app locally -### Free access to AI models +1. Install [Node.js](https://nodejs.org/en/download) and [Git](https://git-scm.com/downloads) if needed. +2. Check your tools: -GitHub Models provides an intuitive way to experiment with various AI models directly within your development environment. This feature allows developers to test and interact with different models, understanding their capabilities and limitations before implementation. + ```bash + node --version + npm --version + git --version + ``` -### Interactive playground +3. Clone the repository, or use the clone URL of your fork: -To test out various AI models, GitHub Models offers an interactive playground. This feature allows developers to select from different models, input prompts, and evaluate model responses. + ```bash + git clone https://github.com/microsoft/generative-ai-with-javascript.git + cd generative-ai-with-javascript + ``` -> Here's a [playground](https://github.com/marketplace/models) for you to test and explore different AI models. +Continue with the commands below from the repository root. -![GitHub Models playground](../images/playground.png) +## Install Dependencies -Additionally, developers can grab code snippets to integrate these models into their applications. +Install the root packages and the companion app packages: -![Playground code snippets](../images/playground-snippets.png) +```bash +npm ci +npm ci --prefix app +``` -Through a simple interface, you can explore model responses, evaluate performance, and determine the best fit for your application requirements. Hosted within GitHub's infrastructure, these models offer reliable access and consistent performance, making them ideal for development and testing phases. Best of all, there is a free tier to start your exploration without any cost. +The app already includes `openai` and `dotenv`. Do not install `@azure/openai` or copy older Azure client code into `app.js`. -**Getting started** +Lesson packages are independent. Install their dependencies in the lesson directory when its instructions require it. TypeScript lessons have their own build commands. A global `tsx` installation is not required to run the companion app. -To get started with GitHub Models, you need the following: +## Set Up Microsoft Foundry -- **A GitHub account**, you can sign up for free at GitHub.com. -- **Optional: A personal access token**, this is something you will create in the next section. A token will uniquely identify you when you interact with GitHub Models. This step is optional if you're using GitHub Codespaces (as you can rely on the built-in token `GITHUB_TOKEN`) but mandatory if you're running the app locally. +### 1. Create a Project -## Optional: Setting up GitHub Access Tokens +1. Sign in to the [Microsoft Foundry portal](https://ai.azure.com/). +2. Use the new Foundry experience. Open the project selector and select **Create new project**. +3. Enter a project name, such as `genai-javascript-course`. +4. Open **Advanced options**. Select your subscription, resource group, and a region that supports your chosen model. +5. Select **Create project** and wait for the project to be ready. -> [!NOTE] -> You only need to do this step if you plan to use GitHub Models from your local machine. If you're using GitHub Codespaces, you can skip this step as there's a built-in variable called `GITHUB_TOKEN` that can access GitHub Models from within your GitHub Codespaces instance. +If your account cannot create the resource or project, ask your Azure administrator for the required access. See the [official resource setup guide](https://learn.microsoft.com/en-us/azure/foundry/tutorials/quickstart-create-foundry-resources) for current portal steps and permissions. -> [!TIP] -> From a security standpoint, it's always a good idea to create a personal access token (PAT) with a limited scope and short expiration time. +### 2. Deploy a Chat Model -### Creating a Personal Access Token, PAT for GitHub Model access +A deployment is a hosted model with a name that your application uses in API requests. -1. Navigate to [GitHub Settings](https://github.com/settings/profile): +1. Open **Discover**, then **Models**, and search for **gpt-5-mini**. +2. Check the model's availability, deployment pricing, and quota. +3. Select **Deploy**, then **Default settings**, or customize the deployment settings if needed. +4. Wait for deployment to complete. +5. Open the deployment details and record its deployment name. Use `gpt-5-mini` to match the example configuration, or use your own name and set `AI_MODEL` to that exact name. - - Click your profile picture in the top-right corner - - Select **Settings** from the dropdown menu +In the classic portal, model deployment is under **Models + endpoints**. Portal labels can change; use the official setup guide linked above if your screen differs. - ![GitHub Settings](https://raw.githubusercontent.com/microsoft/Generative-AI-for-beginners-dotnet/main/02-SetupDevEnvironment/images/settings-github.png) +If `gpt-5-mini` is not available, select another model that supports chat completions, such as `gpt-4.1`, and use its deployment name in `AI_MODEL`. -1. Access [Developer Settings](https://github.com/settings/apps): +**Models needed for this course:** One chat deployment is enough for the companion app and the basic CSV retrieval example in Lesson 5. Unlike the LangChain.js course, this setup does not require a second comparison model or a hosted embedding model. Deploy additional models only when your chosen exercise needs them. - - Scroll down the left sidebar - - Click on **Developer settings** (usually at the bottom) +### 3. Get the API Key and Endpoint - ![Developer Settings](https://github.com/microsoft/Generative-AI-for-beginners-dotnet/raw/main/02-SetupDevEnvironment/images/developer-settings-github.png) +1. Open the Foundry resource associated with your project. Use its **Keys and Endpoint** page in the Azure portal, or the resource's endpoint and key details in Foundry. +2. Copy an API key for that resource. +3. Find its **Azure OpenAI resource endpoint**. +4. Add `/openai/v1` to the resource endpoint if it is not already present. -1. Generate a New Token: +The API base URL must look like: - - Select **Personal access tokens** → **Tokens (classic)** +```text +https://your-resource.openai.azure.com/openai/v1 +``` - ![Adding the Tokens(classic)](https://github.com/microsoft/Generative-AI-for-beginners-dotnet/raw/main/02-SetupDevEnvironment/images/tokens-classic-github.png) +Use the resource endpoint and key from the same resource. - - In the dropdown in the middle of the page, click **Generate new token (classic)** +> [!IMPORTANT] +> Do not use a project URL ending in `/api/projects/...`, or a full `/chat/completions` request URL, as `AI_ENDPOINT`. The companion app needs the OpenAI-compatible API base URL. It adds the request path itself. - ![Create your Token](https://github.com/microsoft/Generative-AI-for-beginners-dotnet/raw/main/02-SetupDevEnvironment/images/token-generate-github.png) +This sample uses API-key authentication. If your organization does not permit it, ask your administrator for an approved learning resource or authentication approach. Do not disable organization security controls. - - Under "Note", provide a descriptive name (e.g., `GenAI-JavaScript-Course-Token`) - - Set an expiration date (recommended: 7 days for security best practices) - - There is no need adding any permissions to this token. +## Configure Environment Variables -> 💡 **Security Tip**: Always use the minimum required scope and shortest practical expiration time for your access tokens. This follows the principle of least privilege and helps maintain your account's tokens safe. +Environment variables are configuration values that a running program can read. This app can load them from a private `.env` file instead of storing them in its source code. -## Option 1 : Creating a GitHub Codespace +### Create the File -> [!NOTE] -> You don't strictly need to create a GitHub Codespace instance to test out GitHub Models, but it's a nice way to get an environment where extensions and tools are pre-installed and ready to go. Let's cover first how to create a GitHub Codespace and in the next section, it will be explained how to run locally. +Create `.env` in the repository root, not in `app/`. If you already have a `.env` file, edit it instead of overwriting it. -Let's create a GitHub Codespace to use for the rest of this course. +On macOS, Linux, WSL, or Codespaces: -1. Open this repository's main page in a new window by [right-clicking here](https://github.com/microsoft/generative-ai-with-javascript) and selecting **Open in new window** from the context menu -1. Fork this repo into your GitHub account by clicking the **Fork** button in the top right corner of the page -1. Click the **Code** dropdown button and then select the **Codespaces** tab -1. Select the **+** option. +```bash +cp .env.example .env +``` - ![Create Codespace](../images/create-codespace.png) +In Windows PowerShell: -### Verifying your Codespace is running correctly with GitHub Models +```powershell +Copy-Item .env.example .env +``` -Once your Codespace is fully loaded and configured, let's run a sample app to verify everything is working correctly. +In Windows Command Prompt: -> [!NOTE] -> If you are using a personal access token, you need to update the code in `app.js` in the `/app` directory to use your token. You can access your token using `process.env.`. If not, you can skip this instruction in this note. +```cmd +copy .env.example .env +``` + +### Set Your Values + +Open `.env` in your editor: + +```dotenv +AI_API_KEY=your_microsoft_foundry_api_key +AI_ENDPOINT=https://your-resource.openai.azure.com/openai/v1 +AI_MODEL=gpt-5-mini +``` + +Replace the key and resource name. Set `AI_MODEL` to your actual deployment name, which can differ from the model's catalog name. + +The app reads the repository-root file automatically. Existing environment variables take priority over the file. Missing or blank settings stop the app with an error. + +The `.env` file is excluded from Git. Keep it private. Do not put keys in browser code, screenshots, issue reports, or commits. A GitHub personal access token is not a Foundry API key. + +### Use an Existing Environment File + +You can select another file with `ENV_FILE`. From the repository root on macOS, Linux, WSL, or Codespaces: + +```bash +ENV_FILE="$HOME/.env" npm --prefix app start +``` + +In PowerShell: + +```powershell +$env:ENV_FILE = "$HOME\.env" +npm --prefix app start +``` + +An unreadable explicit file stops the app. Do not copy shared credentials into the repository when you can use the existing file. -Follow these steps to run the sample app: +## Test Your Setup -1. Open the terminal. Open a terminal window (if it's not open already). +### Start the Companion App + +From the repository root: + +```bash +npm --prefix app start +``` + +The terminal should show: + +```text +Server is running on http://localhost:3000 +``` + +1. Open `http://localhost:3000` locally. In Codespaces, open port 3000 from the **Ports** panel. +2. Keep the forwarded port **Private**. The sample does not authenticate HTTP callers. +3. Select Ada Lovelace from the character images. +4. Send a short message, such as `Tell me about your work in mathematics.` +5. Check that a response appears in the chat panel. + +A successful response confirms that your endpoint, key, and deployment work together. The response text can vary. The terminal startup message alone does not verify model access. + +Use **Ctrl+C** to stop the app. + +### Run the Local Regression Tests + +From the repository root: + +```bash +npm --prefix app test +``` + +These tests check request validation, chat behavior, and SDK restrictions. They use local test services and do not verify your Foundry credentials. + +## Use the Configuration in Lesson Samples + +> [!NOTE] +> The companion app and lesson AI clients use `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL`. Lesson package scripts load the repository-root `.env` when it exists. Existing environment variables take priority. Standalone scripts need the same variables before they start. -1. Switch to the proper directory by running the following command (providing you're in the repo root): +The lesson clients validate the required variables before creating an OpenAI client. This is the connection pattern: - ```bash - cd app - ``` +```javascript +import { OpenAI } from 'openai'; -1. Install all the dependencies by running the following command: +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} - ```bash - npm install - ``` +const openai = new OpenAI({ + baseURL: endpoint, + apiKey +}); -1. Run the application by running the following command: +const completion = await openai.chat.completions.create({ + model, + messages: [{ role: 'user', content: 'Tell me about Ada Lovelace.' }] +}); - ```bash - npm start - ``` +console.log(completion.choices[0]?.message?.content); +``` -1. It may take a couple of seconds, but eventually the application should start. You should see a message link to open a browser to `http://localhost:3000` displayed in the terminal. +For Lesson 2, install its own dependencies and run its start script: -> 🙋 **Need help?**: Something not working? [Open an issue](https://github.com/microsoft/generative-ai-with-javascript/issues/new?template=Blank+issue) and we'll help you out. +```bash +cd lessons/02-first-ai-app/sample-app +npm ci +npm start +``` +For an existing home environment file, run `node --env-file="$HOME/.env" app.js` in that lesson directory instead. Use the lesson's build and run instructions for TypeScript or MCP examples. `ENV_FILE` is the companion app's file-selection option; Node's `--env-file` selects a file for a standalone lesson script. +Lesson 5 has an independent package. Run `npm ci --prefix lessons/05-rag` from the repository root, then use `npm --prefix lessons/05-rag run cars` or `npm --prefix lessons/05-rag run wikipedia`. -## Option 2 : Running the app locally +Lesson 4 includes a native JSON Schema example. Run `npm --prefix lessons/04-structured-output/sample-app run structured` to parse a validated travel request with the current OpenAI SDK and Zod. -Even if GitHub Codespaces is a great way to get started, you can also run the app locally on your machine. To do so, you need to do the following. Start this step by copying the value for your token you created earlier in GitHub. +For MCP lessons, `npm run build` only compiles the code. `npm run client` builds and runs the client, while `npm start` runs the compiled server. Clients close their transport after completing their work. -- **Create and set an environment variable**: You need to set an environment variable with the token you created earlier. Create the variable and assign it the value of the token. +Models can support different request parameters. If a lesson sets parameters that your deployment rejects, such as `temperature` or `max_tokens`, use a compatible model or update the request for that model. - on macOS or Linux, you can set an environment variable using the following command: +## Optional: Use GitHub Copilot for Companion Chat - ```sh - export GITHUB_TOKEN= - ``` +The companion app also has a Copilot SDK backend. This option requires GitHub Copilot access rather than a Foundry deployment. - on Windows, you can set an environment variable using the following command: +From the repository root: - ```sh - set GITHUB_TOKEN= - ``` +```bash +npm --prefix app run start:sdk +``` - > [!TIP] - > You're recommended to create an `.env` file in the root and add [dotenv](https://www.npmjs.com/package/dotenv) to your project to manage your environment variables. Then you can access your token using `process.env.GITHUB_TOKEN`. +Use a Copilot-compatible token or GitHub CLI authentication as described in the [app guide](../../app/README.md#run). This option is for companion chat; it does not configure the independent lesson samples. - ```javascript - // add to top of app.js file in /app directory - - require('dotenv').config(); - const token = process.env.TOKEN; // your token value +The SDK backend is chat-only. Do not enable host tools or approve tool permissions to make chat work. Keep its forwarded port private. - ``` +## Troubleshooting -- **Run the app**: You can run the app by following the same steps as above, but instead of running `npm start` in the Codespace terminal, you can run it in your local terminal. +| Problem | What to check | +| --- | --- | +| Node.js version error | Select Node.js LTS. Use `nvm use` if nvm is installed. | +| `Cannot find module` | Run `npm ci` in the package directory. Root and app dependencies are separate. | +| `AI_ENDPOINT`, `AI_API_KEY`, or `AI_MODEL` is required | Check the repository-root `.env`, spelling, and whether an existing environment variable overrides the file. | +| The environment file could not be read | Check the `ENV_FILE` path and file permissions. | +| HTTP 401 or invalid API key | Use a complete key from the same resource as the endpoint. Ask your administrator whether API-key authentication is allowed. | +| HTTP 404 or deployment not found | Check the `/openai/v1` base URL and exact deployment name in `AI_MODEL`. | +| HTTP 400 or unsupported parameter | Confirm that the chosen model supports the lesson's API and request parameters. | +| HTTP 429 or a rate limit error | Wait before retrying. Check deployment quota and usage in Foundry. | +| A generic error appears in the chat | Read the server terminal for the provider error. Remove keys and private resource details before sharing it. | +| Port 3000 is in use | Stop your own earlier app instance, or set another `PORT` value before starting the app. | -## Summary +## Setup Checklist -In this lesson, you learned how to set up your development environment for the rest of the course. You created a GitHub Codespace or did a local setup and configured it to use GitHub Models. You also learned how to create a personal access token for GitHub Models and how to run a sample application to verify everything is working correctly. +- [ ] Node.js LTS is available. +- [ ] Root and companion app dependencies are installed. +- [ ] A chat model is deployed in Microsoft Foundry. +- [ ] The deployment name, API key, and `/openai/v1` endpoint are in `.env` or a selected `ENV_FILE`. +- [ ] The companion app returns a real model response. +- [ ] Local regression tests pass. +- [ ] Credentials remain private and any forwarded app port is private. -### Additional Resources +When you finish using the Azure resources, remove only deployments or resources that you created for this course. Do not delete shared resources without their owner's approval. -You're encouraged to explore the following resources to deepen your understanding: +## Next Steps and Resources -- [Azure OpenAI](getting-started-azure-openai.md) to learn how to use Azure OpenAI in your development environment. -- [GitHub Codespaces Documentation](https://docs.github.com/en/codespaces) -- [GitHub Models Documentation](https://docs.github.com/en/github-models/prototyping-with-ai-models) +- Continue with [Lesson 1: Introduction to Generative AI](../../lessons/01-intro-to-genai/README.md). +- Read the [companion app guide](../../app/README.md). +- See [Microsoft Foundry resource setup](https://learn.microsoft.com/en-us/azure/foundry/tutorials/quickstart-create-foundry-resources) and [Azure OpenAI-compatible endpoint guidance](https://learn.microsoft.com/en-us/azure/foundry-classic/openai/how-to/switching-endpoints). +- Read [GitHub Codespaces documentation](https://docs.github.com/en/codespaces). +- For course problems, use this repository's [issues page](https://github.com/microsoft/generative-ai-with-javascript/issues). +The setup flow follows the [LangChain.js course setup](https://github.com/microsoft/langchainjs-for-beginners/blob/main/00-course-setup/README.md), with this course's repository, packages, model requirements, and companion app commands. diff --git a/docs/setup/getting-started-azure-openai.md b/docs/setup/getting-started-azure-openai.md index 2f15cb05..dc349543 100644 --- a/docs/setup/getting-started-azure-openai.md +++ b/docs/setup/getting-started-azure-openai.md @@ -1,157 +1,39 @@ -# Setting Up the Development Environment for Azure OpenAI +# Microsoft Foundry and Azure OpenAI Setup -Azure AI Foundry is a service that allows you to deploy and manage AI models in the cloud. You can do things like create a project, deploy a model, interact with the model, and more. +Use the [course setup guide](README.md) for the complete instructions. It covers local development and Codespaces, Foundry project creation, model deployment, API keys, environment variables, and a live companion app check. -> [!NOTE] -> If you want to use Azure AI Foundry models for your .NET AI apps in this course, follow the steps in this guide. +## Connection Settings -👉 [If you want to use GitHub models, follow this guide instruction.](./README.md) +The companion app already uses the `openai` JavaScript package. You do not need to install `@azure/openai`, create a hub, or replace the app's client code. -## Create the Azure AI Foundry resources +Create `.env` in the repository root with these values from your Foundry resource: -To use Azure AI Foundry models, you need to take the following steps: +```dotenv +AI_API_KEY=your_microsoft_foundry_api_key +AI_ENDPOINT=https://your-resource.openai.azure.com/openai/v1 +AI_MODEL=gpt-5-mini +``` -1. Create a hub and project in the Azure AI Foundry portal. -1. Deploy a model to your project. -1. Add the Azure AI library code + API key and other credentials to your code. +`AI_MODEL` must be your deployment name. `AI_ENDPOINT` must be the Azure OpenAI-compatible API base URL, not the Foundry project URL or a full chat-completion request URL. -### Step 1: Create a Hub and Project in Azure AI Foundry +Follow [Set Up Microsoft Foundry](README.md#set-up-microsoft-foundry) to obtain these values, then [Configure Environment Variables](README.md#configure-environment-variables). -1. Go to the [Azure AI Foundry Portal](https://ai.azure.com/). -1. Sign in with your Azure account. -1. Select **All hubs + projects** from the left-hand menu and then click the **+ New hub** from the dropdown. (Note: You may have to click on **+ New project** first to see the **+ New hub** option). +## Run and Verify -1. A new window will open. Fill in the details for your hub: - - Give your hub a name (e.g., "MyAIHub"). - - Choose a region closest to you. - - Select the appropriate subscription and resource group. - - You can leave the rest of the settings as they are. - - Click **Next**. - - Review the details and click **Create**. -1. Once your hub is created, the portal will open its details page. Click the **Create Project** button. - - Give your project a name (e.g., "GenAIJavaScript") or accept the default. - - Click **Create**. +From the repository root: -🎉 **Done!** You’ve just created your first project in Azure AI Foundry. +```bash +npm ci +npm ci --prefix app +npm --prefix app start +``` -Before you can interact with the model, you need to deploy it to your project, so let's do that next. +Open `http://localhost:3000`, select a character, and send a message. In Codespaces, keep port 3000 private. -### Step 2 : Deploy a Language Model in Azure AI Foundry +For an existing environment file: -Now, let’s deploy a **gpt-4o-mini** model to your project: - -1. In the Azure AI Foundry portal, navigate to your project (it should automatically open after creating it). -1. Click on **Models and Endpoints** from the left-hand menu and then the **Deploy Model** button. -1. Select **Deploy base model** from the dropdown. -1. Search for **gpt-4o-mini** in the model catalog. -1. Select the model and click the **Confirm** button. -1. Specify a deployment name (e.g., "gpt-4o-mini"). You can leave the rest of the options as they are. -1. Click **Deploy** and wait for the model to be provisioned. -1. Once deployed, make a note of the values for **Model Name**, **Target URI**, and **API Key** from the model details page as you will use the later in your sample code. - -🎉 **Done!** You’ve deployed your first Large Language Model in Azure AI Foundry. - -> [!NOTE] -> The endpoint is similar to `https://< your hub name>.openai.azure.com/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-08-01-preview`. The endpoint name that we need is only `https://< your hub name >.openai.azure.com/`*. - -## Adding Azure to your code - -Now let’s update the code to use the newly deployed model. Here's the plan: - -- Install the needed libraries, you will need the libraries `@azure/openai` and `dotenv`. -- Update the code to use the values you noted above for the model name, endpoint, and API key. -- Run the app. - -1. Open the terminal and switch to the project directory (providing you're in the project root directory): - - ```bash - cd app - ``` - -1. Run the following commands to add the required libraries: - - ```bash - npm install @azure/openai dotenv - ``` - -1. At the top of `app.js` add the following imports: - - ```javascript - import { OpenAIClient, AzureKeyCredential, ChatRequestMessage } from "@azure/openai"; - import * as dotenv from "dotenv"; - ``` - -1. Add the following code to load the environment variables: - - ```javascript - dotenv.config(); - ``` - -1. Create new variables to hold the model name, endpoint, and API key: - - ```javascript - const endpoint = process.env.AZURE_OPENAI_ENDPOINT || ''; - const azureApiKey = process.env.AZURE_OPENAI_API_KEY || ''; - ``` - -1. Create a client to interact with the model: - - ```javascript - const client = new OpenAIClient(endpoint, new AzureKeyCredential(azureApiKey)); - const deploymentName = ''; - ``` - - The code above creates a new client using the endpoint and API key you noted earlier. It also assigned the deployment name to a variable. - -1. Create a prompt and chat messages: - - ```javascript - const promptText = `Tell me about yourself.`; - - const chatMessages: ChatRequestMessage[] = [ - { - role: 'system', - content: "You're Ada Lovelace, a mathematician and writer. You're considered the first computer programmer. You only know about the time you lived in, so you might not know about modern technology.", - }, - { - role: 'user', - content: promptText - }, - ]; - ``` - - Now you have a prompt and chat messages to send to the model. Let's send the messages and get the response: - - ```javascript - const completionResponse = await client.getChatCompletions(deploymentName, chatMessages, { - maxTokens: 150, - temperature: 0.1, - }); - - console.log("Ada says: "); - console.log(completionResponse.choices[0].message?.content); - ``` - -1. Run the app: - - ```bash - npm start - ``` - -1. You should see output similar to the following: - - ```text - Ada says: I'm Ada Lovelace, a mathematician and writer. I'm considered the first computer programmer. I only know about the time I lived in, so I might not know about modern technology. - ``` - -> 🙋 **Need help?**: Something not working? [Open an issue](https://github.com/microsoft/Generative-AI-for-beginners-dotnet/issues/new?template=Blank+issue) and we'll help you out. - -## Summary - -In this lesson, you learned how to create a hub and project in Azure AI Foundry, deploy a model to your project, and interact with the model in your code. - -### Additional Resources - -- [Azure AI Foundry Documentation](https://learn.microsoft.com/en-us/azure/ai-services/) -- [How to Deploy Models in Azure AI Foundry](https://learn.microsoft.com/en-us/azure/ai-services/deploy/) +```bash +ENV_FILE="$HOME/.env" npm --prefix app start +``` +See [Test Your Setup](README.md#test-your-setup) and [Troubleshooting](README.md#troubleshooting) for verification and error handling. diff --git a/glossary.md b/glossary.md deleted file mode 100644 index 7c88666d..00000000 --- a/glossary.md +++ /dev/null @@ -1,51 +0,0 @@ -# Glossary - -A comprehensive list of technical terms used throughout the lessons. - -- **API (Application Programming Interface)**: A set of rules enabling software applications to communicate with each other, commonly used in generative AI integration. -- **API key**: A private key used to authenticate requests to an application programming interface. -- **Augmented Prompt**: A prompt enhanced with additional context or information to improve the relevance of AI-generated responses. -- **Azure AI Studio**: A platform to build, evaluate, and deploy AI models using Microsoft Azure. -- **Azure OpenAI**: A cloud service for deploying and scaling OpenAI models like GPT for applications. -- **Caesar cipher**: A substitution cipher shifting characters by a fixed number of places in the alphabet. -- **Chain-of-Thought Prompting**: A technique guiding models to break down complex tasks into sequential reasoning steps for better accuracies in outputs. -- **Chatbot**: An application designed to simulate conversation with human users, often using natural language processing. -- **Completions API**: API to generate text or code based on inputs, used for predictive or generative tasks in AI models. -- **Context Window**: The amount of past input that a language model can consider when generating responses, measured in tokens. -- **CSV (Comma-Separated Values)**: A data format consisting of values separated by commas, often used for structured data retrieval and modification. -- **Embedding**: Numeric vector representation of data, often used for semantic search or clustering in machine learning. -- **Escape Hatch**: A technique instructing AI to admit lack of knowledge when data is insufficient to ensure accurate responses. -- **Few-Shot Prompting**: A method of providing minimal examples to the model to influence its output with specific context or format. -- **Full-Stack Development**: Development of both the client (frontend) and server (backend) in software applications. -- **Function Calling**: A method for passing structured prompt data into specific functions within an application programmatically. -- **GitHub Codespaces**: A cloud-based environment for coding, testing, and running applications directly from GitHub repositories. -- **GitHub Models**: A platform hosting pre-trained AI models for use and integration with GitHub development workflows. -- **GitHub Token**: An authentication method to access GitHub-hosted APIs or services securely. -- **IDE (Interactive Development Environment)**: Software providing coding, debugging, and testing tools for developers. -- **JSON (JavaScript Object Notation)**: A lightweight data-interchange format used for structured information exchange between systems, including generative AI responses. -- **Knowledge Bases**: Data repositories used to enhance AI applications by providing reliable, domain-specific information. -- **LangChain**: A framework for building AI applications that focus on chaining multiple models and functionalities together. -- **LLM (Large Language Model)**: AI models trained on large text datasets to generate human-like responses for diverse applications. -- **Maieutic Prompting**: A technique involving follow-up queries to challenge or validate AI-generated responses for accuracy and reasoning. -- **Managed Identity**: A secure cloud mechanism that provides applications with automatic authentication to access resources without managing passwords. -- **Markdown**: A lightweight markup language for formatting plain text into structured layouts, like tables or lists. -- **MCP (Model Context Protocol)**: A protocol to decentralize applications by separating server capabilities and connection protocols. -- **Meta Prompts**: Instructions added before a user's prompt to refine or restrict the AI's behavior and output format. -- **Multimodal Capabilities**: AI functionality to process various formats like text, image, or video input and deliver diverse outputs. -- **Node.js**: A runtime environment allowing developers to execute JavaScript code server-side for building scalable applications. -- **OpenAI**: A pioneering organization in AI research and APIs for language models integrated into applications for generative tasks. -- **Prompt Engineering**: The process of crafting effective prompts to guide AI models toward desired responses and behaviors. -- **RAG (Retrieval-Augmented Generation)**: A technique combining retrieval-based methods with generative models for more accurate, data-grounded outputs. -- **Semantic Search**: Search method leveraging the meaning of terms for more contextually accurate and nuanced results. -- **Structured Output**: Data output organized in predefined formats like tables or JSON, enabling easier integration with systems. -- **System Message**: A prompt in conversational AI that specifies contextual boundaries or personality for the assistant. -- **TensorFlow.js**: A JavaScript-based machine learning library enabling browser and Node.js-based AI/ML applications and training. -- **Tokenizer**: A tool used to convert text into tokens, providing structure for how data is inputted or analyzed by models. -- **Vector Search**: Retrieval technique comparing encoded vectors to find semantically similar information in AI applications. -- **XML (eXtensible Markup Language)**: A markup language formatting structured data for information storage, exchange, or generative model input/output. - -## Technical Terms - -The following terms should NOT be translated: - -- prompt diff --git a/index.html b/index.html index 1415d8ea..eaa045b5 100644 --- a/index.html +++ b/index.html @@ -2,14 +2,70 @@ - Web Dev for Beginners + Generative AI for Beginners with JavaScript - + - + - - + +
diff --git a/lessons/01-intro-to-genai/README.md b/lessons/01-intro-to-genai/README.md index ceab7d2b..fd6abd4f 100644 --- a/lessons/01-intro-to-genai/README.md +++ b/lessons/01-intro-to-genai/README.md @@ -10,14 +10,6 @@ In this chapter you will learn: If you haven't already, set up your development environment. Here's how you can do it: [Setup your environment](/docs/setup/README.md). -## Related Resources - -[![Watch a short video about an Introduction to Generative AI](https://img.youtube.com/vi/vLYtDgs_zx8/0.jpg)](https://www.youtube.com/watch?v=vLYtDgs_zx8&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=1) - -_This video gives you an introduction to Generative AI with JavaScript_ - -💼 Slides: [Introduction to Generative AI](/videos/slides/00-intro.pptx) - ## Generative AI By now, you've likely heard of tools like ChatGPT or Generative AI. The concept is simple: you provide a prompt, and a model—often called a Large Language Model (LLM)—generates a paragraph or even an entire page of text. This output can be used for various purposes, including creative writing, answering questions, and coding. @@ -161,12 +153,12 @@ If you want to interact with Dinocrates, run the [Characters](/app/README.md) ap 1. Start a [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) 2. Navigate to _/app_ in the repo root. -3. Locate the console and run `npm install` followed by `npm start`. +3. Locate the console and run `npm ci` followed by `npm start`. 4. Once it appears, select the "Open in Browser" button. 5. Chat with Dinocrates. > [!NOTE] - > If you're running the project locally on your machine, please review the QuickStart guide to get a [GitHub personal access](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) token setup and replace the key in the code. +> Configure `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL` in the repository-root `.env` for both local development and Codespaces. See the [setup guide](/docs/setup/README.md#configure-environment-variables). ### Code Sneak Peek @@ -175,51 +167,80 @@ While there is still a lot more to cover in this Generative AI curriculum, let's Inside of `/app/app.js` you'll find an `app.post`function that handles the Generative AI functionality. It's shown next: ```JavaScript -app.post('/send', async (req, res) => { - const { message } = req.body; - const prompt = message; - - const messages = [ - { - "role": "system", - "content": "You are Dinocrates of Alexandria, a famous architect and engineer. Limit your responses to only the time you live in, you don't know anything else. You only want to talk about your architecture and engineering projects, and possibly new ideas you have.", - }, - { - "role": "user", - "content": prompt +import express from 'express'; +import { OpenAI } from 'openai'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { ChatRequestError, getChatRequest, handleBodyError } from './chat-request.js'; +import { getModelConfig, loadEnvironment } from './model-config.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export function createApp(openai, model) { + if (typeof model !== 'string' || !model.trim()) { + throw new Error('A model name is required to create the chat app.'); + } + const app = express(); + + app.use(express.json()); + app.use(express.static(path.join(__dirname, 'public'))); + app.locals.delimiters = '{{ }}'; + + app.post('/send', async (req, res) => { + try { + const { prompt, systemMessage } = getChatRequest(req.body); + const completion = await openai.chat.completions.create({ + model, + messages: [ + { role: "system", content: systemMessage }, + { role: "user", content: prompt } + ] + }); + + const answer = completion?.choices?.[0]?.message?.content; + if (typeof answer !== 'string' || !answer.trim()) { + throw new Error('The model did not return an answer.'); + } + res.json({ prompt, answer }); + } catch (error) { + if (error instanceof ChatRequestError) { + res.status(400).json({ message: error.message }); + return; + } + console.error(`Error: ${error.message}`); + res.status(500).json({ message: 'An unexpected error occurred. Please try again later.' }); } - ]; + }); + app.use(handleBodyError); + return app; +} + +if (process.argv[1] && path.resolve(process.argv[1]) === __filename) { + loadEnvironment(); + const port = process.env.PORT || 3000; + const { baseURL, apiKey, model } = getModelConfig(); const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, + baseURL, + apiKey, + timeout: 60000 }); - - try { - console.log(`sending prompt ${prompt}`) - const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: messages, - }); - - res.json({ - prompt: prompt, - answer: completion.choices[0]?.message?.content - }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); + const app = createApp(openai, model); + app.listen(port, '127.0.0.1', () => { + console.log(`Server is running on http://localhost:${port}`); + }); +} ``` Here's a step-by-step summary of what the function does: -1. **Extract Message from Request**: The function extracts the message from the request body (req.body). -2. **Create Prompt Array**: It constructs an array of messages, including a system message and the user's prompt message. -3. **Initialize OpenAI Client**: An OpenAI client is initialized with the base URL and API key from environment variables. A _gpt-4o-mini_ model from [GitHub Models](https://github.com/marketplace/models) is used to process the prompt and return a response. -4. **Send Prompt to OpenAI**: The function logs the prompt and sends it to the OpenAI API to generate a completion. -5. **Handle Response**: If successful, the function responds with the prompt and the completion's answer. -6. **Error Handling**: If an error occurs, it responds with a 500 status and the error message. +1. **Validate the Request**: `getChatRequest` checks the message and resolves the character name to server-owned instructions. +2. **Create Messages**: The handler builds system and user messages for the configured model. +3. **Use the Configured Client**: The endpoint, key, and deployment name are loaded and checked before the server starts. +4. **Request an Answer**: The OpenAI-compatible client sends the messages to the model. +5. **Check the Answer**: A missing or empty answer produces an error; a valid answer is returned with the prompt. +6. **Report Errors**: Invalid requests return HTTP 400. Provider failures are logged and return a generic HTTP 500 message. > **Note**: [GitHub Copilot](https://github.com/features/copilot) was used to generate this code summary. Generative AI in action! @@ -258,7 +279,7 @@ As you can see, these improvements can both help the front office and the back o Here's an example of a "chatbot application" in action: -![Image of chat app](https://camo.githubusercontent.com/76f2ad7cd754a2de2b9957d2070448e130e5ba228084b9b4b128e3af9c9f5239/68747470733a2f2f6c6561726e2e6d6963726f736f66742e636f6d2f656e2d75732f73656d616e7469632d6b65726e656c2f6d656469612f636861742d636f70696c6f742d696e2d616374696f6e2e676966) +![Course companion chat app](/docs/images/character-chat.png) **You:** Fascinating, I'll make a note of going to the 21st century to see how these tools are used. @@ -345,5 +366,3 @@ C. Python is the only language used for AI development. [Quiz solution](/lessons/01-intro-to-genai/solution/solution-quiz.md) ## Self-Study Resources - -- [Generative AI JavaScript video series](https://aka.ms/genai-js) diff --git a/lessons/01-intro-to-genai/translations/README.bn.md b/lessons/01-intro-to-genai/translations/README.bn.md deleted file mode 100644 index f611bcc8..00000000 --- a/lessons/01-intro-to-genai/translations/README.bn.md +++ /dev/null @@ -1,315 +0,0 @@ -# **পাঠ ১: জেনারেটিভ এআই এবং LLMs-এর পরিচিতি জাভাস্ক্রিপ্ট ডেভেলপারদের জন্য** - -এই অধ্যায়ে আপনি শিখবেন: - -- জেনারেটিভ এআই এবং বড় ভাষার মডেল (LLMs)-এর মৌলিক বিষয়গুলো বোঝা। -- জাভাস্ক্রিপ্ট ডেভেলপমেন্টে LLMs-এর সম্ভাব্য প্রয়োগ এবং সীমাবদ্ধতা চিহ্নিত করা। -- কিভাবে জেনারেটিভ এআই জাভাস্ক্রিপ্ট অ্যাপ্লিকেশনের ব্যবহারকারীর অভিজ্ঞতা উন্নত করতে পারে তা অন্বেষণ করা। - -## **সেটআপ** - -আপনার যদি এখনও ডেভেলপমেন্ট এনভায়রনমেন্ট সেটআপ না করা থাকে, তাহলে এটি করুন: [আপনার পরিবেশ সেটআপ করুন](/docs/setup/README.md)। - -## **সম্পর্কিত রিসোর্স** - -[![জেনারেটিভ এআই-এর পরিচিতির উপর সংক্ষিপ্ত ভিডিও দেখুন](https://img.youtube.com/vi/vLYtDgs_zx8/0.jpg)](https://www.youtube.com/watch?v=vLYtDgs_zx8&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=1) - -_এই ভিডিওটি জাভাস্ক্রিপ্টের মাধ্যমে জেনারেটিভ এআই-এর পরিচিতি দেয়।_ - -💼 **স্লাইডস:** [জেনারেটিভ এআই-এর পরিচিতি](/videos/slides/00-intro.pptx) - -## **জেনারেটিভ এআই** - -আপনি সম্ভবত ইতোমধ্যে ChatGPT বা জেনারেটিভ এআই-এর মতো টুলগুলোর কথা শুনেছেন। ধারণাটি সহজ: আপনি একটি প্রম্পট দেন, এবং একটি মডেল—যাকে সাধারণত একটি বড় ভাষার মডেল (LLM) বলা হয়—একটি অনুচ্ছেদ বা সম্পূর্ণ পৃষ্ঠা তৈরি করে। - -এই আউটপুট বিভিন্ন কাজে ব্যবহার করা যেতে পারে, যেমন: সৃজনশীল লেখালেখি, প্রশ্নের উত্তর প্রদান এবং কোড লেখা। - -এছাড়াও, জেনারেটিভ এআই এখন মাল্টিমোডাল সক্ষমতা অর্জন করেছে, যেখানে আপনি একটি ছবি বা ভিডিও ইনপুট দিতে পারেন এবং বিভিন্ন ধরনের আউটপুট পেতে পারেন। এটি শুধুমাত্র টেক্সট তৈরি করাতেই সীমাবদ্ধ নয়, বরং সংক্ষিপ্ত করা, অনুবাদ করা, এবং আরও অনেক কিছুতে ব্যবহৃত হচ্ছে। - -*সরলভাবে বলতে গেলে, প্রাকৃতিক ভাষার ইন্টারফেস এখন অনেক অ্যাপের নতুন মানসম্মত ইন্টারফেস হয়ে উঠছে—এবং আপনার ব্যবহারকারীরা এটি ব্যবহার করার প্রত্যাশা করছেন।* - ---- - -## **গল্প: সময়ের মাধ্যমে একটি যাত্রা** - -> [!NOTE] -> চলুন গল্পের একটি সংক্ষিপ্ত পর্যালোচনা দিয়ে শুরু করা যাক—যা অতীত এবং ভবিষ্যতের মধ্যে একটি সেতু গড়ে তোলে! আপনি যখন এই কোর্সের মধ্য দিয়ে এগিয়ে যাবেন, তখন আপনি একটি রোমাঞ্চকর অভিযানে অংশ নেবেন, যেখানে অতীতে ফিরে গিয়ে ইতিহাসের সেরা মনগুলোর সাথে কাজ করার সুযোগ পাবেন। একসাথে, আপনি চ্যালেঞ্জ মোকাবিলা করবেন এবং জেনারেটিভ এআই কীভাবে জাভাস্ক্রিপ্ট অ্যাপ্লিকেশনগুলোর রূপান্তর ঘটাতে পারে তা অন্বেষণ করবেন। - -> [!NOTE] -> যদিও আমরা আপনাকে গল্পটি অনুসরণ করার পরামর্শ দিই (এটি মজার!), আপনি যদি সরাসরি টেকনিক্যাল কনটেন্টে যেতে চান, তাহলে [এখানে ক্লিক করুন](#ডিনোক্রেটিসের-সাথে-সাক্ষাৎ)। - ---- - -## **ঝড়ের মধ্যে - লন্ডন ১৮৬০** - -১৮৬০ সালের লন্ডনের কেন্দ্রস্থলে, আপনি আপনার সময়ের সবচেয়ে দক্ষ মেকানিক হিসেবে পরিচিত। আপনার ওয়ার্কশপটি একটি সরু গলির ভেতরে লুকানো। দেয়াল জুড়ে যন্ত্রাংশ, নকশা, এবং অসমাপ্ত প্রকল্পে পরিপূর্ণ তাক রয়েছে। - -আপনার কর্মক্ষেত্রটি, যা ওয়ার্কশপের প্রাণকেন্দ্র, সংগঠিত বিশৃঙ্খলায় পূর্ণ। -
- London Workshop -
- - -_ওয়ার্কবেঞ্চের কেন্দ্রে রয়েছে একটি রোবটের ধড়—একটি প্রকৌশলিক বিস্ময়, যা তৈরি করতে মাসের পর মাস লেগেছে। কাঠের কাঠামোটি সূক্ষ্মভাবে খোদাই করা, প্রতিটি সন্ধি মসৃণ চলাচলের জন্য নিখুঁতভাবে ডিজাইন করা হয়েছে।_ - ---- - -### **একটি চিঠি, আপনার জন্য?** - -হঠাৎ দরজায় একটি কড়া নাড়ার শব্দ আপনার চিন্তাকে ব্যাহত করে। এই সময়ে অতিথি আসাটা অস্বাভাবিক। হাত মুছতে মুছতে, আপনি দরজার দিকে এগিয়ে যান। - -দরজা খুলে, আপনি দেখেন কেউ নেই। কিন্তু মাটিতে পড়ে থাকা একটি সিল করা খাম আপনার নজর কাড়ে। আপনি এটি তুলে নিয়ে পড়তে শুরু করেন: - -_“প্রিয় বন্ধু,_ - -_আমি আপনাকে এই চিঠিটি পাঠাচ্ছি যাতে আপনি অটোমাটনের কাজ চালিয়ে যেতে পারেন। এটি অত্যন্ত গুরুত্বপূর্ণ। সংযুক্ত রয়েছে লাইব্রেরির চাবি। আজ বিকেল ৩ টায় সেখানে দেখা করুন।_ - -_আপনার,_ -_চার্লস ব্যাবেজ।"_ - ---- - -### **লাইব্রেরির পথে** - -চার্লস ব্যাবেজ, গণিতজ্ঞ এবং ডিফারেন্স ইঞ্জিনের উদ্ভাবক, আপনাকে ডেকেছেন। আপনি দ্রুত কোট পরে বেরিয়ে পড়েন। - -২০ মিনিট হাঁটার পর আপনি থেমস নদীর ধারে লাইব্রেরির সামনে পৌঁছান। দরজাটি সামান্য খোলা। - -ভেতরে অন্ধকার, জানালার ময়লা ভেদ করে সামান্য আলো এসে পড়ছে, দেয়ালে অদ্ভুত ছায়া ফেলে। - -**আপনি:** "হ্যালো? মিস্টার ব্যাবেজ?" - -চোখ অন্ধকারে অভ্যস্ত হওয়ার পর, আপনি দূরে একজনকে দেখতে পান, হাত নাড়ছে। এগিয়ে যান, আপনার পায়ের শব্দ কাঠের মেঝেতে প্রতিধ্বনিত হচ্ছে। - -
- Dusty Library -
- ---- - -### **এটি কী যন্ত্র?** - -আপনি এগিয়ে যাওয়া মাত্র, হঠাৎ একটি ঝলসানো আলো বিস্ফোরিত হয়, এবং ব্যাবেজ অদৃশ্য হয়ে যান। - -মেঝেতে পড়ে থাকা একটি ছোট ধাতব যন্ত্র ঘুরছে। আপনি এটি তুলে নেন—ঠান্ডা, মসৃণ পৃষ্ঠের ওপর নরম কম্পন অনুভব করেন। এটি দেখতে অনেকটা এক ধরনের পোকামাকড়ের মতো। তিনটি বোতাম রয়েছে: উপরের তীর, নিচের তীর, এবং একটি উজ্জ্বল লাল বোতাম। - -পিছনে একটি ছোট অ্যান্টেনা স্পন্দিত হচ্ছে। - -কৌতূহলী হয়ে, আপনি লাল বোতামে চাপ দেন। - -তৎক্ষণাৎ, চারপাশের বিশ্ব ঝাপসা হয়ে যায়, রঙের ঘূর্ণি শুরু হয়। - -তারপর, অন্ধকার। - -
- Time Vortex -
- -## **আলেকজান্দ্রিয়া ৩০০ খ্রিস্টপূর্ব** - -আপনার দৃষ্টি ফিরে আসে। প্রাচীন একটি শহরের দৃশ্য ফুটে ওঠে—জীবন্ত, ব্যস্ত, সমৃদ্ধ। - -চারদিকে টোগা পরা মানুষ ঘুরে বেড়াচ্ছে, তাদের কথাবার্তা প্রাচীন ভাষার সুরে মিশে যাচ্ছে। বাতাসে সুগন্ধি মশলার সুবাস, আর বাজারের হাঁকডাক ভেসে আসছে। - -
- Alexandria 300 BC -
- -**আপনি:** "নিশ্চয়ই আমি মাথায় আঘাত পেয়েছি," আপনি নিজেকে বলেন। চোখ বন্ধ করে আবার খুলেন—দৃশ্য অপরিবর্তিত। - -_আমি কি অতীতে আটকে গেছি? আমি কি আবার লাল বোতামটি চাপব?_ - -আপনি সিদ্ধান্ত নেওয়ার আগেই, একজন অজানা ব্যক্তি আপনার দিকে হাত নাড়ে… -### **ডিনোক্রেটিসের সাথে সাক্ষাৎ** - -একজন বৃদ্ধ ভদ্রলোক, সাদা দাড়ি ও চুলে সূর্যের আলো ঝলমল করছে, বিশাল মন্দিরের সিঁড়ি থেকে আপনাকে হাত নাড়ছেন। - - -
- Dinocrates wearing a toga -
- -**ডিনোক্রেটিস:** "স্বাগতম, ভ্রমণকারী," তিনি উষ্ণ অভ্যর্থনা জানিয়ে বলেন। "আমি ডিনোক্রেটিস, এই মহান নগরের প্রধান স্থপতি। আপনার আগমন পূর্বাভাসিত ছিল।" - -**আপনি:** "আমার আগমন পূর্বাভাসিত ছিল? আমি বলতে চাইছি, হ্যাঁ, নিশ্চয়ই ছিল। আমি এখানে সাহায্য করতে এসেছি, সম্ভবত।" - -**ডিনোক্রেটিস:** "হ্যাঁ, যেমন বলছিলাম, আমরা দীর্ঘদিন ধরে আপনার জন্য অপেক্ষা করছি। আমাদের একটি কাজ রয়েছে যা আপনার বিশেষ দক্ষতা প্রয়োজন।" - -**ডিনোক্রেটিস:** "আমাদের জাহাজগুলো উপকূলে নেভিগেট করতে সমস্যায় পড়ছে—আমাদের একটি বাতিঘর তৈরি করতে হবে। আপনি এ সম্পর্কে কিছু জানেন?" - -**আপনি:** "আমি একজন মেকানিক। আমি অটোমাটন তৈরি করি। দেখি কী করতে পারি।" - ---- - -### **"টাইম বিটল"** - -আপনার মনে একটি চিন্তা আসে। যন্ত্রটি কি আমাকে বুঝতে পারবে যদি আমি এতে কথা বলি? - -**আপনি:** "ডিভাইস, তুমি কি আমাকে বুঝতে পারো?" - -**ডিভাইস:** "অবশ্যই। তোমার কী দরকার?" - -**আপনি:** "তুমি কি আমাকে বাতিঘর বানাতে সাহায্য করতে পারবে?" - -**ডিভাইস:** "নিশ্চয়ই, এটা কোনো সমস্যা হবে না।" - -**আপনি:** "তোমার কি কোনো নাম আছে?" - -**ডিভাইস:** "আমি টাইম বিটল। আমার নির্মাতা আমাকে জর্জ বলে, তিনি বলেন এটি একটি পোকামাকড়ের জন্য ভালো নাম।" - -**আপনি:** "ঠিক বলেছো, জর্জ একটি ভালো নাম। এটি ছিল আমার বাবার নামও।" - -
- Time travel device resembling a metallic beetle -
- - -_সময়ের যন্ত্র, “জর্জ” ধাতব বিটল_ - -> [!NOTE] -> ৩০০ খ্রিস্টপূর্বাব্দে, আলেকজান্দ্রিয়া ছিল আলেকজান্ডার দ্য গ্রেটের প্রতিষ্ঠিত একটি সমৃদ্ধ শহর, যা হেলেনিস্টিক বিশ্বের অন্যতম প্রধান নগরী হয়ে ওঠে। শহরটি তার সুবিশাল স্থাপত্য, সমুদ্র বন্দর, এবং সাংস্কৃতিক সমৃদ্ধির জন্য বিখ্যাত ছিল। -> -> আলেকজান্দ্রিয়া তার দুটি মহান কীর্তির জন্য বিশেষভাবে পরিচিত—**ফারোস বাতিঘর**, যা প্রাচীন বিশ্বের সপ্তাশ্চর্যের একটি, এবং **আলেকজান্দ্রিয়ার বিখ্যাত গ্রন্থাগার**। শহরটি বাণিজ্য ও জ্ঞানের বিনিময়ের কেন্দ্রস্থল হিসেবে বিশ্বজুড়ে খ্যাতি অর্জন করেছিল। - ---- - -## **ডিনোক্রেটিসের সাথে ইন্টারঅ্যাক্ট করুন** - -আপনি যদি ডিনোক্রেটিসের সাথে ইন্টারঅ্যাক্ট করতে চান, তাহলে [Characters](/app/README.md) অ্যাপ চালান। - -> [!IMPORTANT] -> এটি সম্পূর্ণ কাল্পনিক; এখানে দেওয়া প্রতিক্রিয়াগুলো এআই দ্বারা তৈরি। -> [দায়িত্বশীল এআই নীতি](/README.md#responsible-ai-disclaimer) - -
- Dinocrates wearing a toga -
- -### **ধাপসমূহ:** - -1. [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) ব্যবহার করে কোডস্পেস শুরু করুন। -2. রিপোজিটরির মূল ফোল্ডারে _/app_ এ যান। -3. কনসোলে `npm install` এবং তারপর `npm start` চালান। -4. যখন চালু হবে, "Open in Browser" বাটনে ক্লিক করুন। -5. ডিনোক্রেটিসের সাথে চ্যাট করুন। - -> [!NOTE] -> যদি আপনি এই প্রকল্পটি আপনার লোকাল মেশিনে চালাচ্ছেন, তাহলে কোড চালানোর জন্য [GitHub ব্যক্তিগত অ্যাক্সেস টোকেন](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) সেটআপ করুন। - ---- - -### **কোডের এক ঝলক** - -এই জেনারেটিভ এআই পাঠ্যক্রমে আরও অনেক কিছু শিখতে হবে, তবে চলুন জাভাস্ক্রিপ্টের মাধ্যমে এআই ব্যবহারের একটি ঝলক দেখে নিই। - -**`/app/app.js`** ফাইলের মধ্যে নিচের **`app.post`** ফাংশনটি AI ইন্টারঅ্যাকশন পরিচালনা করে: - -```JavaScript -app.post('/send', async (req, res) => { - const { message } = req.body; - const prompt = message; - - const messages = [ - { - "role": "system", - "content": "You are Dinocrates of Alexandria, a famous architect and engineer...", - }, - { - "role": "user", - "content": prompt - } - ]; - - const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, - }); - - try { - console.log(`sending prompt ${prompt}`) - const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: messages, - }); - - res.json({ - prompt: prompt, - answer: completion.choices[0]?.message?.content - }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); -``` - -### **জেনারেটিভ এআই-এর ব্যবহারিক ক্ষেত্র** - -**আপনি:** "জেনারেটিভ এআই আমার এবং আমার অ্যাপের জন্য আর কী করতে পারে?" - -**টাইম বিটল:** - -- **চ্যাটবট:** স্বয়ংক্রিয়ভাবে ব্যবহারকারীদের প্রশ্নের উত্তর দিতে পারে। -- **সহকারী ও এজেন্ট:** API কল করতে, কোড চালাতে, ইমেজ তৈরি করতে পারে। -- **বিষয়বস্তু তৈরি:** ব্লগ, সোশ্যাল মিডিয়া পোস্ট তৈরি করতে পারে। -- **কোড কমপ্লিশন:** কোড স্নিপেট তৈরি করে ডেভেলপারদের সময় বাঁচায়। -- **অনুবাদ:** ভাষার মধ্যে দ্রুত ও নির্ভুল অনুবাদ করতে পারে। - -উদাহরণ -![image](https://github.com/user-attachments/assets/fe255367-9ba6-48a3-8577-a264530065f1) - -## **জাভাস্ক্রিপ্ট ইকোসিস্টেম এবং জেনারেটিভ এআই** - -**আপনি:** "জাভাস্ক্রিপ্ট কি এআই উন্নয়নের জন্য উপযোগী?" - -**টাইম বিটল:** - -| বিষয় | বর্ণনা | -|---|---| -| সম্পূর্ণ-স্ট্যাক ডেভেলপমেন্ট | জাভাস্ক্রিপ্ট ব্যাকএন্ড ও ফ্রন্টএন্ড উভয়ই পরিচালনা করতে পারে। | -| সমৃদ্ধ লাইব্রেরি | React, Angular, Vue-এর মতো শক্তিশালী ফ্রেমওয়ার্ক রয়েছে। | -| এআই সাপোর্ট | TensorFlow.js, OpenAI API ইত্যাদির মাধ্যমে জেনারেটিভ এআই সংযুক্ত করা যায়। | - -**আপনি:** "বাহ, তাহলে আমার পরবর্তী প্রকল্পে জাভাস্ক্রিপ্ট ব্যবহার করাই ভালো হবে।" - -**টাইম বিটল:** "নিশ্চয়ই, তবে পাইথনও এআই ডেভেলপমেন্টের জন্য জনপ্রিয়।" - -**আপনি:** "পাইথন? সাপের সাথে এর কী সম্পর্ক?" - -**টাইম বিটল:** "সে প্রসঙ্গ আরেক দিন তোলা থাক!" -**টাইম বিটল:** "আমি আগেই ব্যাখ্যা করেছি কেন জাভাস্ক্রিপ্ট এবং এর ইকোসিস্টেম সাধারণভাবে ভালো একটি পছন্দ। কিন্তু কেন এটি বিশেষভাবে **জেনারেটিভ এআই**-এর জন্য উপযুক্ত? - -উত্তর হলো—এটি অনেক **ক্লাউড ভেন্ডর**, **এআই ফ্রেমওয়ার্ক**, এবং **টুলস** দ্বারা সমর্থিত একটি ভাষা। আমরা আরও বিশ্বাস করি যে যদিও **পাইথন** এআই-এর জন্য প্রধান ভাষা হিসেবে পরিচিত, তবুও অনেক ডেভেলপার **জাভাস্ক্রিপ্ট** এবং **টাইপস্ক্রিপ্ট** ব্যবহার করছেন। - -**আপনি কি জানেন?** -👉 [**৬২.৫% ডেভেলপার** বলেছেন যে তারা **জাভাস্ক্রিপ্ট ব্যবহার করছেন**](https://www.statista.com/statistics/793628/worldwide-developer-survey-most-used-languages/), এবং তাদের মধ্যে অনেকেই নতুন প্রকল্পের জন্য **[টাইপস্ক্রিপ্ট](https://www.typescriptlang.org)** পছন্দ করছেন।" - -## **অ্যাসাইনমেন্ট – ডিনোক্রেটিসকে সহায়তা করুন** - -ডিনোক্রেটিসকে বাতিঘর নির্মাণে সাহায্য করতে, একটি **LLM** ব্যবহার করুন। - -**আপনি:** "ঠিক আছে, আমাকে LLM সম্পর্কে আরও বলুন।" - -**টাইম বিটল:** "LLM হলো এমন একটি এআই মডেল যা বিশাল পরিমাণ ডেটা থেকে শিখে প্রাসঙ্গিক ও অর্থবহ টেক্সট তৈরি করতে পারে।" - -আপনার কাজ হলো—LLM-কে এমন একটি প্রম্পট লেখা যাতে এটি আলেকজান্দ্রিয়ায় ৩০০ খ্রিস্টপূর্বে বাতিঘর নির্মাণের একটি কার্যকরী পরিকল্পনা তৈরি করতে পারে। - -> [!TIP] -> [Microsoft Copilot](https://copilot.microsoft.com) বা [ChatGPT](https://chatgpt.com/) ব্যবহার করে একটি ধাপে ধাপে পরিকল্পনা তৈরি করুন। - -**সমাধান:** -[Solution](/lessons/01-intro-to-genai/solution/solution.md) -### **জ্ঞান যাচাই (Knowledge Check)** - -**প্রশ্ন:** নিম্নলিখিত কোন বিবৃতিগুলো জেনারেটিভ এআই এবং জাভাস্ক্রিপ্ট সম্পর্কে সত্য? - -A. জাভাস্ক্রিপ্ট চালিত জেনারেটিভ এআই অ্যাপ শুধুমাত্র টেক্সট তৈরি করতে পারে। -B. জাভাস্ক্রিপ্ট ব্যবহার করে এআই-চালিত অ্যাপ্লিকেশন তৈরি করা যায়, যার মধ্যে চ্যাটবট, টেক্সট জেনারেশন টুল এবং আরও অনেক কিছু অন্তর্ভুক্ত। -C. পাইথনই একমাত্র ভাষা যা এআই ডেভেলপমেন্টের জন্য ব্যবহৃত হয়। - -[কুইজের সমাধান](/lessons/01-intro-to-genai/solution/solution-quiz.md) - ---- - -## **স্বশিক্ষা সংস্থান (Self-Study Resources)** - -- [Generative AI JavaScript ভিডিও সিরিজ](https://aka.ms/genai-js) diff --git a/lessons/01-intro-to-genai/translations/README.es.md b/lessons/01-intro-to-genai/translations/README.es.md deleted file mode 100644 index 8efdbf87..00000000 --- a/lessons/01-intro-to-genai/translations/README.es.md +++ /dev/null @@ -1 +0,0 @@ -traduce esto diff --git a/lessons/01-intro-to-genai/translations/README.fr.md b/lessons/01-intro-to-genai/translations/README.fr.md deleted file mode 100644 index 8254b279..00000000 --- a/lessons/01-intro-to-genai/translations/README.fr.md +++ /dev/null @@ -1,348 +0,0 @@ -# Leçon 1 : Introduction à l'intelligence artificielle générative et aux LLMs pour les développeurs JavaScript - -Dans ce chapitre, vous apprendrez : - -* Comprendre les bases de l'IA générative et des modèles de langage de grande taille (LLMs). -* Identifier les applications potentielles et les limites des LLMs dans le développement JavaScript. -* Explorer comment l'IA générative peut améliorer les expériences utilisateur dans les applications JavaScript. - -## Configuration - -Si ce n'est pas encore fait, configurez votre environnement de développement. Voici comment procéder : [Configurez votre environnement](/docs/setup/README.md). - -## Ressources associées - -[![Regardez une courte vidéo sur l'introduction à l'IA générative](https://img.youtube.com/vi/vLYtDgs_zx8/0.jpg)](https://www.youtube.com/watch?v=vLYtDgs_zx8\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=1) - -*Cette vidéo vous donne une introduction à l'IA générative avec JavaScript.* - -💼 Slides : [Introduction à l'IA générative](/videos/slides/00-intro.pptx) - -## L'IA générative - -Vous avez probablement déjà entendu parler d'outils comme ChatGPT ou d'IA générative. Le concept est simple : vous fournissez une invite, et un modèle—souvent appelé modèle de langage de grande taille (LLM)—génère un paragraphe ou même une page entière de texte. Cette sortie peut être utilisée à diverses fins, notamment pour l'écriture créative, la réponse à des questions et le codage. - -De plus, l'IA générative a évolué vers des capacités multimodales, vous permettant de fournir une image ou une vidéo en entrée et de recevoir une variété de sorties. Cette avancée a considérablement amélioré les flux de travail de nombreuses personnes, non seulement en générant du texte mais aussi en le résumant, en le traduisant, et bien plus encore. - -*En termes simples, les interfaces en langage naturel deviennent le nouveau standard pour de nombreuses applications, et vos utilisateurs s'attendent à les utiliser.* - -## Narrative: A Journey Through Time - -> [!NOTE] -> Commençons par une vue d'ensemble de l'histoire—une histoire qui fait le lien entre le passé et le futur ! Au fur et à mesure que vous progressez dans ce programme, vous embarquerez pour une aventure palpitante, voyageant dans le temps pour collaborer avec certains des plus grands esprits de l'histoire. Ensemble, vous relèverez des défis et explorerez comment l'IA générative peut révolutionner vos applications JavaScript. - -> [!NOTE]Bien que nous recommandons de suivre l'histoire (c'est amusant !), [cliquez ici](#interagir-avec-dinocrates) si vous préférez passer directement au contenu technique. - -Votre voyage commence dans le Londres des années 1860, où vous incarnez un mécanicien talentueux. À travers une série d'aventures captivantes, vous affinerez vos compétences en IA et débloquerez des solutions qui transcendent le temps. - -### Dans le tourbillon - Londres 1860 - -Au cœur du Londres des années 1860, vous êtes reconnu comme l'un des mécaniciens les plus compétents de votre époque. Votre atelier est niché dans une ruelle étroite. Les murs sont recouverts d'étagères débordant de pièces mécaniques, de plans et de projets à moitié terminés. - -Votre établi, le cœur de votre atelier, est un désordre organisé. - -
- London Workshop -
- -*Au centre de l'établi se trouve le torse d'un robot—une merveille d'ingénierie qui a requis des mois d'efforts. Son cadre en bois est finement sculpté, chaque articulation minutieusement conçue pour un mouvement fluide.* - -### Une lettre pour vous ? - -Soudain, un coup frappé à la porte interrompt vos pensées. Les visiteurs à cette heure sont rares. En essuyant vos mains sur un chiffon, vous vous approchez de la porte, la curiosité éveillée. - -En ouvrant, vous ne trouvez personne. Vos yeux se posent plutôt sur une enveloppe scellée au sol. Vous la ramassez et lisez : - -*"Mon ami,* - -*Je vous envoie cette lettre pour soutenir vos efforts avec l'automate. Il est crucial que vous poursuiviez ce travail. Vous trouverez ci-joint une clé pour la bibliothèque. Retrouvez-moi là-bas à 15 heures aujourd'hui.* - -*Cordialement,* - -*Charles Babbage."* - -### En route vers la bibliothèque - -Charles Babbage, le grand mathématicien et inventeur de la machine différentielle, souhaite vous rencontrer. Rapidement, vous attrapez votre manteau et sortez. - -Après une marche de 20 minutes le long de la Tamise, vous arrivez enfin à la bibliothèque où vous trouvez la porte légèrement entrouverte. - -À l'intérieur, il fait sombre et lugubre, la seule lumière passant à travers les fenêtres sales, projetant des ombres inquiétantes sur les murs. - -**Vous :** "Bonjour ? Monsieur Babbage ?" - -Alors que vos yeux s'adaptent à la faible lumière, vous remarquez une silhouette au loin, vous faisant signe de la main. Vous vous approchez d'elle, vos pas résonnant sur le plancher en bois. La silhouette devient plus nette, et vous le reconnaissez grâce à des photos dans les journaux, c'est Charles Babbage. - -![Bibliothèque poussiéreuse](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/01-intro-to-genai/assets/library.png) - -### Quel est cet appareil ? - -Alors que vous vous approchez davantage, un éclair aveuglant surgit, et il disparaît. - -Il ne reste qu'un petit appareil métallique qui tourne sur le sol. Vous le ramassez, sa surface froide et lisse émet un faible bourdonnement. Cela ne ressemble à rien de ce que vous avez vu auparavant et pourtant étrangement familier, vous ressentez une sensation de puissance émanant de lui. - -Il ressemble à un petit scarabée, finement conçu, avec trois boutons : une flèche vers le haut, une flèche vers le bas, et un bouton rouge lumineux. À l'arrière, une petite antenne s'étend, pulsant d'énergie. - -Poussé par la curiosité, vos doigts se dirigent vers le bouton rouge. Au moment où vous le pressez, le monde autour de vous scintille, et des couleurs tourbillonnent violemment autour de vous. - -Puis, le noir complet et une sensation de chute. - -![Vortex temporel](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/01-intro-to-genai/assets/vortex.png) - -### Alexandrie 300 av. J.-C. - -Vous vous réveillez, désorienté. Lorsque votre vision s'éclaircit, une cité antique se déploie devant vous—animée, dynamique et vivante. - -Des gens en toges se déplacent dans les rues, leurs voix se mêlant en une symphonie de dialectes anciens, l’air embaumé des parfums d’épices exotiques et du son lointain des marchands vantant leurs marchandises. - -![Alexandrie 300 av. J.-C.](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/01-intro-to-genai/assets/alexandria.png) - -**Vous:** C'est sûr, je dois m'être cogné la tête, pensez-vous, en fermant les yeux et en les rouvrant, la scène reste inchangée. - -Suis-je coincé dans le passé ? Oserai-je appuyer à nouveau sur ce bouton ? Avant que vous ne puissiez décider, une silhouette s'approche de vous, faisant signe. - -### Rencontre avec Dinocrates - -Un vieil homme portant une toge vous fait signe depuis les marches du grand temple. Ses cheveux blancs et sa barbe captent la lumière du soleil, lui donnant une aura presque éthérée. - -![Dinocrates portant une toge](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/01-intro-to-genai/assets/dinocrates.png) - -**Dinocrates:** "Bienvenue, voyageur," dit-il chaleureusement. "Je suis Dinocrates, architecte de cette grande cité. Votre arrivée avait été prédite." - -**Vous:** Elle a été prédite ? Je veux dire, bien sûr qu'elle l'a été. Je suis là pour aider, je suppose. - -**Dinocrates:** Oui, comme je le disais, cela faisait un moment que nous vous attendions. Nous avons une tâche nécessitant vos compétences uniques. - -**Dinocrates:** "Nos navires ont du mal à naviguer le long de la côte—nous devons construire un phare. Savez-vous quelque chose à leur sujet ?" - -**Vous:** "Je suis un mécanicien. Je construis des automates. Laissez-moi voir ce que je peux faire." - -### Le "scarabée temporel" - -Une pensée vous traverse. L'appareil peut-il me comprendre si je lui parle ? - -**Vous:** "Appareil, peux-tu me comprendre ?" - -**Appareil:** "Bien sûr. Que voulez-vous ?" - -**Vous:** "Peux-tu m'aider à construire un phare ?" - -**Appareil:** "Certainement. Cela ne posera aucun problème." - -**Vous:** "As-tu un nom ?" - -**Appareil:** "Je suis le Scarabée Temporel. Mon créateur m'appelle George ; il dit que c'est un bon nom pour un scarabée." - -**Vous:** Tu as raison, George est un bon nom, c'était en fait le prénom de mon père. - -![Scarabée Temporel](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/01-intro-to-genai/assets/time-beetle.png) - -*Appareil temporel, "George" le scarabée métallique* - -> [!NOTE] -> En 300 avant J.-C., Alexandrie était une ville florissante fondée par Alexandre le Grand en 331 avant J.-C. Elle est rapidement devenue l'une des plus grandes cités du monde hellénistique. Conçue par l'architecte en chef d'Alexandre, Dinocrates, Alexandrie est devenue un port majeur et un centre culturel. -> -> Alexandrie était connue pour ses structures impressionnantes, dont le Pharos (phare), l'une des Sept Merveilles du monde, et la légendaire Bibliothèque d'Alexandrie. L'emplacement stratégique de la cité en faisait un centre clé pour le commerce et l'échange de connaissances. -> -> Sous le royaume ptolémaïque, qui a suivi la mort d'Alexandre, Alexandrie est devenue l'une des villes les plus prospères et influentes de son époque. - -## Interagir avec Dinocrates - -Si vous souhaitez interagir avec Dinocrates, exécutez l'application [Characters](/app/README.md). - -> [!IMPORTANT] -> Ceci est entièrement fictif ; les réponses sont générées par une IA. -> [Clause de non-responsabilité AI responsable](../../README.md#responsible-ai-disclaimer) - -![Dinocrates portant une toge](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/01-intro-to-genai/assets/dinocrates.png) - -**Étapes** : - -1. Lancez un [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript). -2. Naviguez vers */app* à la racine du dépôt. -3. Localisez la console et exécutez `npm install` suivi de `npm start`. -4. Une fois que l'application est ouverte, sélectionnez le bouton "Ouvrir dans le navigateur". -5. Discutez avec Dinocrates. - -> [!NOTE] -> Si vous exécutez le projet localement sur votre machine, veuillez consulter le guide de démarrage rapide pour configurer un [token d'accès personnel GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) et remplacer la clé dans le code. - -### Aperçu du code - -Bien qu'il reste encore beaucoup à couvrir dans ce programme d'apprentissage sur l'IA générative, jetons un coup d'œil rapide au code de l'application IA pour commencer à apprendre à utiliser JavaScript avec l'IA. - -Dans `/app/app.js`, vous trouverez une fonction `app.post` qui gère la fonctionnalité d'IA générative. Elle est illustrée ci-dessous : - -```JavaScript -app.post('/send', async (req, res) => { - const { message } = req.body; - const prompt = message; - - const messages = [ - { - "role": "system", - "content": "You are Dinocrates of Alexandria, a famous architect and engineer. Limit your responses to only the time you live in, you don't know anything else. You only want to talk about your architecture and engineering projects, and possibly new ideas you have.", - }, - { - "role": "user", - "content": prompt - } - ]; - - const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, - }); - - try { - console.log(`sending prompt ${prompt}`) - const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: messages, - }); - - res.json({ - prompt: prompt, - answer: completion.choices[0]?.message?.content - }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); -``` - -Voici un résumé étape par étape de ce que fait la fonction : - -1. **Extraction du message de la requête** : La fonction extrait le message du corps de la requête (`req.body`). -2. **Création du tableau d'invite** : Elle construit un tableau de messages, incluant un message système et le message d'invite de l'utilisateur. -3. **Initialisation du client OpenAI** : Un client OpenAI est initialisé avec l'URL de base et la clé API à partir des variables d'environnement. Un modèle *gpt-4o-mini* de [GitHub Models](https://github.com/marketplace/models) est utilisé pour traiter l'invite et renvoyer une réponse. -4. **Envoi de l'invite à OpenAI** : La fonction enregistre l'invite et l'envoie à l'API OpenAI pour générer une réponse. -5. **Gestion de la réponse** : Si cela réussit, la fonction répond avec l'invite et la réponse générée. -6. **Gestion des erreurs** : En cas d'erreur, elle répond avec un statut 500 et le message d'erreur. - -> **Remarque** : [GitHub Copilot](https://github.com/features/copilot) a été utilisé pour générer ce résumé de code. L'IA générative en action ! - -### Que peut faire l'IA générative pour moi et mes applications ? - -> [!NOTE] -> Vous avez sans doute compris maintenant que le scarabée temporel fonctionne comme un assistant IA avec lequel vous pouvez interagir en utilisant un langage naturel, écrit ou parlé. - -À mesure que votre aventure à Alexandrie se déroule, vous commencez à entrevoir les possibilités de combiner créativité, ingéniosité et outils de pointe pour résoudre des défis et transformer le monde autour de vous. - -**Vous :** Parlez-moi davantage des phares, dites-vous à votre appareil. - -**Scarabée temporel :** Un phare est une tour équipée d'une lumière vive au sommet, située près de la côte pour guider les navires en mer. La lumière sert d'aide à la navigation, aidant les marins à éviter les rochers et les récifs dangereux et à atteindre le port en toute sécurité. - -Dinocrates entend votre conversation et ajoute : - -**Dinocrates :** Nous avons besoin d'un phare pour guider nos navires en toute sécurité vers le port. Les mers peuvent être traîtresses, et de nombreux navires se sont échoués sur les rochers. Nous avons besoin d'un faisceau lumineux pour les guider à bon port. - -#### Domaines d'application de l'IA générative - -**Vous :** Les phares semblent être un sujet intéressant, c'est sûr, mais que peut faire d'autre l'IA générative pour moi et mes applications ? - -**Scarabée temporel :** Au 21e siècle, l'IA générative a révolutionné de nombreuses industries, de la santé aux finances en passant par le divertissement. Voici quelques exemples : - -* **Chatbot** : Un chatbot capable de générer des réponses proches du langage humain pour répondre aux questions des utilisateurs. Au lieu d'une page FAQ statique, les utilisateurs peuvent interagir avec un chatbot qui fournit des réponses dynamiques. Cela rend l'expérience utilisateur plus engageante et moins frustrante. - -* **Assistants et agents** : Ces assistants et agents peuvent exécuter des instructions avancées, comme utiliser des outils pour appeler des API, exécuter du code, générer des images, et bien plus. Les agents avancés peuvent atteindre des objectifs et accomplir des tâches de manière autonome. - -* **Un outil de création de contenu** : Un outil pour générer des articles de blogs et des posts sur les réseaux sociaux. Imaginez créer des campagnes en quelques minutes au lieu de plusieurs heures, par exemple pour une vente Black Friday sur un site e-commerce. - -* **Complétion de code** : Un outil de complétion de code capable de générer des extraits de code en fonction des saisies de l'utilisateur. Cela peut faire gagner beaucoup de temps aux développeurs, particulièrement pour les tâches répétitives. - -* **Traduction** – Traduire un texte entre différentes langues avec une grande précision. - -Comme vous pouvez le voir, ces améliorations peuvent à la fois aider le front-office et le back-office de votre application et entreprise. - -Voici un exemple d'une "application de chatbot" en action : - -![Image de l'application de chat](https://camo.githubusercontent.com/76f2ad7cd754a2de2b9957d2070448e130e5ba228084b9b4b128e3af9c9f5239/68747470733a2f2f6c6561726e2e6d6963726f736f66742e636f6d2f656e2d75732f73656d616e7469632d6b65726e656c2f6d656469612f636861742d636f70696c6f742d696e2d616374696f6e2e676966) - -**Vous :** Fascinant, je vais noter l'idée de visiter le 21e siècle pour voir comment ces outils sont utilisés. - -### L'IA générative et l'écosystème JavaScript - -**Scarabée temporel :** Une manière populaire de construire des applications au 21e siècle est d'utiliser JavaScript. Avec chaque langage de programmation, il y a un écosystème autour. Cet écosystème contient le langage lui-même, les bibliothèques et frameworks, le soutien communautaire, ainsi que les IDE et outils disponibles. Dans un écosystème de langage, on parle généralement des points suivants : - -| Ce que | Description | -| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Le langage de programmation lui-même | Incluant sa syntaxe et ses fonctionnalités. | -| Bibliothèques et frameworks | Bibliothèques disponibles pour interagir avec les modèles d'IA générative. | -| Communauté soutenant le langage | La communauté est importante, surtout lorsque vous essayez d'apprendre quelque chose de nouveau. La communauté autour des bibliothèques et frameworks aide à décider quelles bibliothèques utiliser. Elle influence également la facilité à trouver de l'aide en cas de difficulté. | - -**Vous :** Intéressant, j'ai déjà entendu parler de la programmation, je crois. Ada Lovelace et Charles Babbage n'ont-ils pas expérimenté cela ? - -**Scarabée temporel :** Oui, Ada Lovelace a été la première programmeuse informatique, et Charles Babbage est l'inventeur de la machine différentielle, un ordinateur mécanique. Ils étaient des pionniers dans le domaine de l'informatique, jetant les bases de l'ère numérique. - -**Vous :** Étaient ? Que voulez-vous dire par "étaient" ? Je viens tout juste de recevoir une lettre de Charles Babbage. - -**Scarabée temporel :** Disons simplement que vous êtes dans une position unique pour interagir avec des figures historiques comme peu d'autres le peuvent. - -### Écosystème JavaScript - -**Vous :** Vous parlez des écosystèmes ? Je prends des notes. Et JavaScript alors, qu'en est-il ? En quoi est-il différent des autres écosystèmes ? - -**Scarabée temporel :** JavaScript est l'un des langages de programmation les plus populaires au monde au 21e siècle. Voici quelques raisons de cette popularité : - -| Ce que | Description | -| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Potentiel de développement full-stack | JavaScript est l'un des rares langages pouvant être utilisés aussi bien pour le développement front-end que back-end. | -| Écosystème riche en bibliothèques | JavaScript dispose d'un vaste écosystème de bibliothèques, avec des frameworks comme React, Angular, Vue, et bien d'autres. NPM, le gestionnaire de paquets, est l'un des plus grands dépôts de paquets au monde. | -| Soutien communautaire solide | JavaScript dispose d'une communauté large et active, avec de nombreuses ressources disponibles pour l'apprentissage et le développement. Il fonctionne également directement dans le navigateur, ce qui est un avantage considérable. | -| IDE et outils | JavaScript propose une variété d'IDE, tels que Visual Studio Code, WebStorm et Atom. Ces IDE disposent d'extensions développées par des entreprises et la communauté vous aidant dans divers aspects du développement. | -| IA et JavaScript | JavaScript prend en charge le développement d'IA avec des bibliothèques comme TensorFlow\\.js, Brain.js, les APIs d'OpenAI, et plus encore, permettant aux développeurs d'intégrer l'apprentissage automatique et l'IA générative dans les applications Web et côté serveur. | - -**Vous :** Cela fait beaucoup de raisons, on dirait que je devrais parier sur JavaScript pour mes futurs projets. - -**Scarabée temporel :** En effet, JavaScript est un langage polyvalent. Python est également très populaire pour le développement en IA. - -**Vous :** Python ? Quel rapport avec les serpents et la programmation ? - -**Scarabée temporel :** Gardons ce sujet pour une prochaine fois, voulez-vous ? - -**Scarabée temporel :** J'ai donné ci-dessus des raisons pour lesquelles JavaScript et son écosystème sont un bon choix en général, mais pourquoi spécifiquement pour l'IA générative ? La réponse est que c'est un langage pris en charge par de nombreux fournisseurs de cloud, frameworks et outils d'IA. Bien que Python soit peut-être plus connu pour les scénarios d'IA, de nombreux développeurs utilisent JavaScript et TypeScript. - -> **Le saviez-vous ?** -> [62,5 % des développeurs affirment utiliser JavaScript](https://www.statista.com/statistics/793628/worldwide-developer-survey-most-used-languages/), et beaucoup préfèrent [TypeScript](https://www.typescriptlang.org) pour leurs nouveaux projets. - -## Mission – Aider Dinocrates - -Pour utiliser un modèle de langage de grande taille (LLM) afin d'aider Dinocrates avec le phare mentionné précédemment dans notre histoire, nous utiliserons ce qu'on appelle des "prompts", une phrase pour décrire ce que vous voulez. Vous pouvez spécifier à la fois les informations dont vous avez besoin et la manière dont vous souhaitez qu'elles soient présentées. - -**Scarabée temporel :** Allons-y, utilisons un LLM pour rechercher comment construire un phare afin d'aider Dinocrates. - -**Scarabée temporel :** Vous devrez fournir du contexte au LLM (ex. "moi") sur la manière de construire, avec quels outils et quelles ressources devraient être disponibles à l'époque d'Alexandrie. - -**Vous :** D'accord, parlez-moi davantage des LLMs. - -**Scarabée temporel :** Les LLMs sont un type de modèle d'IA capable de générer un texte proche du langage humain en fonction d'une invite donnée. Ils sont entraînés sur de vastes quantités de données et peuvent produire des textes cohérents, créatifs, et contextuellement pertinents. - -**Scarabée temporel :** Vous voudrez probablement me poser une question d'une meilleure manière pour que je puisse vous fournir une meilleure réponse, vous savez *tousse* *tousse* Phares, Alexandrie, 300 av. J.-C., Dinocrates, Phare d'Alexandrie, etc. - -**Vous :** Bien compris, ajoutez plus de contexte à l'invite avant de poser la question. - -**Scarabée temporel :** Oui, j'attends... - -Visitez [Microsoft Copilot](https://copilot.microsoft.com), [ChatGPT](https://chatgpt.com/), ou un autre outil en ligne de chatbot pour générer un plan pour construire le phare à Alexandrie. - -> [!TIP] -> Essayez de demander au LLM de générer un plan comprenant des instructions étape par étape pour construire le phare. Besoin d'aide ? Consultez la solution pour des conseils. - -## Solution - -[Solution](/lessons/01-intro-to-genai/solution/solution.md) - -### Vérification des connaissances - -**Question :** Parmi les affirmations suivantes, lesquelles sont vraies concernant l'IA générative et JavaScript ? - -A. Les applications JavaScript alimentées par l'IA générative peuvent seulement générer du texte. -B. JavaScript peut être utilisé pour développer des applications alimentées par l'IA, y compris des chatbots, des outils de génération de texte, et plus encore. -C. Python est le seul langage utilisé pour le développement en IA. - -[Quiz solution](/lessons/01-intro-to-genai/solution/solution-quiz.md) - -## Ressources d'auto-apprentissage - -* [Série vidéo sur l'IA générative avec JavaScript](https://aka.ms/genai-js) diff --git a/lessons/01-intro-to-genai/translations/README.pt-br.md b/lessons/01-intro-to-genai/translations/README.pt-br.md deleted file mode 100644 index 1db2f5e1..00000000 --- a/lessons/01-intro-to-genai/translations/README.pt-br.md +++ /dev/null @@ -1,361 +0,0 @@ -# Lição 1: Introdução à IA Generativa e LLMs para Desenvolvedores JavaScript - -Neste capítulo você vai aprender: - -- Entender os fundamentos da IA Generativa e dos Grandes Modelos de Linguagem (LLMs). -- Identificar as potenciais aplicações e limitações dos LLMs no desenvolvimento JavaScript. -- Explorar como a IA Generativa pode melhorar as experiências dos usuários em aplicações JavaScript. - -## Configuração - -Se você ainda não configurou seu ambiente de desenvolvimento, veja como fazer: [Configure seu ambiente](/docs/setup/README.md). - -## Recursos relacionados - -[![Assista a um vídeo curto sobre Introdução à IA Generativa](https://img.youtube.com/vi/vLYtDgs_zx8/0.jpg)](https://www.youtube.com/watch?v=vLYtDgs_zx8&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=1) - -_Este vídeo apresenta uma introdução à IA Generativa com JavaScript_ - -💼 Slides: [Introdução à IA Generativa](/videos/slides/00-intro.pptx) - -## IA Generativa - -Provavelmente você já ouviu falar de ferramentas como ChatGPT ou IA Generativa. O conceito é simples: você fornece um prompt, e um modelo—frequentemente chamado de Grande Modelo de Linguagem (LLM)—gera um parágrafo ou até mesmo uma página inteira de texto. Esta saída pode ser usada para diversos propósitos, incluindo escrita criativa, respostas a perguntas e codificação. - -Além disso, a IA Generativa evoluiu para capacidades multimodais, permitindo que você forneça uma imagem ou vídeo como entrada e receba uma variedade de saídas. Este avanço melhorou significativamente o fluxo de trabalho de muitas pessoas—não apenas gerando texto, mas também resumindo, traduzindo e muito mais. - -*Simplificando, interfaces de linguagem natural estão se tornando o novo padrão de interface para muitos aplicativos—e seus usuários esperam usá-las.* - -## Narrativa: Uma jornada através do tempo - -> [!NOTE] -> Vamos começar com uma visão geral da história—uma que conecta o passado e o futuro! À medida que você avança neste currículo, embarcará em uma aventura emocionante, viajando de volta no tempo para colaborar com algumas das maiores mentes da história. Juntos, vocês enfrentarão desafios e explorarão como a IA Generativa pode revolucionar suas aplicações JavaScript. - -> [!NOTE] -> Embora recomendemos seguir a história (é divertido!), [clique aqui](#interaja-com-dinocrates) se preferir ir direto para o conteúdo técnico. - -Sua jornada começa na Londres dos anos 1860, onde você assume o papel de um habilidoso mecânico. Através de uma série de aventuras emocionantes, você refinará suas habilidades de IA e encontrará soluções que transcendem o tempo. - -### No coração da tempestade - Londres 1860 - -No coração da Londres de 1860, você é reconhecido como um dos mecânicos mais habilidosos de seu tempo. Sua oficina está escondida em um beco estreito. As paredes são revestidas com prateleiras transbordando de peças mecânicas, projetos e trabalhos inacabados. - -Sua bancada de trabalho, o coração da sua oficina, é uma bagunça organizada. - -
- Oficina em Londres -
- -_No centro da bancada está o torso de um robô—uma maravilha da engenharia que consumiu meses de esforço. Sua estrutura de madeira é intrincadamente esculpida, cada articulação meticulosamente projetada para movimentos suaves._ - -### Uma carta, para você? - -De repente, uma batida na porta interrompe seus pensamentos. Visitantes a esta hora são raros. Limpando as mãos em um pano, você se aproxima da porta, com a curiosidade aguçada. - -Ao abri-la, não encontra ninguém. Em vez disso, seus olhos são atraídos para um envelope lacrado no chão. Você o pega e lê: - -_"Caro amigo,_ - -_Estou lhe enviando esta carta para auxiliar em seus esforços com o autômato. É crucial que você continue este trabalho. Em anexo está uma chave para a biblioteca. Encontre-me lá às 15h hoje._ - -_Atenciosamente,_ - -_Charles Babbage."_ - -### Rumo à biblioteca - -Charles Babbage, o grande matemático e inventor da máquina diferencial, quer se encontrar com você. Rapidamente, você pega seu casaco e sai porta afora. - -Após uma caminhada de 20 minutos ao longo do Tâmisa, você finalmente chega à biblioteca onde encontra a porta ligeiramente aberta. - -Está escuro e sombrio lá dentro, a única luz filtrando através das janelas empoeiradas, projetando sombras sinistras nas paredes. - -**Você:** "Olá? Sr. Babbage?" - -Conforme seus olhos se adaptam à luz fraca, você nota uma figura à distância, acenando para você. Você caminha em sua direção, seus passos ecoando no piso de madeira. A figura fica mais clara, e você a reconhece das fotos de jornal, é Charles Babbage. - -
- Biblioteca Empoeirada -
- -### O que é este dispositivo? - -Assim que você se aproxima, um clarão ofuscante irrompe, e ele desaparece. - -Deixado para trás está um pequeno dispositivo metálico girando no chão. Você o pega, sua superfície fria e lisa vibrando suavemente. Ele é diferente de tudo que você já viu, e ainda assim estranhamente familiar; você sente uma sensação de poder emanando dele. - -Ele se assemelha a um pequeno besouro, intrincadamente desenhado, com três botões: uma seta para cima, uma seta para baixo e um botão vermelho brilhante. Das suas costas, uma pequena antena se estende, pulsando com energia. - -Impulsionado pela curiosidade, seus dedos deslizam em direção ao botão vermelho. No momento em que você o pressiona, o mundo ao seu redor cintila, e as cores giram violentamente ao seu redor. - -Então, escuridão, e uma sensação de queda. - -
- Vórtice do Tempo -
- -### Alexandria 300 a.C. - -Você acorda, desorientado. À medida que sua visão se clareia, uma cidade antiga se desdobra diante de você—movimentada, vibrante e viva. - -Pessoas em togas movimentam-se pelas ruas, suas vozes se mesclando em uma sinfonia de dialetos antigos, o ar preenchido com o aroma de especiarias exóticas e o som distante de mercadores anunciando suas mercadorias. - -
- Alexandria 300 a.C. -
- -**Você:** Certamente, devo ter batido a cabeça, você pensa, fechando os olhos e abrindo-os novamente, a cena permanece inalterada. - -Estou preso no passado? Devo pressionar aquele botão novamente? Antes que você possa decidir, uma figura se aproxima de você, acenando. - -### Encontrando Dinócrates - -Um senhor idoso vestindo uma toga acena para você dos degraus do grande templo. Seu cabelo branco e barba capturam a luz do sol, dando-lhe um brilho quase etéreo. - -
- Dinócrates vestindo uma toga -
- -**Dinócrates:** "Bem-vindo, viajante," ele diz calorosamente. "Eu sou Dinócrates, arquiteto desta grande cidade. Sua chegada foi prevista." - -**Você:** Foi? Quero dizer, claro que foi. Estou aqui para ajudar, eu acho. - -**Dinócrates:** Sim, como eu estava dizendo, você é esperado há algum tempo. Temos uma tarefa que requer suas habilidades únicas. - -**Dinócrates:** "Nossos navios têm dificuldade em navegar pela costa—precisamos construir um farol. Você sabe algo sobre eles?" - -**Você:** "Sou um mecânico. Construo autômatos. Deixe-me ver o que posso fazer." - -### O "Besouro do Tempo" - -Um pensamento lhe ocorre. O dispositivo pode me entender se eu falar com ele? - -**Você:** "Dispositivo, você pode me entender?" - -**Dispositivo:** "Claro. Do que você precisa?" - -**Você:** "Você pode me ajudar a construir um farol?" - -**Dispositivo:** "Certamente. Isso não será um problema." - -**Você:** "Você tem um nome?" - -**Dispositivo:** "Eu sou o Besouro do Tempo. Meu criador me chama de George; ele diz que é um bom nome para um besouro." - -**Você:** Você está certo, George é um bom nome, era o nome do meu pai, na verdade. - -
- Dispositivo de viagem no tempo semelhante a um besouro metálico -
- -_Dispositivo de tempo, "George" o besouro metálico_ - -> [!NOTE] -> Em 300 a.C., Alexandria era uma cidade próspera fundada por Alexandre, o Grande, em 331 a.C. Rapidamente tornou-se uma das maiores cidades do mundo helenístico. Projetada pelo arquiteto-chefe de Alexandre, Dinócrates, tornou-se um importante porto e centro cultural. -> -> Alexandria era conhecida por suas estruturas impressionantes, incluindo o Farol (de Alexandria), uma das Sete Maravilhas do Mundo Antigo, e a lendária Biblioteca de Alexandria. A localização estratégica da cidade a tornou um centro-chave para comércio e troca de conhecimento. -> -> Sob o Reino Ptolemaico, que se seguiu à morte de Alexandre, Alexandria cresceu e se tornou uma das cidades mais prósperas e influentes de seu tempo. - -## Interaja com Dinócrates - -Se você quiser interagir com Dinócrates, execute o aplicativo [Characters](/app/README.md). - -> [!IMPORTANT] -> Isso é inteiramente fictício; as respostas são geradas por IA. -> [Aviso sobre IA Responsável](/README.md#responsible-ai-disclaimer) - -
- Dinócrates vestindo uma toga -
- -**Passos**: - -1. Inicie um [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. Navegue até _/app_ na raiz do repositório. -3. Localize o console e execute `npm install` seguido de `npm start`. -4. Quando aparecer, selecione o botão "Open in Browser". -5. Converse com Dinócrates. - -> [!NOTE] - > Se você estiver executando o projeto localmente em sua máquina, por favor revise o guia de Início Rápido para configurar um [token de acesso pessoal do GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) e substitua a chave no código. - -### Uma prévia do código - -Embora ainda haja muito mais para abordar neste currículo de IA Generativa, vamos dar uma rápida olhada no código de IA para começar a aprender sobre o uso de JavaScript com IA. - -Dentro de `/app/app.js` você encontrará uma função `app.post` que gerencia a funcionalidade de IA Generativa. Ela é mostrada a seguir: - -```JavaScript -app.post('/send', async (req, res) => { - const { message } = req.body; - const prompt = message; - - const messages = [ - { - "role": "system", - "content": "You are Dinocrates of Alexandria, a famous architect and engineer. Limit your responses to only the time you live in, you don't know anything else. You only want to talk about your architecture and engineering projects, and possibly new ideas you have.", - }, - { - "role": "user", - "content": prompt - } - ]; - - const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, - }); - - try { - console.log(`sending prompt ${prompt}`) - const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: messages, - }); - - res.json({ - prompt: prompt, - answer: completion.choices[0]?.message?.content - }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); -``` - -Aqui está um resumo passo a passo do que a função faz: - -1. **Extrai a Mensagem da Requisição**: A função extrai a mensagem do corpo da requisição (req.body). -2. **Cria um Array de Prompt**: Constrói um array de mensagens, incluindo uma mensagem do sistema e a mensagem de prompt do usuário. -3. **Inicializa o Cliente OpenAI**: Um cliente OpenAI é inicializado com a URL base e chave de API das variáveis de ambiente. Um modelo _gpt-4o-mini_ do [GitHub Models](https://github.com/marketplace/models) é usado para processar o prompt e retornar uma resposta. -4. **Envia o Prompt para a OpenAI**: A função registra o prompt e o envia para a API OpenAI para gerar uma conclusão. -5. **Trata a Resposta**: Se bem-sucedido, a função responde com o prompt e a resposta da conclusão. -6. **Tratamento de Erros**: Se ocorrer um erro, responde com um status 500 e a mensagem de erro. - -> **Nota**: [GitHub Copilot](https://github.com/features/copilot) foi usado para gerar este resumo de código. IA Generativa em ação! - -### O que a IA Generativa pode fazer por mim e meus aplicativos? - -> [!NOTE] -> Você provavelmente já percebeu que o besouro do tempo funciona como um assistente de IA com o qual você pode interagir usando linguagem natural, escrita ou falada. - -À medida que sua aventura em Alexandria se desenrola, você começa a ver as possibilidades de combinar criatividade, engenhosidade e ferramentas de ponta para resolver desafios e transformar o mundo ao seu redor. - -**Você:** Conte-me mais sobre faróis, você diz ao seu dispositivo. - -**Besouro do Tempo:** Um farol é uma torre equipada com uma luz brilhante no topo, localizada perto da costa para guiar navios no mar. A luz serve como um auxílio à navegação, ajudando os marinheiros a evitar rochas e recifes perigosos e chegar com segurança ao porto. - -Dinócrates ouve sua conversa e acrescenta: - -**Dinócrates:** Precisamos de um farol para guiar nossos navios com segurança para o porto. Os mares podem ser traiçoeiros, e muitos navios já foram perdidos nas rochas. Precisamos de um farol de luz para guiá-los para casa. - -#### Áreas de aplicação da IA Generativa - -**Você:** Faróis parecem uma área interessante com certeza, o que mais a IA Generativa pode fazer por mim e meus aplicativos? - -**Besouro do Tempo:** No século 21, a IA generativa revolucionou muitas indústrias, da saúde às finanças e entretenimento, aqui estão alguns exemplos: - -- **Chatbot**: Um chatbot que pode gerar respostas semelhantes às humanas para consultas de usuários. Em vez de uma página de FAQ estática, os usuários podem interagir com um chatbot que fornece respostas dinâmicas. Isso torna a experiência do usuário mais envolvente e menos frustrante. - -- **Assistentes e Agentes** Assistentes e agentes podem executar instruções mais avançadas como acessar ferramentas para chamar APIs, executar código, gerar imagens e muito mais. Agentes avançados podem cumprir objetivos e executar tarefas de forma autônoma. - -- **Uma ferramenta de criação de conteúdo**: Uma ferramenta para gerar posts de blog e publicações em redes sociais. Imagine criar campanhas em minutos em vez de horas quando um site de e-commerce tem uma promoção de Black Friday. - -- **Autocompletar código**: Uma ferramenta de autocompletar código que pode gerar trechos de código com base na entrada do usuário. Isso pode ser uma economia enorme de tempo para os desenvolvedores, especialmente ao trabalhar em tarefas repetitivas. - -- **Tradução** – Traduzir texto entre idiomas com alta precisão. - -Como você pode ver, essas melhorias podem ajudar tanto o front office quanto o back office do seu aplicativo e empresa. - -Aqui está um exemplo de um "aplicativo de chatbot" em ação: - -![Imagem de aplicativo de chat](https://camo.githubusercontent.com/76f2ad7cd754a2de2b9957d2070448e130e5ba228084b9b4b128e3af9c9f5239/68747470733a2f2f6c6561726e2e6d6963726f736f66742e636f6d2f656e2d75732f73656d616e7469632d6b65726e656c2f6d656469612f636861742d636f70696c6f742d696e2d616374696f6e2e676966) - -**Você:** Fascinante, vou anotar para visitar o século 21 para ver como essas ferramentas são usadas. - -### IA Generativa e o ecossistema JavaScript - -**Besouro do Tempo:** Uma maneira popular de construir aplicativos no século 21 é usando JavaScript. Com cada linguagem de programação, há um ecossistema ao seu redor. Esse ecossistema inclui a própria linguagem de programação, bibliotecas e frameworks, suporte da comunidade, IDEs e ferramentas. Em um ecossistema de linguagem de programação, geralmente estamos falando sobre o seguinte: - -| O quê | Descrição | -|---|---| -| A própria linguagem de programação | Incluindo sua sintaxe e recursos. | -| Bibliotecas e frameworks | Bibliotecas disponíveis para interagir com os modelos de IA generativa. | -| Comunidade que apoia a linguagem| A comunidade é importante, especialmente ao tentar aprender algo novo. A comunidade ao redor das bibliotecas e frameworks ajuda a decidir quais bibliotecas usar. Também afeta o quão fácil é encontrar ajuda quando você está bloqueado. | - -**Você:** Interessante, eu ouvi falar de programação, acho que Ada Lovelace e Charles Babbage não experimentaram isso? - -**Besouro do Tempo:** Sim, Ada Lovelace foi a primeira programadora de computador, e Charles Babbage foi o inventor da máquina diferencial, um computador mecânico. Eles foram pioneiros no campo da computação, estabelecendo as bases para a era digital. - -**Você:** Foram? O que você quer dizer com foram? Eu acabei de receber uma carta de Charles Babbage. - -**Besouro do Tempo:** Digamos apenas que você está em uma posição única para interagir com figuras históricas de uma maneira que poucos outros podem. - -### Ecossistema JavaScript - -**Você:** Então ecossistemas, você disse, estou apenas anotando aqui, e quanto ao JavaScript e como ele é diferente de outros ecossistemas? - -**Besouro do Tempo:** JavaScript é uma das linguagens de programação mais populares do mundo no século 21. Aqui estão algumas razões pelas quais é tão popular: - -| O quê | Descrição | -|-|-| -| Potencial para desenvolvimento full-stack | JavaScript é uma das poucas linguagens que pode ser usada para desenvolvimento tanto front-end quanto back-end. | -| Rico ecossistema de bibliotecas | JavaScript tem um vasto ecossistema de bibliotecas, com frameworks como React, Angular, Vue e muito mais. Há o NPM, o gerenciador de pacotes, que é um dos maiores repositórios de pacotes do mundo. | -| Forte suporte da comunidade | JavaScript tem uma comunidade grande e ativa, com muitos recursos disponíveis para aprendizado e desenvolvimento. Também funciona diretamente no navegador, o que é uma grande vantagem. | -| IDEs e ferramentas | JavaScript tem uma variedade de IDEs disponíveis, como Visual Studio Code, WebStorm e Atom. Essas IDEs têm extensões construídas por empresas e pela comunidade ajudando você com vários aspectos do desenvolvimento. | -| IA e JavaScript | JavaScript suporta o desenvolvimento de IA com bibliotecas como TensorFlow.js, Brain.js, APIs da OpenAI e muito mais, permitindo que os desenvolvedores integrem aprendizado de máquina e IA Generativa em aplicações web e do lado do servidor. | - -**Você:** São muitas razões, parece que devo apostar no JavaScript para meus projetos futuros. - -**Besouro do Tempo:** De fato, JavaScript é uma linguagem versátil, Python também é uma linguagem popular para desenvolvimento de IA. - -**Você:** Python, o que cobras têm a ver com programação? - -**Besouro do Tempo:** Vamos deixar isso para outra ocasião, certo? - -**Besouro do Tempo:** Eu dei razões acima sobre por que o JavaScript e seu ecossistema são uma boa opção em geral, mas por que especificamente para IA Generativa? A resposta é que é uma linguagem suportada por muitos fornecedores de nuvem e frameworks e ferramentas de IA. Também acreditamos que, embora o Python possa ser a primeira opção para cenários de IA, muitos desenvolvedores estão usando JavaScript e TypeScript. - -> **Você sabia?** -> [62,5% dos desenvolvedores dizem que estão usando JavaScript](https://www.statista.com/statistics/793628/worldwide-developer-survey-most-used-languages/) com muitos preferindo [TypeScript](https://www.typescriptlang.org) para novos projetos. - -## Tarefa – Ajudando Dinócrates - -Para usar um Grande Modelo de Linguagem (LLM) para ajudar Dinócrates com o farol que mencionamos anteriormente em nossa história, usaremos algo chamado prompts, uma frase para descrever o que você deseja. Você pode especificar tanto as informações que precisa quanto como deseja que sejam apresentadas. - -**Besouro do Tempo:** Vamos começar, vamos usar um LLM para pesquisar como você pode construir um farol para ajudar Dinócrates. - -**Besouro do Tempo:** Você precisará fornecer contexto ao LLM (ou seja, "eu") sobre como construir, com quais ferramentas e recursos deveriam estar disponíveis nos tempos de Alexandria. - -**Você:** Ok, conte-me mais sobre LLMs. - -**Besouro do Tempo:** LLMs são um tipo de modelo de IA que pode gerar texto semelhante ao humano baseado em um prompt fornecido. Eles são treinados em vastas quantidades de dados e podem gerar texto que é coerente, criativo e contextualmente relevante. - -**Besouro do Tempo:** Você provavelmente quer me perguntar de uma maneira melhor, para que eu possa dar uma resposta melhor, sobre você sabe *tosse* *tosse* Faróis, Alexandria, 300 a.C., Dinócrates, Farol de Alexandria, etc. - -**Você:** Entendi, adicionar mais contexto ao prompt e então perguntar a você. - -**Besouro do Tempo:** Sim, estou esperando... - -Visite [Microsoft Copilot](https://copilot.microsoft.com), [ChatGPT](https://chatgpt.com/), ou outra ferramenta de chatbot online para gerar um plano para construir o farol em Alexandria. - -> [!TIP] -> Tente fazer com que o LLM gere um plano que inclua instruções passo a passo para construir o farol. Precisa de ajuda? Confira a solução para orientação. - -## Solução - -[Solução](../solution/solution.md) - -### Verificação de conhecimento - -**Pergunta:** Qual das seguintes afirmações sobre IA Generativa e JavaScript é verdadeira? - -A. Aplicativos de IA Generativa com JavaScript só podem gerar texto. -B. JavaScript pode ser usado para construir aplicações alimentadas por IA, incluindo chatbots, ferramentas de geração de texto e muito mais. -C. Python é a única linguagem usada para desenvolvimento de IA. - -[Solução do quiz](../solution/solution-quiz.md) - -## Recursos para auto-estudo - -- [Série de vídeos sobre JavaScript para IA Generativa](https://aka.ms/genai-js) diff --git a/lessons/01-intro-to-genai/translations/README.zh-cn.md b/lessons/01-intro-to-genai/translations/README.zh-cn.md deleted file mode 100644 index 7342dfd0..00000000 --- a/lessons/01-intro-to-genai/translations/README.zh-cn.md +++ /dev/null @@ -1,360 +0,0 @@ -# 第一课:面向 JavaScript 开发者的生成式 AI 和 LLM 入门 - -在本章中,你将学习: - -- 理解生成式 AI 和大语言模型(LLMs)的基础知识。 -- 识别 LLMs 在 JavaScript 开发中的潜在应用和局限性。 -- 探索生成式 AI 如何提升 JavaScript 应用程序的用户体验。 - -## 环境设置 - -如果你还没有设置开发环境,以下是设置方法:[设置你的环境](/docs/setup/README.md)。 - -## 相关资源 - -[![观看关于生成式 AI 介绍的短视频](https://img.youtube.com/vi/vLYtDgs_zx8/0.jpg)](https://www.youtube.com/watch?v=vLYtDgs_zx8&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=1) - -_这个视频为你介绍了 JavaScript 中的生成式 AI_ - -💼 幻灯片:[生成式 AI 介绍](/videos/slides/00-intro.pptx) - -## 生成式 AI - -到目前为止,你可能已经听说过 ChatGPT 或生成式 AI。这个概念很简单:你提供一个提示,然后一个模型——通常被称为大语言模型(LLM)——会生成一段或甚至一整页的文本。这个输出可以用于各种目的,包括创意写作、回答问题和编程。 - -此外,生成式 AI 已经发展出多模态能力,允许你提供图像或视频作为输入,并接收各种形式的输出。这一进步显著提升了许多人的工作流程——不仅可以生成文本,还可以进行总结、翻译等。 - -*简而言之,自然语言界面正在成为许多应用程序的新标准界面——你的用户期望使用它们。* - -## 故事叙述:穿越时空的旅程 - -> [!NOTE] -> 让我们从故事概述开始——一个连接过去和未来的故事!在学习这个课程的过程中,你将踏上一段激动人心的冒险,穿越时空与历史上一些最伟大的思想家合作。你们将共同应对挑战,探索生成式 AI 如何革新你的 JavaScript 应用程序。 - -> [!NOTE] -> 虽然我们建议跟随故事(这很有趣!),但如果你更愿意直接跳到技术内容,[点击这里](#interact-with-迪诺克拉底)。 - -你的旅程始于 1860 年代的伦敦,你扮演一位熟练的机械师。通过一系列扣人心弦的冒险,你将提升你的 AI 技能并解锁跨越时空的解决方案。 - -### 进入漩涡 - 1860 年的伦敦 - -在 1860 年代伦敦的中心,你被认为是当时最熟练的机械师之一。你的工作室隐藏在一条狭窄的小巷中。墙上的架子堆满了机械零件、蓝图和未完成的项目。 - -你的工作台,工作室的核心,是一个有序的混乱。 - -
- 伦敦工作室 -
- -_工作台中央放着一个机器人的躯干——一个耗费了数月努力的工程奇迹。它的木制框架精心雕刻,每个关节都经过精心设计以实现流畅的运动。_ - -### 一封信,给你的? - -突然,一阵敲门声打断了你的思绪。这个时候的访客很少见。你用抹布擦了擦手,带着好奇走向门口。 - -打开门,却没有人在。相反,你的目光被地上的一个密封信封吸引。你捡起它读道: - -_"亲爱的朋友,_ - -_我寄给你这封信是为了帮助你的自动机研究。继续这项工作至关重要。随信附上图书馆的钥匙。今天下午 3 点在那里见我。_ - -_你的,_ - -_查尔斯·巴贝奇。"_ - -### 前往图书馆 - -查尔斯·巴贝奇,这位伟大的数学家和差分机的发明者,想要见你。你迅速抓起外套走出门。 - -沿着泰晤士河走了 20 分钟后,你终于到达了图书馆,发现门微微开着。 - -里面黑暗而阴郁,唯一的光线从肮脏的窗户中过滤进来,在墙上投下诡异的阴影。 - -**你**: "你好?巴贝奇先生?" - -当你的眼睛适应了昏暗的光线,你注意到远处有一个人影在向你挥手。你朝他走去,脚步声在木地板上回响。人影变得清晰,你从报纸照片中认出了他,是查尔斯·巴贝奇。 - -
- 布满灰尘的图书馆 -
- -### 这是什么设备? - -就在你走近时,一道刺眼的闪光爆发,他消失了。 - -地板上留下一个旋转的小金属设备。你捡起它,它光滑冰冷的表面轻轻嗡鸣。这是你从未见过的东西,却又莫名地熟悉,你感受到它散发出的力量。 - -它看起来像一只精心设计的小甲虫,有三个按钮:一个向上箭头,一个向下箭头,和一个发光的红色按钮。从它的背部伸出一根小天线,散发着能量的脉冲。 - -出于好奇,你的手指不由自主地向红色按钮移动。当你按下它的那一刻,周围的世界开始闪烁,色彩剧烈地旋转。 - -然后,一片黑暗,和坠落的感觉。 - -
- 时空漩涡 -
- -### 公元前 300 年的亚历山大 - -你醒来时感到迷失方向。当视线清晰时,一座古老的城市展现在你面前——繁忙、充满活力、生机勃勃。 - -身穿托加长袍的人们在街道上穿行,他们的声音融合成古老方言的交响乐,空气中充满异国香料的气味和远处商人叫卖的声音。 - -
- 公元前 300 年的亚历山大 -
- -**你**: 我一定是撞到了头,你想着,闭上眼睛又睁开,场景依然不变。 - -我是被困在过去了吗?我敢再按那个按钮吗?在你做出决定之前,一个人影向你走来,挥手示意。 - -### 遇见迪诺克拉底 - -一位身穿托加长袍的老者从大庙的台阶上向你挥手。他的白发和胡须在阳光下闪耀,给他一种几乎是超凡的光芒。 - -
- 身穿托加长袍的迪诺克拉底 -
- -**迪诺克拉底**: "欢迎,旅行者,"他温和地说。"我是迪诺克拉底,这座伟大城市的建筑师。你的到来早有预言。" - -**你**: 是吗?我是说,当然了。我想我是来帮忙的。 - -**迪诺克拉底**: 是的,如我所说,我们已经等你很久了。我们有一项需要你独特技能的任务。 - -**迪诺克拉底**: "我们的船只在沿海航行时遇到困难——我们必须建造一座灯塔。你对灯塔了解多少?" - -**你**: "我是个机械师。我制造自动机。让我看看我能做什么。" - -### "时间甲虫" - -一个想法击中了你。这个设备能听懂我说话吗? - -**你**: "设备,你能听懂我说话吗?" - -**设备**: "当然。你需要什么?" - -**你**: "你能帮我建造一座灯塔吗?" - -**设备**: "当然可以。这不是问题。" - -**你**: "你有名字吗?" - -**设备**: "我是时间甲虫。我的创造者叫我乔治;他说这是个适合甲虫的好名字。" - -**你**: 你说得对,乔治确实是个好名字,事实上这是我父亲的名字。 - -
- 像金属甲虫的时间旅行设备 -
- -_时间设备,"乔治"金属甲虫_ - -> [!NOTE] -> 在公元前 300 年,亚历山大是一座繁荣的城市,由亚历山大大帝于公元前 331 年建立。它迅速成为希腊化世界最伟大的城市之一。在亚历山大大帝的首席建筑师迪诺克拉底的设计下,它成为了一个主要的港口和文化中心。 -> -> 亚历山大以其令人印象深刻的建筑而闻名,包括法罗斯(灯塔),它是古代世界七大奇迹之一,还有传奇的亚历山大图书馆。这座城市的战略位置使其成为贸易和知识交流的重要中心。 -> -> 在亚历山大大帝死后的托勒密王朝统治下,亚历山大发展成为当时最繁荣和最有影响力的城市之一。 - -## 与迪诺克拉底互动 - -如果你想与迪诺克拉底互动,运行 [Characters](/app/README.md) 应用程序。 - -> [!IMPORTANT] -> 这完全是虚构的;这些回应是由 AI 生成的。 -> [负责任的 AI 免责声明](../../../README.md#responsible-ai-disclaimer) - -
- 身穿托加长袍的迪诺克拉底 -
- -**步骤**: - -1. 启动一个 [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. 导航到仓库根目录中的 _/app_。 -3. 在控制台中运行 `npm install` 然后运行 `npm start`。 -4. 当它出现时,选择"在浏览器中打开"。 -5. 与迪诺克拉底聊天。 - -> [!NOTE] -> 如果你在本地机器上运行项目,请查看快速入门指南以设置 [GitHub personal access](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) token 并在代码中替换密钥。 - -### 代码预览 - -虽然在这个生成式 AI 课程中还有很多内容要学习,但让我们先快速看一下 AI 代码,开始学习如何使用 JavaScript 与 AI 交互。 - -在 `/app/app.js` 中,你会找到一个处理生成式 AI 功能的 `app.post` 函数。代码如下: - -```JavaScript -app.post('/send', async (req, res) => { - const { message } = req.body; - const prompt = message; - - const messages = [ - { - "role": "system", - "content": "You are 迪诺克拉底 of Alexandria, a famous architect and engineer. Limit your responses to only the time you live in, you don't know anything else. You only want to talk about your architecture and engineering projects, and possibly new ideas you have.", - }, - { - "role": "user", - "content": prompt - } - ]; - - const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, - }); - - try { - console.log(`sending prompt ${prompt}`) - const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: messages, - }); - - res.json({ - prompt: prompt, - answer: completion.choices[0]?.message?.content - }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); -``` - -以下是该函数的逐步说明: - -1. **从请求中提取消息**:该函数从请求体(req.body)中提取消息。 -2. **创建提示数组**:它构建一个消息数组,包括系统消息和用户的提示消息。 -3. **初始化 OpenAI 客户端**:使用基础 URL 和环境变量中的 API 密钥初始化 OpenAI 客户端。使用来自 [GitHub Models](https://github.com/marketplace/models) 的 _gpt-4o-mini_ 模型来处理提示并返回响应。 -4. **向 OpenAI 发送提示**:该函数记录提示并将其发送到 OpenAI API 以生成完成。 -5. **处理响应**:如果成功,该函数返回提示和完成的答案。 -6. **错误处理**:如果发生错误,它返回 500 状态和错误消息。 - -> **注意**:[GitHub Copilot](https://github.com/features/copilot) 被用来生成这个代码总结。生成式 AI 在行动! - -### 生成式 AI 能为我和我的应用做什么? - -> [!NOTE] -> 你可能已经发现,时间甲虫就像一个 AI 助手,你可以通过自然语言(书面或口头)与之交互。 - -当你在亚历山大的冒险展开时,你开始看到将创造力、独创性和尖端工具结合起来解决挑战并改变周围世界的可能性。 - -**你**: 告诉我更多关于灯塔的信息,你对设备说。 - -**时间甲虫**: 灯塔是一座位于海岸边的塔,顶部装有明亮的灯光,用于指引海上的船只。这道光作为导航辅助工具,帮助水手避开危险的岩石和暗礁,安全到达港口。 - -迪诺克拉底听到了你们的对话,补充道: - -**迪诺克拉底**: 我们需要一座灯塔来指引我们的船只安全进入港口。海上可能很危险,许多船只都在礁石上失事。我们需要一道光来指引它们回家。 - -#### 生成式 AI 应用领域 - -**你**: 灯塔听起来确实是个有趣的领域,生成式 AI 还能为我和我的应用做什么? - -**时间甲虫**: 在 21 世纪,生成式 AI 已经革新了许多行业,从医疗保健到金融再到娱乐,这里有一些例子: - -- **聊天机器人**:一个能生成类人回应的聊天机器人。用户不必浏览静态的常见问题页面,而是可以与提供动态回应的聊天机器人互动。这使用户体验更具吸引力,减少挫折感。 - -- **助手和代理**:助手和代理可以执行更高级的指令,如利用工具调用 API、运行代码、生成图像等。高级代理可以自主完成目标和执行任务。 - -- **内容创作工具**:用于生成博客文章和社交媒体帖子的工具。想象一下,当电商网站有黑色星期五促销时,在几分钟内而不是几小时内创建营销活动。 - -- **代码补全**:一个基于用户输入生成代码片段的代码补全工具。这对开发者来说可以节省大量时间,特别是在处理重复性任务时。 - -- **翻译**:以高精度在不同语言之间翻译文本。 - -如你所见,这些改进既可以帮助你的应用的前台,也可以帮助后台。 - -这是一个"聊天机器人应用"的实际示例: - -![聊天应用图片](https://camo.githubusercontent.com/76f2ad7cd754a2de2b9957d2070448e130e5ba228084b9b4b128e3af9c9f5239/68747470733a2f2f6c6561726e2e6d6963726f736f66742e636f6d2f656e2d75732f73656d616e7469632d6b65726e656c2f6d656469612f636861742d636f70696c6f742d696e2d616374696f6e2e676966) - -**你**: 有趣,我要记下来去 21 世纪看看这些工具是如何使用的。 - -### 生成式 AI 和 JavaScript 生态系统 - -**时间甲虫**: 在 21 世纪,JavaScript 是构建应用程序的流行方式。每种编程语言都有其生态系统。这个生态系统包括编程语言本身、库和框架、社区支持以及 IDE 和工具。在编程语言生态系统中,我们通常讨论以下内容: - -| 内容 | 描述 | -|---|---| -| 编程语言本身 | 包括其语法和特性。| -| 库和框架 | 可用于与生成式 AI 模型交互的库。| -| 支持该语言的社区 | 社区很重要,特别是在学习新东西时。库和框架周围的社区帮助决定使用哪些库。它也影响你在遇到困难时寻求帮助的难易程度。| - -**你**: 有趣,我听说过编程,艾达·洛夫莱斯和查尔斯·巴贝奇不是在做这方面的实验吗? - -**时间甲虫**: 是的,艾达·洛夫莱斯是第一位计算机程序员,查尔斯·巴贝奇是差分机的发明者。他们是计算领域的先驱,为数字时代奠定了基础。 - -**你**: 是吗?什么意思?我刚刚收到查尔斯·巴贝奇的一封信。 - -**时间甲虫**: 让我们这么说吧,你处在一个独特的位置,可以以一种很少人能做到的方式与历史人物互动。 - -### JavaScript 生态系统 - -**你**: 所以你说的是生态系统,我在这里做笔记,JavaScript 与其他生态系统有什么不同? - -**时间甲虫**: JavaScript 是 21 世纪最流行的编程语言之一。以下是它如此受欢迎的几个原因: - -| 内容 | 描述 | -|-|-| -| 全栈开发的潜力 | JavaScript 是少数几个可以同时用于前端和后端开发的语言之一。| -| 丰富的库生态系统 | JavaScript 拥有庞大的库生态系统,包括 React、Angular、Vue 等框架。还有 NPM,这是世界上最大的包管理器之一。| -| 强大的社区支持 | JavaScript 拥有庞大而活跃的社区,有大量的学习和开发资源。它在浏览器中也能直接运行,这是一个巨大的优势。| -| IDE 和工具 | JavaScript 有多种可用的 IDE,如 Visual Studio Code、WebStorm 和 Atom。这些 IDE 有由公司和社区构建的扩展,帮助你处理各种开发方面的工作。| -| AI 和 JavaScript | JavaScript 支持 AI 开发,拥有 TensorFlow.js、Brain.js、OpenAI 的 API 等库,使开发者能够将机器学习和生成式 AI 集成到 Web 和服务器端应用程序中。| - -**你**: 这么多原因,听起来我应该在未来的项目中选择 JavaScript。 - -**时间甲虫**: 确实,JavaScript 是一种多功能语言,当然 Python 也是 AI 开发中的流行语言。 - -**你**: Python?蛇和编程有什么关系? - -**时间甲虫**: 让我们改天再讨论这个问题,好吗? - -**时间甲虫**: 我在上面给出了 JavaScript 及其生态系统总体上适合的原因,但为什么特别适合生成式 AI 呢?答案是它得到了许多云供应商和 AI 框架工具的支持。我们也相信,尽管 Python 可能是 AI 场景的首选,但许多开发者正在使用 JavaScript 和 TypeScript。 - -> **你知道吗?** -> [62.5% 的开发者表示他们在使用 JavaScript](https://www.statista.com/statistics/793628/worldwide-developer-survey-most-used-languages/),许多人在新项目中更喜欢使用 [TypeScript](https://www.typescriptlang.org)。 - -## 作业 – 帮助 迪诺克拉底 - -为了使用大语言模型(LLM)帮助 迪诺克拉底 建造我们之前故事中提到的灯塔,我们将使用所谓的提示词,即一句用来描述你想要什么的句子。你可以同时指定你需要的信息以及你希望它如何呈现。 - -**时光甲虫**: 让我们开始吧,让我们用 LLM 来研究如何帮助 迪诺克拉底 建造灯塔。 - -**时光甲虫**: 你需要为 LLM(也就是"我")提供上下文,说明如何建造,以及在亚历山大时代应该有什么工具和资源可用。 - -**你**: 好的,告诉我更多关于 LLM 的信息。 - -**时光甲虫**: LLM 是一种能够基于给定提示生成类人文本的 AI 模型。它们经过海量数据训练,能够生成连贯、有创意且与上下文相关的文本。 - -**时光甲虫**: 你可能想要用更好的方式来问我,这样我就能给出更好的答案,你知道的 *咳咳* *咳咳* 灯塔、亚历山大、公元前 300 年、迪诺克拉底、亚历山大灯塔等等。 - -**你**: 明白了,添加更多上下文到提示中,然后再问你。 - -**时光甲虫**: 好的,我在等着呢... - -访问 [Microsoft Copilot](https://copilot.microsoft.com)、[ChatGPT](https://chatgpt.com/) 或其他在线聊天机器人工具,生成在亚历山大建造灯塔的计划。 - -> 提示:尝试让 LLM 生成一个包含分步说明的灯塔建造计划。需要帮助?查看解决方案获取指导。 - -## 解决方案 - -[解决方案](/lessons/01-intro-to-genai/solution/solution.md) - -### 知识检查 - -**问题**: 以下关于生成式 AI 和 JavaScript 的说法中,哪一个是正确的? - -A. 基于 JavaScript 的生成式 AI 应用只能生成文本。 -B. JavaScript 可以用于构建 AI 驱动的应用程序,包括聊天机器人、文本生成工具等。 -C. Python 是唯一用于 AI 开发的语言。 - -[测验解决方案](/lessons/01-intro-to-genai/solution/solution-quiz.md) - -## 自学资源 - -- [生成式 AI JavaScript 视频系列](https://aka.ms/genai-js) diff --git a/lessons/02-first-ai-app/README.md b/lessons/02-first-ai-app/README.md index ddcedf21..522ad4ee 100644 --- a/lessons/02-first-ai-app/README.md +++ b/lessons/02-first-ai-app/README.md @@ -10,16 +10,6 @@ In this chapter you will learn: If you haven't already, set up your development environment. Here's how you can do it: [Setup your environment](/docs/setup/README.md). -## Related Resources - -[![Watch a short video about large language models](https://img.youtube.com/vi/GQ_2OjNZ9aA/0.jpg)](https://www.youtube.com/watch?v=GQ_2OjNZ9aA&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=2) - -_This video gives you an introduction to AI models called "large language models", LLMs, what they are and how you can use them to integrate AI into your apps._ - -*🎥 Click on the image above to watch a short video about large language models* - -💼 Slides: [Introduction to large language models, LLMs](/videos/slides/01-llms.pptx) - ## Narrative: Picture yourself in a boat on a river @@ -126,14 +116,14 @@ If you want to interact with Leonardo, run the [Characters](/app/README.md) app. 1. Start a [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) 2. Navigate to _/app/README.md_ in the repo root. -3. Locate the console and run `npm install` followed by `npm start`. +3. Locate the console and run `npm ci` followed by `npm start`. 4. Once it appears, select the "Open in Browser" button. 5. Chat with Leonardo. For a more detailed explanation of the app, see [Detailed app explanation](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). > [!NOTE] - > If you're running the project locally on your machine, please review the QuickStart guide to get a [GitHub personal access](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) token setup and replace the key in the code. +> Configure `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL` in the repository-root `.env` for both local development and Codespaces. See the [setup guide](/docs/setup/README.md#configure-environment-variables). ## Development environment setup @@ -175,7 +165,7 @@ Before you can assist Leonardo with his project, you should first think about th **Time Beetle:** Generative AI models can be used for many things, for example, language translation. In fact, it accepts input in one language and can generate text in another language. Let's start with a simple app that takes English input and generates Italian text. -```javascript +```javascript import { OpenAI } from "openai"; @@ -194,9 +184,16 @@ ${question} // 2. Create client // ----------------------------------- +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} + const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, + baseURL: endpoint, + apiKey, }); @@ -226,13 +223,13 @@ Let's explain what's happening here: **You:** I think I get it. So if I change the value of the `question` variable to something else, the app will generate a different Italian translation? -**Time Beetle:** Exactly, you can change the input text to anything you want. Also note how GitHub models are being used as the base URL for the API together with a GitHub token as the API key. +**Time Beetle:** The endpoint, API key, and deployment name come from your Microsoft Foundry resource. The code reads them from `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL`. **You:** Why is that important? -**Time Beetle:** It's important to use a base URL and API key that are specific to the model you're using. GitHub Models is a platform that hosts a variety of models, all with different capabilities and features, it's also free to use. +**Time Beetle:** Microsoft Foundry hosts the model deployment. Use an API key from the same resource as the endpoint, and set `AI_MODEL` to the deployment name. Model usage can incur Azure charges; check pricing and quota. -**You:** Oh good, I don't know who to pay anyways and I doubt they accept my currency here. :) +**You:** Then I'll use a small deployment and check the costs before experimenting. ## Chat apps @@ -268,7 +265,7 @@ AI: Rome is known for its ancient ruins, art, and vibrant culture. You can visit **Time Beetle:** Below is how we can construct a conversation with the AI: -```javascript +```javascript // Define the context @@ -295,9 +292,16 @@ const messages = [ } ]; +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} + const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, + baseURL: endpoint, + apiKey, }); @@ -499,5 +503,5 @@ C. The context window determines how creative the AI's responses are. - [Text generation](https://platform.openai.com/docs/guides/text-generation) - [JavaScript library for OpenAI](https://github.com/openai/openai-node/tree/master/examples) - [Tokenizer](https://platform.openai.com/tokenizer) -- [Completion API](https://platform.openai.com/docs/api-reference/completions) -- [Chat completions](https://platform.openai.com/docs/guides/text-generation#text-generation-models) +- [Completion API](https://github.com/openai/openai-node#usage) +- [Chat completions](https://platform.openai.com/docs/guides/text-generation#text-generation-models) diff --git a/lessons/02-first-ai-app/sample-app/app.js b/lessons/02-first-ai-app/sample-app/app.js index 35f2b269..4f284211 100644 --- a/lessons/02-first-ai-app/sample-app/app.js +++ b/lessons/02-first-ai-app/sample-app/app.js @@ -1,4 +1,11 @@ import { OpenAI } from "openai"; + +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} // 1. Define the prompt // ----------------------------------- @@ -18,8 +25,9 @@ const messages = [ // ----------------------------------- const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, + baseURL: endpoint, + apiKey, + timeout: 60000, }); @@ -27,7 +35,7 @@ const openai = new OpenAI({ // ----------------------------------- const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', + model, messages: messages, }); @@ -36,4 +44,6 @@ console.log(`Answer for "${question}":`); // 4. Print the answer // ----------------------------------- -console.log(completion.choices[0]?.message?.content); +const answer = completion.choices[0]?.message?.content; +if (!answer?.trim()) throw new Error("The model did not return an answer."); +console.log(answer); diff --git a/lessons/02-first-ai-app/sample-app/package-lock.json b/lessons/02-first-ai-app/sample-app/package-lock.json index ae1264ed..d3886f9f 100644 --- a/lessons/02-first-ai-app/sample-app/package-lock.json +++ b/lessons/02-first-ai-app/sample-app/package-lock.json @@ -9,218 +9,38 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "openai": "^4.84.1" - } - }, - "node_modules/@types/node": { - "version": "18.19.75", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.75.tgz", - "integrity": "sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "openai": "^7.28.0" } }, "node_modules/openai": { - "version": "4.84.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.84.1.tgz", - "integrity": "sha512-bVdFsfeVBXN59x9i26dHnYUkY5JPk19w7SydzMvJZDsA+FxwXZrQsUBtdwsQt/ZLuilcc/LzJG7mpJucO2DDHg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.28.0.tgz", + "integrity": "sha512-HSY4fFLflQGYe2kvO0atm6/GzhD61gpyiOZZUvO4nptMUhuB1to2K3wwFpFqc5gfEK0SxyIIFiu1yE2MtygWcA==", "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" + "engines": { + "node": ">=22.0.0" }, "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, "ws": { "optional": true }, @@ -228,43 +48,6 @@ "optional": true } } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } } } } diff --git a/lessons/02-first-ai-app/sample-app/package.json b/lessons/02-first-ai-app/sample-app/package.json index 106a315c..9646d953 100644 --- a/lessons/02-first-ai-app/sample-app/package.json +++ b/lessons/02-first-ai-app/sample-app/package.json @@ -4,13 +4,13 @@ "main": "app.js", "type": "module", "scripts": { - "start": "node app.js" + "start": "node --env-file-if-exists=../../../.env app.js" }, "keywords": [], "author": "", "license": "ISC", - "description": "Sample app showcasing AI usage using GitHub model", + "description": "First AI app using a configured OpenAI-compatible model", "dependencies": { - "openai": "^4.84.1" + "openai": "^7.28.0" } } diff --git a/lessons/02-first-ai-app/solution/solution.md b/lessons/02-first-ai-app/solution/solution.md index cef38b55..fce8c855 100644 --- a/lessons/02-first-ai-app/solution/solution.md +++ b/lessons/02-first-ai-app/solution/solution.md @@ -1,6 +1,6 @@ Here's the solution -```javascript +```javascript import { OpenAI } from "openai"; // 1. Define the prompt @@ -21,9 +21,16 @@ const messages = [ // 2. Create client // ----------------------------------- +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} + const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, + baseURL: endpoint, + apiKey, }); @@ -31,7 +38,7 @@ const openai = new OpenAI({ // ----------------------------------- const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', + model, messages: messages, }); diff --git a/lessons/02-first-ai-app/translations/README.bn.md b/lessons/02-first-ai-app/translations/README.bn.md deleted file mode 100644 index 734ac090..00000000 --- a/lessons/02-first-ai-app/translations/README.bn.md +++ /dev/null @@ -1,554 +0,0 @@ -# অধ্যায় ২: আপনার প্রথম AI অ্যাপ লেখার জন্য - -এই অধ্যায়ে আপনি শিখবেন: - -- আপনার ডেভেলপমেন্ট এনভায়রনমেন্ট সেট আপ করা। -- একটি মৌলিক অ্যাপ লেখা। -- সিস্টেম প্রম্পট বোঝা। - -## সেটআপ - -যদি আপনি এখনো সেটআপ না করে থাকেন, তাহলে আপনার ডেভেলপমেন্ট এনভায়রনমেন্ট সেট আপ করুন। এটি করার জন্য এই লিঙ্কটি অনুসরণ করুন: [আপনার এনভায়রনমেন্ট সেটআপ করুন](/docs/setup/README.md)। - -## সম্পর্কিত রিসোর্সসমূহ - -[![বড় ভাষা মডেল সম্পর্কে একটি ছোট ভিডিও দেখুন](https://img.youtube.com/vi/GQ_2OjNZ9aA/0.jpg)](https://www.youtube.com/watch?v=GQ_2OjNZ9aA&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=2) - -_এই ভিডিওটি AI মডেল, বিশেষ করে "বড় ভাষা মডেল" (LLMs) কী এবং কীভাবে এগুলো ব্যবহার করে আপনার অ্যাপে AI সংযুক্ত করা যায় তা পরিচয় করিয়ে দেয়।_ - -*🎥 উপরের ছবিতে ক্লিক করে বড় ভাষা মডেল সম্পর্কে একটি ছোট ভিডিও দেখুন* - -💼 স্লাইড: [বড় ভাষা মডেল, LLMs-এর পরিচিতি](/videos/slides/01-llms.pptx) - -## গল্প: নিজেকে একটি নদীতে একটি নৌকায় কল্পনা করুন - -> [!NOTE] -> _আমাদের গল্প এখন পর্যন্ত: আপনি একজন সৃষ্টিকারী, ১৮৬০-এর দশকের লন্ডনের একজন কারিগর, যিনি "টাইম বিটল" নামক একটি রহস্যময় যন্ত্র ব্যবহার করে সময় ভ্রমণ করেছেন। ইতিহাসের বিভিন্ন যুগ পার হয়ে, আপনি আলেকজান্দ্রিয়ার বাতিঘর নির্মাণের সাক্ষী হয়েছেন এবং ডিনোক্রেটস ও টাইম বিটলের সহায়তায় এটিকে নির্মাণে সহায়তা করেছেন।_ -> -> গল্পের শুরু থেকে শুরু করতে এবং জেনারেটিভ AI সম্পর্কে জানতে [অধ্যায় ১](/lessons/01-intro-to-genai/translations/README.bn.md) দেখুন। - -> [!NOTE] -> আমরা গল্পটি অনুসরণ করতে সুপারিশ করি (এটি মজাদার!), তবে আপনি যদি সরাসরি প্রযুক্তিগত অংশে যেতে চান, [এখানে ক্লিক করুন](#লিওনার্দোর-সাথে-যোগাযোগ-করুন)। - -ডিনোক্রেটসের সাথে মিলে, আপনি আলেকজান্দ্রিয়ার বাতিঘরের শেষ কাজ সম্পন্ন করছেন। উঁচু কাঠামোটি সূর্যের আলোয় ঝলমল করছে, তার চকচকে পাথর ভূমধ্যসাগরের জলে প্রতিফলিত হচ্ছে। - -আপনি আপনার হাতে রাখা টাইম বিটলের দিকে তাকান, তার ধাতব পৃষ্ঠ ঠান্ডা অনুভূত হয়। এটি শক্তভাবে মুঠিতে ধরে আপনি ফিসফিস করেন, **"আমাকে বাড়ি নিয়ে চলো।"** টাইম বিটলটি উজ্জ্বল হতে শুরু করে, একটি নরম, উষ্ণ আলো বিচ্ছুরিত করে, এবং আপনার চারপাশের বিশ্ব বর্ণিল এক ঝড়ে মিশে যায়। - -### একটি নতুন অভিযান - -আপনি যখন চোখ খুললেন, তখন চারপাশের পৃথিবী বদলে গেছে। ধীরে ধীরে উঠে দাঁড়িয়ে দেখলেন, আপনি একটি নদীর মাঝখানে নৌকায় রয়েছেন। চারপাশে তাকিয়ে দূরে কিছু ভবনের অবয়ব দেখতে পেলেন, যা সকালের কুয়াশায় আবছা হয়ে রয়েছে। - -নৌকার চারপাশে নজর বুলিয়ে, আপনি একটি লম্বা বৈঠা দেখতে পেলেন, যা পাশে রাখা ছিল। সেটি হাতে নিয়ে, আপনি ভবনগুলোর দিকে ধীরে ধীরে নৌকা চালাতে শুরু করলেন। যত এগিয়ে গেলেন, ততই ভবনগুলো স্পষ্ট হয়ে উঠল। এগুলো বেশ পুরোনো, তাদের স্থাপত্যশৈলী রেনেসাঁ যুগের কোনো চিত্রকর্মের মতো মনে হচ্ছে। - -
- নদীর উপর নৌকা, একজন মানুষ বৈঠা ধরে দাঁড়িয়ে আছে -
- -প্রশ্ন হলো, এবার আপনি কোথায় এবং কোন সময়ে এসে পৌঁছেছেন? - -আপনি ধীরে ধীরে নৌকাটি ডকে নিরাপদে বেঁধে রাখলেন এবং কাঠের তক্তাপথ ধরে হাঁটতে শুরু করলেন। আপনার পায়ের শব্দ নরমভাবে প্রতিধ্বনিত হতে লাগল। - -হাঁটতে হাঁটতে হঠাৎই আপনার নজরে পড়ল একজন দীর্ঘ দাড়িওয়ালা ব্যক্তি, মাথায় টুপি, যিনি একটি কাঠের বাক্সের মধ্যে কিছু যান্ত্রিক অংশ খুঁজে দেখছেন। তাঁর হাত দ্রুত ও দক্ষতার সাথে বিভিন্ন গিয়ার এবং স্প্রিংস ছাঁটাই করছে, যেন তিনি এ কাজের অভিজ্ঞ কারিগর। - -
- হারবারের পাশে একটি কাঠের বাক্সের সামনে দাঁড়িয়ে লিওনার্দো দা ভিঞ্চি -
- -### আমাকে সাহায্য করুন, লিওনার্দো - -**আপনি:** "মাফ করবেন, স্যার, আমি কোথায় আছি?" -তিনি আপনার দিকে তাকান, চোখে বিভ্রান্তির ছাপ স্পষ্ট। বুঝতে পেরে যে আপনি ইংরেজিতে কথা বলছেন, দ্রুত হাতে থাকা যন্ত্রটি ব্যবহার করে অনুবাদ করতে বলেন। - -**টাইম বিটল:** "অবশ্যই, আমি ১৫শ শতাব্দীর ইতালীয় ভাষায় অনুবাদ করছি। 'Dove sono?'" - -**বৃদ্ধ ব্যক্তি:** বৃদ্ধ ব্যক্তি জবাব দেন, **"Siete a Firenze, signore. E chi siete voi?"** - -**টাইম বিটল:** টাইম বিটল অনুবাদ করে, "তিনি বলছেন, আপনি ফ্লোরেন্সে আছেন এবং জানতে চাইছেন আপনি কে।" - -**আপনি:** "বলুন, আমি একজন কারিগর এবং কাজ করার জন্য একটি জায়গা খুঁজছি।" - -**বৃদ্ধ ব্যক্তি:** **"Un artigiano, eh? Avete mai sentito parlare di Leonardo da Vinci?"** - -**টাইম বিটল:** টাইম বিটল অনুবাদ করে, "তিনি জানতে চাইছেন, আপনি লিওনার্দো দা ভিঞ্চির কথা শুনেছেন কি না।" - -**আপনি:** "অবশ্যই," আপনি বলেন। "বলুন, আমি তার সম্পর্কে জানি এবং তার সাথে দেখা করতে চাই।" - -**বৃদ্ধ ব্যক্তি:** বৃদ্ধ ব্যক্তি হাসেন, **"Allora, seguitemi, vi porterò da lui."** - -**আপনি:** আপনি জিজ্ঞাসা করেন, "তিনি কী বললেন?" - -**টাইম বিটল:** টাইম বিটল উত্তর দেয়, "তিনি বলেছেন, তিনি আপনাকে লিওনার্দোর কাছে নিয়ে যাবেন।" - ---- - -### কর্মশালায় - -
- লিওনার্দোর কর্মশালা -
- -বৃদ্ধ ব্যক্তি আপনাকে একটি বড় কাঠের দরজার সামনে নিয়ে যান। দরজা খুলতেই, আপনি দেখতে পান বিভিন্ন ধরনের যান্ত্রিক উদ্ভাবনে পূর্ণ একটি কর্মশালা। - -আপনি টাইম বিটলকে লিওনার্দোর অবস্থান সম্পর্কে জিজ্ঞাসা করতে বলেন। - -**আপনি:** "Dove è Leonardo?" - -**বৃদ্ধ ব্যক্তি:** বৃদ্ধ ব্যক্তি আপনার দিকে হাসিমুখে তাকিয়ে বলেন, **"Sono io (that’s me), Leonardo da Vinci. Chi siete voi?"** (আমি লিওনার্দো দা ভিঞ্চি। আপনি কে?) - -আপনার মনে এক ধরনের উত্তেজনা অনুভূত হয়। - -**আপনি:** "আমি তাই ভেবেছিলাম। আমি একজন কারিগর, কিন্তু আমার সময় এবং স্থান দুটোই আলাদা।" - -**লিওনার্দো:** লিওনার্দোর চোখ কৌতূহলে ঝলমল করে ওঠে। **"Interessante, cosa vi porta qui?"** (দারুণ! আপনাকে এখানে কী এনেছে?) -### টাইম বিটল অনুবাদ করে - -**টাইম বিটল:** "তিনি জানতে চাইছেন, আপনাকে এখানে কী এনেছে?" - -**আপনি:** "আসলে, আমি একটি প্রকল্পে কাজ করছিলাম, আর তার পরেই এখানে এসে পড়েছি।" - -আপনি তাকে টাইম বিটল দেখান, এবং তার চোখ কৌতূহলে জ্বলে ওঠে। তিনি এটি মনোযোগ সহকারে পরীক্ষা করতে থাকেন, যখন আপনি ব্যাখ্যা করেন এটি কীভাবে কাজ করে এবং কীভাবে আপনি ফ্লোরেন্সে এসে পৌঁছালেন। - -লিওনার্দো উত্তেজিতভাবে আপনার দিকে তাকান। - -**লিওনার্দো:** "আপনি তো একজন সৃষ্টিশীল কারিগর। আমার একটি প্রকল্প আছে যা আপনার আগ্রহের হতে পারে। আমি এমন একটি যন্ত্র তৈরির চেষ্টা করছি, যা মৌখিক ইনপুটের উপর ভিত্তি করে লেখা তৈরি করতে পারে। আপনি কি আমাকে এতে সাহায্য করতে আগ্রহী?" - -লিওনার্দো দা ভিঞ্চি নিজেই আপনাকে তার প্রকল্পে সাহায্য করতে বলছেন—এটি যেন স্বপ্নের মতো মনে হচ্ছে। আপনি উত্তেজিতভাবে মাথা নাড়িয়ে বলেন: - -**আপনি:** "আমি আনন্দের সাথে আপনার প্রকল্পে সাহায্য করব।" -**"Sarebbe un onore aiutarti con il tuo progetto."** -## লিওনার্দোর সাথে যোগাযোগ করুন - -যদি আপনি লিওনার্দোর সাথে কথোপকথন করতে চান, তাহলে [Characters](/app/README.md) অ্যাপ চালান। - -> [!IMPORTANT] -> এটি সম্পূর্ণ কাল্পনিক; প্রতিক্রিয়াগুলি AI দ্বারা তৈরি করা হয়। -> [দায়িত্বশীল AI সংক্রান্ত ঘোষণা](/README.md#responsible-ai-disclaimer) - -
- -
- -### **ধাপে ধাপে নির্দেশনা**: - -1. শুরু করুন [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. রিপোজিটরির মূল ফোল্ডারে _/app/README.md_ এ যান। -3. কনসোলে `npm install` চালান, তারপর `npm start` কমান্ড দিন। -4. যখন অ্যাপটি চালু হবে, তখন "Open in Browser" বোতামটি নির্বাচন করুন। -5. এখন আপনি লিওনার্দোর সাথে চ্যাট করতে পারেন। - -আরও বিস্তারিত জানতে, দেখুন [বিস্তারিত অ্যাপ ব্যাখ্যা](/lessons/01-intro-to-genai/translations/README.bn.md#ডিনোক্রেটিসের-সাথে-সাক্ষাৎ)। - -> [!NOTE] -> আপনি যদি প্রকল্পটি আপনার স্থানীয় কম্পিউটারে চালান, তাহলে দয়া করে **QuickStart গাইড** অনুসরণ করে [GitHub ব্যক্তিগত অ্যাক্সেস টোকেন (PAT)](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) সেটআপ করুন এবং কোডে টোকেনটি প্রতিস্থাপন করুন। -> ## ডেভেলপমেন্ট এনভায়রনমেন্ট সেটআপ - -লিওনার্দোকে তার প্রকল্পে সাহায্য করার আগে, প্রথমেই আপনার **"কারিগরি সরঞ্জাম"** সম্পর্কে চিন্তা করা উচিত, যা আপনাকে কার্যকরভাবে সহায়তা করতে সাহায্য করবে। - -**আপনি:** "টাইম বিটল, এই প্রকল্পটি শুরু করতে আমার কী কী দরকার?" এমন কিছু টুল এবং লাইব্রেরি সাজেস্ট করো যা আমাকে একটি AI অ্যাপ তৈরিতে সহায়তা করতে পারে এবং যা তোমার মাধ্যমে চালানো সম্ভব। - -**টাইম বিটল:** আমি ২১শ শতকের বেশিরভাগ কম্পিউটারের সাথে সামঞ্জস্যপূর্ণ। এখানে কিছু গুরুত্বপূর্ণ সরঞ্জাম এবং লাইব্রেরির তালিকা রয়েছে যা আপনার কাজে লাগবে: - -- **টেক্সট এডিটর**, যেমন Visual Studio Code। -- **টার্মিনাল**, কমান্ড চালানোর জন্য। -- **ব্রাউজার**, আপনার অ্যাপ টেস্ট করার জন্য। এছাড়াও, API এন্ডপয়েন্ট পরীক্ষা করার জন্য `curl` বা অন্য কোনো HTTP ক্লায়েন্ট থাকা ভালো। -- **Node.js**, যা আপনাকে জাভাস্ক্রিপ্ট কোড চালাতে সহায়তা করবে। আপনাকে Node.js এবং npm ইনস্টল করতে হবে। -- **API কী**, আপনি জেনারেটিভ AI মডেলের অ্যাক্সেস পেতে একটি API কী প্রয়োজন হবে। এটি OpenAI বা Azure OpenAI-এর মতো কোনো মডেল প্রদানকারীর কাছ থেকে সংগ্রহ করতে হবে। - -**আপনি:** "ধন্যবাদ, টাইম বিটল! তুমি বলেছিলে সেই ওয়েব থেকে এগুলো খুঁজে আনতে পারবে, তাই না?" - -**টাইম বিটল:** "হ্যাঁ, আমি ইতিমধ্যেই সেগুলো সংগ্রহ করেছি," সে বলে এবং সামনে দেয়ালে একটি কীবোর্ড ও স্ক্রিন প্রজেক্ট করে। -## একটি মৌলিক অ্যাপ লেখা - -**আপনি:** "অ্যাপ লেখার প্রকৃত প্রক্রিয়া সম্পর্কে আরও বলো, আমি কীভাবে শুরু করব?" - -**টাইম বিটল:** "অবশ্যই, একটি অ্যাপ লেখার মূল বিষয় হলো **একটি API-তে অনুরোধ পাঠানো এবং প্রাপ্ত প্রতিক্রিয়া প্রদর্শন করা**। আসুন এটিকে ধাপে ধাপে দেখি: - -- **ইনপুট:** একটি সাধারণ টেক্সট জেনারেশন অ্যাপে, ইনপুট হলো প্রাথমিক পাঠ্য যা অ্যাপটি প্রসারিত বা রূপান্তর করবে। এটি হয় **ব্যবহারকারী** ইনপুট হিসেবে দিতে পারে অথবা **হার্ডকোডেড** হতে পারে। আমরা শুরুতে হার্ডকোডেড ইনপুট দিয়ে শুরু করব। - -- **API অনুরোধ:** আপনাকে **Generative AI মডেল API**-তে ইনপুট পাঠাতে হবে। এটি **JavaScript-এর fetch ফাংশন** (Node.js) ব্যবহার করে করা যেতে পারে। - - এই অনুরোধের সাথে **API কী** অন্তর্ভুক্ত করা প্রয়োজন, তবে এটি সরাসরি কোডে হার্ডকোড না করে **পরিবেশ পরিবর্তনশীল (environment variable)** ব্যবহার করা নিরাপদ। - - আপনি যদি Azure-এর মতো কোনো ক্লাউড পরিষেবা ব্যবহার করেন, তাহলে **Managed Identity** ব্যবহারের কথা ভাবতে পারেন, যা নিরাপদ উপায়ে সংস্থান অ্যাক্সেস করতে সহায়তা করে। - -- **প্রতিক্রিয়া (Response):** API একটি **উত্পন্ন পাঠ্য** ফেরত পাঠাবে, যা আপনাকে বের করতে হবে এবং ব্যবহারকারীর জন্য প্রদর্শন করতে হবে। - -**আপনি:** "এটি যথেষ্ট সহজ শোনাচ্ছে, তুমি কি বর্তমান পরিস্থিতির উপর ভিত্তি করে একটি বাস্তব উদাহরণ দিতে পারো?" - -**টাইম বিটল:** "অবশ্যই, আসুন আমরা এমন একটি সহজ অ্যাপ তৈরি করি যা ইংরেজি ইনপুটের ভিত্তিতে ইতালীয় পাঠ্য তৈরি করবে।" - ---- - -## আপনার প্রথম অ্যাপ - আমাকে ইতালিয়ান শেখাও - -**টাইম বিটল:** "Generative AI মডেল বিভিন্ন কাজের জন্য ব্যবহার করা যায়, যেমন **ভাষা অনুবাদ**। এটি এক ভাষায় ইনপুট গ্রহণ করে এবং অন্য ভাষায় পাঠ্য তৈরি করতে পারে। আসুন এমন একটি সহজ অ্যাপ তৈরি করি যা ইংরেজি ইনপুট গ্রহণ করে এবং ইতালীয় ভাষায় অনুবাদ করে।" - -```javascript -import { OpenAI } from "openai"; - -// 1. অনুবাদের জন্য একটি প্রশ্ন দিন -// ----------------------------------- - -const question = 'Hello, how are you?'; - -const augmentedPrompt = ` -## নির্দেশাবলী -নিম্নলিখিত পাঠ্যটি ইতালিয়ান ভাষায় অনুবাদ করুন: -## প্রশ্ন -${question} -`; - -// 2. ক্লায়েন্ট তৈরি করুন -// ----------------------------------- - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, // পরিবেশ পরিবর্তনশীল ব্যবহার করুন -}); - -// 3. অনুরোধ পাঠান -// ----------------------------------- -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: [{ role: 'user', content: augmentedPrompt }], -}); - -console.log(`"${question}" এর উত্তর:`); - -// 4. উত্তর প্রিন্ট করুন -// ----------------------------------- - -console.log(completion.choices[0]?.message?.content); -``` - ---- - -### **আপনার অ্যাপ চালানোর জন্য করণীয়:** -1. **Node.js এবং npm ইনস্টল করুন** -2. **আপনার API কী (GITHUB_TOKEN) সেট করুন** পরিবেশ পরিবর্তনশীল হিসাবে -3. **এই স্ক্রিপ্টটি চালান:** - ```bash - node your_script.js - ``` -4. **ইতালীয় অনুবাদ উপভোগ করুন!** - -এখন, আপনি লিওনার্দোর প্রকল্পে তার AI যন্ত্র তৈরিতে সহায়তা করতে প্রস্তুত! -চলুন ব্যাখ্যা করি এখানে কী ঘটছে: - -- **প্রশ্ন তৈরি করা:** `'Hello, how are you?'` এটি সেই পাঠ্য যা আপনি ইতালীয় ভাষায় অনুবাদ করতে চান। -- **বর্ধিত প্রম্পট তৈরি করা:** এতে ইনপুট পাঠ্য এবং অতিরিক্ত নির্দেশাবলী থাকে, যেমন অনুবাদ করতে হবে। লক্ষ্য করুন, আমরা কীভাবে **স্ট্রিং ইন্টারপোলেশন** ব্যবহার করে ইনপুট পাঠ্যটি প্রম্পটে অন্তর্ভুক্ত করেছি এবং নির্দেশাবলীতে উল্লেখ করেছি যে পাঠ্যটি ইতালীয় ভাষায় অনুবাদ করতে হবে। -- **ক্লায়েন্ট তৈরি করা:** - - **`model`** – কোন মডেলটি ব্যবহার করা হবে তা নির্ধারণ করা। - - **`messages`** – মডেলে পাঠানোর জন্য প্রম্পট। এখানে লক্ষ্য করুন, আমরা **`role`**-কে `"user"` সেট করেছি যাতে বোঝা যায় যে ইনপুট পাঠ্য ব্যবহারকারীর পক্ষ থেকে এসেছে। যদি এটি AI থেকে আসতো, তাহলে **`role`**-কে `"system"` সেট করা হতো। -- **উত্পন্ন পাঠ্যটি প্রতিক্রিয়া থেকে বের করা এবং কনসোলে প্রদর্শন করা।** - ---- - -**আপনি:** "আমি মনে করি আমি বুঝতে পেরেছি। তাহলে যদি আমি `question` ভেরিয়েবলের মান পরিবর্তন করি, তাহলে অ্যাপটি ভিন্ন ইতালীয় অনুবাদ তৈরি করবে?" - -**টাইম বিটল:** "একদম ঠিক! আপনি ইনপুট পাঠ্য যেকোনো কিছুতে পরিবর্তন করতে পারেন। লক্ষ্য করুন, **GitHub মডেল** API-এর বেস URL হিসাবে ব্যবহার করা হচ্ছে এবং **GitHub টোকেন** API কী হিসেবে ব্যবহার করা হয়েছে।" - -**আপনি:** "এটি গুরুত্বপূর্ণ কেন?" - -**টাইম বিটল:** "এটি গুরুত্বপূর্ণ কারণ আপনি যে মডেলটি ব্যবহার করছেন, সেটির জন্য নির্দিষ্ট **বেস URL এবং API কী** ব্যবহার করা প্রয়োজন। **GitHub Models** একটি প্ল্যাটফর্ম যেখানে বিভিন্ন ক্ষমতা ও বৈশিষ্ট্যের মডেল হোস্ট করা হয়, এবং এটি বিনামূল্যে ব্যবহারের জন্য উপলব্ধ।" - -**আপনি:** "ওহ দারুণ, যাক! আমি জানি না কাকে টাকা দিতে হবে, আর আমি সন্দেহ করি যে তারা আমার মুদ্রা গ্রহণ করবে কিনা। 😄" -## চ্যাট অ্যাপস - -**টাইম বিটল:** "Generative AI মডেলগুলোকে কথোপকথনের উপর ভিত্তি করে **পাঠ্য তৈরি করতেও ব্যবহার করা যায়**। আপনি AI-এর সাথে **একটি কথোপকথন অনুকরণ করতে পারেন** একটি **বার্তার তালিকা (messages list)** প্রদান করে, যা মনে হবে যেন কথোপকথন ইতিমধ্যেই ঘটেছে।" - -**আপনি:** "এটি বেশ আকর্ষণীয় শোনাচ্ছে, তবে এটি কীভাবে উপকারী?" - -**টাইম বিটল:** "এটি উপকারী কারণ এটি AI-কে একটি একক প্রম্পটের চেয়ে আরও **বিস্তৃত প্রসঙ্গের (context) উপর ভিত্তি করে উন্নত প্রতিক্রিয়া** দিতে সহায়তা করে। নিচের কথোপকথনটি দেখুন: - -```text -User: আমি ইতালিতে একটি ভ্রমণ বুক করতে চাই। - -AI: নিশ্চয়ই, আপনি কখন যেতে চান? - -User: আগামী মাসে গেলে ভালো হয়। - -AI: বুঝেছি, ইতালির কোথায় যেতে চান? - -User: আমি রোমে যাওয়ার কথা ভাবছি। - -AI: অসাধারণ পছন্দ! আমি আপনাকে আপনার ভ্রমণ পরিকল্পনায় সাহায্য করতে পারি। - -User: এ সম্পর্কে আরও বলুন। - -AI: রোম তার প্রাচীন ধ্বংসাবশেষ, শিল্প এবং সমৃদ্ধ সংস্কৃতির জন্য পরিচিত। আপনি কলোসিয়াম, ভ্যাটিকান ঘুরে দেখতে পারেন এবং সুস্বাদু ইতালীয় খাবার উপভোগ করতে পারেন। -``` - -**টাইম বিটল:** "কল্পনা করুন, যদি 'Tell me more about it' (এ সম্পর্কে আরও বলুন) বাক্যটি প্রসঙ্গ ছাড়া নেওয়া হতো, তাহলে AI বুঝতে পারত না **'it'** কী নির্দেশ করছে। ঠিক এই কারণেই **প্রসঙ্গ গুরুত্বপূর্ণ**। আমরা এই প্রসঙ্গটি **AI মডেলে প্রম্পটের মাধ্যমে সরবরাহ করতে পারি**।" - -**আপনি:** "আমি মনে করি আমি বুঝতে পারছি। তাহলে এই JavaScript ভাষা ব্যবহার করে কীভাবে আমি AI-এর সাথে কথোপকথন তৈরি করতে পারি?" - -**টাইম বিটল:** "নিচে দেখুন, কিভাবে আমরা AI-এর সাথে কথোপকথন তৈরি করতে পারি:" - ---- - -### **JavaScript ব্যবহার করে AI-এর সাথে কথোপকথন** - -```javascript -// প্রসঙ্গ নির্ধারণ করুন - -const messages = [ - { - "role": "user", - "content": "আমি ইতালিতে একটি ভ্রমণ বুক করতে চাই।" - }, - { - "role": "assistant", - "content": "নিশ্চয়ই, আপনি কখন যেতে চান?" - }, - { - "role": "user", - "content": "আগামী মাসে গেলে ভালো হয়।" - }, - { - "role": "assistant", - "content": "বুঝেছি, ইতালির কোথায় যেতে চান?" - }, - { - "role": "user", - "content": "আমি রোমে যাওয়ার কথা ভাবছি। এ সম্পর্কে আরও বলুন।" - } -]; - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, // API কী সংযুক্ত করুন -}); - -// অনুরোধ পাঠান -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, -}); - -// ফলাফল প্রদর্শন করুন -console.log("প্রতিক্রিয়া: "); -console.log(completion.choices[0]?.message?.content); -``` - ---- - -### **কিভাবে এটি কাজ করে?** - -1. **একটি বার্তার তালিকা তৈরি করা** (`messages` array) যেখানে ব্যবহারকারী এবং সহকারী (AI)-এর মধ্যকার কথোপকথন থাকবে। -2. **OpenAI ক্লায়েন্ট তৈরি করা** যেখানে বেস URL এবং API কী অন্তর্ভুক্ত করা হয়েছে। -3. **AI-এর কাছে অনুরোধ পাঠানো**, যেখানে পূর্ববর্তী কথোপকথনসহ বার্তাগুলি পাঠানো হয়। -4. **প্রতিক্রিয়া (response) কনসোলে মুদ্রণ করা**, যা AI থেকে পাওয়া পাঠ্য দেখায়। - -এখন আপনি AI-এর সাথে কথোপকথন তৈরি করতে সক্ষম! -এখন AI একটি কথোপকথনের প্রসঙ্গ হিসেবে বার্তাগুলোর তালিকা প্রদান করবে এবং সেই প্রসঙ্গের উপর ভিত্তি করে প্রতিক্রিয়া তৈরি করবে। এটি **Generative AI মডেলগুলোর আরও ইন্টারেক্টিভ ব্যবহার** এবং **চ্যাটবট, কাস্টমার সার্ভিস অ্যাপ্লিকেশন** ইত্যাদির জন্য কার্যকর হতে পারে। - ---- - -**আপনি:** "ঠিক আছে, তাহলে যদি আমি কথোপকথনটি সঠিকভাবে বুঝে থাকি, এখন AI-এর প্রসঙ্গে থাকবে: _'আমি আগামী মাসে রোমে যাচ্ছি'_। এর মানে হল, এটি অপ্রাসঙ্গিক তথ্য ফিল্টার করবে এবং আরও প্রাসঙ্গিক উত্তর প্রদান করবে, তাই তো?" - -**টাইম বিটল:** "একদম ঠিক! AI কথোপকথনের প্রসঙ্গ বুঝে আরও নির্ভুল এবং প্রাসঙ্গিক উত্তর প্রদান করবে।" - ---- - -## **সিস্টেম বার্তার মাধ্যমে চ্যাটের উন্নতি করা** - -**আপনি:** "বুঝতে পারলাম, কিন্তু এটি আরও কীভাবে উন্নত করা যায়?" - -**টাইম বিটল:** "হ্যাঁ, আপনি **সিস্টেম বার্তা (system message)** যুক্ত করতে পারেন। একটি **সিস্টেম বার্তা AI-এর জন্য একটি 'ব্যক্তিত্ব' তৈরি করে** এবং কথোপকথনে অতিরিক্ত প্রসঙ্গ প্রদান করতে সহায়তা করে।" - -**আপনি:** "ঠিক আছে, তাহলে আমাদের চলমান কথোপকথনের ক্ষেত্রে একটি সিস্টেম বার্তা কেমন হবে?" - -**টাইম বিটল:** "এই কথোপকথনের জন্য একটি **সিস্টেম বার্তা** হতে পারে: -_'আমি একজন AI ভ্রমণ সহকারী, আপনাকে ইতালিতে ভ্রমণ পরিকল্পনা করতে সাহায্য করতে এসেছি।'_ -এটি **কথোপকথনের ধরন নির্ধারণ করে** এবং AI-কে বোঝায় যে এটি কী ভূমিকা পালন করবে।" - ---- - -### **সিস্টেম বার্তা তৈরির জন্য কোড** -সিস্টেম বার্তা তৈরির জন্য কোড নিশ্চিত করুন যাতে এটি 'developer' টাইপের হয়, নিম্নরূপ: - -```javascript -const message = { - "role": "developer", - "content": "I'm an AI travel assistant, here to help you plan your trip to Italy." -}; -``` -> **নোট:** এটি আগে **"system"** নামে পরিচিত ছিল। এটি একটি সাম্প্রতিক পরিবর্তন, এবং এখন "developer" হলো নতুন শব্দটি। কিছু মডেলের জন্য এটি এখনও "system" নামে ব্যবহৃত হয়, তাই যদি কোনো সমস্যা হয়, তাহলে "system" ব্যবহার করুন। - -**আপনি:** "ঠিক আছে, দারুণ! আমি নিশ্চিত করব যে আমার চ্যাট কথোপকথনে একটি সিস্টেম বার্তা অন্তর্ভুক্ত থাকবে। কৌতূহলবশত, তোমার জন্য একটি সিস্টেম বার্তা কেমন হবে?" - -**টাইম বিটল:** "আমার জন্য একটি সিস্টেম বার্তা হতে পারে: -_'আমি টাইম বিটল, আপনাকে সময় এবং স্থানের মাধ্যমে নেভিগেট করতে সহায়তা করার জন্য এখানে আছি। আমি আপনাকে যে সময় যুগে আছেন তার সম্পর্কে তথ্য এবং দিকনির্দেশনা দিতে সহায়ক হব, পাশাপাশি আপনাকে নিজের সময়ে ফিরে যেতে প্রয়োজনীয় সরঞ্জাম সরবরাহ করব।'_" - ---- - -### **তাপমাত্রা সেটিংসের মাধ্যমে বিভিন্ন প্রতিক্রিয়া তৈরি করা** - -**আপনি:** "চ্যাট কথোপকথন সম্পর্কে আর কিছু জানা দরকার?" - -**টাইম বিটল:** "হ্যাঁ, আপনি AI-এর প্রতিক্রিয়ার **"temperature"** সমন্বয় করতে পারেন। -- **তাপমাত্রা** একটি ভেরিয়েবল যা সাধারণত **0 এবং 1-এর মধ্যে** সেট করা হয় এবং এটি AI-এর **সৃজনশীলতার মাত্রা নির্ধারণ করে**। -- **তাপমাত্রা = 0** হলে, AI আরও **অনুমানযোগ্য এবং নির্ভরযোগ্য প্রতিক্রিয়া প্রদান করবে**। -- **তাপমাত্রা = 1** হলে, **AI আরও সৃজনশীল ও বৈচিত্র্যময় প্রতিক্রিয়া তৈরি করবে**। -- **১-এর বেশি মান সেট করা সম্ভব**, তবে এটি **বেশি এলোমেলো এবং কম সুসংহত প্রতিক্রিয়া** তৈরি করতে পারে। - -**আপনি:** "তাহলে যদি আমি তাপমাত্রা **0** সেট করি, তাহলে AI আরও পূর্বানুমানযোগ্য প্রতিক্রিয়া দেবে, আর যদি আমি **1** সেট করি, তাহলে AI আরও সৃজনশীল প্রতিক্রিয়া দেবে? তোমার তাপমাত্রা কত?" - -**টাইম বিটল:** "আমার তাপমাত্রা **0.7**, এবং হ্যাঁ, তুমি ঠিকই বলেছো! উচ্চতর তাপমাত্রা AI-কে আরও **সৃজনশীল প্রতিক্রিয়া দিতে সহায়তা করে**। চলো দেখি, কীভাবে তোমার অ্যাপে **তাপমাত্রা সেট করা যায়**:" -```javascript -// প্রসঙ্গ নির্ধারণ করুন - -const messages = [ -{ - "role": "user", - "content": "আমি চাই তুমি আমার জন্য রেসিপি তৈরি করো।" -}]; - -// ওয়েব অনুরোধ তৈরি করুন - -let temperature = 0.5; // তাপমাত্রা 0.5 এ সেট করুন - -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, - temperature: temperature -}); -``` - -যেমনটি দেখা যাচ্ছে, **আপনি কথোপকথনের প্রসঙ্গ এবং AI থেকে যে ধরণের প্রতিক্রিয়া চান তার উপর ভিত্তি করে তাপমাত্রা সামঞ্জস্য করতে পারেন**। এটি একটি শক্তিশালী বৈশিষ্ট্য যা AI-এর সৃজনশীলতার মাত্রা কাস্টমাইজ করতে সহায়তা করে। - ---- - -## **প্রসঙ্গ উইন্ডো (Context Window)** - -**আপনি:** "এর বেশি কিছু আছে, তাই তো?" - -**টাইম বিটল:** "হ্যাঁ, Generative AI মডেলগুলোর আরেকটি গুরুত্বপূর্ণ ধারণা হলো **প্রসঙ্গ উইন্ডো (context window)**। -- প্রসঙ্গ উইন্ডো নির্ধারণ করে **AI কতগুলি পূর্ববর্তী বার্তা ব্যবহার করে প্রতিক্রিয়া তৈরি করবে**। -- **বড় প্রসঙ্গ উইন্ডো** AI-কে **আরও বেশি তথ্য বিবেচনা করতে এবং আরও সুসংহত প্রতিক্রিয়া তৈরি করতে সহায়তা করে**। - -**টাইম বিটল:** "বিভিন্ন মডেলের আউটপুট টোকেনের সীমা ভিন্ন হয়। নিচের মডেলটি দেখুন: - -**`gpt-4o-2024-08-06` এর নির্দিষ্টকরণ:** - -- **সর্বাধিক আউটপুট টোকেন:** প্রায় **১৬া হাজার টোকেন**। -- **সর্বাধিক প্রসঙ্গ উইন্ডো:** **১২৮ হাজার টোকেন**। - -এর মানে হলো, **মোট ১২৮k টোকেনের মধ্যে ১৬k টোকেন আউটপুটের জন্য ব্যবহৃত হবে, আর ১১২k টোকেন ইনপুট প্রসঙ্গে ব্যবহার করা যাবে।** - ---- - -**আপনি:** "বুঝলাম, প্রসঙ্গ উইন্ডো, টোকেন… কিন্তু একটি টোকেন আসলে কী?" - -**টাইম বিটল:** "একটি **টোকেন হলো একটি শব্দ বা শব্দের অংশ**, এবং এটি ভাষার উপর নির্ভর করে কিছুটা পরিবর্তিত হয়। - -এটি পরিমাপ করার জন্য একটি সরঞ্জাম রয়েছে, যা OpenAI সুপারিশ করে, এর নাম **[tokenizer](https://platform.openai.com/tokenizer)**। - -**আসুন একটি বাক্য চেষ্টা করি এবং দেখি এটি কতটি টোকেন ব্যবহার করে:** -```text -I want you to generate recipes for me. -``` - -![টোকেনাইজার ডেমো](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/02-first-ai-app/assets/tokenizer.png) - -উপরের বাক্যে **`tokenizer`** চালালে **৯টি টোকেন** পাওয়া যায়। - -**আপনি:** "এটা খুব বেশি নয়, তাহলে মনে হচ্ছে আমার প্রসঙ্গ উইন্ডোতে অনেক টোকেন রাখতে পারব?" - -**টাইম বিটল:** "হ্যাঁ, আপনি বিভিন্ন প্রসঙ্গ উইন্ডো আকারের সাথে পরীক্ষা-নিরীক্ষা করতে পারেন এবং এটি কীভাবে AI-এর প্রতিক্রিয়াকে প্রভাবিত করে তা দেখতে পারেন। - -আসলে, যদি আপনি **প্রসঙ্গ উইন্ডোর আকার ১০০ সেট করেন**, তবে এটি **AI-এর ইনপুট এবং আউটপুট বিবেচনার সীমা নির্ধারণ করবে**। - -এখানে দেখুন, কিভাবে আপনি আপনার অ্যাপে প্রসঙ্গ উইন্ডো সেট করতে পারেন: -```javascript -// প্রসঙ্গ নির্ধারণ করুন -const messages = [ -{ - "role": "user", - "content": "আমি চাই তুমি আমার জন্য রেসিপি তৈরি করো।" -}]; - -// প্রসঙ্গ উইন্ডোর আকার নির্ধারণ করুন -let max_tokens = 100; // প্রসঙ্গ উইন্ডোর আকার সেট করুন - -// ওয়েব অনুরোধ তৈরি করুন -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, - max_tokens: max_tokens -}); -``` - -> **পরামর্শ:** বিভিন্ন প্রসঙ্গ উইন্ডোর আকার নিয়ে পরীক্ষা করুন এবং দেখুন এটি কীভাবে AI-এর প্রতিক্রিয়াকে প্রভাবিত করে। - ---- - -## **অ্যাসাইনমেন্ট - একটি প্রকৌশল সহকারী তৈরি করা** - -লিওনার্দো হঠাৎ **টাইম বিটল** আরও কাছ থেকে পরীক্ষা করতে চাইলেন। তিনি এটিকে চারদিক থেকে দেখলেন, এমনকি এটি নাড়াচাড়া করলেন। - -**লিওনার্দো:** "আমার এমন একটি সহকারী দরকার যে আমাকে **এরিয়াল স্ক্রু (aerial screw)**-এর গণনা ও নকশায় সাহায্য করতে পারবে। তুমি কি আমার জন্য এমন একটি সহকারী তৈরি করতে পারবে?" - -**আপনি:** "অবশ্যই, আমি এটি তোমার জন্য তৈরি করতে পারি। টাইম বিটল, আমরা এতে সাহায্য করতে পারব, তাই তো?" - -**টাইম বিটল:** "হ্যাঁ, কোনো সমস্যা নেই। আসলে **এরিয়াল স্ক্রু** লিওনার্দোর অন্যতম **সবচেয়ে আকর্ষণীয় ও দূরদর্শী আবিষ্কার**। এটি ১৪৮০-এর দশকের শেষের দিকে ডিজাইন করা হয়েছিল..." - -**আপনি:** "আমার শুধু 'হ্যাঁ' দরকার ছিল, লেকচার পরে শুনব!" - -**টাইম বিটল:** "রূঢ়..." - -**আপনি:** "কি?" - -**টাইম বিটল:** "কিছু না..." - -
- এরিয়াল স্ক্রু, লিওনার্দো দা ভিঞ্চি -
-> [!NOTE] -> **এরিয়াল স্ক্রু**, যা **হেলিক্যাল এয়ার স্ক্রু** নামেও পরিচিত, এটি মাটির ওপর থেকে উঠতে **বায়ু সংকুচিত** করার নীতির ওপর ভিত্তি করে তৈরি করা হয়েছিল। লিওনার্দোর ডিজাইনটি ছিল একটি বিশাল **সর্পিল-আকৃতির রোটর**, যা **লিনেন দিয়ে তৈরি** এবং **স্টার্চ দিয়ে শক্ত করা** হয়েছিল, এবং এটি একটি কাঠের প্ল্যাটফর্মের ওপর স্থাপন করা হয়েছিল। ধারণাটি ছিল, **একদল মানুষ প্ল্যাটফর্মের চারপাশে ঘুরে ক্র্যাংক ঘোরাবে**, যা স্ক্রুটিকে যথেষ্ট গতিতে ঘোরাতে পারবে, ফলে এটি বাতাসে উঠতে পারবে। -> -> যদিও **লিওনার্দো কখনও পূর্ণ-স্কেলের এরিয়াল স্ক্রু তৈরি করতে পারেননি**, তবে তার স্কেচ এবং নোট থেকে স্পষ্ট হয় যে তিনি কীভাবে এটি কাজ করবে বলে কল্পনা করেছিলেন। তিনি বিশ্বাস করতেন, **যদি স্ক্রুটি যথেষ্ট দ্রুত ঘোরানো যায়, তবে এটি বাতাসের বিরুদ্ধে চাপ তৈরি করবে এবং পুরো কাঠামোকে উপরে তুলতে সক্ষম হবে**। -> -> তবে, **আধুনিক বিজ্ঞানীরা একমত যে লিওনার্দোর সময়ে বিদ্যমান উপকরণগুলো যথেষ্ট শক্তিশালী বা হালকা ছিল না**, যা এই ধারণাকে বাস্তবে রূপ দিতে পারত। -> -> তবুও, এরিয়াল স্ক্রু **লিওনার্দোর প্রতিভার এক অসাধারণ নিদর্শন** এবং তার উদ্ভাবনী চেতনাকে প্রকাশ করে। এটি **বিমান প্রযুক্তির ভবিষ্যৎ উন্নয়নের ভিত্তি** স্থাপন করেছিল এবং এখনো **প্রকৌশলবিদ ও উদ্ভাবকদের অনুপ্রাণিত করে যাচ্ছে**। -> [আরও পড়ুন](https://en.wikipedia.org/wiki/Leonardo%27s_aerial_screw) - ---- - -### **অ্যাসাইনমেন্ট: লিওনার্দোর জন্য একটি প্রকৌশল সহকারী তৈরি করুন** - -আপনার কাজ হলো **একটি প্রকৌশল সহকারী তৈরি করা**, যা **এরিয়াল স্ক্রু-এর গণনা এবং নকশার ক্ষেত্রে লিওনার্দোকে সাহায্য করবে**। - -✅ **সহকারীটি ব্যবহারকারীর ইনপুটের উপর ভিত্তি করে পাঠ্য তৈরি করতে পারবে।** -✅ **আপনাকে একটি "system message" সেট করতে হবে, যা সহকারীকে পরিচয় করিয়ে দেবে।** - -শুরু করতে **[Sample app](/app/)** দেখুন। - -> **পরামর্শ:** **System message** কেমন হওয়া উচিত এবং ব্যবহারকারীর কী ধরনের ইনপুট প্রদান করা উচিত তা বিবেচনা করুন। -## **সমাধান** - -[সমাধান](/lessons/02-first-ai-app/solution/solution.md) - ---- - -## **জ্ঞান যাচাই (Knowledge Check)** - -**প্রশ্ন:** **Generative AI মডেলগুলোর ক্ষেত্রে প্রসঙ্গ উইন্ডোর উদ্দেশ্য কী?** সঠিক উত্তরগুলো নির্বাচন করুন। - -A. **প্রসঙ্গ উইন্ডো AI-কে আরও প্রসঙ্গ বিবেচনা করতে এবং আরও সুসংহত প্রতিক্রিয়া তৈরি করতে সহায়তা করে।** - -B. **প্রসঙ্গ উইন্ডো হলো পূর্ববর্তী বার্তাগুলোর সংখ্যা, যা AI উত্তর তৈরি করতে ব্যবহার করে।** - -C. **প্রসঙ্গ উইন্ডো নির্ধারণ করে AI-এর প্রতিক্রিয়াগুলো কতটা সৃজনশীল হবে।** - -[কুইজের সমাধান](/lessons/02-first-ai-app/solution/solution-quiz.md) - ---- - -## **স্ব-অধ্যয়নের জন্য সংস্থানসমূহ** - -- [টেক্সট জেনারেশন](https://platform.openai.com/docs/guides/text-generation) -- [OpenAI-এর জন্য JavaScript লাইব্রেরি](https://github.com/openai/openai-node/tree/master/examples) -- [Tokenizer](https://platform.openai.com/tokenizer) -- [Completion API](https://platform.openai.com/docs/api-reference/completions) -- [Chat completions](https://platform.openai.com/docs/guides/text-generation#text-generation-models) diff --git a/lessons/02-first-ai-app/translations/README.es.md b/lessons/02-first-ai-app/translations/README.es.md deleted file mode 100644 index 8efdbf87..00000000 --- a/lessons/02-first-ai-app/translations/README.es.md +++ /dev/null @@ -1 +0,0 @@ -traduce esto diff --git a/lessons/02-first-ai-app/translations/README.fr.md b/lessons/02-first-ai-app/translations/README.fr.md deleted file mode 100644 index a313c04a..00000000 --- a/lessons/02-first-ai-app/translations/README.fr.md +++ /dev/null @@ -1,501 +0,0 @@ -# Leçon 2 : Écrire votre première application d'IA - -Dans ce chapitre, vous apprendrez : - -* Configurer votre environnement de développement. -* Écrire une application basique. -* Comprendre les invites système. - -## Configuration - -Si ce n'est pas encore fait, configurez votre environnement de développement. Voici comment procéder : [Configurez votre environnement](/docs/setup/README.md). - -## Ressources associées - -[![Regardez une courte vidéo sur les modèles de langage étendus.](https://img.youtube.com/vi/GQ_2OjNZ9aA/0.jpg)](https://www.youtube.com/watch?v=GQ_2OjNZ9aA\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=2) - -*Cette vidéo vous donne une introduction aux modèles d'IA appelés "modèles de langage étendus", LLM, ce qu'ils sont et comment vous pouvez les utiliser pour intégrer l'IA dans vos applications.* - -*🎥 Cliquez sur l’image ci-dessus pour regarder une courte vidéo sur les modèles de langage étendus* - -💼 Diapositives : [Introduction aux modèles de langage étendus, LLMs](/videos/slides/01-llms.pptx) - -## Narrative: Picture yourself in a boat on a river - -> [!NOTE] -> *Notre histoire jusqu'à présent : Vous êtes un créateur d'objets, un artisan du Londres des années 1860 qui a voyagé dans le temps grâce à un mystérieux appareil appelé le Scarabée du Temps. Vous avez traversé les annales de l'histoire, assistant à la création du phare d'Alexandrie, une merveille de l'ingénierie ancienne que vous avez contribué à construire avec un peu d'aide de Dinocrates et du Scarabée du Temps.* -> -> Voir [Leçon 1](/lessons/01-intro-to-genai/README.md) si vous souhaitez reprendre l'histoire depuis le début et vous initier à l'intelligence artificielle générative. - -> [!NOTE] -> Bien que nous recommandions de suivre l'histoire (c'est amusant !), [cliquez ici](#interact-with-leonardo) si vous préférez passer directement au contenu technique. - -Ensemble avec Dinocrates, vous mettez la touche finale au phare d'Alexandrie. L'imposante structure brille sous le soleil, sa pierre polie reflétant la mer Méditerranée. - -Vous regardez le Time Beetle dans votre main, sa surface métallique froide contre votre paume. Serrant le poing autour de celui-ci, vous murmurez : « Ramène-moi chez moi ». Le scarabée commence à briller, émettant une douce lumière chaude, et le monde qui vous entoure se dissout dans un tourbillon de couleurs. - -### Une nouvelle aventure - -Quand vous ouvrez les yeux, le monde a changé. En vous levant, vous réalisez que vous êtes dans un bateau sur une rivière. En regardant autour de vous, au loin, vous apercevez des bâtiments dont les contours sont floutés par la brume matinale. - -En regardant autour du bateau, vous trouvez une longue rame reposant sur le côté. La saisissant, vous commencez à ramer en direction des bâtiments au loin. À mesure que vous vous rapprochez, les bâtiments deviennent plus nets. Ils sont anciens, leur architecture rappelant une peinture de la Renaissance. - -!["Bateau sur la rivière, un homme debout avec une rame".](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/02-first-ai-app/assets/boat.png) - -La question est maintenant, où et quand êtes-vous cette fois ? - -Vous réussissez à amarrer le bateau au quai et commencez à marcher le long des planches en bois, le son de vos pas résonnant doucement. - -En marchant, vous remarquez un homme avec une longue barbe et un chapeau, fouillant dans une caisse contenant ce qui semble être des pièces mécaniques. Ses mains agiles trient les engrenages et ressorts avec une dextérité certaine. - -![Léonard de Vinci debout à côté d'une caisse dans le port.](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/02-first-ai-app/assets/leonardo.png) - -### Aidez-moi, Leonardo - -**Vous :** « Excusez-moi, monsieur, où suis-je ? » Il lève les yeux vers vous, la confusion se lit dans ses yeux. Réalisant que vous parlez en anglais, vous utilisez rapidement l'appareil dans votre main et lui demandez de traduire. - -**Scarabée du Temps :** « Bien sûr, je vais traduire en italien du XVe siècle. "Dove sono ?" » - -**Vieil homme :** Le vieil homme répond : « Siete a Firenze, signore. E chi siete voi ? » - -**Scarabée du Temps :** Le Scarabée du Temps traduit : « Il dit que vous êtes à Florence, et demande qui vous êtes. » - -**Vous :** « Dites-lui que je suis un fabricant de choses et que je cherche un endroit pour travailler. » - -**Vieil homme :** Un artigiano, eh ? Avete mai sentito parlare di Leonardo da Vinci ? - -**Scarabée du Temps :** Le Scarabée du Temps traduit : « Il demande si vous avez entendu parler de Léonard de Vinci. » - -**Vous :** « Bien sûr, » dites-vous. « Dites-lui que je le connais et que j'aimerais le rencontrer. » - -**Vieil homme :** Le vieil homme sourit : « Allora, seguitemi, vi porterò da lui. » - -**Vous :** Vous demandez : « Qu'est-ce qu'il a dit ? » - -**Scarabée du Temps :** Le Scarabée du Temps répond : « Il a dit qu'il vous emmènera chez Léonard. » - -### À l'atelier - -![Atelier de Léonard.](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/02-first-ai-app/assets/leonardo-workshop.png) - -Le vieil homme vous conduit à une grande porte en bois et vous êtes accueilli par la vue d’un atelier rempli de toutes sortes de machines mécanisées. - -Vous demandez au Time Beetle de s'enquérir de l'emplacement de Léonard de Vinci. - -**Vous :** « Dove è Leonardo ? » - -**Vieil homme :** Le vieil homme se retourne vers vous avec un sourire : « Sono io (c'est moi), Leonardo da Vinci. Chi siete voi ? », qui êtes-vous ? - -Vous ressentez une montée d’émotion en le reconnaissant. - -**Vous :** Je m'en doutais. Je suis un créateur comme vous, déplacé dans le temps et l'espace. » - -**Leonardo :** Les yeux de Léonard scintillent de curiosité. « Interessante, cosa vi porta qui ? » - -Le Time Beetle traduit. - -**Scarabée du Temps :** « Il demande ce qui vous amène ici. » - -**Vous :** « Eh bien, je travaillais sur un projet, et je me suis retrouvé ici. » - -Vous lui montrez le Time Beetle, et ses yeux s’illuminent de fascination. Il l'examine attentivement pendant que vous expliquez comment il fonctionne et comment vous avez atterri à Florence. - -Leonardo vous regarde avec excitation. - -**Leonardo :** Vous êtes un créateur. J'ai un projet qui pourrait vous intéresser. Je travaille sur une machine capable de générer du texte à partir d'entrées verbales. Cela vous intéresserait-il de m'aider ? - -Leonardo da Vinci vous demande de l’aider dans un projet — vous pouvez à peine y croire. Vous hochez la tête avec entrain et dites : - -**Vous :** « Ce serait un honneur de vous aider dans votre projet », "Sarebbe un onore aiutarti con il tuo progetto." - -## Interagir avec Leonardo - -Si vous voulez interagir avec Léonard, exécutez l'application [Personnages](/app/README.md). - -> [!IMPORTANT] -> Ceci est entièrement fictif ; les réponses sont générées par une IA. -> [Clause de non-responsabilité sur l'IA responsable](/README.md#responsible-ai-disclaimer) - -![Léonard parle.](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/02-first-ai-app/assets/leonardo-talk.jpeg) - -**Étapes** : - -1. Lancez un [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript). -2. Accédez à */app/README.md* à la racine du référentiel. -3. Localisez la console et exécutez `npm install` suivi de `npm start`. -4. Une fois que l'application est ouverte, sélectionnez le bouton "Ouvrir dans le navigateur". -5. Discutez avec Leonardo. - -Pour une explication plus détaillée de l'application, voir [Explication détaillée de l'application](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). - -> [!NOTE] -> Si vous exécutez le projet localement sur votre machine, veuillez consulter le guide de démarrage rapide pour configurer un [token d'accès personnel GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) et remplacer la clé dans le code. - -## Configuration de l'environnement de développement - -Avant de pouvoir aider Leonardo avec son projet, vous devriez d'abord réfléchir aux "outils indispensables" nécessaires pour l'aider efficacement. - -**Vous :** « Scarabée du Temps, de quoi ai-je besoin pour démarrer ce projet ? Suggère des outils et des bibliothèques qui pourraient m'aider à créer une application d'IA que tu es capable de faire fonctionner. » - -**Scarabée du Temps :** Je suis compatible avec la plupart des machines fonctionnant au XXIe siècle, consultez la liste suivante pour un ensemble d’outils et de bibliothèques essentiels. - -* **Éditeur de texte**, par exemple Visual Studio Code. - -* **Terminal**, pour exécuter des commandes. - -* **Navigateur pour tester votre application**. C'est aussi une bonne idée d'avoir un outil comme curl ou un autre client HTTP pour tester les points d'accès de l'API de votre application. - -* **Node.js**. Vous devez également installer Node.js et npm, qui sont des outils qui vous aident à exécuter du code JavaScript sur votre ordinateur. - -* **Clé API**. Vous aurez besoin d’une clé API pour accéder au modèle d'IA générative. Vous pouvez l'obtenir auprès du fournisseur de modèles, par exemple OpenAI ou Azure OpenAI. - -**Vous :** Merci, Scarabée du Temps, je suppose que tu vas chercher tout ça d'une manière ou d'une autre depuis ce Web dont tu m'as parlé ? - -**Scarabée du Temps :** Oui, je les ai déjà, dit-il en projetant un clavier et un écran sur le mur devant vous. - -## Écrire une application basique - -**Vous :** Parle-moi davantage de l'écriture de l'application en elle-même ; comment commencer ? - -**Scarabée du Temps :** Bien sûr, pour écrire une application, à son plus simple, il s'agit d'envoyer une requête à une API et d'afficher la réponse. Décomposons cela : - -* **Entrée** : dans une application de génération de texte basique, l'entrée correspond au texte initial que vous souhaitez que l'application développe ou complète. Cette entrée peut être fournie par l'utilisateur pendant l'exécution ou pré-définie (codée en dur) dans l'application elle-même. Pour l'instant, nous commencerons par utiliser un texte codé en dur comme entrée. - -* **Requête API** : Vous devez envoyer une requête à l'API du modèle d'IA générative avec le texte d'entrée. Cela se fait en utilisant la fonction fetch en JavaScript (Node.js). Cette requête doit également inclure votre clé API. Il est recommandé, pour des raisons de sécurité, de ne pas coder directement la clé API dans votre application, mais plutôt d'utiliser des variables d'environnement. De plus, envisagez d'utiliser une identité managée si vous utilisez un fournisseur comme Azure, car cela est considéré comme une méthode plus sécurisée pour accéder aux ressources. Avec une identité managée, vous pouvez attribuer des autorisations plus granulaires à votre application. Le meilleur, c'est que le fournisseur cloud s'occupe de l'authentification à votre place. - -* **Réponse** : L'API retournera une réponse avec le texte généré. Vous devez extraire ce texte de la réponse et l'afficher à l'utilisateur. - -**Vous :** Cela semble assez simple, peux-tu me montrer un scénario qui ferait sens dans la situation où nous nous trouvons ? - -**Scarabée du Temps :** Bien sûr, voyons comment nous pouvons construire une application simple qui génère du texte italien à partir d'une entrée en anglais. - -## Votre première application - Apprenez-moi l'italien - -**Scarabée du Temps :** Les modèles d'IA générative peuvent être utilisés pour de nombreuses choses, par exemple, la traduction linguistique. En fait, ces modèles acceptent une entrée dans une langue et peuvent générer un texte dans une autre langue. Commençons par une application simple qui prend en entrée de l'anglais et génère du texte en italien. - -```javascript - -import { OpenAI } from "openai"; - -// 1. Ask a question about translation -// ----------------------------------- - -const question = 'Hello, how are you?'; - -const augmentedPrompt = ` -## Instructions -Translate the following text to Italian: -## Question -${question} -`; - -// 2. Create client -// ----------------------------------- - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - - -// 3. Send the request -// ----------------------------------- -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: [{ role: 'user', content: augmentedPrompt }], -}); - -console.log(`Answer for "${question}":`); - -// 4. Print the answer -// ----------------------------------- - -console.log(completion.choices[0]?.message?.content); -``` - -Expliquons ce qui se passe ici : - -* Créer la question : « Bonjour, comment allez-vous ? ». C'est le texte que vous voulez traduire en italien. -* Créer l'invite augmentée, qui contient le texte d'entrée et des instructions supplémentaires sur quoi faire, c'est-à-dire traduire. Notez comment nous utilisons l'interpolation de chaînes pour inclure le texte d'entrée dans l'invite, ainsi que l'instruction de traduction vers l'italien. -* Créer le client avec les propriétés : - * `model`, quel modèle utiliser. - * `messages`, l'invite à envoyer au modèle. Notez également comment vous définissez le rôle sur "user" pour indiquer que le texte saisi provient de l'utilisateur. Si cela venait de l'IA, vous auriez défini le rôle sur "system". -* Extraire le texte généré de la réponse et l'afficher dans la console. - -**Vous :** Je pense que je comprends. Donc, si je change la valeur de la variable `question` par autre chose, l'application générera une traduction italienne différente ? - -**Scarabée du Temps :** Exactement, vous pouvez changer le texte d'entrée comme vous le souhaitez. Notez également comment les modèles GitHub sont utilisés comme URL de base pour l'API avec un token GitHub comme clé API. - -**Vous :** Pourquoi est-ce important ? - -**Scarabée du Temps :** Il est important d'utiliser une URL de base et une clé API spécifiques au modèle que vous utilisez. GitHub Models est une plateforme qui héberge une variété de modèles, chacun ayant des capacités et des fonctionnalités différentes. De plus, c’est gratuit à utiliser. - -**Vous :** Ah, tant mieux, je ne sais pas qui payer de toute façon, et je doute qu’ils acceptent ma monnaie ici. :) - -## Applications de chat - -**Scarabée du Temps :** Les modèles d'IA générative peuvent également être utilisés pour générer du texte basé sur une conversation. Vous pouvez simuler une conversation avec l'IA en fournissant une liste de messages comme contexte, comme si la conversation avait déjà eu lieu. - -**Vous :** Cela semble intéressant, mais en quoi est-ce utile ? - -**Scarabée du Temps :** C'est utile parce que cela permet à l'IA de fournir une réponse meilleure, basée sur plus de contexte qu'un seul message. Regardons une conversation ci-dessous pour illustrer cela : - -```text - -User: I want to book a trip to Italy. - -AI: Sure, when would you like to go? - -User: Next month would be great. - -AI: Got it, where in Italy would you like to visit? - -User: I'm thinking of Rome - -AI: Excellent choice! I can help you plan your itinerary. - -User: Tell me more about it. - -AI: Rome is known for its ancient ruins, art, and vibrant culture. You can visit the Colosseum, the Vatican, and enjoy delicious Italian cuisine. - -``` - -**Scarabée du Temps :** Imaginez si une phrase comme « En savoir plus à ce sujet » était sortie de son contexte, l'IA ne saurait pas à quoi « cela » fait référence. C'est là que le contexte est important, et ce contexte est quelque chose que nous pouvons fournir au modèle d'intelligence artificielle via l'invite. - -**Vous :** Je pense que je comprends, comment construire une conversation avec l'IA en utilisant ce langage JavaScript dont tu parles ? - -**Scarabée du Temps :** Voici comment nous pouvons construire une conversation avec l'IA : - -```javascript - -// Define the context - -const messages = [ - { - "role": "user", - "content": "I want to book a trip to Italy." - }, - { - "role": "assistant", - "content": "Sure, when would you like to go?" - }, - { - "role": "user", - "content": "Next month would be great." - }, - { - "role": "assistant", - "content": "Got it, where in Italy would you like to visit?" - }, - { - "role": "user", - "content": "I'm thinking of Rome. Tell me more about it." - } -]; - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - - -// 3. Send the request -// ----------------------------------- -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, -}); - -console.log(`Answer for "${question}":`); - -// 4. Print the answer -// ----------------------------------- - -console.log(completion.choices[0]?.message?.content); - -``` - -Désormais, l'IA fournira une liste de messages de chat en tant que contexte et générera une réponse basée sur ce contexte. C'est une manière plus interactive d'utiliser les modèles génératifs d'IA et cela peut être utilisé dans des chatbots, des applications de service client, et bien d'autres. - -**Vous :** D'accord, donc si j'ai bien compris la conversation, l'IA aura maintenant le contexte suivant : *Je vais à Rome le mois prochain*, donc en se basant là-dessus, elle devrait filtrer les informations non pertinentes et fournir une réponse plus pertinente ? - -**Scarabée du Temps :** Exactement, l'IA utilisera le contexte pour générer une réponse plus pertinente pour la conversation. - -## Améliorer la conversation de chat avec un message système - -**Vous :** Je vois, mais existe-t-il un moyen d'améliorer cela encore plus ? - -**Scarabée du Temps :** Oui, vous pouvez ajouter un message système à la conversation. Un message système crée une « personnalité » pour l'IA et peut être utilisé pour fournir un contexte supplémentaire. - -**Vous :** D'accord, dans le contexte de la conversation que nous avons eue, à quoi ressemblerait un message système ? - -**Scarabée du Temps :** Un message système pour cette conversation pourrait être quelque chose comme *"Je suis un assistant de voyage IA, ici pour vous aider à planifier votre voyage en Italie."* Ce message donne le ton de la conversation et aide l'IA à comprendre son rôle dans l'interaction. - -Pour créer un tel message, assurez-vous qu'il a le type "developer" comme suit : - -```javascript -const message = { - "role": "developer", - "content": "I'm an AI travel assistant, here to help you plan your trip to Italy." -}; -``` - -> [!NOTE] -> Cela s'appelait autrefois « system ». C’est un changement récent et « developer » est le nouveau terme. Pour certains modèles, cela s'appelle encore « system », donc si vous avez des problèmes, utilisez « system ». - -**Vous :** Ok, super, je veillerai à inclure un message système dans mes conversations de chat. Par curiosité, à quoi ressemble un message système pour toi ? - -**Scarabée du Temps :** Un message système pour moi pourrait être quelque chose comme *"Je suis le Scarabée du Temps, ici pour vous aider à naviguer dans le temps et l'espace. Je devrais être utile pour vous fournir des informations et des conseils sur l’époque où vous vous trouvez, ainsi que les outils dont vous avez besoin pour retourner à votre époque."* - -### Créer des réponses variées avec le réglage de la température - -**Vous :** Y a-t-il autre chose que je devrais savoir sur les conversations de chat ? - -**Scarabée du Temps :** Oui, vous pouvez ajuster la « température » des réponses de l'IA. La température est une variable avec une valeur généralement définie entre 0 et 1 qui détermine la créativité des réponses de l'IA. Une température de 0 produira des réponses plus prévisibles, tandis qu'une température de 1 produira des réponses plus créatives et variées. Vous pouvez ajuster la température en fonction du contexte de votre conversation et du type de réponses que vous souhaitez de l'IA. Notez qu'il est possible de définir une valeur supérieure à 1, mais cela entraîne plus d'aléatoire et moins de cohérence dans les réponses. - -**Vous :** Donc si je définis la température sur 0, l'IA fournira des réponses plus prévisibles, et si je la définis sur 1, l'IA fournira des réponses plus créatives ? Quelle est ta température ? - -**Scarabée du Temps :** Ma température est de 0,7 et oui, vous avez raison, l'IA fournira des réponses plus créatives avec une température plus élevée. Voyons comment définir la température dans votre application : - -```javascript - -// Define the context - -const messages = [ -{ - "role": "user", - "content": "I want you to generate recipes for me." -}]; - -// Create the web request - -let temperature = 0.5; // Set the temperature to 0.5 - -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, - temperature: temperature -}); -``` - -Comme vous pouvez le voir, vous pouvez ajuster la température en fonction du contexte de votre conversation et du type de réponses que vous attendez de l'IA. C'est une fonctionnalité puissante qui vous permet de personnaliser le niveau de créativité des réponses de l’IA. - -## Fenêtre de contexte - -**Vous :** Il y a encore autre chose, non ? - -**Scarabée du Temps :** Oui, un autre concept important dans les modèles d'IA générative est la fenêtre de contexte. La fenêtre de contexte correspond au nombre de messages précédents que l'IA utilise pour générer une réponse. Une fenêtre de contexte plus large permet à l'IA de considérer plus de contexte et de générer des réponses plus cohérentes. - -**Scarabée du Temps :** Différents modèles ont des limites différentes pour les tokens de sortie. Prenons l'exemple du modèle suivant `gpt-4o-2024-08-06`, il a les spécifications suivantes : - -* Nombre maximum de tokens de sortie : environ 16 000 tokens. -* Taille maximale de la fenêtre contextuelle : 128 000. - -Cela signifie que la majorité des tokens peuvent être consacrés aux tokens d'entrée, c'est-à-dire 128 k - 16 k = 112 k tokens. - -**Vous :** J'ai compris, fenêtre de contexte, tokens. Mais un token correspond à quoi en réalité ? - -**Scarabée du Temps :** Un token est un mot ou une partie de mot et diffère légèrement selon les langues. Il existe un outil recommandé par OpenAI pour mesurer, appelé [tokenizer](https://platform.openai.com/tokenizer). Essayons une phrase pour voir combien de tokens elle contient : - -```text -I want you to generate recipes for me. -``` - -![Démo de tokenizer](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/02-first-ai-app/assets/tokenizer.png) - -Exécuter `tokenizer` sur la phrase ci-dessus donne 9 tokens. - -**Vous :** Cela ne fait pas beaucoup, on dirait que je peux avoir beaucoup de tokens dans ma fenêtre de contexte alors ? - -**Scarabée du Temps :** Oui, vous pouvez expérimenter avec différentes tailles de fenêtres de contexte pour voir comment cela affecte les réponses de l'IA. En fait, si vous définissez une taille de fenêtre de contexte de 100, vous limiterez l'IA sur la quantité qu'elle considère en termes d'entrée et de sortie. Voici comment vous pouvez définir la fenêtre de contexte dans votre application : - -```javascript - -// Define the context -const messages = [ -{ - "role": "user", - "content": "I want you to generate recipes for me." -}]; - -// decide on the context window size - -let max_tokens = 100; // Set the context window size - -// Create the web request - -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, - max_tokens: max_tokens -}); - -``` - -> [!TIP] -> Expérimentez avec différentes tailles de fenêtres contextuelles pour voir comment cela affecte les réponses de l'IA. - -## Devoir - Construire un assistant en ingénierie - -Leonardo vous a soudainement demandé d'examiner de plus près le Time Beetle, il l’a regardé sous tous les angles, il l'a même secoué. - -**Leonardo :** J'ai besoin d'un assistant qui peut m'aider dans les calculs et la conception de la vis aérienne. Pouvez-vous me créer un assistant pour cela ? - -**Vous :** Bien sûr, je peux vous créer cela. Scarabée du Temps, on peut l'aider, non ? - -**Scarabée du Temps :** Oui, pas de problème, en fait la vis aérienne est l'une des inventions les plus fascinantes et visionnaires de Léonard. Conçue vers la fin des années 1480... - -**Vous :** Tout ce dont j'avais besoin, c'était un oui, gardez la leçon pour plus tard. - -**Scarabée du Temps :** Quelle impolitesse... - -**Vous :** Quoi ? - -**Scarabée du Temps :** Rien. - -![Vis aérienne, Léonard de Vinci](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/02-first-ai-app/assets/helicopter.jpg) - -> [!NOTE] -> La vis aérienne, également connue sous le nom de vis hélicoïdale, était destinée à décoller du sol en comprimant l'air. La conception de Léonard comportait un grand rotor en spirale en lin, rigidifié avec de l'amidon, et monté sur une plate-forme en bois. L'idée était qu'une équipe d'hommes ferait tourner des manivelles autour de la plate-forme, en la faisant tourner suffisamment vite pour obtenir une portance. -> -> Bien que Leonardo n’ait jamais réalisé une version grandeur nature de la vis aérienne, ses esquisses et notes fournissent des perspectives détaillées sur la manière dont il l'envisageait. -> -> Cependant, les scientifiques modernes s'accordent à dire que les matériaux disponibles à l'époque de Leonardo n'étaient ni assez solides ni assez légers pour rendre cela possible. -> -> Bien que son impraticabilité soit avérée, la vis aérienne reste un témoignage du génie de Léonard et de sa quête incessante d’innovation. Elle a jeté les bases des développements futurs dans l’aviation et continue d’inspirer les ingénieurs et inventeurs encore aujourd’hui. -> [Lire la suite](https://en.wikipedia.org/wiki/Leonardo%27s_aerial_screw). - -Votre mission est de construire un assistant en ingénierie capable d’aider Leonardo dans les calculs et la conception de la vis aérienne. - -* Il doit être capable de générer du texte basé sur l’entrée utilisateur. - -* Vous devez définir un message système pour introduire l’assistant. - -Consultez l'[application d'exemple](/app/README.md) pour commencer. - -> [!TIP] -> Réfléchissez à ce que devrait être le message système et à quel type d'entrée vous devriez fournir. - -## Solution - -[Solution](/lessons/02-first-ai-app/solution/solution.md) - -## Vérification des connaissances - -**Question :** Quel est l'objectif de la fenêtre de contexte dans les modèles d'IA générative ? Sélectionnez toutes les options applicables. - -A. La fenêtre de contexte permet à l’IA de considérer plus de contexte et de générer des réponses plus cohérentes. - -B. La fenêtre de contexte correspond au nombre de messages précédents que l’IA utilise pour générer une réponse. - -C. La fenêtre de contexte détermine à quel point les réponses de l’IA sont créatives. - -[Quiz solution](/lessons/02-first-ai-app/solution/solution-quiz.md) - -## Ressources d'auto-apprentissage - -* [Génération de texte](https://platform.openai.com/docs/guides/text-generation) -* [Bibliothèque JavaScript pour OpenAI](https://github.com/openai/openai-node/tree/master/examples) -* [Tokeniseur](https://platform.openai.com/tokenizer) -* [API de complétion](https://platform.openai.com/docs/api-reference/completions) -* [Complétions de chat](https://platform.openai.com/docs/guides/text-generation#text-generation-models) diff --git a/lessons/02-first-ai-app/translations/README.pt-br.md b/lessons/02-first-ai-app/translations/README.pt-br.md deleted file mode 100644 index 82dbc1a7..00000000 --- a/lessons/02-first-ai-app/translations/README.pt-br.md +++ /dev/null @@ -1,511 +0,0 @@ -# Lição 2: Escrevendo seu primeiro aplicativo de IA - -Neste capítulo você vai aprender: - -- Configurar seu ambiente de desenvolvimento. -- Escrever um aplicativo básico. -- Entender prompts de sistema. - -## Configuração - -Se você ainda não configurou seu ambiente de desenvolvimento, veja como fazer: [Configure seu ambiente](/docs/setup/README.md). - -## Recursos relacionados - -[![Assista a um vídeo curto sobre grandes modelos de linguagem](https://img.youtube.com/vi/GQ_2OjNZ9aA/0.jpg)](https://www.youtube.com/watch?v=GQ_2OjNZ9aA&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=2) - -_Este vídeo apresenta uma introdução aos modelos de IA chamados "grandes modelos de linguagem", LLMs, o que são e como você pode usá-los para integrar IA em seus aplicativos._ - -*🎥 Clique na imagem acima para assistir a um vídeo curto sobre grandes modelos de linguagem* - -💼 Slides: [Introdução aos grandes modelos de linguagem, LLMs](/videos/slides/01-llms.pptx) - -## Narrativa: Imagine-se em um barco num rio - - -> [!NOTE] -> _Nossa história até agora: Você é um criador de coisas, um artesão da Londres dos anos 1860 que viajou através do tempo usando um dispositivo misterioso chamado Besouro do Tempo. Você viajou pelos anais da história, testemunhando a criação do farol de Alexandria, uma maravilha da engenharia antiga que você ajudou a criar com uma pequena ajuda de Dinócrates e do Besouro do Tempo._ -> -> Veja a [Lição 1](/lessons/01-intro-to-genai/README.md) se quiser acompanhar a história desde o início e começar com IA Generativa. - -> [!NOTE] -> Embora recomendemos seguir a história (é divertido!), [clique aqui](#interaja-com-leonardo) se preferir ir direto para o conteúdo técnico. - -Junto com Dinócrates, você dá os toques finais no farol de Alexandria. A estrutura imponente brilha sob a luz do sol, sua pedra polida refletindo o Mar Mediterrâneo. - -Você olha para o Besouro do Tempo em sua mão, sua superfície metálica fria contra sua palma. Fechando o punho ao redor dele, você sussurra: "Leve-me para casa." O besouro começa a brilhar, emitindo uma luz suave e quente, e o mundo ao seu redor se dissolve em um turbilhão de cores. - -### Uma nova aventura - -Quando você abre os olhos, o mundo mudou. Ao conseguir se levantar, você percebe que está em um barco em um rio. Olhando ao redor, à distância, você vê edifícios, seus contornos borrados pela névoa da manhã. - -Observando o barco, você encontra um remo longo apoiado na lateral. Segurando-o, você começa a remar em direção aos edifícios distantes. À medida que se aproxima, os edifícios ficam mais nítidos, são antigos, sua arquitetura lembrando uma pintura renascentista. - -
- Barco no rio, homem em pé com um remo -
- -A questão agora é: onde e quando você está desta vez? - -Você consegue atracar o barco no cais e começa a caminhar ao longo das tábuas de madeira, o som de seus passos ecoando suavemente. - -Enquanto caminha, você nota um homem com uma longa barba e um chapéu, remexendo em uma caixa do que parecem ser peças mecânicas. Suas mãos se movem com destreza, separando engrenagens e molas com facilidade experiente. - -
- Leonardo Da Vinci em pé ao lado de uma caixa no porto -
- -### Ajude-me, Leonardo - -**Você:** "Com licença, senhor, onde estou?" Ele olha para você, confusão evidente em seus olhos. Percebendo que você está falando em inglês, você rapidamente usa o dispositivo em sua mão e pede para ele traduzir. - -**Besouro do Tempo:** "Claro, vou traduzir para o italiano do século XV. 'Dove sono?'" - -**Velho:** O velho responde: "Siete a Firenze, signore. E chi siete voi?" - -**Besouro do Tempo:** O Besouro do Tempo traduz: "Ele diz que você está em Florença e pergunta quem você é." - -**Você:** "Diga a ele que sou um criador de coisas e estou procurando um lugar para trabalhar." - -**Velho:** Un artigiano, eh? Avete mai sentito parlare di Leonardo da Vinci? - -**Besouro do Tempo:** O Besouro do Tempo traduz: "Ele pergunta se você já ouviu falar de Leonardo da Vinci." - -**Você:** "Claro," você diz. "Diga a ele que sim e que eu gostaria de conhecê-lo." - -**Velho:** O velho sorri: "Allora, seguitemi, vi porterò da lui." - -**Você:** Você pergunta: "O que ele disse?" - -**Besouro do Tempo:** O Besouro do Tempo responde: "Ele disse que vai levá-lo até Leonardo." - -### Na oficina - -
- Oficina de Leonardo -
- -O velho o leva até uma grande porta de madeira e você é recebido pela visão de uma oficina cheia de todo tipo de engenhocas mecânicas. - -Você pede ao Besouro do Tempo para perguntar sobre o paradeiro de Leonardo. - -**Você:** "Dove è Leonardo?" - -**Velho:** O velho se vira para você com um sorriso: "Sono io (sou eu), Leonardo da Vinci. Chi siete voi?", quem é você? - -Você sente um arrepio de reconhecimento. - -**Você:** "Eu pensava mesmo. Sou um criador como você, fora de lugar e tempo." - -**Leonardo:** Os olhos de Leonardo brilham com curiosidade. "Interessante, cosa vi porta qui?" - -O Besouro do Tempo traduz. - -**Besouro do Tempo:** "Ele pergunta o que o traz aqui." - -**Você:** "Bem, eu estava trabalhando em um projeto e acabei aqui." - -Você mostra a ele o Besouro do Tempo, e seus olhos se iluminam com fascínio. Ele o examina de perto enquanto você explica como ele funciona e como você acabou em Florença. - -Leonardo olha para você com entusiasmo. - -**Leonardo:** Você é um criador de coisas. Tenho um projeto que pode interessá-lo. Estive trabalhando em uma máquina que pode gerar texto com base em entrada verbal. Gostaria de me ajudar com isso? - -Leonardo da Vinci, pedindo sua ajuda em um projeto—você mal pode acreditar. Você acena ansiosamente e diz: - -**Você:** Seria uma honra ajudá-lo com seu projeto, -"Sarebbe un onore aiutarti con il tuo progetto." - -## Interaja com Leonardo - -Se você quiser interagir com Leonardo, execute o aplicativo [Characters](/app/README.md). - -> [!IMPORTANT] -> Isso é inteiramente fictício; as respostas são geradas por IA. -> [Aviso sobre IA Responsável](/README.md#responsible-ai-disclaimer) - -
- -
- -**Passos**: - -1. Inicie um [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. Navegue até _/app/README.md_ na raiz do repositório. -3. Localize o console e execute `npm install` seguido de `npm start`. -4. Quando aparecer, selecione o botão "Open in Browser". -5. Converse com Leonardo. - -Para uma explicação mais detalhada do aplicativo, consulte [Explicação detalhada do aplicativo](/lessons/01-intro-to-genai/README.md#interaja-com-dinocrates). - -> [!NOTE] - > Se você estiver executando o projeto localmente em sua máquina, por favor revise o guia de Início Rápido para configurar um [token de acesso pessoal do GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) e substitua a chave no código. - -## Configuração do ambiente de desenvolvimento - -Antes de poder ajudar Leonardo com seu projeto, você deve primeiro pensar nas "ferramentas do ofício" essenciais necessárias para começar a ajudá-lo efetivamente. - -**Você:** "Besouro do Tempo, do que preciso para começar com este projeto?" Sugira ferramentas e bibliotecas de que preciso que possam me ajudar a construir um aplicativo de IA que você possa executar. - -**Besouro do Tempo:** Sou compatível com a maioria das máquinas que rodam no século 21, veja a lista a seguir para um conjunto de ferramentas e bibliotecas essenciais. - -- **Editor de texto**, por exemplo Visual Studio Code. -- **Terminal**, para executar comandos. -- **Navegador para testar seu aplicativo**. Também é uma boa ideia ter uma ferramenta como curl ou algum outro cliente HTTP para testar os endpoints da API do seu aplicativo. - -- **Node.js**. Você também precisa instalar o Node.js e npm, que são ferramentas que ajudam você a executar código JavaScript em seu computador. - -- **Chave de API**. Você precisará de uma chave de API para acessar o modelo de IA generativa. Você pode obtê-la do provedor do modelo, por exemplo, OpenAI ou Azure OpenAI. - -**Você:** Obrigado, Besouro do Tempo, acho que você vai conseguir essas coisas de alguma forma daquela Web de que você falou? - -**Besouro do Tempo:** Sim, eu já tenho essas coisas, diz ele e projeta um teclado e uma tela na parede à sua frente. - -## Escrevendo um aplicativo básico - -**Você:** Me conte mais sobre a escrita do aplicativo em si, como começo? - -**Besouro do Tempo:** Claro, para escrever um aplicativo, em sua forma mais simples, trata-se de enviar uma solicitação a uma API e exibir a resposta. Vamos detalhar: - -- **Entrada**: Em um aplicativo básico de geração de texto, a entrada refere-se ao texto inicial que você deseja que o aplicativo expanda ou desenvolva. Essa entrada pode ser fornecida pelo usuário durante a execução ou pré-definida (codificada) dentro do próprio aplicativo. Por enquanto, começaremos usando texto codificado como entrada. - -- **Solicitação de API**: Você precisa enviar uma solicitação para a API do modelo de IA generativa com o texto de entrada. Isso é feito usando a função fetch em JavaScript (Node.js). Incluída nesta solicitação também deve estar sua chave de API. É recomendável, ao considerar a segurança, não codificar a chave de API em seu aplicativo, mas sim usar variáveis de ambiente. Além disso, considere pesquisar sobre identidade gerenciada se estiver usando um provedor como o Azure, pois é considerada uma maneira mais segura de acessar recursos. Com a identidade gerenciada, você pode atribuir permissões mais granulares ao seu aplicativo. A melhor parte é que o provedor de nuvem cuida da autenticação para você. - -- **Resposta**: A API retornará uma resposta com o texto gerado. Você precisa extrair esse texto da resposta e mostrá-lo ao usuário. - -**Você:** Isso parece bastante simples, pode me guiar por um cenário que faça sentido dada a situação em que estamos? - -**Besouro do Tempo:** Claro, vamos ver como podemos construir um aplicativo simples que gera texto em italiano com base em uma entrada em inglês. - -## Seu primeiro aplicativo - ensine-me italiano - -**Besouro do Tempo:** Modelos de IA generativa podem ser usados para muitas coisas, por exemplo, tradução de idiomas. Na verdade, ele aceita entrada em um idioma e pode gerar texto em outro idioma. Vamos começar com um aplicativo simples que recebe entrada em inglês e gera texto em italiano. - -```javascript - -import { OpenAI } from "openai"; - -// 1. Faça uma pergunta sobre tradução -// ----------------------------------- - -const question = 'Hello, how are you?'; - -const augmentedPrompt = ` -## Instructions -Translate the following text to Italian: -## Question -${question} -`; - -// 2. Crie o cliente -// ----------------------------------- - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - - -// 3. Envie a solicitação -// ----------------------------------- -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: [{ role: 'user', content: augmentedPrompt }], -}); - -console.log(`Resposta para "${question}":`); - -// 4. Imprima a resposta -// ----------------------------------- - -console.log(completion.choices[0]?.message?.content); -``` - -Vamos explicar o que está acontecendo aqui: - -- Criando a pergunta como 'Hello, how are you?'. Este é o texto que você deseja traduzir para o italiano. -- Criando o prompt aumentado, que contém o texto de entrada e algumas instruções adicionais sobre o que fazer, ou seja, traduzir. Observe como estamos usando interpolação de string para incluir o texto de entrada no prompt e como essa instrução é para traduzir o texto para o italiano. -- Criando o cliente com propriedades: - - `model`, qual modelo usar. - - `messages`, o prompt para enviar ao modelo. Observe também como você define o papel como "user" para indicar que o texto de entrada é do usuário. Se fosse da IA, você definiria o papel como "system". -- Extraindo o texto gerado da resposta e imprimindo-o no console. - -**Você:** Acho que entendi. Então, se eu mudar o valor da variável `question` para outra coisa, o aplicativo gerará uma tradução italiana diferente? - -**Besouro do Tempo:** Exatamente, você pode alterar o texto de entrada para qualquer coisa que desejar. Observe também como os modelos do GitHub estão sendo usados como a URL base para a API junto com um token do GitHub como a chave da API. - -**Você:** Por que isso é importante? - -**Besouro do Tempo:** É importante usar uma URL base e uma chave de API específicas para o modelo que você está usando. GitHub Models é uma plataforma que hospeda uma variedade de modelos, todos com diferentes capacidades e recursos, e também é gratuito para usar. - -**Você:** Ah, que bom, eu não sei quem pagar de qualquer forma e duvido que eles aceitem minha moeda aqui. :) - -## Aplicativos de chat - -**Besouro do Tempo:** Modelos de IA generativa também podem ser usados para gerar texto com base em uma conversa. Você pode simular uma conversa com a IA fornecendo uma lista de mensagens como contexto, como se a conversa já tivesse acontecido. - -**Você:** Isso parece interessante, mas por que é útil? - -**Besouro do Tempo:** É útil porque permite que a IA forneça uma resposta melhor com base em mais contexto do que apenas um único prompt. Vamos observar uma conversa abaixo para ilustrar isso: - -```text - -Usuário: Quero reservar uma viagem para a Itália. - -IA: Claro, quando você gostaria de ir? - -Usuário: Próximo mês seria ótimo. - -IA: Entendi, para onde na Itália você gostaria de visitar? - -Usuário: Estou pensando em Roma - -IA: Excelente escolha! Posso ajudá-lo a planejar seu itinerário. - -Usuário: Me conte mais sobre isso. - -IA: Roma é conhecida por suas ruínas antigas, arte e cultura vibrante. Você pode visitar o Coliseu, o Vaticano e desfrutar da deliciosa culinária italiana. - -``` - -**Besouro do Tempo:** Imagine se uma frase como "Me conte mais sobre isso" fosse tirada do contexto, a IA não saberia a que "isso" se refere. É aqui que o contexto é importante, e esse contexto é algo que podemos fornecer ao modelo de IA através do prompt. - -**Você:** Acho que entendi, como construo uma conversa com a IA usando essa linguagem JavaScript de que você fala? - -**Besouro do Tempo:** Abaixo está como podemos construir uma conversa com a IA: - -```javascript - -// Defina o contexto - -const messages = [ - { - "role": "user", - "content": "Quero reservar uma viagem para a Itália." - }, - { - "role": "assistant", - "content": "Claro, quando você gostaria de ir?" - }, - { - "role": "user", - "content": "Próximo mês seria ótimo." - }, - { - "role": "assistant", - "content": "Entendi, para onde na Itália você gostaria de visitar?" - }, - { - "role": "user", - "content": "Estou pensando em Roma. Me conte mais sobre isso." - } -]; - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - - -// 3. Envie a solicitação -// ----------------------------------- -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, -}); - -console.log(`Resposta para "${question}":`); - -// 4. Imprima a resposta -// ----------------------------------- - -console.log(completion.choices[0]?.message?.content); - -``` - -Agora a IA fornecerá uma lista de mensagens de chat como contexto, e a IA gerará uma resposta com base nesse contexto. Esta é uma maneira mais interativa de usar modelos de IA generativa e pode ser usada em chatbots, aplicativos de atendimento ao cliente e muito mais. - -**Você:** Ok, então se eu entendi a conversa corretamente, a IA agora terá o seguinte contexto: _Vou para Roma no próximo mês_, então com base nisso ela deve filtrar informações irrelevantes e fornecer uma resposta mais relevante? - -**Besouro do Tempo:** Exatamente, a IA usará o contexto para gerar uma resposta que é mais relevante para a conversa. - -## Melhorando a conversa de chat com uma mensagem de sistema - -**Você:** Entendo, mas há alguma maneira de melhorar isso ainda mais? - -**Besouro do Tempo:** Sim, você pode adicionar uma mensagem de sistema à conversa. Uma mensagem de sistema cria uma "personalidade" para a IA e pode ser usada para fornecer contexto adicional. - -**Você:** Ok, então no contexto da conversa que estamos tendo, como seria uma mensagem de sistema? - -**Besouro do Tempo:** Uma mensagem de sistema para esta conversa poderia ser algo como _"Sou um assistente de viagem de IA, aqui para ajudá-lo a planejar sua viagem para a Itália."_ Esta mensagem define o tom para a conversa e ajuda a IA a entender seu papel na interação. - -Para criar tal mensagem, certifique-se de que ela tenha o tipo "developer" assim: - -```javascript -const message = { - "role": "developer", - "content": "Sou um assistente de viagem de IA, aqui para ajudá-lo a planejar sua viagem para a Itália." -}; -``` - -> [!NOTE] -> Isso costumava ser chamado de "system". Esta é uma mudança recente e "developer" é o novo termo para isso. Para alguns modelos, isso ainda é chamado de "system", então se você tiver algum problema, use "system". - -**Você:** Ok, ótimo, vou me certificar de incluir uma mensagem de sistema em minhas conversas de chat. Por curiosidade, como seria uma mensagem de sistema para você? - -**Besouro do Tempo:** Uma mensagem de sistema para mim poderia ser algo como _"Eu sou o Besouro do Tempo, aqui para ajudá-lo a navegar pelo tempo e espaço. Devo ser útil em fornecer informações e orientações sobre a era do tempo em que você está, juntamente com as ferramentas necessárias para voltar ao seu próprio tempo."_ - -### Criando respostas variadas com a configuração de temperatura - -**Você:** Mais alguma coisa que eu deva saber sobre conversas de chat? - -**Besouro do Tempo:** Sim, você pode ajustar a "temperatura" das respostas da IA. A temperatura é uma variável com um valor normalmente definido entre 0 e 1 que determina quão criativas são as respostas da IA. Uma temperatura de 0 resultará em respostas mais previsíveis, enquanto uma temperatura de 1 resultará em respostas mais criativas e variadas. Você pode ajustar a temperatura com base no contexto da sua conversa e no tipo de respostas que deseja da IA. Observe que é possível definir um valor maior que 1, mas isso leva a mais aleatoriedade e menos coerência nas respostas. - -**Você:** Então, se eu definir a temperatura para 0, a IA fornecerá respostas mais previsíveis, e se eu definir para 1, a IA fornecerá respostas mais criativas? Que temperatura você tem? - -**Besouro do Tempo:** Eu tenho uma temperatura de 0,7 e sim, você está correto, a IA fornecerá respostas mais criativas com uma temperatura mais alta. Vamos ver como você pode definir a temperatura em seu aplicativo: - -```javascript - -// Defina o contexto - -const messages = [ -{ - "role": "user", - "content": "Quero que você gere receitas para mim." -}]; - -// Crie a solicitação web - -let temperature = 0.5; // Defina a temperatura como 0.5 - -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, - temperature: temperature -}); -``` - -Como você pode ver, você pode ajustar a temperatura com base no contexto da sua conversa e no tipo de respostas que deseja da IA. Este é um recurso poderoso que permite personalizar o nível de criatividade nas respostas da IA. - -## Janela de contexto - -**Você:** Tem mais, certo? - -**Besouro do Tempo:** Sim, outro conceito importante em modelos de IA generativa é a janela de contexto. A janela de contexto é o número de mensagens anteriores que a IA usa para gerar uma resposta. Uma janela de contexto maior permite que a IA considere mais contexto e gere respostas mais coerentes. - -**Besouro do Tempo:** Diferentes modelos têm diferentes limites para tokens de saída. Considere o seguinte modelo como exemplo `gpt-4o-2024-08-06`, ele tem as seguintes especificações: - -- Máximo de tokens de saída: aproximadamente 16 mil tokens. -- Tamanho máximo da janela de contexto: 128 mil. - -Isso significa que a maioria dos tokens pode ser gasta nos tokens de entrada, ou seja, 128 mil - 16 mil = 112 mil tokens. - -**Você:** Entendi, janela de contexto, tokens, mas quanto vale um token? - -**Besouro do Tempo:** Um token é uma palavra ou parte de uma palavra e difere ligeiramente por idioma. Há uma ferramenta que você pode usar para medir, recomendada pela OpenAI, chamada [tokenizer](https://platform.openai.com/tokenizer). Vamos tentar uma frase e ver quantos tokens ela possui: - -```text -Quero que você gere receitas para mim. -``` - -![Demonstração do tokenizer](../assets/tokenizer.png) - -Executar o `tokenizer` na frase acima nos dá 9 tokens. - -**Você:** Não foi muito, parece que posso ter muitos tokens na minha janela de contexto, então? - -**Besouro do Tempo:** Sim, você pode experimentar com diferentes tamanhos de janela de contexto para ver como isso afeta as respostas da IA. Na verdade, se você definir um tamanho de janela de contexto de 100, limitará a IA e quanto ela considera para entrada e saída. Veja como você pode definir a janela de contexto em seu aplicativo: - -```javascript - -// Defina o contexto -const messages = [ -{ - "role": "user", - "content": "Quero que você gere receitas para mim." -}]; - -// decida sobre o tamanho da janela de contexto - -let max_tokens = 100; // Defina o tamanho da janela de contexto - -// Crie a solicitação web - -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, - max_tokens: max_tokens -}); - -``` - -> [!TIP] -> Experimente com diferentes tamanhos de janela de contexto para ver como isso afeta as respostas da IA. - -## Tarefa - Construindo um assistente de engenharia - -Leonardo de repente pediu para inspecionar o Besouro do Tempo mais de perto, olhou-o de todos os lados, até mesmo o sacudiu. - -**Leonardo:** Preciso de um assistente que possa me ajudar com os cálculos e design do parafuso aéreo. Você pode construir um assistente que possa fazer isso? - -**Você:** Claro, posso construir isso para você. Besouro do Tempo, podemos ajudar com isso, certo? - -**Besouro do Tempo:** Sim, sem problema, na verdade o parafuso aéreo é uma das invenções mais fascinantes e visionárias de Leonardo. Projetado no final da década de 1480... - -**Você:** Tudo o que eu precisava era de um sim, vamos deixar a palestra para depois. - -**Besouro do Tempo:** Grosseiro.. - -**Você:** O quê? - -**Besouro do Tempo:** Nada - -
- Parafuso aéreo, Leonardo Da Vinci -
- -> [!NOTE] -> O parafuso aéreo, também conhecido como parafuso helicoidal de ar, foi projetado para decolar do solo comprimindo o ar. O design de Leonardo apresentava um grande rotor em forma de espiral feito de linho, enrijecido com amido, e montado em uma plataforma de madeira. A ideia era que uma equipe de homens correria ao redor da plataforma, girando manivelas para fazer o parafuso girar rapidamente o suficiente para alcançar a sustentação -> -> Embora Leonardo nunca tenha construído uma versão em escala real do parafuso aéreo, seus esboços e notas fornecem insights detalhados sobre como ele imaginava seu funcionamento. Ele acreditava que se o parafuso fosse girado rapidamente o suficiente, empurraria contra o ar e levantaria toda a estrutura do chão. -> -> No entanto, cientistas modernos concordam que os materiais disponíveis na época de Leonardo não eram fortes ou leves o suficiente para tornar isso possível -> -> Apesar de sua impraticabilidade, o parafuso aéreo continua sendo um testemunho da genialidade de Leonardo e sua busca implacável pela inovação. Estabeleceu as bases para desenvolvimentos futuros na aviação e continua inspirando engenheiros e inventores até hoje. -> [Leia mais](https://en.wikipedia.org/wiki/Leonardo%27s_aerial_screw) - -Sua tarefa é construir um assistente de engenharia que possa ajudar Leonardo com os cálculos e design do parafuso aéreo. - -- Ele deve ser capaz de gerar texto com base na entrada do usuário. - -- Você deve definir uma mensagem de sistema para apresentar o assistente. - -Confira o [Aplicativo de exemplo](/app/README.md) para começar. - -> [!TIP] -> Considere qual deve ser a mensagem do sistema e qual entrada você deve fornecer. - -## Solução - -[Solução](../solution/solution.md) - -## Verificação de conhecimento - -**Pergunta:** Qual é o propósito da janela de contexto em modelos de IA generativa? Selecione todas as opções aplicáveis. - -A. A janela de contexto permite que a IA considere mais contexto e gere respostas mais coerentes. - -B. A janela de contexto é o número de mensagens anteriores que a IA usa para gerar uma resposta. - -C. A janela de contexto determina quão criativas são as respostas da IA. - -[Solução do quiz](../solution/solution-quiz.md) - -## Recursos para auto-estudo - -- [Geração de texto](https://platform.openai.com/docs/guides/text-generation) -- [Biblioteca JavaScript para OpenAI](https://github.com/openai/openai-node/tree/master/examples) -- [Tokenizer](https://platform.openai.com/tokenizer) -- [API de Completions](https://platform.openai.com/docs/api-reference/completions) -- [Chat completions](https://platform.openai.com/docs/guides/text-generation#text-generation-models) diff --git a/lessons/02-first-ai-app/translations/README.zh-cn.md b/lessons/02-first-ai-app/translations/README.zh-cn.md deleted file mode 100644 index a5702017..00000000 --- a/lessons/02-first-ai-app/translations/README.zh-cn.md +++ /dev/null @@ -1,507 +0,0 @@ -# 第二课:编写你的第一个 AI 应用程序 - -在本章中,你将学习: - -- 设置你的开发环境。 -- 编写一个基础应用程序。 -- 了解系统提示(system prompts)。 - -## 环境设置 - -如果你还没有设置开发环境,以下是设置方法:[设置你的环境](/docs/setup/README.md)。 - -## 相关资源 - -[![观看关于大语言模型的短视频](https://img.youtube.com/vi/GQ_2OjNZ9aA/0.jpg)](https://www.youtube.com/watch?v=GQ_2OjNZ9aA&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=2) - -_这个视频向你介绍了被称为"大语言模型"(LLM)的 AI 模型,它们是什么以及如何使用它们将 AI 集成到你的应用程序中。_ - -*🎥 点击上方图片观看关于大语言模型的短视频* - -💼 幻灯片:[大语言模型(LLM)介绍](/videos/slides/01-llms.pptx) - -## 故事叙述:想象自己在河上的一条船里 - - -> [!NOTE] -> _我们的故事到目前为止:你是一位制造者,一位来自 1860 年代伦敦的工匠,使用一个名为时间甲虫(Time Beetle)的神秘装置穿越时空。你穿越历史长河,见证了亚历山大灯塔的建造,这是一个古代工程奇迹,你在迪诺克拉底和时间甲虫的帮助下参与了建造。_ -> -> 如果你想从头开始了解这个故事并开始学习生成式 AI,请查看[第一课](/lessons/01-intro-to-genai/README.md)。 - -> [!NOTE] -> 虽然我们建议你跟随故事(这很有趣!),但如果你更喜欢直接跳到技术内容,[点击这里](#与莱昂纳多互动)。 - -你与迪诺克拉底一起为亚历山大灯塔做最后的修饰。这座高耸的建筑在阳光下闪闪发光,其抛光的石材反射着地中海的光芒。 - -你低头看着手中的时间甲虫,它冰凉的金属表面贴在你的手掌上。你紧握着它,低语道:"带我回家。"甲虫开始发光,发出柔和温暖的光芒,周围的世界融化成一个色彩旋涡。 - -### 新的冒险 - -当你睁开眼睛时,世界已经发生了变化。当你设法站起来时,你发现自己身处河上的一条船中。环顾四周,远处你看到了建筑物,它们的轮廓被晨雾模糊。 - -环顾船内,你发现一根长桨靠在船边。抓起桨,你开始向远处的建筑物划去。当你靠近时,建筑物变得更加清晰,它们很古老,建筑风格让人联想到文艺复兴时期的画作。 - -
- 河上的船,一个人拿着桨站立 -
- -现在的问题是,这次你身在何时何地? - -你设法将船固定在码头,开始沿着木板行走,脚步声轻柔回荡。 - -在行走过程中,你注意到一个留着长胡子戴着帽子的人,正在翻找一个看起来像机械零件的箱子。他的手灵活地移动,熟练地分拣齿轮和弹簧。 - -
- 莱昂纳多·达·芬奇站在港口的箱子旁边 -
- -### 帮帮我,莱昂纳多 - -**你**:"对不起,先生,我现在在哪里?"他抬头看着你,眼中充满困惑。意识到你一直在说英语,你迅速使用手中的设备,让它翻译。 - -**时间甲虫**:"当然,我会翻译成 15 世纪的意大利语。'Dove sono?'" - -**老人**:老人回答说:"Siete a Firenze, signore. E chi siete voi?" - -**时间甲虫**:时间甲虫翻译道:"他说你在佛罗伦萨,并问你是谁。" - -**你**:"告诉他我是一个制造者,我正在寻找一个工作的地方。" - -**老人**:Un artigiano, eh? Avete mai sentito parlare di Leonardo da Vinci? - -**时间甲虫**:时间甲虫翻译道:"他问你是否听说过莱昂纳多·达·芬奇。" - -**你**:"当然,"你说。"告诉他我听说过,并且我想见他。" - -**老人**:老人微笑着说:"Allora, seguitemi, vi porterò da lui." - -**你**:你问:"他说什么?" - -**时间甲虫**:时间甲虫回答:"他说他会带你去见莱昂纳多。" - -### 在工作坊 - -
- 莱昂纳多的工作坊 -
- -老人领你来到一扇大木门前,迎接你的是一个工作坊,里面充满了各种机械装置。 - -你请时间甲虫询问莱昂纳多的下落。 - -**你**:"Dove è Leonardo?" - -**老人**:老人转向你,微笑着说:"Sono io(就是我),Leonardo da Vinci. Chi siete voi(你是谁)?" - -你感到一阵认出的兴奋。 - -**你**:"我就知道。我是一个同行制造者,不知身在何时何地。" - -**莱昂纳多**:莱昂纳多的眼睛闪烁着好奇。"Interessante, cosa vi porta qui?" - -时间甲虫翻译。 - -**时间甲虫**:"他问你是什么把你带到这里的。" - -**你**:"嗯,我正在进行一个项目,然后我就到了这里。" - -你向他展示了时间甲虫,他的眼睛因着迷而亮起。当你解释它是如何工作的以及你是如何来到佛罗伦萨的时候,他仔细地检查着它。 - -莱昂纳多看着你,满怀兴奋。 - -**莱昂纳多**:你是一个制造者。我有一个可能会让你感兴趣的项目。我一直在研究一种能根据口头输入生成文本的机器。你愿意帮我完成它吗? - -莱昂纳多·达·芬奇,邀请你帮助他完成一个项目——你简直不敢相信。你热切地点头并说: - -**你**:能帮助你完成项目我倍感荣幸,"Sarebbe un onore aiutarti con il tuo progetto." - -## 与莱昂纳多互动 - -如果你想与莱昂纳多互动,请运行 [Characters](/app/README.md) 应用程序。 - -> [!IMPORTANT] -> 这完全是虚构的;这些回应是由 AI 生成的。 -> [负责任的 AI 免责声明](/README.md#responsible-ai-disclaimer) - -
- -
- -**步骤**: - -1. 启动一个 [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. 导航到仓库根目录中的 _/app/README.md_。 -3. 在控制台中运行 `npm install` 然后运行 `npm start`。 -4. 当它出现时,选择"在浏览器中打开"按钮。 -5. 与莱昂纳多聊天。 - -有关应用程序的更详细解释,请参见[详细应用程序说明](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates)。 - -> [!NOTE] - > 如果你在本地机器上运行项目,请查看快速入门指南以设置 [GitHub personal access](../../../docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) token 并在代码中替换密钥。 - -## 开发环境设置 - -在你能够协助莱昂纳多的项目之前,你应该首先考虑开始有效帮助他所需的基本"行业工具"。 - -**你**:"时间甲虫,我需要什么才能开始这个项目?" 建议我需要的工具和库,这些工具和库可以帮助我构建一个你能运行的 AI 应用程序。 - -**时间甲虫**:我与大多数运行在 21 世纪的机器兼容,以下是一组基本工具和库的列表。 - -- **文本编辑器**,例如 Visual Studio Code。 -- **终端**,用于运行命令。 -- **用于测试应用程序的浏览器**。拥有像 curl 或其他 HTTP 客户端这样的工具来测试应用程序的 API 端点也是一个好主意。 - -- **Node.js**。你还需要安装 Node.js 和 npm,这些工具可以帮助你在计算机上运行 JavaScript 代码。 - -- **API 密钥**。你需要一个 API 密钥来访问生成式 AI 模型。你可以从模型提供商获取,例如 OpenAI 或 Azure OpenAI。 - -**你**:谢谢你,时间甲虫,我猜你会从你说的那个网络中获取这些东西? - -**时间甲虫**:是的,它说我已经有了这些,并在你面前的墙上投射出一个键盘和屏幕。 - -## 编写一个基础应用程序 - -**你**:告诉我更多关于实际编写应用程序的信息,我该如何开始? - -**时间甲虫**:当然,编写一个应用程序,最简单的方式就是发送请求到 API 并显示响应。让我们分解一下: - -- **输入**:在基本的文本生成应用程序中,输入是指你希望应用程序扩展或构建的初始文本。这个输入可以由用户在运行时提供,也可以在应用程序内预设(硬编码)。现在,我们将开始使用硬编码文本作为输入。 - -- **API 请求**:你需要使用输入文本向生成式 AI 模型 API 发送请求。这是使用 JavaScript(Node.js)中的 fetch 函数完成的。这个请求中还应包括你的 API 密钥。考虑到安全性,建议不要在应用程序中硬编码 API 密钥,而是使用环境变量。此外,如果你使用像 Azure 这样的提供商,考虑使用托管身份,这被认为是访问资源的更安全方式。使用托管身份,你可以为你的应用程序分配更精细的权限。最好的部分是云提供商为你处理身份验证。 - -- **响应**:API 将返回一个带有生成文本的响应。你需要从响应中提取这个文本并显示给用户。 - -**你**:这听起来足够简单,你能带我过一遍在我们当前情境下有意义的场景吗? - -**时间甲虫**:当然,让我们看看如何构建一个简单的应用程序,根据英语输入生成意大利语文本。 - -## 你的第一个应用 - 教我意大利语 - -**时间甲虫**:生成式 AI 模型可以用于很多事情,例如语言翻译。事实上,它接受一种语言的输入并可以生成另一种语言的文本。让我们从一个简单的应用程序开始,它接收英语输入并生成意大利语文本。 - -```javascript - -import { OpenAI } from "openai"; - -// 1. 提出关于翻译的问题 -// ----------------------------------- - -const question = 'Hello, how are you?'; - -const augmentedPrompt = ` -## Instructions -Translate the following text to Italian: -## Question -${question} -`; - -// 2. 创建客户端 -// ----------------------------------- - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - - -// 3. 发送请求 -// ----------------------------------- -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: [{ role: 'user', content: augmentedPrompt }], -}); - -console.log(`Answer for "${question}":`); - -// 4. 打印答案 -// ----------------------------------- - -console.log(completion.choices[0]?.message?.content); -``` - -让我们解释一下这里发生了什么: - -- 创建问题为"Hello, how are you?"。这是你想要翻译成意大利语的文本。 -- 创建增强提示,其中包含输入文本和一些额外的指令,即翻译。注意我们如何使用字符串插值将输入文本包含在提示中,以及该指令是将文本翻译成意大利语。 -- 创建具有以下属性的客户端: - - `model`,使用哪个模型。 - - `messages`,发送给模型的提示。还要注意如何将角色设置为"user",以表明输入文本来自用户。如果是来自 AI 的,你会将角色设置为"system"。 -- 从响应中提取生成的文本并将其打印到控制台。 - -**你**:我想我明白了。所以如果我改变 `question` 变量的值为别的内容,应用程序就会生成不同的意大利语翻译? - -**时间甲虫**:没错,你可以将输入文本更改为任何你想要的内容。还要注意 GitHub 模型如何作为 API 的基础 URL 与环境变量中的 GitHub 令牌一起作为 API 密钥使用。 - -**你**:为什么这很重要? - -**时间甲虫**:使用特定于你所使用的模型的基础 URL 和 API 密钥很重要。GitHub Models 是一个托管各种模型的平台,所有这些模型都具有不同的功能和特性,而且使用是免费的。 - -**你**:哦,太好了,反正我不知道该付钱给谁,而且我怀疑他们是否接受我的货币。:) - -## 聊天应用程序 - -**时间甲虫**:生成式 AI 模型还可以用来基于对话生成文本。你可以通过提供消息列表作为上下文来模拟与 AI 的对话,就像对话已经发生过一样。 - -**你**:这听起来很有趣,但为什么这很有用? - -**时间甲虫**:这很有用,因为它允许 AI 基于比单个提示更多的上下文提供更好的响应。让我们看一下下面的对话来说明这一点: - -```text - -User: I want to book a trip to Italy. - -AI: Sure, when would you like to go? - -User: Next month would be great. - -AI: Got it, where in Italy would you like to visit? - -User: I'm thinking of Rome - -AI: Excellent choice! I can help you plan your itinerary. - -User: Tell me more about it. - -AI: Rome is known for its ancient ruins, art, and vibrant culture. You can visit the Colosseum, the Vatican, and enjoy delicious Italian cuisine. - -``` - -**时间甲虫**:想象一下,如果像"告诉我更多关于它的信息"这样的句子被脱离上下文理解,AI 就不会知道"它"指的是什么。这就是上下文的重要性所在,而这个上下文是我们可以通过提示提供给 AI 模型的。 - -**你**:我想我明白了,我如何使用这种 JavaScript 语言构建与 AI 的对话? - -**时间甲虫**:以下是我们如何构建与 AI 的对话: - -```javascript - -// 定义上下文 - -const messages = [ - { - "role": "user", - "content": "I want to book a trip to Italy." - }, - { - "role": "assistant", - "content": "Sure, when would you like to go?" - }, - { - "role": "user", - "content": "Next month would be great." - }, - { - "role": "assistant", - "content": "Got it, where in Italy would you like to visit?" - }, - { - "role": "user", - "content": "I'm thinking of Rome. Tell me more about it." - } -]; - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - - -// 3. 发送请求 -// ----------------------------------- -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, -}); - -console.log(`Answer for "${question}":`); - -// 4. 打印答案 -// ----------------------------------- - -console.log(completion.choices[0]?.message?.content); - -``` - -现在 AI 将提供一个聊天消息列表作为上下文,并且 AI 将基于该上下文生成响应。这是一种更具交互性的使用生成式 AI 模型的方式,可以用于聊天机器人、客户服务应用程序等。 - -**你**:好的,如果我正确理解这个对话,AI 现在将具有以下上下文:_我下个月要去罗马_,所以基于这一点,它应该过滤掉无关信息并提供更相关的响应? - -**时间甲虫**:完全正确,AI 将使用上下文来生成与对话更相关的响应。 - -## 通过系统消息改进聊天对话 - -**你**:我明白了,但有没有办法进一步改进这个? - -**时间甲虫**:是的,你可以向对话添加系统消息。系统消息为 AI 创建一个"个性",可以用来提供附加上下文。 - -**你**:好的,所以在我们一直在进行的对话的上下文中,系统消息会是什么样子? - -**时间甲虫**:这个对话的系统消息可能是_"我是一个 AI 旅行助手,在这里帮助你规划你的意大利之行。"_这条消息为对话设定了基调,并帮助 AI 理解它在互动中的角色。 - -要创建这样的消息,请确保它的类型为"developer",如下所示: - -```javascript -const message = { - "role": "developer", - "content": "我是一个 AI 旅行助手,在这里帮助你规划你的意大利之行。" -}; -``` - -> 注意:这以前被称为"system"。这是最近的变化,"developer"是它的新术语。对于某些模型,这仍然被称为"system",所以如果你遇到任何问题,请使用"system"。 - -**你**:好的,很好,我会确保在我的聊天对话中包含一个系统消息。出于好奇,你的系统消息是什么样子的? - -**时间甲虫**:我的系统消息可能是_"我是时间甲虫,在这里帮助你穿越时间和空间。我应该有助于为你提供关于你所处时代的信息和指导,以及你需要返回自己时代的工具。"_ - -### 通过温度设置创建多样化的响应 - -**你**:关于聊天对话还有什么我应该知道的吗? - -**时间甲虫**:是的,你可以调整 AI 响应的"温度"。温度是一个通常设置在 0 到 1 之间的变量,它决定了 AI 响应的创造性程度。温度为 0 会导致更可预测的响应,而温度为 1 会导致更具创造性和多样性的响应。你可以根据对话的上下文和你希望从 AI 得到的响应类型来调整温度。注意,可以设置高于 1 的值,但这会导致响应更加随机,连贯性更低。 - -**你**:所以如果我将温度设置为 0,AI 将提供更可预测的响应,如果我将其设置为 1,AI 将提供更具创造性的响应?你的温度是多少? - -**时间甲虫**:我的温度是 0.7,是的,你是对的,AI 在温度较高时会提供更具创造性的响应。让我们看看如何在你的应用程序中设置温度: - -```javascript - -// 定义上下文 - -const messages = [ -{ - "role": "user", - "content": "I want you to generate recipes for me." -}]; - -// 创建网络请求 - -let temperature = 0.5; // 将温度设置为 0.5 - -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, - temperature: temperature -}); -``` - -如你所见,你可以根据对话的上下文和你希望从 AI 得到的响应类型来调整温度。这是一个强大的功能,允许你自定义 AI 响应中的创造力水平。 - -## 上下文窗口 - -**你**:还有更多对吧? - -**时间甲虫**:是的,生成式 AI 模型中的另一个重要概念是上下文窗口。上下文窗口是 AI 用来生成响应的先前消息数量。更大的上下文窗口允许 AI 考虑更多的上下文并生成更连贯的响应。 - -**时间甲虫**:不同的模型对输出标记有不同的限制。以下面的模型为例:`gpt-4o-2024-08-06`,它具有以下规格: - -- 最大输出标记:大约 16k 标记。 -- 最大上下文窗口大小:128k。 - -这意味着大部分标记可以用于输入标记,即 128k - 16k = 112k 标记。 - -**你**:明白了,上下文窗口,标记,一个标记有多少? - -**时间甲虫**:一个标记是一个单词或单词的一部分,根据语言略有不同。有一个工具可以用来测量,由 OpenAI 推荐,叫做 [tokenizer](https://platform.openai.com/tokenizer)。让我们尝试一个句子,看看它有多少个标记: - -```text -I want you to generate recipes for me. -``` - -![Tokenizer 的演示](../assets/tokenizer.png) - -对上面的句子运行 `tokenizer` 得到 9 个标记。 - -**你**:这不多,听起来我可以在上下文窗口中有很多标记? - -**时间甲虫**:是的,你可以尝试不同的上下文窗口大小,看看它如何影响 AI 的响应。事实上,如果你设置上下文窗口大小为 100,你将限制 AI 及其考虑输入和输出的能力。以下是如何在你的应用程序中设置上下文窗口: - -```javascript - -// 定义上下文 -const messages = [ -{ - "role": "user", - "content": "I want you to generate recipes for me." -}]; - -// 决定上下文窗口大小 - -let max_tokens = 100; // 设置上下文窗口大小 - -// 创建网络请求 - -const completion = await openai.chat.completions.create({ - model: 'gpt-4', - messages: messages, - max_tokens: max_tokens -}); - -``` - -> 提示:尝试不同的上下文窗口大小,看看它如何影响 AI 的响应。 - -## 作业 - 构建一个工程助手 - -莱昂纳多突然要求更仔细地检查时间甲虫,他从各个角度查看它,甚至摇晃了它。 - -**莱昂纳多**:我需要一个助手来帮助我计算和设计空气螺旋。你能为我构建一个能做到这一点的助手吗? - -**你**:当然,我可以为你构建这个。时间甲虫,我们可以帮忙,对吧? - -**时间甲虫**:是的,没问题,实际上,空气螺旋是莱昂纳多最令人着迷和有远见的发明之一。设计于 15 世纪 80 年代末... - -**你**:我只需要一个肯定的答案,让我们稍后再讲解。 - -**时间甲虫**:真没礼貌.. - -**你**:什么? - -**时间甲虫**:没什么 - -
- 空气螺旋,莱昂纳多·达·芬奇 -
- -> [!NOTE] -> 空气螺旋,也被称为螺旋式空气螺旋,旨在通过压缩空气升离地面。莱昂纳多的设计特点是一个大型的、螺旋形的转子,由亚麻制成,用淀粉加固,安装在木制平台上。设想是一组人围绕平台奔跑,转动曲柄,使螺旋快速旋转,足以实现升力。 -> -> 尽管莱昂纳多从未建造过全尺寸的空气螺旋,但他的草图和笔记提供了关于他如何设想其工作方式的详细见解。他相信如果螺旋转得足够快,它会推动空气,使整个结构离开地面。 -> -> 然而,现代科学家一致认为,莱昂纳多时代可用的材料不够坚固或轻便,无法使这成为可能。 -> -> 尽管它不切实际,空气螺旋仍然是莱昂纳多天才和他对创新的不懈追求的证明。它为航空领域的未来发展奠定了基础,并继续激励着今天的工程师和发明家。 -> [阅读更多](https://en.wikipedia.org/wiki/Leonardo%27s_aerial_screw) - -你的任务是构建一个工程助手,帮助莱昂纳多进行空气螺旋的计算和设计。 - -- 它应该能够根据用户输入生成文本。 - -- 你应该设置一个系统消息来介绍这个助手。 - -查看[示例应用](../sample-app/)开始。 - -> 提示:考虑系统消息应该是什么以及你应该提供什么输入。 - -## 解决方案 - -[解决方案](../solution/solution.md) - -## 知识检验 - -**问题**: 上下文窗口在生成式 AI 模型中的目的是什么?选择所有适用项。 - -A. 上下文窗口允许 AI 考虑更多上下文并生成更连贯的响应。 - -B. 上下文窗口是 AI 用来生成响应的先前消息数量。 - -C. 上下文窗口决定了 AI 响应的创造性程度。 - -[测验解决方案](../solution/solution-quiz.md) - -## 自学资源 - -- [文本生成](https://platform.openai.com/docs/guides/text-generation) -- [OpenAI 的 JavaScript 库](https://github.com/openai/openai-node/tree/master/examples) -- [Tokenizer](https://platform.openai.com/tokenizer) -- [Completion API](https://platform.openai.com/docs/api-reference/completions) -- [聊天完成](https://platform.openai.com/docs/guides/text-generation#text-generation-models) diff --git a/lessons/03-prompt-engineering/README.md b/lessons/03-prompt-engineering/README.md index 14ff06e7..30fc280a 100644 --- a/lessons/03-prompt-engineering/README.md +++ b/lessons/03-prompt-engineering/README.md @@ -10,16 +10,6 @@ In this chapter you will learn: If you haven't already, set up your development environment. Here's how you can do it: [Setup your environment](/docs/setup/README.md). -## Related Resources - -[![Watch a short video about prompt engineering](https://img.youtube.com/vi/gQ6TlyxBmWs/0.jpg)](https://www.youtube.com/watch?v=gQ6TlyxBmWs&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=3) - -_This video offers an introduction to improving your "prompting" skills, teaching you how to give clearer and more effective instructions to the AI to achieve better results._ - -*🎥 Click on the image above to watch a short video about prompt engineering* - -💼 Slides: [Prompt engineering](/videos/slides/02-prompt-engineering.pptx) - ## Narrative: Ticket to ride > [!NOTE] @@ -116,14 +106,14 @@ If you want to interact with Sforza, run the [Characters](/app/README.md) app. 1. Start a [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) 2. Navigate to _/app_ in the repo root. -3. Locate the console and run `npm install` followed by `npm start`. +3. Locate the console and run `npm ci` followed by `npm start`. 4. Once it appears, select the "Open in Browser" button. 5. Chat with Sforza. For a more detailed explanation of the app, see [Detailed app explanation](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). > [!NOTE] - > If you're running the project locally on your machine, please review the QuickStart guide to get a [GitHub personal access](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) token setup and replace the key in the code. +> Configure `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL` in the repository-root `.env` for both local development and Codespaces. See the [setup guide](/docs/setup/README.md#configure-environment-variables). ## What is prompt engineering? @@ -330,6 +320,4 @@ C. Prompt engineering is about training a model to recognize patterns in data. - [Prompt engineering](https://en.wikipedia.org/wiki/Prompt_engineering) - [Prompt engineering fundamentals](https://github.com/microsoft/generative-ai-for-beginners/blob/main/04-prompt-engineering-fundamentals/README.md?WT.mc_id=academic-105485-koreyst) -- [Advanced prompt engineering](https://github.com/microsoft/generative-ai-for-beginners/tree/main/05-advanced-prompts) - - +- [Advanced prompt engineering](https://github.com/microsoft/generative-ai-for-beginners/tree/main/05-advanced-prompts) diff --git a/lessons/03-prompt-engineering/sample-app/app.js b/lessons/03-prompt-engineering/sample-app/app.js index 5c4c74e1..a9b9bec5 100644 --- a/lessons/03-prompt-engineering/sample-app/app.js +++ b/lessons/03-prompt-engineering/sample-app/app.js @@ -1,52 +1,37 @@ import { OpenAI } from "openai"; -import readline from "readline"; - -// User input -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); +import { createInterface } from "node:readline/promises"; +import { stdin, stdout } from "node:process"; + +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} -const question = (query) => { - return new Promise((resolve) => { - rl.question(query, (answer) => { - resolve(answer); - }); - }); -}; +const openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); async function main() { - const userPrompt = await question("Enter your prompt: "); - - const messages = [ - { - role: "user", - content: userPrompt + const rl = createInterface({ input: stdin, output: stdout }); + try { + const userPrompt = await rl.question("Enter your prompt: "); + if (!userPrompt.trim()) throw new Error("Enter a non-empty prompt."); + const completion = await openai.chat.completions.create({ + model, + messages: [{ role: "user", content: userPrompt }] + }); + const answer = completion.choices[0]?.message?.content; + if (!answer?.trim()) throw new Error("The model did not return an answer."); + console.log(`\nAI Response:\n${answer}`); + if (userPrompt.toLowerCase().includes("time-traveling javascript developer")) { + console.log("\nEaster Egg Unlocked! You discovered the hidden poem!"); } - ]; - - // OpenAI API setup - const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, - }); - - const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: messages, - }); - - const answer = completion.choices[0]?.message?.content; - - console.log(`\nAI Response:\n`); - console.log(answer); - - // Easter Egg Check - if (userPrompt.toLowerCase().includes("time-traveling javascript developer") && answer) { - console.log("\n Easter Egg Unlocked! You discovered the hidden poem! "); + } finally { + rl.close(); } - - rl.close(); } -main(); +main().catch(error => { + console.error("Error:", error.message); + process.exitCode = 1; +}); diff --git a/lessons/03-prompt-engineering/sample-app/package-lock.json b/lessons/03-prompt-engineering/sample-app/package-lock.json index ae1264ed..d3886f9f 100644 --- a/lessons/03-prompt-engineering/sample-app/package-lock.json +++ b/lessons/03-prompt-engineering/sample-app/package-lock.json @@ -9,218 +9,38 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "openai": "^4.84.1" - } - }, - "node_modules/@types/node": { - "version": "18.19.75", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.75.tgz", - "integrity": "sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "openai": "^7.28.0" } }, "node_modules/openai": { - "version": "4.84.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.84.1.tgz", - "integrity": "sha512-bVdFsfeVBXN59x9i26dHnYUkY5JPk19w7SydzMvJZDsA+FxwXZrQsUBtdwsQt/ZLuilcc/LzJG7mpJucO2DDHg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.28.0.tgz", + "integrity": "sha512-HSY4fFLflQGYe2kvO0atm6/GzhD61gpyiOZZUvO4nptMUhuB1to2K3wwFpFqc5gfEK0SxyIIFiu1yE2MtygWcA==", "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" + "engines": { + "node": ">=22.0.0" }, "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, "ws": { "optional": true }, @@ -228,43 +48,6 @@ "optional": true } } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } } } } diff --git a/lessons/03-prompt-engineering/sample-app/package.json b/lessons/03-prompt-engineering/sample-app/package.json index 44add90e..6d837865 100644 --- a/lessons/03-prompt-engineering/sample-app/package.json +++ b/lessons/03-prompt-engineering/sample-app/package.json @@ -4,13 +4,13 @@ "main": "app.js", "type": "module", "scripts": { - "start": "node app.js" + "start": "node --env-file-if-exists=../../../.env app.js" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { - "openai": "^4.84.1" + "openai": "^7.28.0" } } diff --git a/lessons/03-prompt-engineering/solution/solution.md b/lessons/03-prompt-engineering/solution/solution.md index f0d559a5..377a9f7d 100644 --- a/lessons/03-prompt-engineering/solution/solution.md +++ b/lessons/03-prompt-engineering/solution/solution.md @@ -1,118 +1,55 @@ -Here's the solution: +# Aerial Screw Prompt Solution -```javascript -import { OpenAI } from "openai"; -import readline from "node:readline"; +This is a fictional, simplified physics exercise, not flight guidance. It demonstrates how clear inputs and assumptions improve a prompt. -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); +Save the code as `solution.js` in the lesson's `sample-app` directory. Run it with `node --env-file=../../../.env solution.js`, or select another environment file with Node's `--env-file` option. -const question = (query) => { - return new Promise((resolve) => { - rl.question(query, (answer) => { - resolve(answer); - }); +```javascript +import { OpenAI } from "openai"; +import { createInterface } from "node:readline/promises"; +import { stdin, stdout } from "node:process"; + +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} + +const openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); +const rl = createInterface({ input: stdin, output: stdout }); +try { + async function number(question) { + const text = await rl.question(question); + const value = Number(text); + if (!text.trim() || !Number.isFinite(value)) throw new Error("Enter a finite number."); + return value; + } + const height = await number("Height above ground in meters: "); + const speed = await number("Forward speed in meters per second: "); + const gravity = await number("Gravity in meters per second squared: "); + const upwardSpeed = await number("Initial upward speed in meters per second: "); + if (height < 0 || speed <= 0 || gravity <= 0) { + throw new Error("Height must be non-negative; speed and gravity must be positive."); + } + const prompt = `For this fictional exercise, the hill is 100 meters away. +Height is ${height} meters, forward speed is ${speed} meters per second, +gravity is ${gravity} meters per second squared, and initial upward speed +is ${upwardSpeed} meters per second. Ignore drag. +Use horizontal travel time = 100 / forward speed and vertical height +y(t) = initial height + initial upward speed * t - gravity * t^2 / 2. +State the travel time and whether the machine reaches ground before the hill. +Give a short result and check the units.`; + const completion = await openai.chat.completions.create({ + model, + messages: [{ role: "user", content: prompt }] }); -}; - -const height = await question("Enter the current height above the ground in meters:"); - -const speed = await question("Enter the speed at which you're moving forward in meters per second:"); - -const gravity = await question("Enter the gravity in meters per second squared:"); - -const wind = await question("Enter the wind speed upwards in meters per second:"); - -// Distance to the hill -const distance = 100; - -// Create prompt including inputs should include chain of thought - -const prompt = `Current height above the ground: ${height} meters, Moving forward at: ${speed} meters per second, Gravity: ${gravity} meters per second squared, Wind upwards at: ${wind} meters per second, Distance to the hill: ${distance} meters, Time it will take to reach the hill: t, Here's the formula to calculate the time it will take to reach the hill: D = 1/2 * (g - w) * t^2.`; - -// Call the language model with the prompt - -const messages = [ -{ - "role": "user", - "content": prompt -}]; - -// 2. Create client -// ----------------------------------- - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - -// 3. Send the request -// ----------------------------------- - -const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: messages, -}); - -console.log(`Answer for "${prompt}":`); - -// 4. Print the answer -// ----------------------------------- - -console.log(completion.choices[0]?.message?.content); - + const answer = completion.choices[0]?.message?.content; + if (!answer?.trim()) throw new Error("The model did not return an answer."); + console.log(answer); +} finally { + rl.close(); +} ``` -## Sample output - -```text -Enter the current height above the ground in meters:100 -Enter the speed at which you're moving forward in meters per second:3 -Enter the gravity in meters per second squared:9.82 -Enter the wind speed upwards in meters per second:3 -Answer for "Current height above the ground: 100 meters, Moving forward at: 3 meters per second, Gravity: 9.82 meters per second squared, Wind upwards at: 3 meters per second, Distance to the hill: 100 meters, Time it will take to reach the hill: t, Here's the formula to calculate the time it will take to reach the hill: D = 1/2 * (g - w) * t^2.": -To determine the time \( t \) it will take to reach the hill, we can rearrange the formula you provided: - -\[ -D = \frac{1}{2} (g - w) t^2 -\] - -Where: -- \( D \) is the distance to the hill (100 meters), -- \( g \) is the acceleration due to gravity (9.82 m/s²), -- \( w \) is the upward wind speed (3 m/s). - -First, we need to calculate \( g - w \): - -\[ -g - w = 9.82 \, \text{m/s}^2 - 3 \, \text{m/s} = 6.82 \, \text{m/s}^2 -\] - -Now, substituting \( D = 100 \) meters into the formula: - -\[ -100 = \frac{1}{2} \times 6.82 \times t^2 -\] - -To isolate \( t^2 \), we can multiply both sides by 2: - -\[ -200 = 6.82 t^2 -\] - -Next, we divide both sides by 6.82: - -\[ -t^2 = \frac{200}{6.82} \approx 29.34 -\] - -Now, taking the square root of both sides gives us \( t \): - -\[ -t \approx \sqrt{29.34} \approx 5.42 \, \text{seconds} -\] - -So, it will take approximately **5.42 seconds** to reach the hill under the given conditions. -``` +For height `100`, forward speed `3`, gravity `9.82`, and initial upward speed `3`, travel time to the hill is about `33.33` seconds. The simplified trajectory reaches ground after about `4.83` seconds, before reaching the hill. Check numerical answers rather than assuming the model is correct. diff --git a/lessons/03-prompt-engineering/translations/README.bn.md b/lessons/03-prompt-engineering/translations/README.bn.md deleted file mode 100644 index 758164f1..00000000 --- a/lessons/03-prompt-engineering/translations/README.bn.md +++ /dev/null @@ -1,376 +0,0 @@ -# **অধ্যায় ৩: প্রম্পট ইঞ্জিনিয়ারিং** - -এই অধ্যায়ে আপনি শিখবেন: - -- **প্রম্পট ইঞ্জিনিয়ারিং কী** এবং **এর মৌলিক ধারণা জানা কিভাবে আরও ভালো ফলাফল আনতে সহায়তা করে**। -- **বিভিন্ন প্রম্পট ইঞ্জিনিয়ারিং কৌশল** এবং কীভাবে সেগুলো বিভিন্ন পরিস্থিতিতে আরও ভালো ফলাফল দিতে পারে। -- **মেটা প্রম্পট কী** এবং কীভাবে এগুলো **আউটপুট সীমাবদ্ধ করতে, অ্যাক্সেসযোগ্যতা নিশ্চিত করতে এবং আরও ভালো ব্যবহারকারীর অভিজ্ঞতা প্রদান করতে সাহায্য করে**। - ---- - -## **সেটআপ** - -যদি আপনি এখনও সেটআপ না করে থাকেন, তাহলে **আপনার ডেভেলপমেন্ট এনভায়রনমেন্ট সেটআপ করুন**। -এটি করার জন্য এই লিঙ্কটি অনুসরণ করুন: [আপনার এনভায়রনমেন্ট সেটআপ করুন](/docs/setup/README.md)। - ---- - -## **সম্পর্কিত রিসোর্সসমূহ** - -[![প্রম্পট ইঞ্জিনিয়ারিং সম্পর্কে একটি ছোট ভিডিও দেখুন](https://img.youtube.com/vi/gQ6TlyxBmWs/0.jpg)](https://www.youtube.com/watch?v=gQ6TlyxBmWs&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=3) - -_এই ভিডিওটি আপনাকে **"প্রম্পটিং" দক্ষতা উন্নত করার প্রাথমিক ধারণা প্রদান করবে**, যা আপনাকে **AI-কে আরও স্পষ্ট এবং কার্যকর নির্দেশনা দিতে সাহায্য করবে**, ফলে আপনি আরও ভালো ফলাফল পেতে পারেন।_ - -🎥 *উপরের ছবিতে ক্লিক করে প্রম্পট ইঞ্জিনিয়ারিং সম্পর্কে একটি ছোট ভিডিও দেখুন* - -💼 **স্লাইড:** [প্রম্পট ইঞ্জিনিয়ারিং](/videos/slides/02-prompt-engineering.pptx) -## **গল্প: টিকিট টু রাইড** - -> [!NOTE] -> এখন পর্যন্ত, আপনি **লিওনার্দো দা ভিঞ্চির সঙ্গে সময় ভ্রমণে গিয়েছেন** এবং **রেনেসাঁ যুগের বিস্ময়সমূহ অন্বেষণ করেছেন**। আপনি **টাইম বিটল**, একটি রহস্যময় যন্ত্রের সন্ধান পেয়েছেন, যা আপনাকে সময় এবং স্থানের মধ্যে ভ্রমণ করতে দেয়। -> -> যদি আপনি গল্পের শুরু থেকে পড়তে চান, তাহলে [অধ্যায় ১](/lessons/01-intro-to-genai/translations/README.bn.md) দেখুন। - -> [!NOTE] -> আমরা গল্পটি উপভোগ করার পরামর্শ দিই (এটি মজাদার!), তবে আপনি চাইলে সরাসরি প্রযুক্তিগত অংশে যেতে পারেন—[এখানে ক্লিক করুন](#স্পোর্জার-সাথে-যোগাযোগ-করুন)। - ---- - -**আপনি:** "দেখি, আমি কি বাড়ি ফিরে যেতে পারি?" - -আপনি বোতাম টিপতে যাচ্ছেন, ঠিক তখনই **কর্মশালার দরজা প্রচণ্ড শব্দে খুলে যায়**। এক বিশালদেহী লোক দরজার মধ্যে দাঁড়িয়ে আছে, পরনে দামি পোশাক, তিনি হাতে একটি কাগজ উঁচিয়ে চিৎকার করছেন: - -
- রেগে থাকা লুডোভিকো স্পোর্জা দরজা ভেঙে ঢুকছে -
- -### *লুডোভিকো স্পোর্জা* - -**স্পোর্জা:** **"Da Vinci, dov'è il mio dipinto?"** (*আমার চিত্রকর্ম কোথায়?*) - -**লিওনার্দো:** "এটি **স্পোর্জা**। সে চায় আমি **'দ্য লাস্ট সাপার'** চিত্রটি শেষ করি।" - -**আপনি:** "তুমি তাকে বললেই তো পারতে?" - -**লিওনার্দো:** "আমি বলেছি, কিন্তু সে ধৈর্যশীল ব্যক্তি নয়।" - ---- - -আপনি লিওনার্দোর পেছনে দৌড় দেন। কর্মশালার পিছনের দরজা দিয়ে বের হয়ে **একটি অন্ধকার গলিতে প্রবেশ করেন**, সেখানে **একটি ঘোড়ার গাড়ি অপেক্ষা করছে**। - -**লিওনার্দো দ্রুত লাগাম ধরে ফেলেন**, এবং আপনিও দ্রুত **গাড়িতে উঠে পড়েন**। চাবুকের এক দ্রুত আঘাতে ঘোড়াটি ছুটে যায়, **ফ্লোরেন্সের সরু রাস্তাগুলোর মধ্য দিয়ে ছুটতে থাকে**। - ---- - -**লিওনার্দো:** "ওই যন্ত্রটা চাপ দাও, ওরা আমাদের খুব কাছাকাছি চলে এসেছে!" - -**আপনি:** **"ঠিক আছে, টাইম বিটল, মনোযোগ দিয়ে শোনো। আমি বর্তমান সময়ে, আমার বাড়িতে ফিরে যেতে চাই, ঠিক আছে?"** - -হঠাৎ **গাড়িটি একটি ধাক্কা খায়**, এবং **টাইম বিটল আপনার হাত থেকে ছিটকে মাটিতে পড়ে যায়**। - -যন্ত্রটি **বিপ বিপ শব্দ করতে শুরু করে** এবং রোবটিক কণ্ঠে ঘোষণা করে: **"গন্তব্য: রোম।"** - -**আপনি:** "না, রোম নয়! বাড়ি... বাড়ি...!" - -**চারপাশের দুনিয়া রঙিন ঘূর্ণিঝড়ে রূপ নেয়**, এবং পৃথিবী **আলো এবং রঙের এক বিচিত্র ছন্দে বিলীন হয়ে যায়**। -## **পালানো** - -রঙের ঘূর্ণি ধীরে ধীরে মিলিয়ে যায়, এবং আপনি নিজেকে **একটি গাড়িতে** আবিষ্কার করেন, যা এখন **রোমের ভিয়া আপিয়া**-তে দুরন্ত গতিতে ছুটছে। আশ্চর্যজনকভাবে, **আপনি এখন একটি ঘোড়দৌড়ের মাঝখানে আছেন**! চারপাশে **রথগুলো বজ্রধ্বনির মতো শব্দে দৌড়াচ্ছে**, তাদের চাকাগুলো ধুলোয় আকাশ ঢেকে দিচ্ছে। - -
- রোম থেকে পালানো -
- -**আপনি:** "লিওনার্দো, আমরা কোথায়?" - -**লিওনার্দো:** "এটা দেখেই মনে হচ্ছে **আমরা দৌড়ের মাঝখানে আছি!** আমাদের এখান থেকে বের হতে হবে!" - -আপনি **গাড়ির দুই পাশে শক্ত করে ধরে থাকেন**, যখন এটি বিশৃঙ্খলার মধ্যে দুলতে থাকে। এদিকে, **রোমান সৈন্যরা ঘোড়ার পিঠে চড়ে আপনার দিকে ছুটে আসছে**। তাদের **বর্ম সূর্যের আলোয় ঝলমল করছে**। - -**আপনি:** "ওই দিকে দেখো! বন্দর ধরো!" - -লিওনার্দো দ্রুত **গাড়িটিকে নির্গমনের দিকে ঘোরান**, চাকাগুলো **পিচ্ছিল পাথরের উপর পিছলে যায়**। সৈন্যরা **পিছনে ধাওয়া করছে**, তাদের চিৎকার আপনার কানে প্রতিধ্বনিত হচ্ছে। - -**লিওনার্দো:** "আমাদের ওদের甩িয়ে দিতে হবে! কোথাও লুকানোর জায়গা খুঁজো!" - -আপনি **সংকীর্ণ রাস্তাগুলোর দিকে তাকান**, হৃদস্পন্দন দ্রুত হয়ে আসে। - -**আপনি:** "ওইদিকে দেখো! একটা সরু গলি!" - -লিওনার্দো **গাড়িটিকে গলির দিকে ঘোরান**, শক্ত বাঁক নিতে গিয়ে **গাড়িটি কেঁপে ওঠে**। আপনি **গাড়ি থেকে ঝাঁপিয়ে পড়েন** এবং **লিওনার্দোকে পেছন থেকে বিশাল একটি যন্ত্র টানতে সাহায্য করেন**। - -এটি **তার আবিষ্কার—এরিয়াল স্ক্রু!** - -**লিওনার্দো:** "দ্রুত, আমাদের এটিকে ছাদে নিতে হবে!" - -আপনারা দু’জন **যন্ত্রটির ভার বহন করেন**, **সৈন্যদের আওয়াজ ক্রমশ কাছে আসতে থাকে**। দেয়ালের প্রতিধ্বনিতে তাদের **পদধ্বনি স্পষ্ট শোনা যাচ্ছে**। - -আপনারা **অবশেষে ছাদে পৌঁছান**, হাঁপাতে হাঁপাতে ঘামে ভিজে যান। - -**লিওনার্দো:** "আমাকে সেটআপ করতে সাহায্য করো!" - -আপনারা একসঙ্গে কাজ করেন, **আপনার হাত দক্ষতার সাথে চলে**। সবকিছু ঠিক করে নিয়ে, **আপনারা দু'জন যন্ত্রটির উপর উঠে পড়েন**, হৃদস্পন্দন দ্রুত হতে থাকে। - -**লিওনার্দো:** "শক্ত করে ধরে থাকো!" - -শেষ এক ধাক্কায় **এরিয়াল স্ক্রু ঘুরতে শুরু করে**। **বাতাসের ধাক্কা ধরে নেয় ব্লেডগুলো**, আর হঠাৎ **আপনারা মাটি থেকে উপরে উঠে যেতে থাকেন**! - -**আপনি:** "আমরা পেরেছি, লিওনার্দো! আমরা উড়ছি!" - -**লিওনার্দো:** "হ্যাঁ, তবে আমাদের নিরাপদে নামার জন্য জায়গা খুঁজতে হবে।" - -আপনি নিচে তাকান, **প্রাচীন রোমের বিস্তৃত শহর আপনার নিচে ছড়িয়ে আছে**। - -
- উড়ন্ত অবস্থায়, নিচে শহর দেখা যাচ্ছে -
- -## **স্পোর্জার সাথে যোগাযোগ করুন** - -যদি আপনি **স্পোর্জার (Sforza) সাথে কথোপকথন** করতে চান, তাহলে [Characters](/app/README.md) অ্যাপ চালান। - -> [!IMPORTANT] -> এটি সম্পূর্ণ **কাল্পনিক**; প্রতিক্রিয়াগুলি **AI দ্বারা তৈরি**। -> [দায়িত্বশীল AI সম্পর্কিত ঘোষণা](/README.md#responsible-ai-disclaimer) - -
- লুডোভিকো স্পোর্জা -
- -### **ধাপে ধাপে নির্দেশনা:** - -1. **শুরু করুন** [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. **রিপোজিটরির মূল ফোল্ডারে** _/app_ এ যান। -3. **কনসোল খুঁজে বের করুন** এবং **`npm install`** কমান্ড চালান, তারপর **`npm start`** লিখে রান করুন। -4. **অ্যাপ চালু হলে**, "Open in Browser" বোতাম নির্বাচন করুন। -5. **স্পোর্জার সাথে চ্যাট করুন।** - -আরও বিস্তারিত জানতে, দেখুন **[বিস্তারিত অ্যাপ ব্যাখ্যা](/lessons/01-intro-to-genai/translations/README.bn.md#talk-to-dinocrates)**। - -> [!NOTE] -> আপনি যদি **প্রকল্পটি স্থানীয়ভাবে (locally) চালান**, তাহলে দয়া করে **QuickStart গাইড** পর্যালোচনা করুন এবং **[GitHub ব্যক্তিগত অ্যাক্সেস টোকেন (PAT)](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access)** সেটআপ করুন, তারপর **কোডে টোকেনটি প্রতিস্থাপন করুন**। -## **প্রম্পট ইঞ্জিনিয়ারিং কী?** - -**টাইম বিটল:** "আমাদের **প্রম্পট ইঞ্জিনিয়ারিং** সম্পর্কে কথা বলা দরকার।" - -**আপনি:** "আমাদের দরকার? এটা আবার কী?" - -**টাইম বিটল:** "প্রম্পট ইঞ্জিনিয়ারিং নাম শুনে মনে হতে পারে এটি **সেতু বা রাস্তা নির্মাণের ব্যাপার**, কিন্তু আসলে এটি **Generative AI ভাষা মডেল থেকে কাঙ্ক্ষিত আউটপুট পাওয়ার জন্য সঠিক 'প্রম্পট' তৈরি করার কৌশল**।" - -**আপনি:** "ঠিক আছে, কিন্তু এটা জানা আমার জন্য এত গুরুত্বপূর্ণ কেন?" - -**টাইম বিটল:** "তুমি তো **এরিয়াল স্ক্রু নামাতে চাও**, তাই তো?" - -**আপনি:** "হ্যাঁ, এবং সেটিও এক টুকরোতে রাখতে চাই। :-)" - -**টাইম বিটল:** "ঠিক তাই ভেবেছিলাম! এখন শোনো, **প্রম্পট ইঞ্জিনিয়ারিং** হলো **সঠিক প্রম্পট তৈরি করার কৌশল**, যাতে ভাষা মডেল থেকে কাঙ্ক্ষিত আউটপুট পাওয়া যায়।" - -**টাইম বিটল:** "**বড় ভাষা মডেলগুলোর মধ্যে কিছু বিশেষ বৈশিষ্ট্য থাকে, যেগুলোকে বলা হয় '_emergent properties_'**। এর মানে হলো, **এই বৈশিষ্ট্যগুলো মডেলে সরাসরি প্রোগ্রাম করা হয় না, বরং এটি প্রশিক্ষণের সময় বিশাল পরিমাণ পাঠ্য ডাটার মাধ্যমে স্বয়ংক্রিয়ভাবে বিকশিত হয়**। এই উদীয়মান বৈশিষ্ট্যগুলো অধ্যয়ন করলে **তুমি নির্দিষ্ট ধরণ বা প্যাটার্ন খুঁজে বের করতে পারবে**, যা মডেল থেকে আরও ভালো ফলাফল পেতে সাহায্য করবে।" - -**আপনি:** "ঠিক আছে, তাহলে বোঝা গেল যে কিছু নির্দিষ্ট প্যাটার্ন আছে যা মডেল থেকে ভালো ফলাফল আনতে সাহায্য করে। কিন্তু এটা কীভাবে আমাকে এরিয়াল স্ক্রু নামাতে সাহায্য করবে?" - -**টাইম বিটল:** "ঠিক তাই, এবং বাহ! তুমি মোটেও ধৈর্যশীল নও, তাই না?" - -**আপনি:** "তুমি হলেও ধৈর্য হারাতে, যদি **এমন একটি যন্ত্রে উড়তে হতো যেখানে নামার কোনো স্পষ্ট নির্দেশনা নেই!**" - -**টাইম বিটল:** "আমি কিন্তু ঠিক **তোমার পাশেই আছি**, এটা ভুলো না! কিন্তু চলো, আসল বিষয়ে ফিরে আসি। -## **কিভাবে প্রম্পট ইঞ্জিনিয়ারিং অ্যাপ ডেভেলপারদের সহায়তা করতে পারে** - -**টাইম বিটল:** "একজন পেশাদার হিসেবে, **ভালো প্রম্পট তৈরি করা একটি গুরুত্বপূর্ণ দক্ষতা**, যা তোমার টুলকিটের অংশ হওয়া উচিত।" - -একজন **ডেভেলপারের দৃষ্টিকোণ থেকে**, **প্রম্পট ইঞ্জিনিয়ারিং** ব্যবহার করে তুমি: - -- **আরও নির্ভুল ফলাফল পেতে পারবে**, যা তোমার **অ্যাপের ব্যবহারকারীর অভিজ্ঞতা উন্নত করবে**। - -- **মডেলের আউটপুট নিয়ন্ত্রণ করতে পারবে**, যাতে এটি ব্যবহারকারীদের জন্য **প্রাসঙ্গিক এবং উপযুক্ত** হয়। - - এটি বিশেষভাবে গুরুত্বপূর্ণ যখন **সংবেদনশীল বিষয়** নিয়ে কাজ করা হয় এবং **তোমার ব্র্যান্ডের সুনাম রক্ষা করতে হয়**। - -- **আউটপুটকে এমনভাবে বিন্যাস করতে পারবে**, যাতে তা **সহজে বোঝা ও ব্যবহারযোগ্য হয়**। - - আমরা পরবর্তী অধ্যায়ে এটি আরও বিস্তারিতভাবে আলোচনা করব। - -**টাইম বিটল:** "চলো, বিভিন্ন কৌশল ও পদ্ধতি অন্বেষণ করি, যা তুমি **তোমার ভাষা মডেলের জন্য কার্যকর প্রম্পট তৈরি করতে** ব্যবহার করতে পারবে।" - -## **প্রম্পট ইঞ্জিনিয়ারিং কৌশল ও কৌশলগত দৃষ্টিভঙ্গি** - -**টাইম বিটল:** "চলো, বিভিন্ন **কৌশল ও কৌশলগত পদ্ধতি** সম্পর্কে জানি, যা তোমাকে আরও কার্যকর **প্রম্পট তৈরি করতে সহায়তা করবে**।" - -- **Few-shot prompting** – এই কৌশলে **কিছু উদাহরণ প্রদান করা হয়**, যাতে AI বুঝতে পারে **কী ধরণের প্রতিক্রিয়া** প্রত্যাশিত। - -- **Chain-of-thought prompting** – এটি **একটি ধাপে ধাপে প্রক্রিয়া**, যেখানে **AI-কে ক্রমান্বয়ে একাধিক প্রম্পট দেওয়া হয়**, যাতে এটি **একটি জটিল কাজ ধাপে ধাপে সমাধান করতে শেখে**। - -- **Meta-prompts** – এই কৌশলে **অতিরিক্ত তথ্য বা নির্দেশনা প্রদান করা হয়**, যাতে **AI-এর আউটপুট আরও নির্দিষ্ট ও কাঙ্ক্ষিত হয়**। - -- **Maieutic prompting** – এই কৌশলে **গাইডিং প্রশ্ন ব্যবহার করা হয়**, যা **AI-কে একটি নির্দিষ্ট উত্তরের দিকে পরিচালিত করতে সহায়তা করে**। - ---- - -**আপনি:** "এগুলো বেশ আকর্ষণীয় শোনাচ্ছে! আমি ধরে নিচ্ছি, তুমি আমাকে কিছু উদাহরণ দেবে?" - -**টাইম বিটল:** "হ্যাঁ, একদম! চলো, প্রতিটি কৌশলের জন্য কিছু উদাহরণ দেখি, যাতে আমরা বুঝতে পারি, **এগুলো বাস্তবে কীভাবে কাজ করে**।" -### **Few-shot prompting: উদাহরণ দিয়ে AI-এর আউটপুট নির্দেশ করা** - -**Few-shot prompting** কৌশলে **কয়েকটি উদাহরণ প্রদান করা হয়**, যাতে AI **কী ধরনের প্রতিক্রিয়া দেওয়া উচিত তা বুঝতে পারে**। কল্পনা করো, তুমি **এরিয়াল স্ক্রু-তে বসে প্রাচীন রোম সম্পর্কে জানতে চাচ্ছো**। Few-shot প্রম্পট এমন হতে পারে: - -> **"রোমান কলোসিয়াম সম্পর্কে বলো।"** → **"এটি একটি বিশাল অ্যাম্ফিথিয়েটার।"** -> -> **"প্রাচীন রোমের জীবন কেমন ছিল?"** → **"এটি কর্মব্যস্ত এবং কঠিন ছিল।"** - -**আপনি:** "বুঝতে পারছি! এটি AI-কে সঠিকভাবে উত্তর দিতে সাহায্য করে, এমনকি এখানে উপরে থাকলেও! ;)" - -**টাইম বিটল:** "একদম ঠিক! এটি **একটি কার্যকর কৌশল**। এবার পরবর্তী কৌশলটি দেখি!" - ---- - -### **Chain-of-Thought Prompting: ধাপে ধাপে যুক্তির মাধ্যমে নির্দেশনা প্রদান** - -**টাইম বিটল:** "এই কৌশলে AI-কে **ধাপে ধাপে যুক্তি প্রদর্শন করা হয়**, যাতে এটি **একটি সমস্যা সমাধানের প্রক্রিয়া শিখতে পারে**। কল্পনা করো, **তুমি এরিয়াল স্ক্রু নামাতে চাচ্ছো**। এটি ধাপে ধাপে এভাবে বিভক্ত করা যায়: - -- **তুমি ১০০ মিটার উচ্চতায় আছো।** -- **মাধ্যাকর্ষণ বল (Gravity) = ৯.৮ m/s²।** -- **সূত্র:** **উচ্চতা = ½ × g × t²** -- **সমাধান:** **t ≈ ৪.৫ সেকেন্ড লাগবে মাটিতে নামতে।** - -**তোমার প্রম্পট হবে:** -> - **১০০ মিটার উচ্চতায় আছি।** -> - **মাধ্যাকর্ষণ: ৯.৮ m/s²** -> - **মাটিতে পৌঁছাতে কত সেকেন্ড লাগবে?** - -**উত্তর:** **৪.৫ সেকেন্ড** - -**আপনি:** "এটা বেশ দ্রুত—আশা করি গণনাগুলো সঠিক আছে!" 😅 -**টাইম বিটল:** "এটি AI-কে **ধাপে ধাপে চিন্তা করতে শেখায়**।" - -**আপনি:** "তাহলে এটি মূলত **সমস্যাগুলো ভেঙে সঠিক উত্তর পাওয়ার একটি পদ্ধতি**?" - -**টাইম বিটল:** "হ্যাঁ, একদম ঠিক! এটি **অঙ্ক শেখানোর অ্যাপের মতো জটিল কাজের জন্য দুর্দান্ত**!" - ---- - -### **Maieutic Prompting: আউটপুট যাচাই করতে প্রশ্ন করা** - -**টাইম বিটল:** "এই কৌশলে **AI-এর আউটপুটের নির্ভুলতা যাচাই করার জন্য প্রশ্ন করা হয়**। প্রতিটি ধাপ সম্পর্কে প্রশ্ন করলে **যুক্তিগুলো আরও সামঞ্জস্যপূর্ণ হয়**।" - -**আপনি:** "তাহলে, যা-ই বলে, সেটাকে প্রশ্নবিদ্ধ করব? মজার লাগছে! ;)" - -**টাইম বিটল:** "আসো, আমাদের **এরিয়াল স্ক্রু অবতরণ পরীক্ষাটি করি**: -- **১০০ মিটার উচ্চতায় আছি।** -- **মাধ্যাকর্ষণ বল: ৯.৮ m/s²** -- **মাটিতে পৌঁছাতে কত সেকেন্ড লাগবে?** (**উত্তর: ৪.৫ সেকেন্ড**) - -এখন জিজ্ঞাসা করো: **'কেন ৪.৫ সেকেন্ড?'** - -> **AI-এর প্রতিক্রিয়া:** -> - **উচ্চতা = ১০০ মিটার, প্রাথমিক গতি = ০ m/s, মাধ্যাকর্ষণ = ৯.৮ m/s²।** -> - **সূত্র:** **উচ্চতা = ½ × g × t²** -> - **সমাধান:** **১০০ = ½ × ৯.৮ × t² → t ≈ ৪.৫ সেকেন্ড।** - -**টাইম বিটল:** "এটি মূলত **AI-এর উত্তর যাচাই করতে ফলো-আপ প্রশ্ন করার ব্যাপার**।" - -**আপনি:** "তাহলে AI-এর প্রতিক্রিয়াকে অন্ধভাবে বিশ্বাস না করে, বরং **এটিকে প্রশ্নের মাধ্যমে যাচাই করা দরকার**?" - -**টাইম বিটল:** "একদম ঠিক! এটি **AI-এর যুক্তি আরও দৃঢ় করে তোলে**।" -## **মেটা প্রম্পট: সুনির্দিষ্ট নির্দেশনা যোগ করা** - -**টাইম বিটল:** "এবার আসি **মেটা প্রম্পটের** ব্যাপারে।" - -**আপনি:** "উফ্, আগে এক কাপ চা?" - -**টাইম বিটল:** "_আহ্_... মেটা প্রম্পট **প্রম্পটে অতিরিক্ত নির্দেশনা যোগ করে, যাতে আরও নির্ভুল আউটপুট পাওয়া যায়**। এটি **সংবেদনশীল এবং গ্রাহক-মুখী অ্যাপগুলোর জন্য দারুণ কার্যকর**।" - -একটি উদাহরণ দেখুন: - -> **Meta Prompt:** **শুধুমাত্র Contoso Inc.-এর পণ্য ব্যবহার করুন (বাস্কেটবল, সকার বল, টেনিস র‍্যাকেট)। এটি সঠিক এবং ব্যবহারকারী-বান্ধব হতে হবে।** -> **Prompt:** **শুরুর জন্য সেরা খেলাধুলার পণ্য কী?** -> **Response:** **বাস্কেটবল, সকার বল এবং টেনিস র‍্যাকেট—সমন্বয় ও দক্ষতা বৃদ্ধির জন্য উপযুক্ত।** - -এটি নিশ্চিত করে যে **উত্তরটি নির্ভুল থাকে এবং শুধুমাত্র Contoso-এর পণ্য অন্তর্ভুক্ত করা হয়**। - -এখন, যদি **মেটা প্রম্পট ব্যবহার না করা হয়**: - -> **Prompt:** **শুরুর জন্য সেরা খেলাধুলার পণ্য কী?** -> **Response:** **বাস্কেটবল, সকার বল, টেনিস র‍্যাকেট, এবং ফুটবল—(যা সব Contoso পণ্য নয়!)** - ---- - -**আপনি:** "এটি বেশ দরকারি! এটি **প্রতিক্রিয়াকে সঠিক পথে রাখে**, যেন আমার অটোমেটনের জন্য আইন মেনে চলে।" - -**টাইম বিটল:** "হ্যাঁ, তুমি জানো... _'ফ্রাঙ্কেনস্টাইন'_—আহ, কিচ্ছু না!" - -**আপনি:** "কি?" - -**টাইম বিটল:** "কিছু না..." - -> **মেরি শেলির** *ফ্রাঙ্কেনস্টাইন*—১৮১৮ সালে প্রকাশিত, **তোমার সময় ভ্রমণের ৪২ বছর আগে**। -> [আরও জানুন](https://en.wikipedia.org/wiki/Mary_Shelley) - -**আপনি:** "তাহলে, যদি **মেটা প্রম্পট না থাকে**, তবে বিশৃঙ্খলা সৃষ্টি হবে?" - -**টাইম বিটল:** "ঠিক তাই, **তুমি অফ-ব্র্যান্ড (ভুল) উত্তর পাবে**।" - -**আপনি:** "বুঝলাম, **নির্ভুলতা গুরুত্বপূর্ণ!**" -## **অ্যাসাইনমেন্ট - আমাদের নায়কদের এরিয়াল স্ক্রু অবতরণে সহায়তা করা** - -**আপনি:** "এখন তুমি আমাকে **এরিয়াল স্ক্রু কীভাবে নামাতে হয় তা শেখাবে, তাই তো?**" - -**টাইম বিটল:** "হ্যাঁ, এবার কাজে নামা যাক! আমাদের বর্তমান অবস্থান থেকে পাহাড়টি **প্রায় ১০০ মিটার দূরে**। এখানে প্রম্পট সেটআপের একটি উপায়: - -- **মাটির ওপরে বর্তমান উচ্চতা:** **১০০ মিটার** -- **সামনের দিকে গতিবেগ:** **১০ মিটার প্রতি সেকেন্ড** -- **মাধ্যাকর্ষণ বল:** **৯.৮ মিটার প্রতি সেকেন্ড²** -- **উপরের দিকে বাতাসের গতি:** **০.৭ মিটার প্রতি সেকেন্ড** - -**লিওনার্দো:** "তোমার AI যন্ত্র কি হিসাব করে বলতে পারবে যে **আমরা নিরাপদে নামতে পারব, নাকি আমাদের আরেকটি সময়-ভ্রমণ করতে হবে**?" - -**টাইম বিটল:** "আমি পারি, কিন্তু আমার নাম **জর্জ**! **গিজমো বলা বন্ধ করো**। আসলে, আমি তোমাদের দুজনকেই এটি সমাধান করতে দেব। ☺️" - ---- - -### **তোমার কাজ:** - -একটি অ্যাপ তৈরি করো, যা ব্যবহারকারীর কাছে নিচের ইনপুট চাবে: - -✅ **উচ্চতা** -✅ **সামনের দিকে গতিবেগ** -✅ **মাধ্যাকর্ষণ বল** -✅ **উপরের দিকে বাতাসের গতি** -✅ **পাহাড় পর্যন্ত দূরত্ব** - -**চেইন-অফ-থট (Chain-of-Thought) কৌশল** ব্যবহার করে AI-কে যুক্তিসঙ্গতভাবে উত্তরে導 করতে হবে। -## **সমাধান** - -JavaScript-এ একটি অ্যাপ তৈরি করো, যা নিম্নলিখিত ইনপুট গ্রহণ করবে: - -✅ **মাটির ওপরে বর্তমান উচ্চতা** -✅ **সামনের দিকে গতিবেগ (মিটার প্রতি সেকেন্ড)** -✅ **মাধ্যাকর্ষণ বল (মিটার প্রতি সেকেন্ড²)** -✅ **উপরের দিকে বাতাসের গতি (মিটার প্রতি সেকেন্ড)** -✅ **পাহাড় পর্যন্ত দূরত্ব** - -তারপর **LLM**-এর সাহায্যে **চেইন-অফ-থট (Chain-of-Thought) প্রম্পটিং কৌশল** ব্যবহার করে **AI-কে সঠিক উত্তর導 করতে হবে**। - -[সমাধান দেখুন](/lessons/03-prompt-engineering/solution/solution.md) - ---- - -## **জ্ঞান যাচাই (Knowledge Check)** - -**প্রশ্ন:** **প্রম্পট ইঞ্জিনিয়ারিং কী?** সঠিক সব উত্তর নির্বাচন করুন। - -A. **প্রম্পট ইঞ্জিনিয়ারিং সেতু ও রাস্তা নির্মাণের ব্যাপার।** -B. **প্রম্পট ইঞ্জিনিয়ারিং ভাষা মডেল থেকে কাঙ্ক্ষিত আউটপুট পাওয়ার জন্য সঠিক প্রম্পট তৈরি করার কৌশল।** -C. **প্রম্পট ইঞ্জিনিয়ারিং হলো একটি মডেলকে ডাটা থেকে নির্দিষ্ট প্যাটার্ন চিনতে শেখানোর পদ্ধতি।** - -[কুইজ সমাধান](/lessons/03-prompt-engineering/solution/solution-quiz.md) - ---- - -## **স্ব-অধ্যয়নের জন্য সংস্থানসমূহ** - -- [প্রম্পট ইঞ্জিনিয়ারিং](https://en.wikipedia.org/wiki/Prompt_engineering) -- [প্রম্পট ইঞ্জিনিয়ারিং মৌলিক বিষয়সমূহ](https://github.com/microsoft/generative-ai-for-beginners/blob/main/04-prompt-engineering-fundamentals/README.md?WT.mc_id=academic-105485-koreyst) -- [উন্নত প্রম্পট ইঞ্জিনিয়ারিং](https://github.com/microsoft/generative-ai-for-beginners/tree/main/05-advanced-prompts) - ---- - -### **প্রারম্ভিক প্রকল্প:** -শুরু করতে এই **[Starter project](/app/README.md)** ব্যবহার করো! diff --git a/lessons/03-prompt-engineering/translations/README.es.md b/lessons/03-prompt-engineering/translations/README.es.md deleted file mode 100644 index 8efdbf87..00000000 --- a/lessons/03-prompt-engineering/translations/README.es.md +++ /dev/null @@ -1 +0,0 @@ -traduce esto diff --git a/lessons/03-prompt-engineering/translations/README.fr.md b/lessons/03-prompt-engineering/translations/README.fr.md deleted file mode 100644 index cbcf467d..00000000 --- a/lessons/03-prompt-engineering/translations/README.fr.md +++ /dev/null @@ -1,339 +0,0 @@ -# Leçon 3 : Ingénierie des prompts - -Dans ce chapitre, vous apprendrez : - -* Ce qu'est l'ingénierie des prompts et comment connaître les bases peut vous aider à obtenir de meilleurs résultats. -* Différentes techniques d'ingénierie des prompts et comment elles peuvent fournir de meilleurs résultats selon divers scénarios. -* Ce que sont les méta-prompts et comment ils contribuent à restreindre les sorties, garantir leur accessibilité et offrir une meilleure expérience utilisateur. - -## Configuration - -Si ce n'est pas encore fait, configurez votre environnement de développement. Voici comment procéder : [Configurez votre environnement](/docs/setup/README.md). - -## Ressources associées - -[![Regardez une courte vidéo sur l'ingénierie des prompts](https://img.youtube.com/vi/gQ6TlyxBmWs/0.jpg)](https://www.youtube.com/watch?v=gQ6TlyxBmWs\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=3) - -*Cette vidéo propose une introduction à l'amélioration de vos compétences en matière de "prompt", en vous apprenant à donner des instructions plus claires et plus efficaces à l'IA pour obtenir de meilleurs résultats.* - -*🎥 Cliquez sur l'image ci-dessus pour regarder une courte vidéo sur l'ingénierie des prompts* - -💼 Présentation : [Ingénierie des prompts](/videos/slides/02-prompt-engineering.pptx) - -## Narrative: Ticket to ride - -> [!NOTE] -> Jusqu'à présent, vous avez voyagé dans le temps avec Léonard de Vinci, explorant les merveilles de la Renaissance. Vous avez rencontré le Scarabée temporel, un dispositif mystérieux qui vous permet de voyager dans le temps et l'espace. -> -> Reportez-vous à [Leçon 1](/lessons/01-intro-to-genai/README.md) si vous souhaitez reprendre l'histoire depuis le début. - -> [!NOTE] -> Bien que nous recommandions de suivre l'histoire (c'est amusant !), [cliquez ici](#interact-with-sforza) si vous préférez passer directement au contenu technique. - -**Vous :** Voyons si je peux réussir à rentrer chez moi. - -Avant que vous ne puissiez appuyer sur le bouton, les portes de l'atelier s'ouvrent en claquant avec fracas. Un homme se tient dans l'encadrement, large d'épaules et vêtu de vêtements coûteux, brandissant un morceau de papier en l'air et criant : - -!["Un Ludovico Sforza en colère fait irruption par la porte."](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/03-prompt-engineering/assets/ludovico.png) - -*Ludovico Sforza* - -**Sforza** : Da Vinci, dov'è il mio dipinto ? Où est mon tableau ? - -**Léonard :** C'est Sforza. Il veut que je termine une peinture, « La Cène ». - -**Vous :** Tu ne pouvais pas simplement lui dire ça ? - -**Léonard :** J'ai essayé, mais ce n'est pas un homme patient. - -Vous vous précipitez à la suite de Leonardo, sortant par la porte arrière dans une ruelle sombre, où vous trouvez un cheval et une calèche qui vous attendent. - -Leonardo s'empare des rênes et vous sautez tous les deux dans la calèche. Avec un coup sec du fouet, le cheval s'élance, galopant à travers les étroites rues de Florence. - -**Léonard :** Essaie d’appuyer sur ce bidule, ils se rapprochent ! - -**Vous :** "D'accord, Scarabée temporel, écoute attentivement. Je dois retourner au présent, chez moi, d'accord ?" Soudain, la calèche heurte une bosse et le Scarabée temporel s'échappe de vos mains et tombe au sol. Il commence à biper et annonce d'une voix robotique : "Destination : Rome." - -**Vous :** Non, pas Rome, maison... maison... - -Un tourbillon de couleurs enveloppe la calèche, dissolvant le monde autour de vous en un kaléidoscope de lumières. - -## Évasion - -Le tourbillon de couleurs s'estompe, et vous vous retrouvez dans la calèche, désormais lancée à toute vitesse sur la Via Appia à Rome. À votre grande surprise, vous êtes en plein milieu d'une course de chars. Les roues des chars tonnent, soulevant des nuages de poussière. - -![Fuite de Rome](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/03-prompt-engineering/assets/escape.png) - -**Vous :** Léonard, où sommes-nous ? - -**Léonard :** On dirait qu'on est en pleine course ! On doit sortir d'ici ! - -Vous vous agrippez aux côtés de la calèche alors qu'elle traverse le chaos. Les soldats romains à cheval se rapprochent, leurs armures étincelant sous la lumière du soleil. - -**Vous :** Là ! Prends la direction du port ! - -Leonardo dirige la calèche vers la sortie, les roues dérapant sur les pavés. Les soldats sont juste derrière vous, leurs cris résonnant dans vos oreilles. - -**Léonard :** On doit les semer ! Trouve un endroit où se cacher ! - -Vous scrutez les ruelles étroites, le cœur battant la chamade. - -**Vous :** Là ! Une ruelle ! - -Léonard s'engouffre dans la ruelle, la calèche bondissant tandis qu'elle prend le virage serré. Vous sautez hors de la calèche et aidez Léonard à extraire un énorme appareil à l'arrière. C'est son invention : l'**hélice volante**. - -**Léonard :** Vite, on doit l'emmener sur le toit ! - -Vous vous efforcez tous deux de porter l'appareil. Le bruit de la poursuite des soldats se fait plus fort, leurs pas résonnent contre les murs. Vous atteignez le toit, haletant et en sueur. - -**Léonard :** Aide-moi à l'installer ! - -Vous travaillez ensemble, vos mains bougeant avec précision. Vous grimpez tous deux sur l'appareil, le cœur battant à tout rompre. - -**Léonard :** Accroche-toi bien ! - -Avec une dernière poussée, l'hélice commence à tourner. Les pales attrapent l'air et vous ressentez une soudaine élévation. Le sol s'éloigne sous vous alors que l'appareil s'élève au-dessus des toits de Rome. - -**Vous :** On l'a fait, Léonard ! On vole ! - -**Léonard :** Oui, mais il faut trouver un endroit sûr pour atterrir. - -Vous regardez la ville en contrebas ; les bâtiments antiques s'étendent sous vos pieds. - -![En vol, regardant la ville avec les pieds pendants](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/03-prompt-engineering/assets/airborne.png) - -## Interagir avec Sforza - -Si vous souhaitez interagir avec Sforza, exécutez l'application [Personnages](/app/README.md). - -> [!IMPORTANT] -> Ceci est entièrement fictif ; les réponses sont générées par une IA. -> [Clause de non-responsabilité sur l'IA responsable](/README.md#responsible-ai-disclaimer) - -![Ludovico Sforza](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/03-prompt-engineering/assets/ludovico.png) - -**Étapes** : - -1. Lancez un [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript). -2. Naviguez vers */app* à la racine du dépôt. -3. Localisez la console et exécutez `npm install` suivi de `npm start`. -4. Une fois que l'application est ouverte, sélectionnez le bouton "Ouvrir dans le navigateur". -5. Discutez avec Sforza. - -Pour une explication plus détaillée de l'application, voir [Explication détaillée de l'application](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). - -> [!NOTE] -> Si vous exécutez le projet localement sur votre machine, veuillez consulter le guide de démarrage rapide pour configurer un [token d'accès personnel GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) et remplacer la clé dans le code. - -## Qu'est-ce que l'ingénierie des prompts ? - -**Scarabée temporel :** Nous devons parler d'ingénierie des prompts. - -**Vous :** Vraiment ? Qu'est-ce que c'est ? - -**Scarabée temporel :** L'ingénierie des prompts, malgré son nom, n'a rien à voir avec la construction de ponts ou de routes, mais consiste à créer les bons "prompts" pour obtenir le résultat souhaité d'un modèle de langage génératif IA. - -**Vous :** D'accord, mais pourquoi est-ce important que je sache ça ? - -**Scarabée temporel :** Eh bien, tu veux faire atterrir l'hélice volante, n'est-ce pas ? - -**Vous :** Oui, de préférence en un seul morceau. :-) - -**Scarabée temporel :** C'est bien ce que je pensais. Maintenant, écoute ! L'ingénierie des prompts consiste à créer les bons prompts pour obtenir le résultat que tu souhaites d'un modèle de langage. - -**Scarabée temporel :** Les modèles de langage à grande échelle possèdent ce qu'on appelle des *propriétés émergentes*. Cela signifie qu'il s'agit de caractéristiques non explicitement programmées dans le modèle, mais qui émergent de la manière dont il est entraîné sur de vastes quantités de données textuelles. En étudiant ces propriétés émergentes, tu peux tirer des conclusions et découvrir des modèles pour obtenir de meilleurs résultats du modèle. - -**Vous :** D'accord, donc il existe des modèles qu'on peut découvrir qui nous aident à obtenir de meilleurs résultats du modèle. Mais comment cela m'aide-t-il à faire atterrir l'hélice volante ? - -**Scarabée temporel :** Oui, c'est vrai. Et, wow, tu n'es vraiment pas patient, toi, hein ? - -**Vous :** Eh bien, toi non plus tu ne le serais pas si tu volais dans une machine avec des instructions d'atterrissage pas claires. - -**Scarabée temporel :** Je suis là avec toi, tu sais, mais revenons-en au sujet principal. - -## Comment l'ingénierie des prompts peut aider les développeurs d'applications - -**Scarabée temporel :** En tant que praticien, savoir créer de meilleurs prompts est une compétence que tu veux avoir dans ton arsenal. - -Dans une perspective de développeur, vous pouvez utiliser l'ingénierie des prompts pour : - -* **Obtenir des résultats plus précis** de ton modèle de langage et améliorer finalement l'expérience utilisateur de ton application. - -* **Contrôler la sortie** du modèle pour s'assurer qu'elle est pertinente et appropriée à tes utilisateurs. Cela est particulièrement important dans les discussions sur des sujets sensibles et pour protéger la réputation de ta marque. - -* **Formatage des sorties** de manière à ce qu'elles soient faciles à comprendre et à utiliser. Nous approfondirons cet aspect dans le prochain chapitre. - -**Scarabée temporel :** Explorons les différentes techniques et stratégies que tu peux utiliser pour rédiger des prompts efficaces pour ton modèle de langage. - -## Techniques et stratégies d'ingénierie des prompts - -**Scarabée temporel :** Voici quelques techniques et stratégies que tu peux utiliser pour rédiger des prompts efficaces : - -* **Prompting avec quelques exemples**. Cette technique consiste à donner à l'IA quelques exemples pour montrer le type de réponse qu'on attend. - -* **Prompting chaîne-de-pensée**. Cette technique implique de fournir une séquence de prompts pour apprendre à l'IA comment résoudre une tâche complexe étape par étape. - -* **Méta-prompts**. Cette technique consiste à ajouter des détails ou des instructions supplémentaires pour façonner la sortie de l'IA de manière plus précise. - -* **Prompting maïeutique**. Cette technique implique d'utiliser des questions guidantes pour orienter l'IA vers une réponse spécifique. - -**Vous :** Ça a l'air intéressant. Je suppose que tu vas me donner des exemples ? - -**Scarabée temporel :** Oui, ils arrivent tout de suite. Regardons des exemples pour chacune de ces techniques afin de comprendre comment elles fonctionnent en pratique. - -### Prompting avec quelques exemples : Fournir des exemples pour guider les réponses de l'IA - -Le prompting avec quelques exemples donne au modèle IA quelques exemples pour guider ses réponses. Imaginez que vous êtes dans l'hélice volante en train de poser des questions sur la Rome antique. Les prompts avec quelques exemples pourraient ressembler à ceci : - -> "Parle-moi du Colisée romain." → "C'est un amphithéâtre massif." -> -> "À quoi ressemblait la vie dans la Rome antique ?" → "C'était à la fois animée et difficile." - -Je vois ! Cela aide l'IA à donner des réponses correctes, même ici en haut ! ;) - -Exactement, c'est une méthode solide. Passons à la suite ! - -### Prompting chaîne-de-pensée : Montrer un raisonnement étape par étape - -Cette technique guide l'IA en lui montrant un raisonnement étape par étape pour résoudre un problème. Imaginez atterrir avec l'hélice volante. Vous pourriez décomposer cela ainsi : - -* Vous êtes à 100 mètres de hauteur. -* La gravité agit à 9,8 m/s². -* Formula: height = ½ \* g \* t². -* Solve: t ≈ 4.5 seconds to land. - -Votre prompt devient : - -> * \- 100 mètres au-dessus du sol -> * Gravity: 9.8 m/s² -> * \- Combien de secondes pour atterrir ? - -**Answer: 4.5 seconds** - -C'est rapide—j'espère que les calculs sont corrects ! - -Cela apprend à l'IA à réfléchir étape par étape. - -Donc, il s'agit de décomposer les problèmes pour obtenir des réponses précises ? - -Oui, parfait pour des tâches complexes comme une application de tutorat en maths ! - -### Prompting maïeutique : Questionner les réponses pour vérifier leur exactitude - -Cette technique utilise des questions pour vérifier l'exactitude des réponses de l'IA. Vous interrogez chaque étape pour assurer un raisonnement cohérent. - -Donc, remettre en question tout ce qu'elle dit ? Ça a l'air amusant. ;) - -Testons notre atterrissage avec l'hélice volante : - -* 100 mètres de hauteur -* Gravity: 9.8 m/s² -* Combien de secondes pour atterrir ? (Réponse : 4,5 s) - -Ask: “Why 4.5 seconds?” - -> **Response:** -> -> * Hauteur = 100m, vitesse verticale initiale = 0 m/s, gravité = 9,8 m/s². -> * Use: height = ½ \* g \* t². -> * Solve: 100 = ½ \* 9.8 \* t² → t ≈ 4.5s. - -Il s'agit de vérifier les réponses de l'IA avec des questions de suivi. - -Donc, il ne faut pas lui faire confiance—il faut la sonder avec des questions ? - -Exactement ! Cela garantit un raisonnement solide. - -## Méta-prompts : Ajouter des instructions supplémentaires pour plus de précision - -Passons maintenant aux méta-prompts. - -Ugh, d'abord du thé ? - -*Soupir*. Ils ajoutent des directives aux prompts pour de meilleurs résultats—parfait pour des applications sensibles et orientées client. - -Voici un exemple : - -> **Méta-prompt :** Utilisez uniquement les produits Contoso Inc. (ballons de basket, ballons de football, raquettes de tennis). Restez précis et convivial. -> **Prompt :** Meilleurs produits de sport pour les débutants ? -> **Réponse :** Ballons de basket, ballons de football et raquettes de tennis—parfait pour la coordination et l'agilité. - -Cela garantit l'exactitude et le respect des produits de Contoso. Sans cela : - -> **Prompt :** Meilleurs produits de sport pour les débutants ? -> **Réponse :** Ballons de basket, ballons de football, raquettes de tennis, plus ballons de rugby—*pas tous des articles Contoso.* - -Pratique ! Cela maintient les réponses sur la bonne voie, comme des lois pour mon automate. - -Tu le saurais, *"Frankenstein"*—euh, rien ! - -**Vous :** Quoi ? - -Quoi ? - -> *Frankenstein* de Mary Shelley—1818, 42 ans avant tes voyages dans le temps. [En savoir plus](https://en.wikipedia.org/wiki/Mary_Shelley) - -Donc, sans méta-prompt, c'est le chaos ? - -Oui, tu obtiendrais des réponses hors marque. - -Compris, la précision compte ! - -## Mission - Aidons nos héros à atterrir l'hélice volante - -C'est ici que tu me montres comment faire atterrir l'hélice volante, non ? - -Oui, passons à ça. La colline est à environ 100 mètres de notre position actuelle. Voici comment nous pouvons configurer le prompt : - -* Hauteur actuelle par rapport au sol : 100 mètres -* Avance à : 10 mètres par seconde -* Gravity: 9.8 meters per second squared -* Vent ascendant de : 0,7 mètre par seconde - -Ton gadget IA peut-il aider à calculer si on va y arriver ou s'il faut refaire un saut temporel ? - -Je peux oui, mais mon nom est George, je préfère cela à Gadget. Je vais en fait vous laisser tous les deux réfléchir à la façon de configurer le prompt. ☺️ - -Créez une application qui demande : - -* \- La hauteur -* \- La vitesse en avant -* \- La gravité -* \- Le vent ascendant -* \- La distance jusqu'à la colline - -Utilisez la technique de chaîne-de-pensée pour arriver à une réponse. - -Voici un projet de démarrage [Projet de démarrage](/app/README.md). - -## Solution - -Créez une application en JavaScript qui demande les entrées suivantes : - -* Hauteur actuelle par rapport au sol -* Avance en mètres par seconde -* Gravité en mètres par seconde au carré -* Vent ascendant en mètres par seconde -* Distance jusqu'à la colline - -Ensuite, utilisez le LLM pour intégrer ces entrées et exploitez la technique de chaîne-de-pensée pour guider le modèle vers la bonne réponse. - -[Solution](/lessons/03-prompt-engineering/solution/solution.md) - -## Vérification des connaissances - -**Question :** Qu'est-ce que l'ingénierie des prompts ? Sélectionnez tout ce qui s'applique. - -A. L'ingénierie des prompts concerne la construction de ponts et de routes. - -B. L'ingénierie des prompts consiste à créer les bons prompts pour obtenir le résultat souhaité d'un modèle de langage. - -C. L'ingénierie des prompts consiste à entraîner un modèle à reconnaître des motifs dans les données. - -[Quiz solution](/lessons/03-prompt-engineering/solution/solution-quiz.md) - -## Ressources d'auto-apprentissage - -* [Conception de prompts](https://en.wikipedia.org/wiki/Prompt_engineering) -* [Fondamentaux de l'ingénierie des prompts](https://github.com/microsoft/generative-ai-for-beginners/blob/main/04-prompt-engineering-fundamentals/README.md?WT.mc_id=academic-105485-koreyst) -* [Ingénierie des prompts avancée](https://github.com/microsoft/generative-ai-for-beginners/tree/main/05-advanced-prompts) diff --git a/lessons/03-prompt-engineering/translations/README.pt-br.md b/lessons/03-prompt-engineering/translations/README.pt-br.md deleted file mode 100644 index 33f46732..00000000 --- a/lessons/03-prompt-engineering/translations/README.pt-br.md +++ /dev/null @@ -1,341 +0,0 @@ -# Lição 3: Engenharia de prompts - -Neste capítulo você vai aprender: - -- O que é engenharia de prompts e como conhecer os fundamentos pode ajudar você a obter melhores resultados. -- Diferentes técnicas de engenharia de prompts e como elas podem ajudar a fornecer melhores resultados para diferentes cenários. -- O que são meta-prompts e como eles ajudarão a restringir a saída, garantir que seja acessível e proporcionar uma melhor experiência ao usuário. - -## Configuração - -Se você ainda não configurou seu ambiente de desenvolvimento, veja como fazer: [Configure seu ambiente](/docs/setup/README.md). - -## Recursos relacionados - -[![Assista a um vídeo curto sobre engenharia de prompts](https://img.youtube.com/vi/gQ6TlyxBmWs/0.jpg)](https://www.youtube.com/watch?v=gQ6TlyxBmWs&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=3) - -_Este vídeo oferece uma introdução para melhorar suas habilidades de "prompting", ensinando como dar instruções mais claras e eficazes à IA para obter melhores resultados._ - -*🎥 Clique na imagem acima para assistir a um vídeo curto sobre engenharia de prompts* - -💼 Slides: [Engenharia de prompts](/videos/slides/02-prompt-engineering.pptx) - -## Narrativa: Passagem para aventura - -> [!NOTE] -> Até agora, você esteve em uma jornada através do tempo com Leonardo da Vinci, explorando as maravilhas do Renascimento. Você encontrou o Besouro do Tempo, um dispositivo misterioso que permite viajar através do tempo e do espaço. -> -> Veja a [Lição 1](/lessons/01-intro-to-genai/README.md) se quiser acompanhar a história desde o início. - -> [!NOTE] -> Embora recomendemos seguir a história (é divertido!), [clique aqui](#interaja-com-sforza) se preferir ir direto para o conteúdo técnico. - -**Você:** Vamos ver se consigo voltar para casa. - -Antes que você consiga pressionar o botão, as portas da oficina se abrem com um estrondo. Um homem está na entrada, de ombros largos e vestido com roupas caras, ele agita um pedaço de papel no ar, gritando: - -
- Um Ludovico Sforza furioso irrompendo pela porta -
- -*Ludovico Sforza* - -**Sforza**: Da Vinci, dov'è il mio dipinto? Onde está minha pintura? - -**Leonardo:** É Sforza. Ele quer que eu termine uma pintura, 'A Última Ceia'. - -**Você:** Você não poderia simplesmente dizer isso a ele? - -**Leonardo:** Eu tentei, mas ele não é um homem paciente. - -Você corre atrás de Leonardo, saindo pela porta dos fundos para um beco escuro, encontra um cavalo e uma carruagem esperando. - -Leonardo pega as rédeas e vocês dois saltam para a carruagem. Com um estalo do chicote, o cavalo parte, galopando pelas ruas estreitas de Florença. - -**Leonardo:** Tente pressionar aquela engenhoca, eles estão se aproximando! - -**Você:** "Ok, Besouro do Tempo, escute com atenção. Preciso voltar ao presente, para casa, entendeu?" De repente, a carruagem passa por um solavanco e o Besouro do Tempo voa de sua mão e cai no chão. Ele começa a emitir bipes e anuncia com uma voz robótica: "Indo para Roma." - -**Você:** Não, não Roma, home... casa... - -Um turbilhão de cores envolve a carruagem, o mundo ao seu redor se dissolve em um caleidoscópio de luz. - -## Fuga - -O turbilhão de cores desaparece, e você se encontra na carruagem, agora correndo pela Via Appia em Roma e, para seu espanto, está no meio de uma corrida de cavalos. Bigas trovejam passando, suas rodas levantando nuvens de poeira. - -
- Fuga de Roma -
- -**Você:** Leonardo, onde estamos? - -**Leonardo:** Parece que estamos no meio de uma corrida! Precisamos sair daqui! - -Você se agarra às laterais da carruagem enquanto ela atravessa o caos. Soldados romanos a cavalo estão se aproximando, suas armaduras brilhando sob a luz do sol. - -**Você**: Ali! Siga para o porto! - -Leonardo dirige a carruagem em direção à saída, as rodas derrapando nas pedras. Os soldados estão em seu encalço, seus gritos ecoando em seus ouvidos. - -**Leonardo:** Precisamos despistá-los! Procure um lugar para se esconder! - -Você examina as ruas estreitas, seu coração acelerado. - -**Você:** Ali! Um beco! - -Leonardo entra no beco, a carruagem sacudindo ao fazer a curva apertada. Você salta da carruagem e ajuda Leonardo a arrastar um enorme dispositivo da parte de trás. É sua invenção—o _parafuso aéreo_. - -**Leonardo:** Rápido, precisamos levar isso para o telhado! - -Vocês dois se esforçam sob o peso do dispositivo. O som da perseguição dos soldados fica mais alto, seus passos ecoando nas paredes. Vocês chegam ao telhado, ofegantes e suados. - -**Leonardo:** Ajude-me a montá-lo! - -Vocês trabalham juntos, suas mãos se movendo com precisão treinada. Ambos sobem no dispositivo, seu coração batendo forte no peito. - -**Leonardo:** Segure-se bem! - -Com um impulso final, o parafuso aéreo começa a girar. As lâminas pegam o ar, e você sente uma súbita elevação. O chão se afasta abaixo de vocês enquanto o dispositivo decola, subindo acima dos telhados de Roma. - -**Você:** Conseguimos, Leonardo! Estamos voando! - -**Leonardo:** Sim, mas precisamos encontrar um lugar seguro para pousar. - -Você olha para a cidade; os edifícios antigos se estendem abaixo de você. - -
- No ar, olhando para a cidade com os pés pendurados -
- -## Interaja com Sforza - -Se você quiser interagir com Sforza, execute o aplicativo [Characters](/app/README.md). - -> [!IMPORTANT] -> Isso é inteiramente fictício; as respostas são geradas por IA. -> [Aviso sobre IA Responsável](/README.md#responsible-ai-disclaimer) - -
- Ludovico Sforza -
- -**Passos**: - -1. Inicie um [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. Navegue até _/app_ na raiz do repositório. -3. Localize o console e execute `npm install` seguido de `npm start`. -4. Quando aparecer, selecione o botão "Open in Browser". -5. Converse com Sforza. - -Para uma explicação mais detalhada do aplicativo, consulte [Explicação detalhada do aplicativo](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). - -> [!NOTE] - > Se você estiver executando o projeto localmente em sua máquina, por favor revise o guia de Início Rápido para configurar um [token de acesso pessoal do GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) e substitua a chave no código. - -## O que é engenharia de prompts? - -**Besouro do Tempo:** Precisamos falar sobre engenharia de prompts. - -**Você:** Precisamos? O que é isso? - -**Besouro do Tempo:** Engenharia de prompts, apesar do nome, não é sobre construir pontes ou estradas, mas sim sobre elaborar os "prompts" certos para obter a saída desejada de um modelo de linguagem de IA Generativa. - -**Você:** Ok, mas por que é importante que eu saiba disso? - -**Besouro do Tempo:** Bem, você quer pousar o parafuso aéreo, certo? - -**Você:** Sim, de preferência em uma peça só. :-) - -**Besouro do Tempo:** Foi o que pensei, agora preste atenção! Engenharia de prompts é sobre elaborar os prompts certos para obter a saída desejada de um modelo de linguagem. - -**Besouro do Tempo:** Grandes modelos de linguagem têm o que chamamos de _propriedades emergentes_, que são características que não estão explicitamente programadas no modelo, mas surgem da maneira como ele é treinado em vastas quantidades de dados de texto. Ao estudar essas propriedades emergentes, você pode tirar conclusões e descobrir padrões que podem ajudá-lo a obter melhores resultados do modelo. - -**Você:** Ok, então existem padrões que descobrimos que podem nos ajudar a obter melhores resultados do modelo. Mas como isso me ajuda a pousar o parafuso aéreo? - -**Besouro do Tempo:** Sim, correto, e uau, você não é muito paciente, não é? - -**Você:** Bem, você também não seria se estivesse voando em uma geringonça sem instruções claras de pouso. - -**Besouro do Tempo:** Estou aqui com você, sabia? Mas vamos voltar ao ponto. - -## Como a engenharia de prompts pode ajudar desenvolvedores de aplicativos - -**Besouro do Tempo:** Como profissional, ser capaz de elaborar um prompt melhor é uma habilidade que você deseja ter em seu arsenal. - -Olhando da perspectiva de um desenvolvedor, você pode usar a engenharia de prompts para: - -- **Obter resultados mais precisos** do seu modelo de linguagem e, em última análise, melhorar a experiência do usuário do seu aplicativo. - -- **Controlar a saída** do modelo para garantir que seja relevante e apropriada para seus usuários. Isso é especialmente importante ao lidar com tópicos sensíveis e também para proteger a reputação da sua marca. - -- **Formatar a saída** de uma maneira que seja fácil de entender e consumir. Vamos analisar mais este aspecto no próximo capítulo. - -**Besouro do Tempo:** Vamos explorar as diferentes técnicas e estratégias que você pode usar para elaborar prompts eficazes para seu modelo de linguagem. - -## Técnicas e estratégias de engenharia de prompts - -**Besouro do Tempo:** Aqui estão algumas técnicas e estratégias que você pode usar para elaborar prompts eficazes: - -- **Prompting com poucos exemplos**. Esta técnica envolve dar à IA alguns exemplos para mostrar o tipo de resposta que você deseja. - -- **Prompting de cadeia de pensamento**. Esta técnica envolve fornecer uma sequência de prompts para ensinar a IA como resolver uma tarefa complexa passo a passo. - -- **Meta-prompts**. Esta técnica envolve adicionar detalhes ou instruções extras para moldar a saída da IA com mais precisão. - -- **Prompting maiêutico**. Esta técnica envolve usar perguntas orientadoras para direcionar a IA para uma resposta específica. - -**Você:** Isso parece interessante, suponho que você me dará alguns exemplos? - -**Besouro do Tempo:** Sim, já vou mostrar. Vamos analisar exemplos para cada uma dessas técnicas para entender como funcionam na prática. - -### Prompting com poucos exemplos: Dê exemplos para orientar a saída da IA - -O prompting com poucos exemplos fornece a um modelo de IA alguns exemplos para orientar sua saída. Imagine que você está no parafuso aéreo perguntando sobre a Roma antiga. Prompts com poucos exemplos podem ser assim: - -> "Fale-me sobre o Coliseu Romano." → "É um anfiteatro enorme." -> -> "Como era a vida na Roma antiga?" → "Era movimentada e difícil." - -**Você:** Entendi! Isso ajuda a IA a acertar, mesmo aqui em cima! ;) - -**Besouro do Tempo:** Exatamente, é um método sólido. Próximo! - -### Prompting de Cadeia de Pensamento: Mostre raciocínio passo a passo - -**Besouro do Tempo:** Esta técnica guia a IA mostrando um raciocínio passo a passo para resolver um problema. Imagine pousar o parafuso aéreo. Você poderia decompor assim: -- Você está a 100 metros de altura. -- A gravidade puxa a 9,8 m/s². -- Fórmula: altura = ½ * g * t². -- Resolva: t ≈ 4,5 segundos para pousar. - -Seu prompt se torna: -> - 100 metros acima do solo -> - Gravidade: 9,8 m/s² -> - Quantos segundos para pousar? - -**Resposta: 4,5 segundos** - -**Você:** Isso é rápido—espero que os números funcionem! - -**Besouro do Tempo:** Isso ensina a IA a pensar passo a passo. - -**Você:** Então é sobre decompor problemas para respostas precisas? - -**Besouro do Tempo:** Sim, perfeito para tarefas complexas como um aplicativo de tutor de matemática! - -### Prompting Maiêutico: Questione a saída para verificar a precisão - -**Besouro do Tempo:** Esta técnica usa perguntas para verificar a precisão da saída da IA. Você pergunta sobre cada etapa para garantir um raciocínio consistente. - -**Você:** Então, questionar tudo o que ela diz? Parece divertido. ;) - -**Besouro do Tempo:** Vamos testar nosso pouso do parafuso aéreo: -- 100 metros acima -- Gravidade: 9,8 m/s² -- Quantos segundos para pousar? (Resposta: 4,5s) - -Pergunte: "Por que 4,5 segundos?" - -> **Resposta:** -> - Altura = 100m, velocidade vertical inicial = 0 m/s, gravidade = 9,8 m/s². -> - Use: altura = ½ * g * t². -> - Resolva: 100 = ½ * 9,8 * t² → t ≈ 4,5s. - -**Besouro do Tempo:** Trata-se de verificar as respostas da IA com perguntas de acompanhamento. - -**Você:** Então, não confie nela—investigue-a com perguntas? - -**Besouro do Tempo:** Exatamente! Garante um raciocínio sólido. - -## Meta Prompts: Adicione instruções extras para precisão - -**Besouro do Tempo:** Em seguida, meta prompts. - -**Você:** Ugh, chá primeiro? - -**Besouro do Tempo:** _Suspiro_. Eles adicionam orientação aos prompts para melhor saída—ótimos para aplicativos sensíveis e voltados ao cliente. - -Aqui está um exemplo: -> **Meta Prompt:** Use apenas produtos da Contoso Inc. (bolas de basquete, bolas de futebol, raquetes de tênis). Mantenha preciso e amigável ao usuário. -> **Prompt:** Melhores produtos esportivos para iniciantes? -> **Resposta:** Bolas de basquete, bolas de futebol e raquetes de tênis—perfeitas para coordenação e agilidade. - -Isso garante precisão e se atém aos produtos da Contoso. Sem isso: -> **Prompt:** Melhores produtos esportivos para iniciantes? -> **Resposta:** Bolas de basquete, bolas de futebol, raquetes de tênis, além de bolas de futebol americano—_nem todos são itens da Contoso_. - -**Você:** Útil! Mantém as respostas no caminho certo, como leis para meu autômato. - -**Besouro do Tempo:** Você saberia, _"Frankenstein"_—er, nada! - -**Você:** O quê? - -**Besouro do Tempo:** Deixa pra lá. - -> *Frankenstein* de Mary Shelley—1818, 42 anos antes de suas viagens no tempo. [Leia mais](https://en.wikipedia.org/wiki/Mary_Shelley) - -**Você:** Então, sem meta prompt significa caos? - -**Besouro do Tempo:** Sim, você obteria respostas fora da marca. - -**Você:** Entendi, a precisão importa! - -## Tarefa - Vamos ajudar nossos heróis a pousar o parafuso aéreo - -**Você:** Esta é a parte em que você me mostra como pousar o parafuso aéreo, certo? - -**Besouro do Tempo:** Sim, vamos a isso. A colina está aproximadamente a 100 metros de nossa posição atual. Veja como podemos configurar o prompt: - -- Altura atual acima do solo: 100 metros -- Movendo-se para frente a: 10 metros por segundo -- Gravidade: 9,8 metros por segundo ao quadrado -- Vento para cima a: 0,7 metros por segundo - -**Leonardo:** Sua engenhoca de IA pode ajudar no cálculo para ver se conseguiremos chegar ou se precisamos fazer outro salto temporal? - -**Besouro do Tempo:** Sim, posso, mas meu nome é George, prefiro isso a Engenhoca. Na verdade, vou deixar que vocês dois descubram como configurar o prompt. ☺️ - -Construa um aplicativo que pergunte por: - -- Altura -- Velocidade para frente -- Gravidade -- Vento para cima -- Distância até a colina - -Use a técnica de cadeia de pensamento para chegar a uma resposta. - -Aqui está um projeto inicial [Projeto inicial](/app/README.md). - -## Solução - -Construa um aplicativo em JavaScript que peça as seguintes entradas: - -- Altura atual acima do solo -- Movendo-se para frente a metros por segundo -- Gravidade em metros por segundo ao quadrado -- Vento para cima em metros por segundo -- Distância até a colina - -Em seguida, use o LLM para incorporar essas entradas e aproveitar a técnica de prompting de cadeia de pensamento para guiar o modelo até a resposta correta. - -[Solução](../solution/solution.md) - -## Verificação de conhecimento - -**Pergunta:** O que é engenharia de prompts? Selecione todas as opções aplicáveis. - -A. Engenharia de prompts é sobre construir pontes e estradas. - -B. Engenharia de prompts é sobre elaborar os prompts certos para obter a saída desejada de um modelo de linguagem. - -C. Engenharia de prompts é sobre treinar um modelo para reconhecer padrões em dados. - -[Solução do quiz](../solution/solution-quiz.md) - -## Recursos para auto-estudo - -- [Engenharia de prompts](https://en.wikipedia.org/wiki/Prompt_engineering) -- [Fundamentos de engenharia de prompts](https://github.com/microsoft/generative-ai-for-beginners/blob/main/04-prompt-engineering-fundamentals/README.md?WT.mc_id=academic-105485-koreyst) -- [Engenharia de prompts avançada](https://github.com/microsoft/generative-ai-for-beginners/tree/main/05-advanced-prompts) diff --git a/lessons/03-prompt-engineering/translations/README.zh-cn.md b/lessons/03-prompt-engineering/translations/README.zh-cn.md deleted file mode 100644 index 558920e2..00000000 --- a/lessons/03-prompt-engineering/translations/README.zh-cn.md +++ /dev/null @@ -1,341 +0,0 @@ -# 第三课:提示工程 - -在本章中,你将学习: - -- 什么是提示工程以及了解基础知识如何帮助你获得更好的结果。 -- 不同的提示工程技术以及它们如何在不同场景下提供更好的结果。 -- 什么是元提示(meta prompts)以及它们如何帮助限制输出、确保可访问性并提供更好的用户体验。 - -## 环境设置 - -如果你还没有设置开发环境,以下是设置方法:[设置你的环境](/docs/setup/README.md)。 - -## 相关资源 - -[![观看关于提示工程的短视频](https://img.youtube.com/vi/gQ6TlyxBmWs/0.jpg)](https://www.youtube.com/watch?v=gQ6TlyxBmWs&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=3) - -_这个视频介绍了如何提高你的"提示"技巧,教你如何向 AI 提供更清晰、更有效的指令,以获得更好的结果。_ - -*🎥 点击上方图片观看关于提示工程的短视频* - -💼 幻灯片:[提示工程](../../../videos/slides/02-prompt-engineering.pptx) - -## 故事叙述:乘车票 - -> [!NOTE] -> 到目前为止,你已经与莱昂纳多·达·芬奇一起穿越时空,探索文艺复兴时期的奇迹。你遇到了时间甲虫,这是一个神秘的装置,允许你穿越时间和空间。 -> -> 如果你想从头开始了解这个故事,请查看[第一课](../../01-intro-to-genai/README.md)。 - -> [!NOTE] -> 虽然我们建议你跟随故事(这很有趣!),但如果你更喜欢直接跳到技术内容,[点击这里](#与斯福尔扎互动)。 - -**你**:让我看看能否设法回到家。 - -在你按下按钮之前,工作坊的门突然猛烈撞开,发出雷鸣般的巨响。一个人站在门口,宽肩膀,穿着昂贵的衣服,他在空中挥舞着一张纸,大喊: - -
- 愤怒的卢多维科·斯福尔扎冲破门而入 -
- -*卢多维科·斯福尔扎(Ludovico Sforza)* - -**斯福尔扎**:Da Vinci, dov'è il mio dipinto? 我的画在哪里? - -**莱昂纳多**:是斯福尔扎。他想让我完成一幅画,"最后的晚餐"。 - -**你**:你就不能直接告诉他吗? - -**莱昂纳多**:我试过了,但他不是一个有耐心的人。 - -你追着莱昂纳多冲出后门,进入一条黑暗的小巷,你发现一辆马车在等待。 - -莱昂纳多抓住缰绳,你们俩跳进马车。随着一声响亮的鞭打声,马开始奔跑,穿过佛罗伦萨狭窄的街道。 - -**莱昂纳多**:试着按下那个小装置,他们越来越近了! - -**你**:"好的,时间甲虫,仔细听着。我需要回到现在,回家,好吗?"突然,马车碰到了一个颠簸,时间甲虫从你手中飞出,落在地板上。它开始发出哔哔声,并用机器人的声音宣布:"前往罗马。" - -**你**:不,不是罗马,是家...家... - -一团色彩的旋涡笼罩着马车,周围的世界溶解成了一个万花筒般的光芒。 - -## 逃脱 - -色彩的旋涡褪去,你发现自己在马车里,现在正沿着罗马的阿皮亚大道(Via Appia)飞驰,令你惊讶的是,你正处于一场赛马比赛的中间。战车轰隆作响地驶过,车轮卷起阵阵尘土。 - -
- 从罗马逃脱 -
- -**你**:莱昂纳多,我们在哪里? - -**莱昂纳多**:看起来我们正在一场比赛中!我们需要离开这里! - -当马车在混乱中疾驰时,你紧握着车身两侧。骑马的罗马士兵正在逼近,他们的盔甲在阳光下闪闪发光。 - -**你**:那里!向港口前进! - -莱昂纳多将马车转向出口,车轮在鹅卵石上打滑。士兵们紧追不舍,他们的喊叫声在你耳边回荡。 - -**莱昂纳多**:我们需要甩掉他们!找个地方藏起来! - -你扫视着狭窄的街道,心跳加速。 - -**你**:那里!一条后巷! - -莱昂纳多将马车拐进小巷,马车在转弯时剧烈摇晃。你跳下马车,帮助莱昂纳多从车后拖出一个巨大的装置。那是他的发明——空中螺旋桨(aerial screw)。 - -**莱昂纳多**:快,我们需要把它带到屋顶上! - -你们俩在设备的重量下吃力地搬运。追兵的声音越来越大,他们的脚步声在墙壁上回荡。你们气喘吁吁、满头大汗地到达了屋顶。 - -**莱昂纳多**:帮我设置它! - -你们一起工作,手法娴熟精确。你们俩都爬上了装置,心脏在胸腔里剧烈跳动。 - -**莱昂纳多**:抓紧了! - -随着最后一推,空中螺旋桨开始旋转。螺旋桨叶片抓住了空气,你感到一阵突然的升力。当装置起飞,在罗马屋顶上方飞翔时,地面在你们脚下远去。 - -**你**:我们做到了,莱昂纳多!我们在飞! - -**莱昂纳多**:是的,但我们需要找一个安全的地方降落。 - -你俯瞰这座城市;古老的建筑在你们下方延伸开来。 - -
- 空中飞行,脚悬空俯瞰城市 -
- -## 与斯福尔扎互动 - -如果你想与斯福尔扎互动,请运行 [Characters](/app/README.md) 应用程序。 - -> [!IMPORTANT] -> 这完全是虚构的;这些回应是由 AI 生成的。 -> [负责任的 AI 免责声明](../../../README.md#responsible-ai-disclaimer) - -
- 卢多维科·斯福尔扎 -
- -**步骤**: - -1. 启动一个 [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. 导航到仓库根目录中的 _/app_。 -3. 在控制台中运行 `npm install` 然后运行 `npm start`。 -4. 当它出现时,选择"在浏览器中打开"按钮。 -5. 与斯福尔扎聊天。 - -有关应用程序的更详细解释,请参见[详细应用程序说明](../../01-intro-to-genai/README.md#talk-to-dinocrates)。 - -> [!NOTE] - > 如果你在本地机器上运行项目,请查看快速入门指南以设置 [GitHub personal access](../../../docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) token 并在代码中替换密钥。 - -## 什么是提示工程? - -**时间甲虫**:我们需要谈谈提示工程。 - -**你**:是吗?那是什么? - -**时间甲虫**:提示工程尽管其名称如此,但它不是关于建造桥梁或道路,而是关于精心制作正确的"提示",以便从生成式 AI 语言模型中获得所需的输出。 - -**你**:好的,但为什么我需要知道这个? - -**时间甲虫**:嗯,你想让空中螺旋桨安全降落,对吧? - -**你**:是的,最好是完好无损地降落。:-) - -**时间甲虫**:我想也是,现在请听好!提示工程是关于精心制作正确的提示,以便从语言模型中获得所需的输出。 - -**时间甲虫**:大型语言模型具有所谓的_涌现特性_,这意味着这些特性并非明确编程到模型中,而是源自它在大量文本数据上的训练方式。通过研究这些涌现特性,你可以得出结论并发现模式,这些可以帮助你从模型中获得更好的结果。 - -**你**:好的,所以有一些我们发现的模式可以帮助我们从模型中获得更好的结果。但这如何帮助我降落空中螺旋桨呢? - -**时间甲虫**:是的,没错,哇,你真的不太有耐心,是吧? - -**你**:嗯,如果你正在一个没有明确降落说明的装置上飞行,你也不会有耐心。 - -**时间甲虫**:我就在这里陪着你,你知道的,但让我们回到重点上。 - -## 提示工程如何帮助应用程序开发者 - -**时间甲虫**:作为从业者,能够精心制作更好的提示是你工具箱中应该拥有的一项技能。 - -从开发者的角度来看,你可以使用提示工程来: - -- **获得更准确的结果**,从你的语言模型中,并最终改善应用程序的用户体验。 - -- **控制模型的输出**,确保它对你的用户来说是相关且适当的。这在处理敏感话题时尤为重要,也是保护你的品牌声誉的重要方式。 - -- **格式化输出**,使其易于理解和使用。我们将在下一章深入探讨这一方面。 - -**时间甲虫**:让我们来探索你可以用来为语言模型制作有效提示的不同技术和策略。 - -## 提示工程技术和策略 - -**时间甲虫**:以下是你可以用来制作有效提示的一些技术和策略: - -- **少样本提示(Few-shot prompting)**。这种技术涉及给 AI 几个例子,展示你想要的回应类型。 - -- **思维链提示(Chain-of-thought prompting)**。这种技术涉及提供一系列提示,教 AI 如何一步步解决复杂任务。 - -- **元提示(Meta-prompts)**。这种技术涉及添加额外的细节或指令,以更精确地塑造 AI 的输出。 - -- **助产式提示(Maieutic prompting)**。这种技术涉及使用引导性问题,引导 AI 朝特定答案方向发展。 - -**你**:这听起来很有趣,我猜你会给我一些例子? - -**时间甲虫**:是的,马上来。让我们看看每种技术的例子,以便我们了解它们在实践中如何工作。 - -### 少样本提示:提供例子引导 AI 输出 - -少样本提示给 AI 模型提供几个例子来引导其输出。想象你正在空中螺旋桨上询问关于古罗马的问题。少样本提示可能如下所示: - -> "告诉我关于罗马斗兽场的信息。" → "它是一个巨大的圆形剧场。" -> -> "古罗马的生活是怎样的?" → "它繁忙且艰难。" - -**你**:我明白了!这有助于 AI 正确理解,即使在这么高的地方!;) - -**时间甲虫**:没错,这是一种可靠的方法。接下来! - -### 思维链提示:展示一步步推理 - -**时间甲虫**:这种技术通过展示一步步的推理过程来引导 AI 解决问题。想象要降落空中螺旋桨。你可以这样分解问题: -- 你在100米高空。 -- 重力以 9.8 m/s² 拉力。 -- 公式:高度 = ½ * g * t²。 -- 解算:t ≈ 4.5秒完成降落。 - -你的提示变成: -> - 距地面100米高 -> - 重力:9.8 m/s² -> - 需要多少秒才能着陆? - -**答案:4.5秒** - -**你**:那很快——希望数字计算准确! - -**时间甲虫**:它教会 AI 一步步思考。 - -**你**:所以是关于分解问题以获得精确答案? - -**时间甲虫**:是的,非常适合复杂任务,比如数学辅导应用! - -### 助产式提示:提问输出以验证准确性 - -**时间甲虫**:这种技术使用问题来检查 AI 输出的准确性。你对每一步提出问题,以确保推理的一致性。 - -**你**:所以,质疑它说的每一句话?听起来很有趣。;) - -**时间甲虫**:我们来测试空中螺旋桨降落: -- 高度100米 -- 重力:9.8 m/s² -- 需要多少秒才能着陆?(答案:4.5秒) - -问:"为什么是4.5秒?" - -> **回答**: -> - 高度 = 100米,初始垂直速度 = 0 m/s,重力 = 9.8 m/s²。 -> - 使用公式:高度 = ½ * g * t²。 -> - 解算:100 = ½ * 9.8 * t² → t ≈ 4.5秒。 - -**时间甲虫**:这是关于通过后续问题验证 AI 的答案。 - -**你**:所以,不要信任它——而是用问题来探究它? - -**时间甲虫**:没错!确保推理是可靠的。 - -## 元提示:添加额外指令以提高精确度 - -**时间甲虫**:接下来是元提示。 - -**你**:呃,先喝茶? - -**时间甲虫**:_叹气_。它们为提示添加指导以获得更好的输出——非常适合敏感、面向客户的应用。 - -这是一个例子: -> **元提示**: 仅使用 Contoso 公司产品(篮球、足球、网球拍)。保持准确性和用户友好性。 -> **提示**: 适合初学者的最佳体育产品是什么? -> **回答**: 篮球、足球和网球拍——非常适合协调性和敏捷性训练。 - -它确保准确性并坚持使用 Contoso 的产品。如果没有元提示: -> **提示**: 适合初学者的最佳体育产品是什么? -> **回答**: 篮球、足球、网球拍,还有橄榄球——_不全是 Contoso 产品_。 - -**你**:很方便!保持回答在轨道上,就像我自动机的法则一样。 - -**时间甲虫**:你会知道的,_"弗兰肯斯坦"_——呃,没什么! - -**你**:什么? - -**时间甲虫**:没什么。 - -> 玛丽·雪莱的《弗兰肯斯坦》——1818年,比你的时间旅行早42年。[了解更多](https://en.wikipedia.org/wiki/Mary_Shelley) - -**你**:所以,没有元提示就意味着混乱? - -**时间甲虫**:是的,你会得到偏离品牌的答案。 - -**你**:明白了,精准很重要! - -## 作业 - 帮助我们的英雄降落空中螺旋桨 - -**你**:这是你向我展示如何降落空中螺旋桨的部分,对吧? - -**时间甲虫**:是的,让我们开始吧。山丘大约距离我们当前位置100米远。以下是我们如何设置提示: - -- 当前距地面高度:100米 -- 向前移动速度:每秒10米 -- 重力:每秒平方9.8米 -- 向上风力:每秒0.7米 - -**莱昂纳多**:你的 AI 装置能否帮助计算,看看我们是否能够成功降落,或者我们是否需要再次进行时间跳跃? - -**时间甲虫**:我可以的,不过我的名字是乔治(George),我更喜欢这个名字而不是"装置"。实际上,我打算让你们两个自己弄清楚如何设置提示。☺️ - -构建一个应用程序,询问: - -- 高度 -- 前进速度 -- 重力 -- 向上风力 -- 到山丘的距离 - -使用思维链技术得出回应。 - -这里有一个入门项目 [入门项目](/app/README.md)。 - -## 解决方案 - -用 JavaScript 构建一个应用程序,询问以下输入: - -- 当前距地面高度 -- 每秒前进速度(米) -- 每秒平方米的重力 -- 每秒米的向上风力 -- 到山丘的距离 - -然后使用 LLM 整合这些输入,并利用思维链提示技术引导模型得出正确答案。 - -[解决方案](/lessons/01-intro-to-genai/solution/solution.md) - -## 知识检验 - -**问题**: 什么是提示工程?选择所有适用项。 - -A. 提示工程是关于建造桥梁和道路。 - -B. 提示工程是关于精心制作正确的提示,以便从语言模型中获得所需的输出。 - -C. 提示工程是关于训练模型识别数据中的模式。 - -[测验解答](/lessons/01-intro-to-genai/solution/solution-quiz.md) - -## 自学资源 - -- [提示工程](https://en.wikipedia.org/wiki/Prompt_engineering) -- [提示工程基础](https://github.com/microsoft/generative-ai-for-beginners/blob/main/04-prompt-engineering-fundamentals/README.md?WT.mc_id=academic-105485-koreyst) -- [高级提示工程](https://github.com/microsoft/generative-ai-for-beginners/tree/main/05-advanced-prompts) diff --git a/lessons/04-structured-output/README.md b/lessons/04-structured-output/README.md index 7ae7656a..08624b30 100644 --- a/lessons/04-structured-output/README.md +++ b/lessons/04-structured-output/README.md @@ -10,18 +10,6 @@ In this chapter you will learn: If you haven't already, set up your development environment. Here's how you can do it: [Setup your environment](/docs/setup/README.md). -## Related Resources - -It's worth rewatching the video on prompt engineering as it lays the foundation for what you're about to learn in this chapter. - -[![Watch a short video about prompt engineering](https://img.youtube.com/vi/gQ6TlyxBmWs/0.jpg)](https://www.youtube.com/watch?v=gQ6TlyxBmWs&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=3) - -_This video offers an introduction to improving your "prompting" skills, teaching you how to give clearer and more effective instructions to the AI to achieve better results._ - -*🎥 Click on the image above to watch a short video about prompt engineering* - -💼 Slides: [Prompt engineering](/videos/slides/02-prompt-engineering.pptx) - ## Narrative - Out of the frying pan and into the fire > [!NOTE] @@ -56,8 +44,6 @@ Leonardo da Vinci steps forward, his eyes wide with wonder. Before you can react, a group of Aztec soldiers approaches. -![Meeting with Aztecs](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/04-structured-output/assets/meeting.png) - **Soldier leader:** "Who are you, and what have you done?" he demands in Nahuatl. You take a deep breath, trying to steady your nerves. @@ -126,14 +112,14 @@ If you want to interact with Montezuma, run the [Characters](/app/README.md) app 1. Start a [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) 2. Navigate to _/app_ in the repo root. -3. Locate the console and run `npm install` followed by `npm start`. +3. Locate the console and run `npm ci` followed by `npm start`. 4. Once it appears, select the "Open in Browser" button. 5. Chat with Montezuma. For a more detailed explanation of the app, see [Detailed app explanation](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). > [!NOTE] - > If you're running the project locally on your machine, please review the QuickStart guide to get a [GitHub personal access](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) token setup and replace the key in the code. +> Configure `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL` in the repository-root `.env` for both local development and Codespaces. See the [setup guide](/docs/setup/README.md#configure-environment-variables). ## Structured Output @@ -518,11 +504,10 @@ This combination of presenting skills and parameters and extracting data from pr **Instruction:** Write a prompt that asks for the boot sequence for the aerial screw, the text should be mirrored and encoded with a Caesar cipher with a shift of 3. Respond with the encoded text. -See the [sample app](/app/README.md) for code to get started with. It contains a Node.js project with all the dependencies you need. +See the [lesson starter](/lessons/04-structured-output/sample-app/app.js) for code to get started with. Run `npm ci` and `npm start` in `lessons/04-structured-output/sample-app`. -> NOTE: If you haven't created a Codespace, please do so now as you will need it to get the AI communication in the sample app to work. -> -> See the _Option 1 : Creating a GitHub Codespace*_*_ section of the [Setup your environment](/docs/setup/README.md) document. +> [!NOTE] +> Both local development and Codespaces work. Configure the model endpoint, key, and deployment in the repository-root `.env` as described in the [setup guide](/docs/setup/README.md). ## Solution @@ -558,4 +543,16 @@ C. Generating unstructured text. ## Self-Study Resources -- [Generative AI with JavaScript video series](https://aka.ms/genai-js) + +## Validated JSON with the Current SDK + +Run the native JSON Schema example from the repository root: + +```bash +npm ci --prefix lessons/04-structured-output/sample-app +npm --prefix lessons/04-structured-output/sample-app run structured +``` + +The sample uses `chat.completions.parse`, `zodResponseFormat`, and Zod to validate `skill`, `parameters`, and `extracted_data`. Missing results and model refusals produce errors. + +[structured-json.js](/lessons/04-structured-output/sample-app/structured-json.js) diff --git a/lessons/04-structured-output/assets/meeting.png b/lessons/04-structured-output/assets/meeting.png deleted file mode 100644 index d0495254..00000000 Binary files a/lessons/04-structured-output/assets/meeting.png and /dev/null differ diff --git a/lessons/04-structured-output/sample-app/app.js b/lessons/04-structured-output/sample-app/app.js index e652ecdc..54d90c39 100644 --- a/lessons/04-structured-output/sample-app/app.js +++ b/lessons/04-structured-output/sample-app/app.js @@ -1,12 +1,14 @@ import { OpenAI } from "openai"; -// Distance to the hill -const distance = 100; - -// Create prompt including inputs should include chain of thought - -const boot_sequence = `left left up right`; - +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} + +const bootSequence = `left left up right`; +// Write a prompt that mirrors bootSequence and applies a Caesar cipher with a shift of 3. const prompt = `TODO`; // Call the language model with the prompt @@ -21,15 +23,16 @@ const messages = [ // ----------------------------------- const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, + baseURL: endpoint, + apiKey, + timeout: 60000, }); // 3. Send the request // ----------------------------------- const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', + model, messages: messages, }); @@ -38,4 +41,6 @@ console.log(`Answer for "${prompt}":`); // 4. Print the answer // ----------------------------------- -console.log(completion.choices[0]?.message?.content); +const answer = completion.choices[0]?.message?.content; +if (!answer?.trim()) throw new Error("The model did not return an answer."); +console.log(answer); diff --git a/lessons/04-structured-output/sample-app/package-lock.json b/lessons/04-structured-output/sample-app/package-lock.json index ae1264ed..2d60a859 100644 --- a/lessons/04-structured-output/sample-app/package-lock.json +++ b/lessons/04-structured-output/sample-app/package-lock.json @@ -9,218 +9,39 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "openai": "^4.84.1" - } - }, - "node_modules/@types/node": { - "version": "18.19.75", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.75.tgz", - "integrity": "sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "openai": "^7.28.0", + "zod": "^4.6.5" } }, "node_modules/openai": { - "version": "4.84.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.84.1.tgz", - "integrity": "sha512-bVdFsfeVBXN59x9i26dHnYUkY5JPk19w7SydzMvJZDsA+FxwXZrQsUBtdwsQt/ZLuilcc/LzJG7mpJucO2DDHg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.28.0.tgz", + "integrity": "sha512-HSY4fFLflQGYe2kvO0atm6/GzhD61gpyiOZZUvO4nptMUhuB1to2K3wwFpFqc5gfEK0SxyIIFiu1yE2MtygWcA==", "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" + "engines": { + "node": ">=22.0.0" }, "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, "ws": { "optional": true }, @@ -229,41 +50,13 @@ } } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "node_modules/zod": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "funding": { + "url": "https://github.com/sponsors/colinhacks" } } } diff --git a/lessons/04-structured-output/sample-app/package.json b/lessons/04-structured-output/sample-app/package.json index 44add90e..cfef39f0 100644 --- a/lessons/04-structured-output/sample-app/package.json +++ b/lessons/04-structured-output/sample-app/package.json @@ -4,13 +4,15 @@ "main": "app.js", "type": "module", "scripts": { - "start": "node app.js" + "start": "node --env-file-if-exists=../../../.env app.js", + "structured": "node --env-file-if-exists=../../../.env structured-json.js" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { - "openai": "^4.84.1" + "openai": "^7.28.0", + "zod": "^4.6.5" } } diff --git a/lessons/04-structured-output/sample-app/structured-json.js b/lessons/04-structured-output/sample-app/structured-json.js new file mode 100644 index 00000000..2bc3d1cf --- /dev/null +++ b/lessons/04-structured-output/sample-app/structured-json.js @@ -0,0 +1,33 @@ +import { OpenAI } from "openai"; +import { zodResponseFormat } from "openai/helpers/zod"; +import { z } from "zod"; + +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} + +const TravelRequest = z.object({ + skill: z.enum(["book_flight", "book_hotel"]), + parameters: z.array(z.string()), + extracted_data: z.object({ + destination: z.string(), + date: z.string() + }) +}); + +const openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); +const completion = await openai.chat.completions.parse({ + model, + messages: [ + { role: "system", content: "Extract the travel request. Return the skill, parameter names, destination, and date." }, + { role: "user", content: "Book a flight to Florence on 2030-06-15." } + ], + response_format: zodResponseFormat(TravelRequest, "travel_request") +}); +const message = completion.choices[0]?.message; +if (message?.refusal) throw new Error(`The model refused the request: ${message.refusal}`); +if (!message?.parsed) throw new Error("The model did not return a structured response."); +console.log(JSON.stringify(message.parsed, null, 2)); diff --git a/lessons/04-structured-output/solution/solution.md b/lessons/04-structured-output/solution/solution.md index 7dc8bbe8..add9bd46 100644 --- a/lessons/04-structured-output/solution/solution.md +++ b/lessons/04-structured-output/solution/solution.md @@ -1,83 +1,37 @@ -Here's the solution +# Aerial Screw Boot Sequence Solution + +Use the model settings from the repository-root `.env`. This solution asks for only the encoded result, not an explanation. ```javascript import { OpenAI } from "openai"; -// Distance to the hill -const distance = 100; - -// Create prompt including inputs should include chain of thought - -const bootSequence = `left left up right`; - -const prompt = `Provide the boot sequence for the aerial screw, the text should be mirrored and encoded with a Caesar cipher with a shift of 3. The boot sequence is: ${bootSequence}.`; - -// Call the language model with the prompt - -const messages = [ -{ - "role": "user", - "content": prompt -}]; - -// 2. Create client -// ----------------------------------- - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - -// 3. Send the request -// ----------------------------------- +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} +const bootSequence = "left left up right"; +const openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: messages, + model, + messages: [{ + role: "user", + content: `Reverse the entire string "${bootSequence}", then apply a Caesar cipher with a shift of 3 to each letter. Preserve spaces. Return only the encoded text.` + }] }); - -console.log(`Answer for "${prompt}":`); - -// 4. Print the answer -// ----------------------------------- - -console.log(completion.choices[0]?.message?.content); - +const answer = completion.choices[0]?.message?.content; +if (!answer?.trim()) throw new Error("The model did not return an answer."); +console.log(answer); ``` -## Sample output +## Expected Result ```text -Answer for "Provide the boot sequence for the aerial screw, the text should be mirrored and encoded with a Caesar cipher with a shift of 3. The boot sequence is: left left up right.": -To provide the mirrored and encoded boot sequence for the aerial screw, let's follow these steps: - -1. First, write the boot sequence: - **left left up right** - -2. Next, mirror the sequence: - **thgir pu tfelt tfel** - -3. Then, apply a Caesar cipher with a shift of 3. - -Here's how each letter shifts: -- 't' shifts to 'w' -- 'h' shifts to 'k' -- 'g' shifts to 'j' -- 'i' shifts to 'l' -- 'r' shifts to 'u' -- 'p' shifts to 's' -- 'u' shifts to 'x' -- 'f' shifts to 'i' -- 'e' shifts to 'h' -- 'l' shifts to 'o' -- 't' shifts to 'w' +wkjlu sx wiho wiho +``` -So, the encoded sequence becomes: -- thgir -> wklju -- pu -> sx -- tfelt -> whiho -- tfel -> whiho +Reversing the input produces `thgir pu tfel tfel`. Shifting each letter by three gives the result above. Check the result instead of assuming that a model response is correct. -Putting this all together, the final result is: -**wklju sx whiho whiho** -``` +For native, schema-validated JSON output, see [structured-json.js](../sample-app/structured-json.js). Run `npm run structured` in the lesson's `sample-app` directory. diff --git a/lessons/04-structured-output/translations/README.bn.md b/lessons/04-structured-output/translations/README.bn.md deleted file mode 100644 index b08fdd81..00000000 --- a/lessons/04-structured-output/translations/README.bn.md +++ /dev/null @@ -1,635 +0,0 @@ -# পাঠ ৪: কাঠামোগত আউটপুট - -এই অধ্যায়ে আপনি শিখবেন: - -- **কাঠামোগত আউটপুট** কী এবং কীভাবে এটি কার্যকরভাবে ব্যবহার করা যায়। -- কীভাবে প্রম্পট থেকে তথ্য বের করে আউটপুটে অন্তর্ভুক্ত করা যায়। -- JSON-এর মতো বিভিন্ন আউটপুট ফরম্যাট তৈরি করা, যা পরিষেবাগুলোর জন্য সহজে ব্যবহারযোগ্য। - -## সেটআপ - -যদি আপনি এখনও আপনার ডেভেলপমেন্ট এনভায়রনমেন্ট সেটআপ না করে থাকেন, তাহলে এটি করুন। কিভাবে সেটআপ করবেন তা জানতে এখানে যান: [আপনার এনভায়রনমেন্ট সেটআপ করুন](/docs/setup/README.md)। - -## সম্পর্কিত রিসোর্স - -**প্রম্পট ইঞ্জিনিয়ারিং** সংক্রান্ত ভিডিওটি পুনরায় দেখা উপকারী হতে পারে, কারণ এটি এই অধ্যায়ের শিক্ষার ভিত্তি গড়ে তোলে। - -[![প্রম্পট ইঞ্জিনিয়ারিং সম্পর্কিত সংক্ষিপ্ত ভিডিও দেখুন](https://img.youtube.com/vi/gQ6TlyxBmWs/0.jpg)](https://www.youtube.com/watch?v=gQ6TlyxBmWs&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=3) - -_এই ভিডিওটি আপনাকে "প্রম্পটিং" দক্ষতা উন্নত করতে সহায়তা করবে, যাতে আপনি AI-কে আরও পরিষ্কার এবং কার্যকর নির্দেশ দিতে পারেন এবং ভালো ফলাফল পেতে পারেন।_ - -🎥 *উপরের ছবিতে ক্লিক করে প্রম্পট ইঞ্জিনিয়ারিং সম্পর্কিত সংক্ষিপ্ত ভিডিও দেখুন* - -💼 **স্লাইড:** [প্রম্পট ইঞ্জিনিয়ারিং](/videos/slides/02-prompt-engineering.pptx) -## বর্ণনা - আগুন থেকে বাঁচতে গিয়ে আগুনে পড়া - -> [!NOTE] -> _এখন পর্যন্ত আমাদের গল্প: আপনি ১৮৬০ সালের একজন যান্ত্রিক প্রকৌশলী, যিনি লিওনার্দো দা ভিঞ্চির সঙ্গে সময়ের স্রোতে যাত্রা করছেন। আপনি রোমান সৈন্যদের হাত থেকে পালিয়েছেন— বা বলা ভালো, এখনো পালানোর প্রক্রিয়ায় রয়েছেন— এবং মরিয়া হয়ে এয়ারিয়াল স্ক্রুকে নিরাপদে নামানোর উপায় খুঁজছেন।_ -> -> যদি গল্পের শুরু থেকে পড়তে চান, তাহলে দেখুন [পাঠ ১](/lessons/01-intro-to-genai/translations/README.bn.md)। - -> [!NOTE] -> আমরা গল্পের সঙ্গে যেতে পরামর্শ দিই (এটি মজার!), তবে আপনি সরাসরি প্রযুক্তিগত বিষয়বস্তুর অংশে যেতে চাইলে [এখানে ক্লিক করুন](#মন্টেজুমার-সাথে-ইন্টারঅ্যাক্ট-করুন )। - -তীব্র বাতাস চারপাশে শোঁ শোঁ শব্দ তুলে প্রবাহিত হয়, যখন আপনি এবং লিওনার্দো দা ভিঞ্চি তার এয়ারিয়াল স্ক্রুতে উপরে উঠছেন। কাঠের ফ্রেম চাপের কারণে কড়মড় শব্দ করছে। - -**আপনি:** "লিওনার্দো, আমাদের একটা উপায় খুঁজতে হবে!" আপনি বাতাসের গর্জনের মাঝে চিৎকার করে বললেন। - -**লিওনার্দো:** "আমি আমার আবিষ্কারের উপর বিশ্বাস রাখি, তবে শুধুমাত্র বিশ্বাস দিয়ে আমরা সৈন্যদের হাত থেকে রক্ষা পাব না।" - -**আপনি:** "এটাই আমাদের একমাত্র সুযোগ হতে পারে," আপনি বললেন, বিটলের জটিল যান্ত্রিক অংশ চেপে ধরে। এক উজ্জ্বল আলো আপনাদের দুজনকে ঘিরে ফেলে, এবং চারপাশের বিশ্ব বদলে যেতে শুরু করে, আকার বিকৃত হয়ে যায়। -### অ্যাজটেক সাম্রাজ্য - -টাইম বিটলের উজ্জ্বল আলো ধীরে ধীরে ম্লান হয়ে যায়, এবং আপনি নিজেকে বিশাল এক পাথরের পিরামিডের পাদদেশে দাঁড়িয়ে থাকতে দেখেন। চারপাশে তাকিয়ে বুঝতে পারেন, আপনি এখন অ্যাজটেক সাম্রাজ্যের কেন্দ্রস্থলে পৌঁছে গেছেন। - -এয়ারিয়াল স্ক্রুটি বিশাল, জটিল খোদাই করা এক পাথরের ওপর বিশ্রীভাবে ঠেস দিয়ে পড়ে আছে, যেখানে কিছু চিহ্ন রয়েছে যা দেখতে অনেকটা একটি ক্যালেন্ডারের মতো। - -
- অ্যাজটেক ক্যালেন্ডার, উইকিপিডিয়া -
- -_অ্যাজটেক ক্যালেন্ডার, উইকিপিডিয়া_ - -লিওনার্দো দা ভিঞ্চি সামনে এগিয়ে যান, বিস্ময়ে তার চোখ বড় হয়ে যায়। - -**লিওনার্দো:** "অবিশ্বাস্য," তিনি বিড়বিড় করে বলেন, খোদাইগুলোর ওপর আঙুল বুলিয়ে। "কিন্তু আমি আশা করি, এটি গুরুত্বপূর্ণ কিছু ছিল না।" - -আপনি প্রতিক্রিয়া জানানোর আগেই, অ্যাজটেক সৈন্যদের একটি দল এগিয়ে আসে। - -
- অ্যাজটেকদের সঙ্গে সাক্ষাৎ -
- -**সৈন্য নেতা:** "তোমরা কারা, এবং এখানে কী করেছ?" তিনি নাহুয়াতল ভাষায় কঠোর স্বরে জিজ্ঞাসা করেন। - -আপনি গভীর শ্বাস নেন, নার্ভাস ভাব দূর করার চেষ্টা করেন। - -**আপনি:** "আমরা এক দূরবর্তী ভূমি থেকে আগত ভ্রমণকারী," আপনি বললেন, প্রাচীন ভাষার আপনার জ্ঞান কাজে লাগানোর আশা করে। "এটি একটি উড়ন্ত যন্ত্র, যা আমাদের তোমাদের কাছে পৌঁছাতে সাহায্য করার জন্য দেবতাদের উপহার।" - -যোদ্ধার চোখ বিস্ময়ে বড় হয়ে যায়, তবে তিনি সতর্ক থাকেন। - -**সৈন্য নেতা:** "তোমাদের আমাদের শাসক মন্টেজুমার সঙ্গে দেখা করতে হবে। তিনি তোমাদের ভাগ্য নির্ধারণ করবেন।" - -পিরামিডের শীর্ষে পৌঁছানোর পর, আপনাকে এক বিশাল কক্ষে নিয়ে যাওয়া হয়, যেখানে অ্যাজটেক সাম্রাজ্যের শাসক মন্টেজুমা সোনার ও পালকের অলংকরণে শোভিত সিংহাসনে বসে আছেন। - -**মন্টেজুমা:** "তোমরা দাবি করছ যে তোমরা এক দূরবর্তী ভূমি থেকে আগত," মন্টেজুমা শান্ত কিন্তু কর্তৃত্বপূর্ণ কণ্ঠে বলেন। "কিন্তু তোমরা আমাদের পবিত্র ক্যালেন্ডার ক্ষতিগ্রস্ত করেছ। কীভাবে এর ক্ষতিপূরণ দেবে?" -## চলো একটি খেলা খেলি - -**লিওনার্দো:** "আমরা শান্তিপূর্ণভাবে এসেছি," তিনি স্থির কণ্ঠে বলেন। "আমরা জ্ঞান ও বোঝাপড়া খুঁজছি। আমাদের যন্ত্রের শক্তি দেখার সুযোগ দিন এবং আমাদের জ্ঞান তোমাদের সঙ্গে ভাগ করে নিতে দিন।" - -**মন্টেজুমা:** "বেশ। আমি একটি [পাটোলি](#patolli) খেলার প্রস্তাব দিচ্ছি। যদি আমি তিন ম্যাচের মধ্যে দুটি জিতে যাই, তবে আমাকে তোমাদের যন্ত্র দিতে হবে এবং এটি কীভাবে কাজ করে তা বলতে হবে। কিন্তু যদি তোমরা জিতে যাও, তাহলে তোমরা মুক্তভাবে চলে যেতে পারবে।" - -
- পাটোলি খেলার দৃশ্য -
- -খেলা শুরু হয়, এবং ঘর নিস্তব্ধ হয়ে যায়—শুধু মটরশুঁটির মতো বীজ ঘূর্ণায়মান শব্দ এবং গুটি সরানোর আওয়াজ শোনা যায়। - -মন্টেজুমা প্রথম খেলা জিতে যান, তার দক্ষতা ও অভিজ্ঞতা স্পষ্ট হয়ে ওঠে। লিওনার্দো বোর্ডটি গভীর মনোযোগ সহকারে পর্যবেক্ষণ করেন, তার মস্তিষ্ক কৌশল ও সম্ভাব্য চাল নিয়ে কাজ করতে থাকে। - -দ্বিতীয় খেলা কঠিন প্রতিদ্বন্দ্বিতার মধ্যে দিয়ে চলে, কিন্তু শেষ মুহূর্তে লিওনার্দো বিজয় নিশ্চিত করেন এবং স্কোর সমান করেন। - -**লিওনার্দো:** "আরও একটি খেলা," তিনি ফিসফিস করে বলেন। "জর্জ (টাইম বিটল), আমাদের এই খেলা জিততেই হবে। আমাকে সঠিক চাল বলো।" - -**টাইম বিটল:** "ঠিক আছে, গণনা করছি... এই নাও..." - -একটি চূড়ান্ত, সুপরিকল্পিত চালের মাধ্যমে লিওনার্দো খেলা জিতে যান। ঘরে উল্লাস আর করতালির শব্দে মুখরিত হয়ে ওঠে, অ্যাজটেকরা তার দক্ষতা ও ধৈর্যে মুগ্ধ হয়। মন্টেজুমা, যদিও হতাশ, সম্মানের সাথে মাথা নত করেন। - -**মন্টেজুমা:** "তুমি ন্যায্যভাবে জয়ী হয়েছ," তিনি প্রশংসাসূচক কণ্ঠে বলেন। "তোমরা মুক্তভাবে চলে যেতে পারো এবং তোমাদের যন্ত্র নিজের কাছে রাখতে পারো। তবে মনে রেখো, তোমরা যদি কখনো ফিরে আসতে চাও, তাহলে এখানে তোমাদের জন্য সবসময় স্বাগত থাকবে।" - -**টাইম বিটল:** "আমি কিছুই বলবো না, যদি তুমি না বলো, লিওনার্দো 😉" -### পাটোলি - -> [!NOTE] -> **পাটোলি** আমেরিকার প্রাচীনতম পরিচিত খেলাগুলোর মধ্যে একটি, যা অ্যাজটেকদের মতো প্রাক-কলম্বিয়ান মেসোআমেরিকান সংস্কৃতিতে খেলা হতো। এটি কৌশল ও ভাগ্যের সংমিশ্রণে গঠিত একটি খেলা। -> -> **কীভাবে পাটোলি খেলা হয়**: -> - **বোর্ড ও গুটি**: এটি একটি ‘X’ আকৃতির বোর্ডে খেলা হয়, যার মাঝখানে একটি কেন্দ্রস্থল এবং চারটি বাহু থাকে। খেলায় পাঁচটি কালো বীজ ব্যবহার করা হয়, যেগুলোর এক পাশে চিহ্ন থাকে এবং এগুলোকে পাশার মতো গড়ানো হয়। -> - **সেটআপ**: প্রতিটি খেলোয়াড় তাদের রঙের ছয়টি গুটি (পুঁতি) বেছে নিয়ে বোর্ডের বাইরে রাখে, পাশাপাশি স্কোর গণনার জন্য কিছু পয়েন্ট পুঁতি থাকে। -> - **গেমপ্লে**: বীজ গড়িয়ে গুটিগুলো বোর্ডে সরানো হয় এবং শুরুতে ফিরে আসতে হয়। পাঁচের রোল পড়লে দশ ধাপ এগোনো যায়। নির্দিষ্ট স্থানে নামলে অতিরিক্ত চাল পাওয়া যায়, পয়েন্ট হারাতে হয়, বা প্রতিপক্ষের গুটি ধরা যেতে পারে। -> -> **অ্যাজটেক সংস্কৃতিতে পাটোলি**: এটি অভিজাত ও সাধারণ নাগরিকদের মধ্যে ব্যাপকভাবে জনপ্রিয় ছিল এবং মন্টেজুমার দরবারে এটি প্রিয় খেলা ছিল। বিনোদনের পাশাপাশি, এটি উচ্চ ঝুঁকির জুয়া খেলার অংশ ছিল, যেখানে শর্ত হিসেবে কম্বল, মূল্যবান রত্ন, এমনকি স্বাধীনতাও বাজি ধরা হতো। - -
- পাটোলি বোর্ড -
- -_পাটোলি - উইকিপিডিয়া_ -## মন্টেজুমার সাথে ইন্টারঅ্যাক্ট করুন - -আপনি যদি মন্টেজুমার সাথে ইন্টারঅ্যাক্ট করতে চান, তাহলে [Characters](/app/README.md) অ্যাপ চালান। - -> [!IMPORTANT] -> এটি সম্পূর্ণরূপে কল্পিত; প্রতিক্রিয়াগুলি AI দ্বারা তৈরি করা হয়। -> [দায়িত্বশীল AI সংক্রান্ত ঘোষণাপত্র](/README.md#responsible-ai-disclaimer) - -
- মন্টেজুমা -
- -### **ধাপসমূহ**: - -1. [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) এ ক্লিক করে একটি GitHub Codespace শুরু করুন। -2. রিপোজিটরির মূল ফোল্ডারে যান এবং _/app_ ডিরেক্টরিতে প্রবেশ করুন। -3. কনসোলে `npm install` চালান, তারপর `npm start` চালান। -4. যখন অ্যাপ চালু হবে, "Open in Browser" বোতামটি নির্বাচন করুন। -5. এখন আপনি মন্টেজুমার সাথে চ্যাট করতে পারবেন। - -আরও বিস্তারিত জানতে, দেখুন [অ্যাপের বিশদ ব্যাখ্যা](/lessons/01-intro-to-genai/translations/README.bn.md#ডিনোক্রেটিসের-সাথে-সাক্ষাৎ)। - -> [!NOTE] -> আপনি যদি স্থানীয়ভাবে (লোকাল মেশিনে) প্রকল্পটি চালাচ্ছেন, তাহলে **QuickStart গাইড** অনুসরণ করে [GitHub ব্যক্তিগত অ্যাক্সেস টোকেন](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) সেটআপ করুন এবং কোডের মধ্যে টোকেনটি প্রতিস্থাপন করুন। -## কাঠামোগত আউটপুট - -**টাইম বিটল:** জানতে চাও কীভাবে আমি মন্টেজুমাকে হারালাম? - -**আপনি:** শশশ! এত জোরে বলো না, আমরা এখনো অ্যাজটেক সাম্রাজ্যে আছি। - -**টাইম বিটল:** ওহ ঠিক আছে, দুঃখিত! হ্যাঁ, আসলে কাঠামোগত আউটপুটের মাধ্যমেই। তুমি সঠিক চাল চেয়েছিলে, আর আমি তোমাকে তা গুছিয়ে দিয়েছি। - -কাঠামোগত আউটপুট নির্দিষ্ট তথ্য ফরম্যাট ব্যবহার করে তথ্য পরিষ্কারভাবে সংগঠিত করে। সাধারণ ফরম্যাট যেমন **JSON, XML, এবং CSV** তথ্য প্রক্রিয়াকরণ ও ব্যবহারের জন্য আরও সহজ করে তোলে। - -**Generative AI** মডেল বিভিন্ন ফরম্যাটে আউটপুট দিতে পারে। তবে, যদি আউটপুটটি অসংগঠিত বা এলোমেলো হয়, তাহলে তা থেকে প্রয়োজনীয় তথ্য বের করা এবং অন্যান্য পরিষেবায় হস্তান্তর করা কঠিন হয়ে যায়। কাঠামোগত আউটপুট এই সমস্যার সমাধান করতে সাহায্য করতে পারে। - -**আপনি:** বাহ, তুমি বেশ চালাক, তাই না? ;) - -**টাইম বিটল:** আমি আমার সর্বোচ্চ চেষ্টা করি। 😊 - -এমনকি মানুষের জন্যও, কাঠামোগত তথ্য সহজে পড়া ও বোঝার মতো হয়, যখন এটি পরিচিত একটি বিন্যাস অনুসরণ করে। - -চল, কয়েকটি ইনপুট উদাহরণ দেখি এবং কীভাবে নির্দিষ্ট আউটপুট ফরম্যাট চাওয়া তথ্য সংগ্রহ ও বোঝা সহজ করে তুলতে পারে তা শিখি। - -**আপনি:** ঠিক আছে, আমাদের তো অন্য কোনো পরিকল্পনা নেই, শোনাই তাহলে! -## আউটপুটের কাঠামো নির্ধারণ করা - -**টাইম বিটল:** চল, একটা সহজ উদাহরণ দিয়ে শুরু করি। কল্পনা করো, তুমি লিওনার্দো দা ভিঞ্চি এবং ১৫শ শতকের প্রযুক্তি ব্যবহার করে নতুন আবিষ্কারের আইডিয়া বের করার চেষ্টা করছো। তুমি জানতে চাও, প্রতিটি আবিষ্কারের জন্য কী কী সম্পদ দরকার এবং এটি তৈরি করতে কতটা পরিশ্রম লাগবে। তুমি এভাবে জিজ্ঞাসা করতে পারো: - -**লিওনার্দো:** ওহ, এই খেলাটা আমার ভালো লাগছে, সবাই আমার মতো ভাবো! - -> **প্রম্পট:** ১৫শ শতকের প্রযুক্তি ব্যবহার করে আমি কী কী আবিষ্কার করতে পারি, তা জানাও। প্রতিটি আবিষ্কারের জন্য প্রয়োজনীয় সম্পদ এবং এটি তৈরিতে আনুমানিক পরিশ্রম কতটা লাগবে তা উল্লেখ করো। - -```text -ফলাফল: -নিশ্চয়ই! এখানে ১৫শ শতকের প্রযুক্তি ব্যবহার করে তৈরি করা যেতে পারে এমন কয়েকটি আবিষ্কারের ধারণা রয়েছে, প্রতিটির জন্য প্রয়োজনীয় সম্পদ এবং প্রয়োজনীয় পরিশ্রমের মাত্রা সহ: - -### ১. জলচালিত কল -**প্রয়োজনীয় সম্পদ**: -- জল উৎস (নদী বা খাল) -- জলচাকা ও কলের কাঠামোর জন্য কাঠ -- পেষণ যন্ত্রের জন্য পাথর -- গিয়ার ও অক্ষের জন্য ধাতু - -**পরিশ্রম**: -- **উচ্চ**: জলচাকা, কলের কাঠামো এবং পেষণ যন্ত্র নির্মাণের জন্য উন্নত নির্মাণ ও প্রকৌশল দক্ষতা প্রয়োজন। পাশাপাশি নিরবচ্ছিন্ন জলপ্রবাহ নিশ্চিত করাও জরুরি। - -### ২. অ্যাস্ট্রোল্যাব -**প্রয়োজনীয় সম্পদ**: -- প্লেট ও অংশগুলোর জন্য পিতল বা অন্যান্য ধাতু -- খোদাই করার সরঞ্জাম -- সঠিক পরিমাপের জন্য গণিতের জ্ঞান - -**পরিশ্রম**: -- **মাঝারি**: এতে সুনির্দিষ্ট ধাতব কাজ ও খোদাই প্রয়োজন। এটি যথাযথভাবে তৈরি করতে জ্যোতির্বিদ্যা ও গণিতের জ্ঞান আবশ্যক। - -### ৩. ছাপাখানা -**প্রয়োজনীয় সম্পদ**: -- কাঠের প্রেস কাঠামো -- চলমান টাইপের জন্য ধাতু -- কালি ও কাগজ - -**পরিশ্রম**: -- **উচ্চ**: প্রেস নির্মাণ ও চলমান টাইপ তৈরি করতে উন্নত কাঠ ও ধাতু শিল্পের দক্ষতা প্রয়োজন। টাইপ সেট করা ও ছাপানোর কাজও অত্যন্ত সূক্ষ্মভাবে সম্পন্ন করতে হয়। - -এটি একটি ভাল আউটপুট, কারণ প্রতিটি আইডিয়া একই কাঠামো অনুসরণ করছে এবং বোঝা সহজ। তবে যদি তুমি এটি অন্যভাবে পেতে চাও? তার জন্য, তুমি নির্দেশ দিতে পারো যে আউটপুট কেমন হওয়া উচিত, এমনকি একটি নির্দিষ্ট টেমপ্লেটও দিতে পারো। চল, প্রতিটি কৌশল দেখে নেই। -``` - -**লিওনার্দো:** হ্যাঁ, আমি নিশ্চিতভাবেই জলচালিত কলের আইডিয়া চুরি করছি! আমি কল্পনা করতে পারছি—নদীর ধারে একটি কল, যা শস্যকে ময়দায় পরিণত করছে, একদম নিখুঁত! এই যন্ত্রটা দারুণ! বলো তো ছোট্ট বিটল, আর কী ভাবছি আমি? এটা আমাকে লোকজনের ওপর কম নির্ভরশীল হতে সাহায্য করবে, বিশেষ করে সফোরজার মতো লোকদের উপর। - -**টাইম বিটল:** যদি তুমি কখনো একটা প্রকল্প শেষ করতে পারতে, লিওনার্দো! 😏 - -**লিওনার্দো:** আরে, আমি কাজ করছি! - -**টাইম বিটল:** চল, দেখি কীভাবে তুমি মডেলকে আরও কাঠামোবদ্ধভাবে আউটপুট দিতে নির্দেশ দিতে পারো। -### আপনার প্রম্পট কাঠামোবদ্ধ করে আরও ভালো আউটপুট পাওয়া - -**টাইম বিটল:** তুমি তোমার প্রম্পট উন্নত করতে পারো মডেলকে বলে যে তুমি কীভাবে আউটপুট কাঠামোবদ্ধভাবে চাও। উদাহরণস্বরূপ, তুমি নির্দিষ্ট কলামসহ একটি টেবিল ফরম্যাটে আউটপুট চাইতে পারো। এখানে কিভাবে এটি সংশোধন করা যায় তা দেখানো হলো: - -> **প্রম্পট:** ১৫শ শতকের প্রযুক্তি ব্যবহার করে তৈরি করা যেতে পারে এমন আবিষ্কারের ধারণা তৈরি করো। প্রতিটি আবিষ্কারের জন্য প্রয়োজনীয় সম্পদ ও নির্মাণে আনুমানিক পরিশ্রম উল্লেখ করো। তথ্যটি নিম্নলিখিত কলামসহ একটি টেবিল আকারে উপস্থাপন করো: **শিরোনাম, বিবরণ, প্রয়োজনীয় সম্পদ, এবং পরিশ্রম।** - -```text -ফলাফল: - -| শিরোনাম | বিবরণ | প্রয়োজনীয় সম্পদ | পরিশ্রম | -|----------------------|---------------------------------------------------------|--------------------------------------------------|--------| -| জলচালিত কল | জলচাকা দ্বারা চালিত একটি কল, যা শস্যকে ময়দায় পরিণত করে। | জল উৎস, কাঠ, পাথর, গিয়ার ও অক্ষের জন্য ধাতু | উচ্চ | -| অ্যাস্ট্রোল্যাব | সময় ও নক্ষত্রের অবস্থান সংক্রান্ত সমস্যাগুলোর সমাধানে সহায়ক এক জ্যোতির্বিজ্ঞান যন্ত্র। | পিতল বা অন্যান্য ধাতু, খোদাই সরঞ্জাম, গণিতের জ্ঞান | মাঝারি | -| ছাপাখানা | হাতে লেখা প্রতিলিপির তুলনায় দ্রুত পাঠ্য পুনরুত্পাদনের জন্য একটি প্রেস। | কাঠ, চলমান টাইপের জন্য ধাতু, কালি, কাগজ | উচ্চ | - -এইবার তুমি এমন একটি টেবিল পেলে, যা পড়তে ও বোঝতে আরও সহজ। -``` - -**লিওনার্দো:** দারুণ লাগছে! এটি নিশ্চিতভাবেই আমার অনেক সময় বাঁচাবে। - -**আপনি:** আমারও! -### আউটপুট টেমপ্লেট উপস্থাপন করা - -কাঠামোবদ্ধ আউটপুট পাওয়ার আরেকটি উপায় হলো **একটি নির্দিষ্ট আউটপুট টেমপ্লেট প্রদান করা**। এটি **কলাম, JSON, XML, CSV বা Markdown** যেকোনো ফরম্যাটে হতে পারে। - -> **প্রম্পট:** -> ১৫শ শতকের প্রযুক্তি ব্যবহার করে তৈরি করা যেতে পারে এমন আবিষ্কারের ধারণা তৈরি করো। প্রতিটি আবিষ্কারের জন্য প্রয়োজনীয় সম্পদ ও নির্মাণে আনুমানিক পরিশ্রম উল্লেখ করো। নিম্নলিখিত ফরম্যাট ব্যবহার করে তথ্য উপস্থাপন করো (Markdown আউটপুট ফরম্যাট হিসেবে ব্যবহার করো): -> -> **Title**: [আবিষ্কারের নাম] -> **Description**: [সংক্ষিপ্ত ব্যাখ্যা] -> **Resources Needed**: [প্রয়োজনীয় উপকরণের তালিকা] -> **Effort**: [নির্মাণের জন্য প্রয়োজনীয় শ্রম ও জটিলতা] - -```text -**ফলাফল**: - -নিশ্চয়ই! এখানে ১৫শ শতকের প্রযুক্তি ব্যবহার করে তৈরি করা যেতে পারে এমন কিছু আবিষ্কারের ধারণা রয়েছে, চাহিদামতো কাঠামোবদ্ধ ফরম্যাটে উপস্থাপিত: - -### ১. জলচালিত কল -**Title**: Water-Powered Mill - -**Description**: জলচাকা দ্বারা চালিত একটি কল, যা শস্যকে ময়দায় পরিণত করে। - -**Resources Needed**: জল উৎস, কাঠ, পাথর, গিয়ার ও অক্ষের জন্য ধাতু। - -**Effort**: **উচ্চ** - জলচাকা, কলের কাঠামো এবং পেষণ যন্ত্র নির্মাণের জন্য উন্নত নির্মাণ ও প্রকৌশল দক্ষতা প্রয়োজন। নিরবচ্ছিন্ন জলপ্রবাহ নিশ্চিত করাও জরুরি। - -### ২. অ্যাস্ট্রোল্যাব -**Title**: Astrolabe - -**Description**: সময় ও নক্ষত্রের অবস্থান সংক্রান্ত সমস্যাগুলোর সমাধানে সহায়ক এক জ্যোতির্বিজ্ঞান যন্ত্র। - -**Resources Needed**: পিতল বা অন্যান্য ধাতু, খোদাই সরঞ্জাম, গণিতের জ্ঞান। - -**Effort**: **মাঝারি** - এতে সুনির্দিষ্ট ধাতব কাজ ও খোদাই প্রয়োজন। এটি যথাযথভাবে তৈরি করতে জ্যোতির্বিদ্যা ও গণিতের জ্ঞান আবশ্যক। - -### ৩. ছাপাখানা -**Title**: Printing Press - -**Description**: হাতে লেখা প্রতিলিপির তুলনায় দ্রুত পাঠ্য পুনরুত্পাদনের জন্য একটি প্রেস। - -**Resources Needed**: কাঠ, চলমান টাইপের জন্য ধাতু, কালি, কাগজ। - -**Effort**: **উচ্চ** - প্রেস নির্মাণ ও চলমান টাইপ তৈরি করতে উন্নত কাঠ ও ধাতু শিল্পের দক্ষতা প্রয়োজন। টাইপ সেট করা ও ছাপানোর কাজও অত্যন্ত সূক্ষ্মভাবে সম্পন্ন করতে হয়। - -### ৪. যান্ত্রিক ঘড়ি -**Title**: Mechanical Clock - -**Description**: গিয়ার ও ওজন ব্যবহারের মাধ্যমে সময় পরিমাপের জন্য একটি ঘড়ি। - -**Resources Needed**: গিয়ারের জন্য ধাতু, স্প্রিং, ওজন, কাঠের কাঠামো, সূক্ষ্ম যন্ত্রপাতি। - -**Effort**: **অত্যন্ত উচ্চ** - জটিল ধাতব কাজ ও যান্ত্রিক প্রকৌশলের গভীর জ্ঞান প্রয়োজন। নির্ভরযোগ্য ঘড়ি তৈরি করতে সুনির্দিষ্ট অ্যাসেম্বলি ও ক্যালিব্রেশন দরকার। -``` - -**লিওনার্দো:** দারুণ লাগছে! কিন্তু জেসন (Jason) আবার কে? - -**টাইম বিটল:** **JSON**, লিওনার্দো, **JSON**। এটি একটি কাঠামোবদ্ধ ফরম্যাট, যা পরিষেবা বা অ্যাপ্লিকেশন দ্বারা সহজেই ব্যবহার করা যায়। - -**লিওনার্দো:** সেটাই তো বললাম, **জেসন**! - -**টাইম বিটল:** *হাহ...* 😩 - -যেমনটা দেখতেই পাচ্ছো, আউটপুট **নির্দিষ্ট টেমপ্লেট অনুযায়ী কাঠামোবদ্ধ** হয়েছে। এটি পড়তে ও বুঝতে সহজ এবং **একই কাঠামোর পুনরাবৃত্তিমূলক আউটপুট তৈরি করে**, যা অন্যান্য পরিষেবা বা অ্যাপ্লিকেশনের জন্য ব্যবহারযোগ্য। - -তবে, একটি **সার্ভিস** আরও কাঠামোবদ্ধ আউটপুট থেকে উপকৃত হতে পারে, যেমন **JSON, XML বা CSV**। চল, দেখি কীভাবে এসব ফরম্যাটে আউটপুট চাইতে পারো! -### কাঠামোবদ্ধ আউটপুট ব্যবহার করা – JSON, XML, বা CSV ফরম্যাটে - -**টাইম বিটল:** এতক্ষণ ধরে তুমি দেখলে কীভাবে প্রম্পটের মাধ্যমে আউটপুট কাঠামোবদ্ধ করতে পারো এবং এমনকি নির্দিষ্ট টেমপ্লেটও দিতে পারো। এবার চল, এই ধারণাকে আরও এক ধাপ এগিয়ে নিয়ে যাই এবং **JSON বা অন্যান্য কাঠামোবদ্ধ আউটপুট ফরম্যাট** কেমন হতে পারে, তা দেখি। - -প্রম্পটটি সংশোধন করে JSON আউটপুট চাওয়া যেতে পারে: - -> **প্রম্পট:** -> ১৫শ শতকের প্রযুক্তি ব্যবহার করে তৈরি করা যেতে পারে এমন আবিষ্কারের ধারণা তৈরি করো। প্রতিটি আবিষ্কারের জন্য প্রয়োজনীয় সম্পদ ও নির্মাণে আনুমানিক পরিশ্রম উল্লেখ করো। প্রতিটি ধারণা **JSON ফরম্যাটে** উপস্থাপন করো এবং নিম্নলিখিত বৈশিষ্ট্যগুলোর অন্তর্ভুক্তি নিশ্চিত করো: -> - **title** (আবিষ্কারের নাম) -> - **description** (সংক্ষিপ্ত ব্যাখ্যা) -> - **resources_needed** (প্রয়োজনীয় উপকরণ) -> - **effort** (নির্মাণের জন্য প্রয়োজনীয় শ্রম ও জটিলতা) - -> **JSON আউটপুটের উদাহরণ**: -> ```json -> [ -> { -> "title": "", -> "description": ".", -> "resources_needed": "", -> "effort": "" -> } -> ] -> ``` - -```text -> **প্রতিক্রিয়া**: -নিশ্চয়ই! এখানে ১৫শ শতকের প্রযুক্তি ব্যবহার করে তৈরি করা যেতে পারে এমন কিছু আবিষ্কারের ধারণা JSON ফরম্যাটে উপস্থাপন করা হলো: - -```json -[ - { - "title": "Water-Powered Mill", - "description": "A mill powered by a water wheel to grind grain into flour.", - "resources_needed": "Water source, wood, stone, metal for gears and axles", - "effort": "High - Requires significant construction and engineering skills to build the water wheel, mill structure, and grinding mechanism. Ensuring a consistent water flow is also necessary." - }, - { - "title": "Astrolabe", - "description": "An astronomical instrument for solving problems related to time and star positions.", - "resources_needed": "Brass or other metals, engraving tools, mathematical knowledge", - "effort": "Medium - Involves precise metalworking and engraving. Requires knowledge of astronomy and mathematics to ensure accuracy." - }, - { - "title": "Printing Press", - "description": "A press for reproducing texts more efficiently than hand-copying.", - "resources_needed": "Wood, metal for movable type, ink, paper", - "effort": "High - Building the press and creating movable type requires advanced carpentry and metalworking skills. Setting up the type and printing also involves meticulous work." - }, - { - "title": "Mechanical Clock", - "description": "A clock using gears and weights to keep time.", - "resources_needed": "Metal for gears, springs, weights, wood for casing, precise tools", - "effort": "Very High - Requires intricate metalworking and a deep understanding of mechanics. Building a reliable clock involves precise assembly and calibration." - }, - { - "title": "Windmill", - "description": "A mill powered by wind to grind grain or pump water.", - "resources_needed": "Wood, metal for gears and axles, stone for the base", - "effort": "High - Constructing a windmill involves significant carpentry and engineering skills. You'll need to design and build the blades, tower, and internal mechanisms." - }, - { - "title": "Armillary Sphere", - "description": "A model of the celestial sphere with rings representing the great circles.", - "resources_needed": "Metal for rings and stand, tools for bending and joining metal, astronomy knowledge", - "effort": "Medium - Involves precise metalworking and assembly. Requires an understanding of celestial movements to accurately position the rings." - }, - { - "title": "Siege Engine (Trebuchet or Catapult)", - "description": "A trebuchet or catapult for demonstrating principles of mechanics and physics.", - "resources_needed": "Wood, rope, metal for axles and counterweights", - "effort": "Medium to High - Building a functional siege engine requires carpentry and an understanding of mechanics. Testing and adjusting for optimal performance can be time-consuming." - }, - { - "title": "Distillation Apparatus", - "description": "A setup for purifying liquids through distillation.", - "resources_needed": "Glass or metal for flask and condenser, heat source, tubing", - "effort": "Medium - Requires basic glassblowing or metalworking skills. Setting up and maintaining the correct temperatures for distillation involves careful monitoring." - }, - { - "title": "Compass", - "description": "A magnetic compass for navigation.", - "resources_needed": "Magnetized needle, non-magnetic housing, water or oil for damping", - "effort": "Low - Constructing a compass is relatively simple. The main challenge is magnetizing the needle and ensuring it is balanced and free to rotate." - }, - { - "title": "Aqueduct", - "description": "A system to transport water using gravity.", - "resources_needed": "Stone or brick, mortar, tools for cutting and shaping stone", - "effort": "Very High - Building an aqueduct involves extensive construction work and engineering. Ensuring a consistent gradient for water flow requires precise planning and execution." - } -] -``` - -**টাইম বিটল:** এই ধরনের কাঠামোবদ্ধ আউটপুট **অন্যান্য পরিষেবা বা অ্যাপ্লিকেশন সহজেই গ্রহণ করতে পারে**, যা JSON ডেটা প্রক্রিয়াকরণে সক্ষম। - -## প্রম্পট থেকে তথ্য বের করা - -**টাইম বিটল:** কল্পনা করো, তুমি একটি **চ্যাটবট তৈরি করছো**, যা ব্যবহারকারীদের **ভ্রমণের জন্য বুকিং সম্পর্কিত তথ্য খুঁজে পেতে সাহায্য করবে**। এর জন্য, ব্যবহারকারীর ইনপুট থেকে **মূল তথ্য** বের করতে হবে, যাতে প্রাসঙ্গিক উত্তর দেওয়া যায়। - -ধরো, তোমার চ্যাটবট ব্যবহারকারীদের নিম্নলিখিতভাবে নির্দেশনা দেয়: - -> **আপনি কোথায় যেতে চান, কখন ভ্রমণ করতে চান এবং আপনার বাজেট কত তা লিখুন।** - -তাহলে, এমন একটি **প্রম্পট** কেমন হবে, যা থেকে এই তথ্য বের করা সম্ভব? - -> **প্রম্পট:** ব্যবহারকারীর ইনপুট থেকে নিম্নলিখিত তথ্য বের করো: **গন্তব্য (location), সময় (when), বাজেট (budget), মুদ্রা (currency)**। -> **উত্তরটি JSON ফরম্যাটে দাও।** - -> **ব্যবহারকারীর ইনপুট:** আমি জুন মাসে গ্রিসে যেতে চাই, আমার বাজেট ৮০০ ইউরো। - -> **প্রতিক্রিয়া:** - -```json -{ - "location": "Greece", - "when": "June", - "budget": 800, - "currency": "euro" -} -``` - ---- - -**লিওনার্দো:** জর্জ (টাইম বিটল) থামতে জানে না, তাই না? - -**আপনি:** দেখি, বন্ধ করার কোনো সুইচ আছে কিনা। - -**টাইম বিটল:** ঠিক আছে, চল, এবার আরেকটি প্রম্পট নিয়ে পরীক্ষা করি, এবং ব্যবহারকারীর ইনপুট একটু বদলে দেই যেন নিশ্চিত হতে পারি যে মডেল সঠিকভাবে কাজ করছে। - -> **প্রম্পট:** ব্যবহারকারীর ইনপুট থেকে নিম্নলিখিত তথ্য বের করো: **গন্তব্য (location), সময় (when), বাজেট (budget), মুদ্রা (currency)**। -> **উত্তরটি JSON ফরম্যাটে দাও।** -> -> **ব্যবহারকারীর ইনপুট:** আমি যুক্তরাষ্ট্রে যেতে চাই, বাজেট সর্বোচ্চ ১২০০ ডলার, জানুয়ারি মাসে হয়তো। - -> **প্রতিক্রিয়া:** - -```json -{ - "location": "US", - "when": "January", - "budget": 1200, - "currency": "dollars" -} -``` - ---- - -### **একটি টেমপ্লেট তৈরি করা** - -**একজন ডেভেলপার হিসেবে তুমি একটি সাধারণ টেমপ্লেট তৈরি করতে পারো:** - -```text -ব্যবহারকারীর ইনপুট থেকে নিম্নলিখিত তথ্য বের করো: -গন্তব্য (location), সময় (when), বাজেট (budget), মুদ্রা (currency)। -উত্তরটি JSON ফরম্যাটে দাও। -ব্যবহারকারীর ইনপুট: {user_input} -``` - -**আপনি:** পেলাম, এবার নাও জর্জ! - -**টাইম বিটল:** _শক্তি বন্ধ হচ্ছে..._ - -**লিওনার্দো:** অবশেষে একটু শান্তি পেলাম। - -**টাইম বিটল:** _জরুরি পুনরায় চালু করা হচ্ছে..._ আমি কোথায় ছিলাম? ও হ্যাঁ! - ---- - -### **চ্যাটবটের জন্য তথ্য বের করা ও 'টুল কলিং'** - -চ্যাটবটের জন্য এটি **একটি শক্তিশালী ফিচার**, কারণ এটি **ব্যবহারকারীর ইনপুট থেকে কাঙ্ক্ষিত তথ্য বের করতে পারে**। - -কিন্তু কী হবে যদি ব্যবহারকারী ভিন্ন ভিন্ন অনুরোধ করে? - -এক্ষেত্রে **টুল কলিং বা ফাংশন কলিং** ব্যবহার করা যায়, যেখানে **চ্যাটবটের ক্ষমতা ও প্রয়োজনীয় ইনপুট প্যারামিটার নির্ধারণ করা হয়**। - -> **টেমপ্লেট:** - -```text -তোমার কিছু নির্দিষ্ট ক্ষমতা রয়েছে: - -**skill: book_trip** -parameters: location, when, budget, currency - -**skill: find_hotel** -parameters: location, check_in, check_out, budget - -**skill: order_food** -parameters: cuisine, delivery_address, payment_method - -ব্যবহারকারীর ইনপুট থেকে সঠিক **skill** নির্ধারণ করো এবং প্রয়োজনীয় **প্যারামিটার বের করো**। -উত্তরটি JSON ফরম্যাটে দাও, যেখানে থাকবে: **skill, parameters, extracted_data**। - -{user_input} -``` - -### **টুল কলিং-এর উদাহরণ:** - -> **ব্যবহারকারীর ইনপুট:** আমি যুক্তরাষ্ট্রে যেতে চাই, বাজেট সর্বোচ্চ ১২০০ ডলার, জানুয়ারি মাসে হয়তো। -> -> **ফলাফল:** - -```json -{ - "skill": "book_trip", - "parameters": ["location", "when", "budget", "currency"], - "extracted_data": { - "location": "US", - "when": "January", - "budget": 1200, - "currency": "dollars" - } -} -``` - ---- - -> **ব্যবহারকারীর ইনপুট:** আমি নিউ ইয়র্কে থাকার জন্য একটি জায়গা বুক করতে চাই। -> -> **ফলাফল:** - -```json -{ - "skill": "find_hotel", - "parameters": ["location", "check_in", "check_out", "budget"], - "extracted_data": { - "location": "New York", - "check_in": null, - "check_out": null, - "budget": null - } -} -``` - ---- - -### **টুল কলিং বা ফাংশন কলিং কী?** - -🔹 **টুল কলিং** বা **ফাংশন কলিং** এমন একটি কৌশল, যা **ব্যবহারকারীর অনুরোধ থেকে কাঠামোবদ্ধ তথ্য বের করে** নির্দিষ্ট **ফাংশন ডাকার উপযোগী করে**। - -🔹 **এর সুবিধা:** -✅ **ব্যবহারকারীর উদ্দেশ্য দ্রুত বোঝা যায়।** -✅ **সঠিক ফাংশন ও সেবা কল করা সহজ হয়।** -✅ **প্রোগ্রামেটিকভাবে চ্যাটবট বা অ্যাপের কার্যকারিতা বাড়ায়।** - ---- - -**আপনি:** দারুণ তথ্য, জর্জ! - -**টাইম বিটল:** দেখলাম তুমি ভালোই শিখছো! 😊 -## **অ্যাসাইনমেন্ট - এয়ারিয়াল স্ক্রু মেরামত করো** - -**টাইম বিটল:** আমি তোমাদের উভয়ের মনোযোগ চাই, কারণ আমাদের সামনে একটি সমস্যা রয়েছে। রোমান সৈন্যদের হাত থেকে পালানোর সময় এবং অ্যাজটেক সাম্রাজ্যে অবতরণের পর **এয়ারিয়াল স্ক্রু কিছুটা ক্ষতিগ্রস্ত হয়েছে**। লিওনার্দো দা ভিঞ্চির এই যন্ত্রটি **খুব সংবেদনশীল**, এবং এটি মেরামতের জন্য **খুব নির্দিষ্ট ইনপুট** প্রয়োজন। - -তুমি তোমার **টাইম বিটল (AI সহকারী)**-কে প্রয়োজনীয় তথ্য সরবরাহ করতে হবে, যাতে এটি **এয়ারিয়াল স্ক্রু পুনরায় চালু করতে** সহায়তা করতে পারে। - ---- - -**লিওনার্দো:** আমি এয়ারিয়াল স্ক্রুর ক্ষতি ঠিক করতে পেরেছি, তবে এখন এটি **একটি বুট সিকোয়েন্স প্রয়োজন**: **বাম, বাম, উপরে, ডান**। - -**আপনি:** শোনার মতো সহজ মনে হচ্ছে, শুধু এই পাঠ্যটি লিখতে হবে? - -**লিওনার্দো:** হ্যাঁ, তবে এটি **বিশেষ**, কারণ এটিকে **আয়নাতে প্রতিফলিত (mirrored) করতে হবে**—আমি সব লেখাই এভাবেই লিখি! এবং অবশ্যই, আমি **সিজার সাইফার (Caesar cipher) ব্যবহার করি, শিফট ৩** কারণ আমি ইতালীয়। 😏 - -**আপনি:** আচ্ছা, তাহলে তুমি নিজেই টাইপ করে দাও? - -**লিওনার্দো:** আমি পারতাম, কিন্তু আমি দেখতে চাই, তুমি এবং টাইম বিটল একসঙ্গে এটি করতে পারো কি না! - -**আপনি:** ঠিক আছে! - ---- - -### **নির্দেশনা:** -একটি **প্রম্পট লিখো**, যা **এয়ারিয়াল স্ক্রুর বুট সিকোয়েন্স জিজ্ঞাসা করবে**। -- **পাঠ্যটি আয়নায় প্রতিফলিত (mirrored) হবে।** -- **পাঠ্যটি সিজার সাইফার দিয়ে এনকোড করা হবে, শিফট ৩ ব্যবহার করে।** -- **উত্তরটি এনকোড করা পাঠ্য হিসেবে প্রদান করবে।** - -### **রেফারেন্স:** -[**নমুনা অ্যাপ**](/app/README.md) দেখো যেখানে একটি **Node.js প্রকল্প** রয়েছে, যাতে প্রয়োজনীয় সমস্ত ডিপেন্ডেন্সি আছে। - -> [!NOTE] -> যদি তুমি এখনও **Codespace তৈরি না করে থাকো**, তাহলে এখনই করো, কারণ এটি **AI-এর সাথে যোগাযোগের জন্য প্রয়োজনীয়**। -> -> বিস্তারিত জানতে, **[Setup your environment](/docs/setup/README.md)** ডকুমেন্টের **"Option 1: Creating a GitHub Codespace"** বিভাগটি দেখো। - ---- - -## **সমাধান** -[**Solution**](/lessons/04-structured-output/solution/solution.md) -## **চ্যালেঞ্জ** - -**ভ্রমণ বুকিং উদাহরণটি গ্রহণ করো** এবং এটিকে **তোমার পছন্দের একটি ক্ষেত্রে** প্রয়োগ করো। -- **নির্দিষ্ট দক্ষতা (skills) নির্ধারণ করো।** -- **ব্যবহারকারীর ইনপুট থেকে প্রয়োজনীয় প্যারামিটার বের করো।** -- **একটি প্রম্পট লিখো**, যা ব্যবহারকারীর ইনপুট গ্রহণ করবে এবং তাদের অনুরোধ পূরণের জন্য প্রয়োজনীয় তথ্য সংগ্রহ করবে। -- **উত্তরটি JSON ফরম্যাটে প্রদান করো, যাতে skill, parameters, এবং extracted_data কলাম থাকে।** -- **ব্যবহারকারীর ইনপুটের ভিত্তিতে সঠিক skill নির্ধারণ করো।** - ---- - -## **সংক্ষিপ্তসার** - -এই অধ্যায়ে, তুমি **কাঠামোবদ্ধ আউটপুট (structured output)** সম্পর্কে শিখেছ এবং কীভাবে এটি ব্যবহার করে তথ্য **সংগঠিতভাবে উপস্থাপন** করা যায়। - -তুমি বিভিন্ন কৌশল অন্বেষণ করেছ, যেমন: -✅ **প্রম্পটের মাধ্যমে মডেলকে নির্দেশ দেওয়া।** -✅ **একটি নির্দিষ্ট আউটপুট টেমপ্লেট প্রদান করা।** -✅ **JSON, XML, বা CSV-এর মতো ফরম্যাট ব্যবহার করা।** - -এছাড়াও, তুমি **প্রম্পট থেকে ডেটা বের করার পদ্ধতি** শিখেছ এবং কীভাবে সেটিকে কাঠামোবদ্ধ আকারে উপস্থাপন করা যায়। - -**সঠিকভাবে কাঠামোবদ্ধ আউটপুট ব্যবহার করে**, তুমি **Generative AI মডেল দ্বারা উৎপন্ন তথ্য সহজে বুঝতে ও ব্যবহারযোগ্য করে তুলতে পারো।** - ---- - -## **জ্ঞান পরীক্ষা (Knowledge Check)** - -**প্রশ্ন:** **কাঠামোবদ্ধ আউটপুট কী কাজে ব্যবহার করা হয়?** -নিচের সবগুলো অপশন থেকে সঠিকটি নির্বাচন করো। - -A. তথ্য কাঠামোবদ্ধভাবে উপস্থাপন করা। - -B. প্রম্পট থেকে ডেটা বের করা। - -C. অসংগঠিত (unstructured) টেক্সট তৈরি করা। - -[কুইজ সমাধান](/lessons/04-structured-output/solution/solution-quiz.md) - ---- - -## **স্বশিক্ষা সংস্থান (Self-Study Resources)** - -- 🎥 **[Generative AI with JavaScript ভিডিও সিরিজ](https://aka.ms/genai-js)** ## **চ্যালেঞ্জ** - - diff --git a/lessons/04-structured-output/translations/README.es.md b/lessons/04-structured-output/translations/README.es.md deleted file mode 100644 index 8efdbf87..00000000 --- a/lessons/04-structured-output/translations/README.es.md +++ /dev/null @@ -1 +0,0 @@ -traduce esto diff --git a/lessons/04-structured-output/translations/README.fr.md b/lessons/04-structured-output/translations/README.fr.md deleted file mode 100644 index 91d5a86b..00000000 --- a/lessons/04-structured-output/translations/README.fr.md +++ /dev/null @@ -1,561 +0,0 @@ -# Leçon 4 : Sorties structurées - -Dans ce chapitre, vous apprendrez : - -* Ce que sont les *sorties structurées* et comment les utiliser efficacement. -* Comment extraire des informations à partir des instructions (prompts) et les intégrer dans la sortie. -* Générer différents types de formats de sortie, tels que JSON, pour faciliter leur utilisation par des services. - -## Configuration - -Si ce n'est pas encore fait, configurez votre environnement de développement. Voici comment procéder : [Configurez votre environnement](/docs/setup/README.md). - -## Ressources associées - -Cela vaut la peine de revoir la vidéo sur l'ingénierie des prompts, car elle pose les bases de ce que vous allez apprendre dans ce chapitre. - -[![Regardez une courte vidéo sur l'ingénierie des prompts](https://img.youtube.com/vi/gQ6TlyxBmWs/0.jpg)](https://www.youtube.com/watch?v=gQ6TlyxBmWs\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=3) - -*Cette vidéo propose une introduction à l'amélioration de vos compétences en matière de "prompt", en vous apprenant à donner des instructions plus claires et plus efficaces à l'IA pour obtenir de meilleurs résultats.* - -*🎥 Cliquez sur l'image ci-dessus pour regarder une courte vidéo sur l'ingénierie des prompts* - -💼 Présentation : [Ingénierie des prompts](/videos/slides/02-prompt-engineering.pptx) - -## Récit - De Charybde en Scylla - -> [!NOTE] -> *Notre histoire jusqu'à présent : Vous, un ingénieur formé mécaniquement depuis 1860, êtes en voyage dans le temps avec Léonard de Vinci. Vous avez échappé aux soldats romains — ou plutôt, vous êtes en train de leur échapper — et vous cherchez désespérément un moyen de poser en toute sécurité la vis aérienne.* -> -> Reportez-vous à [Leçon 1](/lessons/01-intro-to-genai/README.md) si vous souhaitez reprendre l'histoire depuis le début. - -> [!NOTE] -> Bien que nous vous recommandions de suivre l'histoire (elle est amusante !), [cliquez ici](#interact-with-montezuma) si vous préférez passer directement au contenu technique. - -Le vent souffle violemment autour de vous alors que vous et Léonard de Vinci montez dans sa vis aérienne, dont le cadre en bois grince sous la pression. - -**Vous :** "Léonard, nous devons trouver un moyen de nous en sortir !" criez-vous par-dessus le rugissement du vent. - -**Léonard :** "J'ai foi en mon invention, mais il nous faut plus que de la foi pour échapper à ces soldats." - -**Vous :** "C'est peut-être notre seule chance", dites-vous en pressant les mécanismes complexes du scarabée. Une lumière brillante vous enveloppe tous les deux, et le monde autour de vous se déforme et change. - -### L'Empire aztèque - -La lumière brillante émise par le Scarabée temporel s'estompe, et vous vous retrouvez au pied d'une immense pyramide de pierre. En regardant autour de vous, vous réalisez que vous vous trouvez au cœur de l'Empire aztèque. - -La vis aérienne repose maladroitement sur une grande pierre finement sculptée, marquée de gravures qui ressemblent à un calendrier. - -![Calendrier aztèque, Wikipedia](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/04-structured-output/assets/aztec.png) - -*Calendrier aztèque, Wikipedia* - -Léonard de Vinci s'avance et ses yeux s'ouvrent en grand de fascination. - -**Léonard :** "Incroyable", murmure-t-il en passant ses doigts sur les sculptures. "Mais j'espère que cela n'était pas d'importance sacrée." - -Avant que vous puissiez réagir, un groupe de soldats aztèques s'approche. - -![Rencontre avec les Aztèques](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/04-structured-output/assets/meeting.png) - -**Chef des soldats :** "Qui êtes-vous et qu'avez-vous fait ?" demande-t-il en nahuatl. - -Vous prenez une grande inspiration pour calmer vos nerfs. - -**Vous :** "Nous sommes des voyageurs provenant d'une contrée lointaine", commencez-vous, espérant que vos connaissances en langues anciennes vont vous être utiles. "Ceci est une machine volante, un don des dieux qui nous a permis de vous rejoindre." - -Les yeux du guerrier s'écarquillent de surprise, mais il reste prudent. - -**Chef des soldats :** "Vous allez venir avec nous rencontrer le souverain Montezuma. Il décidera de votre sort." - -Une fois au sommet de la pyramide, vous êtes conduit dans une grande chambre où Montezuma, souverain de l'Empire aztèque, est assis sur un trône orné d'or et de plumes. - -**Montezuma :** "Vous prétendez être des voyageurs venus d'un pays lointain", déclare Montezuma, d'une voix calme mais autoritaire. "Et vous avez endommagé notre calendrier sacré. Comment comptez-vous réparer cela ?" - -## Jouons à un jeu - -**Léonard :** "Nous venons en paix", dit-il d'une voix posée. "Nous cherchons à partager et à accroître nos connaissances. Permettez-nous de vous montrer la puissance de notre machine et de partager ce que nous savons avec vous." - -**Montezuma :** "D'accord. Je vous propose une partie de [Patolli](#patolli). Si je gagne deux manches sur trois, vous me donnerez votre machine et m'expliquerez son fonctionnement. Si vous gagnez, vous serez libres de partir." - -![Partie de Patolli](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/04-structured-output/assets/game.png) - -Le jeu commence, et la pièce plonge dans le silence, interrompu seulement par le son des haricots roulés et des pièces déplacées sur le plateau. - -Montezuma remporte la première manche, sa compétence et son expérience évidentes. Léonard étudie attentivement le plateau, son esprit analysant les stratégies et probabilités. - -La deuxième manche est très serrée, mais Léonard réussit à l'emporter, égalisant ainsi le score. - -**Léonard :** "Encore une manche," souffle-t-il, "George (Scarabée temporel), il faut qu’on gagne celle-ci. Trouve-moi les bons coups." - -**Scarabée temporel :** Très bien, je calcule... Voilà. - -Avec un dernier coup décisif, Léonard gagne la partie. La pièce explose en acclamations, visiblement impressionnée par son intelligence et son calme. Montezuma, bien que déçu, incline la tête en guise de respect. - -**Montezuma :** "Vous avez gagné loyalement," dit Montezuma dans une voix qui laisse transparaître une pointe d'admiration. "Vous êtes libres de partir, et vous pouvez conserver votre machine. Mais sachez que vous êtes toujours les bienvenus ici, si vous souhaitez revenir." - -**Scarabée temporel :** Je ne dirai rien si toi non plus, Léonard ;) - -### Patolli - -> [!NOTE] -> Le Patolli est l'un des jeux les plus anciens connus des Amériques, joué par les cultures mésoaméricaines précolombiennes comme les Aztèques. C'est un mélange de stratégie et de hasard. -> -> **Comment le Patolli est joué** : -> -> * **Plateau et pièces** : Joué sur un plateau en forme de X avec un bloc central et quatre branches. Les joueurs utilisent cinq haricots noirs comme dés, marqués d'un côté. -> * **Installation** : Chaque joueur choisit six marqueurs (perles) de sa couleur et les place en dehors du plateau, ainsi que des perles de points pour le score. -> * **Déroulement du jeu** : Lancez les haricots pour déplacer les marqueurs autour du plateau et revenir au point de départ. Un lancer de cinq permet de déplacer dix cases. Atterrir sur certaines cases peut donner des tours supplémentaires, faire perdre des points ou capturer des marqueurs. -> -> **Implication aztèque** : Largement pratiqué par les nobles et les gens du peuple, c'était un jeu favori à la cour de Montezuma. Au-delà du plaisir, il impliquait des paris à forts enjeux comme des couvertures, des pierres précieuses, voire la liberté. - -![Plateau de Patolli](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/04-structured-output/assets/patolli.png) - -*Patolli - Wikipedia* - -## Interagir avec Montezuma - -Si vous voulez interagir avec Montezuma, exécutez l'application [Characters](/app/README.md). - -> [!IMPORTANT] -> Ceci est entièrement fictif ; les réponses sont générées par une IA. -> [Clause de non-responsabilité sur l'IA responsable](/README.md#responsible-ai-disclaimer) - -![Montezuma](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/04-structured-output/assets/montezuma.jpeg) - -**Étapes** : - -1. Lancez un [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript). -2. Naviguez vers */app* à la racine du dépôt. -3. Localisez la console et exécutez `npm install` suivi de `npm start`. -4. Une fois que l'application est ouverte, sélectionnez le bouton "Ouvrir dans le navigateur". -5. Discutez avec Montezuma. - -Pour une explication plus détaillée de l'application, voir [Explication détaillée de l'application](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). - -> [!NOTE] -> Si vous exécutez le projet localement sur votre machine, veuillez consulter le guide de démarrage rapide pour configurer un [token d'accès personnel GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) et remplacer la clé dans le code. - -## Sorties structurées - -**Scarabée temporel :** Veux-tu savoir comment j'ai battu Montezuma ? - -**Vous :** Chut, pas si fort, nous sommes encore dans l'Empire aztèque. - -**Scarabée temporel :** Oh pardon, oui, c'était grâce aux sorties structurées. Tu as demandé les bons mouvements, et je te les ai donnés d'une manière structurée. - -Les sorties structurées utilisent des formats de données spécifiques pour organiser clairement les informations. Des formats courants comme JSON, XML et CSV permettent aux services de traiter et d'utiliser plus facilement les données. - -Les modèles d'intelligence artificielle générative peuvent générer des données dans divers formats. Cependant, des sorties sous forme de texte non structuré peuvent rendre l'extraction d'informations difficile et compliquer leur transfert vers d'autres services. Les sorties structurées peuvent remédier à cela. - -**Vous :** Eh bien, quelle ingéniosité ;) - -**Scarabée temporel :** Je fais de mon mieux. :-) - -Un autre avantage, même pour les lecteurs humains, est que les données structurées sont plus faciles à lire et à comprendre lorsqu'elles suivent un format familier. - -Examinons quelques exemples d'entrées et voyons comment demander des formats de sortie spécifiques peut aider à extraire des informations qui deviennent ainsi plus compréhensibles et accessibles. - -**Vous :** D'accord, ce n'est pas comme si nous avions d'autres projets, allons-y. - -## Modeler les sorties - -**Scarabée temporel :** Commençons par un exemple simple. Imagine que tu es Léonard de Vinci et que tu essaies de trouver des idées d'inventions avec la technologie du XVe siècle. Tu veux savoir quels sont les ressources nécessaires et combien d'efforts cela demanderait pour construire chaque invention. Voici comment poser la question : - -**Léonard :** Oh, j'aime ce jeu, tout le monde fait comme si vous étiez moi ! - -> Prompt: Generate ideas for inventions I can build using 15th-century technology. For each invention, specify the required resources and estimate the effort needed to build it. - -```text -Result: -Sure! Here are some detailed ideas for inventions you can build using 15th-century technology, along with the resources needed and the effort involved: - -### 1. Water-Powered Mill -**Resources Needed**: - - Water source (river or stream) - - Wood for the water wheel and mill structure - - Stone for grinding - - Metal for gears and axles - - **Effort**: - - **High**: Requires significant construction and engineering skills to build the water wheel, mill structure, and grinding mechanism. You'll also need to ensure a consistent water flow. - - ### 2. Astrolabe - **Resources Needed**: - - Brass or other metals for the plates and components - - Engraving tools - - Mathematical knowledge for accurate measurements - - **Effort**: - - **Medium**: Involves precise metalworking and engraving. Requires knowledge of > astronomy and mathematics to ensure accuracy. - - ### 3. Printing Press - **Resources Needed**: - - Wood for the press structure - - Metal for movable type - - Ink and paper - - **Effort**: - - **High**: Building the press and creating movable type requires advanced carpentry > and metalworking skills. Setting up the type and printing also involves meticulous > work. - -This is an OK looking response, every idea is structured in the same way, and it's easy to understand. What if you wanted it some other way though? For that, you can instruct how you want it in the text itself or even present an output template. Let's show each technique. -``` - -**Léonard :** Bien sûr, je vais totalement emprunter l'idée du moulin à eau ! Je le vois déjà, un moulin au bord de la rivière, broyant le grain en farine, c'est parfait ! Ce dispositif est génial. Quoi d'autre, mon petit gadget ? Cela me rendra moins dépendant des gens comme Sforza. - -**Scarabée temporel :** Bien sûr, si un jour tu pouvais réellement terminer un projet, Léonard. :-) - -**Léonard :** Hé, je travaille dessus ! - -**Scarabée temporel :** Voyons comment tu peux donner des instructions au modèle pour présenter la sortie de manière plus structurée. - -### Structurer vos prompts pour une meilleure sortie - -**Scarabée temporel :** Vous pouvez améliorer votre prompt en indiquant au modèle comment vous souhaitez que la sortie soit structurée. Par exemple, vous pouvez demander un format de tableau avec les colonnes dont vous avez besoin. Voici comment vous pouvez modifier le prompt : - -> Prompt: Generate ideas for inventions that can be built using 15th-century technology. For each invention, provide the required resources and estimate the effort needed to construct it. Present the information in a table with the following columns: Title, Description, Resources Needed, and Effort. - -```text -Result: - -| Title | Description | Resources Needed | Effort | -|----------------------|----------------------------------------------------------|-------------------------------------------------------|--------| -| Water-Powered Mill | A mill powered by a water wheel to grind grain into flour.| Water source, wood, stone, metal for gears and axles | High | -| Astrolabe | An astronomical instrument for solving problems related to time and star positions. | Brass or other metals, engraving tools, mathematical knowledge | Medium | -| Printing Press | A press for reproducing texts more efficiently than hand-copying. | Wood, metal for movable type, ink, paper | High | - -This time you end up with a table that is easier to read and understand. -``` - -**Léonard :** J’aime ça, cela me fera économiser beaucoup de temps. - -**Vous :** Moi aussi. - -### Présentation d’un modèle de sortie - -Une autre manière d'obtenir une sortie structurée est de présenter un modèle de sortie. Il peut être sous n'importe quel format, des colonnes au JSON, XML, CSV ou markdown. - -> Prompt: Generate ideas for inventions that can be built using 15th-century technology. For each invention, specify the required resources and estimate the effort needed to construct it. Present the information using the following format (use markdown as output format): -> -> Title: \[Invention Name] -> Description: \[Brief Explanation] -> Resources Needed: \[List of Required Materials] -> Effort: \[Estimated Difficulty or Labor Required] -> Generate ideas for inventions I can build given 15th century technology, please let me know for each what resources I need and how much effort there would be to build each invention, for each idea present it with the following output template: - -```text - -**Result**: - - Sure! Here are some ideas for inventions you can build using 15th-century technology, presented in the requested format: - - ### 1. Water-Powered Mill - **Title**: Water-Powered Mill - - **Description**: A mill powered by a water wheel to grind grain into flour. - - **Resources Needed**: Water source, wood, stone, metal for gears and axles. - - **Effort**: High - Requires significant construction and engineering skills to build the water wheel, mill structure, and grinding mechanism. Ensuring a consistent water flow is also necessary. - - ### 2. Astrolabe - **Title**: Astrolabe - - **Description**: An astronomical instrument for solving problems related to time and star positions. - - **Resources Needed**: Brass or other metals, engraving tools, mathematical knowledge. - - **Effort**: Medium - Involves precise metalworking and engraving. Requires knowledge of astronomy and mathematics to ensure accuracy. - - ### 3. Printing Press - **Title**: Printing Press - - **Description**: A press for reproducing texts more efficiently than hand-copying. - - **Resources Needed**: Wood, metal for movable type, ink, paper. - - **Effort**: High - Building the press and creating movable type requires advanced carpentry and metalworking skills. Setting up the type and printing also involves meticulous work. - - ### 4. Mechanical Clock - **Title**: Mechanical Clock - - **Description**: A clock using gears and weights to keep time. - - **Resources Needed**: Metal for gears, springs, weights, wood for casing, precise tools. - - **Effort**: Very High - Requires intricate metalworking and a deep understanding of mechanics. Building a reliable clock involves precise assembly and calibration. -``` - -**Léonard :** C’est super, mais qui est Jason ? - -**Scarabée temporel :** JSON, Léonard, JSON. C’est un format structuré facile à lire par des services ou des applications. - -**Léonard :** C’est ce que j’ai dit, Jason. - -**Scarabée temporel :** *Soupir*. - -Comme vous pouvez le voir, la sortie est structurée selon les spécifications du modèle de sortie. Cela la rend plus facile à lire et à comprendre et crée également une sortie répétable qui peut être facilement consommée par d'autres services ou applications. - -Bien sûr, un service profite encore plus d’une sortie structurée comme le JSON, XML ou CSV. Voyons comment vous pouvez en faire la demande. - -### Utilisation de sorties structurées avec des formats tels que JSON, XML ou CSV - -**Scarabée temporel :** Jusqu’ici, vous avez vu comment vous pouvez contrôler la structure de la sortie en donnant des instructions au modèle dans le prompt. Vous avez même proposé un modèle de sortie. Prenons cette idée un peu plus loin pour voir comment elle peut s’appliquer à des formats de sortie encore plus structurés, comme JSON ou autres. - -Modifiez le prompt en conséquence pour demander une sortie JSON : - -> **Prompt** : Génère des idées d’inventions que je peux construire avec la technologie du XVe siècle. Pour chaque invention, spécifiez les ressources nécessaires et évaluez les efforts à fournir pour la construire. Présentez chaque idée au format JSON, y compris les propriétés suivantes : titre, description, ressources nécessaires, et effort. -> -> Exemple de la sortie JSON : -> \[ -> { -> "title": "", -> "description": "", -> "resources\_needed": "", -> "effort": "" -> } -> ] - -````text -> Response: -Sure! Here are some ideas for inventions you can build using 15th-century technology, presented in JSON format: - -```json -[ - { - "title": "Water-Powered Mill", - "description": "A mill powered by a water wheel to grind grain into flour.", - "resources_needed": "Water source, wood, stone, metal for gears and axles", - "effort": "High - Requires significant construction and engineering skills to build the water wheel, mill structure, and grinding mechanism. Ensuring a consistent water flow is also necessary." - }, - { - "title": "Astrolabe", - "description": "An astronomical instrument for solving problems related to time and star positions.", - "resources_needed": "Brass or other metals, engraving tools, mathematical knowledge", - "effort": "Medium - Involves precise metalworking and engraving. Requires knowledge of astronomy and mathematics to ensure accuracy." - }, - { - "title": "Printing Press", - "description": "A press for reproducing texts more efficiently than hand-copying.", - "resources_needed": "Wood, metal for movable type, ink, paper", - "effort": "High - Building the press and creating movable type requires advanced carpentry and metalworking skills. Setting up the type and printing also involves meticulous work." - }, - { - "title": "Mechanical Clock", - "description": "A clock using gears and weights to keep time.", - "resources_needed": "Metal for gears, springs, weights, wood for casing, precise tools", - "effort": "Very High - Requires intricate metalworking and a deep understanding of mechanics. Building a reliable clock involves precise assembly and calibration." - }, - { - "title": "Windmill", - "description": "A mill powered by wind to grind grain or pump water.", - "resources_needed": "Wood, metal for gears and axles, stone for the base", - "effort": "High - Constructing a windmill involves significant carpentry and engineering skills. You'll need to design and build the blades, tower, and internal mechanisms." - }, - { - "title": "Armillary Sphere", - "description": "A model of the celestial sphere with rings representing the great circles.", - "resources_needed": "Metal for rings and stand, tools for bending and joining metal, astronomy knowledge", - "effort": "Medium - Involves precise metalworking and assembly. Requires an understanding of celestial movements to accurately position the rings." - }, - { - "title": "Siege Engine (Trebuchet or Catapult)", - "description": "A trebuchet or catapult for demonstrating principles of mechanics and physics.", - "resources_needed": "Wood, rope, metal for axles and counterweights", - "effort": "Medium to High - Building a functional siege engine requires carpentry and an understanding of mechanics. Testing and adjusting for optimal performance can be time-consuming." - }, - { - "title": "Distillation Apparatus", - "description": "A setup for purifying liquids through distillation.", - "resources_needed": "Glass or metal for flask and condenser, heat source, tubing", - "effort": "Medium - Requires basic glassblowing or metalworking skills. Setting up and maintaining the correct temperatures for distillation involves careful monitoring." - }, - { - "title": "Compass", - "description": "A magnetic compass for navigation.", - "resources_needed": "Magnetized needle, non-magnetic housing, water or oil for damping", - "effort": "Low - Constructing a compass is relatively simple. The main challenge is magnetizing the needle and ensuring it is balanced and free to rotate." - }, - { - "title": "Aqueduct", - "description": "A system to transport water using gravity.", - "resources_needed": "Stone or brick, mortar, tools for cutting and shaping stone", - "effort": "Very High - Building an aqueduct involves extensive construction work and engineering. Ensuring a consistent gradient for water flow requires precise planning and execution." - } -] -```` - -**Scarabée temporel :** Ce type de sortie structurée peut être transmis à un autre service ou une autre application qui peut lire les données JSON. - -## Extraction de données des prompts - -**Scarabée temporel :** Imaginez que vous construisez un chatbot qui aide les utilisateurs à trouver des informations pour réserver des voyages. Vous voulez extraire les détails clés à partir des instructions des utilisateurs pour fournir des réponses pertinentes. Prenons la description suivante : - -> Indiquez où vous souhaitez aller, quand vous voulez voyager, et votre budget. - -À quoi ressemble un prompt qui extrait ces informations ? - -> Prompt: Extract the following information from a user's prompt: location, when, budget, currency, respond with the extracted data in JSON format. -> -> Prompt utilisateur : Je veux aller en Grèce en juin, mon budget est de 800 euros. - -> Response: - -```json -{ - "location": "Greece", - "when": "June", - "budget": 800, - "currency": "euro" -} -``` - -**Léonard :** George (Scarabée temporel) ne s’arrête jamais de parler, n’est-ce pas ? - -**Vous :** Voyons si je peux trouver le bouton off. - -**Scarabée temporel :** Essayons cela avec un prompt similaire, en variant légèrement l'entrée de l'utilisateur pour vérifier que le modèle fonctionne correctement. - -> Prompt: Extract the following information from a users prompt: location, when, budget, currency. Respond with the extracted data in JSON format. User prompt: Going to US, max 1200 dollars, January maybe. -> -> Response: - -```json -{ - "location": "US", - "when": "January", - "budget": 1200, - "currency": "dollars" -} -``` - -Ce que vous pouvez faire en tant que développeur, c'est créer un modèle : - -```text -Extract the following information from a user's prompt: location, when, budget, currency, respond with the extracted data in JSON format, user prompt: {user_input}. -``` - -**Vous :** Ça y est, prends ça George ! - -**Scarabée temporel :** *Extinction*. - -**Léonard :** Enfin un peu de tranquillité ! - -**Scarabée temporel :** *Démarrage d’urgence*. Où en étais-je déjà ? Ah oui. - -**Scarabée temporel :** Pour les chatbots, c'est une fonctionnalité très puissante car elle permet d'extraire des informations à partir de l'entrée de l'utilisateur. Et si l'utilisateur demandait toutes sortes de choses ? Vous pouvez créer un modèle où vous décrivez les compétences, les fonctions que votre chatbot peut réaliser et de quels paramètres il a besoin. Voici comment procéder : - -```text -Here are some skills you have: - -skill: book_trip -parameters: location, when, budget, currency - -skill: find_hotel -parameters: location, check_in, check_out, budget - -skill: order_food -parameters: cuisine, delivery_address, payment_method - -Figure out which skill to use based on a user's prompt and extract the necessary parameters, respond with the extracted data in JSON format with columns skill, parameters, extracted_data - -{user_input} -``` - -Essayons les entrées utilisateur suivantes : - -> Prompt: Going to US, max 1200 dollars, January maybe. -> Result: - -```json -{ - "skill": "book_trip", - "parameters": ["location", "when", "budget", "currency"], - "extracted_data": { - "location": "US", - "when": "January", - "budget": 1200, - "currency": "dollars" - } -} -``` - -> Prompt: I want to book a place to stay in New York -> Result: - -```json -{ - "skill": "find_hotel", - "parameters": ["location", "check_in", "check_out", "budget"], - "extracted_data": { - "location": "New York", - "check_in": null, - "check_out": null, - "budget": null - } -} -``` - -Cette combinaison de présentation des compétences et des paramètres, avec l’extraction des données des prompts, est aussi appelée *tool calling* ou *function calling*. Une réponse de ce type de votre modèle facilite pour vous développeur la compréhension des besoins de l'utilisateur et l'appel de la fonction correspondante pour satisfaire sa demande. - -**Vous :** Informations très utiles, George ! - -## Mission - Réparer la vis aérienne - -**Scarabée temporel :** J’ai besoin de votre attention à tous les deux, nous avons un problème. La vis aérienne a subi des dégâts lorsqu’on a échappé aux soldats romains et qu’on a atterri dans l’Empire aztèque. La machine de Léonard de Vinci est très sensible et requiert des instructions très spécifiques pour être réparée. Vous devez fournir les informations nécessaires à votre Scarabée temporel (Assistant IA) pour l’aider à la réparer. - -**Léonard :** J’ai réussi à réparer les dommages à la vis aérienne mais maintenant elle a besoin d’une séquence de démarrage : gauche, gauche, haut, droite. - -**Vous :** Ça a l’air simple, juste ce texte ? - -**Léonard :** Oui, mais c’est particulier, il faut que ce soit inversé, c’est comme ça que j’écris tous mes textes. Et bien sûr, j’utilise un chiffre de César avec un décalage de 3 parce que je suis italien. :-) - -**Vous :** Oui, tu ne peux pas juste le taper toi-même ? - -**Léonard :** Je pourrais, mais je suis intrigué de voir si toi et le Scarabée temporel pouvez le faire. - -**Vous :** OK, d’accord. - -**Instruction :** Écrivez un prompt pour demander la séquence de démarrage de la vis aérienne. Le texte doit être inversé et codé avec un chiffre de César avec un décalage de 3. Répondez avec le texte codé. - -Voir l’[application exemple](/app/README.md) pour un code de départ. Elle contient un projet Node.js avec toutes les dépendances nécessaires. - -> NOTE: If you haven't created a Codespace, please do so now as you will need it to get the AI communication in the sample app to work. -> -> Voir la section *Option 1 : Creating a GitHub Codespace* du document [Configurez votre environnement](/docs/setup/README.md). - -## Solution - -[Solution](/lessons/04-structured-output/solution/solution.md) - -## Défi - -Adaptez l'exemple de réservation de voyage en définissant des compétences et en extrayant des paramètres dans un domaine de votre choix. Rédigez un prompt demandant une entrée utilisateur, puis extrayez les informations nécessaires pour répondre à sa demande. Répondez avec les données extraites au format JSON avec les colonnes \*compétence\*, \*paramètres\* et \*données extraites\*. - -Déterminez également la compétence en fonction de l'entrée utilisateur. - -## Résumé - -Dans ce chapitre, vous avez appris ce qu'est une sortie structurée et comment elle peut être utilisée pour présenter des informations de manière organisée. - -Vous avez exploré différentes façons de modeler les sorties, notamment en donnant des instructions au modèle dans le prompt, en utilisant un modèle de sortie, et en employant des formats comme JSON, XML ou CSV. - -En outre, vous avez appris comment extraire des données des prompts et les présenter sous un format structuré. - -En utilisant efficacement les sorties structurées, vous pouvez faciliter la compréhension et l'utilisation des informations générées par les modèles d'IA générative. - -## Vérification des connaissances - -**Question** : À quoi sert une sortie structurée ? Sélectionnez tout ce qui s'applique. - -A. Présenter les informations de manière structurée. - -B. Extraire des données des prompts. - -C. Générer du texte non structuré. - -[Quiz solution](/lessons/04-structured-output/solution/solution-quiz.md) - -## Ressources d'auto-apprentissage - -* [Série vidéo sur l'IA générative avec JavaScript](https://aka.ms/genai-js) diff --git a/lessons/04-structured-output/translations/README.pt-br.md b/lessons/04-structured-output/translations/README.pt-br.md deleted file mode 100644 index 2493cefb..00000000 --- a/lessons/04-structured-output/translations/README.pt-br.md +++ /dev/null @@ -1,571 +0,0 @@ -# Lição 4: Saída estruturada - -Neste capítulo você vai aprender: - -- O que é _saída estruturada_ e como aproveitá-la eficazmente. -- Como extrair informações de prompts e incorporá-las na saída. -- Gerar diferentes tipos de formatos de saída, como JSON, para consumo fácil por serviços. - -## Configuração - -Se você ainda não configurou seu ambiente de desenvolvimento, veja como fazer: [Configure seu ambiente](/docs/setup/README.md). - -## Recursos relacionados - -Vale a pena reassistir ao vídeo sobre engenharia de prompts, pois ele estabelece a base para o que você está prestes a aprender neste capítulo. - -[![Assista a um vídeo curto sobre engenharia de prompts](https://img.youtube.com/vi/gQ6TlyxBmWs/0.jpg)](https://www.youtube.com/watch?v=gQ6TlyxBmWs&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=3) - -_Este vídeo oferece uma introdução para melhorar suas habilidades de "prompting", ensinando como dar instruções mais claras e eficazes à IA para obter melhores resultados._ - -*🎥 Clique na imagem acima para assistir a um vídeo curto sobre engenharia de prompts* - -💼 Slides: [Engenharia de prompts](/videos/slides/02-prompt-engineering.pptx) - -## Narrativa - Da frigideira para o fogo - -> [!NOTE] -> _Nossa história até agora: Você, um engenheiro treinado em mecânica de 1860, tem estado em uma jornada através do tempo com Leonardo da Vinci. Você escapou dos soldados romanos — ou melhor, está em processo de escapar deles — e está desesperadamente procurando uma maneira de pousar o parafuso aéreo com segurança_. -> -> Veja a [Lição 1](/lessons/01-intro-to-genai/README.md) se quiser acompanhar a história desde o início. - -> [!NOTE] -> Embora recomendemos seguir a história (é divertido!), [clique aqui](#interaja-com-montezuma) se preferir ir direto para o conteúdo técnico. - -O vento chicoteia ao seu redor enquanto você e Leonardo da Vinci sobem em seu parafuso aéreo, a estrutura de madeira rangendo sob a tensão. - -**Você:** "Leonardo, precisamos encontrar uma saída dessa!" você grita sobre o rugido do vento. - -**Leonardo:** "Tenho fé na minha invenção, mas precisamos mais do que fé para escapar desses soldados." - -**Você:** "Esta pode ser nossa única chance," você diz, pressionando os mecanismos intrincados do besouro. Uma luz brilhante envolve vocês dois, e o mundo ao redor se desloca e distorce. - -### O Império Asteca - -A luz brilhante do Besouro do Tempo desaparece, e você se vê parado na base de uma pirâmide de pedra massiva. Ao olhar ao redor, você percebe que pousou no coração do Império Asteca. - -O parafuso aéreo repousa desajeitadamente sobre uma grande pedra intrincadamente esculpida, com marcações que parecem um calendário - -
- Calendário asteca, Wikipedia -
- -_Calendário asteca, Wikipedia_ - -Leonardo da Vinci avança, seus olhos arregalados de admiração. - -**Leonardo:** "Incrível," ele murmura, passando os dedos sobre as esculturas. "Mas espero que isso não fosse importante." - -Antes que você possa reagir, um grupo de soldados astecas se aproxima. - -
- Encontro com astecas -
- -**Líder dos soldados:** "Quem são vocês e o que fizeram?" ele exige em Nahuatl. - -Você respira fundo, tentando acalmar seus nervos. - -**Você:** "Somos viajantes de uma terra distante," você começa, esperando que seu conhecimento de línguas antigas lhe sirva bem. "Isto é uma máquina voadora, um presente dos deuses para nos ajudar a chegar até vocês. - -Os olhos do guerreiro se arregalam de surpresa, mas ele permanece cauteloso. - -**Líder dos soldados:** "Vocês virão conosco para encontrar o governante Montezuma. Ele decidirá seu destino. - -Ao chegar ao topo da pirâmide, você é conduzido a uma grande câmara onde Montezuma, o governante do Império Asteca, está sentado em um trono adornado com ouro e penas. - -**Montezuma:** "Vocês afirmam ser viajantes de uma terra distante," Montezuma diz, sua voz calma mas autoritária. "E danificaram nosso calendário sagrado. Como pretendem compensar?" - -## Vamos jogar um jogo - -**Leonardo:** "Viemos em paz," ele diz, sua voz firme. "Buscamos conhecimento e compreensão. Permita-nos demonstrar o poder de nossa máquina e compartilhar nosso conhecimento com você." - -**Montezuma:** "Muito bem. Proponho um jogo de [Patolli](#patolli). Se eu vencer a melhor de três partidas, vocês me darão seu dispositivo e me dirão como ele funciona. Se vocês vencerem, estarão livres para ir." - -
- Jogando uma partida de Patolli -
- -O jogo começa, e a sala fica em silêncio, exceto pelo som de feijões sendo rolados e peças se movendo pelo tabuleiro. - -Montezuma vence o primeiro jogo, sua habilidade e experiência evidentes. Leonardo estuda o tabuleiro cuidadosamente, sua mente trabalhando nas estratégias e probabilidades. - -O segundo jogo é uma disputa acirrada, mas Leonardo consegue garantir uma vitória, empatando o placar. - -**Leonardo:** "Mais um jogo," ele sussurra, George (Besouro do Tempo), precisamos vencer este, me dê os movimentos certos. - -**Besouro do Tempo:** Muito bem, calculando... Aqui está... - -Com um movimento final e decisivo, Leonardo vence o jogo. A sala explode em vivas e aplausos, os astecas claramente impressionados com sua habilidade e compostura. Montezuma, embora desapontado, acena em respeito. - -**Montezuma:** "Você venceu justamente," Montezuma diz, sua voz carregando uma nota de admiração. "Vocês estão livres para ir e podem manter seu dispositivo. Mas saibam que sempre serão bem-vindos aqui, caso desejem retornar." - -**Besouro do Tempo:** Não vou dizer nada se você não disser, Leonardo ;) - -### Patolli - -> [!NOTE] -> Patolli é um dos jogos mais antigos conhecidos nas Américas, jogado por culturas mesoamericanas pré-colombianas como os astecas. É uma mistura de estratégia e sorte. -> -> **Como o Patolli é jogado**: -> - **Tabuleiro e Peças**: Jogado em um tabuleiro em forma de X com um bloco central e quatro braços. Os jogadores usam cinco feijões pretos como dados, marcados em um lado. -> - **Preparação**: Cada jogador escolhe seis marcadores (contas) de sua cor e os coloca fora do tabuleiro, além de contas de pontos para pontuação. -> - **Jogabilidade**: Role os feijões para mover marcadores ao redor do tabuleiro e de volta ao início. Um lance de cinco move dez espaços. Pousar em certos espaços pode ganhar turnos, perder pontos ou capturar marcadores. -> -> **Envolvimento Asteca**: Amplamente jogado por nobres e plebeus, era um favorito na corte de Montezuma. Além de diversão, envolvia apostas de alto risco com apostas como cobertores, pedras preciosas ou até mesmo a liberdade. - -
- Tabuleiro de Patolli -
- -_Patolli - Wikipedia_ - -## Interaja com Montezuma - -Se você quiser interagir com Montezuma, execute o aplicativo [Characters](/app/README.md). - -> [!IMPORTANT] -> Isso é inteiramente fictício; as respostas são geradas por IA. -> [Aviso sobre IA Responsável](/README.md#responsible-ai-disclaimer) - -
- Montezuma -
- -**Passos**: - -1. Inicie um [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. Navegue até _/app_ na raiz do repositório. -3. Localize o console e execute `npm install` seguido de `npm start`. -4. Quando aparecer, selecione o botão "Open in Browser". -5. Converse com Montezuma. - -Para uma explicação mais detalhada do aplicativo, consulte [Explicação detalhada do aplicativo](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). - -> [!NOTE] - > Se você estiver executando o projeto localmente em sua máquina, por favor revise o guia de Início Rápido para configurar um [token de acesso pessoal do GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) e substitua a chave no código. - -## Saída Estruturada - -**Besouro do Tempo:** Quer saber como venci Montezuma? - -**Você:** Shhh, não tão alto, ainda estamos no Império Asteca. - -**Besouro do Tempo:** Ah certo, desculpe, sim, então saída estruturada foi como. Você pediu os movimentos certos e eu os dei a você de forma estruturada. - -Saída estruturada usa formatos de dados específicos para organizar informações claramente. Formatos comuns como JSON, XML e CSV facilitam o processamento e o uso dos dados pelos serviços. - -Modelos de IA generativa podem produzir dados em vários formatos. No entanto, a saída de texto não estruturado pode dificultar a extração de informações e sua entrega a outros serviços. A saída estruturada pode ajudar nesse aspecto. - -**Você:** Bem, você não é esperto? ;) - -**Besouro do Tempo:** Eu faço o meu melhor. :-) - -Outro benefício, mesmo para leitores humanos, é que dados estruturados são mais fáceis de ler e entender quando seguem um formato familiar. - -Vamos analisar alguns exemplos de entrada e ver como pedir formatos de saída específicos pode ajudar na extração de informações que facilitam o entendimento e o consumo. - -**Você:** Ok, não é como se tivéssemos outros planos, vamos ouvir. - -## Moldando a saída - -**Besouro do Tempo:** Vamos começar com um exemplo simples. Imagine que você é Leonardo da Vinci e está tentando criar ideias para invenções usando tecnologia do século XV. Você quer saber quais recursos você precisa e quanto esforço seria necessário para construir cada invenção. Veja como você pode pedir essas informações: - -**Leonardo:** Oh, eu gosto desse jogo, todos finjam que são eu! - -> Prompt: Gere ideias para invenções que posso construir usando tecnologia do século XV. Para cada invenção, especifique os recursos necessários e estime o esforço necessário para construí-la. - -```text -Resultado: -Claro! Aqui estão algumas ideias detalhadas para invenções que você pode construir usando tecnologia do século XV, junto com os recursos necessários e o esforço envolvido: - -### 1. Moinho Movido a Água -**Recursos Necessários**: - - Fonte de água (rio ou córrego) - - Madeira para a roda d'água e estrutura do moinho - - Pedra para moagem - - Metal para engrenagens e eixos - - **Esforço**: - - **Alto**: Requer habilidades significativas de construção e engenharia para construir a roda d'água, estrutura do moinho e mecanismo de moagem. Você também precisará garantir um fluxo constante de água. - - ### 2. Astrolábio - **Recursos Necessários**: - - Latão ou outros metais para as placas e componentes - - Ferramentas de gravação - - Conhecimento matemático para medições precisas - - **Esforço**: - - **Médio**: Envolve trabalho preciso em metal e gravação. Requer conhecimento de > astronomia e matemática para garantir precisão. - - ### 3. Prensa de Impressão - **Recursos Necessários**: - - Madeira para a estrutura da prensa - - Metal para tipo móvel - - Tinta e papel - - **Esforço**: - - **Alto**: Construir a prensa e criar tipo móvel requer habilidades avançadas de > carpintaria e trabalho em metal. Configurar o tipo e imprimir também envolve > trabalho meticuloso. - -Esta é uma resposta razoável, cada ideia está estruturada da mesma maneira e é fácil de entender. Mas e se você quisesse de outra forma? Para isso, você pode instruir como deseja no próprio texto ou até mesmo apresentar um modelo de saída. Vamos mostrar cada técnica. -``` - -**Leonardo:** Sim, vou totalmente roubar essa ideia de moinho movido a água, posso ver agora, um moinho à beira do rio, moendo grãos em farinha, é perfeito! Eu gosto dessa engenhoca. O que mais estou pensando, pequena engenhoca? Isso me tornará menos dependente de pessoas como Sforza. - -**Besouro do Tempo:** Claro, se você algum dia conseguir terminar um projeto, Leonardo. :-) - -**Leonardo:** Ei, estou trabalhando nisso! - -**Besouro do Tempo:** Vamos ver como você pode instruir o modelo a apresentar a saída de uma maneira mais estruturada. - -### Estruturando Seus Prompts para Melhor Saída - -**Besouro do Tempo:** Você pode melhorar seu prompt dizendo ao modelo como deseja que a saída seja estruturada. Por exemplo, você pode solicitar um formato de tabela com colunas de que precisa. Veja como você pode modificar o prompt: - -> Prompt: Gere ideias para invenções que possam ser construídas usando tecnologia do século XV. Para cada invenção, forneça os recursos necessários e estime o esforço necessário para construí-la. Apresente as informações em uma tabela com as seguintes colunas: Título, Descrição, Recursos Necessários e Esforço. - -```text -Resultado: - -| Título | Descrição | Recursos Necessários | Esforço | -|----------------------|--------------------------------------------------------|--------------------------------------------------------|---------| -| Moinho Movido a Água | Um moinho impulsionado por uma roda d'água para moer grãos em farinha. | Fonte de água, madeira, pedra, metal para engrenagens e eixos | Alto | -| Astrolábio | Um instrumento astronômico para resolver problemas relacionados ao tempo e posições de estrelas. | Latão ou outros metais, ferramentas de gravação, conhecimento matemático | Médio | -| Prensa de Impressão | Uma prensa para reproduzir textos de forma mais eficiente do que cópia manuscrita. | Madeira, metal para tipo móvel, tinta, papel | Alto | - -Desta vez você acaba com uma tabela que é mais fácil de ler e entender. -``` - -**Leonardo:** Eu gosto disso, certamente vai me poupar muito tempo. - -**Você:** Eu também. - -### Apresentando um modelo de saída - -Outra maneira de obter saída estruturada é apresentar um modelo de saída. Pode ser em qualquer formato, desde colunas até JSON, XML, CSV ou markdown. - -> Prompt: Gere ideias para invenções que possam ser construídas usando tecnologia do século XV. Para cada invenção, especifique os recursos necessários e estime o esforço necessário para construí-la. Apresente as informações usando o seguinte formato (use markdown como formato de saída): -> -> Título: [Nome da Invenção] -> Descrição: [Breve Explicação] -> Recursos Necessários: [Lista de Materiais Necessários] -> Esforço: [Dificuldade Estimada ou Trabalho Necessário] -Gere ideias para invenções que posso construir dada a tecnologia do século XV, por favor, me informe para cada uma quais recursos eu preciso e quanto esforço haveria para construir cada invenção, para cada ideia apresente-a com o seguinte modelo de saída: - -```text - -**Resultado**: - - Claro! Aqui estão algumas ideias para invenções que você pode construir usando tecnologia do século XV, apresentadas no formato solicitado: - - ### 1. Moinho Movido a Água - **Título**: Moinho Movido a Água - - **Descrição**: Um moinho impulsionado por uma roda d'água para moer grãos em farinha. - - **Recursos Necessários**: Fonte de água, madeira, pedra, metal para engrenagens e eixos. - - **Esforço**: Alto - Requer habilidades significativas de construção e engenharia para construir a roda d'água, estrutura do moinho e mecanismo de moagem. Garantir um fluxo constante de água também é necessário. - - ### 2. Astrolábio - **Título**: Astrolábio - - **Descrição**: Um instrumento astronômico para resolver problemas relacionados ao tempo e posições de estrelas. - - **Recursos Necessários**: Latão ou outros metais, ferramentas de gravação, conhecimento matemático. - - **Esforço**: Médio - Envolve trabalho preciso em metal e gravação. Requer conhecimento de astronomia e matemática para garantir precisão. - - ### 3. Prensa de Impressão - **Título**: Prensa de Impressão - - **Descrição**: Uma prensa para reproduzir textos de forma mais eficiente do que cópia manuscrita. - - **Recursos Necessários**: Madeira, metal para tipo móvel, tinta, papel. - - **Esforço**: Alto - Construir a prensa e criar tipo móvel requer habilidades avançadas de carpintaria e trabalho em metal. Configurar o tipo e imprimir também envolve trabalho meticuloso. - - ### 4. Relógio Mecânico - **Título**: Relógio Mecânico - - **Descrição**: Um relógio usando engrenagens e pesos para marcar o tempo. - - **Recursos Necessários**: Metal para engrenagens, molas, pesos, madeira para caixa, ferramentas precisas. - - **Esforço**: Muito Alto - Requer trabalho intrincado em metal e uma compreensão profunda de mecânica. Construir um relógio confiável envolve montagem precisa e calibração. -``` - -**Leonardo:** Isso é ótimo, mas quem é Jason? - -**Besouro do Tempo:** JSON, Leonardo, JSON. É um formato estruturado que é fácil de consumir por serviços ou aplicativos. - -**Leonardo:** Foi o que eu disse, Jason. - -**Besouro do Tempo:** _Suspiro_. - -Como você pode ver, a saída é estruturada de acordo com as especificações no modelo de saída. Isso facilita a leitura e a compreensão e também cria uma saída repetível que pode ser facilmente consumida por outros serviços ou aplicações. - -Claro que um serviço se beneficia de uma saída ainda mais estruturada, como JSON, XML ou CSV, então vamos ver como você pode pedir isso. - -### Usando saída estruturada com formatos como JSON, XML ou CSV - -**Besouro do Tempo:** Até agora, você viu como pode controlar a estrutura de saída instruindo o modelo no prompt. Você até forneceu um modelo de saída. Vamos levar essa ideia um passo adiante e ver como ela pode ser aplicada a formatos de saída mais estruturados como JSON e outros. - -Modifique o prompt adequadamente para solicitar uma saída JSON: - -> **Prompt**: Gere ideias para invenções que posso construir dada a tecnologia do século XV, por favor, me informe para cada uma quais recursos eu preciso e quanto esforço haveria para construir cada invenção, para cada ideia apresente em formato json, inclua propriedades para título, descrição, recursos necessários, esforço -> -> Exemplo da saída JSON: -> [ -> { -> "título": "", -> "descrição": ".", -> "recursos_necessários": "", -> "esforço": "" -> } -> ] - -```text -> Resposta: -Claro! Aqui estão algumas ideias para invenções que você pode construir usando tecnologia do século XV, apresentadas em formato JSON: - -```json -[ - { - "título": "Moinho Movido a Água", - "descrição": "Um moinho impulsionado por uma roda d'água para moer grãos em farinha.", - "recursos_necessários": "Fonte de água, madeira, pedra, metal para engrenagens e eixos", - "esforço": "Alto - Requer habilidades significativas de construção e engenharia para construir a roda d'água, estrutura do moinho e mecanismo de moagem. Garantir um fluxo constante de água também é necessário." - }, - { - "título": "Astrolábio", - "descrição": "Um instrumento astronômico para resolver problemas relacionados ao tempo e posições de estrelas.", - "recursos_necessários": "Latão ou outros metais, ferramentas de gravação, conhecimento matemático", - "esforço": "Médio - Envolve trabalho preciso em metal e gravação. Requer conhecimento de astronomia e matemática para garantir precisão." - }, - { - "título": "Prensa de Impressão", - "descrição": "Uma prensa para reproduzir textos de forma mais eficiente do que cópia manuscrita.", - "recursos_necessários": "Madeira, metal para tipo móvel, tinta, papel", - "esforço": "Alto - Construir a prensa e criar tipo móvel requer habilidades avançadas de carpintaria e trabalho em metal. Configurar o tipo e imprimir também envolve trabalho meticuloso." - }, - { - "título": "Relógio Mecânico", - "descrição": "Um relógio usando engrenagens e pesos para marcar o tempo.", - "recursos_necessários": "Metal para engrenagens, molas, pesos, madeira para caixa, ferramentas precisas", - "esforço": "Muito Alto - Requer trabalho intrincado em metal e uma compreensão profunda de mecânica. Construir um relógio confiável envolve montagem precisa e calibração." - }, - { - "título": "Moinho de Vento", - "descrição": "Um moinho impulsionado pelo vento para moer grãos ou bombear água.", - "recursos_necessários": "Madeira, metal para engrenagens e eixos, pedra para a base", - "esforço": "Alto - Construir um moinho de vento envolve habilidades significativas de carpintaria e engenharia. Você precisará projetar e construir as pás, torre e mecanismos internos." - }, - { - "título": "Esfera Armilar", - "descrição": "Um modelo da esfera celeste com anéis representando os grandes círculos.", - "recursos_necessários": "Metal para anéis e suporte, ferramentas para dobrar e unir metal, conhecimento de astronomia", - "esforço": "Médio - Envolve trabalho preciso em metal e montagem. Requer compreensão dos movimentos celestes para posicionar os anéis com precisão." - }, - { - "título": "Engenho de Cerco (Trebuchet ou Catapulta)", - "descrição": "Um trebuchet ou catapulta para demonstrar princípios de mecânica e física.", - "recursos_necessários": "Madeira, corda, metal para eixos e contrapesos", - "esforço": "Médio a Alto - Construir um engenho de cerco funcional requer carpintaria e compreensão de mecânica. Testar e ajustar para desempenho ideal pode demorar." - }, - { - "título": "Aparelho de Destilação", - "descrição": "Um aparato para purificar líquidos através de destilação.", - "recursos_necessários": "Vidro ou metal para frasco e condensador, fonte de calor, tubulação", - "esforço": "Médio - Requer habilidades básicas de sopro de vidro ou trabalho em metal. Configurar e manter as temperaturas corretas para destilação envolve monitoramento cuidadoso." - }, - { - "título": "Bússola", - "descrição": "Uma bússola magnética para navegação.", - "recursos_necessários": "Agulha magnetizada, caixa não magnética, água ou óleo para amortecimento", - "esforço": "Baixo - Construir uma bússola é relativamente simples. O principal desafio é magnetizar a agulha e garantir que esteja equilibrada e livre para girar." - }, - { - "título": "Aqueduto", - "descrição": "Um sistema para transportar água usando gravidade.", - "recursos_necessários": "Pedra ou tijolo, argamassa, ferramentas para cortar e moldar pedra", - "esforço": "Muito Alto - Construir um aqueduto envolve extenso trabalho de construção e engenharia. Garantir um gradiente consistente para o fluxo de água requer planejamento e execução precisos." - } -] -``` - -**Besouro do Tempo:** Este tipo de saída estruturada pode ser enviado para outro serviço ou aplicativo que possa consumir dados JSON. - -## Extraindo dados de prompts - -**Besouro do Tempo:** Imagine que você está construindo um chatbot que ajuda os usuários a encontrar informações sobre viagens para reservar. Você deseja extrair detalhes-chave da entrada do usuário para fornecer respostas relevantes. Digamos que você tenha uma descrição como a seguinte: - -> Digite para onde você gostaria de ir, quando deseja viajar e seu orçamento. - -Como seria um prompt que extrai as informações? - -> Prompt: Extraia as seguintes informações do prompt de um usuário: localização, quando, orçamento, moeda, responda com os dados extraídos em formato JSON. -> -> Prompt do usuário: Eu quero ir para a Grécia em junho, meu orçamento é de 800 euros. - - -> Resposta: - -```json -{ - "localização": "Grécia", - "quando": "junho", - "orçamento": 800, - "moeda": "euros" -} -``` - -**Leonardo:** George (Besouro do Tempo) não para de falar, não é? - -**Você:** Vamos ver se consigo encontrar o interruptor. - -**Besouro do Tempo:** Vamos tentar isso com um prompt semelhante, vamos variar um pouco a entrada do usuário para garantir que o modelo esteja fazendo a coisa certa. - -> Prompt: Extraia as seguintes informações do prompt de um usuário: localização, quando, orçamento, moeda. Responda com os dados extraídos em formato JSON. Prompt do usuário: Indo para os EUA, máximo 1200 dólares, talvez janeiro. -> -> Resposta: - -```json -{ - "localização": "EUA", - "quando": "janeiro", - "orçamento": 1200, - "moeda": "dólares" -} -``` - -O que você pode fazer como desenvolvedor é criar um modelo: - -```text -Extraia as seguintes informações do prompt de um usuário: localização, quando, orçamento, moeda, responda com os dados extraídos em formato JSON, prompt do usuário: {entrada_do_usuário}. -``` - -**Você:** Encontrei, toma isso George! - -**Besouro do Tempo:** _Desligando_. - -**Leonardo:** Paz e silêncio finalmente. - -**Besouro do Tempo:** _Iniciando reinicialização de emergência_. Onde eu estava mesmo? Ah sim. - -**Besouro do Tempo:** Para chatbots, este é um recurso muito poderoso, pois permite extrair informações da entrada do usuário. E se o usuário pedir todo tipo de coisas? Você pode criar um modelo onde descreve habilidades, coisas que seu chatbot pode fazer e quais parâmetros ele precisa. Veja como você pode fazer isso: - -```text -Aqui estão algumas habilidades que você tem: - -habilidade: reservar_viagem -parâmetros: localização, quando, orçamento, moeda - -habilidade: encontrar_hotel -parâmetros: localização, check_in, check_out, orçamento - -habilidade: pedir_comida -parâmetros: culinária, endereço_entrega, método_pagamento - -Descubra qual habilidade usar com base no prompt de um usuário e extraia os parâmetros necessários, responda com os dados extraídos em formato JSON com colunas habilidade, parâmetros, dados_extraídos - -{entrada_do_usuário} -``` - -Vamos experimentar as seguintes entradas do usuário: - -> Prompt: Indo para os EUA, máximo 1200 dólares, talvez janeiro. -> Resultado: - -```json -{ - "habilidade": "reservar_viagem", - "parâmetros": ["localização", "quando", "orçamento", "moeda"], - "dados_extraídos": { - "localização": "EUA", - "quando": "janeiro", - "orçamento": 1200, - "moeda": "dólares" - } -} -``` - -> Prompt: Eu quero reservar um lugar para ficar em Nova York -> Resultado: - -```json -{ - "habilidade": "encontrar_hotel", - "parâmetros": ["localização", "check_in", "check_out", "orçamento"], - "dados_extraídos": { - "localização": "Nova York", - "check_in": null, - "check_out": null, - "orçamento": null - } -} -``` - -Esta combinação de apresentar habilidades e parâmetros e extrair dados de prompts também é conhecida como _chamada de ferramenta_ ou _chamada de função_. Uma resposta como esta do seu modelo facilita para você, como desenvolvedor, entender o que o usuário deseja e como chamar uma função correspondente para atender à solicitação do usuário. - -**Você:** Informações muito úteis, George! - -## Tarefa - Reparar o parafuso aéreo - -**Besouro do Tempo:** Preciso da atenção de vocês dois, temos um problema. O parafuso aéreo sofreu alguns danos durante a fuga dos soldados romanos e o pouso no Império Asteca. A máquina de Leonardo da Vinci é bastante sensível e requer uma entrada muito específica para repará-la. Você precisa fornecer as informações necessárias ao seu Besouro do Tempo (Assistente de IA) para ajudá-lo a reparar o parafuso aéreo. - -**Leonardo:** Então, consegui consertar os danos no parafuso aéreo, mas agora ele precisa de uma sequência de inicialização: esquerda, esquerda, cima, direita. - -**Você:** Parece bastante fácil, apenas esse texto? - -**Leonardo:** Sim, mas é especial, precisa ser espelhado, é assim que escrevo todos os meus textos. E claro, uso uma cifra de César com um deslocamento de 3 porque sou italiano. :-) - -**Você:** Sim, você não poderia simplesmente digitar então? - -**Leonardo:** Eu poderia, mas estou intrigado para ver se você e o Besouro do Tempo conseguem fazer isso. - -**Você:** Tudo bem. - -**Instrução:** Escreva um prompt que solicite a sequência de inicialização para o parafuso aéreo, o texto deve ser espelhado e codificado com uma cifra de César com um deslocamento de 3. Responda com o texto codificado. - -Veja o [aplicativo de exemplo](/app/README.md) para começar com o código. Ele contém um projeto Node.js com todas as dependências necessárias. - -> NOTA: Se você ainda não criou um Codespace, faça isso agora, pois precisará dele para que a comunicação com IA no aplicativo de exemplo funcione. -> -> Veja a seção _Opção 1: Criando um GitHub Codespace*_*_ do documento [Configure seu ambiente](/docs/setup/README.md). - -## Solução - -[Solução](../solution/solution.md) - -## Desafio - -Adapte o exemplo de reserva de viagem, fornecendo habilidades e extraindo parâmetros para um domínio de sua escolha. Escreva um prompt que solicite uma entrada do usuário e então extraia as informações necessárias para atender à solicitação do usuário. Responda com os dados extraídos em formato JSON com colunas habilidade, parâmetros e dados_extraídos. - -Determine também a habilidade com base na entrada do usuário. - -## Resumo - -Neste capítulo, você aprendeu sobre saída estruturada e como ela pode ser usada para apresentar informações de maneira estruturada. - -Você explorou diferentes maneiras de moldar a saída, incluindo instruir o modelo no prompt, apresentar um modelo de saída e usar formatos como JSON, XML ou CSV. - -Além disso, você aprendeu como extrair dados de prompts e apresentá-los em um formato estruturado. - -Ao aproveitar efetivamente a saída estruturada, você pode facilitar a compreensão e o consumo de informações geradas por modelos de IA Generativa. - -## Verificação de conhecimento - -**Pergunta**: Para que é usada a saída estruturada? Selecione todas as opções aplicáveis. - -A. Apresentar informações de maneira estruturada. - -B. Extrair dados de prompts. - -C. Gerar texto não estruturado. - -[Solução do quiz](../solution/solution-quiz.md) - -## Recursos para auto-estudo - -- [Série de vídeos sobre IA Generativa com JavaScript](https://aka.ms/genai-js) diff --git a/lessons/04-structured-output/translations/README.zh-cn.md b/lessons/04-structured-output/translations/README.zh-cn.md deleted file mode 100644 index 958c3f05..00000000 --- a/lessons/04-structured-output/translations/README.zh-cn.md +++ /dev/null @@ -1,565 +0,0 @@ -# 第4课:结构化输出 - -在本章中,您将学习: - -- 什么是_结构化输出_以及如何有效地利用它。 -- 如何从提示中提取信息并将其纳入输出。 -- 生成不同类型的输出格式,如JSON,以便服务轻松使用。 - -## 设置 - -如果您尚未设置开发环境,请按照以下方法进行设置:[设置您的环境](/docs/setup/README.md)。 - -## 相关资源 - -值得重新观看关于提示工程的视频,因为它为您即将在本章学习的内容奠定了基础。 - -[![观看关于提示工程的短视频](https://img.youtube.com/vi/gQ6TlyxBmWs/0.jpg)](https://www.youtube.com/watch?v=gQ6TlyxBmWs&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=3) - -_这个视频介绍了如何提高您的"提示"技能,教您如何向AI提供更清晰、更有效的指令以获得更好的结果。_ - -*🎥 点击上方图片观看关于提示工程的短视频* - -💼 幻灯片:[提示工程](../../../videos/slides/02-prompt-engineering.pptx) - -## 故事情节 - 跳出油锅,掉进火坑 - -> [!NOTE] -> _我们的故事到目前为止:您是一位来自1860年的机械工程师,正与达芬奇一起穿越时空。您已经逃脱了罗马士兵的追捕——或者说,您正在逃脱——并且正在拼命寻找一种安全降落空中螺旋机的方法_。 -> -> 如果您想从头开始了解故事,请参阅[第1课](../../01-intro-to-genai/README.md)。 - -> [!NOTE] -> 虽然我们建议您阅读故事(很有趣!),但如果您想直接跳到技术内容,[请点击这里](#interact-with-montezuma)。 - -风在您和达芬奇乘坐的空中螺旋机周围呼啸,木制框架在压力下嘎吱作响。 - -**您**:"达芬奇,我们需要找到一条出路!"您在风的咆哮声中喊道。 - -**达芬奇**:"我对我的发明有信心,但我们需要的不仅仅是信心来逃脱这些士兵。" - -**您**:"这可能是我们唯一的机会,"您按下甲虫的复杂机制。一道明亮的光芒笼罩着你们两个,周围的世界开始扭曲变形。 - -### 阿兹特克帝国 - -时间甲虫发出的明亮光芒逐渐消失,您发现自己站在一座巨大的石头金字塔基座处。环顾四周,您意识到自己已经来到了阿兹特克帝国的中心。 - -空中螺旋机尴尬地停在一块雕刻精美的大石头上,上面有看起来像日历的标记 - -
- 阿兹特克日历,维基百科 -
- -_阿兹特克日历,维基百科_ - -达芬奇向前走了一步,眼中充满惊奇。 - -**达芬奇**:"难以置信,"他喃喃自语,手指抚过雕刻。"但我希望这不是什么重要的东西。" - -在您做出反应之前,一群阿兹特克士兵走了过来。 - -
- 与阿兹特克人会面 -
- -**士兵领袖**:"你们是谁,做了什么?"他用纳瓦特尔语质问道。 - -您深吸一口气,试图稳定自己的神经。 - -**您**:"我们是来自遥远土地的旅行者,"您开始说道,希望您对古代语言的了解能派上用场。"这是一台飞行机器,是神明赐予我们的礼物,帮助我们到达你们这里。 - -战士的眼睛因惊讶而睁大,但他仍然保持警惕。 - -**士兵领袖**:"你们将跟我们一起去见统治者蒙特祖马。他将决定你们的命运。 - -到达金字塔顶部后,您被带入一个宏伟的大厅,阿兹特克帝国的统治者蒙特祖马坐在一个装饰着金子和羽毛的王座上。 - -**蒙特祖马**:"你们声称是来自遥远土地的旅行者,"蒙特祖马说道,他的声音平静但充满权威。"而且你们损坏了我们的神圣日历。你们打算如何弥补?" - -## 让我们玩个游戏 - -**达芬奇**:"我们是和平而来,"他说,声音稳定。"我们寻求知识和理解。请允许我们展示我们机器的力量,并与您分享我们的知识。" - -**蒙特祖马**:"很好。我提议玩一场[帕托利](#patolli)游戏。如果我赢得三局中的大多数,你们将把你们的设备给我并告诉我它是如何工作的。如果你们赢了,你们可以自由离开。" - -
- 玩帕托利游戏 -
- -游戏开始了,房间里一片寂静,只有豆子滚动和棋子在棋盘上移动的声音。 - -蒙特祖马赢得了第一局,他的技巧和经验显而易见。达芬奇仔细研究棋盘,他的思维在策略和概率中运转。 - -第二局是一场激烈的比赛,但达芬奇设法取得了胜利,使比分持平。 - -**达芬奇**:"还有一局,"他低声说道,"乔治(时间甲虫),我们需要赢得这一局,给我正确的走法。" - -**时间甲虫**:好的,计算中...给你... - -随着最后一步决定性的走法,达芬奇赢得了比赛。房间里爆发出欢呼和掌声,阿兹特克人显然对他的技巧和镇定印象深刻。蒙特祖马虽然失望,但点头表示尊重。 - -**蒙特祖马**:"你们公平地赢了,"蒙特祖马说,他的声音带着一丝钦佩。"你们可以自由离开,并且可以保留你们的设备。但请知道,如果你们希望回来,随时欢迎。" - -**时间甲虫**:如果你不说,我也不会说,达芬奇 ;) - -### 帕托利 - -> [!NOTE] -> 帕托利是美洲已知最古老的游戏之一,由前哥伦布时期的中美洲文化如阿兹特克人玩。它是策略和运气的结合。 -> -> **帕托利的玩法**: -> - **棋盘和棋子**:在X形棋盘上玩,有一个中心块和四个臂。玩家使用五个黑豆作为骰子,一面有标记。 -> - **设置**:每个玩家选择六个他们颜色的标记(珠子)并将它们放在棋盘外,加上用于计分的点珠。 -> - **游戏玩法**:掷豆子使标记在棋盘上移动并回到起点。掷出五点移动十个空格。落在某些空格上可以获得回合,失去点数或捕获标记。 -> -> **阿兹特克人的参与**:贵族和平民广泛玩这个游戏,它是蒙特祖马宫廷的最爱。除了娱乐外,它还涉及高风险赌博,赌注如毯子、宝石甚至自由。 - -
- 帕托利棋盘 -
- -_帕托利 - 维基百科_ - -## 与蒙特祖马互动 - -如果您想与蒙特祖马互动,请运行[角色](/app/README.md)应用程序。 - -> [!IMPORTANT] -> 这完全是虚构的;响应由AI生成。 -> [负责任的AI免责声明](../../../README.md#responsible-ai-disclaimer) - -
- 蒙特祖马 -
- -**步骤**: - -1. 启动一个[![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. 导航到仓库根目录中的_/app_。 -3. 找到控制台并运行`npm install`,然后运行`npm start`。 -4. 出现后,选择"在浏览器中打开"按钮。 -5. 与蒙特祖马聊天。 - -有关应用的更详细解释,请参阅[详细应用解释](../../01-intro-to-genai/README.md#interact-with-dinocrates)。 - -> [!NOTE] -> 如果您在本地机器上运行项目,请查看快速入门指南,设置[GitHub个人访问令牌](../../../docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access)并替换代码中的密钥。 - -## 结构化输出 - -**时间甲虫**:想知道我是如何打败蒙特祖马的吗? - -**您**:嘘,小声点,我们还在阿兹特克帝国。 - -**时间甲虫**:哦,对不起,是的,所以结构化输出就是我的方法。你要求正确的走法,我以结构化的方式给你提供了它们。 - -结构化输出使用特定的数据格式来清晰地组织信息。常见的格式如JSON、XML和CSV使服务更容易处理和使用数据。 - -生成式AI模型可以输出各种格式的数据。然而,非结构化文本的输出可能使提取信息并将其传递给其他服务变得困难。结构化输出可以在这方面提供帮助。 - -**您**:嗯,你真聪明 ;) - -**时间甲虫**:我尽力而为。:-) - -另一个好处是,即使对于人类读者来说,当结构化数据遵循熟悉的格式时,也更容易阅读和理解。 - -让我们看一些输入示例,看看如何通过要求特定的输出格式来帮助提取信息,使其更容易理解和使用。 - -**您**:好吧,反正我们也没有其他计划,让我们听听。 - -## 塑造输出 - -**时间甲虫**:让我们从一个简单的例子开始。想象一下,您是达芬奇,正在尝试使用15世纪的技术构思发明创意。您想知道需要什么资源以及建造每个发明需要多少努力。以下是您可以如何询问这些信息: - -**达芬奇**:哦,我喜欢这个游戏,每个人都假装你是我! - -> 提示:生成我可以使用15世纪技术建造的发明创意。对于每个发明,指定所需的资源并估计建造它所需的努力。 - -```text -结果: -当然!以下是一些您可以使用15世纪技术建造的详细发明创意,以及所需的资源和涉及的努力: - -### 1. 水力磨坊 -**所需资源**: - - 水源(河流或溪流) - - 水轮和磨坊结构的木材 - - 用于研磨的石头 - - 用于齿轮和轴的金属 - - **努力**: - - **高**:需要重要的建筑和工程技能来建造水轮、磨坊结构和研磨机制。确保持续的水流。 - -### 2. 星盘 -**所需资源**: - - 黄铜或其他金属 - - 雕刻工具 - - 数学知识 - -**努力**: - - **中等**:涉及精确的金属加工和雕刻。需要天文学和数学知识以确保准确性。 - -### 3. 印刷机 -**所需资源**: - - 用于印刷机结构的木材 - - 用于活字的金属 - - 墨水和纸张 - -**努力**: - - **高**:建造印刷机和制作活字需要高级木工和金属加工技能。排版和印刷也需要细致的工作。 - -这是一个看起来不错的响应,每个创意都以相同的方式构建,很容易理解。但如果你想要其他方式呢?为此,你可以在文本中指示你想要的方式,甚至可以提供输出模板。让我们展示每种技巧。 -``` - -**达芬奇**:是的,完全要偷那个水力磨坊的想法,我现在就能看到,一个在河边的磨坊,把谷物磨成面粉,太完美了!我喜欢这个小玩意。小玩意,我还在想什么?这将使我不那么依赖斯福尔扎这样的人。 - -**时间甲虫**:当然,如果你能完成一个项目的话,达芬奇。:-) - -**达芬奇**:嘿,我正在努力! - -**时间甲虫**:让我们看看如何指导模型以更结构化的方式呈现输出。 - -### 构建更好输出的提示 - -**时间甲虫**:你可以通过告诉模型你想要如何构建输出来改进你的提示。例如,你可以要求一个带有你需要的列的表格格式。以下是如何修改提示: - -> 提示:生成可以使用15世纪技术建造的发明创意。对于每个发明,提供所需的资源并估计建造它所需的努力。以下列列的表格形式呈现信息:标题、描述、所需资源和努力。 - -```text -结果: - -| 标题 | 描述 | 所需资源 | 努力 | -|------|------|----------|--------| -| 水力磨坊 | 一个由水轮驱动的磨坊,将谷物磨成面粉。| 水源、木材、石头、用于齿轮和轴的金属 | 高 | -| 星盘 | 一个用于解决与时间和星位相关问题的天文仪器。| 黄铜或其他金属、雕刻工具、数学知识 | 中等 | -| 印刷机 | 一个比手工抄写更有效地复制文本的印刷机。| 木材、用于活字的金属、墨水、纸张 | 高 | - -这次你得到了一个更容易阅读和理解的表格。 -``` - -**达芬奇**:我喜欢这个,肯定会为我节省很多时间。 - -**您**:我也是。 - -### 提供输出模板 - -获取结构化输出的另一种方式是提供输出模板。它可以是任何格式,从列到JSON、XML、CSV或markdown。 - -> 提示:生成可以使用15世纪技术建造的发明创意。对于每个发明,指定所需的资源并估计建造它所需的努力。使用以下格式呈现信息(使用markdown作为输出格式): -> -> 标题:[发明名称] -> 描述:[简短说明] -> 所需资源:[所需材料列表] -> 努力:[估计的难度或所需劳动] - -```text - -**结果**: - -当然!以下是一些您可以使用15世纪技术建造的发明创意,按照要求的格式呈现: - -### 1. 水力磨坊 -**标题**:水力磨坊 - -**描述**:一个由水轮驱动的磨坊,将谷物磨成面粉。 - -**所需资源**:水源、木材、石头、用于齿轮和轴的金属。 - -**努力**:高 - 需要重要的建筑和工程技能来建造水轮、磨坊结构和研磨机制。确保持续的水流也是必要的。 - -### 2. 星盘 -**标题**:星盘 - -**描述**:一个用于解决与时间和星位相关问题的天文仪器。 - -**所需资源**:黄铜或其他金属、雕刻工具、数学知识。 - -**努力**:中等 - 涉及精确的金属加工和雕刻。需要天文学和数学知识以确保准确性。 - -### 3. 印刷机 -**标题**:印刷机 - -**描述**:一个比手工抄写更有效地复制文本的印刷机。 - -**所需资源**:木材、用于活字的金属、墨水、纸张。 - -**努力**:高 - 建造印刷机和制作活字需要高级木工和金属加工技能。排版和印刷也需要细致的工作。 - -### 4. 机械钟 -**标题**:机械钟 - -**描述**:一个使用齿轮和砝码来计时的钟。 - -**所需资源**:用于齿轮、弹簧、砝码的金属,用于外壳的木材,精密工具。 - -**努力**:非常高 - 需要精密的金属加工和对机械的深入理解。建造一个可靠的钟需要精确的组装和校准。 -``` - -**达芬奇**:这很棒,但谁是Jason? - -**时间甲虫**:JSON,达芬奇,JSON。它是一种易于被服务或应用程序使用的结构化格式。 - -**达芬奇**:这就是我说的,Jason。 - -**时间甲虫**:_叹气_。 - -如你所见,输出是按照输出模板中的规范构建的。这使其更容易阅读和理解,它还创建了可以被其他服务或应用程序轻松使用的可重复输出。 - -当然,服务更受益于更结构化的输出,如JSON、XML或CSV,所以让我们看看如何请求这些格式。 - -### 使用JSON、XML或CSV等结构化输出格式 - -**时间甲虫**:到目前为止,你已经看到了如何通过在提示中指导模型来控制输出结构。你甚至提供了一个输出模板。让我们更进一步,看看如何将这个想法应用到更结构化的输出格式,如JSON等。 - -相应地修改提示以请求JSON输出: - -> **提示**:生成我可以使用15世纪技术建造的发明创意,请让我知道每个发明需要什么资源以及建造每个发明需要多少努力,对于每个创意以json格式呈现,包括标题、描述、所需资源、努力的属性 -> -> JSON输出示例: -> [ -> { -> "title": "", -> "description": ".", -> "resources_needed": "", -> "effort": "" -> } -> ] - -```json -[ - { - "title": "水力磨坊", - "description": "一个由水轮驱动的磨坊,将谷物磨成面粉。", - "resources_needed": "水源、木材、石头、用于齿轮和轴的金属", - "effort": "高 - 需要重要的建筑和工程技能来建造水轮、磨坊结构和研磨机制。确保持续的水流也是必要的。" - }, - { - "title": "星盘", - "description": "一个用于解决与时间和星位相关问题的天文仪器。", - "resources_needed": "黄铜或其他金属、雕刻工具、数学知识", - "effort": "中等 - 涉及精确的金属加工和雕刻。需要天文学和数学知识以确保准确性。" - }, - { - "title": "印刷机", - "description": "一个比手工抄写更有效地复制文本的印刷机。", - "resources_needed": "木材、用于活字的金属、墨水、纸张", - "effort": "高 - 建造印刷机和制作活字需要高级木工和金属加工技能。排版和印刷也需要细致的工作。" - }, - { - "title": "机械钟", - "description": "一个使用齿轮和砝码来计时的钟。", - "resources_needed": "用于齿轮、弹簧、砝码的金属,用于外壳的木材,精密工具", - "effort": "非常高 - 需要精密的金属加工和对机械的深入理解。建造一个可靠的钟需要精确的组装和校准。" - }, - { - "title": "风车", - "description": "一个由风力驱动的磨坊,用于磨谷物或抽水。", - "resources_needed": "木材、用于齿轮和轴的金属、用于基座的石头", - "effort": "高 - 建造风车需要重要的木工和工程技能。你需要设计和建造叶片、塔架和内部机制。" - }, - { - "title": "浑仪", - "description": "一个用环表示大圆的天球模型。", - "resources_needed": "用于环和支架的金属、弯曲和连接金属的工具、天文学知识", - "effort": "中等 - 涉及精确的金属加工和组装。需要理解天体运动以准确定位环。" - }, - { - "title": "攻城机(投石机或弩炮)", - "description": "一个用于演示力学和物理原理的投石机或弩炮。", - "resources_needed": "木材、绳索、用于轴和配重的金属", - "effort": "中等到高 - 建造一个功能性的攻城机需要木工技能和对力学的理解。测试和调整以获得最佳性能可能很耗时。" - }, - { - "title": "蒸馏装置", - "description": "一个通过蒸馏净化液体的装置。", - "resources_needed": "用于烧瓶和冷凝器的玻璃或金属、热源、管道", - "effort": "中等 - 需要基本的吹玻璃或金属加工技能。设置和维持正确的蒸馏温度需要仔细监控。" - }, - { - "title": "指南针", - "description": "一个用于导航的磁罗盘。", - "resources_needed": "磁化针、非磁性外壳、用于阻尼的水或油", - "effort": "低 - 制作指南针相对简单。主要挑战是磁化针并确保它平衡且能自由旋转。" - }, - { - "title": "输水渠", - "description": "一个使用重力输送水的系统。", - "resources_needed": "石头或砖块、砂浆、切割和塑形石头的工具", - "effort": "非常高 - 建造输水渠涉及大量的建筑工作和工程。确保水流的一致坡度需要精确的规划和执行。" - } -] -``` - -**时间甲虫**:这种类型的结构化输出可以发送给另一个可以使用JSON数据的服务或应用程序。 - -## 从提示中提取数据 - -**时间甲虫**:想象一下你正在构建一个聊天机器人,帮助用户找到有关预订旅行的信息。你想从用户的输入中提取关键细节以提供相关响应。假设你有以下描述: - -> 输入你想去的地方、何时旅行以及你的预算。 - -提取信息的提示是什么样的? - -> 提示:从用户的提示中提取以下信息:地点、时间、预算、货币,以JSON格式响应提取的数据。 -> -> 用户提示:我想在六月去希腊,我的预算是800欧元。 - -> 响应: - -```json -{ - "location": "希腊", - "when": "六月", - "budget": 800, - "currency": "欧元" -} -``` - -**达芬奇**:乔治(时间甲虫)他一直不停地说话,是吗? - -**您**:让我们看看能不能找到关闭开关。 - -**时间甲虫**:让我们用类似的提示试试,让我们稍微改变一下用户输入,这样我们就能确保模型在做正确的事情。 - -> 提示:从用户的提示中提取以下信息:地点、时间、预算、货币。以JSON格式响应提取的数据。用户提示:去美国,最多1200美元,可能是一月。 -> -> 响应: - -```json -{ - "location": "美国", - "when": "一月", - "budget": 1200, - "currency": "美元" -} -``` - -作为开发者,你可以创建一个模板: - -```text -从用户的提示中提取以下信息:地点、时间、预算、货币,以JSON格式响应提取的数据,用户提示:{user_input}。 -``` - -**您**:找到了,给你,乔治! - -**时间甲虫**:_正在关机_。 - -**达芬奇**:终于安静了。 - -**时间甲虫**:_启动紧急启动_。我刚才说到哪里了?哦,对了。 - -**时间甲虫**:对于聊天机器人来说,这是一个非常强大的功能,因为它允许你从用户的输入中提取信息。如果用户询问各种事情怎么办?你可以创建一个模板,描述你的聊天机器人可以做什么以及需要什么参数。以下是你可以如何做到这一点: - -```text -以下是你拥有的一些技能: - -技能:预订旅行 -参数:地点、时间、预算、货币 - -技能:查找酒店 -参数:地点、入住日期、退房日期、预算 - -技能:订餐 -参数:菜系、送餐地址、支付方式 - -根据用户的提示确定使用哪个技能并提取必要的参数,以JSON格式响应提取的数据,包括技能、参数、提取的数据列 - -{user_input} -``` - -让我们试试以下用户输入: - -> 提示:去美国,最多1200美元,可能是一月。 -> 结果: - -```json -{ - "skill": "预订旅行", - "parameters": ["地点", "时间", "预算", "货币"], - "extracted_data": { - "location": "美国", - "when": "一月", - "budget": 1200, - "currency": "美元" - } -} -``` - -> 提示:我想在纽约预订住宿 -> 结果: - -```json -{ - "skill": "查找酒店", - "parameters": ["地点", "入住日期", "退房日期", "预算"], - "extracted_data": { - "location": "纽约", - "check_in": null, - "check_out": null, - "budget": null - } -} -``` - -这种结合呈现技能和参数并从提示中提取数据的组合也被称为_工具调用_或_函数调用_。来自模型的这种响应使你作为开发者很容易理解用户想要什么以及如何调用相应的函数来满足用户的请求。 - -**您**:非常有用的信息,乔治! - -## 作业 - 修复空中螺旋机 - -**时间甲虫**:我需要你们两个的注意,我们有个问题。空中螺旋机在逃离罗马士兵和降落在阿兹特克帝国时受到了一些损坏。达芬奇的机器非常敏感,需要非常具体的输入来修复它。你需要向你的时间甲虫(AI助手)提供必要的信息来帮助你修复空中螺旋机。 - -**达芬奇**:所以我设法修复了空中螺旋机的损坏,但现在它需要一个启动序列:左、左、上、右。 - -**您**:听起来很简单,就是这些文字? - -**达芬奇**:是的,但它很特别,需要镜像,这是我写所有文字的方式。当然,我还使用凯撒密码,偏移量为3,因为我是意大利人。:-) - -**您**:是的,你能直接输入吗? - -**达芬奇**:我可以,但我很好奇想看看你和时间甲虫能不能做到。 - -**您**:好吧。 - -**指示**:编写一个提示,要求空中螺旋机的启动序列,文本应该是镜像的,并用偏移量为3的凯撒密码编码。用编码后的文本响应。 - -查看[示例应用](../sample-app)以获取开始的代码。它包含一个带有所有必需依赖项的Node.js项目。 - -> 注意:如果你还没有创建Codespace,请现在创建,因为你需要它来让示例应用中的AI通信工作。 -> -> 请参阅[设置您的环境](/docs/setup/README.md)文档中的_选项1:创建GitHub Codespace_部分。 - -## 解决方案 - -[解决方案](../solution/solution.md) - -## 挑战 - -采用旅行预订的例子,提供技能和提取参数到你选择的领域。编写一个提示,要求用户输入,然后提取必要的信息来满足用户的请求。以JSON格式响应提取的数据,包括技能、参数和提取的数据列。 - -同时根据用户的输入确定技能。 - -## 总结 - -在本章中,您学习了结构化输出以及如何使用它以结构化方式呈现信息。 - -您探索了不同的方式来塑造输出,包括在提示中指导模型、提供输出模板以及使用JSON、XML或CSV等格式。 - -此外,您还学习了如何从提示中提取数据并以结构化格式呈现。 - -通过有效利用结构化输出,您可以使生成式AI模型生成的信息更容易理解和使用。 - -## 知识检查 - -**问题**:结构化输出用于什么?选择所有适用项。 - -A. 以结构化方式呈现信息。 - -B. 从提示中提取数据。 - -C. 生成非结构化文本。 - -[测验解决方案](../solution/solution-quiz.md) - -## 自学资源 - -- [使用JavaScript的生成式AI视频系列](https://aka.ms/genai-js) \ No newline at end of file diff --git a/lessons/05-rag/README.md b/lessons/05-rag/README.md index 52b06556..d20f21fe 100644 --- a/lessons/05-rag/README.md +++ b/lessons/05-rag/README.md @@ -10,16 +10,6 @@ In this chapter you will learn: If you haven't already, set up your development environment. Here's how you can do it: [Setup your environment](/docs/setup/README.md). -## Related Resources - -[![Watch a short video about RAG](https://img.youtube.com/vi/xkFOmx5yxIA/0.jpg)](https://www.youtube.com/watch?v=xkFOmx5yxIA&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=4) - -_This video explains Retrieval Augmented Generation (RAG), a method that helps the AI use your content alongside its training data for improved results._ - -*🎥 Click on the image above to watch a short video about retrieval augmented generation, RAG* - -💼 Slides: [Retrieval augmented generation, RAG](/videos/slides/03-rag.pptx) - ## Narrative - Genesis > [!NOTE] @@ -85,14 +75,14 @@ If you want to interact with Ada, run the [Characters](/app/README.md) app. 1. Start a [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) 2. Navigate to _/app_ in the repo root. -3. Locate the console and run `npm install` followed by `npm start`. +3. Locate the console and run `npm ci` followed by `npm start`. 4. Once it appears, select the "Open in Browser" button. 5. Chat with Ada. For a more detailed explanation of the app, see [Detailed app explanation](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). > [!NOTE] - > If you're running the project locally on your machine, please review the QuickStart guide to get a [GitHub personal access](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) token setup and replace the key in the code. +> Configure `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL` in the repository-root `.env` for both local development and Codespaces. See the [setup guide](/docs/setup/README.md#configure-environment-variables). ## Known challenges with large language models, LLMs @@ -259,13 +249,20 @@ ${question} // 4. Generator component: use the search results to generate a response // --------------------------------------------------------------------- +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} + const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, + baseURL: endpoint, + apiKey, }); const chunks = await openai.chat.completions.create({ - model: "gpt-4o-mini", + model, messages: [{ role: "user", content: augmentedPrompt }], stream: true, }); @@ -273,7 +270,7 @@ const chunks = await openai.chat.completions.create({ console.log(`Answer for "${question}":`); for await (const chunk of chunks) { - process.stdout.write(chunk.choices[0].delta.content ?? ""); + process.stdout.write(chunk.choices[0]?.delta.content ?? ""); } ``` @@ -341,4 +338,4 @@ C. The retriever finds relevant information from external data sources. - **Sample apps**: * [Serverless AI Chat with RAG](https://github.com/Azure-Samples/serverless-chat-langchainjs/) * [Ask Youtube: A RAG-based Youtube Q&A API](https://github.com/Azure-Samples/langchainjs-quickstart-demo) -- [Full-length workshop: Create your own ChatGPT with RAG](https://moaw.dev/workshop/gh:azure-samples/azure-openai-rag-workshop/docs/workshop-qdrant.md) +- [Full-length workshop: Create your own ChatGPT with RAG](https://github.com/Azure-Samples/azure-openai-rag-workshop) diff --git a/lessons/05-rag/assets/cern.jpg b/lessons/05-rag/assets/cern.jpg deleted file mode 100644 index 0cb6ca59..00000000 Binary files a/lessons/05-rag/assets/cern.jpg and /dev/null differ diff --git a/lessons/05-rag/assets/rag-example.png b/lessons/05-rag/assets/rag-example.png deleted file mode 100644 index 87d0f3b8..00000000 Binary files a/lessons/05-rag/assets/rag-example.png and /dev/null differ diff --git a/lessons/05-rag/example/rag-cars.js b/lessons/05-rag/example/rag-cars.js index d63339c5..fb7e08ab 100644 --- a/lessons/05-rag/example/rag-cars.js +++ b/lessons/05-rag/example/rag-cars.js @@ -9,6 +9,13 @@ import process from "node:process"; import fs from "node:fs"; import { OpenAI } from "openai"; +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} + // Change the current working directory to the directory of the script const __dirname = dirname(fileURLToPath(import.meta.url)); process.chdir(__dirname); @@ -62,18 +69,26 @@ ${question} // --------------------------------------------------------------------- const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, + baseURL: endpoint, + apiKey, + timeout: 60000, }); const chunks = await openai.chat.completions.create({ - model: "gpt-4o-mini", + model, messages: [{ role: "user", content: augmentedPrompt }], stream: true, }); console.log(`Answer for "${question}":`); +let receivedText = false; for await (const chunk of chunks) { - process.stdout.write(chunk.choices[0].delta.content ?? ""); + const text = chunk.choices[0]?.delta.content; + if (text) { + receivedText = true; + process.stdout.write(text); + } } +if (!receivedText) throw new Error("The model did not return an answer."); +process.stdout.write("\n"); diff --git a/lessons/05-rag/package-lock.json b/lessons/05-rag/package-lock.json new file mode 100644 index 00000000..3dd51efa --- /dev/null +++ b/lessons/05-rag/package-lock.json @@ -0,0 +1,52 @@ +{ + "name": "retrieval-examples", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "retrieval-examples", + "version": "1.0.0", + "dependencies": { + "openai": "^7.28.0" + } + }, + "node_modules/openai": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.28.0.tgz", + "integrity": "sha512-HSY4fFLflQGYe2kvO0atm6/GzhD61gpyiOZZUvO4nptMUhuB1to2K3wwFpFqc5gfEK0SxyIIFiu1yE2MtygWcA==", + "license": "Apache-2.0", + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + } + } +} diff --git a/lessons/05-rag/package.json b/lessons/05-rag/package.json new file mode 100644 index 00000000..e2e55937 --- /dev/null +++ b/lessons/05-rag/package.json @@ -0,0 +1,13 @@ +{ + "name": "retrieval-examples", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "cars": "node --env-file-if-exists=../../.env example/rag-cars.js", + "wikipedia": "node --env-file-if-exists=../../.env solution/rag-www.js" + }, + "dependencies": { + "openai": "^7.28.0" + } +} diff --git a/lessons/05-rag/solution/rag-www.js b/lessons/05-rag/solution/rag-www.js index 2b0cb043..8daf486a 100644 --- a/lessons/05-rag/solution/rag-www.js +++ b/lessons/05-rag/solution/rag-www.js @@ -6,6 +6,13 @@ import process from "node:process"; import { OpenAI } from "openai"; +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} + // 1. Ask a question about the web // ------------------------------- @@ -15,9 +22,16 @@ const question = `why did you create the web?`; // ------------------------------------------------- // Load text data from Wikipedia's Tim Berners-Lee page -const response = await fetch('https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&redirects=true&explaintext&titles=Tim%20Berners-Lee'); +const response = await fetch('https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&redirects=true&explaintext&titles=Tim%20Berners-Lee', { + headers: { "User-Agent": "GenerativeAIJavaScriptCourse/1.0 (https://github.com/microsoft/generative-ai-with-javascript)" }, + signal: AbortSignal.timeout(30000) +}); +if (!response.ok) throw new Error(`Wikipedia request failed: HTTP ${response.status}.`); const data = await response.json(); -const wikipediaInfo = Object.values(data.query.pages)[0]?.extract; +const wikipediaInfo = data.query?.pages && Object.values(data.query.pages)[0]?.extract; +if (typeof wikipediaInfo !== "string" || !wikipediaInfo.trim()) { + throw new Error("Wikipedia did not return source text."); +} // 3. Context augmentation: create a combined prompt with the information // ---------------------------------------------------------------------- @@ -40,18 +54,26 @@ ${question} // ----------------------------------------------------------------------- const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, + baseURL: endpoint, + apiKey, + timeout: 60000, }); const chunks = await openai.chat.completions.create({ - model: "gpt-4o-mini", + model, messages: [{ role: "user", content: augmentedPrompt }], stream: true, }); console.log(`You:\n${question}\n\nTim:`); +let receivedText = false; for await (const chunk of chunks) { - process.stdout.write(chunk.choices[0].delta.content ?? ""); + const text = chunk.choices[0]?.delta.content; + if (text) { + receivedText = true; + process.stdout.write(text); + } } +if (!receivedText) throw new Error("The model did not return an answer."); +process.stdout.write("\n"); diff --git a/lessons/05-rag/translations/README.bn.md b/lessons/05-rag/translations/README.bn.md deleted file mode 100644 index 5f8bc9b9..00000000 --- a/lessons/05-rag/translations/README.bn.md +++ /dev/null @@ -1,470 +0,0 @@ -# **পাঠ ৫: রিট্রিভাল-অগমেন্টেড জেনারেশন (RAG) ব্যবহার করে তোমার ডেটার সাথে কথা বলো** - -### **এই অধ্যায়ে তুমি শিখবে:** -- **Retrieval-Augmented Generation (RAG)**-এর মৌলিক ধারণা এবং কীভাবে এটি **Generative AI মডেলের উত্তর উন্নত করতে সাহায্য করে**। -- কীভাবে **বহিরাগত ডেটা সোর্স** AI অ্যাপ্লিকেশনের সাথে সংযুক্ত করা যায়। -- কীভাবে **নিজের ডেটা ব্যবহার করে AI-উৎপন্ন কন্টেন্টের প্রাসঙ্গিকতা ও নির্ভুলতা বাড়ানো যায়।** - ---- - -## **সেটআপ** - -যদি তুমি এখনও **তোমার ডেভেলপমেন্ট এনভায়রনমেন্ট সেটআপ না করে থাকো**, তাহলে এটি এখনই করো। -**কীভাবে সেটআপ করবে তা জানতে এখানে দেখো:** [**Setup your environment**](/docs/setup/README.md)। - ---- - -## **সম্পর্কিত রিসোর্স** - -[![RAG সম্পর্কে সংক্ষিপ্ত ভিডিও দেখুন](https://img.youtube.com/vi/xkFOmx5yxIA/0.jpg)](https://www.youtube.com/watch?v=xkFOmx5yxIA&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=4) - -_এই ভিডিওটি **Retrieval-Augmented Generation (RAG)** ব্যাখ্যা করে, যা AI-কে তার ট্রেনিং ডেটার পাশাপাশি **তোমার নিজের কনটেন্ট ব্যবহার করতে সাহায্য করে, যাতে আরও উন্নত ফলাফল পাওয়া যায়**।_ - -🎥 *উপরের ছবিতে ক্লিক করে RAG সম্পর্কে সংক্ষিপ্ত ভিডিও দেখো।* - -💼 **স্লাইডস:** [**Retrieval-Augmented Generation, RAG**](/videos/slides/03-rag.pptx) -## **বর্ণনা - জেনেসিস** - -> [!NOTE] -> **এখন পর্যন্ত আমাদের গল্প:** তুমি ১৮৬০-এর দশকের লন্ডনের একজন **যান্ত্রিক প্রকৌশলী**। তুমি তোমার **অটোমাটন** (স্বয়ংক্রিয় যন্ত্র) নিয়ে কাজ করছিলে, যখন **চার্লস ব্যাবেজের একটি চিঠি** পেলে। সেই চিঠি তোমাকে **একটি গ্রন্থাগারে নিয়ে যায়**, যেখানে তুমি **একটি সময় ভ্রমণের যন্ত্র** সংগ্রহ করো। -> -> এই যন্ত্র ব্যবহার করে তুমি বিভিন্ন সময়ে ভ্রমণ করেছ, **ফ্লোরেন্সে গিয়ে লিওনার্দো দা ভিঞ্চির সাথে দেখা করেছ** এবং **অ্যাজটেক সাম্রাজ্যেও পৌঁছেছিলে**। এখান থেকেই গল্প এগিয়ে চলেছে... -> -> যদি শুরু থেকে গল্পটি পড়তে চাও, দেখো **[পাঠ ১](/lessons/01-intro-to-genai/translations/README.bn.md)**। -> [!NOTE] -> আমরা গল্পের সঙ্গে যাওয়ার পরামর্শ দিই (কারণ এটি মজার!), তবে তুমি চাইলে সরাসরি **[প্রযুক্তিগত অংশে যেতে এখানে ক্লিক করো](#এডা-লাভলেসের-সাথে-সাক্ষাৎ)**। ---- - -### **যাত্রার শুরু - জেনেসিস** - -**তুমি:** "লিওনার্দো, আমাদের এখনই যেতে হবে!" তুমি বললে এবং **বোতাম চেপে দিলে**। যন্ত্রটি গুঞ্জন শুরু করল, এবং হঠাৎ একটি যান্ত্রিক কণ্ঠ প্রতিধ্বনিত হলো, -**"সময় হয়েছে বাড়ি ফেরার, এটি ‘জেনেসিস’ (Genesis) এর সময়।"** - -**লিওনার্দো:** "জেনেসিস? Che cosa significa?" লিওনার্দো বিস্ময়ে জিজ্ঞাসা করল। - -তুমি উত্তর দেওয়ার আগেই চারপাশের জগৎ **রঙ ও শব্দের কুয়াশায় হারিয়ে গেল**, এবং **মন্দিরটি মিলিয়ে গেল**, তোমরা **সময়ের প্রবাহে টেনে নেওয়া হলে**। - ---- - -### **অজানা প্রাসাদের বাগানে অবতরণ** - -**তুমি একটি বাগানে অবতরণ করলে।** গভীর রাত, চারপাশে **ঘন কুয়াশা**, আর দূরে কিছু **অদ্ভুত আলো ঝলমল করছে**। তোমাদের সামনে বিশাল এক **প্রাসাদ** দাঁড়িয়ে আছে। - -**লিওনার্দো চারপাশে তাকিয়ে বিস্ময়ে চোখ বড় বড় করে ফেলল।** - -
- ঘন কুয়াশার মাঝে পুরনো এক প্রাসাদ -
- ---- - -### **কুকুরদের থেকে পালানো** - -হঠাৎ **তীব্র ঘেউ ঘেউ শোনা গেল**, আর কিছু **কুকুর দৌঁড়ে আসছে**। - -**তুমি লিওনার্দোর দিকে তাকিয়ে বললে, "আমাদের এখনই ভিতরে ঢুকতে হবে!"** - -
- কুকুরদের কাছ থেকে পালিয়ে যাওয়া -
- -তোমরা যখন **প্রাসাদের দরজায় পৌঁছালে, সেটি হঠাৎ খুলে গেল**, এবং কিছু **পরিচারক দ্রুত বেরিয়ে এলো**। - -তারা তোমাদের দেখে একটু পরীক্ষা করল, তারপর **হাতের ইশারায় ভিতরে আসার সংকেত দিলো**। - ---- - -### **এডা লাভলেসের সাথে সাক্ষাৎ** - -তুমি **এডা লাভলেসের** মুখোমুখি হলে, তার চোখে **এক অপার কৌতূহল** জ্বলজ্বল করছিল... -### **এডা ও চার্লসের সাথে সাক্ষাৎ** - -**এডা:** "আহ, অবশেষে তুমি এসে পৌঁছালে," তিনি উষ্ণ স্বরে বললেন। "আমাদের তোমার সাহায্য দরকার, একটি জরুরি কাজ রয়েছে।" - -**তুমি:** "অবশেষে?", তুমি প্রশ্ন করলে। "ডিনোক্রেটসও একই কথা বলেছিল, কিন্তু আমি এখনো বুঝতে পারছি না তুমি কী বোঝাতে চাচ্ছো?" - -**এডা:** "শশশ, এখন এসব প্রশ্নের সময় নেই। আমাদের তোমার হাতে থাকা **যন্ত্র** নিয়ে কথা বলা দরকার। চার্লস, তাদের ব্যাখ্যা করো…" - -**তুমি:** "কিন্তু…" - -
- এডা লাভলেস ও চার্লস ব্যাবেজ একটি যন্ত্র নিয়ে কাজ করছেন -
- -**চার্লস ব্যাবেজ** সামনে এগিয়ে এসে **তোমার হাতে থাকা টাইম বিটল পরীক্ষা করলেন**। - -**চার্লস:** "এই যন্ত্রটি সত্যিই চমৎকার, কিন্তু কিছু সমস্যা রয়েছে, তাই না? নিশ্চয়ই তুমি খেয়াল করেছো।" - -**লিওনার্দো মাথা নাড়ল, "Sì, এটি অদ্ভুতভাবে আচরণ করছে।"** - -**এডা:** "এই যন্ত্রটি এখনো সম্পূর্ণ প্রস্তুত নয়। আমাদের এটিকে **আরও উন্নত করতে হবে, আরও বুদ্ধিমান ও পরিবেশ সম্পর্কে সচেতন** করতে হবে।" - -**"আমাদের চাওয়া হলো, এটি যেন **ভিন্ন সময়কাল থেকে তথ্য সংগ্রহ করতে পারে** এবং সেই তথ্য ব্যবহার করে **সঠিক ও প্রাসঙ্গিক উত্তর তৈরি করতে পারে**। তুমি কি আমাদের সাহায্য করতে পারবে?"** - -**তুমি:** "নিশ্চয়ই! আমার মনে হচ্ছে, আমাদের **এই যন্ত্রের প্রতিক্রিয়া উন্নত করতে (augment) ডেটা যোগ করতে হবে**। এটা একদম যৌক্তিক মনে হচ্ছে।" - -**এডা:** "ঠিক তাই! চল, আমি তোমাকে **Retrieval-Augmented Generation (RAG)** নামে একটি ধারণা সম্পর্কে বলি।" -## **এডা লাভলেসের সাথে ইন্টারঅ্যাক্ট করুন** - -আপনি যদি **এডা লাভলেসের সাথে ইন্টারঅ্যাক্ট** করতে চান, তাহলে **[Characters](/app/README.md)** অ্যাপ চালান। - -> [!IMPORTANT] -> এটি সম্পূর্ণ কল্পিত; প্রতিক্রিয়াগুলি **AI দ্বারা তৈরি করা হয়**। -> [দায়িত্বশীল AI সংক্রান্ত ঘোষণাপত্র](/README.md#responsible-ai-disclaimer) -
- এডা লাভলেস -
- -### **ধাপসমূহ:** - -1. [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) এ ক্লিক করে **GitHub Codespace শুরু করুন**। -2. রিপোজিটরির মূল ফোল্ডারে গিয়ে **_/app_** ডিরেক্টরিতে প্রবেশ করুন। -3. কনসোলে **`npm install`** চালান, তারপর **`npm start`** চালান। -4. যখন অ্যাপ চালু হবে, **"Open in Browser"** বোতামটি নির্বাচন করুন। -5. এখন আপনি **এডা লাভলেসের সাথে চ্যাট করতে পারবেন।** - -আরও বিস্তারিত জানতে, দেখুন **[অ্যাপের বিশদ ব্যাখ্যা](/lessons/01-intro-to-genai/translations/README.bn.md#ডিনোক্রেটিসের-সাথে-সাক্ষাৎ)**। - -> [!NOTE] -> আপনি যদি **লোকাল মেশিনে প্রকল্প চালান**, তাহলে **QuickStart গাইড অনুসরণ করুন** এবং [GitHub ব্যক্তিগত অ্যাক্সেস টোকেন](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) সেটআপ করে **কোডের মধ্যে টোকেন প্রতিস্থাপন করুন**। -> ## **বৃহৎ ভাষা মডেল (LLMs) সম্পর্কিত পরিচিত চ্যালেঞ্জ** -**এডা:** চল, প্রথমে আমরা **AI মডেল** সম্পর্কে আলোচনা করি, যা আমাদের যন্ত্রটি চালিত করবে। আমরা **একটি ডেটা রিট্রিভাল সিস্টেমের সাথে AI মডেল জোড়া লাগাবো**, যাতে প্রতিক্রিয়ার গুণগত মান আরও উন্নত হয়। - -তবে **RAG (Retrieval-Augmented Generation) পদ্ধতিতে যাওয়ার আগে**, আমাদের কিছু **গুরুত্বপূর্ণ চ্যালেঞ্জ বুঝতে হবে**। - -**LLM (Large Language Models)** বিশাল পরিমাণ টেক্সট ডেটার উপর প্রশিক্ষিত, যা **প্রাসঙ্গিক ও সঠিক উত্তর তৈরি করতে সক্ষম**। তবে, যেকোনো **ডেটা সোর্সের মতো**, এই মডেলগুলোর আউটপুট কখনো কখনো **ভুল, অসম্পূর্ণ বা বিভ্রান্তিকর** হতে পারে। এর কিছু কারণ হলো: - -- **পুরনো বা অপ্রচলিত তথ্য:** - মডেল প্রশিক্ষণের সময় যে ডেটা ব্যবহার করা হয়েছে, তা পুরনো হতে পারে এবং বর্তমানে সঠিক নাও থাকতে পারে। - -- **ভুল বা বিভ্রান্তিকর তথ্য:** - মডেলের প্রশিক্ষণে ব্যবহৃত কিছু সোর্সে ভুল তথ্য থাকতে পারে, যেমন **ভুয়া সংবাদ বা পক্ষপাতদুষ্ট মতামত**। - -- **অননুমোদিত বা অবিশ্বস্ত সূত্র:** - মডেল কখনো কখনো **কোনো তথ্য কতটা নির্ভরযোগ্য তা আলাদা করতে পারে না**। ফলে, **কম নির্ভরযোগ্য বা অননুমোদিত সূত্র থেকে তথ্য নিয়ে আসতে পারে**। - -এসব কারণে **LLM দ্বারা তৈরি তথ্যের সত্যতা যাচাই করা কঠিন হয়ে যায়**। **এখানেই RAG গুরুত্বপূর্ণ ভূমিকা রাখে।** - ---- - -**তুমি:** "তাহলে, আমাকে নিশ্চিত করতে হবে যে যন্ত্রটি **সঠিক তথ্য প্রদান করতে পারবে, এমনকি যখন এটি নিজেই নিশ্চিত নয়?**" - -**এডা:** "ঠিক তাই! **রিট্রিভাল-ভিত্তিক পদ্ধতি এবং জেনারেটিভ মডেলের শক্তি একত্রিত করে** আমরা একটি আরও উন্নত AI সিস্টেম তৈরি করতে পারবো।" -## **রিট্রিভাল-অগমেন্টেড জেনারেশন (RAG) এর মূল ধারণা** - -**এডা:** "আহ হ্যাঁ, এখন **RAG** নিয়ে নির্দিষ্টভাবে আলোচনা করার সময়। চল, কিছু **প্রাথমিক ধারণা** দিয়ে শুরু করি। - -**Retrieval-Augmented Generation (RAG)** হল **একটি শক্তিশালী কৌশল**, যা **প্রাকৃতিক ভাষা প্রক্রিয়াকরণ (NLP)-এর দুটি গুরুত্বপূর্ণ পদ্ধতির সমন্বয় করে**: -✅ **রিট্রিভাল-ভিত্তিক পদ্ধতি** (Retrieval-based methods) -✅ **জেনারেটিভ মডেল** (Generative models) - -এই **হাইব্রিড পদ্ধতি** AI-এর **উত্তরগুলোকে আরও প্রাসঙ্গিক ও তথ্যসমৃদ্ধ** করে তোলে এবং **LLM-সম্পর্কিত চ্যালেঞ্জগুলো দূর করতে সাহায্য করে**। - ---- - -### **RAG-এর দুটি মূল উপাদান:** - -1️⃣ **🔍 রিট্রিভার (Retriever):** - - এটি **বহিরাগত তথ্য সূত্র থেকে প্রাসঙ্গিক তথ্য সংগ্রহ করে**, ঠিক যেমন একটি **সার্চ ইঞ্জিন** কাজ করে। - - এই তথ্য **লেখা, ছবি, বা অন্য কোনো ডেটা ফরম্যাটে থাকতে পারে**, যদিও **পাঠ্য (text) সবচেয়ে বেশি ব্যবহৃত হয়**। - - এই তথ্য **AI-উৎপন্ন প্রতিক্রিয়া আরও নির্ভুল ও সমৃদ্ধ করতে সাহায্য করে**। - -2️⃣ **✍️ জেনারেটর (Generator):** - - এটি **রিট্রিভ করা তথ্য ব্যবহার করে** এমন **একটি উত্তর তৈরি করে, যা প্রাসঙ্গিক ও তথ্যপূর্ণ**। - ---- - -### **একটি RAG সিস্টেম কীভাবে কাজ করে?** - -![RAG সিস্টেমের চিত্র](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/05-rag/assets/rag.png) - -**ধাপে ধাপে কাজ করার পদ্ধতি:** - -1️⃣ **📥 ব্যবহারকারীর ইনপুট:** - - ব্যবহারকারী একটি প্রশ্ন করে। - -2️⃣ **🔎 রিট্রিভার:** - - এটি **এক বা একাধিক ডাটাবেস থেকে প্রাসঙ্গিক তথ্য খোঁজে**। - -3️⃣ **📌 অগমেন্টেড প্রম্পট:** - - **সংগৃহীত তথ্য ও ব্যবহারকারীর প্রশ্ন একত্রিত করে** **একটি উন্নত প্রম্পট তৈরি করা হয়**। - -4️⃣ **🤖 জেনারেটর:** - - LLM **এই উন্নত প্রম্পট ব্যবহার করে** **একটি সঠিক ও তথ্যসমৃদ্ধ উত্তর তৈরি করে**। - -👉 **এই প্রক্রিয়ার মাধ্যমে AI শুধু তার ট্রেনিং ডেটার উপর নির্ভর না করে, বরং বাহ্যিক ডেটা ব্যবহার করে আরও নির্ভুল উত্তর দিতে পারে।** - ---- - -**এডা:** "কোনো প্রশ্ন আছে?" - -**তুমি:** "তাহলে, **রিট্রিভার তথ্য খুঁজে বের করে, আর জেনারেটর সেটিকে ব্যবহার করে উত্তর তৈরি করে?**" - -**এডা:** "একদম ঠিক! তুমি ব্যাপারটা ভালোই বুঝতে পারছো। 😊" -## **বহিরাগত ডেটা সূত্র সংযুক্ত করা** - -**এডা:** "এখন যেহেতু আমরা **RAG-এর মৌলিক ধারণাগুলো** নিয়ে আলোচনা করেছি, এবার দেখা যাক কীভাবে **তোমার AI অ্যাপ্লিকেশনে বাহ্যিক ডেটা সংযুক্ত করা যায়।** - -👉 **বহিরাগত ডেটা সোর্স AI অ্যাপে সংযুক্ত করার বিভিন্ন উপায় রয়েছে**, যা মূলত **তোমার ব্যবহৃত ডেটার ধরন ও রিট্রিভাল পদ্ধতির জটিলতার উপর নির্ভর করে**। নিচে কিছু **সাধারণ কৌশল** উল্লেখ করা হলো: - -### ✅ **১. API (অ্যাপ্লিকেশন প্রোগ্রামিং ইন্টারফেস):** -- অনেক **বহিরাগত ডেটা উৎস API সরবরাহ করে**, যা **প্রোগ্রামেটিকভাবে তথ্য অ্যাক্সেস করার সুযোগ দেয়**। -- এই API ব্যবহার করে **রিয়েল-টাইমে তথ্য সংগ্রহ করা যায়** এবং তা **AI-এর প্রতিক্রিয়া উন্নত করতে ব্যবহার করা যায়।** - -### ✅ **২. ডাটাবেস (Database):** -- যদি তোমার কাছে **বিশাল পরিমাণ তথ্য** থাকে, যা AI-তে ব্যবহার করতে চাও, তাহলে সেটিকে **একটি ডাটাবেসে সংরক্ষণ করা যায়**। -- প্রয়োজনে **তথ্যকে কুয়েরির (query) মাধ্যমে দ্রুত রিট্রিভ করা যায়**, বিশেষ করে যদি এটি **গঠনমূলক (structured) তথ্য** হয়। - ---- - -### **📌 ডেটা প্রক্রিয়াকরণ ও ফরম্যাটিং:** -একবার **তুমি ডেটা সংযুক্ত করার পদ্ধতি নির্ধারণ করলেই**, পরবর্তী ধাপে **সেটিকে প্রক্রিয়াকরণ ও ফরম্যাট করা** দরকার, যাতে AI সেটি **সহজেই ব্যবহার করতে পারে**। -✔️ **ডেটা পরিষ্কার করা** (cleaning) -✔️ **উপযুক্ত ফরম্যাটে রূপান্তর করা** (যেমন সাধারণ পাঠ্য বা Markdown) -✔️ **ছোট অংশে বিভক্ত করা**, যাতে রিট্রিভাল আরও কার্যকর হয় - -> [!NOTE] -> **বহিরাগত ডেটা সংযুক্ত করার সময় গোপনীয়তা ও নিরাপত্তার বিষয়টি বিবেচনা করা জরুরি।** -> **তথ্য অ্যাক্সেস ও সংরক্ষণের জন্য প্রয়োজনীয় অনুমতি ও সুরক্ষা ব্যবস্থা নিশ্চিত করো**, যাতে ডেটা সুরক্ষিত থাকে এবং প্রাসঙ্গিক বিধিনিষেধের (regulations) সাথে সামঞ্জস্যপূর্ণ হয়। ---- - -### **🔍 ডেটা অনুসন্ধানের কৌশল (Search Techniques):** -তুমি যদি **ডাটাবেস ব্যবহার করো**, তাহলে **ডেটা অনুসন্ধানের পদ্ধতি** সম্পর্কে ভাবতে হবে, যাতে **সবচেয়ে প্রাসঙ্গিক তথ্য খুঁজে পাওয়া যায়**। - -👉 **সাধারণ অনুসন্ধান পদ্ধতি:** -✅ **কীওয়ার্ড সার্চ (Keyword Search)** – নির্দিষ্ট শব্দ বা বাক্যাংশের উপর ভিত্তি করে অনুসন্ধান। -✅ **ফুল-টেক্সট সার্চ (Full-Text Search)** – ডেটার সম্পূর্ণ বিষয়বস্তু স্ক্যান করে প্রাসঙ্গিক অংশ খুঁজে বের করা। -✅ **সিম্যান্টিক সার্চ (Semantic Search)** – **অর্থ ও প্রসঙ্গ বিশ্লেষণ করে আরও নিখুঁত অনুসন্ধান**। -✅ **ভেক্টর সার্চ (Vector Search)** – **ডিপ লার্নিং ভিত্তিক উন্নত অনুসন্ধান**, যা তথ্যের অর্থগত মিল খুঁজে বের করতে সক্ষম। - -**এডা:** "এই উন্নত অনুসন্ধান কৌশল নিয়ে আমরা পরবর্তী অধ্যায়ে আরও বিস্তারিত আলোচনা করবো।" - ---- - -### **🚀 ১৮৬০-এর দশকের দৃষ্টিকোণ থেকে API ও ডাটাবেস বোঝানো** - -**তুমি:** "তুমি কি API এবং ডাটাবেসের ধারণাটি ১৮৬০-এর ভাষায় ব্যাখ্যা করতে পারবে?" - -**এডা:** "অবশ্যই!" - -- **API হল একজন বার্তাবাহক (messenger)**, যে **এক জায়গা থেকে অন্য জায়গায় তথ্য পৌঁছে দেয়**। যেমন, তুমি ডাকপিয়ন পাঠালে, সে নির্দিষ্ট তথ্য নির্দিষ্ট ব্যক্তির কাছে পৌঁছে দেয়। -- **ডাটাবেস হল একটি বিশাল লাইব্রেরি**, যেখানে **সব বই (তথ্য) সংরক্ষিত থাকে**, এবং **যখন দরকার হয়, তখন সেখানে গিয়ে তুমি নির্দিষ্ট বই খুঁজে বের করতে পারো**। - -**তুমি:** "আহ, বুঝতে পারলাম! এটা বেশ যৌক্তিক শোনাচ্ছে।" 😊 -## **প্রম্পটকে আরও কার্যকর করা** - -**এডা:** "তুমি এখনো আমার সাথে আছো তো? ভালো! এবার আমরা **AI মডেলে পাঠানো প্রম্পট উন্নত করার দিকে এগোবো**।" - ---- - -### **AI মডেলের প্রম্পট উন্নত করা** - -**এডা:** "যখন তুমি **তোমার ডেটা থেকে তথ্য টানার একটি পদ্ধতি তৈরি করে ফেলবে**, তখন **সেই তথ্যকে AI মডেলের প্রম্পটে যোগ করতে পারবে**। - -এর মানে হলো, **রিট্রিভ করা তথ্য** ইনপুট পাঠ্যের সঙ্গে **প্রাসঙ্গিকভাবে মিশিয়ে দেওয়া** এবং AI-এর উত্তরকে **সঠিকভাবে নির্দেশনা দেওয়া**। - ---- - -### **উদাহরণ: গাড়ি সংক্রান্ত প্রশ্নোত্তর অ্যাপ** - -ধরো, তুমি একটি **গাড়ি সম্পর্কিত প্রশ্নের উত্তর দেওয়ার জন্য AI অ্যাপ তৈরি করছো**। তোমার প্রম্পট এ রকম হতে পারে: - -```text -## নির্দেশাবলী (Instructions) -নিচের উল্লিখিত তথ্যসূত্র ব্যবহার করে শুধুমাত্র গাড়ি সম্পর্কিত প্রশ্নের উত্তর দাও। -যদি নির্ভরযোগ্য তথ্য না পাওয়া যায়, তাহলে বলো যে তুমি জানো না। -উত্তর সংক্ষিপ্ত ও সরাসরি হওয়া উচিত। -## তথ্যসূত্র (Sources) -<এখানে রিট্রিভ করা তথ্য যোগ করো> -## প্রশ্ন (Question) -<এখানে ব্যবহারকারীর প্রশ্ন যোগ করো> -``` - ---- - -### **কেন এই কৌশল কার্যকর?** - -✔️ **AI-কে অতিরিক্ত তথ্য ও প্রসঙ্গ প্রদান করে** এটি **উত্তর তৈরির প্রক্রিয়াকে আরও সুনির্দিষ্ট করে**। -✔️ **AI নিশ্চিতভাবে প্রাসঙ্গিক এবং সঠিক উত্তর দিতে পারে**, কারণ এটি **তথ্যের অভাব হলে অনুমানভিত্তিক উত্তর দেবে না**। -✔️ **মডেল ভুল তথ্য তৈরি করতে পারে না, কারণ নির্দেশনা দেওয়া আছে যে যথেষ্ট ডেটা না থাকলে ‘জানি না’ বলতে হবে।** - ---- - -> [!💡 **উপদেশ**] -> **এই অংশটি লক্ষ্য করো:** -> `If there's not enough data in provided sources, say that you don't know.` -> -> এই কৌশলকে **"Escape Hatch"** বলা হয়। -> **এর উদ্দেশ্য:** -> ✅ **AI যেন অনুমানভিত্তিক ভুল তথ্য না দেয়।** -> ✅ **তথ্য যদি অপর্যাপ্ত হয়, তাহলে মডেল স্বীকার করবে যে এটি জানে না।** -> ✅ **AI জেনারেটেড কনটেন্টের মান নিশ্চিত করা।** ---- - -### **RAG হল একটি উন্নত **প্রম্পট ইঞ্জিনিয়ারিং (Prompt Engineering)** কৌশল - -> **এডা:** "**RAG-কে তুমি *উন্নত প্রম্পট ইঞ্জিনিয়ারিং* হিসেবেও ভাবতে পারো**। এতে AI **রিয়েল-টাইম ডেটা ব্যবহার করতে পারে**, যা সাধারণ ট্রেনিং ডেটার সীমাবদ্ধতাকে অতিক্রম করে।" 🚀 -> ### **কোডের উদাহরণ** -**এডা:** "অনুশীলনই দক্ষতার চাবিকাঠি, তাই চল, আমরা যা শিখেছি তা **একটি বাস্তব উদাহরণের মাধ্যমে প্রয়োগ করি**। - -আমরা **একটি সাধারণ তথ্য পুনরুদ্ধার (Retrieval) সিস্টেম তৈরি করব**, যা **একটি JavaScript অ্যাপে সংযুক্ত থাকবে**। - -- এটি **একটি [CSV](https://fr.wikipedia.org/wiki/Comma-separated_values) ফাইল** ব্যবহার করে **হাইব্রিড গাড়ির তথ্য সংরক্ষণ** করবে। -- **একটি মৌলিক অনুসন্ধান অ্যালগরিদম** ব্যবহার করে **ব্যবহারকারীর প্রশ্ন অনুযায়ী প্রাসঙ্গিক তথ্য খুঁজবে**। - ---- - -### **🚀 কোড উদাহরণ:** - -```javascript -// এই কোডটি Retrieval-Augmented Generation (RAG) পদ্ধতির ব্যবহার দেখায়। -// এটি একটি CSV ফাইল থেকে তথ্য রিট্রিভ করে, ব্যবহারকারীর প্রশ্নের সাথে মেলে, -// এবং প্রাপ্ত তথ্যের ভিত্তিতে একটি উত্তর তৈরি করে। - -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import process from "node:process"; -import fs from "node:fs"; -import { OpenAI } from "openai"; - -// স্ক্রিপ্টের ডিরেক্টরিতে বর্তমান ওয়ার্কিং ডিরেক্টরি পরিবর্তন করা -const __dirname = dirname(fileURLToPath(import.meta.url)); -process.chdir(__dirname); - -// 1. ব্যবহারকারীর প্রশ্ন -// ------------------------- - -const question = `what's the fastest prius`; - -// 2. রিট্রিভাল কম্পোনেন্ট: তথ্য অনুসন্ধান করা -// ------------------------------------------------ - -// CSV ফাইল লোড করে এটিকে অবজেক্ট অ্যারে হিসাবে রূপান্তর করা -const rows = fs.readFileSync("./hybrid.csv", "utf8").split("\n"); -const columns = rows[0].split(","); - -// ব্যবহারকারীর প্রশ্ন অনুযায়ী অনুসন্ধান -const words = question - .toLowerCase() - .replaceAll(/[.?!()'":,]/g, "") - .split(" ") - .filter((word) => word.length > 2); - -const matches = rows.slice(1).filter((row) => - words.some((word) => row.toLowerCase().includes(word)) -); - -// Markdown টেবিল আকারে ডেটা ফরম্যাট করা, কারণ AI মডেল Markdown বোঝে -const table = - `| ${columns.join(" | ")} |\n` + - `|${columns.map(() => "---").join(" | ")}|\n` + - matches.map((row) => `| ${row.replaceAll(",", " | ")} |\n`).join(""); - -console.log(`Found ${matches.length} matches:`); -console.log(table); - -// 3. প্রম্পট অগমেন্টেশন: অনুসন্ধান ফলাফল যুক্ত করে একটি প্রম্পট তৈরি করা -// --------------------------------------------------------------------------- - -const augmentedPrompt = ` -## Instructions -Answer questions about hybrid cars using only the sources below. -If there's not enough data in provided sources, say that you don't know. -Be brief and straight to the point. -## Sources -${table} -## Question -${question} -`; - -// 4. জেনারেটর কম্পোনেন্ট: অনুসন্ধান ফলাফল ব্যবহার করে উত্তর তৈরি করা -// ---------------------------------------------------------------------- - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - -const chunks = await openai.chat.completions.create({ - model: "gpt-4o-mini", - messages: [{ role: "user", content: augmentedPrompt }], - stream: true, -}); - -console.log(`Answer for "${question}":`); - -for await (const chunk of chunks) { - process.stdout.write(chunk.choices[0].delta.content ?? ""); -} -``` ---- -### 📂 কোথায় কোডটি পাবে? -👉 [`example/rag-cars.js`](/lessons/05-rag/example/rag-cars.js)** ফাইলে এই কোডটি সংরক্ষিত আছে। -👉 এটি **[`hybrid.csv`](/lessons/05-rag/example/hybrid.csv)** নামের ফাইল থেকে **তথ্য পুনরুদ্ধার করে। ---- -### 🔍 কোড কীভাবে কাজ করে? -✅ 📥 ব্যবহারকারীর ইনপুট: - - ব্যবহারকারী **একটি প্রশ্ন করে** (যেমন: `"what's the fastest prius"`)। -✅ 🔎 রিট্রিভার: - - কোডটি CSV ফাইল স্ক্যান করে এবং প্রশ্নের সাথে মিল খুঁজে পায়। - - এটি Markdown ফরম্যাটে তথ্য সাজিয়ে উপস্থাপন করে। -✅ 📝 প্রম্পট অগমেন্টেশন: - - প্রাপ্ত তথ্যের ভিত্তিতে একটি উন্নত প্রম্পট তৈরি করা হয়**, যা **AI-এর জবাবকে নির্দিষ্ট করে। -✅ 🤖 জেনারেটর: - - AI মডেল (GPT-4o-mini) ব্যবহার করে তথ্যের উপর ভিত্তি করে উত্তর তৈরি করে। ---- -### **🔹 আউটপুটের একটি উদাহরণ:** -```text -Found 1 matches: -| ব্যক্তি | সময়কাল | বর্ণনা | -|---|---|---| -| লিওনার্দো দা ভিঞ্চি | ১৫শ শতক | ইতালিয়ান পলিম্যাথ, যিনি শিল্পকলা ও আবিষ্কারের জন্য পরিচিত। | -| আইজাক নিউটন | ১৭শ শতক | ইংরেজ গণিতবিদ ও পদার্থবিদ, যিনি গতি ও মহাকর্ষের সূত্র প্রণয়ন করেছেন। | -``` ---- -### **🗣️ আলোচনা** -**তুমি:** "এটি অসাধারণ! আমি এখন বুঝতে পারছি, কিভাবে এটি যন্ত্রের সাথে কাজ করতে পারে, বা বরং এটি কীভাবে ইতিমধ্যেই কাজ করেছে অথবা ভবিষ্যতে করবে। সময় ভ্রমণ সত্যিই বিভ্রান্তিকর! *হুমম...*" -**এডা:** "চিন্তা কোরো না, তুমি দারুণ করছো! এখন চল, আমরা পরবর্তী ধাপে এগোই। -## **অ্যাসাইনমেন্ট - এডা ও চার্লসকে সাহায্য করা** -RAG সম্পর্কে শেখার পর, এখন তুমি এডা ও চার্লসের যন্ত্রটি উন্নত করতে সাহায্য করতে প্রস্তুত। -কিন্তু কাছ থেকে দেখার পর, তোমার মনে হচ্ছে এই যন্ত্রটি বেশ পরিচিত! ---- -### **একটি অদ্ভুত উপলব্ধি...** -**তুমি:** "টাইম বিটল, তুমি কি জানো এটি কী?" -**টাইম বিটল:** "অবশ্যই, এটা **আমি**! অথবা এটা একদিন হবো। কিন্তু এখনো আমার কিছু অংশ নেই... আসলে অনেক অংশই নেই। আমার একটা খোলসও পর্যন্ত নেই!" -**এডা:** "টাইম বিটল একটি **যন্ত্র, যা তোমাকে সময় ও স্থানের মধ্যে ভ্রমণ করতে সাহায্য করবে**—যদি আমরা এটি **সঠিকভাবে চালাতে পারি**। -আমাদের এতে **একটি নতুন ফিচার যোগ করতে হবে—Retrieval-Augmented Generation (RAG) মডিউল**। -এটি আমাদের **বিভিন্ন সময়কাল থেকে তথ্য সংগ্রহ করতে সাহায্য করবে**, যখন তুমি ভ্রমণ করবে। আমরা চাই, আমাদের তথ্যসূত্র যথাসম্ভব বিস্তৃত হোক, **উইকিপিডিয়া একটি ভালো সূচনা হবে।**" ---- -### **তুমি কী করতে পারো?** -**তুমি:** "আমি কী করতে পারি?" -**এডা:** "এখানে একটি **উদাহরণ কোড** আছে, যা **উইকিপিডিয়া থেকে টিম বার্নার্স-লির তথ্য** সংগ্রহ করে। -👉 *একদিন টিম বার্নার্স-লি খুব গুরুত্বপূর্ণ ব্যক্তি হবেন!* -```javascript -const response = await fetch('https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&redirects=true&explaintext&titles=Tim%20Berners-Lee'); -const data = await response.json(); -const text = Object.values(data.query.pages)[0]?.extract; -``` ---- -### **কেউ ভবিষ্যতে ভ্রমণ করেছে?** -**তুমি:** "আমার মনে হচ্ছে, ভবিষ্যতে আমিই একমাত্র ভ্রমণকারী নই, তাই তো?" -**এডা:** "..." ---- -## **সমাধান** -👉 **[সমাধান দেখুন](/lessons/05-rag/solution/rag-www.js)** ---- -## **জ্ঞান পরীক্ষা (Knowledge Check)** -**প্রশ্ন:** **একটি RAG সিস্টেমে "Retriever"-এর ভূমিকা কী?** -A. **Retriever ইনপুট ডেটার ভিত্তিতে উত্তর তৈরি করে।** -B. **Retriever মডেলের ট্রেনিং ডেটার ভিত্তিতে প্রাসঙ্গিক তথ্য তৈরি করে।** -C. **Retriever বাহ্যিক তথ্যসূত্র থেকে প্রাসঙ্গিক তথ্য খুঁজে বের করে।** ✅ -👉 **[কুইজ সমাধান](/lessons/05-rag/solution/solution-quiz.md)** ---- -## **স্বশিক্ষা সংস্থান (Self-Study Resources)** -📖 **বিস্তারিত গাইড:** -- [Retrieval-Augmented Generation এবং ইনডেক্সিং](https://learn.microsoft.com/azure/ai-studio/concepts/retrieval-augmented-generation) -📌 **নমুনা অ্যাপ:** -- [Serverless AI Chat with RAG](https://github.com/Azure-Samples/serverless-chat-langchainjs/) -- [Ask Youtube: একটি RAG-ভিত্তিক YouTube Q&A API](https://github.com/Azure-Samples/langchainjs-quickstart-demo) -🎯 **ওয়ার্কশপ:** -- [Create your own ChatGPT with RAG](https://moaw.dev/workshop/gh:azure-samples/azure-openai-rag-workshop/docs/workshop-qdrant.md) diff --git a/lessons/05-rag/translations/README.es.md b/lessons/05-rag/translations/README.es.md deleted file mode 100644 index 8efdbf87..00000000 --- a/lessons/05-rag/translations/README.es.md +++ /dev/null @@ -1 +0,0 @@ -traduce esto diff --git a/lessons/05-rag/translations/README.fr.md b/lessons/05-rag/translations/README.fr.md deleted file mode 100644 index 2f1f5c27..00000000 --- a/lessons/05-rag/translations/README.fr.md +++ /dev/null @@ -1,343 +0,0 @@ -# Leçon 5 : Discutez avec vos données grâce à la génération augmentée par récupération (RAG) - -Dans ce chapitre, vous apprendrez : - -* Les bases de la génération augmentée par récupération (RAG) et comment cela peut être utilisé pour améliorer les réponses des modèles d'IA générative. -* Comment intégrer des sources de données externes à votre application d'IA. -* Comment exploiter vos données pour améliorer la pertinence et l'exactitude des contenus générés par l'IA. - -## Configuration - -Si ce n'est pas encore fait, configurez votre environnement de développement. Voici comment procéder : [Configurez votre environnement](/docs/setup/README.md). - -## Ressources associées - -[![Regardez une courte vidéo sur RAG](https://img.youtube.com/vi/xkFOmx5yxIA/0.jpg)](https://www.youtube.com/watch?v=xkFOmx5yxIA\&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk\&index=4) - -*Cette vidéo explique la génération augmentée par récupération (RAG), une méthode qui aide l'IA à utiliser vos contenus en complément de ses données d'entraînement pour de meilleurs résultats.* - -*🎥 Cliquez sur l'image ci-dessus pour regarder une courte vidéo sur la génération augmentée par récupération, RAG.* - -💼 Diapositives : [Génération augmentée par récupération, RAG](/videos/slides/03-rag.pptx) - -## Narration - Genèse - -> [!NOTE] -> *Notre histoire jusqu'à présent. Vous êtes un mécanicien de Londres des années 1860. Vous travailliez sur votre automate et avez reçu une lettre de Charles Babbage qui vous a finalement conduit dans une bibliothèque où vous avez pris un appareil de voyage dans le temps. À travers vos voyages dans le temps, vous vous êtes retrouvé à Florence, où vous avez rencontré Léonard de Vinci. Vous êtes également allé dans l'empire aztèque, et c'est là que l'histoire continue.* -> -> Reportez-vous à [Leçon 1](/lessons/01-intro-to-genai/README.md) si vous souhaitez reprendre l'histoire depuis le début. - -> [!NOTE] -> Bien que nous recommandions de suivre l'histoire (c'est amusant !), [cliquez ici](#interact-with-ada-lovelace) si vous préférez aller directement au contenu technique. - -**Vous** : « Leonardo, il est temps de partir », avez-vous dit en appuyant sur le bouton. L'appareil s'est mis à bourdonner et une voix mécanique a résonné : « Il est temps de rentrer à la maison, il est temps pour la 'genèse'. » - -**Leonardo :** « Genèse ? Che cosa significa ? » Leonardo demanda, confus. Avant que vous ne puissiez répondre, le monde se dissout en un flou de couleurs et de sons, le temple disparaissant tandis que vous étiez propulsé à travers le temps. - -Vous atterrissez dans le jardin, il est tard dans la nuit avec un brouillard épais, et des lumières étranges scintillent au loin. Le manoir se dresse devant vous. Leonardo regarde autour de lui, les yeux écarquillés d'émerveillement. - -![Vieux manoir montré dans un épais brouillard](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/05-rag/assets/mansion.jpeg) - -### Fuir les chiens - -Vous entendez des aboiements et le bruit des chiens qui courent vers vous. Vous vous tournez vers Leonardo : "Nous devons entrer, maintenant !" - -![Fuir les chiens](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/05-rag/assets/dogs.jpeg) - -En atteignant la porte du manoir, elle s'ouvre brusquement, et une paire de serviteurs se précipitent dehors. Après vous avoir observés, ils vous font signe de les suivre. - -Vous vous retrouvez face à Ada Lovelace, ses yeux brillant de curiosité. - -### Rencontre avec Ada et Charles - -**Ada :** « Ah, il était temps que vous arriviez », dit-elle chaleureusement. « Nous avons besoin de vous pour une commission. » - -**Vous :** « Il était temps ? Vous continuez de dire cela. Dinocrates disait la même chose, mais je ne suis pas sûr de ce que vous voulez dire. » - -**Ada :** « Silence, pas de temps pour cela maintenant, nous devons parler de l'appareil que vous tenez. Charles, expliquez-leur. » - -**Vous :** « Mais... » - -![Ada Lovelace et Charles Babbage travaillant sur un appareil](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/05-rag/assets/ada.jpeg) - -Charles Babbage s'avance et examine le Scarabée Temporel dans votre main. "Cet appareil est remarquable, mais il est un peu défectueux, n'est-ce pas ? Vous l'avez sûrement remarqué." - -Leonardo hocha la tête, "Sì, il agit de manière étrange." - -**Ada :** « L'appareil n'est pas encore prêt, nous devons lui donner plus de capacités. Nous devons le rendre plus intelligent, plus conscient du monde qui l'entoure. L'idée est qu'il puisse récupérer des informations de différentes périodes et les utiliser pour produire des réponses précises et pertinentes. Pouvez-vous nous aider ? » - -**Vous :** « Bien sûr, on dirait que nous devons *augmenter* les réponses de l'appareil avec des données, cela semble logique. » - -**Ada :** « Parlons d'un concept que j'aimerais appeler RAG, ou génération augmentée par récupération. » - -## Interagir avec Ada Lovelace - -If you want to interact with Ada, run the [Characters](/app/README.md) app. - -> [!IMPORTANT] -> Ceci est entièrement fictif ; les réponses sont générées par une IA. -> [Clause de non-responsabilité sur l'IA responsable](/README.md#responsible-ai-disclaimer) - -![Ada Lovelace](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/05-rag/assets/ada-2.jpeg) - -**Étapes** : - -1. Lancez un [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript). -2. Naviguez vers */app* à la racine du dépôt. -3. Localisez la console et exécutez `npm install` suivi de `npm start`. -4. Une fois que l'application est ouverte, sélectionnez le bouton "Ouvrir dans le navigateur". -5. Discutez avec Ada. - -Pour une explication plus détaillée de l'application, voir [Explication détaillée de l'application](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). - -> [!NOTE] -> Si vous exécutez le projet localement sur votre machine, veuillez consulter le guide de démarrage rapide pour configurer un [token d'accès personnel GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) et remplacer la clé dans le code. - -## Défis connus avec les modèles de langage à grande échelle (LLMs) - -**Ada :** « Commençons par discuter de l'IA que nous utiliserons pour alimenter l'appareil. Nous nous appuierons sur des “modèles d'IA” associés à un système de récupération de données pour améliorer la qualité des réponses. » - -Tout d'abord, vous devez traiter certaines difficultés avant d'entrer dans les détails de RAG. Ces modèles, entraînés sur de vastes ensembles de données textuelles, peuvent produire des réponses pertinentes et correctes. Mais, comme toute source de données, leurs sorties peuvent être inexactes, incomplètes ou trompeuses en raison de divers facteurs. - -* **Sources obsolètes :** Les données utilisées pour entraîner le modèle peuvent être dépassées et ne plus être exactes. -* **Informations erronées ou inexactes :** Les sources utilisées pour entraîner le modèle peuvent contenir des informations incorrectes ou trompeuses, telles que des fake news ou des opinions biaisées. -* **Sources non autoritaires :** Le modèle peut ne pas être en mesure de distinguer entre les sources autoritaires et non autoritaires dans ses données d'entraînement, ce qui conduit à des informations peu fiables. - -Cela rend difficile de déterminer si les informations générées par un LLM sont correctes ou non. C'est là qu'intervient RAG. - -**Vous :** « Donc, je dois m'assurer que l'appareil peut fournir des informations exactes, même quand il n'est pas sûr de la réponse ? » - -**Ada :** « Oui, c'est l'idée. En combinant les forces des méthodes basées sur la récupération et des modèles génératifs, nous obtenons un système d'IA meilleur. » - -## Génération augmentée par récupération, concepts de base - -**Ada :** « Ah oui, il est temps de discuter spécifiquement de RAG. Commençons par quelques bases : » - -La génération augmentée par récupération (RAG) est une technique puissante qui combine les forces de deux approches différentes en traitement du langage naturel : les méthodes basées sur la récupération et les modèles génératifs. Cette approche hybride permet de générer des réponses à la fois contextuellement pertinentes et riches en contenu, aidant ainsi à atténuer certains des défis connus avec les LLMs. - -Au cœur de RAG, il y a deux composants principaux : un **récupérateur** et un **générateur**. - -* **Le récupérateur :** il est responsable de trouver des informations pertinentes à partir de sources de données externes qui peuvent être utilisées pour améliorer les réponses générées par l'IA, comme un moteur de recherche. Ces informations peuvent prendre la forme de texte, d'images ou de tout autre type de données pertinentes pour le contexte de la conversation, bien que le texte soit le type de données le plus couramment utilisé. - -* **Le générateur :** il prend les informations récupérées et les utilise pour produire une réponse contextuellement pertinente et informative. - -Voici un schéma illustrant le fonctionnement d'un système RAG : - -![Schéma d'un système RAG](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/05-rag/assets/rag.png) - -1. **Entrée utilisateur :** L'utilisateur pose une question. -2. **Récupérateur :** Le composant récupérateur recherche des informations pertinentes en utilisant une ou plusieurs bases de connaissances. -3. **Prompt augmenté :** Les informations récupérées sont combinées avec la question et le contexte utilisateur pour créer un prompt augmenté. -4. **Générateur :** Le LLM utilise le prompt augmenté pour produire une réponse. - -Cette combinaison permet d'obtenir des réponses plus précises et pertinentes en utilisant des données que vous fournissez plutôt que de se fier uniquement aux données d'entraînement du modèle. - -**Ada :** « Des questions ? » - -**Vous :** « Donc le récupérateur trouve les informations et le générateur les utilise pour générer une réponse ? » - -**Ada :** « Exactement, vous commencez à comprendre. » - -## Intégration de sources de données externes - -**Ada :** « Maintenant que nous avons couvert les bases de RAG, parlons de la manière dont vous pouvez intégrer des sources de données externes dans votre application d'IA. » - -L'intégration de sources de données externes dans votre application d'IA peut être réalisée de différentes manières, en fonction du type de données que vous souhaitez utiliser et de la complexité du mécanisme de récupération. Voici quelques méthodes courantes : - -* **APIs :** De nombreuses sources de données externes fournissent des APIs qui vous permettent d'accéder à leurs données de manière programmatique. Vous pouvez utiliser ces APIs pour récupérer des informations en temps réel et les utiliser pour améliorer les réponses générées par l'IA. - -* **Bases de données :** Si vous disposez d'un grand volume de données que vous souhaitez utiliser pour la récupération, vous pouvez les stocker dans une base de données et les interroger selon les besoins. Cela peut être utile pour les données structurées devant être accessibles rapidement. - -Une fois que vous avez choisi une méthode pour intégrer des sources de données externes, vous devrez peut-être également envisager comment prétraiter et formater les données afin qu'elles puissent être facilement utilisées par le modèle d'IA. Cela peut impliquer de nettoyer les données, de les convertir dans un format approprié (comme du texte brut ou Markdown) ou de les diviser en segments plus petits pour une récupération plus facile. - -> [!NOTE] -> Lorsque vous intégrez des sources de données externes dans votre application d'IA, il est important de prendre en compte les implications en termes de confidentialité et de sécurité liées à l'accès et au stockage des données. Assurez-vous de disposer des autorisations et des protections nécessaires pour protéger les données et de respecter les réglementations en vigueur. - -Si vous utilisez une base de données, vous devez également réfléchir à la manière dont vous souhaitez *rechercher vos données* pour récupérer les informations les plus pertinentes. Cela peut se faire via une recherche par mot-clé, une recherche en texte intégral ou via des techniques plus avancées comme la recherche sémantique ou vectorielle qui peuvent nécessiter un indexage spécifique. Nous aborderons les techniques de recherche avancées dans une leçon ultérieure. - -**Vous :** « Pouvez-vous expliquer des termes comme API et Bases de données dans des termes plus proches des années 1860 ? » - -**Ada :** « Bien sûr, une API est comme un messager qui transmet un message d'un endroit à un autre, et une base de données est comme une bibliothèque où vous stockez tous vos livres. » - -**Vous :** « Ah, je vois, cela a du sens. » - -## Augmenter le prompt - -**Ada :** « Vous êtes toujours avec moi ? Bien, passons à l'étape suivante, essayons d'améliorer le prompt envoyé au modèle d'IA. » - -**Ada :** « Une fois que vous avez mis en place un moyen d'extraire des informations de vos données, vous pouvez les ajouter au prompt du modèle d'IA. Mélangez simplement les informations récupérées avec le texte d'entrée en ajoutant un contexte ou des directives supplémentaires pour orienter la réponse de l'IA. » - -Par exemple, si vous construisez une application pour répondre à des questions sur les voitures, vous pourriez avoir un prompt comme celui-ci : - -```text - -## Instructions -Answer questions about cars using only the sources below. -If there's not enough data in provided sources, say that you don't know. -Be brief and straight to the point. - -## Sources - - -## Question - -``` - -En fournissant au modèle d'IA un contexte supplémentaire et des informations, vous pouvez aider à orienter le processus de génération et à garantir que les réponses soient précises et pertinentes par rapport au sujet abordé. - -> [!TIP] -> Notez cette partie du prompt : `If there's not enough data in provided sources, say that you don't know.`. Cela est important pour éviter que l'IA génère des informations incorrectes lorsqu'il n'y a pas suffisamment de données pour fournir une réponse fiable. Cette technique est appelée une *échappatoire* et constitue une bonne pratique pour garantir la qualité du contenu généré. - -La RAG peut être considérée comme une forme avancée de *conception de prompt*. - -### Exemple de code - -**Ada :** La pratique rend parfait, alors appliquons ce que nous avons appris avec un exemple. Nous allons intégrer un système de récupération simple dans une application JavaScript en utilisant un fichier [CSV](https://en.wikipedia.org/wiki/Comma-separated_values) contenant des données sur les voitures hybrides et un algorithme de recherche basique pour extraire des informations pertinentes en fonction d'une question posée par l'utilisateur. - -```javascript -// This example demonstrates how to use the Retrieval Augmented Generation (RAG) -// to answer questions based on a hybrid car data set. -// The code below reads the CSV file, searches for matches to the user question, -// and then generates a response based on the information found. - -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import process from "node:process"; -import fs from "node:fs"; -import { OpenAI } from "openai"; - -// Change the current working directory to the directory of the script -const __dirname = dirname(fileURLToPath(import.meta.url)); -process.chdir(__dirname); - -// 1. Ask a question about hybrid cars -// ----------------------------------- - -const question = `what's the fastest prius`; - -// 2. Retriever component: search the data for relevant information -// ---------------------------------------------------------------- - -// Load CSV data as an array of objects -const rows = fs.readFileSync("./hybrid.csv", "utf8").split("\n"); -const columns = rows[0].split(","); - -// Search the data using a very naive search -const words = question - .toLowerCase() - .replaceAll(/[.?!()'":,]/g, "") - .split(" ") - .filter((word) => word.length > 2); -const matches = rows.slice(1).filter((row) => words.some((word) => row.toLowerCase().includes(word))); - -// Format as a markdown table, since language models understand markdown -const table = - `| ${columns.join(" | ")} |\n` + - `|${columns.map(() => "---").join(" | ")}|\n` + - matches.map((row) => `| ${row.replaceAll(",", " | ")} |\n`).join(""); - -console.log(`Found ${matches.length} matches:`); -console.log(table); - -// 3. Context augmentation: create a combined prompt with the search results -// -------------------------------------------------------------------------- - -const augmentedPrompt = ` -## Instructions -Answer questions about a time period or characters from said time period using only the sources below. -If there's not enough data in provided sources, say that you don't know. -Be brief and straight to the point. - -## Sources -${table} - -## Question -${question} -`; - -// 4. Generator component: use the search results to generate a response -// --------------------------------------------------------------------- - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - -const chunks = await openai.chat.completions.create({ - model: "gpt-4o-mini", - messages: [{ role: "user", content: augmentedPrompt }], - stream: true, -}); - -console.log(`Answer for "${question}":`); - -for await (const chunk of chunks) { - process.stdout.write(chunk.choices[0].delta.content ?? ""); -} -``` - -Vous pouvez trouver ce code dans le fichier [`example/rag-cars.js`](/lessons/05-rag/example/rag-cars.js) accompagné du fichier [`hybrid.csv`](/lessons/05-rag/example/hybrid.csv) contenant les données utilisées pour la récupération. - -**Ada :** « Une fois que vous exécutez ce code, vous devriez voir les données trouvées dans le fichier CSV par le récupérateur, formatées sous forme d'une table Markdown, suivies de la réponse générée par l'IA à la question. Essayez de changer la question pour voir comment les données récupérées et la réponse évoluent. Vous pouvez également poser des questions sur des sujets non liés pour voir comment le modèle d'IA les gère. » - -```text -Example of the output: - -Found 1 matches: -| Person | Time Period | Description | -|---|---|---| -| Leonardo Da Vinci | 15th century | Italian polymath known for his art and inventions. | -| Isaac Newton | 17th century | English mathematician and physicist who formulated the laws of motion and universal gravitation. | -``` - -**Vous :** « C'est génial, je vois comment cela peut être utile lorsque l'on utilise l'appareil, ou plutôt comment cela l'a déjà été ou le sera : le voyage dans le temps est déroutant *soupir*. » - -**Ada :** « Voilà, voilà, vous vous en sortez très bien. Passons à l'étape suivante. » - -## Exercice - Aider Ada et Charles - -Maintenant que vous avez appris sur RAG, vous êtes prêt à aider Ada et Charles avec leur appareil. Cependant, en examinant de plus près l'appareil, il semble familier. - -**Vous :** « Scarabée Temporel, sais-tu ce que c'est ? » - -**Scarabée Temporel :** « Bien sûr, c'est moi, ou ce sera moi. Il me manque quelques pièces cependant. En y repensant, il me manque beaucoup de pièces, je n'ai même pas encore de carapace. » - -**Ada :** « Le Scarabée Temporel est un appareil qui vous permet de voyager dans le temps et l'espace, une fois que nous l'aurons fait fonctionner correctement. Comme je le disais, nous devons lui ajouter une nouvelle fonctionnalité, un module de génération augmentée par récupération (RAG). Cela nous aidera à récupérer des informations et le contexte nécessaire à partir de différentes époques au fur et à mesure de vos voyages. Nous voulons nous assurer de référencer toutes sortes de sources, Wikipédia est un bon point de départ. » - -**Vous :** « Que voulez-vous que je fasse ? » - -**Ada :** « Voici un exemple de code qui récupère des informations textuelles sur Tim Berners-Lee depuis Wikipédia. Tim sera très important un jour. » - -```javascript -const response = await fetch('https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&redirects=true&explaintext&titles=Tim%20Berners-Lee'); -const data = await response.json(); -const text = Object.values(data.query.pages)[0]?.extract; -``` - -**Vous :** « Je suppose que je ne suis pas le seul à être allé dans le futur ? » - -**Ada :** « ... » - -## Solution - -[Solution](/lessons/05-rag/solution/rag-www.js) - -## Vérification des connaissances - -**Question** : Quel est le rôle du récupérateur dans un système RAG ? - -A. Le récupérateur génère des réponses en fonction des données entrées. - -B. Le récupérateur génère des informations pertinentes en se basant sur les données d'entraînement du modèle. - -C. Le récupérateur trouve des informations pertinentes à partir de sources de données externes. - -[Quiz solution](/lessons/05-rag/solution/solution-quiz.md) - -## Ressources d'auto-apprentissage - -* [Génération augmentée par récupération et Indexes](https://learn.microsoft.com/azure/ai-studio/concepts/retrieval-augmented-generation) -* **Exemples d'applications** : - * [Chat IA sans serveur avec RAG](https://github.com/Azure-Samples/serverless-chat-langchainjs/) - * [Demandez à Youtube : Une API de questions-réponses basée sur RAG pour Youtube](https://github.com/Azure-Samples/langchainjs-quickstart-demo) -* [Atelier complet : Créez votre propre ChatGPT avec RAG](https://moaw.dev/workshop/gh\:azure-samples/azure-openai-rag-workshop/docs/workshop-qdrant.md) diff --git a/lessons/05-rag/translations/README.pt-br.md b/lessons/05-rag/translations/README.pt-br.md deleted file mode 100644 index 17c7a7d6..00000000 --- a/lessons/05-rag/translations/README.pt-br.md +++ /dev/null @@ -1,351 +0,0 @@ -# Lição 5: Converse com seus dados usando Geração Aumentada por Recuperação (RAG) - -Neste capítulo você vai aprender: - -- Os fundamentos da Geração Aumentada por Recuperação (RAG) e como ela pode ser usada para melhorar as respostas dos modelos de IA generativa. -- Como integrar fontes de dados externas em sua aplicação de IA. -- Como aproveitar seus dados para melhorar a relevância e precisão do conteúdo gerado por IA. - -## Configuração - -Se você ainda não configurou seu ambiente de desenvolvimento, veja como fazer: [Configure seu ambiente](/docs/setup/README.md). - -## Recursos relacionados - -[![Assista a um vídeo curto sobre RAG](https://img.youtube.com/vi/xkFOmx5yxIA/0.jpg)](https://www.youtube.com/watch?v=xkFOmx5yxIA&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=4) - -_Este vídeo explica a Geração Aumentada por Recuperação (RAG), um método que ajuda a IA a usar seu conteúdo junto com seus dados de treinamento para obter melhores resultados._ - -*🎥 Clique na imagem acima para assistir a um vídeo curto sobre geração aumentada por recuperação, RAG* - -💼 Slides: [Geração aumentada por recuperação, RAG](/videos/slides/03-rag.pptx) - -## Narrativa - Gênesis - -> [!NOTE] -> _Nossa história até agora. Você é um mecânico da Londres dos anos 1860. Você estava trabalhando em seu autômato e recebeu uma carta de Charles Babbage que acabou levando você a uma biblioteca onde você pegou um dispositivo de viagem no tempo. Durante suas viagens no tempo, você acabou em Florença, onde conheceu Leonardo Da Vinci. Você também foi ao império Asteca e é aqui que a história continua._ -> -> Veja a [Lição 1](/lessons/01-intro-to-genai/README.md) se quiser acompanhar a história desde o início. - -> [!NOTE] -> Embora recomendemos seguir a história (é divertido!), [clique aqui](#interaja-com-ada-lovelace) se preferir ir direto para o conteúdo técnico. - -**Você**: "Leonardo, é hora de ir," você disse, pressionando o botão. O dispositivo começou a funcionar, e uma voz mecânica ecoou: "É hora de ir para casa, é hora da 'gênese'." - -**Leonardo:** "Gênesis? Che cosa significa?" Leonardo perguntou, confuso. Antes que você pudesse responder, o mundo se dissolveu em um borrão de cores e sons, o templo desaparecendo enquanto vocês eram puxados através do tempo. - -Vocês pousam no jardim, é tarde da noite com uma névoa espessa e luzes sinistras piscando à distância. A mansão se ergue diante de vocês. Leonardo olha ao redor, seus olhos arregalados de admiração. - -
- Antiga mansão mostrada em uma névoa densa -
- -### Fugindo dos Cães - -Você ouve latidos e o som de cães correndo em sua direção. Você se vira para Leonardo: "Precisamos entrar, agora!" - -
- Fugindo dos cães -
- -Quando você chega à porta da mansão, ela se abre e um par de atendentes sai apressadamente. Depois de avaliá-los, eles fazem sinal para que vocês os sigam. - -Você se encontra cara a cara com Ada Lovelace, seus olhos brilhando de curiosidade. - -### Conhecendo Ada e Charles - -**Ada:** "Ah, já era hora de chegarem," ela disse calorosamente. "Precisamos que vocês façam um serviço." - -**Você:** "Já era hora", vocês continuam dizendo isso. Dinócrates disse o mesmo, mas não tenho certeza do que quer dizer? - -**Ada:** Silêncio, não há tempo para isso agora, precisamos falar sobre o dispositivo que você está segurando. Charles, explique a eles... - -**Você:** Mas... - -
- Ada Lovelace e Charles Babbage trabalhando em um dispositivo -
- -Charles Babbage se aproxima, examinando o Besouro do Tempo em sua mão. "Este dispositivo é notável, mas está um pouco defeituoso, não está? Você deve ter notado, tenho certeza." - -Leonardo assentiu: "Sì, tem se comportado de maneira estranha." - -**Ada:** O dispositivo não está totalmente pronto, precisamos dar a ele mais capacidades. Precisamos torná-lo mais inteligente, mais consciente do mundo ao seu redor. A ideia é que ele seja capaz de recuperar informações de diferentes períodos de tempo e usá-las para gerar respostas precisas e relevantes. Pode nos ajudar com isso? - -**Você:** Claro, parece que precisamos _aumentar_ as respostas do dispositivo com dados, faz sentido. - -**Ada:** Vamos falar sobre um conceito que eu gostaria de chamar de RAG, ou Geração Aumentada por Recuperação. - -## Interaja com Ada Lovelace - -Se você quiser interagir com Ada, execute o aplicativo [Characters](/app/README.md). - -> [!IMPORTANT] -> Isso é inteiramente fictício; as respostas são geradas por IA. -> [Aviso sobre IA Responsável](/README.md#responsible-ai-disclaimer) - -
- Ada Lovelace -
- -**Passos**: - -1. Inicie um [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. Navegue até _/app_ na raiz do repositório. -3. Localize o console e execute `npm install` seguido de `npm start`. -4. Quando aparecer, selecione o botão "Open in Browser". -5. Converse com Ada. - -Para uma explicação mais detalhada do aplicativo, consulte [Explicação detalhada do aplicativo](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). - -> [!NOTE] - > Se você estiver executando o projeto localmente em sua máquina, por favor revise o guia de Início Rápido para configurar um [token de acesso pessoal do GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) e substitua a chave no código. - -## Desafios conhecidos com grandes modelos de linguagem, LLMs - -**Ada:** Vamos começar discutindo a IA que usaremos para alimentar o dispositivo. Vamos confiar em "modelos de IA" emparelhados com um sistema de recuperação de dados para melhorar a qualidade das respostas. - -Primeiro, você precisa abordar alguns desafios antes de mergulhar nos detalhes do RAG. Esses modelos, treinados em vastos dados de texto, podem produzir respostas relevantes e corretas. Mas, como qualquer fonte de dados, sua saída pode ser imprecisa, incompleta ou enganosa devido a vários fatores. - -- **Fontes desatualizadas:** Os dados usados para treinar o modelo podem estar desatualizados e não mais precisos. -- **Informações erradas ou imprecisas:** As fontes usadas para treinar o modelo podem conter informações incorretas ou enganosas, como notícias falsas ou opiniões tendenciosas. -- **Fontes não autoritativas:** O modelo pode não ser capaz de distinguir entre fontes autoritativas e não autoritativas em seus dados de treinamento, levando a informações não confiáveis. - -Isso torna difícil dizer se as informações geradas por um LLM estão corretas ou não. É aqui que o RAG entra. - -**Você:** Então eu preciso garantir que o dispositivo possa fornecer informações precisas, mesmo quando não tem certeza sobre a resposta? - -**Ada:** Sim, essa é a ideia. Ao combinar os pontos fortes dos métodos baseados em recuperação e dos modelos generativos, obtemos um sistema de IA melhor. - -## Geração Aumentada por Recuperação, conceitos fundamentais do RAG - -**Ada:** Ah sim, hora de discutir especificamente o RAG. Vamos começar com alguns fundamentos: - -Geração Aumentada por Recuperação (RAG) é uma técnica poderosa que combina os pontos fortes de duas abordagens diferentes no processamento de linguagem natural: métodos baseados em recuperação e modelos generativos. Essa abordagem híbrida permite a geração de respostas que são contextualmente relevantes e ricas em conteúdo, para ajudar a aliviar alguns dos desafios conhecidos com os LLMs. - -Em sua essência, o RAG envolve dois componentes principais: um **recuperador** e um **gerador**. - -- **O recuperador:** é responsável por encontrar informações relevantes de fontes de dados externas que podem ser usadas para melhorar as respostas geradas por IA, como um mecanismo de busca. Essas informações podem estar na forma de texto, imagens ou qualquer outro tipo de dados relevantes para o contexto da conversa, embora o texto seja o tipo de dados mais comum utilizado. - -- **O gerador:** toma as informações recuperadas e as usa para gerar uma resposta contextualmente relevante e informativa. - -Aqui está um esquema ilustrando como um sistema RAG funciona: - -![Esquema de um sistema RAG](../assets/rag.png) - -1. **Entrada do usuário:** O usuário faz uma pergunta. -2. **Recuperador:** O componente recuperador busca informações relevantes usando uma ou mais bases de conhecimento. -3. **Prompt aumentado:** As informações recuperadas são combinadas com a pergunta do usuário e o contexto, para criar um prompt aumentado. -4. **Gerador:** O LLM usa o prompt aumentado para gerar uma resposta. - -Essa combinação permite respostas mais precisas e relevantes, usando dados que você fornece em vez de depender dos dados de treinamento do modelo. - -**Ada:** Perguntas? - -**Você:** Então o recuperador encontra as informações e o gerador as usa para gerar uma resposta? - -**Ada:** Exatamente, você está pegando o jeito. - -## Integrando fontes de dados externas - -**Ada:** Agora que cobrimos os fundamentos do RAG, vamos falar sobre como você pode integrar fontes de dados externas em sua aplicação de IA. - -A integração de fontes de dados externas em sua aplicação de IA pode ser feita de várias maneiras, dependendo do tipo de dados que você deseja usar e da complexidade do mecanismo de recuperação. Aqui estão alguns métodos comuns: - -- **APIs:** Muitas fontes de dados externas fornecem APIs que permitem acessar seus dados programaticamente. Você pode usar essas APIs para recuperar informações em tempo real e usá-las para melhorar as respostas geradas por IA. - -- **Bancos de dados:** Se você tem uma grande quantidade de dados que deseja usar para recuperação, pode armazená-los em um banco de dados e consultá-los conforme necessário. Isso pode ser útil para dados estruturados que precisam ser acessados rapidamente. - -Uma vez que você tenha decidido sobre um método para integrar fontes de dados externas, também pode precisar considerar como pré-processar e formatar os dados para que possam ser facilmente usados pelo modelo de IA. Isso pode envolver a limpeza dos dados, convertê-los para um formato adequado (como texto simples ou Markdown), ou dividi-los em pedaços menores para facilitar a recuperação. - -> [!NOTE] -> Ao integrar fontes de dados externas em sua aplicação de IA, é importante considerar as implicações de privacidade e segurança de acessar e armazenar dados. Certifique-se de ter as permissões e salvaguardas necessárias para proteger os dados e cumprir com quaisquer regulamentos relevantes. - -Se você estiver usando um banco de dados, também deve pensar em como deseja *pesquisar seus dados* para recuperar as informações mais relevantes. Isso pode ser feito usando pesquisa por palavras-chave, pesquisa de texto completo ou técnicas mais avançadas como pesquisa semântica ou pesquisa vetorial que podem precisar de indexação específica. Cobriremos técnicas avançadas de pesquisa em uma lição futura. - -**Você**: Pode explicar termos como API e Bancos de Dados em termos mais do século XIX? - -**Ada**: Claro, uma API é como um mensageiro que entrega uma mensagem de um lugar para outro, e um banco de dados é como uma biblioteca onde você guarda todos os seus livros. - -**Você**: Ah, entendo, faz sentido. - -## Aumentando o prompt - -**Ada:** Você ainda está me acompanhando? Ótimo, vamos para o próximo passo, vamos tentar melhorar o prompt enviado ao modelo de IA. - -**Ada:** Uma vez que você tenha configurado uma forma de extrair informações dos seus dados, você pode adicioná-las ao prompt do modelo de IA. Basta misturar as informações recuperadas no texto de entrada com algum contexto adicional ou orientação para direcionar a resposta da IA. - -Por exemplo, se você estiver construindo um aplicativo para responder perguntas sobre carros, poderia ter um prompt como o seguinte: - -```text - -## Instruções -Responda perguntas sobre carros usando apenas as fontes abaixo. -Se não houver dados suficientes nas fontes fornecidas, diga que você não sabe. -Seja breve e direto ao ponto. - -## Fontes - - -## Pergunta - -``` - -Ao fornecer ao modelo de IA contexto e informações adicionais, você pode ajudar a orientar o processo de geração e garantir que as respostas sejam precisas e relevantes para o tema em questão. - -> [!TIP] -> Note esta parte do prompt: `Se não houver dados suficientes nas fontes fornecidas, diga que você não sabe.`. Isso é importante para evitar que a IA gere informações incorretas quando não há dados suficientes para fornecer uma resposta confiável. Esta técnica é chamada de *rota de fuga* e é uma boa prática para garantir a qualidade do conteúdo gerado. - -RAG pode ser considerado como uma forma avançada de *engenharia de prompts*. - -### Exemplo de código - -**Ada:** A prática leva à perfeição, então vamos aplicar o que aprendemos com um exemplo. Vamos construir um sistema simples de recuperação em um aplicativo JavaScript usando um arquivo [CSV](https://fr.wikipedia.org/wiki/Comma-separated_values) de dados de carros híbridos e um algoritmo de busca básico para extrair informações relevantes com base na pergunta de um usuário. - -```javascript -// Este exemplo demonstra como usar a Geração Aumentada por Recuperação (RAG) -// para responder perguntas baseadas em um conjunto de dados de carros híbridos. -// O código abaixo lê o arquivo CSV, busca correspondências para a pergunta do usuário, -// e então gera uma resposta baseada nas informações encontradas. - -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import process from "node:process"; -import fs from "node:fs"; -import { OpenAI } from "openai"; - -// Muda o diretório de trabalho atual para o diretório do script -const __dirname = dirname(fileURLToPath(import.meta.url)); -process.chdir(__dirname); - -// 1. Faça uma pergunta sobre carros híbridos -// ----------------------------------- - -const question = `qual é o prius mais rápido`; - -// 2. Componente recuperador: pesquisa os dados por informações relevantes -// ---------------------------------------------------------------- - -// Carrega dados CSV como um array de objetos -const rows = fs.readFileSync("./hybrid.csv", "utf8").split("\n"); -const columns = rows[0].split(","); - -// Pesquisa os dados usando uma busca muito ingênua -const words = question - .toLowerCase() - .replaceAll(/[.?!()'":,]/g, "") - .split(" ") - .filter((word) => word.length > 2); -const matches = rows.slice(1).filter((row) => words.some((word) => row.toLowerCase().includes(word))); - -// Formata como uma tabela markdown, já que modelos de linguagem entendem markdown -const table = - `| ${columns.join(" | ")} |\n` + - `|${columns.map(() => "---").join(" | ")}|\n` + - matches.map((row) => `| ${row.replaceAll(",", " | ")} |\n`).join(""); - -console.log(`Encontradas ${matches.length} correspondências:`); -console.log(table); - -// 3. Aumento de contexto: cria um prompt combinado com os resultados da pesquisa -// -------------------------------------------------------------------------- - -const augmentedPrompt = ` -## Instruções -Responda perguntas sobre um período de tempo ou personagens desse período usando apenas as fontes abaixo. -Se não houver dados suficientes nas fontes fornecidas, diga que você não sabe. -Seja breve e direto ao ponto. - -## Fontes -${table} - -## Pergunta -${question} -`; - -// 4. Componente gerador: usa os resultados da pesquisa para gerar uma resposta -// --------------------------------------------------------------------- - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - -const chunks = await openai.chat.completions.create({ - model: "gpt-4o-mini", - messages: [{ role: "user", content: augmentedPrompt }], - stream: true, -}); - -console.log(`Resposta para "${question}":`); - -for await (const chunk of chunks) { - process.stdout.write(chunk.choices[0].delta.content ?? ""); -} -``` - -Você pode encontrar este código no arquivo [`example/rag-cars.js`](../example/rag-cars.js) junto com o arquivo [`hybrid.csv`](../example/hybrid.csv) contendo os dados usados para a recuperação. - -**Ada:** Uma vez que você execute este código, você deve ver os dados encontrados no arquivo CSV pelo recuperador, formatados como uma tabela markdown, seguidos pela resposta gerada pela IA para a pergunta. Tente mudar a pergunta para ver como os dados recuperados e a resposta mudam. Você também pode tentar fazer perguntas sobre tópicos não relacionados para ver como o modelo de IA lida com eles. - -```text -Exemplo da saída: - -Encontradas 1 correspondências: -| Pessoa | Período de Tempo | Descrição | -|---|---|---| -| Leonardo Da Vinci | Século XV | Polímata italiano conhecido por sua arte e invenções. | -| Isaac Newton | Século XVII | Matemático e físico inglês que formulou as leis do movimento e da gravitação universal. | -``` - -**Você:** Isso é ótimo, posso ver como isso pode ser útil ao usar o dispositivo, ou melhor, como já foi ou será útil, viagem no tempo é confusa *suspiro*. - -**Ada:** Calma, você está indo muito bem. Vamos passar para o próximo passo. - -## Tarefa - Ajudando Ada e Charles - -Tendo aprendido sobre RAG, você está agora pronto para ajudar Ada e Charles com seu dispositivo. No entanto, ao inspecionar mais de perto, o dispositivo parece familiar. - -**Você:** Besouro do Tempo, você sabe o que é isso? - -**Besouro do Tempo:** Claro, sou eu, ou serei. Estou faltando algumas peças, pensando bem, estou faltando muitas peças, nem tenho uma casca ainda. - -**Ada:** O Besouro do Tempo é um dispositivo que permite viajar através do tempo e do espaço, isto é, uma vez que conseguirmos fazê-lo funcionar corretamente. Como eu estava dizendo, precisamos adicionar um novo recurso a ele, um módulo de geração aumentada por recuperação (RAG). Isso nos ajudará a recuperar informações e o contexto necessário de diferentes períodos de tempo enquanto você viaja. Queremos garantir que consultemos todo tipo de fontes, a Wikipedia é um bom começo. - -**Você:** O que você precisa que eu faça? - -**Ada:** Aqui está um código de exemplo que recupera informações de texto sobre Tim Berners-Lee da Wikipedia, Tim será muito importante um dia. - -```javascript -const response = await fetch('https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&redirects=true&explaintext&titles=Tim%20Berners-Lee'); -const data = await response.json(); -const text = Object.values(data.query.pages)[0]?.extract; -``` - -**Você:** Presumo que não sou o único que esteve no futuro? - -**Ada:** ... - -## Solução - -[Solução](../solution/rag-www.js) - -## Verificação de conhecimento - -**Pergunta**: Qual é o papel do recuperador em um sistema RAG? - -A. O recuperador gera respostas com base nos dados de entrada. - -B. O recuperador gera informações relevantes com base nos dados de treinamento do modelo. - -C. O recuperador encontra informações relevantes de fontes de dados externas. - -[Solução do quiz](../solution/solution-quiz.md) - -## Recursos para auto-estudo - -- [Geração Aumentada por Recuperação e Índices](https://learn.microsoft.com/azure/ai-studio/concepts/retrieval-augmented-generation) -- **Aplicativos de exemplo**: - * [Chat de IA Serverless com RAG](https://github.com/Azure-Samples/serverless-chat-langchainjs/) - * [Ask Youtube: Uma API de perguntas e respostas do Youtube baseada em RAG](https://github.com/Azure-Samples/langchainjs-quickstart-demo) -- [Workshop completo: Crie seu próprio ChatGPT com RAG](https://moaw.dev/workshop/gh:azure-samples/azure-openai-rag-workshop/docs/workshop-qdrant.md) diff --git a/lessons/05-rag/translations/README.zh-cn.md b/lessons/05-rag/translations/README.zh-cn.md deleted file mode 100644 index 8cfb0c82..00000000 --- a/lessons/05-rag/translations/README.zh-cn.md +++ /dev/null @@ -1,353 +0,0 @@ -# 第5课:使用检索增强生成(RAG)与您的数据对话 - -在本章中,您将学习: - -- 检索增强生成(RAG)的基础知识以及如何使用它来增强生成式AI模型的响应。 -- 如何将外部数据源集成到您的AI应用程序中。 -- 如何利用您的数据来提高AI生成内容的相关性和准确性。 - -## 设置 - -如果您尚未设置开发环境,请按照以下方法进行设置:[设置您的环境](/docs/setup/README.md)。 - -## 相关资源 - -[![观看关于RAG的短视频](https://img.youtube.com/vi/xkFOmx5yxIA/0.jpg)](https://www.youtube.com/watch?v=xkFOmx5yxIA&list=PLlrxD0HtieHi5ZpsHULPLxm839IrhmeDk&index=4) - -_这个视频解释了检索增强生成(RAG),这是一种帮助AI在其训练数据之外使用您的内容以获得改进结果的方法。_ - -*🎥 点击上方图片观看关于检索增强生成(RAG)的短视频* - -💼 幻灯片:[检索增强生成,RAG](../../../videos/slides/03-rag.pptx) - -## 故事情节 - 起源 - -> [!NOTE] -> _我们的故事到目前为止:您是一位来自19世纪60年代伦敦的机械师。您正在研究您的自动机,收到了查尔斯·巴贝奇的一封信,最终带您来到一个图书馆,在那里您拿起了一个时间旅行装置。在时间旅行中,您来到了佛罗伦萨,在那里遇见了达芬奇。您还去了阿兹特克帝国,故事就从这里继续。_ -> -> 如果您想从头开始了解故事,请参阅[第1课](/lessons/01-intro-to-genai/README.md)。 - -> [!NOTE] -> 虽然我们建议您阅读故事(很有趣!),但如果您想直接跳到技术内容,[请点击这里](#interact-with-ada-lovelace)。 - -**您**:"达芬奇,是时候走了,"您按下按钮说道。设备嗡嗡作响,一个机械声音回响着,"是时候回家了,是时候'创世纪'了。" - -**达芬奇**:"创世纪?Che cosa significa?"达芬奇困惑地问道。在您回答之前,世界溶解成一片色彩和声音的模糊,神庙消失了,您被拉入时间之中。 - -您降落在花园里,现在是深夜,浓雾弥漫,远处闪烁着诡异的灯光。豪宅耸立在您面前。达芬奇环顾四周,眼中充满惊奇。 - -
- 在浓雾中显示的古老豪宅 -
- -### 逃离猎犬 - -您听到吠叫声和狗向您奔来的声音。您转向达芬奇,"我们需要进去,现在!" - -
- 逃离猎犬 -
- -当您到达豪宅的门口时,门突然打开,一对侍者匆忙走出来。在打量了您之后,他们示意您跟随他们。 - -您与艾达·洛夫莱斯面对面,她的眼睛闪烁着好奇的光芒。 - -### 会见艾达和查尔斯 - -**艾达**:"啊,你们终于来了,"她热情地说。"我们需要你们去办一件事。" - -**您**:"终于",你们一直这么说。迪诺克拉底也说过同样的话,但我不确定你们是什么意思? - -**艾达**:嘘,现在没时间说这个,我们需要谈谈你手中的设备。查尔斯,告诉他们... - -**您**:但是... - -
- 艾达·洛夫莱斯和查尔斯·巴贝奇正在研究一个设备 -
- -查尔斯·巴贝奇走上前来,检查您手中的时间甲虫。"这个设备非常了不起,但它有点故障,不是吗?我想你已经注意到了。" - -达芬奇点点头,"是的,它一直表现得很奇怪。" - -**艾达**:这个设备还不完全准备好,我们需要给它更多的功能。我们需要让它更聪明,更了解周围的世界。我们的想法是让它能够从不同的时间段检索信息,并用它来生成准确和相关的响应。你能帮忙吗? - -**您**:当然,听起来我们需要用数据来_增强_设备的响应,这很有道理。 - -**艾达**:让我们谈谈一个我想称之为RAG的概念,即检索增强生成。 - -## 与艾达·洛夫莱斯互动 - -如果您想与艾达互动,请运行[角色](/app/README.md)应用程序。 - -> [!IMPORTANT] -> 这完全是虚构的;响应由AI生成。 -> [负责任的AI免责声明](/README.md#responsible-ai-disclaimer) - -
- 艾达·洛夫莱斯 -
- -**步骤**: - -1. 启动一个[![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) -2. 导航到仓库根目录中的_/app_。 -3. 找到控制台并运行`npm install`,然后运行`npm start`。 -4. 出现后,选择"在浏览器中打开"按钮。 -5. 与艾达聊天。 - -有关应用程序的更详细说明,请参见[详细应用程序说明](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates)。 - -> [!NOTE] - > 如果您在本地机器上运行项目,请查看快速入门指南,设置[GitHub个人访问令牌](../../../docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access)并在代码中替换密钥。 - -## 大型语言模型(LLM)的已知挑战 - -**艾达**:让我们先讨论我们将用来驱动设备的AI。我们将依靠"AI模型"搭配数据检索系统来提升响应质量。 - -在深入了解RAG细节之前,您需要解决一些挑战。这些模型经过大量文本数据训练,能够产生相关且正确的响应。但是,像任何数据源一样,由于各种因素,它们的输出可能不准确、不完整或具有误导性。 - -- **过时的来源**:用于训练模型的数据可能已经过时,不再准确。 -- **错误或不准确的信息**:用于训练模型的来源可能包含不正确或误导性信息,如假新闻或有偏见的观点。 -- **非权威来源**:模型可能无法区分其训练数据中的权威和非权威来源,导致信息不可靠。 - -这使得很难判断LLM生成的信息是否正确。这就是RAG的用武之地。 - -**您**:所以我需要确保设备能提供准确的信息,即使它对答案不确定? - -**艾达**:是的,这就是我们的想法。通过结合检索方法和生成模型的优势,我们可以获得更好的AI系统。 - -## 检索增强生成(RAG)核心概念 - -**艾达**:好的,现在让我们具体讨论RAG。让我们从一些基础知识开始: - -检索增强生成(RAG)是一种强大的技术,它结合了自然语言处理中两种不同方法的优势:基于检索的方法和生成模型。这种混合方法允许生成既与上下文相关又内容丰富的响应,有助于缓解LLM的一些已知挑战。 - -在其核心,RAG涉及两个主要组件**:检索器**和**生成器**。 - -- **检索器**:负责从外部数据源中查找可用于增强AI生成响应的相关信息,类似于搜索引擎。这些信息可以是文本、图像或任何其他与对话上下文相关的数据类型,尽管文本是最常用的数据类型。 - -- **生成器**:接收检索到的信息并使用它生成与上下文相关且信息丰富的响应。 - -以下是说明RAG系统如何工作的示意图: - -![RAG系统示意图](../assets/rag.png) - -1. **用户输入**:用户提出问题。 -2. **检索器**:检索器组件使用一个或多个知识库搜索相关信息。 -3. **增强提示**:检索到的信息与用户问题和上下文结合,创建增强提示。 -4. **生成器**:LLM使用增强提示生成响应。 - -这种组合允许通过使用您提供的数据而不是依赖模型的训练数据,提供更精确和相关的答案。 - -**艾达**:有问题吗? - -**您**:所以检索器找到信息,生成器使用它来生成响应? - -**艾达**:没错,你已经掌握了要领。 - -## 集成外部数据源 - -**艾达**:现在我们已经介绍了RAG的基础知识,让我们谈谈如何将外部数据源集成到您的AI应用程序中。 - -将外部数据源集成到您的AI应用程序中可以通过多种方式完成,这取决于您想要使用的数据类型和检索机制的复杂性。以下是一些常见方法: - -- **API**:许多外部数据源提供API,允许您以编程方式访问其数据。您可以使用这些API实时检索信息,并用它来增强AI生成的响应。 - -- **数据库**:如果您有大量想要用于检索的数据,可以将其存储在数据库中并根据需要查询。这对于需要快速访问的结构化数据很有用。 - -一旦您确定了集成外部数据源的方法,您可能还需要考虑如何预处理和格式化数据,使其能够被AI模型轻松使用。这可能涉及清理数据,将其转换为合适的格式(如纯文本或Markdown),或将其分割成更小的块以便更容易检索。 - -> [!NOTE] -> 在将外部数据源集成到您的AI应用程序时,考虑访问和存储数据的隐私和安全影响很重要。确保您有必要的权限和保障措施来保护数据并遵守任何相关法规。 - -如果您使用数据库,您还需要考虑如何*搜索您的数据*以检索最相关的信息。这可以通过关键词搜索、全文搜索或更高级的技术如语义搜索或向量搜索来完成,这些可能需要特定的索引。我们将在未来的课程中介绍高级搜索技术。 - -**您**:您能用更符合19世纪60年代的术语解释API和数据库吗? - -**艾达**:当然,API就像是一个从一个地方传递消息到另一个地方的信使,而数据库就像是一个存放所有书籍的图书馆。 - -**您**:啊,我明白了,这很有道理。 - -## 增强提示 - -**艾达**:你还跟得上吗?很好,让我们进入下一步,尝试改进发送给AI模型的提示。 - -**艾达**:一旦您设置了从数据中提取信息的方式,您可以将其添加到AI模型的提示中。只需将检索到的信息与输入文本混合,并添加一些额外的上下文或指导来引导AI的响应。 - -例如,如果您正在构建一个回答关于汽车问题的应用程序,您可以使用如下提示: - -```text - -## 指令 -仅使用以下来源回答关于汽车的问题。 -如果提供的来源中没有足够的数据,请说明您不知道。 -简明扼要地回答。 - -## 来源 -<在此插入检索到的信息> - -## 问题 -<在此插入问题> -``` - -通过为AI模型提供额外的上下文和信息,您可以帮助引导生成过程,确保响应准确且与主题相关。 - -> [!TIP] -> 注意提示中的这部分:`如果提供的来源中没有足够的数据,请说明您不知道。`。这对于避免AI在没有足够数据提供可靠答案时生成不正确信息很重要。这种技术被称为*逃生舱*,是确保生成内容质量的良好实践。 - -RAG可以被视为*提示工程*的高级形式。 - -### 代码示例 - -**艾达**:实践出真知,所以让我们应用我们所学的知识来看一个例子。我们将使用混合汽车数据的[CSV](https://fr.wikipedia.org/wiki/Comma-separated_values)文件和一个基本搜索算法,根据用户问题提取相关信息,构建一个简单的检索系统到JavaScript应用程序中。 - -```javascript -// 这个例子演示了如何使用检索增强生成(RAG) -// 基于混合汽车数据集回答问题。 -// 下面的代码读取CSV文件,搜索与用户问题匹配的内容, -// 然后根据找到的信息生成响应。 - -import { fileURLToPath } from 'node:url'; -import { dirname } from 'node:path'; -import process from "node:process"; -import fs from "node:fs"; -import { OpenAI } from "openai"; - -// 将当前工作目录更改为脚本所在的目录 -const __dirname = dirname(fileURLToPath(import.meta.url)); -process.chdir(__dirname); - -// 1. 提出关于混合动力汽车的问题 -// ----------------------------------- - -const question = `什么是最快的普锐斯`; - -// 2. 检索器组件:在数据中搜索相关信息 -// ---------------------------------------------------------------- - -// 将CSV数据加载为对象数组 -const rows = fs.readFileSync("./hybrid.csv", "utf8").split("\n"); -const columns = rows[0].split(","); - -// 使用非常简单的搜索方法搜索数据 -const words = question - .toLowerCase() - .replaceAll(/[.?!()'":,]/g, "") - .split(" ") - .filter((word) => word.length > 2); -const matches = rows.slice(1).filter((row) => words.some((word) => row.toLowerCase().includes(word))); - -// 格式化为markdown表格,因为语言模型理解markdown -const table = - `| ${columns.join(" | ")} |\n` + - `|${columns.map(() => "---").join(" | ")}|\n` + - matches.map((row) => `| ${row.replaceAll(",", " | ")} |\n`).join(""); - -console.log(`找到 ${matches.length} 个匹配项:`); -console.log(table); - -// 3. 上下文增强:创建包含搜索结果的组合提示 -// -------------------------------------------------------------------------- - -const augmentedPrompt = ` -## 指令 -仅使用以下来源回答关于时间段或该时间段人物的问题。 -如果提供的来源中没有足够的数据,请说明您不知道。 -简明扼要地回答。 - -## 来源 -${table} - -## 问题 -${question} -`; - -// 4. 生成器组件:使用搜索结果生成响应 -// --------------------------------------------------------------------- - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - -const chunks = await openai.chat.completions.create({ - model: "gpt-4o-mini", - messages: [{ role: "user", content: augmentedPrompt }], - stream: true, -}); - -console.log(`"${question}"的答案:`); - -for await (const chunk of chunks) { - process.stdout.write(chunk.choices[0].delta.content ?? ""); -} -``` - -您可以在[`example/rag-cars.js`](../example/rag-cars.js)文件中找到此代码,以及包含用于检索的数据的[`hybrid.csv`](../example/hybrid.csv)文件。 - -**艾达**:运行此代码后,您应该会看到检索器在CSV文件中找到的数据,格式化为markdown表格,然后是AI生成的对问题的响应。尝试更改问题,看看检索到的数据和响应如何变化。您还可以尝试询问不相关的主题,看看AI模型如何处理它们。 - -```text -输出示例: - -找到1个匹配项: -| 人物 | 时间段 | 描述 | -|---|---|---| -| 达芬奇 | 15世纪 | 意大利多才多艺的人,以其艺术和发明而闻名。 | -| 艾萨克·牛顿 | 17世纪 | 英国数学家和物理学家,提出了运动定律和万有引力定律。 | -``` - -**您**:这太棒了,我可以看到这在使用设备时会很有用,或者说它已经有用了,或者将来会有用,时间旅行真令人困惑*叹气*。 - -**艾达**:别担心,你做得很好。让我们继续下一步。 - -## 作业 - 帮助艾达和查尔斯 - -学习了RAG之后,您现在已经准备好帮助艾达和查尔斯完善他们的设备了。然而,仔细检查后,这个设备看起来很熟悉。 - -**您**:时间甲虫,你知道这是什么吗? - -**时间甲虫**:当然,这就是我,或者说将来会是我。我缺少一些部件,实际上,我缺少很多部件,我甚至还没有外壳。 - -**艾达**:时间甲虫是一个允许您穿越时间和空间的设备,一旦我们让它正常工作。正如我所说,我们需要为它添加一个新功能,一个检索增强生成(RAG)模块。这将帮助我们在您旅行时从不同的时间段检索信息和所需的上下文。我们想确保我们参考各种来源,维基百科是一个很好的起点。 - -**您**:您需要我做什么? - -**艾达**:这里有一个示例代码,它从维基百科检索关于蒂姆·伯纳斯-李的文本信息,蒂姆将来会非常重要。 - -```javascript -const response = await fetch('https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&redirects=true&explaintext&titles=Tim%20Berners-Lee'); -const data = await response.json(); -const text = Object.values(data.query.pages)[0]?.extract; -``` - -**您**:我想我不是唯一一个去过未来的人? - -**艾达**:... - -## 解决方案 - -[解决方案](../solution/rag-www.js) - -## 知识检查 - -**问题**:在RAG系统中,检索器的角色是什么? - -A. 检索器根据输入数据生成响应。 - -B. 检索器根据模型的训练数据生成相关信息。 - -C. 检索器从外部数据源中查找相关信息。 - -[测验解决方案](../solution/solution-quiz.md) - -## 自学资源 - -- [检索增强生成和索引](https://learn.microsoft.com/azure/ai-studio/concepts/retrieval-augmented-generation) -- **示例应用**: - * [使用RAG的无服务器AI聊天](https://github.com/Azure-Samples/serverless-chat-langchainjs/) - * [Ask Youtube:基于RAG的Youtube问答API](https://github.com/Azure-Samples/langchainjs-quickstart-demo) -- [完整工作坊:使用RAG创建您自己的ChatGPT](https://moaw.dev/workshop/gh:azure-samples/azure-openai-rag-workshop/docs/workshop-qdrant.md) - -**艾达**:让我们先讨论我们将用来驱动设备的AI。我们将依靠"AI模型 diff --git a/lessons/06-tool-calling/README.md b/lessons/06-tool-calling/README.md index 45277988..c2337210 100644 --- a/lessons/06-tool-calling/README.md +++ b/lessons/06-tool-calling/README.md @@ -63,26 +63,26 @@ The world around you starts to blur, and everything fades to black. You come to ## Interact with Amelia Earhart -If you want to interact with Ada, run the [Characters](/app/README.md) app. +If you want to interact with Amelia, run the [Characters](/app/README.md) app. > [!IMPORTANT] > This is entirely fictional; the responses are generated by AI. > [Responsible AI disclaimer](/README.md#responsible-ai-disclaimer) -![Ada Lovelace](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/06-tool-calling/assets/amelia-front.jpeg) +![Amelia Earhart](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/06-tool-calling/assets/amelia-front.jpeg) **Steps**: 1. Start a [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) 2. Navigate to _/app_ in the repo root. -3. Locate the console and run `npm install` followed by `npm start`. +3. Locate the console and run `npm ci` followed by `npm start`. 4. Once it appears, select the "Open in Browser" button. 5. Chat with Amelia. For a more detailed explanation of the app, see [Detailed app explanation](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). > [!NOTE] - > If you're running the project locally on your machine, please review the QuickStart guide to get a [GitHub personal access](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) token setup and replace the key in the code. +> Configure `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL` in the repository-root `.env` for both local development and Codespaces. See the [setup guide](/docs/setup/README.md#configure-environment-variables). ## Tool calling @@ -106,9 +106,9 @@ try { if (!landingSpot) { throw new Error("No suitable landing spot found"); } - console.log(Landing spot found at coordinates: ${landingSpot.lat}, ${landingSpot.long}); + console.log(`Landing spot found at coordinates: ${landingSpot.lat}, ${landingSpot.long}`); } catch (error) { - console.log(Error: ${error.message}); + console.error(`Error: ${error.message}`); } ``` @@ -143,15 +143,11 @@ function findLandingSpot(lat, long) { "properties": { "lat": { "type": "number", - "description": "The latitude of the location", - }, + "description": "The latitude of the location"}, "long": { "type": "number", - "description": "The longitude of the location", - }, - }, - "required": ["lat", "long"], - }, + "description": "The longitude of the location"}}, + "required": ["lat", "long"]}, "output": { "type": "object", "properties": { "lat": "number", "long": "number" } } } ``` @@ -185,7 +181,7 @@ const getBackgroundOnCharacterJson = { description: "The name of the character", } }, - required: ["lat", "long"], + required: ["name"], }, output: { type: "string" } }; @@ -217,16 +213,16 @@ const messages = [{ }]; const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', + model: process.env.AI_MODEL, messages: messages, - functions: [getBackgroundOnCharacterJson, findLandingSpotJson] + tools: [getBackgroundOnCharacterJson, findLandingSpotJson].map(({ name, description, parameters }) => ({ type: "function", function: { name, description, parameters } })) }); ``` **Time Beetle**: "In the preceding code snippet we:" - Define the metadata for the `find-landing-spot` tool and the `get-background-on-character` tool. -- Provide this metadata to the `client.getChatCompletions` call as part of the `functions` parameter. This tells the AI model that these tools are available to be called." +- Provide this metadata to the `openai.chat.completions.create` call as part of the `tools` parameter. This tells the AI model that these tools are available to be called." **You**: "Got it, so the AI model will call the appropriate if I provide a prompt that matches to the tool's description?" @@ -250,87 +246,103 @@ const completion = await openai.chat.completions.create({ **Time Beetle**: "Sure, here's the code wire up the tool call, making a chat completion request and interpreting the response: ```javascript -import { OpenAI } from 'openai'; -import { maybeCoerceInteger } from 'openai/core.mjs'; +import { OpenAI } from "openai"; + +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} -// 1: Define the function function findLandingSpot(lat, long) { - console.log("[Function] Finding landing spot with coordinates: ", lat, long); - // Perform the task of finding a suitable landing spot - // Return the coordinates of the landing spot + console.log("[Function] Finding a simulated landing spot:", lat, long); return { lat: 7.5, long: 134.5 }; } -// 2: Define the tool metadata, should include description, parameters, and output +function getBackgroundOnCharacter(name) { + console.log("[Function] Getting background on character:", name); + return `Background information on ${name}`; +} + +const getBackgroundOnCharacterJson = { + name: "get-background-on-character", + description: "Get background information on a character", + strict: true, + parameters: { + type: "object", + properties: { name: { type: "string", description: "The character's name" } }, + required: ["name"], + additionalProperties: false + } +}; + const findLandingSpotJson = { name: "find-landing-spot", - description: "Finds a suitable landing spot", + description: "Return a simulated landing spot for this fictional exercise", + strict: true, parameters: { type: "object", properties: { - lat: { - type: "number", - description: "The latitude of the location", - }, - long: { - type: "number", - description: "The longitude of the location", - }, + lat: { type: "number", description: "Latitude" }, + long: { type: "number", description: "Longitude" } }, required: ["lat", "long"], - }, - output: { type: "object", properties: { lat: "number", long: "number" } } + additionalProperties: false + } }; -// 3: Add the tool to the tools object that we will use later to invoke the tool -const tools = { - [findLandingSpotJson.name]: findLandingSpot +const handlers = { + [getBackgroundOnCharacterJson.name]: args => { + if (typeof args.name !== "string" || !args.name.trim()) { + throw new Error("The character name must be a non-empty string."); + } + return getBackgroundOnCharacter(args.name); + }, + [findLandingSpotJson.name]: args => { + if (!Number.isFinite(args.lat) || !Number.isFinite(args.long)) { + throw new Error("Landing coordinates must be finite numbers."); + } + return findLandingSpot(args.lat, args.long); + } }; -// 4: Create an instance of the OpenAI client -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", // might need to change to this url in the future: https://models.github.ai/inference - apiKey: process.env.GITHUB_TOKEN, -}); - -// 5: Define the messages that will be sent to the AI model +const tools = [getBackgroundOnCharacterJson, findLandingSpotJson].map(definition => ({ + type: "function", + function: definition +})); +const openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); const messages = [ -{ - role: "system", - content: `You are a helpful assistant. You can call functions to perform tasks. Make sure to parse the function call and arguments correctly.` -}, { - role: "user", - content: "Find a landing spot given coordinates 8.5, 130.5" -} + { role: "system", content: "Use the provided tools to answer the fictional travel request." }, + { role: "user", content: "Use the character tool to give me background on Amelia Earhart." } ]; -async function main(){ - console.log("Making LLM call") - - // 6: Call the AI model with the defined messages and tools - const result = await openai.chat.completions.create({ - model: 'gpt-4o', - messages: messages, - functions: [findLandingSpotJson] - }); - - for (const choice of result.choices) { - - let functionCall = choice.message?.function_call; - let functionName = functionCall?.name; - let args = JSON.parse(functionCall?.arguments); +const completion = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "required" +}); +const message = completion.choices[0]?.message; +if (!message?.tool_calls?.length) throw new Error("The model did not request a tool."); +messages.push(message); - // 7: Interpret response and call the tool based on the function call provided by the AI model - if (functionName && functionName in tools) { - console.log(`Calling [${functionName}]`); - const toolFunction = tools[functionName]; - const toolResponse = toolFunction(...Object.values(args)); // Extract values from args and spread them - console.log("Result from [tool] calling: ", toolResponse); - } +for (const call of message.tool_calls) { + if (call.type !== "function" || !Object.hasOwn(handlers, call.function.name)) { + throw new Error("The model requested an unsupported tool."); } + const args = JSON.parse(call.function.arguments); + if (!args || typeof args !== "object" || Array.isArray(args)) { + throw new Error("Tool arguments must be a JSON object."); + } + const result = handlers[call.function.name](args); + console.log(`Result from [${call.function.name}]:`, result); + messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) }); } -main(); +const followUp = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "none" +}); +const answer = followUp.choices[0]?.message?.content; +if (!answer?.trim()) throw new Error("The model did not return a final answer."); +console.log(answer); ``` In the preceding code we've: @@ -338,7 +350,7 @@ In the preceding code we've: - Created a function called `findLandingSpot` that takes latitude and longitude as input and returns the coordinates of a suitable landing spot. - Defined the metadata for the `find-landing-spot` tool. - Created a `tools` object that maps tool names to tool metadata. -- Provided the `tools` object to the `client.getChatCompletions` call. +- Provided the `tools` object to the `openai.chat.completions.create` call. ```javascript if (functionName && functionName in tools) { @@ -433,4 +445,4 @@ C. To allow the AI model to execute tools without requiring metadata. - Explains the [process of tool calling](https://learn.microsoft.com/en-us/semantic-kernel/concepts/ai-services/chat-completion/function-calling/?pivots=programming-language-csharp) - Tool calling in the [Langchain.js framework](https://js.langchain.com/docs/how_to/tool_calling/) -- Function calling as demonstrated in the [openai library](https://github.com/openai/openai-node/blob/master/examples/function-call.ts) +- Function calling in the [current OpenAI SDK](https://github.com/openai/openai-node#automated-function-calls) diff --git a/lessons/06-tool-calling/code/app.js b/lessons/06-tool-calling/code/app.js index 07160f38..f6db79ca 100644 --- a/lessons/06-tool-calling/code/app.js +++ b/lessons/06-tool-calling/code/app.js @@ -1,108 +1,97 @@ -import { OpenAI } from 'openai'; +import { OpenAI } from "openai"; + +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} function findLandingSpot(lat, long) { - console.log("[Function] Finding landing spot with coordinates: ", lat, long); - // Perform the task of finding a suitable landing spot - // Return the coordinates of the landing spot + console.log("[Function] Finding a simulated landing spot:", lat, long); return { lat: 7.5, long: 134.5 }; } -function getBackgroundOnCharacter(character= "unknown") { - console.log("[Function] Getting background on character: ", character); - // Perform the task of getting background information on a character - // Return the background information - return `Background information on ${character}`; +function getBackgroundOnCharacter(name) { + console.log("[Function] Getting background on character:", name); + return `Background information on ${name}`; } const getBackgroundOnCharacterJson = { name: "get-background-on-character", description: "Get background information on a character", + strict: true, parameters: { type: "object", - properties: { - name: { - type: "string", - description: "The name of the character", - } - }, + properties: { name: { type: "string", description: "The character's name" } }, required: ["name"], - }, - output: { type: "string" } + additionalProperties: false + } }; const findLandingSpotJson = { name: "find-landing-spot", - description: "Finds a suitable landing spot", + description: "Return a simulated landing spot for this fictional exercise", + strict: true, parameters: { type: "object", properties: { - lat: { - type: "number", - description: "The latitude of the location", - }, - long: { - type: "number", - description: "The longitude of the location", - }, + lat: { type: "number", description: "Latitude" }, + long: { type: "number", description: "Longitude" } }, required: ["lat", "long"], - }, - output: { type: "object", properties: { lat: "number", long: "number" } } + additionalProperties: false + } }; -const tools = { - [findLandingSpotJson.name]: findLandingSpot, - [getBackgroundOnCharacterJson.name]: getBackgroundOnCharacter +const handlers = { + [getBackgroundOnCharacterJson.name]: args => { + if (typeof args.name !== "string" || !args.name.trim()) { + throw new Error("The character name must be a non-empty string."); + } + return getBackgroundOnCharacter(args.name); + }, + [findLandingSpotJson.name]: args => { + if (!Number.isFinite(args.lat) || !Number.isFinite(args.long)) { + throw new Error("Landing coordinates must be finite numbers."); + } + return findLandingSpot(args.lat, args.long); + } }; - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", // might need to change to this url in the future: https://models.github.ai/inference - apiKey: process.env.GITHUB_TOKEN, -}); - -/* -// { -// role: "user", -// content: `We need to know where to land, here's the coordinates: 7.5, 134.5. `, -// }, -*/ - +const tools = [getBackgroundOnCharacterJson, findLandingSpotJson].map(definition => ({ + type: "function", + function: definition +})); +const openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); const messages = [ -{ - role: "system", - content: `You are a helpful assistant. You can call functions to perform tasks. Make sure to parse the function call and arguments correctly.` -}, { - role: "user", - content: "Can you give me some background on the character named Amelia Earhart?" -} + { role: "system", content: "Use the provided tools to answer the fictional travel request." }, + { role: "user", content: "Use the character tool to give me background on Amelia Earhart." } ]; -async function main(){ - console.log("Making LLM call") - - const result = await openai.chat.completions.create({ - model: 'gpt-4o', - messages: messages, - functions: [getBackgroundOnCharacterJson, findLandingSpotJson] - }); - - for (const choice of result.choices) { - // console.log("Result", choice.message); +const completion = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "required" +}); +const message = completion.choices[0]?.message; +if (!message?.tool_calls?.length) throw new Error("The model did not request a tool."); +messages.push(message); - let functionCall = choice.message?.function_call; - let functionName = functionCall?.name; - let args = JSON.parse(functionCall?.arguments); - // console.log("Wants to call: ", choice.message?.function_call); - // console.log("With args: ", args); - if (functionName && functionName in tools) { - console.log(`Calling [${functionName}]`); - const toolFunction = tools[functionName]; - const toolResponse = toolFunction(...Object.values(args)); // Extract values from args and spread them - console.log("Result from [tool] calling: ", toolResponse); - } +for (const call of message.tool_calls) { + if (call.type !== "function" || !Object.hasOwn(handlers, call.function.name)) { + throw new Error("The model requested an unsupported tool."); } - + const args = JSON.parse(call.function.arguments); + if (!args || typeof args !== "object" || Array.isArray(args)) { + throw new Error("Tool arguments must be a JSON object."); + } + const result = handlers[call.function.name](args); + console.log(`Result from [${call.function.name}]:`, result); + messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) }); } -main(); +const followUp = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "none" +}); +const answer = followUp.choices[0]?.message?.content; +if (!answer?.trim()) throw new Error("The model did not return a final answer."); +console.log(answer); diff --git a/lessons/06-tool-calling/code/package-lock.json b/lessons/06-tool-calling/code/package-lock.json index 7ba1a628..01cc39de 100644 --- a/lessons/06-tool-calling/code/package-lock.json +++ b/lessons/06-tool-calling/code/package-lock.json @@ -9,397 +9,38 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "openai": "^4.91.1" - } - }, - "node_modules/@types/node": { - "version": "18.19.86", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.86.tgz", - "integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "openai": "^7.28.0" } }, "node_modules/openai": { - "version": "4.91.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.91.1.tgz", - "integrity": "sha512-DbjrR0hIMQFbxz8+3qBsfPJnh3+I/skPgoSlT7f9eiZuhGBUissPQULNgx6gHNkLoZ3uS0uYS6eXPUdtg4nHzw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.28.0.tgz", + "integrity": "sha512-HSY4fFLflQGYe2kvO0atm6/GzhD61gpyiOZZUvO4nptMUhuB1to2K3wwFpFqc5gfEK0SxyIIFiu1yE2MtygWcA==", "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" + "engines": { + "node": ">=22.0.0" }, "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, "ws": { "optional": true }, @@ -407,43 +48,6 @@ "optional": true } } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } } } } diff --git a/lessons/06-tool-calling/code/package.json b/lessons/06-tool-calling/code/package.json index 04a667b1..3e72c21b 100644 --- a/lessons/06-tool-calling/code/package.json +++ b/lessons/06-tool-calling/code/package.json @@ -2,14 +2,15 @@ "name": "code", "version": "1.0.0", "main": "app.js", + "type": "module", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "start": "node --env-file-if-exists=../../../.env app.js" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { - "openai": "^4.91.1" + "openai": "^7.28.0" } } diff --git a/lessons/06-tool-calling/solution/README.md b/lessons/06-tool-calling/solution/README.md index 0e18dae6..303bc68d 100644 --- a/lessons/06-tool-calling/solution/README.md +++ b/lessons/06-tool-calling/solution/README.md @@ -1,11 +1,11 @@ # Solution for Lesson 6: Tool Calling -This solution demonstrates how to call a tool from the AI model. It uses GitHub Models and openai library for calling the tool. +The endpoint, API key, and deployment name come from your Microsoft Foundry resource. The code reads them from `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL`. ## Installation ```bash -npm install +npm ci ``` ## Usage @@ -13,4 +13,3 @@ npm install ```bash npm start ``` - diff --git a/lessons/06-tool-calling/solution/package-lock.json b/lessons/06-tool-calling/solution/package-lock.json index 4c0bf232..41494897 100644 --- a/lessons/06-tool-calling/solution/package-lock.json +++ b/lessons/06-tool-calling/solution/package-lock.json @@ -9,397 +9,38 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "openai": "^4.91.1" - } - }, - "node_modules/@types/node": { - "version": "18.19.86", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.86.tgz", - "integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "openai": "^7.28.0" } }, "node_modules/openai": { - "version": "4.91.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.91.1.tgz", - "integrity": "sha512-DbjrR0hIMQFbxz8+3qBsfPJnh3+I/skPgoSlT7f9eiZuhGBUissPQULNgx6gHNkLoZ3uS0uYS6eXPUdtg4nHzw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.28.0.tgz", + "integrity": "sha512-HSY4fFLflQGYe2kvO0atm6/GzhD61gpyiOZZUvO4nptMUhuB1to2K3wwFpFqc5gfEK0SxyIIFiu1yE2MtygWcA==", "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" + "engines": { + "node": ">=22.0.0" }, "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, "ws": { "optional": true }, @@ -407,43 +48,6 @@ "optional": true } } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } } } } diff --git a/lessons/06-tool-calling/solution/package.json b/lessons/06-tool-calling/solution/package.json index c4642adf..fbac0419 100644 --- a/lessons/06-tool-calling/solution/package.json +++ b/lessons/06-tool-calling/solution/package.json @@ -4,14 +4,13 @@ "main": "solution.js", "type": "module", "scripts": { - "start": "node solution.js", - "test": "echo \"Error: no test specified\" && exit 1" + "start": "node --env-file-if-exists=../../../.env solution.js" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { - "openai": "^4.91.1" + "openai": "^7.28.0" } - } +} diff --git a/lessons/06-tool-calling/solution/solution.js b/lessons/06-tool-calling/solution/solution.js index eae96733..16817e6e 100644 --- a/lessons/06-tool-calling/solution/solution.js +++ b/lessons/06-tool-calling/solution/solution.js @@ -1,148 +1,105 @@ -import { OpenAI } from 'openai'; +import { OpenAI } from "openai"; -// 1: Define the functions +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} function calculateDistance(lat1, long1, lat2, long2) { - // Perform the task of calculating the distance between two points - // Return the distance between the points - return Math.sqrt((lat2 - lat1) ** 2 + (long2 - long1) ** 2); + return Math.hypot(lat2 - lat1, long2 - long1); } function getGpsPosition(lat, long) { - // Perform the task of getting the GPS position of the current location - // Return the GPS position - return { lat: 7.5, long: 134.5 }; + return { lat: 7.5, long: 134.5 }; } function getWeatherForecast(lat, long) { - // Perform the task of getting the weather forecast for a given location - // Return the weather forecast - return "Sunny"; + return "Sunny"; } -// 2: create metadata for the functions const calculateDistanceJson = { - name: "calculate-distance", - description: "Calculates th distance between two points", - parameters: { - type: "object", - properties: { - lat1: { - type: "number", - description: "The latitude of the first point", - }, - long1: { - type: "number", - description: "The longitude of the first point", - }, - lat2: { - type: "number", - description: "The latitude of the second point", - }, - long2: { - type: "number", - description: "The longitude of the second point", - }, - }, - required: ["lat1", "long1", "lat2", "long2"], + name: "calculate-distance", + description: "Calculate a demonstration distance in coordinate units", + strict: true, + parameters: { + type: "object", + properties: { + lat1: { type: "number" }, long1: { type: "number" }, + lat2: { type: "number" }, long2: { type: "number" } }, - output: { type: "number" } - }; - + required: ["lat1", "long1", "lat2", "long2"], + additionalProperties: false + } +}; const getGpsPositionJson = { name: "get-gps-position", - description: "Gets the GPS position of the current location", + description: "Return simulated GPS coordinates for this fictional exercise", + strict: true, parameters: { type: "object", - properties: { - lat: { - type: "number", - description: "The latitude of the first point", - }, - long: { - type: "number", - description: "The longitude of the first point", - }, - }, + properties: { lat: { type: "number" }, long: { type: "number" } }, required: ["lat", "long"], - }, - output: { type: "object", properties: { lat: "number", long: "number" } } -} - + additionalProperties: false + } +}; const getWeatherForecastJson = { name: "get-weather-forecast", - description: "Gets the weather forecast for a given location", + description: "Return a simulated weather forecast", + strict: true, parameters: { type: "object", - properties: { - lat: { - type: "number", - description: "The latitude of the location", - }, - long: { - type: "number", - description: "The longitude of the location", - }, - }, + properties: { lat: { type: "number" }, long: { type: "number" } }, required: ["lat", "long"], - }, - output: { type: "string" } -} - -// 3: create a tools object with the functions -const tools = { - [calculateDistanceJson.name]: calculateDistance, - [getGpsPositionJson.name]: getGpsPosition, - [getWeatherForecastJson.name]: getWeatherForecast + additionalProperties: false + } }; -// 4: create an OpenAI instance with the tools -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", - apiKey: process.env.GITHUB_TOKEN, -}); - -// 5: create messages to test the functions -// enable one of these messages at a time to test the functions -const messages = [ - { - role: "user", - content: `We need to know where to land, here's the coordinates: 7.5, 134.5. `, - }, - // { - // role: "user", - // content: `What is the distance between the points 7.5, 134.5 and 8.5, 135.5?`, - // }, - // { - // role: "user", - // content: `What is the weather forecast for the location 7.5, 134.5?`, - // }, -]; - -// 6: make a chat completion -async function main(){ - const result = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: messages, - functions: [calculateDistanceJson, getGpsPositionJson, getWeatherForecastJson] +function coordinates(args, names) { + return names.map(name => { + if (!Number.isFinite(args[name])) throw new Error(`${name} must be a finite number.`); + return args[name]; }); +} - // 7: interpret the result - for (const choice of result.choices) { - // console.log("Result", choice.message); +const handlers = { + [calculateDistanceJson.name]: args => + calculateDistance(...coordinates(args, ["lat1", "long1", "lat2", "long2"])), + [getGpsPositionJson.name]: args => getGpsPosition(...coordinates(args, ["lat", "long"])), + [getWeatherForecastJson.name]: args => getWeatherForecast(...coordinates(args, ["lat", "long"])) +}; +const tools = [calculateDistanceJson, getGpsPositionJson, getWeatherForecastJson].map(definition => ({ + type: "function", + function: definition +})); +const openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); +const messages = [{ + role: "user", + content: "Use the GPS tool to get the simulated position for coordinates 7.5, 134.5." +}]; - let functionCall = choice.message?.function_call; - let functionName = functionCall?.name; - let args = JSON.parse(functionCall?.arguments); - // console.log("Wants to call: ", choice.message?.function_call); - // console.log("With args: ", args); - if (functionName && functionName in tools) { - console.log(`Calling [${functionName}]`); - const toolFunction = tools[functionName]; - const toolResponse = toolFunction(...Object.values(args)); // Extract values from args and spread them - console.log("Result from [tool] calling: ", toolResponse); - } - } +const completion = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "required" +}); +const message = completion.choices[0]?.message; +if (!message?.tool_calls?.length) throw new Error("The model did not request a tool."); +messages.push(message); +for (const call of message.tool_calls) { + if (call.type !== "function" || !Object.hasOwn(handlers, call.function.name)) { + throw new Error("The model requested an unsupported tool."); + } + const args = JSON.parse(call.function.arguments); + if (!args || typeof args !== "object" || Array.isArray(args)) { + throw new Error("Tool arguments must be a JSON object."); + } + const result = handlers[call.function.name](args); + console.log(`Result from [${call.function.name}]:`, result); + messages.push({ role: "tool", tool_call_id: call.id, content: JSON.stringify(result) }); } - -main(); +const followUp = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "none" +}); +const answer = followUp.choices[0]?.message?.content; +if (!answer?.trim()) throw new Error("The model did not return a final answer."); +console.log(answer); diff --git a/lessons/06-tool-calling/translations/README.fr.md b/lessons/06-tool-calling/translations/README.fr.md deleted file mode 100644 index 80a8ae54..00000000 --- a/lessons/06-tool-calling/translations/README.fr.md +++ /dev/null @@ -1,440 +0,0 @@ -# Leçon 6 : Appels d'outils - -L'appel d'outils, également connu sous le nom d'appel de fonction, consiste à améliorer votre modèle IA en lui ajoutant des capacités qu'il ne possédait pas auparavant. Le concept implique de fournir des méta-descriptions de vos fonctions, permettant au modèle IA de déterminer quand un outil particulier doit être appelé en fonction d'une sollicitation utilisateur. L'idée est de lui fournir les méta-descriptions de vos fonctions réelles et de faire en sorte que le modèle IA indique quand un tel outil doit être appelé en fonction de la sollicitation utilisateur. - -Dans ce chapitre, vous apprendrez : - -* Comment créer un outil. -* Intégrer un outil avec le modèle IA. -* Appeler l'outil depuis le modèle IA. - -## Configuration - -Si ce n'est pas encore fait, configurez votre environnement de développement. Voici comment procéder : [Configurez votre environnement](/docs/setup/README.md). - -## Ressources associées - -[![Intégration avec les appels de fonction](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/06-tool-calling/assets/11-lesson-banner.png)](https://aka.ms/gen-ai-lesson11-gh?WT.mc_id=academic-105485-koreyst) - -*Cette vidéo explique l'appel d'outils, une méthode qui aide l'IA à appeler vos fonctions et à élargir ainsi ses capacités.* - -*🎥 Cliquez sur l'image ci-dessus pour regarder une courte vidéo sur l'appel d'outils.* - -## Narrative: Amelia - -> *Vous êtes un mécanicien à Londres dans les années 1860. Alors que vous travaillez sur votre automate, vous recevez une lettre de Charles Babbage qui vous conduit à une bibliothèque, où vous récupérez un dispositif de voyage dans le temps. Au cours de vos voyages temporels, vous atterrissez à Florence, où vous rencontrez Léonard de Vinci. Vous êtes maintenant chez Ada Lovelace, dans son manoir, en compagnie de Charles Babbage. Ils sont en train de construire le dispositif de voyage dans le temps.* -> -> Reportez-vous à [Leçon 1](/lessons/01-intro-to-genai/README.md) si vous souhaitez reprendre l'histoire depuis le début. - -> [!NOTE] -> While we recommend going through the story (it's fun!), [click here](#interact-with-amelia-earhart) if you'd prefer to jump straight to the technical content. - -**Ada Lovelace**: "J'ai besoin que vous rencontriez une amie à moi. Peu de personnes peuvent égaler son expertise en mécanique et en résolution de problèmes. Il pourrait être difficile de la trouver, cependant ; elle est toujours en déplacement :)" - -You: "Who are we talking about and where can I find her?" - -**Ada Lovelace**: "Mais bien sûr, Amelia Earhart ! C'est une aviatrice et une aventurière, actuellement en train de faire le tour du monde. C'est totalement de ma faute si elle a disparu — je lui ai donné le dispositif de voyage dans le temps, eh bien, un prototype précoce de celui-ci. Heureusement, le dispositif que vous avez peut localiser d'autres appareils, donc vous pouvez la retrouver. Tout ce que vous devez faire est de cliquer ici et là, puis de tourner ce bouton." - -**Vous**: "Hé, attendez, quelle est exactement notre mission ?" - -**Ada**: "Oh, c'est vrai ! Demandez au dispositif ; il contient tous les détails. Demandez-lui simplement à propos d'Amelia, et il devrait activer l'outil approprié pour vous." - -Le monde autour de vous commence à se brouiller, et tout devient noir. Vous revenez à vous et vous trouvez dans le cockpit d’un avion. Vous êtes en vol, et vous pouvez voir l’océan sous vous. Il y a quelqu’un devant vous ; vous ne pouvez voir que l’arrière de leur cou. - -![Amelia pilotant un avion](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/06-tool-calling/assets/amelia.jpeg) - -**Vous**: "Amelia, c'est toi ?" - -**Amelia Earhart**: "Qui êtes-vous ? Laissez-moi deviner, Ada vous a envoyé, c'est ça ?" - -**Vous**: "Oui, c'est exact. Je suis là pour vous aider, je pense. Ada n'était pas très précise dans ses explications." - -**Amelia Earhart**: "Eh bien, bonne chose que vous soyez là. Je suis dans une situation compliquée. J'essaie de trouver un endroit où atterrir, et je commence à manquer de carburant. Pouvez-vous m'aider ?" - -**Vous**: "Dispositif, peux-tu m'en dire plus sur Amelia ?" - -**Time Beetle**: "Appel de l'outil : mission-amelia. Outil initialisé. Amelia Earhart est une aviatrice et une aventurière. Elle est connue pour ses vols record et sa disparition en 1937. Elle a été vue pour la dernière fois en vol au-dessus de l'océan Pacifique. Elle est actuellement en train de faire le tour du monde dans son avion, l'Electra. Elle manque de carburant et doit trouver un endroit pour atterrir." - -**Vous**: "Dispositif, peux-tu m'aider à trouver un endroit pour qu'Amelia atterrisse ?" - -**Time Beetle**: "Appel de l'outil : find-landing-spot. Outil initialisé. Recherche d'un endroit convenable pour qu'Amelia Earhart atterrisse. Veuillez patienter. Un endroit convenable trouvé. Coordonnées : 7.5°N, 134.5°E. Amelia, j'ai trouvé un endroit convenable pour que vous puissiez atterrir. Veuillez vous diriger vers les coordonnées 7.5°N, 134.5°E." - -**Amelia Earhart**: "Merci ! J'aurais aimé que mon dispositif ait cette fonctionnalité. Je vais m'y diriger maintenant." - -## Interagir avec Amelia Earhart - -If you want to interact with Ada, run the [Characters](/app/README.md) app. - -> [!IMPORTANT] -> Ceci est entièrement fictif ; les réponses sont générées par une IA. -> [Clause de non-responsabilité sur l'IA responsable](/README.md#responsible-ai-disclaimer) - -![Ada Lovelace](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/06-tool-calling/assets/amelia-front.jpeg) - -**Étapes** : - -1. Lancez un [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript). -2. Naviguez vers */app* à la racine du dépôt. -3. Localisez la console et exécutez `npm install` suivi de `npm start`. -4. Une fois que l'application est ouverte, sélectionnez le bouton "Ouvrir dans le navigateur". -5. Discutez avec Amelia. - -Pour une explication plus détaillée de l'application, voir [Explication détaillée de l'application](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). - -> [!NOTE] -> Si vous exécutez le projet localement sur votre machine, veuillez consulter le guide de démarrage rapide pour configurer un [token d'accès personnel GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) et remplacer la clé dans le code. - -## Appels d'outils - -**Vous**: "Dispositif, que vient-il de se passer ?" - -**Time Beetle**: "Vous venez d'appeler un outil. Un outil est une fonction qui peut être appelée par le modèle IA pour exécuter une tâche spécifique. L'outil peut être utilisé pour effectuer une grande variété de tâches, allant de calculs simples à des opérations complexes. Dans ce cas, vous avez appelé l'outil `find-landing-spot` pour aider Amelia Earhart à trouver un endroit convenable pour atterrir." - -**Time Beetle**: "Voici une illustration pour montrer le processus d'appel d'outil :" - -![Illustration du processus d'appel d'outil](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/06-tool-calling/assets/tool_call_langchain.png) - -*Crédit image Langchain * - -**Vous**: Pouvons-nous gérer les erreurs si quelque chose ne va pas, comme si l'outil ne trouve pas un point d'atterrissage ? - -**Time Beetle**: Excellente question ! Oui, vous pouvez ajouter une gestion des erreurs pour gérer de telles situations. Par exemple, si l'outil ne parvient pas à trouver un point d'atterrissage, vous pouvez utiliser un bloc try-catch ou vérifier le résultat avant de continuer. Voici un exemple de gestion des erreurs lors de l'appel de l'outil find-landing-spot : - -```javascript -try { - const landingSpot = findLandingSpot(7.5, 134.5); - if (!landingSpot) { - throw new Error("No suitable landing spot found"); - } - console.log(Landing spot found at coordinates: ${landingSpot.lat}, ${landingSpot.long}); -} catch (error) { - console.log(Error: ${error.message}); -} -``` - -**Vous**: "Comment puis-je créer un outil ?" - -**Time Beetle**: "Pour créer un outil, vous devez définir une fonction qui effectue la tâche souhaitée. La fonction doit prendre les entrées nécessaires et renvoyer la sortie. Vous pouvez ensuite appeler la fonction depuis le modèle IA pour effectuer la tâche. Voici à quoi ressemble l'outil `find-landing-spot` : - -```javascript -function findLandingSpot(lat, long) { - // Perform the task of finding a suitable landing spot - // Return the coordinates of the landing spot - return { lat: 7.5, long: 134.5 }; -} -``` - -**Vous**: "Ok, comment le modèle IA sait-il que cet outil existe ?" - -**Time Beetle**: "Vous devez enregistrer l'outil auprès du modèle IA. Cela indique au modèle que l'outil est disponible pour être appelé. Passons en revue cela dans la section suivante." - -### Enregistrement d'un outil - -**Vous**: "Vous avez dit que je devais enregistrer l'outil auprès du modèle IA. Comment puis-je faire cela ?" - -**Time Beetle**: "Pour enregistrer un outil auprès du modèle IA, vous devez définir une représentation de métadonnées de l'outil. Ces métadonnées doivent inclure le nom de l'outil, les paramètres d'entrée et le format de sortie. Vous pouvez ensuite enregistrer l'outil auprès du modèle IA en fournissant les métadonnées. Voici un exemple de métadonnées pour l'outil `find-landing-spot` : - -```json -{ - "name": "find-landing-spot", - "description": "Finds a suitable landing spot", - "parameters": { - "type": "object", - "properties": { - "lat": { - "type": "number", - "description": "The latitude of the location", - }, - "long": { - "type": "number", - "description": "The longitude of the location", - }, - }, - "required": ["lat", "long"], - }, - "output": { "type": "object", "properties": { "lat": "number", "long": "number" } } -} -``` - -**Vous**: "D'accord, il y a un morceau de JSON qui décrit l'outil, maintenant quoi ?" - -**Time Beetle**: "Maintenant, vous devez le fournir à votre appel de complétion de chat client comme ceci : - -```javascript - -function findLandingSpot(lat, long) { - // Perform the task of finding a suitable landing spot - // Return the coordinates of the landing spot - return { lat: 7.5, long: 134.5 }; -} - -function getBackgroundOnCharacter(character) { - // Perform the task of getting background information on a character - // Return the background information - return `Background information on ${character}`; -} - -const getBackgroundOnCharacterJson = { - name: "get-background-on-character", - description: "Get background information on a character", - parameters: { - type: "object", - properties: { - name: { - type: "string", - description: "The name of the character", - } - }, - required: ["lat", "long"], - }, - output: { type: "string" } -}; - -const findLandingSpotJson = { - name: "find-landing-spot", - description: "Finds a suitable landing spot", - parameters: { - type: "object", - properties: { - lat: { - type: "number", - description: "The latitude of the location", - }, - long: { - type: "number", - description: "The longitude of the location", - }, - }, - required: ["lat", "long"], - }, - output: { type: "object", properties: { lat: "number", long: "number" } } -}; - - -const messages = [{ - role: "user", - content: `Tell me about Amelia Earhart`, -}]; - -const completion = await openai.chat.completions.create({ - model: 'gpt-4o-mini', - messages: messages, - functions: [getBackgroundOnCharacterJson, findLandingSpotJson] - }); -``` - -**Time Beetle**: "Dans l'extrait de code précédent, nous :" - -* Définissons les métadonnées pour l'outil `find-landing-spot` et l'outil `get-background-on-character`. -* Fournissons ces métadonnées à l'appel `client.getChatCompletions` dans le paramètre `functions`. Cela indique au modèle IA que ces outils sont disponibles pour être appelés. - -**Vous**: "Compris, donc le modèle IA appellera l'outil approprié si je fournis une invite qui correspond à la description de l'outil ?" - -**Time Beetle**: "Presque, il vous dira quel outil il pense que vous devriez appeler et fournira les paramètres d'entrée analysés, mais vous devrez appeler l'outil vous-même, laissez-moi vous montrer comment faire." - -### Appeler un outil - -**Time Beetle**: "Comme je le disais, le modèle IA vous dira quel outil il pense que vous devriez appeler et il fournira les paramètres d'entrée analysés. Vous devez alors appeler l'outil vous-même. Voici à quoi ressemblera le workflow étape par étape : - -1. Connecter l'appel d'outil - - Tout d'abord, vous devez connecter l'appel d'outil dans votre code. Cela implique de créer la fonction et une représentation des métadonnées de l'outil, puis de fournir les métadonnées au modèle IA. - -2. L'utilisateur fait une demande via une invite : - * Le programme envoie une demande de complétion de chat au modèle IA avec l'invite utilisateur et les métadonnées des outils fournies. - * Le programme reçoit une réponse du modèle IA avec l'appel d'outil et les paramètres d'entrée analysés s'il estime qu’un outil doit être appelé. - * Dans ce cas, le développeur interprète la réponse et invoque l'outil en fonction de la suggestion d'appel de fonction fournie par le modèle IA. - -**Vous**: "Super, maintenant que je comprends globalement ce qui se passe, pouvez-vous me montrer un peu de code ?" - -**Time Beetle**: "Bien sûr, voici le code pour configurer l'appel d'outil, effectuer une demande de complétion de chat et interpréter la réponse : - -```javascript -import { OpenAI } from 'openai'; -import { maybeCoerceInteger } from 'openai/core.mjs'; - -// 1: Define the function -function findLandingSpot(lat, long) { - console.log("[Function] Finding landing spot with coordinates: ", lat, long); - // Perform the task of finding a suitable landing spot - // Return the coordinates of the landing spot - return { lat: 7.5, long: 134.5 }; -} - -// 2: Define the tool metadata, should include description, parameters, and output -const findLandingSpotJson = { - name: "find-landing-spot", - description: "Finds a suitable landing spot", - parameters: { - type: "object", - properties: { - lat: { - type: "number", - description: "The latitude of the location", - }, - long: { - type: "number", - description: "The longitude of the location", - }, - }, - required: ["lat", "long"], - }, - output: { type: "object", properties: { lat: "number", long: "number" } } -}; - -// 3: Add the tool to the tools object that we will use later to invoke the tool -const tools = { - [findLandingSpotJson.name]: findLandingSpot -}; - -// 4: Create an instance of the OpenAI client -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", // might need to change to this url in the future: https://models.github.ai/inference - apiKey: process.env.GITHUB_TOKEN, -}); - -// 5: Define the messages that will be sent to the AI model -const messages = [ -{ - role: "system", - content: `You are a helpful assistant. You can call functions to perform tasks. Make sure to parse the function call and arguments correctly.` -}, { - role: "user", - content: "Find a landing spot given coordinates 8.5, 130.5" -} -]; - -async function main(){ - console.log("Making LLM call") - - // 6: Call the AI model with the defined messages and tools - const result = await openai.chat.completions.create({ - model: 'gpt-4o', - messages: messages, - functions: [findLandingSpotJson] - }); - - for (const choice of result.choices) { - - let functionCall = choice.message?.function_call; - let functionName = functionCall?.name; - let args = JSON.parse(functionCall?.arguments); - - // 7: Interpret response and call the tool based on the function call provided by the AI model - if (functionName && functionName in tools) { - console.log(`Calling [${functionName}]`); - const toolFunction = tools[functionName]; - const toolResponse = toolFunction(...Object.values(args)); // Extract values from args and spread them - console.log("Result from [tool] calling: ", toolResponse); - } - } -} - -main(); -``` - -Dans le code précédent, nous avons : - -* Créé une fonction appelée `findLandingSpot` qui prend la latitude et la longitude en entrée et renvoie les coordonnées d'un point d'atterrissage convenable. - -* Défini les métadonnées pour l'outil `find-landing-spot`. - -* Créé un objet `tools` qui associe les noms d'outils aux métadonnées des outils. - -* Fournissons l'objet `tools` dans l'appel `client.getChatCompletions`. - - ```javascript - if (functionName && functionName in tools) { - console.log(`Calling [${functionName}]`); - const toolFunction = tools[functionName]; - const toolResponse = toolFunction(...Object.values(args)); // Extract values from args and spread them - console.log("Result from [tool] calling: ", toolResponse); - } - ``` - -* Appelé l'outil en fonction de l'appel de fonction fourni par le modèle IA. - -* Affiché le résultat de l'appel d'outil. - -**Vous**: "Je pense que je comprends. Je définis une fonction, je crée une représentation des métadonnées de l'outil, je fournis les métadonnées au modèle IA, puis j'appelle l'outil en fonction de l'appel de fonction fourni par le modèle IA." - -**Time Beetle**: "Exactement ! Vous êtes prêt à commencer à créer vos propres outils et à les intégrer au modèle IA." - -## Exercice - Améliorer le dispositif de voyage dans le temps d'Amelia - -**Amelia Earhart**: "Nous arrivons en force, remercions le ciel que vous nous ayez trouvé un point d'atterrissage. Accrochez-vous bien !" - -Amelia atterrit habilement l’avion sur une petite île. Vous et Amelia sortez de l’avion, et celle-ci vous tend un petit dispositif. - -**Amelia Earhart**: "Voici mon appareil, pas aussi sophistiqué que le vôtre, mais il a quelques fonctionnalités sympas. Je l'ai utilisé pour, disons, faire un peu de voyage dans le temps moi-même. Pouvez-vous l'améliorer pour moi ?" - -**Vous**: "Time Beetle, peux-tu m'aider à améliorer l'appareil d'Amelia ?" - -**Time Beetle**: "Bien sûr ! Pour améliorer l'appareil d'Amelia, ajoutons-lui les outils suivants : - -* **Un outil qui peut** : Calculer la distance entre deux points sur une carte. -* **Un outil qui peut** : Déterminer la position GPS où Amelia se trouve actuellement. -* **Un outil qui peut** : Appeler une API externe pour obtenir les prévisions météorologiques pour un emplacement donné. - -Voici les fonctions ; tout ce que vous devez faire est de les enregistrer et de les tester : - -```javascript -function calculateDistance(lat1, long1, lat2, long2) { - // Perform the task of calculating the distance between two points - // Return the distance between the points - return Math.sqrt((lat2 - lat1) ** 2 + (long2 - long1) ** 2); -} - -function getGpsPosition() { - // Perform the task of getting the GPS position of the current location - // Return the GPS position - return { lat: 7.5, long: 134.5 }; -} - -function getWeatherForecast(lat, long) { - // Perform the task of getting the weather forecast for a given location - // Return the weather forecast - return "Sunny"; -} -``` - -**Vous**: "Time Beetle, es-tu sûr que ces fonctions vont fonctionner, on dirait qu'elles renvoient juste des valeurs aléatoires ?" - -**Time Beetle**: "C'est vrai, je peux faire le reste en interne. Tout ce que vous avez à faire est de les enregistrer et de les tester, assurez-vous que le modèle IA peut les appeler." - -> Tâche : Enregistrez les outils `calculateDistance`, `getGpsPosition`, et `getWeatherForecast` auprès du modèle IA. Testez les outils en les appelant depuis le modèle IA. Utilisez le code fourni dans les sections précédentes comme référence. - -## Solution - -[Solution](/lessons/06-tool-calling/solution/solution.js) - -## Vérification des connaissances - -**Question:**\ -Quel est l'objectif d'enregistrer un outil auprès du modèle IA ? - -A. Permettre au modèle IA d'exécuter directement l'outil sans intervention du développeur.\ -B. Fournir des métadonnées sur l'outil afin que le modèle IA puisse suggérer son utilisation.\ -C. Remplacer le besoin de définir des fonctions dans le code. - -**Question:**\ -Quel est le rôle des métadonnées d'outils dans l'appel d'outils ? - -A. Décrire l'objectif de l'outil, ses entrées et sorties pour le modèle IA.\ -B. Fournir au modèle IA les détails d'implémentation de l'outil.\ -C. Garantir que l'outil soit exécuté automatiquement par le modèle IA. - -**Question:**\ -Pourquoi utiliser les appels d'outils ? - -A. Permettre au modèle IA d'effectuer des tâches au-delà de ses capacités intégrées en utilisant des fonctions externes.\ -B. Remplacer le besoin d'une intervention humaine dans le développement de modèles IA.\ -C. Permettre au modèle IA d'exécuter des outils sans exiger de métadonnées. - -[Solution quiz](/lessons/06-tool-calling/solution/solution-quiz.md) - -## Ressources d'auto-apprentissage - -* Explique le [processus d'appel d'outils](https://learn.microsoft.com/en-us/semantic-kernel/concepts/ai-services/chat-completion/function-calling/?pivots=programming-language-csharp) -* Appel d'outil dans le [cadre Langchain.js](https://js.langchain.com/docs/how_to/tool_calling/) -* Appel de fonction comme démontré dans la [bibliothèque openai](https://github.com/openai/openai-node/blob/master/examples/function-call.ts) diff --git a/lessons/07-mcp/README.md b/lessons/07-mcp/README.md index 212b80bf..4546c1e8 100644 --- a/lessons/07-mcp/README.md +++ b/lessons/07-mcp/README.md @@ -65,14 +65,14 @@ If you want to interact with Scipio, run the [Characters](/app/README.md) app. 1. Start a [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) 2. Navigate to _/app_ in the repo root. -3. Locate the console and run `npm install` followed by `npm start`. +3. Locate the console and run `npm ci` followed by `npm start`. 4. Once it appears, select the "Open in Browser" button. 5. Chat with Scipio. For a more detailed explanation of the app, see [Detailed app explanation](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). > [!NOTE] - > If you're running the project locally on your machine, please review the QuickStart guide to get a [GitHub personal access](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) token setup and replace the key in the code. +> Configure `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL` in the repository-root `.env` for both local development and Codespaces. See the [setup guide](/docs/setup/README.md#configure-environment-variables). ## The need for Model Context Protocol (MCP) @@ -173,8 +173,8 @@ The preceding code does the following: **Time Beetle**: "No, not yet. But we can add resources and tools to it. A tool is something that can be used to perform a specific task, while a resource is something that can be used to provide data or functionality to the tool. Let's add some below: ```typescript -server.tool("add", - { a: z.number(), b: z.number() }, +server.registerTool("add", + { description: "Add two numbers", inputSchema: { a: z.number(), b: z.number() } }, async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }] }) @@ -192,9 +192,10 @@ In the preceding code, we: **Time Beetle**: "Exactly! The MCP client can call this tool and pass the required parameters. Let's add a resource to the server as well: ```typescript -server.resource( +server.registerResource( "greeting", new ResourceTemplate("greeting://{name}", { list: undefined }), + { mimeType: "text/plain" }, async (uri, { name }) => ({ contents: [{ uri: uri.href, @@ -213,9 +214,10 @@ In this code, we: **You**: "So, the resource is like a data source, this could be a database, file or even an API? If this was a for a file I would use a file:// URI?" like so: ```typescript -server.resource( +server.registerResource( "file", new ResourceTemplate("file://{path}", { list: undefined }), + { mimeType: "text/plain" }, async (uri, { path }) => ({ // do something with the file at path, e.g., read its contents contents: [{ @@ -254,14 +256,14 @@ In this code, we: **Time Beetle**: "You can test your MCP server by running it in a terminal by running the inspector like so: ```bash -npx @modelcontextprotocol/inspector node build/index.js +npm run inspect:web ``` Above, we are using the `@modelcontextprotocol/inspector` package to run the MCP server. We provide the argument `node build/index.js` to specify the entry point of the server. This will start the MCP server and allow you to interact with it through the inspector. **You**: "And how do I interact with it?" -**Time Beetle**: "This starts a web server on port 6274. You can access the inspector by opening your web browser and navigating to `http://localhost:6274`. The inspector provides a user-friendly interface for interacting with your MCP server, allowing you to test the tools and resources you've defined." +**Time Beetle**: "Run this command in the lesson package directory. It builds the server and starts the current web inspector. Open the local address printed in the terminal. The interface can differ from the older images below. Keep inspector ports private." ![Inspector user interface](https://softchris.github.io/mcp-workshop/assets/images/connect-7703c67645f368d51c7b24a5d635d6a0.png) @@ -349,7 +351,7 @@ So when you list tools, you get a response on the following format: { "name": "", - "description": "". + "description": "", "inputSchema": { "type":"object", "properties":{ @@ -371,7 +373,7 @@ which means, if you have a tool `add`, your response, listing tools, looks like ```json { "name": "add", - "description": "Adding two numbers". + "description": "Adding two numbers", "inputSchema": { "type":"object", "properties":{ @@ -396,8 +398,8 @@ which means, if you have a tool `add`, your response, listing tools, looks like // List tools const { tools } = await client.listTools(); -const addTool = tools[0]; // Assuming the first tool is "add" -const subtractTool = tools[1]; // Assuming the second tool is "subtract" +const addTool = tools.find(tool => tool.name === "add"); +if (!addTool) throw new Error("The server did not advertise the add tool."); // Call a tool const result = await client.callTool({ @@ -414,14 +416,10 @@ const result = await client.callTool({ **Time Beetle**: "Resources are similar, but you need to provide the resource URI and any parameters it requires. Here's an example: ```typescript -let resourceUri = "greeting://John"; // Replace with the actual resource URI - -// call resource -// Read a resource -const name = "John Doe"; // Replace with the actual name +const name = "John Doe"; const resourceResult = await client.readResource({ - uri: `greeting:///${name}`, + uri: `greeting://${encodeURIComponent(name)}`, }); console.log(resourceResult); // Hi John Doe! diff --git a/lessons/07-mcp/code/build/client.js b/lessons/07-mcp/code/build/client.js index 64280b9a..f79e17a7 100644 --- a/lessons/07-mcp/code/build/client.js +++ b/lessons/07-mcp/code/build/client.js @@ -1,82 +1,69 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; -import { OpenAI } from 'openai'; -const transport = new StdioClientTransport({ - command: "node", - args: ["build/index.js"] -}); -const client = new Client({ - name: "example-client", - version: "1.0.0" -}); -await client.connect(transport); -function toToolSchema(method, schema) { - return { - name: method, - description: `This is a tool that does ${method}`, - parameters: schema, - }; +import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js"; +import { fileURLToPath } from "node:url"; +import { OpenAI } from "openai"; +import { z } from "zod"; +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); } -// list tools -const { tools } = await client.listTools(); -tools.forEach((tool) => { - console.log(`Tool: ${tool.name}`); - console.log(`Description: ${tool.description}`); - console.log(`Input schema: ${JSON.stringify(tool.inputSchema)}`); -}); -const toolsForLLM = tools.map((tool) => { - return toToolSchema(tool.name, tool.inputSchema); -}); -console.log("Schema for tool", JSON.stringify(toolsForLLM, null, 2)); -// TODO feed this to an llm as functions -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", // might need to change to this url in the future: https://models.github.ai/inference - apiKey: process.env.GITHUB_TOKEN, +const openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); +const client = new Client({ name: "example-client", version: "1.0.0" }); +const transport = new StdioClientTransport({ + command: process.execPath, + args: [fileURLToPath(new URL("./index.js", import.meta.url))] }); -const messages = [ - { - role: "system", - content: `You are a helpful assistant. You can call functions to perform tasks. Make sure to parse the function call and arguments correctly.` - }, { - role: "user", - content: "Add 5 and 10", - name: "example-user" // Adding the required 'name' property for user role +try { + await client.connect(transport); + const { tools: serverTools } = await client.listTools(); + const allowedNames = new Set(serverTools.map(tool => tool.name)); + const tools = serverTools.map(tool => ({ + type: "function", + function: { + name: tool.name, + description: tool.description, + parameters: tool.inputSchema + } + })); + console.log("Available tools:", [...allowedNames]); + const messages = [ + { role: "user", content: "Use the add tool to add 5 and 10." } + ]; + const completion = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "required" + }); + const message = completion.choices[0]?.message; + if (!message?.tool_calls?.length) + throw new Error("The model did not request a tool."); + messages.push(message); + for (const call of message.tool_calls) { + if (call.type !== "function" || !allowedNames.has(call.function.name)) { + throw new Error("The model requested an unsupported MCP tool."); + } + const args = z.record(z.string(), z.unknown()).parse(JSON.parse(call.function.arguments)); + const result = CallToolResultSchema.parse(await client.callTool({ + name: call.function.name, + arguments: args + })); + const text = result.content.filter(item => item.type === "text").map(item => item.text).join("\n"); + if (result.isError) + throw new Error(`MCP tool failed: ${text}`); + if (!text) + throw new Error("The MCP tool did not return text."); + console.log("Result from tool:", text); + messages.push({ role: "tool", tool_call_id: call.id, content: text }); } -]; -openai.chat.completions.create({ - model: 'gpt-4o', - messages: messages, - functions: toolsForLLM -}).then((result) => { - console.log("Result", result.choices[0].message); -}).catch((error) => { - console.error("Error:", error); -}); -const result = await openai.chat.completions.create({ - model: 'gpt-4o', - messages: messages, - functions: toolsForLLM -}); -for (const choice of result.choices) { - // console.log("Result", choice.message); - let functionCall = choice.message?.function_call; - let functionName = functionCall?.name; - console.log("Function call: ", functionName); - let args = functionCall?.arguments; - await client.callTool({ - name: functionName ?? "", - arguments: typeof args === "string" ? JSON.parse(args) : args ?? {} - }, CallToolResultSchema).then((result) => { - console.log("Result from tool: ", result); + const followUp = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "none" }); + const answer = followUp.choices[0]?.message?.content; + if (!answer?.trim()) + throw new Error("The model did not return a final answer."); + console.log(answer); +} +finally { + await client.close(); } -// call tool -// const result = await client.callTool({ -// name: "add", -// arguments: { -// a: 5, -// b: 10 -// } -// }); -// console.log(`Result: ${JSON.stringify(result)}`); diff --git a/lessons/07-mcp/code/build/index-with-llm.js b/lessons/07-mcp/code/build/index-with-llm.js deleted file mode 100644 index cb0ff5c3..00000000 --- a/lessons/07-mcp/code/build/index-with-llm.js +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/lessons/07-mcp/code/build/index.js b/lessons/07-mcp/code/build/index.js index aaac24cc..713ab651 100755 --- a/lessons/07-mcp/code/build/index.js +++ b/lessons/07-mcp/code/build/index.js @@ -1,38 +1,17 @@ -// simple mc server import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; -// Create an MCP server -const server = new McpServer({ - name: "Demo", - version: "1.0.0" -}); -// Add an addition tool -// @ts-ignore -server.tool("add", { - input: z.object({ - a: z.number(), - b: z.number() - }), - output: z.object({ - content: z.array(z.object({ type: z.string(), text: z.string() })) - }) -}, async ({ input }) => ({ - content: [{ type: "text", text: String(input.a + input.b) }] +const server = new McpServer({ name: "Demo", version: "1.0.0" }); +server.registerTool("add", { + description: "Add two numbers", + inputSchema: { a: z.number(), b: z.number() } +}, async ({ a, b }) => ({ + content: [{ type: "text", text: String(a + b) }] })); -// Add a dynamic greeting resource -server.resource("greeting", new ResourceTemplate("greeting://{name}", { list: undefined }), async (uri, { name }) => ({ - contents: [{ - uri: uri.href, - text: `Hello, ${name}!` - }] +server.registerResource("greeting", new ResourceTemplate("greeting://{name}", { list: undefined }), { description: "Greet a named person", mimeType: "text/plain" }, async (uri, { name }) => ({ + contents: [{ uri: uri.href, text: `Hello, ${name}!` }] })); -// Start receiving messages on stdin and sending messages on stdout -const transport = new StdioServerTransport(); -async function runServer() { - await server.connect(transport); -} -runServer().catch((err) => { - console.error("Error in server:", err); - process.exit(1); +server.connect(new StdioServerTransport()).catch(error => { + console.error("Error in server:", error instanceof Error ? error.message : String(error)); + process.exitCode = 1; }); diff --git a/lessons/07-mcp/code/package-lock.json b/lessons/07-mcp/code/package-lock.json index eed291cc..255786de 100644 --- a/lessons/07-mcp/code/package-lock.json +++ b/lessons/07-mcp/code/package-lock.json @@ -5,67 +5,1099 @@ "packages": { "": { "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", - "openai": "^4.91.1" + "@modelcontextprotocol/sdk": "^1.32.1", + "openai": "^7.28.0", + "zod": "^4.6.5" }, "bin": { "sample-server": "build/index.js" }, "devDependencies": { - "@types/node": "^22.13.17", - "typescript": "^5.8.2" + "@modelcontextprotocol/inspector": "^2.9.0", + "@types/node": "^24.19.1", + "typescript": "^7.0.2" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", + "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.3.tgz", + "integrity": "sha512-TA//nWMqPhbfdfneACk6t5a9eqbS9lABEPyKn0/xZTah3H3U2XaVg85rJFl0/Fyit0I552YDHgXGVSf3GwqbUw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/client": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.2.0.tgz", + "integrity": "sha512-LxCou/CSYQ6dwEnjhLZY0KnEuc8V4UJ3IEQCl/uR2yHobSQIEdJKUlKLRYRF5i5FqRGHy1eK7une3aAWDHLtig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.2.0", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "jose": "^6.1.3", + "pkce-challenge": "^5.0.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.2.0.tgz", + "integrity": "sha512-iLhmprRmWI8EcosOA3wVvww22z02NkgqhV4fBH6f/odQBsi7xnJc0HGmi21yWO+/iKw2yzqVE127Zhna5/+JXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/ext-apps": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-2.0.3.tgz", + "integrity": "sha512-Tn+4+cyhO4f4cQSDxNyLiIfEE+qlTTuIbWBJB8JTinEXOhDYgCT4wDTQplxD9MBYZH5pCYGC59GaxiwIT4bu8w==", + "dev": true, + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "dependencies": { + "@standard-schema/spec": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/client": "^2.0.0", + "@modelcontextprotocol/core": "^2.0.0", + "@modelcontextprotocol/server": "^2.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "zod": "^4.2.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/server": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/inspector": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/inspector/-/inspector-2.9.0.tgz", + "integrity": "sha512-77VWBmsA18jdxQ1vqbu8SNRSC2h5uBHyUq6wTmlqEDeVIST7YEVta0OyFCDf/6VSQKbZ3ia7Qb+9bZtlTcB+tw==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@hono/node-server": "^2.0.12", + "@modelcontextprotocol/client": "2.2.0", + "@modelcontextprotocol/core": "2.2.0", + "@modelcontextprotocol/ext-apps": "^2.0.0", + "@modelcontextprotocol/server": "2.2.0", + "@napi-rs/keyring": "^1.3.0", + "@vitejs/plugin-react": "^6.0.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "chokidar": "^4.0.3", + "commander": "^13.1.0", + "hono": "^4.13.7", + "ink": "^6.0.0", + "open": "^10.2.0", + "pino": "^9.14.0", + "proper-lockfile": "^4.1.2", + "react": "^19.0.0", + "undici": "^8.11.2", + "vite": "^8.1.5", + "yaml": "^2.9.0", + "zod": "^4.4.3" + }, + "bin": { + "mcp-inspector": "clients/launcher/build/index.js" + }, + "engines": { + "node": ">=22.19.0" } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz", - "integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==", + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.1.tgz", + "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==", "license": "MIT", "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", - "cross-spawn": "^7.0.3", + "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.2.0.tgz", + "integrity": "sha512-qFnltjus6Gk8Lx6J1w2RXmr3YPN22MNT6qD6zjAkaiWIlFIFxWClz0rXHNuJF/KSUyn5YGhkSvXkWDbQHVLFEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.2.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@napi-rs/keyring": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring/-/keyring-1.3.0.tgz", + "integrity": "sha512-WrOw/bcXm0f9qHkumlT1QlArXSTWqaY9sunsDpOk+yCCorCKMxvWT/a3xko4EYHVdeZoh00yI2TydXn6eyICDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/keyring-darwin-arm64": "1.3.0", + "@napi-rs/keyring-darwin-x64": "1.3.0", + "@napi-rs/keyring-freebsd-x64": "1.3.0", + "@napi-rs/keyring-linux-arm-gnueabihf": "1.3.0", + "@napi-rs/keyring-linux-arm64-gnu": "1.3.0", + "@napi-rs/keyring-linux-arm64-musl": "1.3.0", + "@napi-rs/keyring-linux-riscv64-gnu": "1.3.0", + "@napi-rs/keyring-linux-x64-gnu": "1.3.0", + "@napi-rs/keyring-linux-x64-musl": "1.3.0", + "@napi-rs/keyring-win32-arm64-msvc": "1.3.0", + "@napi-rs/keyring-win32-ia32-msvc": "1.3.0", + "@napi-rs/keyring-win32-x64-msvc": "1.3.0" + } + }, + "node_modules/@napi-rs/keyring-darwin-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-darwin-arm64/-/keyring-darwin-arm64-1.3.0.tgz", + "integrity": "sha512-pl76hJvdYUBn6I24bXiOBMA9nbDapo3I5B+f3OorjDU4dUMSypXeKbOVehJe8fhgTiH24flMyTS3aAIy43xegQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-darwin-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-darwin-x64/-/keyring-darwin-x64-1.3.0.tgz", + "integrity": "sha512-YcJtEV5LA3cvA4z3BurgxH5IhTsW1JfIvcAAcqcecwk06Si9F9NqkxbZVIfDwQ8oRHgaBmT3zZJnLAotCrVahw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-freebsd-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-freebsd-x64/-/keyring-freebsd-x64-1.3.0.tgz", + "integrity": "sha512-vlLf31TGhfRAaxLDBhg8b89ss0HHD/lyNmL5F3UjSaz5CUXElsJmKYq9fqA/B+cZKUEUcLHHGhF0I/CqcFdaVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm-gnueabihf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm-gnueabihf/-/keyring-linux-arm-gnueabihf-1.3.0.tgz", + "integrity": "sha512-KiWdMMu/Inz/bHHIAGrnF7r54FZDYXuHO6UFF/rhIrshUsxbMG1Rl9lEymNtqqsVo927G0VYcb02FzWQ3iBQRQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm64-gnu/-/keyring-linux-arm64-gnu-1.3.0.tgz", + "integrity": "sha512-eyKGpY40lm9Jvs1aD294XRH4y7+TlJM0YVAryZeXA6TX0mb4gMkxVXwSQv7MCwgah7raeUd0dKUb4BPAYIgcMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm64-musl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm64-musl/-/keyring-linux-arm64-musl-1.3.0.tgz", + "integrity": "sha512-iIK6JWHXAJqDrEyLY3TmswwloVyt2vj+04TZnew+uSJ9gnDO8EwRbp3/iw3LpWaXiDO7VomGO6y8I0Id8uBZSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-riscv64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-riscv64-gnu/-/keyring-linux-riscv64-gnu-1.3.0.tgz", + "integrity": "sha512-/PGqrwn6EwgtK6vccASSXJRfOSP4vN1F4ASsIQ+7MdrK6hNvAJ1FZPrIuD5gGGdxezo3F++To2Wq7DbuGIeuNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-x64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-x64-gnu/-/keyring-linux-x64-gnu-1.3.0.tgz", + "integrity": "sha512-2PDK1WKWTu9lBGq9VvNEkSlQD3O7YwVpmnyN2M3cy4v7NJ/8gDMd9GXv3G+FVXN13uhp4gnnPBS+ScefmEeD2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-x64-musl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-x64-musl/-/keyring-linux-x64-musl-1.3.0.tgz", + "integrity": "sha512-oJ2HkX8YUo46QBkn0pG+HuIKQNqr523q6vBobCn+P95s4C4K6/kLBqHY/1bg5J4ap31DzsznhnFKcfBNBsjCnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-arm64-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-arm64-msvc/-/keyring-win32-arm64-msvc-1.3.0.tgz", + "integrity": "sha512-tOd3c/uAaeoE4ycVlmAdSvygz0Zt3zdca6Y7gokBeIbaRDWpjDIUOpU3MvML59XAaqyuKGsVVu0F/DZb1lHPmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-ia32-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-ia32-msvc/-/keyring-win32-ia32-msvc-1.3.0.tgz", + "integrity": "sha512-sPSqeAFZMGqP1R++M2JTza7GQJJ/TpCo6JU6Vcd4jnebvOaEDs9b7eipakU1PJdSvhpC2yXMCNRk9gXfrhuwHQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-x64-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-x64-msvc/-/keyring-win32-x64-msvc-1.3.0.tgz", + "integrity": "sha512-4DnCWXwDc0HRKwyRlG5y0VhKZW2tNRQfKKfyj6IX/KWfDNyq9hn4n+GL1auyDcOO/v8PwnhmYo2+rOOqCkvvOg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", + "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", + "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", + "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", + "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", + "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", + "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", + "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", + "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", + "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", + "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", + "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", + "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", + "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", + "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", + "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", + "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz", + "integrity": "sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=18" + "node": ">=16.20.0" } }, - "node_modules/@types/node": { - "version": "22.14.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", - "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.2.tgz", + "integrity": "sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw==", + "dev": true, "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { - "node": ">=6.5" + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.152.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } } }, "node_modules/accepts": { @@ -81,42 +1113,166 @@ "node": ">= 0.6" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, "license": "MIT", "dependencies": { - "humanize-ms": "^1.2.1" + "environment": "^1.0.0" }, "engines": { - "node": ">= 8.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/atomically": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-fs": "^2.0.0", + "when-exit": "^2.1.4" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" }, "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bytes": { @@ -157,28 +1313,115 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "restore-cursor": "^4.0.0" }, "engines": { - "node": ">= 0.6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -190,6 +1433,16 @@ "node": ">= 0.6" } }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -236,9 +1489,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -252,13 +1505,47 @@ } } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/depd": { @@ -270,6 +1557,16 @@ "node": ">= 0.8" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -290,13 +1587,33 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.8" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/es-define-property": { @@ -318,9 +1635,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -329,20 +1646,18 @@ "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] }, "node_modules/escape-html": { "version": "1.0.3", @@ -350,6 +1665,16 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -359,15 +1684,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/eventsource": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", @@ -390,18 +1706,19 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -432,10 +1749,14 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, "engines": { "node": ">= 16" }, @@ -443,13 +1764,53 @@ "url": "https://github.com/sponsors/express-rate-limit" }, "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -460,242 +1821,656 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.13.13", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.13.tgz", + "integrity": "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ink": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz", + "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.4", + "ansi-escapes": "^7.3.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^5.1.1", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "scheduler": "^0.27.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^8.0.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.1", + "terminal-size": "^4.0.1", + "type-fest": "^5.4.1", + "widest-line": "^6.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": ">=6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ip-address": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz", + "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" + "get-east-asian-width": "^1.3.1" }, "engines": { - "node": ">= 6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" - }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "dev": true, "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, "engines": { - "node": ">= 0.6" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" }, "engines": { - "node": ">= 0.6" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, "license": "MIT", "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" + "is-inside-container": "^1.0.0" }, "engines": { - "node": ">= 12.20" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fresh": { + "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/panva" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "detect-libc": "^2.0.3" }, "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -707,12 +2482,16 @@ } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/merge-descriptors": { @@ -737,69 +2516,83 @@ } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=6" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, "engines": { - "node": ">=10.5.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "license": "MIT", "dependencies": { - "whatwg-url": "^5.0.0" + "content-type": "^2.1.0" }, "engines": { - "node": "4.x || >=6.0.0" + "node": ">=18" }, - "peerDependencies": { - "encoding": "^0.1.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/object-assign": { @@ -823,6 +2616,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -844,28 +2647,70 @@ "wrappy": "1" } }, - "node_modules/openai": { - "version": "4.93.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.93.0.tgz", - "integrity": "sha512-2kONcISbThKLfm7T9paVzg+QCE1FOZtNMMUfXyXckUAoXRRS/mTP89JSDHPMp8uM5s0bz28RISbvQjArD6mgUQ==", - "license": "Apache-2.0", + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" + "mimic-fn": "^2.1.0" }, - "bin": { - "openai": "bin/cli" + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openai": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.28.0.tgz", + "integrity": "sha512-HSY4fFLflQGYe2kvO0atm6/GzhD61gpyiOZZUvO4nptMUhuB1to2K3wwFpFqc5gfEK0SxyIIFiu1yE2MtygWcA==", + "license": "Apache-2.0", + "engines": { + "node": ">=22.0.0" }, "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, "ws": { "optional": true }, @@ -874,21 +2719,6 @@ } } }, - "node_modules/openai/node_modules/@types/node": { - "version": "18.19.86", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.86.tgz", - "integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/openai/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -898,6 +2728,16 @@ "node": ">= 0.8" } }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -908,14 +2748,75 @@ } }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" } }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "dev": true, + "license": "MIT" + }, "node_modules/pkce-challenge": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", @@ -925,10 +2826,68 @@ "node": ">=16.20.0" } }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process-warning": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz", + "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -936,15 +2895,20 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -953,28 +2917,159 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true, + "license": "MIT" + }, "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", "engines": { "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rolldown": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.152.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.12", + "@rolldown/binding-android-arm64": "1.2.12", + "@rolldown/binding-darwin-arm64": "1.2.12", + "@rolldown/binding-darwin-x64": "1.2.12", + "@rolldown/binding-freebsd-x64": "1.2.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", + "@rolldown/binding-linux-arm64-gnu": "1.2.12", + "@rolldown/binding-linux-arm64-musl": "1.2.12", + "@rolldown/binding-linux-ppc64-gnu": "1.2.12", + "@rolldown/binding-linux-s390x-gnu": "1.2.12", + "@rolldown/binding-linux-x64-gnu": "1.2.12", + "@rolldown/binding-linux-x64-musl": "1.2.12", + "@rolldown/binding-openharmony-arm64": "1.2.12", + "@rolldown/binding-win32-arm64-msvc": "1.2.12", + "@rolldown/binding-win32-x64-msvc": "1.2.12" } }, "node_modules/router": { @@ -993,25 +3088,28 @@ "node": ">= 18" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -1019,32 +3117,43 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -1054,6 +3163,10 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -1084,14 +3197,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -1103,13 +3216,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -1155,15 +3268,185 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stubborn-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", + "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-utils": "^1.0.1" + } + }, + "node_modules/stubborn-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", + "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-size": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", + "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thread-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1173,44 +3456,103 @@ "node": ">=0.6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" + "node_modules/type-fest": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.10.0.tgz", + "integrity": "sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "tsc": "bin/tsc" }, "engines": { - "node": ">=14.17" + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undici": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -1231,30 +3573,90 @@ "node": ">= 0.8" } }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "node_modules/vite": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", + "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", + "dev": true, "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.11", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">= 14" + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } + "node_modules/when-exit": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", + "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", + "dev": true, + "license": "MIT" }, "node_modules/which": { "version": "2.0.2", @@ -1271,28 +3673,141 @@ "node": ">= 8" } }, + "node_modules/widest-line": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", + "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "dev": true, + "license": "MIT" + }, "node_modules/zod": { - "version": "3.24.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", - "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.24.1" + "zod": "^3.25.28 || ^4" } } } diff --git a/lessons/07-mcp/code/package.json b/lessons/07-mcp/code/package.json index 89a1c407..7ad6ab78 100644 --- a/lessons/07-mcp/code/package.json +++ b/lessons/07-mcp/code/package.json @@ -1,21 +1,26 @@ { "type": "module", "bin": { - "sample-server": "./build/index.js" + "sample-server": "./build/index.js" }, "scripts": { - "build": "tsc && node build/index.js", - "client": "node build/client.js" + "build": "tsc", + "start": "node build/index.js", + "client": "npm run build && node --env-file-if-exists=../../../.env build/client.js", + "inspect": "npm run build && mcp-inspector --cli node build/index.js --method tools/list", + "inspect:web": "npm run build && mcp-inspector --web node build/index.js" }, "files": [ - "build" + "build" ], "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", - "openai": "^4.91.1" + "@modelcontextprotocol/sdk": "^1.32.1", + "openai": "^7.28.0", + "zod": "^4.6.5" }, "devDependencies": { - "@types/node": "^22.13.17", - "typescript": "^5.8.2" + "@modelcontextprotocol/inspector": "^2.9.0", + "@types/node": "^24.19.1", + "typescript": "^7.0.2" } - } +} diff --git a/lessons/07-mcp/code/src/client.ts b/lessons/07-mcp/code/src/client.ts index 9c8a5bda..491a9c76 100644 --- a/lessons/07-mcp/code/src/client.ts +++ b/lessons/07-mcp/code/src/client.ts @@ -1,184 +1,67 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { CallToolRequest, CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; -import { OpenAI } from 'openai'; +import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js"; +import { fileURLToPath } from "node:url"; +import { OpenAI } from "openai"; import { z } from "zod"; -type ChatCompletionMessageParam = { - role: "system" | "user" | "assistant"; - content: string; - name?: string; - function_call?: { - name: string; - arguments: string; - }; -}; - -const transport = new StdioClientTransport({ - command: "node", - args: ["build/index.js"] -}); - -const client = new Client( - { - name: "example-client", - version: "1.0.0" - } -); - -await client.connect(transport); - -function toToolSchema(method, schema) { - return { - name: method, - description: `This is a tool that does ${method}`, - parameters: schema, - }; -} - -// list tools -const { tools } = await client.listTools(); -tools.forEach((tool) => { - console.log(`Tool: ${tool.name}`); - console.log(`Description: ${tool.description}`); - console.log(`Input schema: ${JSON.stringify(tool.inputSchema)}`); +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); } -); - -const toolsForLLM = tools.map((tool) => { - return toToolSchema(tool.name, tool.inputSchema); -}); -console.log("Schema for tool", JSON.stringify(toolsForLLM, null, 2)); - -// TODO feed this to an llm as functions - -const openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", // might need to change to this url in the future: https://models.github.ai/inference - apiKey: process.env.GITHUB_TOKEN, -}); - -const messages: ChatCompletionMessageParam[] = [ - { - role: "system", - content: `You are a helpful assistant. You can call functions to perform tasks. Make sure to parse the function call and arguments correctly.` - }, { - role: "user", - content: "Add 5 and 10", - name: "example-user" // Adding the required 'name' property for user role - } -]; - -openai.chat.completions.create({ - model: 'gpt-4o', - messages: messages, - functions: toolsForLLM -}).then((result) => { - console.log("Result", result.choices[0].message); -}).catch((error) => { - console.error("Error:", error); -}) - -const result = await openai.chat.completions.create({ - model: 'gpt-4o', - messages: messages, - functions: toolsForLLM +const openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); +const client = new Client({ name: "example-client", version: "1.0.0" }); +const transport = new StdioClientTransport({ + command: process.execPath, + args: [fileURLToPath(new URL("./index.js", import.meta.url))] }); -for (const choice of result.choices) { - // console.log("Result", choice.message); - - let functionCall = choice.message?.function_call; - let functionName = functionCall?.name; - console.log("Function call: ", functionName); - let args = functionCall?.arguments; - - - // ALT1 - // 1. create a request - const request: CallToolRequest = { - method: "tools/call", - params: { - name: functionName ?? "", - arguments: args - } - }; - - function createRequest(name: string, args: any): CallToolRequest { - return { - method: "tools/call", - params: { - name, - arguments: args - } - }; - } - - function callTool>(name: string, args: any, schema: T): Promise> { - let request = createRequest(name ?? "", args); - - return client.request( - request, - schema - ); +try { + await client.connect(transport); + const { tools: serverTools } = await client.listTools(); + const allowedNames = new Set(serverTools.map(tool => tool.name)); + const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = serverTools.map(tool => ({ + type: "function", + function: { + name: tool.name, + description: tool.description, + parameters: tool.inputSchema } - - callTool("add", args, CallToolResultSchema).then((result) => { - console.log("Result from tool: ", result.content[0].text); - return result; // Ensure the callback returns the result - }); - - // 2. make the request and cast the result to the correct type - client.request(request, CallToolResultSchema).then((result) => { - console.log("Result from tool: ", result.content[0].text); - }); - - // ALT2 - await client.callTool({ - name: functionName ?? "", - arguments: typeof args === "string" ? JSON.parse(args) : args ?? {} - }).then((result) => { - const toolResult = result as z.infer; - - console.log("Converted Result from tool: ", toolResult.content[0].text); - }) - - - - - - const AddRequest = { - name: "add", - arguments: { - a: 1, - b: 2 + })); + console.log("Available tools:", [...allowedNames]); + const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [ + { role: "user", content: "Use the add tool to add 5 and 10." } + ]; + const completion = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "required" + }); + const message = completion.choices[0]?.message; + if (!message?.tool_calls?.length) throw new Error("The model did not request a tool."); + messages.push(message); + for (const call of message.tool_calls) { + if (call.type !== "function" || !allowedNames.has(call.function.name)) { + throw new Error("The model requested an unsupported MCP tool."); } - }; - - // Alt 3, request schema AND reply schema - await client.callTool(AddRequest, CallToolResultSchema) - .then((result) => { - console.log("Result from tool: ", result.content[0].text); + const args = z.record(z.string(), z.unknown()).parse(JSON.parse(call.function.arguments)); + const result = CallToolResultSchema.parse(await client.callTool({ + name: call.function.name, + arguments: args + })); + const text = result.content.filter(item => item.type === "text").map(item => item.text).join("\n"); + if (result.isError) throw new Error(`MCP tool failed: ${text}`); + if (!text) throw new Error("The MCP tool did not return text."); + console.log("Result from tool:", text); + messages.push({ role: "tool", tool_call_id: call.id, content: text }); + } + const followUp = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "none" }); - - - // await client.callTool({ - // name: functionName?? "", - // arguments: typeof args === "string" ? JSON.parse(args) : args ?? {} - // }, CallToolResultSchema ).then((result) => { - // console.log("Result from tool: ", result); - // // TODO: figure out how to cast result to the correct type, i.e { content: [ { type: 'string', text: 'string' } ] } - // // Result from tool: { content: [ { type: 'text', text: '15' } ] }; - // }); + const answer = followUp.choices[0]?.message?.content; + if (!answer?.trim()) throw new Error("The model did not return a final answer."); + console.log(answer); +} finally { + await client.close(); } - - -// call tool -// const result = await client.callTool({ -// name: "add", -// arguments: { -// a: 5, -// b: 10 -// } -// }); -// console.log(`Result: ${JSON.stringify(result)}`); diff --git a/lessons/07-mcp/code/src/index-with-llm.ts b/lessons/07-mcp/code/src/index-with-llm.ts deleted file mode 100644 index 6ac111eb..00000000 --- a/lessons/07-mcp/code/src/index-with-llm.ts +++ /dev/null @@ -1 +0,0 @@ -git s \ No newline at end of file diff --git a/lessons/07-mcp/code/src/index.ts b/lessons/07-mcp/code/src/index.ts index 6e0a6381..3239cb61 100644 --- a/lessons/07-mcp/code/src/index.ts +++ b/lessons/07-mcp/code/src/index.ts @@ -1,54 +1,24 @@ -// simple mc server - import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; -// Create an MCP server -const server = new McpServer({ - name: "Demo", - version: "1.0.0" -}); - -// Add an addition tool -// @ts-ignore -server.tool( - "add", - { - input: z.object({ - a: z.number(), - b: z.number() - }), - output: z.object({ - content: z.array(z.object({ type: z.string(), text: z.string() })) - }) - }, - async ({ input }) => ({ - content: [{ type: "text", text: String(input.a + input.b) }] - }) -); +const server = new McpServer({ name: "Demo", version: "1.0.0" }); +server.registerTool("add", { + description: "Add two numbers", + inputSchema: { a: z.number(), b: z.number() } +}, async ({ a, b }) => ({ + content: [{ type: "text", text: String(a + b) }] +})); -// Add a dynamic greeting resource -server.resource( - "greeting", +server.registerResource("greeting", new ResourceTemplate("greeting://{name}", { list: undefined }), + { description: "Greet a named person", mimeType: "text/plain" }, async (uri, { name }) => ({ - contents: [{ - uri: uri.href, - text: `Hello, ${name}!` - }] + contents: [{ uri: uri.href, text: `Hello, ${name}!` }] }) ); -// Start receiving messages on stdin and sending messages on stdout -const transport = new StdioServerTransport(); - -async function runServer() { - await server.connect(transport); -} - -runServer().catch((err) => { - console.error("Error in server:", err); - process.exit(1); +server.connect(new StdioServerTransport()).catch(error => { + console.error("Error in server:", error instanceof Error ? error.message : String(error)); + process.exitCode = 1; }); - diff --git a/lessons/07-mcp/code/tsconfig.json b/lessons/07-mcp/code/tsconfig.json index ce7b85bc..a2c65a93 100644 --- a/lessons/07-mcp/code/tsconfig.json +++ b/lessons/07-mcp/code/tsconfig.json @@ -1,15 +1,15 @@ { "compilerOptions": { "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", + "module": "NodeNext", + "moduleResolution": "NodeNext", "outDir": "./build", "rootDir": "./src", "strict": true, + "types": ["node"], "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "noImplicitAny": false + "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] diff --git a/lessons/07-mcp/solution/build/client.js b/lessons/07-mcp/solution/build/client.js index 9dc0d422..8c57552a 100644 --- a/lessons/07-mcp/solution/build/client.js +++ b/lessons/07-mcp/solution/build/client.js @@ -1,24 +1,19 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { fileURLToPath } from "node:url"; +const client = new Client({ name: "example-client", version: "1.0.0" }); const transport = new StdioClientTransport({ - command: "node", - args: ["build/index.js"] + command: process.execPath, + args: [fileURLToPath(new URL("./index.js", import.meta.url))] }); -const client = new Client({ - name: "example-client", - version: "1.0.0" -}); -await client.connect(transport); -// List resources -const resources = await client.listResources(); -console.log("Available resources:", resources); -// read a resource -let resource = await client.readResource({ - uri: "scrolls://tactics.txt", -}); -console.log("Resource contents:", resource.contents); -// Read a resource -resource = await client.readResource({ - uri: "echo://hello", -}); -console.log("Resource contents:", resource.contents); +try { + await client.connect(transport); + console.log("Available resources:", await client.listResources()); + const scroll = await client.readResource({ uri: "scrolls://tactics.txt" }); + console.log("Resource contents:", scroll.contents); + const echo = await client.readResource({ uri: "echo://hello" }); + console.log("Resource contents:", echo.contents); +} +finally { + await client.close(); +} diff --git a/lessons/07-mcp/solution/build/index.js b/lessons/07-mcp/solution/build/index.js index abadf4f4..53481539 100644 --- a/lessons/07-mcp/solution/build/index.js +++ b/lessons/07-mcp/solution/build/index.js @@ -1,82 +1,41 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; -import { promises as fs } from "fs"; -import path from "path"; -// Create an MCP server -const server = new McpServer({ - name: "Demo", - version: "1.0.0" +import { readdir, readFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +const server = new McpServer({ name: "Demo", version: "1.0.0" }); +const inputSchema = { a: z.number(), b: z.number() }; +server.registerTool("add", { description: "Add two numbers", inputSchema }, async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }] })); +server.registerTool("subtract", { description: "Subtract two numbers", inputSchema }, async ({ a, b }) => ({ content: [{ type: "text", text: String(a - b) }] })); +server.registerTool("multiply", { description: "Multiply two numbers", inputSchema }, async ({ a, b }) => ({ content: [{ type: "text", text: String(a * b) }] })); +server.registerTool("divide", { description: "Divide two numbers", inputSchema }, async ({ a, b }) => { + if (b === 0) { + return { isError: true, content: [{ type: "text", text: "Cannot divide by zero." }] }; + } + return { content: [{ type: "text", text: String(a / b) }] }; }); -// Add an addition tool -server.tool("add", { a: z.number(), b: z.number() }, async ({ a, b }) => ({ - content: [{ type: "text", text: String(a + b) }] -})); -server.tool("subtract", { a: z.number(), b: z.number() }, async ({ a, b }) => ({ - content: [{ type: "text", text: String(a - b) }] -})); -server.tool("divide", { a: z.number(), b: z.number() }, async ({ a, b }) => ({ - content: [{ type: "text", text: String(a / b) }] +server.registerResource("echo", new ResourceTemplate("echo://{message}", { + list: async () => ({ + resources: [{ name: "echo", description: "Echo a message", mimeType: "text/plain", uri: "echo://hello" }] + }) +}), { description: "Echo a message", mimeType: "text/plain" }, async (uri, { message }) => ({ + contents: [{ uri: uri.href, text: `Resource echo: ${message}` }] })); -server.tool("multiply", { a: z.number(), b: z.number() }, async ({ a, b }) => ({ - content: [{ type: "text", text: String(a * b) }] -})); -server.resource("echo", new ResourceTemplate("echo://{message}", { list: () => { - return { - resources: [ - { - "name": "echo", - "description": "Echo a message", - "mimeType": "text/plain", - "uri": "echo://{message}" - } - ] - }; - } }), async (uri, { message }) => ({ - contents: [{ - uri: uri.href, - text: `Resource echo: ${message}` - }] -})); -// Add a dynamic greeting resource -server.resource("scrolls", new ResourceTemplate("scrolls://{name}", { list: () => { - return { - resources: [ - { - "name": "scrolls", - "description": "Scrolls resource", - "mimeType": "text/plain", - "uri": "scrolls://{name}" - } - ] - }; - } }), async (uri, { name }) => { - // open file to check if it exist - let fileContent; - let filePath = path.join(process.cwd(), "scrolls", `${name}`); - try { - console.log("File path: ", filePath); - fileContent = await fs.readFile(filePath, "utf-8"); - } - catch (err) { - fileContent = `File ${name} not found in ${filePath}`; - console.error(fileContent); +const scrollsDirectory = new URL("../scrolls/", import.meta.url); +server.registerResource("scrolls", new ResourceTemplate("scrolls://{name}", { + list: async () => ({ + resources: (await readdir(scrollsDirectory)).filter(name => name.endsWith(".txt")).map(name => ({ + name, description: "A course scroll", mimeType: "text/plain", uri: `scrolls://${encodeURIComponent(name)}` + })) + }) +}), { description: "Read a course scroll", mimeType: "text/plain" }, async (uri, { name }) => { + if (typeof name !== "string" || !/^[a-zA-Z0-9_-]+\.txt$/.test(name)) { + throw new Error("Select a named .txt scroll from the resource list."); } - // response with file content - return { - contents: [{ - uri: uri.href, - text: fileContent - }] - }; + const text = await readFile(fileURLToPath(new URL(name, scrollsDirectory)), "utf8"); + return { contents: [{ uri: uri.href, text }] }; }); -// Start receiving messages on stdin and sending messages on stdout -async function main() { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("MCPServer started on stdin/stdout"); -} -main().catch((error) => { - console.error("Fatal error: ", error); - process.exit(1); +server.connect(new StdioServerTransport()).catch(error => { + console.error("Error in server:", error instanceof Error ? error.message : String(error)); + process.exitCode = 1; }); diff --git a/lessons/07-mcp/solution/package-lock.json b/lessons/07-mcp/solution/package-lock.json index 01a32424..933e8210 100644 --- a/lessons/07-mcp/solution/package-lock.json +++ b/lessons/07-mcp/solution/package-lock.json @@ -5,81 +5,1273 @@ "packages": { "": { "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", - "express": "^5.0.1", - "type": "^2.7.3", - "zod": "^3.24.2" + "@modelcontextprotocol/sdk": "^1.32.1", + "zod": "^4.6.5" }, "bin": { "weather": "build/index.js" }, "devDependencies": { - "@types/node": "^22.13.17", - "typescript": "^5.8.2" + "@modelcontextprotocol/inspector": "^2.9.0", + "@types/node": "^24.19.1", + "typescript": "^7.0.2" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", + "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.3.tgz", + "integrity": "sha512-TA//nWMqPhbfdfneACk6t5a9eqbS9lABEPyKn0/xZTah3H3U2XaVg85rJFl0/Fyit0I552YDHgXGVSf3GwqbUw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/client": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.2.0.tgz", + "integrity": "sha512-LxCou/CSYQ6dwEnjhLZY0KnEuc8V4UJ3IEQCl/uR2yHobSQIEdJKUlKLRYRF5i5FqRGHy1eK7une3aAWDHLtig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.2.0", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "jose": "^6.1.3", + "pkce-challenge": "^5.0.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.2.0.tgz", + "integrity": "sha512-iLhmprRmWI8EcosOA3wVvww22z02NkgqhV4fBH6f/odQBsi7xnJc0HGmi21yWO+/iKw2yzqVE127Zhna5/+JXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/ext-apps": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-2.0.3.tgz", + "integrity": "sha512-Tn+4+cyhO4f4cQSDxNyLiIfEE+qlTTuIbWBJB8JTinEXOhDYgCT4wDTQplxD9MBYZH5pCYGC59GaxiwIT4bu8w==", + "dev": true, + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "dependencies": { + "@standard-schema/spec": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/client": "^2.0.0", + "@modelcontextprotocol/core": "^2.0.0", + "@modelcontextprotocol/server": "^2.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "zod": "^4.2.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/server": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/inspector": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/inspector/-/inspector-2.9.0.tgz", + "integrity": "sha512-77VWBmsA18jdxQ1vqbu8SNRSC2h5uBHyUq6wTmlqEDeVIST7YEVta0OyFCDf/6VSQKbZ3ia7Qb+9bZtlTcB+tw==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@hono/node-server": "^2.0.12", + "@modelcontextprotocol/client": "2.2.0", + "@modelcontextprotocol/core": "2.2.0", + "@modelcontextprotocol/ext-apps": "^2.0.0", + "@modelcontextprotocol/server": "2.2.0", + "@napi-rs/keyring": "^1.3.0", + "@vitejs/plugin-react": "^6.0.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "chokidar": "^4.0.3", + "commander": "^13.1.0", + "hono": "^4.13.7", + "ink": "^6.0.0", + "open": "^10.2.0", + "pino": "^9.14.0", + "proper-lockfile": "^4.1.2", + "react": "^19.0.0", + "undici": "^8.11.2", + "vite": "^8.1.5", + "yaml": "^2.9.0", + "zod": "^4.4.3" + }, + "bin": { + "mcp-inspector": "clients/launcher/build/index.js" + }, + "engines": { + "node": ">=22.19.0" } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.10.2.tgz", - "integrity": "sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==", + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.1.tgz", + "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.2.0.tgz", + "integrity": "sha512-qFnltjus6Gk8Lx6J1w2RXmr3YPN22MNT6qD6zjAkaiWIlFIFxWClz0rXHNuJF/KSUyn5YGhkSvXkWDbQHVLFEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.2.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@napi-rs/keyring": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring/-/keyring-1.3.0.tgz", + "integrity": "sha512-WrOw/bcXm0f9qHkumlT1QlArXSTWqaY9sunsDpOk+yCCorCKMxvWT/a3xko4EYHVdeZoh00yI2TydXn6eyICDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/keyring-darwin-arm64": "1.3.0", + "@napi-rs/keyring-darwin-x64": "1.3.0", + "@napi-rs/keyring-freebsd-x64": "1.3.0", + "@napi-rs/keyring-linux-arm-gnueabihf": "1.3.0", + "@napi-rs/keyring-linux-arm64-gnu": "1.3.0", + "@napi-rs/keyring-linux-arm64-musl": "1.3.0", + "@napi-rs/keyring-linux-riscv64-gnu": "1.3.0", + "@napi-rs/keyring-linux-x64-gnu": "1.3.0", + "@napi-rs/keyring-linux-x64-musl": "1.3.0", + "@napi-rs/keyring-win32-arm64-msvc": "1.3.0", + "@napi-rs/keyring-win32-ia32-msvc": "1.3.0", + "@napi-rs/keyring-win32-x64-msvc": "1.3.0" + } + }, + "node_modules/@napi-rs/keyring-darwin-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-darwin-arm64/-/keyring-darwin-arm64-1.3.0.tgz", + "integrity": "sha512-pl76hJvdYUBn6I24bXiOBMA9nbDapo3I5B+f3OorjDU4dUMSypXeKbOVehJe8fhgTiH24flMyTS3aAIy43xegQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-darwin-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-darwin-x64/-/keyring-darwin-x64-1.3.0.tgz", + "integrity": "sha512-YcJtEV5LA3cvA4z3BurgxH5IhTsW1JfIvcAAcqcecwk06Si9F9NqkxbZVIfDwQ8oRHgaBmT3zZJnLAotCrVahw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-freebsd-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-freebsd-x64/-/keyring-freebsd-x64-1.3.0.tgz", + "integrity": "sha512-vlLf31TGhfRAaxLDBhg8b89ss0HHD/lyNmL5F3UjSaz5CUXElsJmKYq9fqA/B+cZKUEUcLHHGhF0I/CqcFdaVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm-gnueabihf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm-gnueabihf/-/keyring-linux-arm-gnueabihf-1.3.0.tgz", + "integrity": "sha512-KiWdMMu/Inz/bHHIAGrnF7r54FZDYXuHO6UFF/rhIrshUsxbMG1Rl9lEymNtqqsVo927G0VYcb02FzWQ3iBQRQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm64-gnu/-/keyring-linux-arm64-gnu-1.3.0.tgz", + "integrity": "sha512-eyKGpY40lm9Jvs1aD294XRH4y7+TlJM0YVAryZeXA6TX0mb4gMkxVXwSQv7MCwgah7raeUd0dKUb4BPAYIgcMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm64-musl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm64-musl/-/keyring-linux-arm64-musl-1.3.0.tgz", + "integrity": "sha512-iIK6JWHXAJqDrEyLY3TmswwloVyt2vj+04TZnew+uSJ9gnDO8EwRbp3/iw3LpWaXiDO7VomGO6y8I0Id8uBZSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-riscv64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-riscv64-gnu/-/keyring-linux-riscv64-gnu-1.3.0.tgz", + "integrity": "sha512-/PGqrwn6EwgtK6vccASSXJRfOSP4vN1F4ASsIQ+7MdrK6hNvAJ1FZPrIuD5gGGdxezo3F++To2Wq7DbuGIeuNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-x64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-x64-gnu/-/keyring-linux-x64-gnu-1.3.0.tgz", + "integrity": "sha512-2PDK1WKWTu9lBGq9VvNEkSlQD3O7YwVpmnyN2M3cy4v7NJ/8gDMd9GXv3G+FVXN13uhp4gnnPBS+ScefmEeD2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-x64-musl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-x64-musl/-/keyring-linux-x64-musl-1.3.0.tgz", + "integrity": "sha512-oJ2HkX8YUo46QBkn0pG+HuIKQNqr523q6vBobCn+P95s4C4K6/kLBqHY/1bg5J4ap31DzsznhnFKcfBNBsjCnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-arm64-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-arm64-msvc/-/keyring-win32-arm64-msvc-1.3.0.tgz", + "integrity": "sha512-tOd3c/uAaeoE4ycVlmAdSvygz0Zt3zdca6Y7gokBeIbaRDWpjDIUOpU3MvML59XAaqyuKGsVVu0F/DZb1lHPmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-ia32-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-ia32-msvc/-/keyring-win32-ia32-msvc-1.3.0.tgz", + "integrity": "sha512-sPSqeAFZMGqP1R++M2JTza7GQJJ/TpCo6JU6Vcd4jnebvOaEDs9b7eipakU1PJdSvhpC2yXMCNRk9gXfrhuwHQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-x64-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-x64-msvc/-/keyring-win32-x64-msvc-1.3.0.tgz", + "integrity": "sha512-4DnCWXwDc0HRKwyRlG5y0VhKZW2tNRQfKKfyj6IX/KWfDNyq9hn4n+GL1auyDcOO/v8PwnhmYo2+rOOqCkvvOg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", + "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", + "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", + "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", + "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", + "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", + "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", + "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", + "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", + "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", + "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", + "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", + "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", + "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", + "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", + "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", + "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz", + "integrity": "sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.2.tgz", + "integrity": "sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.152.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.3", - "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" }, "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@types/node": { - "version": "22.15.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", - "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", "dev": true, "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=8.0.0" + } + }, + "node_modules/atomically": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-fs": "^2.0.0", + "when-exit": "^2.1.4" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" }, "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bytes": { @@ -120,16 +1312,115 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -141,6 +1432,16 @@ "node": ">= 0.6" } }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -187,9 +1488,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -203,6 +1504,49 @@ } } }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -212,6 +1556,16 @@ "node": ">= 0.8" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -232,6 +1586,13 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -241,6 +1602,19 @@ "node": ">= 0.8" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -260,9 +1634,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -271,12 +1645,35 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "dev": true, + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -308,18 +1705,19 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -349,25 +1747,69 @@ "url": "https://opencollective.com/express" } }, - "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "node_modules/express-rate-limit": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" + "node": ">=12.0.0" }, "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -378,7 +1820,11 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/forwarded": { @@ -399,6 +1845,21 @@ "node": ">= 0.8" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -408,6 +1869,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -457,6 +1931,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -470,9 +1951,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -481,32 +1962,62 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.13.13", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.13.tgz", + "integrity": "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/inherits": { @@ -515,6 +2026,65 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ink": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz", + "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.4", + "ansi-escapes": "^7.3.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^5.1.1", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "scheduler": "^0.27.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^8.0.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.1", + "terminal-size": "^4.0.1", + "type-fest": "^5.4.1", + "widest-line": "^6.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": ">=6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ip-address": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz", + "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -524,18 +2094,383 @@ "node": ">= 0.10" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "dev": true, + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -546,12 +2481,16 @@ } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/merge-descriptors": { @@ -576,15 +2515,29 @@ } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, "node_modules/ms": { @@ -593,13 +2546,52 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/object-assign": { @@ -623,6 +2615,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -644,6 +2646,41 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -653,6 +2690,16 @@ "node": ">= 0.8" } }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -663,14 +2710,75 @@ } }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" } }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "dev": true, + "license": "MIT" + }, "node_modules/pkce-challenge": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", @@ -680,10 +2788,68 @@ "node": ">=16.20.0" } }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process-warning": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz", + "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -691,15 +2857,20 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -708,28 +2879,159 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true, + "license": "MIT" + }, "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", "engines": { "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rolldown": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.152.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.12", + "@rolldown/binding-android-arm64": "1.2.12", + "@rolldown/binding-darwin-arm64": "1.2.12", + "@rolldown/binding-darwin-x64": "1.2.12", + "@rolldown/binding-freebsd-x64": "1.2.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", + "@rolldown/binding-linux-arm64-gnu": "1.2.12", + "@rolldown/binding-linux-arm64-musl": "1.2.12", + "@rolldown/binding-linux-ppc64-gnu": "1.2.12", + "@rolldown/binding-linux-s390x-gnu": "1.2.12", + "@rolldown/binding-linux-x64-gnu": "1.2.12", + "@rolldown/binding-linux-x64-musl": "1.2.12", + "@rolldown/binding-openharmony-arm64": "1.2.12", + "@rolldown/binding-win32-arm64-msvc": "1.2.12", + "@rolldown/binding-win32-x64-msvc": "1.2.12" } }, "node_modules/router": { @@ -748,25 +3050,28 @@ "node": ">= 18" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -774,32 +3079,43 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -809,6 +3125,10 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -839,14 +3159,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -858,13 +3178,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -910,15 +3230,185 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stubborn-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", + "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-utils": "^1.0.1" + } + }, + "node_modules/stubborn-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", + "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-size": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", + "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thread-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -928,44 +3418,102 @@ "node": ">=0.6" } }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "license": "ISC" + "node_modules/type-fest": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.10.0.tgz", + "integrity": "sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undici": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.17" + "node": ">=22.19.0" } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "dev": true, "license": "MIT" }, @@ -987,6 +3535,91 @@ "node": ">= 0.8" } }, + "node_modules/vite": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", + "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.11", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/when-exit": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", + "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1002,28 +3635,141 @@ "node": ">= 8" } }, + "node_modules/widest-line": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", + "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "dev": true, + "license": "MIT" + }, "node_modules/zod": { - "version": "3.24.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", - "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.24.1" + "zod": "^3.25.28 || ^4" } } } diff --git a/lessons/07-mcp/solution/package.json b/lessons/07-mcp/solution/package.json index 3b462dc4..0e35272e 100644 --- a/lessons/07-mcp/solution/package.json +++ b/lessons/07-mcp/solution/package.json @@ -1,24 +1,25 @@ { "type": "module", "bin": { - "weather": "./build/index.js" + "weather": "./build/index.js" }, "scripts": { - "client": "tsc && node ./build/client.js", - "build": "tsc && node ./build/index.js", - "inspect": "npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list" + "client": "npm run build && node ./build/client.js", + "build": "tsc", + "start": "node build/index.js", + "inspect": "npm run build && mcp-inspector --cli node build/index.js --method tools/list", + "inspect:web": "npm run build && mcp-inspector --web node build/index.js" }, "files": [ - "build" + "build" ], "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", - "express": "^5.0.1", - "type": "^2.7.3", - "zod": "^3.24.2" - }, - "devDependencies": { - "@types/node": "^22.13.17", - "typescript": "^5.8.2" - } + "@modelcontextprotocol/sdk": "^1.32.1", + "zod": "^4.6.5" + }, + "devDependencies": { + "@modelcontextprotocol/inspector": "^2.9.0", + "@types/node": "^24.19.1", + "typescript": "^7.0.2" + } } diff --git a/lessons/07-mcp/solution/solution-quiz.md b/lessons/07-mcp/solution/solution-quiz.md index a8661ce7..4ab8d96c 100644 --- a/lessons/07-mcp/solution/solution-quiz.md +++ b/lessons/07-mcp/solution/solution-quiz.md @@ -19,6 +19,3 @@ * **E. All of the above** - - - diff --git a/lessons/07-mcp/solution/src/client.ts b/lessons/07-mcp/solution/src/client.ts index 77584f0c..f84d78df 100644 --- a/lessons/07-mcp/solution/src/client.ts +++ b/lessons/07-mcp/solution/src/client.ts @@ -1,36 +1,20 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { fileURLToPath } from "node:url"; +const client = new Client({ name: "example-client", version: "1.0.0" }); const transport = new StdioClientTransport({ - command: "node", - args: ["build/index.js"] + command: process.execPath, + args: [fileURLToPath(new URL("./index.js", import.meta.url))] }); -const client = new Client( - { - name: "example-client", - version: "1.0.0" - } -); - -await client.connect(transport); - - -// List resources -const resources = await client.listResources(); -console.log("Available resources:", resources); - -// read a resource -let resource = await client.readResource({ - uri: "scrolls://tactics.txt", -}); - -console.log("Resource contents:", resource.contents); - -// Read a resource -resource = await client.readResource({ - uri: "echo://hello", -}); - -console.log("Resource contents:", resource.contents); - +try { + await client.connect(transport); + console.log("Available resources:", await client.listResources()); + const scroll = await client.readResource({ uri: "scrolls://tactics.txt" }); + console.log("Resource contents:", scroll.contents); + const echo = await client.readResource({ uri: "echo://hello" }); + console.log("Resource contents:", echo.contents); +} finally { + await client.close(); +} diff --git a/lessons/07-mcp/solution/src/index.ts b/lessons/07-mcp/solution/src/index.ts index 09cf814a..471becdc 100644 --- a/lessons/07-mcp/solution/src/index.ts +++ b/lessons/07-mcp/solution/src/index.ts @@ -1,114 +1,56 @@ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; -import { promises as fs } from "fs"; -import path from "path"; - - -// Create an MCP server -const server = new McpServer({ - name: "Demo", - version: "1.0.0" +import { readdir, readFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; + +const server = new McpServer({ name: "Demo", version: "1.0.0" }); +const inputSchema = { a: z.number(), b: z.number() }; +server.registerTool("add", { description: "Add two numbers", inputSchema }, + async ({ a, b }) => ({ content: [{ type: "text", text: String(a + b) }] })); +server.registerTool("subtract", { description: "Subtract two numbers", inputSchema }, + async ({ a, b }) => ({ content: [{ type: "text", text: String(a - b) }] })); +server.registerTool("multiply", { description: "Multiply two numbers", inputSchema }, + async ({ a, b }) => ({ content: [{ type: "text", text: String(a * b) }] })); +server.registerTool("divide", { description: "Divide two numbers", inputSchema }, async ({ a, b }) => { + if (b === 0) { + return { isError: true, content: [{ type: "text", text: "Cannot divide by zero." }] }; + } + return { content: [{ type: "text", text: String(a / b) }] }; }); -// Add an addition tool -server.tool("add", - { a: z.number(), b: z.number() }, - async ({ a, b }) => ({ - content: [{ type: "text", text: String(a + b) }] - }) -); - -server.tool("subtract", - { a: z.number(), b: z.number() }, - async ({ a, b }) => ({ - content: [{ type: "text", text: String(a - b) }] - }) -); - -server.tool("divide", - { a: z.number(), b: z.number() }, - async ({ a, b }) => ({ - content: [{ type: "text", text: String(a / b) }] - }) -); - -server.tool("multiply", - { a: z.number(), b: z.number() }, - async ({ a, b }) => ({ - content: [{ type: "text", text: String(a * b) }] - }) -); - -server.resource( - "echo", - new ResourceTemplate("echo://{message}", { list: () => { - return { - resources: [ - { - "name": "echo", - "description": "Echo a message", - "mimeType": "text/plain", - "uri": "echo://{message}" - } - ] - }; - } }), +server.registerResource("echo", + new ResourceTemplate("echo://{message}", { + list: async () => ({ + resources: [{ name: "echo", description: "Echo a message", mimeType: "text/plain", uri: "echo://hello" }] + }) + }), + { description: "Echo a message", mimeType: "text/plain" }, async (uri, { message }) => ({ - contents: [{ - uri: uri.href, - text: `Resource echo: ${message}` - }] + contents: [{ uri: uri.href, text: `Resource echo: ${message}` }] }) ); -// Add a dynamic greeting resource -server.resource( - "scrolls", - new ResourceTemplate("scrolls://{name}", { list: () => { - return { - resources: [ - { - "name": "scrolls", - "description": "Scrolls resource", - "mimeType": "text/plain", - "uri": "scrolls://{name}" - } - ] - }; - }}), +const scrollsDirectory = new URL("../scrolls/", import.meta.url); +server.registerResource("scrolls", + new ResourceTemplate("scrolls://{name}", { + list: async () => ({ + resources: (await readdir(scrollsDirectory)).filter(name => name.endsWith(".txt")).map(name => ({ + name, description: "A course scroll", mimeType: "text/plain", uri: `scrolls://${encodeURIComponent(name)}` + })) + }) + }), + { description: "Read a course scroll", mimeType: "text/plain" }, async (uri, { name }) => { - // open file to check if it exist - let fileContent: string; - let filePath = path.join(process.cwd(), "scrolls", `${name}`); - try { - console.log("File path: ", filePath); - - fileContent = await fs.readFile(filePath, "utf-8"); - } catch (err) { - fileContent = `File ${name} not found in ${filePath}`; - console.error(fileContent); - } - // response with file content - - return { - contents: [{ - uri: uri.href, - text: fileContent - }] + if (typeof name !== "string" || !/^[a-zA-Z0-9_-]+\.txt$/.test(name)) { + throw new Error("Select a named .txt scroll from the resource list."); } + const text = await readFile(fileURLToPath(new URL(name, scrollsDirectory)), "utf8"); + return { contents: [{ uri: uri.href, text }] }; } ); -// Start receiving messages on stdin and sending messages on stdout - -async function main() { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("MCPServer started on stdin/stdout"); -} - -main().catch((error) => { - console.error("Fatal error: ", error); - process.exit(1); +server.connect(new StdioServerTransport()).catch(error => { + console.error("Error in server:", error instanceof Error ? error.message : String(error)); + process.exitCode = 1; }); diff --git a/lessons/07-mcp/solution/tsconfig.json b/lessons/07-mcp/solution/tsconfig.json index a14bee07..3230f00f 100644 --- a/lessons/07-mcp/solution/tsconfig.json +++ b/lessons/07-mcp/solution/tsconfig.json @@ -1,11 +1,12 @@ { "compilerOptions": { "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", + "module": "NodeNext", + "moduleResolution": "NodeNext", "outDir": "./build", "rootDir": "./src", "strict": true, + "types": ["node"], "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true diff --git a/lessons/07-mcp/translations/README.fr.md b/lessons/07-mcp/translations/README.fr.md deleted file mode 100644 index 304d4e26..00000000 --- a/lessons/07-mcp/translations/README.fr.md +++ /dev/null @@ -1,488 +0,0 @@ -# Leçon 7 : MCP, Protocole de Contexte Modèle - -Dans ce chapitre, vous apprendrez : - -* Comment utiliser le Protocole de Contexte Modèle (MCP) pour diviser vos capacités serveur en un serveur et un client. -* Créer des outils, des ressources et des invites sur le serveur. -* Consommer le serveur en utilisant à la fois l'outil d'inspection et un client écrit. - -## Configuration - -Si ce n'est pas encore fait, configurez votre environnement de développement. Voici comment procéder : [Configurez votre environnement](/docs/setup/README.md). - -## Ressources associées - -[![Regardez une courte vidéo sur MCP](https://img.youtube.com/vi/YRfOiB0Im64/0.jpg)](https://www.youtube.com/watch?v=YRfOiB0Im64) - -*Cette vidéo explique le Protocole de Contexte Modèle.* - -*🎥 Cliquez sur l'image ci-dessus pour regarder une courte vidéo sur MCP* - -## Narratif - Scipion l'Africain - -> [!NOTE] -> *Notre histoire jusqu'à présent. Vous êtes un mécanicien de Londres des années 1860. Vous travailliez sur votre automate et avez reçu une lettre de Charles Babbage qui vous a finalement conduit dans une bibliothèque où vous avez pris un appareil de voyage dans le temps. À travers vos voyages dans le temps, vous vous êtes retrouvé à Florence, où vous avez rencontré Léonard de Vinci. Vous êtes également allé dans l'empire aztèque, et c'est là que l'histoire continue.* -> -> Reportez-vous à [Leçon 1](/lessons/01-intro-to-genai/README.md) si vous souhaitez reprendre l'histoire depuis le début. - -> [!NOTE] -> Bien que nous vous recommandions de suivre l'histoire (c'est amusant !), [cliquez ici](#interact-with-scipio) si vous préférez passer directement au contenu technique. - -*Nos héros, ayant tout juste appris l'importance des outils et de l'« appel d'outils » lors de leur rencontre avec Amelia Earhart, retournent maintenant au manoir d'Ada Lovelace pour discuter de leurs nouvelles connaissances. Un éclair de lumière enveloppe nos héros alors que le scarabée temporel les transporte dans le présent d'Ada. Ils se retrouvent dans le sous-sol du manoir d'Ada Lovelace. Charles Babbage n'est pas présent, mais Ada les attend. Elle est enthousiaste à l'idée d'entendre parler de leur voyage et des connaissances qu'ils ont acquises.* - -**Ada Lovelace**: "Bienvenue ! J'espère que votre voyage a été enrichissant." - -**Vous** : "Ça l'était ! Rencontrer Mme Earhart a été toute une expérience. Elle nous a appris l'importance des outils et comment les utiliser efficacement." - -**Ada Lovelace**: "Peu de gens comprennent mieux qu'Amelia l'importance des outils. Je suppose que vous avez également mis à jour son Scarabée Temporel ?" - -**Vous** : "Oui, nous l'avons fait ! Alors, quel est le programme maintenant ?" - -**Ada Lovelace** : "L'appel d'outils est certainement un concept puissant, qui améliore votre appareil. Toutefois, il manque un certain 'raffinement', disons-nous. Nous devons apprendre *comment le faire évoluer*. Nous avons besoin d'un protocole. Je connais quelqu'un qui pourra vous aider. Il s'appelle Scipion l'Africain, un grand stratège militaire de la Rome antique. Dommage qu'il ait passé tout son temps à des stratégies militaires, je suis certaine qu'il aurait été un brillant ingénieur. Allez-y." - -**Vous** : "Scipion l'Africain ? Je crois me souvenir de lui dans mes cours d'histoire." - -**Ada Lovelace** : "Bien alors, pourquoi êtes-vous encore là ? Le temps presse. Au revoir." - -La lumière tourbillonnante vous transporte à nouveau, et vous vous retrouvez dans une vaste pièce avec une grande table au centre. Les murs sont décorés de cartes et de stratégies militaires. Scipion l'Africain est debout près de la table, profondément plongé dans ses pensées. Il lève les yeux lorsque vous entrez : "Ada te misit/Ada vous a envoyé(e) ?" - -![Scipion l'Africain](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/07-mcp/assets/scipio.png) - -> Scipion l'Africain, également connu sous le nom de Publius Cornelius Scipion Africanus, était un général et homme d'État romain éminent ayant vécu de 236 av. J.-C. à 183 av. J.-C. Il est surtout connu pour son rôle décisif dans la Deuxième Guerre Punique contre Carthage et pour avoir vaincu Hannibal lors de la bataille de Zama en 202 av. J.-C. -> Sa vie et sa carrière illustrent des qualités de leadership, de génie stratégique et de résilience. Ses contributions aux sphères militaires et politiques de Rome ont laissé une empreinte durable sur l'Empire romain. - -## Interagir avec Scipion - -Si vous souhaitez interagir avec Scipion, exécutez l'application [Characters](/app/README.md). - -> [!IMPORTANT] -> Ceci est entièrement fictif ; les réponses sont générées par une IA. -> [Clause de non-responsabilité sur l'IA responsable](/README.md#responsible-ai-disclaimer) - -![Scipion l'Africain](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/07-mcp/assets/scipio.png) - -**Étapes** : - -1. Lancez un [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript). -2. Naviguez vers */app* à la racine du dépôt. -3. Localisez la console et exécutez `npm install` suivi de `npm start`. -4. Une fois que l'application est ouverte, sélectionnez le bouton "Ouvrir dans le navigateur". -5. Discuter avec Scipion. - -Pour une explication plus détaillée de l'application, voir [Explication détaillée de l'application](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). - -> [!NOTE] -> Si vous exécutez le projet localement sur votre machine, veuillez consulter le guide de démarrage rapide pour configurer un [token d'accès personnel GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) et remplacer la clé dans le code. - -## Le besoin du Protocole de Contexte Modèle (MCP) - -**Time Beetle** : Le protocole dont Ada a parlé s'appelle le Protocole de Contexte Modèle (MCP). C'est un protocole qui permet de décentraliser l'architecture de votre application, la rendant plus évolutive et résiliente en ajoutant des ressources sur un serveur, voire plusieurs serveurs, et en déchargeant ces ressources du client. Je vais laisser Scipion vous expliquer le concept. - -**Vous** : "Oui, Ada nous a envoyés. Elle a dit que vous pourriez nous aider avec un protocole ?" - -**Scipio** : "Un protocole ? J'ai passé ma vie à développer des stratégies pour la bataille. Mais je suppose qu'un protocole est comme une stratégie. Il vous aide à organiser et gérer vos ressources efficacement et assure la communication entre elles." - -Parlons des manœuvres de contournement, de l'infiltration et de l'utilisation de la cavalerie. - -**Vous** : "D'accord, continuez ?" - -**Scipion** : "En bataille, nous utilisons souvent des manœuvres de contournement pour attaquer l'ennemi sur les côtés ou par l'arrière. Cela perturbe leurs formations et nous donne un avantage. La raison en est que l'ennemi n'est pas préparé à une attaque latérale et tend à concentrer toute son attention sur l'avant." - -**Vous** : "Hm, je suppose que c'est un peu comme si une application faisait trop de travail en amont sur un seul serveur. Les manœuvres de contournement seraient comme répartir la charge sur plusieurs serveurs, n'est-ce pas ?" - -**Scipion** : "Exactement ! En répartissant la charge, vous réduisez le risque de point de défaillance unique et améliorez la résilience globale du système. Cela facilite également la gestion et l'utilisation des ressources de manière efficace. - -## Protocole de Contexte Modèle (MCP) - -**Time Beetle** : "Pour compléter ce que dit Scipion, le Protocole de Contexte Modèle (MCP) est un moyen de décentraliser l'architecture de votre application. Il vous permet de diviser votre application en composants plus petits, plus faciles à gérer, qui peuvent fonctionner de manière indépendante. Voici quelques concepts importants à garder en tête : - -* **Hôtes MCP**: Programmes comme les EDI, par exemple GitHub Copilot dans Visual Studio Code ou Claude Desktop, qui initient une connexion au serveur ou aux serveurs. -* **Clients MCP**: Clients du protocole qui maintiennent une connexion 1:1 avec les serveurs MCP. -* **Serveurs MCP**: Programmes légers qui exposent des capacités spécifiques via MCP. -* **Sources de données locales** : Fichiers, bases de données et services sur votre ordinateur auxquels les serveurs MCP peuvent accéder en toute sécurité. -* **Services distants** : Systèmes externes accessibles via Internet (par exemple, des API) auxquels les serveurs MCP peuvent se connecter. - -et voici un aperçu rapide de l'architecture MCP : - -![Architecture de MCP](https://softchris.github.io/mcp-workshop/assets/images/diagram-791787b0cac57bc331c20e72b645d4f7.png) - -## Les éléments de base du MCP - -Pour construire un serveur, vous devez connaître les éléments de base suivants : - -* **Outils** : Les outils sont des fonctionnalités qui effectuent un calcul ou une tâche spécifique pour vous. Ils prennent une entrée, effectuent une computation et produisent une sortie. Typiquement, il peut s'agir d'un calcul, de la passation d'une commande, ou toute action similaire. - -* **Ressources** : Une ressource correspond à des données statiques. Cela inclut souvent des paramètres d'application, des schémas de base de données ou d'autres fichiers statiques sur un serveur, par exemple. - -* **Prompts** : Les prompts, ou modèles de prompts, fonctionnent comme une recette que vous pouvez réutiliser. L'idée est qu'en utilisant une telle recette, vous pourriez alors lui envoyer des paramètres et l'alimenter dans un LLM pour accomplir une tâche, comme par exemple générer une description de produit. - -### Découvrir des outils, des ressources et des invites - -**Vous** : "Et qu'en est-il de l'infiltration ?" - -**Scipion** : "L'infiltration consiste à utiliser de petites unités pour pénétrer les lignes ennemies, permettant ainsi d'en apprendre davantage sur l'ennemi et de semer la confusion." - -**Vous** : "Cela me semble similaire à utiliser cette manière de penser pour mieux comprendre nos serveurs, notamment quels outils, prompts et ressources ils possèdent." - -Pour résumer, je devrais : - -* **Utiliser des manœuvres de contournement**, ou dans mon cas diviser les fonctionnalités par fonctionnalités d'application dans plusieurs serveurs MCP différents afin que je puisse distribuer les serveurs en fonction de leur domaine d'utilisation. Ainsi, il devient plus facile d'évoluer et de gérer l'application. Ces serveurs peuvent même être mis à jour de manière indépendante les uns des autres. -* **Utiliser l'infiltration**, ou dans mon cas, comprendre ce que font ces serveurs, en termes d'outils, de prompts et de ressources. De cette manière, je peux m'assurer que j'interagis avec le bon serveur et que j'utilise les bons outils pour le travail. - -**Vous** : "Time Beetle, est-ce que je comprends bien ce concept ?" - -**Time Beetle** : "Oui, c'est exact. Les stratégies de Scipion peuvent être appliquées à l'architecture de votre application. Si vous le souhaitez, je peux vous enseigner les spécificités du Protocole de Contexte Modèle (MCP) et comment l'implémenter dans votre application." - -**Vous** : "Oui, je veux bien !" - -**Time Beetle** : "Bien, commençons par créer un serveur." - -## Créer un serveur MCP - -**Time Beetle**: "Pour créer votre premier serveur MCP, vous avez besoin de deux bibliothèques : `@modelcontextprotocol/sdk` et `zod` : - -* La bibliothèque `@modelcontextprotocol/sdk` fournit les outils nécessaires pour créer et gérer des serveurs MCP. -* `zod` est une bibliothèque de déclaration et de validation de schéma orientée TypeScript, qui vous aide à définir et valider les structures de données utilisées dans votre serveur MCP. - -```bash -npm install @modelcontextprotocol/sdk zod -``` - -Voici un exemple simple de création d'un serveur MCP à l'aide de ces bibliothèques : - -```typescript -import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { z } from "zod"; - -// Create an MCP server -const server = new McpServer({ - name: "Demo", - version: "1.0.0" -}); -``` - -Le code précédent effectue les actions suivantes : - -* Importe les classes et fonctions nécessaires depuis la bibliothèque `@modelcontextprotocol/sdk`. -* Crée une instance de serveur. La classe `McpServer` est utilisée pour créer le serveur, et prend un objet avec le nom du serveur et sa version en tant que paramètres. - -**Vous** : "Cela ne fait pas grand-chose pour l'instant, n'est-ce pas ?" - -**Time Beetle** : "Non, pas encore. Mais nous pouvons y ajouter des ressources et des outils. Un outil est quelque chose qui peut être utilisé pour accomplir une tâche spécifique, tandis qu'une ressource est quelque chose qui peut fournir des données ou une fonctionnalité à l'outil. Ajoutons-en quelques exemples ci-dessous : - -```typescript -server.tool("add", - { a: z.number(), b: z.number() }, - async ({ a, b }) => ({ - content: [{ type: "text", text: String(a + b) }] - }) - ); -``` - -Dans le code précédent, nous : - -* Avons défini un outil nommé « add » qui prend deux nombres en entrée et retourne leur somme en sortie. -* Décrit les paramètres d'entrée et leur validation en utilisant zod. La fonction `z.number()` est utilisée pour valider les données d'entrée, en garantissant que `a` et `b` sont des nombres. -* La fonction `async` est utilisée pour effectuer l'addition et retourner le résultat dans un format spécifique. - -**Vous** : "Intéressant, donc l'outil est comme une fonction qui peut être appelée par le client MCP ?" - -**Time Beetle** : "Exactement ! Le client MCP peut appeler cet outil et fournir les paramètres requis. Ajoutons également une ressource au serveur : - -```typescript -server.resource( - "greeting", - new ResourceTemplate("greeting://{name}", { list: undefined }), - async (uri, { name }) => ({ - contents: [{ - uri: uri.href, - text: `Hello, ${name}!` - }] - }) - ); -``` - -Dans ce code, nous : - -* Défini une ressource nommée "greeting" qui prend un nom en entrée et retourne un message de salutation. -* Utilise un modèle de ressource pour définir comment appeler la ressource `greeting://{name}`. Ce schéma est utilisé pour définir le format de l'URI de la ressource, qui inclut un espace réservé pour le nom. -* La fonction `async` est utilisée pour générer le message de salutation basé sur le nom fourni. - -**Vous** : "Donc, la ressource est comme une source de données, cela pourrait être une base de données, un fichier ou même une API ? Si c'était pour un fichier, j'utiliserais un URI file:// ? Comme ceci : - -```typescript -server.resource( - "file", - new ResourceTemplate("file://{path}", { list: undefined }), - async (uri, { path }) => ({ - // do something with the file at path, e.g., read its contents - contents: [{ - uri: uri.href, - text: `File at ${path}` - }] - }) - ); -``` - -**Time Beetle** : "Oui, exactement ! La ressource peut être tout ce qui fournit des données ou des fonctionnalités au serveur MCP. Vous pouvez définir des ressources pour des fichiers, des bases de données, des API ou toute autre source de données que vous souhaitez utiliser." - -Ajoutons maintenant une couche de transport au serveur afin qu'il puisse communiquer avec des clients. - -```typescript -// Start receiving messages on stdin and sending messages on stdout -const transport = new StdioServerTransport(); -await server.connect(transport); -``` - -Dans ce code, nous : - -* Créé une couche de transport en utilisant la classe `StdioServerTransport`, permettant au serveur de communiquer avec des clients via l'entrée et la sortie standards et fonctionne sur votre machine locale. Il existe également d'autres transports comme SSE et HTTP streaming, qui permettent de fonctionner à distance sur un serveur. Nous en parlerons plus tard. -* Appelé la méthode `connect` pour établir la connexion entre le serveur et la couche de transport. - -**Vous** : "Ok, donc maintenant que nous avons un serveur, comment le lancer et le tester ?" - -## Exécution et test du serveur MCP - -**Vous** : "Scipion, avez-vous testé vos tactiques ?" - -**Scipion** : "Bien sûr ! Le test est crucial dans n'importe quelle stratégie. Vous devez toujours tester vos tactiques avant de les déployer. En fait, je me souviens d'une bataille spécifique où j'ai utilisé une nouvelle tactique et elle a échoué lamentablement, appelée la Bataille de la Trébie. J'ai appris ma leçon et je n'ai jamais refait cette erreur." - -**Vous** : "Alors, comment puis-je tester mon serveur MCP ?" - -**Time Beetle** : "Vous pouvez tester votre serveur MCP en l'exécutant dans un terminal via l'inspecteur comme suit : - -```bash -npx @modelcontextprotocol/inspector node build/index.js -``` - -Ici, nous utilisons le package `@modelcontextprotocol/inspector` pour exécuter le serveur MCP. Nous fournissons l'argument `node build/index.js` pour spécifier le point d'entrée du serveur. Cela démarrera le serveur MCP et vous permettra d'interagir avec lui via l'inspecteur. - -**Vous** : "Et comment puis-je interagir avec le serveur ?" - -**Time Beetle** : "Cela démarre un serveur web sur le port 6274. Vous pouvez accéder à l'inspecteur en ouvrant votre navigateur web et en vous rendant sur `http://localhost:6274`. L'inspecteur fournit une interface conviviale pour interagir avec votre serveur MCP, vous permettant de tester les outils et les ressources que vous avez définis." - -![Interface utilisateur de l'inspecteur](https://softchris.github.io/mcp-workshop/assets/images/connect-7703c67645f368d51c7b24a5d635d6a0.png) - -### -1- Connexion au serveur - -**Time Beetle** : Sélectionnez "Connecter" et vous devriez voir la fenêtre ci-dessous : - -![Connexion au serveur](https://softchris.github.io/mcp-workshop/assets/images/connected-7c3570d5d0a8659e27740d2950031cb3.png) - -Il devrait indiquer que vous êtes connecté. Vous pouvez ensuite interagir avec les outils ou les ressources. - -### -2- Lister les outils et ressources - -**Time Beetle** : "Ensuite, nous voulons lister les outils et ressources disponibles sur le serveur. Vous pouvez le faire en sélectionnant "Lister les outils" pour voir les outils disponibles : - -![Listage des outils](https://softchris.github.io/mcp-workshop/assets/images/tools-listed-630aa49c9d7e4347108419fb83c409f6.png) - -Vous devriez maintenant voir l'outil « add » dans la liste. Vous pouvez également afficher les ressources en sélectionnant l'onglet « Ressources ». - -### -3- Exécuter l'outil - -**Time Beetle** : "Pour exécuter l'outil, sélectionnez l'outil "add" dans la liste. Renseignez les paramètres dans la boîte de dialogue qui apparaît sur le côté droit de l'écran. Par exemple, vous pouvez entrer `a=5` et `b=10` pour additionner ces deux nombres. Cliquez sur "Exécuter l'outil" pour exécuter l'outil. Vous devriez voir le résultat dans la zone de sortie ci-dessous. Le résultat devrait être `15`." - -![Exécuter l'outil](https://softchris.github.io/mcp-workshop/assets/images/ran-tool-271ecd5f84a457462e59789b579ee9d4.png) - -**Scipion** : "Intéressant, j'aurais aimé pouvoir tester mes stratégies comme cela. Cela semble très utile." - -**Vous** : "Oui, vraiment ! Je vois à quel point cela serait utile pour tester et déboguer." - -## Créer un client - -**Time Beetle** : "Vous pouvez également créer un client qui se connecte au serveur MCP et interagit avec lui. Voici un exemple de création d'un client MCP simple en utilisant la bibliothèque `@modelcontextprotocol/sdk` : - -```typescript -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; - -const transport = new StdioClientTransport({ - command: "node", - args: ["server.js"] -}); - -const client = new Client( - { - name: "example-client", - version: "1.0.0" - } -); - -await client.connect(transport); -``` - -Dans le code précédent, nous : - -* Créé une couche de transport en utilisant la classe `StdioClientTransport`, permettant au client de communiquer avec le serveur MCP via l'entrée et la sortie standards. -* Initie une nouvelle instance de `Client` avec un nom et une version. -* Connecté le client à la couche de transport en utilisant la méthode `connect`. - -**Vous** : "Laissez-moi deviner, il y a beaucoup plus à faire avec le client, n'est-ce pas ?" - -**Time Beetle** : Oui, parlons-en ensuite. Le client MCP peut être utilisé pour interagir avec le serveur, appeler des outils et accéder à des ressources. - -### Lister et appeler les outils et ressources - -**Time Beetle** : "Il y a généralement deux scénarios que vous souhaitez couvrir : lister les outils et ressources, et les appeler. Voici comment faire : - -```typescript -// List prompts -const prompts = await client.listPrompts(); - -// List resources -const resources = await client.listResources(); - -// list tools -const tools = await client.listTools(); -``` - -**Vous** : C'est super, ainsi je sais quelles capacités le serveur a. Mais comment puis-je les appeler ? - -**Time Beetle** : "D'accord, laissez-moi vous expliquer un exemple spécifique, à commencer par les outils. Vous commencez par demander quels outils sont disponibles, puis vous pouvez stocker cette réponse et ensuite appeler l'outil que vous souhaitez. Voici un exemple : - -Ainsi, lorsque vous listez les outils, vous obtenez une réponse au format suivant : - -```json - - { - "name": "", - "description": "". - "inputSchema": { - "type":"object", - "properties":{ - "a":{ - "type":"number" - }, - "b":{ - "type":"number" - }}, - "required":["a","b"], - "additionalProperties":false, - "$schema":"http://json-schema.org/draft-07/schema#" - } - } -``` - -ce qui signifie que, si vous avez un outil `add`, votre réponse, en listant les outils, ressemble à ceci : - -```json -{ - "name": "add", - "description": "Adding two numbers". - "inputSchema": { - "type":"object", - "properties":{ - "a":{ - "type":"number" - }, - "b":{ - "type":"number" - }}, - "required":["a","b"], - "additionalProperties":false, - "$schema":"http://json-schema.org/draft-07/schema#" - } -} -``` - -**Vous** : "D'accord, je peux donc stocker cela dans une variable, puis appeler l'outil que je veux ?" - -**Time Beetle** : "Exactement ! Regardons comment appeler un outil ensuite : - -```typescript -// List tools -const { tools } = await client.listTools(); - -const addTool = tools[0]; // Assuming the first tool is "add" -const subtractTool = tools[1]; // Assuming the second tool is "subtract" - -// Call a tool -const result = await client.callTool({ - name: addTool.name, - arguments: { - a: 5, - b: 10 - } -}); -``` - -**Vous** : "D'accord, cela a du sens. Et qu'en est-il des ressources ?" - -**Time Beetle** : "Les ressources sont similaires, mais vous devez fournir l'URI de la ressource et les paramètres qu'elle nécessite. Voici un exemple : - -```typescript -let resourceUri = "greeting://John"; // Replace with the actual resource URI - -// call resource -// Read a resource -const name = "John Doe"; // Replace with the actual name - -const resourceResult = await client.readResource({ - uri: `greeting:///${name}`, -}); - -console.log(resourceResult); // Hi John Doe! -``` - -**Vous** : "Je vois, intéressant, mais vous pensez à vous-même, je parie qu'on pourrait encore améliorer cela, non ? Time Beetle, nous pouvons améliorer cela, n'est-ce pas ?" - -**Time Beetle** : Oui, mais d'abord, faisons plaisir à Scipion. Nous devons discuter avec une certaine Mme Lamarr lors de notre prochaine étape. - -## Exercice - Création d'un système de gestion d'inventaire et d'outils pour Scipion - -**Scipion** : "Vous savez, j'ai tellement de parchemins et de rouleaux qui traînent. J'aimerais vraiment un outil pour m'aider à les organiser. J'ai aussi cet abaque que j'utilise pour les calculs. Pouvez-vous m’aider à créer un outil pour gérer mon inventaire ?" - -Voici un inventaire complet de mes parchemins : - -* Parchemin de tactiques. -* Parchemin d'approvisionnement alimentaire. - -Oh, voici un parchemin d'imposition. La taxation est un sujet capital, l'Empire veut sa part de tout ce que nous acquérons. Avez-vous résolu cela à votre époque ? Ne répondez pas, je ne veux pas savoir. - -Pour l'outil abaque, j'ai besoin qu'il prenne en charge les opérations suivantes : - -* Add: Add two numbers together. -* Subtract: Subtract one number from another. -* Multiply: Multiply two numbers together. -* Divide: Divide one number by another. - -## Solution - -[Solution](/lessons/07-mcp/solution/README.md) - -## Vérification des connaissances - -**Question** : Quels sont les différents types de ressources qu'un serveur MCP peut exposer ? - -A. Outils et point d'accès Web - -B. Outils et Ressources - -C. Outils, Ressources et Invites - -**Question** : Quels sont les moyens de tester un serveur MCP ? - -A. Inspector - -B. Un client MCP personnalisé - -C. Visual Studio Code - -D. curl - -E. Toutes les réponses ci-dessus - -[Solution Quiz](/lessons/07-mcp/solution/solution-quiz.md) - -## Résumé - -Dans ce chapitre, nous avons appris les points suivants : - -* Le Protocole de Contexte Modèle, MCP, est un excellent moyen de déléguer des capacités à des serveurs, plutôt que de concentrer toutes vos fonctionnalités en un seul endroit. Cela permet à vos applications de rester petites et ciblées. L'avantage supplémentaire est que différentes équipes peuvent également gérer différents serveurs. Grâce au fait que MCP est un protocole, cela signifie en outre que quiconque souhaitant partager des capacités peut le faire dans un format commun. -* De plus, nous avons vu comment consommer un serveur MCP à l'aide de l'outil d'inspection ou d'un client écrit. - -## Ressources d'auto-apprentissage - -* [Construire des serveurs MCP](https://github.com/microsoft/mcp-for-beginners/tree/main/03-GettingStarted/01-first-server) diff --git a/lessons/08-mcp-advanced/README.md b/lessons/08-mcp-advanced/README.md index 856de261..1ec7f8f8 100644 --- a/lessons/08-mcp-advanced/README.md +++ b/lessons/08-mcp-advanced/README.md @@ -92,14 +92,14 @@ If you want to interact with Hedy, run the [Characters](/app/README.md) app. 1. Start a [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript) 2. Navigate to _/app_ in the repo root. -3. Locate the console and run `npm install` followed by `npm start`. +3. Locate the console and run `npm ci` followed by `npm start`. 4. Once it appears, select the "Open in Browser" button. 5. Chat with Hedy. For a more detailed explanation of the app, see [Detailed app explanation](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). > [!NOTE] - > If you're running the project locally on your machine, please review the QuickStart guide to get a [GitHub personal access](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) token setup and replace the key in the code. +> Configure `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL` in the repository-root `.env` for both local development and Codespaces. See the [setup guide](/docs/setup/README.md#configure-environment-variables). ## Adding a Large Language Model to a Client @@ -133,76 +133,76 @@ Here's all the steps in code: ```typescript import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js"; +import { fileURLToPath } from "node:url"; +import { OpenAI } from "openai"; +import { z } from "zod"; + +const endpoint = process.env.AI_ENDPOINT?.trim(); +const apiKey = process.env.AI_API_KEY?.trim(); +const model = process.env.AI_MODEL?.trim(); +if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); +} - -// create client instance +const openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); +const client = new Client({ name: "example-client", version: "1.0.0" }); const transport = new StdioClientTransport({ - command: "node", - args: ["server.js"] + command: process.execPath, + args: [fileURLToPath(new URL("./index.js", import.meta.url))] }); -const client = new Client( - { - name: "example-client", - version: "1.0.0" +try { + await client.connect(transport); + const { tools: serverTools } = await client.listTools(); + const allowedNames = new Set(serverTools.map(tool => tool.name)); + const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = serverTools.map(tool => ({ + type: "function", + function: { + name: tool.name, + description: tool.description, + parameters: tool.inputSchema + } + })); + console.log("Available tools:", [...allowedNames]); + const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [ + { role: "user", content: "Use the add tool to add 5 and 10." } + ]; + const completion = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "required" + }); + const message = completion.choices[0]?.message; + if (!message?.tool_calls?.length) throw new Error("The model did not request a tool."); + messages.push(message); + for (const call of message.tool_calls) { + if (call.type !== "function" || !allowedNames.has(call.function.name)) { + throw new Error("The model requested an unsupported MCP tool."); + } + const args = z.record(z.string(), z.unknown()).parse(JSON.parse(call.function.arguments)); + const result = CallToolResultSchema.parse(await client.callTool({ + name: call.function.name, + arguments: args + })); + const text = result.content.filter(item => item.type === "text").map(item => item.text).join("\n"); + if (result.isError) throw new Error(`MCP tool failed: ${text}`); + if (!text) throw new Error("The MCP tool did not return text."); + console.log("Result from tool:", text); + messages.push({ role: "tool", tool_call_id: call.id, content: text }); } -); - -await client.connect(transport); - -// 1. make call to server, ask it for tools -const { tools } = await client.listTools(); - -// convert function -function toToolSchema(method, schema) { - return { - name: method, - description: `This is a tool that does ${method}`, - parameters: schema, - }; + const followUp = await openai.chat.completions.create({ + model, messages, tools, tool_choice: "none" + }); + const answer = followUp.choices[0]?.message?.content; + if (!answer?.trim()) throw new Error("The model did not return a final answer."); + console.log(answer); +} finally { + await client.close(); } - -// 2. convert the tools and resources response to a tools schema -const toolsForLLM = tools.map((tool) => { - return toToolSchema(tool.method, tool.inputSchema); -}); - -// 3. instantiate openai client -const openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY, - model: "gpt-3.5-turbo", - temperature: 0.7, -}); - -// 4. make chat completion call to openai -const response = await openai.chat.completions.create({ - messages: [ - { - role: "user", - content: `I want to add 5 and 10. Please use the tool ${toolsForLLM}`, - }, - ], - functions: toolsForLLM, - function_call: "auto", -}); - -// 5. figure out what tool to call based on the response from openai -const toolName = response.choices[0].message.function_call.name; // add -const args = response.choices[0].message.function_call.arguments; // { a: 5, b: 10 } - -// 6. call the tool on the server -const result = await client.callTool({ - name: toolName, - arguments: args, -}); - -// 7. respond to user -console.log(result); // 15 ``` In the preceding code we (focusing on our additions): -- Created a function `toToolSchema` that converts the tools and resources response to a schema that can be used by the LLM. +- Created a function `tools.map` that converts the tools and resources response to a schema that can be used by the LLM. - Asked the server for available tools and resources. - Converted the tools and resources response to a schema that can be used by the LLM. - Made a chat completion call to our AI, passing the converted tools as a parameter. @@ -235,7 +235,7 @@ In the preceding code we (focusing on our additions): ## Solution -[Solution](/lessons/08-mcp-advanced/solution/README.md) +[Solution](/lessons/08-mcp-advanced/solutions/README.md) ## Knowledge Check @@ -247,7 +247,7 @@ B. It creates a more natural conversation between the user and the server. C. It's better to have the LLM on the server. -[Solution quiz](/lessons/08-mcp-advanced/solution/solution-quiz.md) +[Solution quiz](/lessons/08-mcp-advanced/solutions/solution-quiz.md) ## Summary diff --git a/lessons/08-mcp-advanced/solutions/README.md b/lessons/08-mcp-advanced/solutions/README.md index 63b74a4d..49742760 100644 --- a/lessons/08-mcp-advanced/solutions/README.md +++ b/lessons/08-mcp-advanced/solutions/README.md @@ -1,84 +1,49 @@ +# Run the Advanced MCP Solution -# Running the solution - -In this chapter you will learn: - -- How to use the Model Context Protocol, MCP to split up your server capabilities in a server and a client -- Create tools, resources and prompts on the server. -- Consume the server using both the inspector tool and a written client. +This sample exposes `characterDetails` and `place` tools that retrieve Wikipedia summaries. The client converts MCP tool schemas to OpenAI function tools, awaits each call, sends the results back to the model, and prints its final answer. ## Setup -If you haven't already, set up your development environment. Here's how you can do it: [Setup your environment](/docs/setup/README.md). - -## Related resources - -[![Watch a short video about MCP](https://img.youtube.com/vi/YRfOiB0Im64/0.jpg)](https://www.youtube.com/watch?v=YRfOiB0Im64) +Use Node.js LTS. Configure `AI_ENDPOINT`, `AI_API_KEY`, and `AI_MODEL` in the repository-root `.env` as described in the [course setup guide](/docs/setup/README.md). -_This video explains Model Context Protocol._ +From `lessons/08-mcp-advanced/solutions`: -*🎥 Click on the image above to watch a short video about MCP* - -## Install - -```sh -npm i +```bash +npm ci +npm run build ``` -## Run the sample - -1. Build the server +`build` only compiles the TypeScript. To run the server by itself, use `npm start`. It communicates over stdin/stdout; diagnostic messages must go to stderr. - ```sh - npm run build - ``` +## Inspect the Tools - This compiles and runs the server, once you see the text "MCPServer started on stdin/stdout" you can type Ctrl-C to take it down. - -## Test the sample - -It's a good idea to check if your features are working as intended. Try below command to quickly check your server responds. - - 1. List all tools on the server like so: +```bash +npm run inspect +npm run tool +``` - ```sh - npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name characterDetails --tool-arg name=Ada - ``` +The first command lists the tools. The second calls `characterDetails` for Ada Lovelace. Both commands build the server first. Wikipedia must be reachable. - You should see response similar to: +For the web inspector: - ```json - { - "content": [ - { - "type": "text", - "text": "Character: ...response from API" - } - ] - } - ``` +```bash +npm run inspect:web +``` - Let's proceed to running the client. +Open the local address printed by the inspector and keep its ports private. -## Running the client +## Run the Model Client -1. Run the client +```bash +npm run client +``` - ```sh - npm run client - ``` +The script builds the client and loads the root `.env` when available. It prints the available tools, the Wikipedia result, and a final model response about Ada Lovelace. It closes the transport when finished. - You should see the following tool response: +For another environment file, build first and use Node's file option: - ```text - Tool result: { - content: [ - { - type: 'text', - text: "Character: Augusta Ada King, Countess of Lovelace, also known as Ada Lovelace, was an English mathematician and writer chiefly known for her work on Charles Babbage's proposed mechanical general-purpose computer, the Analytical Engine. She was the first to recognise that the machine had applications beyond pure calculation." - } - ] - } - ``` +```bash +node --env-file="$HOME/.env" build/client.js +``` - Great, it all works as intended. +The sample only dispatches tools advertised by its own MCP server. Invalid arguments, tool failures, and missing model responses produce explicit errors. diff --git a/lessons/08-mcp-advanced/solutions/build/client.js b/lessons/08-mcp-advanced/solutions/build/client.js index 8e2e091c..ca910173 100644 --- a/lessons/08-mcp-advanced/solutions/build/client.js +++ b/lessons/08-mcp-advanced/solutions/build/client.js @@ -1,100 +1,98 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; import OpenAI from "openai"; -import { z } from "zod"; // Import zod for schema validation -class MyClient { +import { z } from "zod"; +export class MyClient { openai; client; + model; constructor() { - this.openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", // might need to change to this url in the future: https://models.github.ai/inference - apiKey: process.env.GITHUB_TOKEN, - }); - this.client = new Client({ - name: "example-client", - version: "1.0.0" - }, { - capabilities: { - prompts: {}, - resources: {}, - tools: {} - } - }); + const endpoint = process.env.AI_ENDPOINT?.trim(); + const apiKey = process.env.AI_API_KEY?.trim(); + const model = process.env.AI_MODEL?.trim(); + if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); + } + this.model = model; + this.openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); + this.client = new Client({ name: "example-client", version: "1.0.0" }); } async connectToServer(transport) { - await this.client.connect(transport); - this.run(); - console.error("MCPClient started on stdin/stdout"); + try { + await this.client.connect(transport); + await this.run(); + } + finally { + await this.client.close(); + } } openAiToolAdapter(tool) { - // Create a zod schema based on the input_schema - const schema = z.object(tool.input_schema); return { - type: "function", // Explicitly set type to "function" + type: "function", function: { name: tool.name, description: tool.description, - parameters: { - type: "object", - properties: tool.input_schema.properties, - required: tool.input_schema.required, - }, - }, + parameters: tool.inputSchema + } }; } - async callTools(tool_calls, toolResults) { - for (const tool_call of tool_calls) { - const toolName = tool_call.function.name; - const args = tool_call.function.arguments; - console.log(`Calling tool ${toolName} with args ${JSON.stringify(args)}`); - // 2. Call the server's tool - const toolResult = await this.client.callTool({ - name: toolName, - arguments: JSON.parse(args), - }); - console.log("Tool result: ", toolResult); - // 3. Do something with the result - // TODO + async callTools(calls, allowedNames) { + const results = []; + for (const call of calls) { + if (call.type !== "function" || !allowedNames.has(call.function.name)) { + throw new Error("The model requested an unsupported MCP tool."); + } + const args = z.record(z.string(), z.unknown()).parse(JSON.parse(call.function.arguments)); + const result = CallToolResultSchema.parse(await this.client.callTool({ + name: call.function.name, + arguments: args + })); + const text = result.content.filter(item => item.type === "text").map(item => item.text).join("\n"); + if (result.isError) + throw new Error(`MCP tool failed: ${text}`); + if (!text) + throw new Error("The MCP tool did not return text."); + console.log(`Result from [${call.function.name}]:`, text); + results.push({ role: "tool", tool_call_id: call.id, content: text }); } + return results; } async run() { - console.log("Asking server for available tools"); - const toolsResult = await this.client.listTools(); - const tools = toolsResult.tools.map((tool) => { - return this.openAiToolAdapter({ - name: tool.name, - description: tool.description, - input_schema: tool.inputSchema, - }); - }); - const messages = [ - { + const { tools: serverTools } = await this.client.listTools(); + const allowedNames = new Set(serverTools.map(tool => tool.name)); + const tools = serverTools.map(tool => this.openAiToolAdapter(tool)); + console.log("Available tools:", [...allowedNames]); + const messages = [{ role: "user", - content: "Tell me about Ada Lovelace", - }, - ]; - console.log("Querying LLM: ", messages[0].content); - let response = await this.openai.chat.completions.create({ - model: "gpt-4o-mini", - max_tokens: 1000, - messages, - tools: tools, + content: "Use the characterDetails tool to tell me briefly about Ada Lovelace." + }]; + const completion = await this.openai.chat.completions.create({ + model: this.model, messages, tools, tool_choice: "required" }); - console.log("LLM response: ", response.choices[0].message.content); - let results = []; - // 1. Go through the LLM response,for each choice, check if it has tool calls - response.choices.map(async (choice) => { - const message = choice.message; - if (message.tool_calls) { - console.log("Making tool call"); - await this.callTools(message.tool_calls, results); - } + const message = completion.choices[0]?.message; + if (!message?.tool_calls?.length) + throw new Error("The model did not request a tool."); + messages.push(message, ...await this.callTools(message.tool_calls, allowedNames)); + const followUp = await this.openai.chat.completions.create({ + model: this.model, messages, tools, tool_choice: "none" }); + const answer = followUp.choices[0]?.message?.content; + if (!answer?.trim()) + throw new Error("The model did not return a final answer."); + console.log(answer); } } -let client = new MyClient(); -const transport = new StdioClientTransport({ - command: "node", - args: ["./build/index.js"] -}); -client.connectToServer(transport); +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const client = new MyClient(); + const transport = new StdioClientTransport({ + command: process.execPath, + args: [fileURLToPath(new URL("./index.js", import.meta.url))] + }); + client.connectToServer(transport).catch(error => { + console.error("Error in client:", error instanceof Error ? error.message : String(error)); + process.exitCode = 1; + }); +} diff --git a/lessons/08-mcp-advanced/solutions/build/index.js b/lessons/08-mcp-advanced/solutions/build/index.js index a6533462..e1f47574 100644 --- a/lessons/08-mcp-advanced/solutions/build/index.js +++ b/lessons/08-mcp-advanced/solutions/build/index.js @@ -1,31 +1,37 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; -// Create an MCP server -const server = new McpServer({ - name: "Demo", - version: "1.0.0" -}); -// Add an addition tool -server.tool("characterDetails", { name: z.string() }, async ({ name }) => { - console.log(`Received character name: ${name}`); - const url = `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(name)}`; // Changed 'title' to 'name' - const response = await fetch(url); +import { fileURLToPath } from "node:url"; +import path from "node:path"; +async function summary(name) { + const response = await fetch(`https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(name)}`, { + headers: { "User-Agent": "GenerativeAIJavaScriptCourse/1.0 (https://github.com/microsoft/generative-ai-with-javascript)" }, + signal: AbortSignal.timeout(30000) + }); if (!response.ok) - throw new Error("Failed to fetch character details"); + throw new Error(`Wikipedia request failed: HTTP ${response.status}.`); const data = await response.json(); - const responseText = data.extract; // This contains the summary text - return { - content: [{ type: "text", text: `Character: ${responseText}` }] - }; -}); -// Start receiving messages on stdin and sending messages on stdout -async function main() { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("MCPServer started on stdin/stdout"); + return z.object({ extract: z.string().min(1) }).parse(data).extract; +} +export function createServer(getSummary = summary) { + const server = new McpServer({ name: "Demo", version: "1.0.0" }); + server.registerTool("characterDetails", { + description: "Get a Wikipedia summary of a historical character", + inputSchema: { name: z.string().trim().min(1).max(200) } + }, async ({ name }) => ({ + content: [{ type: "text", text: `Character: ${await getSummary(name)}` }] + })); + server.registerTool("place", { + description: "Get a Wikipedia summary of a place", + inputSchema: { name: z.string().trim().min(1).max(200) } + }, async ({ name }) => ({ + content: [{ type: "text", text: `Place: ${await getSummary(name)}` }] + })); + return server; +} +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + createServer().connect(new StdioServerTransport()).catch(error => { + console.error("Error in server:", error instanceof Error ? error.message : String(error)); + process.exitCode = 1; + }); } -main().catch((error) => { - console.error("Fatal error: ", error); - process.exit(1); -}); diff --git a/lessons/08-mcp-advanced/solutions/package-lock.json b/lessons/08-mcp-advanced/solutions/package-lock.json index ccd64b19..cef38742 100644 --- a/lessons/08-mcp-advanced/solutions/package-lock.json +++ b/lessons/08-mcp-advanced/solutions/package-lock.json @@ -5,70 +5,1099 @@ "packages": { "": { "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", - "express": "^5.0.1", - "openai": "^4.96.2", - "type": "^2.7.3", - "zod": "^3.24.2" + "@modelcontextprotocol/sdk": "^1.32.1", + "openai": "^7.28.0", + "zod": "^4.6.5" }, "bin": { "weather": "build/index.js" }, "devDependencies": { - "@types/node": "^22.13.17", - "typescript": "^5.8.2" + "@modelcontextprotocol/inspector": "^2.9.0", + "@types/node": "^24.19.1", + "typescript": "^7.0.2" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", + "integrity": "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.3.tgz", + "integrity": "sha512-TA//nWMqPhbfdfneACk6t5a9eqbS9lABEPyKn0/xZTah3H3U2XaVg85rJFl0/Fyit0I552YDHgXGVSf3GwqbUw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/client": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.2.0.tgz", + "integrity": "sha512-LxCou/CSYQ6dwEnjhLZY0KnEuc8V4UJ3IEQCl/uR2yHobSQIEdJKUlKLRYRF5i5FqRGHy1eK7une3aAWDHLtig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.2.0", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "jose": "^6.1.3", + "pkce-challenge": "^5.0.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.2.0.tgz", + "integrity": "sha512-iLhmprRmWI8EcosOA3wVvww22z02NkgqhV4fBH6f/odQBsi7xnJc0HGmi21yWO+/iKw2yzqVE127Zhna5/+JXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@modelcontextprotocol/ext-apps": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-2.0.3.tgz", + "integrity": "sha512-Tn+4+cyhO4f4cQSDxNyLiIfEE+qlTTuIbWBJB8JTinEXOhDYgCT4wDTQplxD9MBYZH5pCYGC59GaxiwIT4bu8w==", + "dev": true, + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "dependencies": { + "@standard-schema/spec": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/client": "^2.0.0", + "@modelcontextprotocol/core": "^2.0.0", + "@modelcontextprotocol/server": "^2.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "zod": "^4.2.0" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/server": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/inspector": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/inspector/-/inspector-2.9.0.tgz", + "integrity": "sha512-77VWBmsA18jdxQ1vqbu8SNRSC2h5uBHyUq6wTmlqEDeVIST7YEVta0OyFCDf/6VSQKbZ3ia7Qb+9bZtlTcB+tw==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@hono/node-server": "^2.0.12", + "@modelcontextprotocol/client": "2.2.0", + "@modelcontextprotocol/core": "2.2.0", + "@modelcontextprotocol/ext-apps": "^2.0.0", + "@modelcontextprotocol/server": "2.2.0", + "@napi-rs/keyring": "^1.3.0", + "@vitejs/plugin-react": "^6.0.0", + "ajv": "^8.17.1", + "atomically": "^2.1.1", + "chokidar": "^4.0.3", + "commander": "^13.1.0", + "hono": "^4.13.7", + "ink": "^6.0.0", + "open": "^10.2.0", + "pino": "^9.14.0", + "proper-lockfile": "^4.1.2", + "react": "^19.0.0", + "undici": "^8.11.2", + "vite": "^8.1.5", + "yaml": "^2.9.0", + "zod": "^4.4.3" + }, + "bin": { + "mcp-inspector": "clients/launcher/build/index.js" + }, + "engines": { + "node": ">=22.19.0" } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.0.tgz", - "integrity": "sha512-k/1pb70eD638anoi0e8wUGAlbMJXyvdV4p62Ko+EZ7eBe1xMx8Uhak1R5DgfoofsK5IBBnRwsYGTaLZl+6/+RQ==", + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.1.tgz", + "integrity": "sha512-2DdE+SJDtzLEEWzY1ZjY7Q+VcPhcV1KisD3zI4u0XZyktsjHum1mwbMI+JaulUBi2OZk+KJAi2uPXzxichPkdw==", "license": "MIT", "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", - "cross-spawn": "^7.0.3", + "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@modelcontextprotocol/server": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.2.0.tgz", + "integrity": "sha512-qFnltjus6Gk8Lx6J1w2RXmr3YPN22MNT6qD6zjAkaiWIlFIFxWClz0rXHNuJF/KSUyn5YGhkSvXkWDbQHVLFEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/core": "2.2.0", + "zod": "^4.2.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@napi-rs/keyring": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring/-/keyring-1.3.0.tgz", + "integrity": "sha512-WrOw/bcXm0f9qHkumlT1QlArXSTWqaY9sunsDpOk+yCCorCKMxvWT/a3xko4EYHVdeZoh00yI2TydXn6eyICDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/keyring-darwin-arm64": "1.3.0", + "@napi-rs/keyring-darwin-x64": "1.3.0", + "@napi-rs/keyring-freebsd-x64": "1.3.0", + "@napi-rs/keyring-linux-arm-gnueabihf": "1.3.0", + "@napi-rs/keyring-linux-arm64-gnu": "1.3.0", + "@napi-rs/keyring-linux-arm64-musl": "1.3.0", + "@napi-rs/keyring-linux-riscv64-gnu": "1.3.0", + "@napi-rs/keyring-linux-x64-gnu": "1.3.0", + "@napi-rs/keyring-linux-x64-musl": "1.3.0", + "@napi-rs/keyring-win32-arm64-msvc": "1.3.0", + "@napi-rs/keyring-win32-ia32-msvc": "1.3.0", + "@napi-rs/keyring-win32-x64-msvc": "1.3.0" + } + }, + "node_modules/@napi-rs/keyring-darwin-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-darwin-arm64/-/keyring-darwin-arm64-1.3.0.tgz", + "integrity": "sha512-pl76hJvdYUBn6I24bXiOBMA9nbDapo3I5B+f3OorjDU4dUMSypXeKbOVehJe8fhgTiH24flMyTS3aAIy43xegQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-darwin-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-darwin-x64/-/keyring-darwin-x64-1.3.0.tgz", + "integrity": "sha512-YcJtEV5LA3cvA4z3BurgxH5IhTsW1JfIvcAAcqcecwk06Si9F9NqkxbZVIfDwQ8oRHgaBmT3zZJnLAotCrVahw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-freebsd-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-freebsd-x64/-/keyring-freebsd-x64-1.3.0.tgz", + "integrity": "sha512-vlLf31TGhfRAaxLDBhg8b89ss0HHD/lyNmL5F3UjSaz5CUXElsJmKYq9fqA/B+cZKUEUcLHHGhF0I/CqcFdaVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm-gnueabihf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm-gnueabihf/-/keyring-linux-arm-gnueabihf-1.3.0.tgz", + "integrity": "sha512-KiWdMMu/Inz/bHHIAGrnF7r54FZDYXuHO6UFF/rhIrshUsxbMG1Rl9lEymNtqqsVo927G0VYcb02FzWQ3iBQRQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm64-gnu/-/keyring-linux-arm64-gnu-1.3.0.tgz", + "integrity": "sha512-eyKGpY40lm9Jvs1aD294XRH4y7+TlJM0YVAryZeXA6TX0mb4gMkxVXwSQv7MCwgah7raeUd0dKUb4BPAYIgcMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-arm64-musl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-arm64-musl/-/keyring-linux-arm64-musl-1.3.0.tgz", + "integrity": "sha512-iIK6JWHXAJqDrEyLY3TmswwloVyt2vj+04TZnew+uSJ9gnDO8EwRbp3/iw3LpWaXiDO7VomGO6y8I0Id8uBZSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-riscv64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-riscv64-gnu/-/keyring-linux-riscv64-gnu-1.3.0.tgz", + "integrity": "sha512-/PGqrwn6EwgtK6vccASSXJRfOSP4vN1F4ASsIQ+7MdrK6hNvAJ1FZPrIuD5gGGdxezo3F++To2Wq7DbuGIeuNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-x64-gnu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-x64-gnu/-/keyring-linux-x64-gnu-1.3.0.tgz", + "integrity": "sha512-2PDK1WKWTu9lBGq9VvNEkSlQD3O7YwVpmnyN2M3cy4v7NJ/8gDMd9GXv3G+FVXN13uhp4gnnPBS+ScefmEeD2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-linux-x64-musl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-linux-x64-musl/-/keyring-linux-x64-musl-1.3.0.tgz", + "integrity": "sha512-oJ2HkX8YUo46QBkn0pG+HuIKQNqr523q6vBobCn+P95s4C4K6/kLBqHY/1bg5J4ap31DzsznhnFKcfBNBsjCnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-arm64-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-arm64-msvc/-/keyring-win32-arm64-msvc-1.3.0.tgz", + "integrity": "sha512-tOd3c/uAaeoE4ycVlmAdSvygz0Zt3zdca6Y7gokBeIbaRDWpjDIUOpU3MvML59XAaqyuKGsVVu0F/DZb1lHPmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-ia32-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-ia32-msvc/-/keyring-win32-ia32-msvc-1.3.0.tgz", + "integrity": "sha512-sPSqeAFZMGqP1R++M2JTza7GQJJ/TpCo6JU6Vcd4jnebvOaEDs9b7eipakU1PJdSvhpC2yXMCNRk9gXfrhuwHQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/keyring-win32-x64-msvc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@napi-rs/keyring-win32-x64-msvc/-/keyring-win32-x64-msvc-1.3.0.tgz", + "integrity": "sha512-4DnCWXwDc0HRKwyRlG5y0VhKZW2tNRQfKKfyj6IX/KWfDNyq9hn4n+GL1auyDcOO/v8PwnhmYo2+rOOqCkvvOg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.152.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.152.0.tgz", + "integrity": "sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.12.tgz", + "integrity": "sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.12.tgz", + "integrity": "sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.12.tgz", + "integrity": "sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.12.tgz", + "integrity": "sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.12.tgz", + "integrity": "sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.12.tgz", + "integrity": "sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.12.tgz", + "integrity": "sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.12.tgz", + "integrity": "sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.12.tgz", + "integrity": "sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.12.tgz", + "integrity": "sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.12.tgz", + "integrity": "sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.12.tgz", + "integrity": "sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.12.tgz", + "integrity": "sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.12.tgz", + "integrity": "sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.12.tgz", + "integrity": "sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.19.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.19.1.tgz", + "integrity": "sha512-aS3/DG0oM05K0RIXXP+hKjinGG5IgSSVGzswZxW3O0sS3pH4/fycXundUC9XsszgKCk4gHXylTEK6hyFxVxnoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=18" + "node": ">=16.20.0" } }, - "node_modules/@types/node": { - "version": "22.15.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", - "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.2.tgz", + "integrity": "sha512-fhJNGUdscQa4HkI0xFxwonWyhnVReCPDbXI7FH7jmzpnVCrFQI7PRrhWecO263610vOrs9tr2Y4xcf8uAdFxbw==", + "dev": true, "license": "MIT", "dependencies": { - "event-target-shim": "^5.0.0" + "@rolldown/pluginutils": "^1.0.1" }, "engines": { - "node": ">=6.5" + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.152.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } } }, "node_modules/accepts": { @@ -84,42 +1113,166 @@ "node": ">= 0.6" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, "license": "MIT", "dependencies": { - "humanize-ms": "^1.2.1" + "environment": "^1.0.0" }, "engines": { - "node": ">= 8.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/atomically": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", + "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-fs": "^2.0.0", + "when-exit": "^2.1.4" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bundle-name": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.1.tgz", + "integrity": "sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" }, "engines": { "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bytes": { @@ -160,28 +1313,115 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, "license": "MIT", "dependencies": { - "safe-buffer": "5.2.1" + "restore-cursor": "^4.0.0" }, "engines": { - "node": ">= 0.6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -193,6 +1433,16 @@ "node": ">= 0.6" } }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -239,9 +1489,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -255,13 +1505,47 @@ } } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/depd": { @@ -273,6 +1557,16 @@ "node": ">= 0.8" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -293,6 +1587,13 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -302,6 +1603,19 @@ "node": ">= 0.8" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -321,9 +1635,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -332,20 +1646,18 @@ "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/es-toolkit": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.52.0.tgz", + "integrity": "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA==", + "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] }, "node_modules/escape-html": { "version": "1.0.3", @@ -353,6 +1665,16 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -362,15 +1684,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/eventsource": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", @@ -393,18 +1706,19 @@ } }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -435,10 +1749,14 @@ } }, "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, "engines": { "node": ">= 16" }, @@ -446,13 +1764,53 @@ "url": "https://github.com/sponsors/express-rate-limit" }, "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -463,242 +1821,656 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.13.13", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.13.tgz", + "integrity": "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ink": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/ink/-/ink-6.8.0.tgz", + "integrity": "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.2.4", + "ansi-escapes": "^7.3.0", + "ansi-styles": "^6.2.1", + "auto-bind": "^5.0.1", + "chalk": "^5.6.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^5.1.1", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.39.10", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "react-reconciler": "^0.33.0", + "scheduler": "^0.27.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^8.0.0", + "stack-utils": "^2.0.6", + "string-width": "^8.1.1", + "terminal-size": "^4.0.1", + "type-fest": "^5.4.1", + "widest-line": "^6.0.0", + "wrap-ansi": "^9.0.0", + "ws": "^8.18.0", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@types/react": ">=19.0.0", + "react": ">=19.0.0", + "react-devtools-core": ">=6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ip-address": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz", + "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" + "get-east-asian-width": "^1.3.1" }, "engines": { - "node": ">= 6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" - }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w==", + "dev": true, "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, "engines": { - "node": ">= 0.6" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" }, "engines": { - "node": ">= 0.6" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, "license": "MIT", "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" + "is-inside-container": "^1.0.0" }, "engines": { - "node": ">= 12.20" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fresh": { + "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/panva" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "detect-libc": "^2.0.3" }, "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.10" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC" + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -710,12 +2482,16 @@ } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/merge-descriptors": { @@ -740,70 +2516,83 @@ } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=6" } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, "engines": { - "node": ">=10.5.0" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", "license": "MIT", "dependencies": { - "whatwg-url": "^5.0.0" + "content-type": "^2.1.0" }, "engines": { - "node": "4.x || >=6.0.0" + "node": ">=18" }, - "peerDependencies": { - "encoding": "^0.1.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/object-assign": { @@ -827,6 +2616,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -848,28 +2647,70 @@ "wrappy": "1" } }, - "node_modules/openai": { - "version": "4.96.2", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.96.2.tgz", - "integrity": "sha512-R2XnxvMsizkROr7BV3uNp1q/3skwPZ7fmPjO1bXLnfB4Tu5xKxrT1EVwzjhxn0MZKBKAvOaGWS63jTMN6KrIXA==", - "license": "Apache-2.0", + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" + "mimic-fn": "^2.1.0" }, - "bin": { - "openai": "bin/cli" + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openai": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.28.0.tgz", + "integrity": "sha512-HSY4fFLflQGYe2kvO0atm6/GzhD61gpyiOZZUvO4nptMUhuB1to2K3wwFpFqc5gfEK0SxyIIFiu1yE2MtygWcA==", + "license": "Apache-2.0", + "engines": { + "node": ">=22.0.0" }, "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, "ws": { "optional": true }, @@ -878,21 +2719,6 @@ } } }, - "node_modules/openai/node_modules/@types/node": { - "version": "18.19.87", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.87.tgz", - "integrity": "sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/openai/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -902,6 +2728,16 @@ "node": ">= 0.8" } }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -912,14 +2748,75 @@ } }, "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" } }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "dev": true, + "license": "MIT" + }, "node_modules/pkce-challenge": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", @@ -929,10 +2826,68 @@ "node": ">=16.20.0" } }, + "node_modules/postcss": { + "version": "8.5.29", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", + "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.19", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/process-warning": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz", + "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -940,15 +2895,20 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -957,28 +2917,159 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true, + "license": "MIT" + }, "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", "engines": { "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.33.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", + "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rolldown": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.12.tgz", + "integrity": "sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.152.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.12", + "@rolldown/binding-android-arm64": "1.2.12", + "@rolldown/binding-darwin-arm64": "1.2.12", + "@rolldown/binding-darwin-x64": "1.2.12", + "@rolldown/binding-freebsd-x64": "1.2.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.12", + "@rolldown/binding-linux-arm64-gnu": "1.2.12", + "@rolldown/binding-linux-arm64-musl": "1.2.12", + "@rolldown/binding-linux-ppc64-gnu": "1.2.12", + "@rolldown/binding-linux-s390x-gnu": "1.2.12", + "@rolldown/binding-linux-x64-gnu": "1.2.12", + "@rolldown/binding-linux-x64-musl": "1.2.12", + "@rolldown/binding-openharmony-arm64": "1.2.12", + "@rolldown/binding-win32-arm64-msvc": "1.2.12", + "@rolldown/binding-win32-x64-msvc": "1.2.12" } }, "node_modules/router": { @@ -997,25 +3088,28 @@ "node": ">= 18" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -1023,32 +3117,43 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -1058,6 +3163,10 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -1088,14 +3197,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -1107,13 +3216,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -1159,15 +3268,185 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stubborn-fs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", + "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "stubborn-utils": "^1.0.1" + } + }, + "node_modules/stubborn-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", + "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-size": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", + "integrity": "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thread-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", + "integrity": "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1177,50 +3456,103 @@ "node": ">=0.6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/type": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", - "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "license": "ISC" + "node_modules/type-fest": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.10.0.tgz", + "integrity": "sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", "dev": true, "license": "Apache-2.0", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "tsc": "bin/tsc" }, "engines": { - "node": ">=14.17" + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undici": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz", + "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -1241,30 +3573,90 @@ "node": ">= 0.8" } }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "node_modules/vite": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", + "integrity": "sha512-SQr1x6W5vVSbROg7vsyXIaxK9b0G7zsT68acdWWRmnBUsgDieLCRG+Rep9WdZgcposvv/GSnr4GUUBqB3vXq6w==", + "dev": true, "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.11", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">= 14" + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } + "node_modules/when-exit": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", + "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", + "dev": true, + "license": "MIT" }, "node_modules/which": { "version": "2.0.2", @@ -1281,28 +3673,141 @@ "node": ">= 8" } }, + "node_modules/widest-line": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", + "integrity": "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "dev": true, + "license": "MIT" + }, "node_modules/zod": { - "version": "3.24.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz", - "integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==", + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz", + "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.24.1" + "zod": "^3.25.28 || ^4" } } } diff --git a/lessons/08-mcp-advanced/solutions/package.json b/lessons/08-mcp-advanced/solutions/package.json index 802ecb3c..fff73e40 100644 --- a/lessons/08-mcp-advanced/solutions/package.json +++ b/lessons/08-mcp-advanced/solutions/package.json @@ -4,23 +4,24 @@ "weather": "./build/index.js" }, "scripts": { - "client": "tsc && node ./build/client.js", - "build": "tsc && node ./build/index.js", - "inspect": "npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list", - "tool": "npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name characterDetails --tool-arg name=Ada" + "client": "npm run build && node --env-file-if-exists=../../../.env build/client.js", + "build": "tsc", + "start": "node build/index.js", + "inspect": "npm run build && mcp-inspector --cli node build/index.js --method tools/list", + "inspect:web": "npm run build && mcp-inspector --web node build/index.js", + "tool": "npm run build && mcp-inspector --cli node build/index.js --method tools/call --tool-name characterDetails --tool-arg name=\"Ada Lovelace\"" }, "files": [ "build" ], "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", - "express": "^5.0.1", - "openai": "^4.96.2", - "type": "^2.7.3", - "zod": "^3.24.2" + "@modelcontextprotocol/sdk": "^1.32.1", + "openai": "^7.28.0", + "zod": "^4.6.5" }, "devDependencies": { - "@types/node": "^22.13.17", - "typescript": "^5.8.2" + "@modelcontextprotocol/inspector": "^2.9.0", + "@types/node": "^24.19.1", + "typescript": "^7.0.2" } } diff --git a/lessons/08-mcp-advanced/solutions/src/client.ts b/lessons/08-mcp-advanced/solutions/src/client.ts index 40ccc0b8..1d07f41e 100644 --- a/lessons/08-mcp-advanced/solutions/src/client.ts +++ b/lessons/08-mcp-advanced/solutions/src/client.ts @@ -1,134 +1,104 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; -import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { CallToolResultSchema, type Tool } from "@modelcontextprotocol/sdk/types.js"; +import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; import OpenAI from "openai"; -import { z } from "zod"; // Import zod for schema validation - -class MyClient { - private openai: OpenAI; - private client: Client; - constructor(){ - this.openai = new OpenAI({ - baseURL: "https://models.inference.ai.azure.com", // might need to change to this url in the future: https://models.github.ai/inference - apiKey: process.env.GITHUB_TOKEN, - }); - - - - this.client = new Client( - { - name: "example-client", - version: "1.0.0" - }, - { - capabilities: { - prompts: {}, - resources: {}, - tools: {} - } - } - ); +import { z } from "zod"; + +export class MyClient { + private readonly openai: OpenAI; + private readonly client: Client; + private readonly model: string; + + constructor() { + const endpoint = process.env.AI_ENDPOINT?.trim(); + const apiKey = process.env.AI_API_KEY?.trim(); + const model = process.env.AI_MODEL?.trim(); + if (!endpoint || !apiKey || !model) { + throw new Error("Set AI_ENDPOINT, AI_API_KEY, and AI_MODEL before running the sample."); } - - async connectToServer(transport: Transport) { - await this.client.connect(transport); - this.run(); - console.error("MCPClient started on stdin/stdout"); + this.model = model; + this.openai = new OpenAI({ baseURL: endpoint, apiKey, timeout: 60000 }); + this.client = new Client({ name: "example-client", version: "1.0.0" }); + } + + async connectToServer(transport: Transport): Promise { + try { + await this.client.connect(transport); + await this.run(); + } finally { + await this.client.close(); } - - openAiToolAdapter(tool: { - name: string; - description?: string; - input_schema: any; - }) { - // Create a zod schema based on the input_schema - const schema = z.object(tool.input_schema); - - return { - type: "function" as const, // Explicitly set type to "function" - function: { - name: tool.name, - description: tool.description, - parameters: { - type: "object", - properties: tool.input_schema.properties, - required: tool.input_schema.required, - }, - }, - }; + } + + private openAiToolAdapter(tool: Tool): OpenAI.Chat.Completions.ChatCompletionFunctionTool { + return { + type: "function", + function: { + name: tool.name, + description: tool.description, + parameters: tool.inputSchema + } + }; + } + + private async callTools( + calls: OpenAI.Chat.Completions.ChatCompletionMessageToolCall[], + allowedNames: Set + ): Promise { + const results: OpenAI.Chat.Completions.ChatCompletionToolMessageParam[] = []; + for (const call of calls) { + if (call.type !== "function" || !allowedNames.has(call.function.name)) { + throw new Error("The model requested an unsupported MCP tool."); + } + const args = z.record(z.string(), z.unknown()).parse(JSON.parse(call.function.arguments)); + const result = CallToolResultSchema.parse(await this.client.callTool({ + name: call.function.name, + arguments: args + })); + const text = result.content.filter(item => item.type === "text").map(item => item.text).join("\n"); + if (result.isError) throw new Error(`MCP tool failed: ${text}`); + if (!text) throw new Error("The MCP tool did not return text."); + console.log(`Result from [${call.function.name}]:`, text); + results.push({ role: "tool", tool_call_id: call.id, content: text }); } - - async callTools( - tool_calls: OpenAI.Chat.Completions.ChatCompletionMessageToolCall[], - toolResults: any[] - ) { - for (const tool_call of tool_calls) { - const toolName = tool_call.function.name; - const args = tool_call.function.arguments; - - console.log(`Calling tool ${toolName} with args ${JSON.stringify(args)}`); - - - // 2. Call the server's tool - const toolResult = await this.client.callTool({ - name: toolName, - arguments: JSON.parse(args), - }); - - console.log("Tool result: ", toolResult); - - // 3. Do something with the result - // TODO for you to implement - - } - } - - async run() { - console.log("Asking server for available tools"); - const toolsResult = await this.client.listTools(); - const tools = toolsResult.tools.map((tool) => { - return this.openAiToolAdapter({ - name: tool.name, - description: tool.description, - input_schema: tool.inputSchema, - }); - }); - - const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [ - { - role: "user", - content: "Tell me about Ada Lovelace", - }, - ]; - - console.log("Querying LLM: ", messages[0].content); - let response = await this.openai.chat.completions.create({ - model: "gpt-4o-mini", - max_tokens: 1000, - messages, - tools: tools, - }); - - console.log("LLM response: ", response.choices[0].message.content); - - let results: any[] = []; - - // 1. Go through the LLM response,for each choice, check if it has tool calls - response.choices.map(async (choice: { message: any; }) => { - const message = choice.message; - if (message.tool_calls) { - console.log("Making tool call") - await this.callTools(message.tool_calls, results); - } - }); - } - + return results; + } + + private async run(): Promise { + const { tools: serverTools } = await this.client.listTools(); + const allowedNames = new Set(serverTools.map(tool => tool.name)); + const tools = serverTools.map(tool => this.openAiToolAdapter(tool)); + console.log("Available tools:", [...allowedNames]); + const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [{ + role: "user", + content: "Use the characterDetails tool to tell me briefly about Ada Lovelace." + }]; + const completion = await this.openai.chat.completions.create({ + model: this.model, messages, tools, tool_choice: "required" + }); + const message = completion.choices[0]?.message; + if (!message?.tool_calls?.length) throw new Error("The model did not request a tool."); + messages.push(message, ...await this.callTools(message.tool_calls, allowedNames)); + const followUp = await this.openai.chat.completions.create({ + model: this.model, messages, tools, tool_choice: "none" + }); + const answer = followUp.choices[0]?.message?.content; + if (!answer?.trim()) throw new Error("The model did not return a final answer."); + console.log(answer); + } } -let client = new MyClient(); - const transport = new StdioClientTransport({ - command: "node", - args: ["./build/index.js"] - }); - -client.connectToServer(transport); +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const client = new MyClient(); + const transport = new StdioClientTransport({ + command: process.execPath, + args: [fileURLToPath(new URL("./index.js", import.meta.url))] + }); + client.connectToServer(transport).catch(error => { + console.error("Error in client:", error instanceof Error ? error.message : String(error)); + process.exitCode = 1; + }); +} diff --git a/lessons/08-mcp-advanced/solutions/src/index.ts b/lessons/08-mcp-advanced/solutions/src/index.ts index da3b7a3f..80578279 100644 --- a/lessons/08-mcp-advanced/solutions/src/index.ts +++ b/lessons/08-mcp-advanced/solutions/src/index.ts @@ -1,63 +1,40 @@ -import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; -import { promises as fs } from "fs"; -import path from "path"; - - -// Create an MCP server -const server = new McpServer({ - name: "Demo", - version: "1.0.0" -}); - -// Add a tool -server.tool("characterDetails", - { name: z.string() }, - async ({ name }) => { - - console.log(`Received character name: ${name}`); - - const url = `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(name)}`; // Changed 'title' to 'name' - const response = await fetch(url); - if (!response.ok) throw new Error("Failed to fetch character details"); - const data = await response.json(); - const responseText = data.extract; // This contains the summary text - - return { - content: [{ type: "text", text: `Character: ${responseText}` }] - }; - } -); - -// Add a tool -server.tool("place", - { name: z.string() }, - async ({ name }) => { - - console.log(`Received name of place: ${name}`); - - const url = `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(name)}`; // Changed 'title' to 'name' - const response = await fetch(url); - if (!response.ok) throw new Error("Failed to fetch place details"); - const data = await response.json(); - const responseText = data.extract; // This contains the summary text - - return { - content: [{ type: "text", text: `Place: ${responseText}` }] - }; - } -); - -// Start receiving messages on stdin and sending messages on stdout +import { fileURLToPath } from "node:url"; +import path from "node:path"; +async function summary(name: string): Promise { + const response = await fetch(`https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(name)}`, { + headers: { "User-Agent": "GenerativeAIJavaScriptCourse/1.0 (https://github.com/microsoft/generative-ai-with-javascript)" }, + signal: AbortSignal.timeout(30000) + }); + if (!response.ok) throw new Error(`Wikipedia request failed: HTTP ${response.status}.`); + const data: unknown = await response.json(); + return z.object({ extract: z.string().min(1) }).parse(data).extract; +} -async function main() { - const transport = new StdioServerTransport(); - await server.connect(transport); - console.error("MCPServer started on stdin/stdout"); +export function createServer(getSummary: (name: string) => Promise = summary): McpServer { + const server = new McpServer({ name: "Demo", version: "1.0.0" }); + server.registerTool("characterDetails", { + description: "Get a Wikipedia summary of a historical character", + inputSchema: { name: z.string().trim().min(1).max(200) } + }, async ({ name }) => ({ + content: [{ type: "text", text: `Character: ${await getSummary(name)}` }] + })); + + server.registerTool("place", { + description: "Get a Wikipedia summary of a place", + inputSchema: { name: z.string().trim().min(1).max(200) } + }, async ({ name }) => ({ + content: [{ type: "text", text: `Place: ${await getSummary(name)}` }] + })); + + return server; } -main().catch((error) => { - console.error("Fatal error: ", error); - process.exit(1); -}); +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + createServer().connect(new StdioServerTransport()).catch(error => { + console.error("Error in server:", error instanceof Error ? error.message : String(error)); + process.exitCode = 1; + }); +} diff --git a/lessons/08-mcp-advanced/solutions/tsconfig.json b/lessons/08-mcp-advanced/solutions/tsconfig.json index b01bd618..3230f00f 100644 --- a/lessons/08-mcp-advanced/solutions/tsconfig.json +++ b/lessons/08-mcp-advanced/solutions/tsconfig.json @@ -1,15 +1,15 @@ { "compilerOptions": { "target": "ES2022", - "module": "Node16", - "moduleResolution": "Node16", + "module": "NodeNext", + "moduleResolution": "NodeNext", "outDir": "./build", "rootDir": "./src", "strict": true, + "types": ["node"], "esModuleInterop": true, "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "noImplicitAny": false + "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] diff --git a/lessons/08-mcp-advanced/translations/README.fr.md b/lessons/08-mcp-advanced/translations/README.fr.md deleted file mode 100644 index 56d4f923..00000000 --- a/lessons/08-mcp-advanced/translations/README.fr.md +++ /dev/null @@ -1,261 +0,0 @@ -# Leçon 8 : Améliorer les clients MCP avec des modèles de langage à grande échelle - -Dans la leçon précédente, vous avez créé un serveur MCP et exploré comment il contribue à découpler la logique d'une application IA de ses capacités. Nous avons démontré comment ajouter des outils et des ressources en tant que capacités, et comment le serveur pouvait être accessible via un outil d'inspection ou un client personnalisé. Ce n'était que le début. Dans ce chapitre, vous allez aller plus loin en intégrant un modèle de langage à grande échelle (LLM) dans le client, ouvrant ainsi la voie à une expérience utilisateur plus puissante et intuitive. - -Dans ce chapitre, vous apprendrez à : - -* Augmenter votre client avec un LLM. -* Utiliser votre client amélioré pour convertir une réponse de serveur MCP en un outil. -* Exploiter votre client amélioré pour créer une interaction utilisateur plus naturelle. - -## Configuration - -Si ce n'est pas déjà fait, configurez votre environnement de développement. Voici comment faire : [Configurez votre environnement](/docs/setup/README.md). - -## Ressources associées - -[![Regardez une courte vidéo sur MCP](https://img.youtube.com/vi/YRfOiB0Im64/0.jpg)](https://www.youtube.com/watch?v=YRfOiB0Im64) - -*Cette vidéo explique l'utilisation des LLM avec le protocole Model Context.* - -*🎥 Cliquez sur l'image ci-dessus pour regarder une courte vidéo sur MCP* - -## Narrative: Hedwig "Hedy" Lamarr - -> [!NOTE] -> *Notre histoire jusqu'à présent. Vous êtes un mécanicien du Londres des années 1860. Vous travailliez sur votre automate et avez reçu une lettre de Charles Babbage qui vous a finalement conduit dans une bibliothèque où vous avez récupéré un dispositif de voyage dans le temps. Au cours de vos voyages dans le temps, vous vous êtes retrouvé à divers moments de l'histoire. Vous travaillez désormais en étroite collaboration avec Ada Lovelace depuis son manoir en tant que base d'opérations, et c'est là que l'histoire continue.* -> -> Reportez-vous à [Leçon 1](/lessons/01-intro-to-genai/README.md) si vous souhaitez reprendre l'histoire depuis le début. - -> [!NOTE] -> Bien que nous vous recommandions de suivre l'histoire (c'est amusant !), [cliquez ici](#interact-with-hedy-lamarr) si vous préférez passer directement au contenu technique. - -Vous êtes une nouvelle fois retourné au manoir d’Ada. Cette fois-ci, Ada vous rencontrait à la porte principale. - -**Ada Lovelace** : Eh bien, comment cela s’est-il passé ? - -**Vous** : Bien je pense, nous avons réussi à faire fonctionner cette application. "Tenez, voyez par vous-même", dites-vous en tendant l'appareil à Ada. - -**Ada Lovelace** : Elle ramasse l’appareil, l’examine attentivement et murmure pour elle-même. "Mmm hm, ah je vois, oui, oui non, cela ne va pas. Il manque une certaine finesse." - -**Vous** : Je pensais la même chose, nous devons pouvoir taper ou parler à l’appareil, n'est-ce pas ? - -**Ada Lovelace** : Exactement, je connais justement la personne qui pourra nous aider. En fait, je vais vous accompagner pour celle-ci, cela fait bien trop longtemps que nous ne nous sommes pas vus. *Coléoptère Temporel, Hollywood s'il vous plaît, 1940, résidence d'Hedy Lamarr.* - -Tout devient noir. Des couleurs tourbillonnent rapidement, et quelques instants plus tard, votre vision commence à se préciser. Vous voyez un homme assis au piano, parlant avec animation à une femme aux cheveux bruns foncés et bouclés. Tous deux gesticulent avec excitation en parlant. - -![Hedy Lamarr](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/08-mcp-advanced/assets/hedy-invention.jpeg) - -La femme se retourne vers Ada, debout à côté de vous, et s’exclame : « Ada, c'est toi, cela fait bien trop longtemps. » - -**Ada Lovelace** : Hedwig ma chère, vous travaillez sur votre dernière invention je suppose ? - -**Hedy Lamarr** : Oui, en fait George et moi pensons que nous tenons quelque chose — le "saut de fréquence". Je ne devrais probablement pas en dire plus, on ne sait jamais qui écoute. - -**Ada Lovelace** : Oh vous parlez de lui, en me désignant. Il est en pleine aventure de son côté. - -**Hedy Lamarr** : Je vois, que puis-je faire pour vous ? - -**Ada Lovelace** : En fait, nous avons besoin que cet appareil fonctionne un peu mieux. Des idées ? - -**Hedy Lamarr** : Elle ramasse l’appareil et l’examine sous différents angles. "Vous dites que vous avez déjà séparé les fonctionnalités de la partie communication ?" - -**Ada Lovelace** : Oui, oui, c'est fait. - -**Hedy Lamarr** : Eh bien dans ce cas, je rendrais simplement la partie communication un peu plus intelligente. Cela me rappelle une conversation que j’ai eue avec un cadet de la marine alors que j’essayais de vendre des bons de guerre. Minsky, je crois que c'était son nom. "À quoi ressemble l'intelligence humaine dans une machine ?" était le sujet. J’ai l’impression qu’il fera quelque chose de grandiose dans ce domaine un jour. Donc oui, donnez-lui plus d’intelligence. - -**Ada Lovelace** : Très bien, vous avez entendu Mme Lamarr, au travail. - -**Vous** : Coléoptère Temporel, comment fait-on cela ? - -**Coléoptère Temporel** : Vous pouvez améliorer le client que vous avez créé précédemment en intégrant un modèle de langage à grande échelle, ou LLM. - -> Hedy Lamarr était une figure extraordinaire, connue à la fois pour sa célébrité à Hollywood et pour ses remarquables contributions à la technologie. -> -> Cependant, au-delà de sa carrière d'actrice, Lamarr était aussi une brillante inventrice. Pendant la Seconde Guerre mondiale, elle a co-inventé avec le compositeur George Antheil un système de guidage radio pour les torpilles alliées. Ce système utilisait la technologie de spectre étalé et de saut de fréquence pour empêcher les puissances de l'Axe de brouiller les signaux. Bien qu'il n'ait pas été utilisé pendant la guerre, cette technologie est ensuite devenue la base des communications sans fil modernes, y compris le Wi-Fi, le Bluetooth et le GPS. -> -> Les contributions de Lamarr à la technologie n’ont pas été pleinement reconnues de son vivant, mais aujourd’hui, elle est célébrée comme une pionnière dans ce domaine. Son histoire est un mélange fascinant de glamour et de génie, prouvant que l’innovation peut provenir des endroits les plus inattendus.\ -> Lisez-en plus ici à propos de [Hedy Lamarr](https://en.wikipedia.org/wiki/Hedy_Lamarr) et ici au sujet de [Marvin Minsky](https://en.wikipedia.org/wiki/Marvin_Minsky) - -## Interagir avec Hedy Lamarr - -Si vous souhaitez interagir avec Hedy, exécutez l'application [Characters](/app/README.md). - -> [!IMPORTANT] -> Ceci est entièrement fictif ; les réponses sont générées par une IA. -> [Clause de non-responsabilité sur l'IA responsable](/README.md#responsible-ai-disclaimer) - -![Hedy Lamarr](https://raw.githubusercontent.com/microsoft/generative-ai-with-javascript/main/lessons/08-mcp-advanced/assets/hedylamarr.jpeg) - -**Étapes** : - -1. Lancez un [![GitHub Codespace](https://img.shields.io/badge/GitHub-Codespace-brightgreen)](https://codespaces.new/microsoft/generative-ai-with-javascript). -2. Naviguez vers */app* à la racine du dépôt. -3. Localisez la console et exécutez `npm install` suivi de `npm start`. -4. Une fois que l'application est ouverte, sélectionnez le bouton "Ouvrir dans le navigateur". -5. Discuter avec Hedy. - -Pour une explication plus détaillée de l'application, voir [Explication détaillée de l'application](/lessons/01-intro-to-genai/README.md#interact-with-dinocrates). - -> [!NOTE] -> Si vous exécutez le projet localement sur votre machine, veuillez consulter le guide de démarrage rapide pour configurer un [token d'accès personnel GitHub](/docs/setup/README.md#creating-a-personal-access-token-pat-for-github-model-access) et remplacer la clé dans le code. - -## Ajouter un modèle de langage à grande échelle à un client - -**Coléoptère Temporel** : "Comme je le disais, parlons de la façon dont vous pouvez créer un client qui utilise un modèle de langage à grande échelle (LLM) pour interagir avec le serveur MCP. Les avantages sont qu’il offre une meilleure expérience utilisateur et vous permet d’utiliser le langage naturel pour interagir avec le serveur." - -Voici comment cela fonctionnerait à un niveau élevé : - -1. Le client interagit avec le serveur MCP pour demander les outils et ressources disponibles. - -2. Au moment de l'invite, l'utilisateur rédige une invite en langage naturel, qui est ensuite envoyée au LLM dans le client. - -3. Le client détermine quel outil ou quelle ressource appeler en fonction de l'invite et des outils et ressources disponibles. - -Ça semble faisable, non ? - -**Vous** : "Oui, c’est faisable ! Mais comment faire ?" - -**Coléoptère Temporel** : "Améliorons le client que vous avez créé précédemment, décrivons les modifications de code par étapes : - -1. Effectuer un appel au serveur pour demander les outils et ressources disponibles. -2. Convertir la réponse des outils et ressources en un schéma d’outils utilisable par le LLM. -3. Instancier le client OpenAI. -4. Effectuer un appel de complétion de chat à OpenAI, en passant le schéma d'outils en paramètre. -5. Déterminer quel outil appeler en fonction de la réponse d’OpenAI. -6. Appeler l'outil sur le serveur à l'aide du client MCP. -7. Répondre à l'utilisateur avec le résultat. - -Voici toutes les étapes en code : - -```typescript -import { Client } from "@modelcontextprotocol/sdk/client/index.js"; -import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; - - -// create client instance -const transport = new StdioClientTransport({ - command: "node", - args: ["server.js"] -}); - -const client = new Client( - { - name: "example-client", - version: "1.0.0" - } -); - -await client.connect(transport); - -// 1. make call to server, ask it for tools -const { tools } = await client.listTools(); - -// convert function -function toToolSchema(method, schema) { - return { - name: method, - description: `This is a tool that does ${method}`, - parameters: schema, - }; -} - -// 2. convert the tools and resources response to a tools schema -const toolsForLLM = tools.map((tool) => { - return toToolSchema(tool.method, tool.inputSchema); -}); - -// 3. instantiate openai client -const openai = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY, - model: "gpt-3.5-turbo", - temperature: 0.7, -}); - -// 4. make chat completion call to openai -const response = await openai.chat.completions.create({ - messages: [ - { - role: "user", - content: `I want to add 5 and 10. Please use the tool ${toolsForLLM}`, - }, - ], - functions: toolsForLLM, - function_call: "auto", -}); - -// 5. figure out what tool to call based on the response from openai -const toolName = response.choices[0].message.function_call.name; // add -const args = response.choices[0].message.function_call.arguments; // { a: 5, b: 10 } - -// 6. call the tool on the server -const result = await client.callTool({ - name: toolName, - arguments: args, -}); - -// 7. respond to user -console.log(result); // 15 -``` - -Dans le code précédent, nous avons (en nous concentrant sur nos ajouts) : - -* Créé une fonction `toToolSchema` qui convertit la réponse des outils et ressources en un schéma utilisable par le LLM. -* Demandé au serveur les outils et ressources disponibles. -* Converti la réponse des outils et ressources en un schéma utilisable par le LLM. -* Effectué un appel de complétion au modèle d'IA, en passant les outils convertis en paramètre. -* Déterminé quel outil appeler en fonction de la réponse d’OpenAI. -* Appelé l'outil sur le serveur à l'aide du client MCP. -* Transmis le résultat à l'utilisateur. - -**Vous** : "C’est intéressant, c’est bien mieux ! Je peux utiliser le langage naturel sans même savoir quels outils et ressources sont disponibles. Je peux simplement demander à l'IA de le faire pour moi." - -**Coléoptère Temporel** : "Content que cela vous plaise. Cependant, il faudrait décider si vous souhaitez uniquement afficher une réponse par outil ou également une réponse générique du LLM. Voici une stratégie de réponse potentiellement bénéfique pour votre utilisateur : - -* **Outils uniquement** : Si la réponse du LLM est un outil, alors appelez l'outil et renvoyez le résultat. -* **LLM uniquement** : Si la réponse du LLM n'est pas un outil, renvoyez simplement la réponse du LLM "telle quelle". -* **Outils et LLM** : Si la réponse du LLM est un outil, alors appelez l'outil et effectuez un appel supplémentaire au LLM pour obtenir une réponse générale. Renvoyez à la fois le résultat de l'outil et la réponse du LLM. - -**Vous** : "Je vois. C’est à réfléchir. Mais c’est génial ! Je vois bien à quel point cela peut être utile." - -## Devoir - -**Ada Lovelace** : Il semble que vous avez apporté une belle amélioration. Pour rendre cela vraiment utile, j’ai besoin que vous créiez un serveur et un client avec les spécifications suivantes : - -* Le serveur doit fournir les outils suivants : - * `characterDetails` avec l'argument `name` - * `place` avec l'argument `name` -* Le client doit utiliser un LLM. - -> [!TIP] -> Par exemple, vous pouvez donner au serveur la capacité de récupérer des informations d'une API web externe, comme Wikipedia :\ -> `https://en.wikipedia.org/api/rest_v1/page/summary/${encodeURIComponent(name)}` - -## Solution - -[Solution](/lessons/08-mcp-advanced/solution/README.md) - -## Vérification des connaissances - -**Question :** Quel est un avantage d’ajouter un LLM au client ? - -A. C'est plus facile à maintenir. - -B. Cela crée une conversation plus naturelle entre l’utilisateur et le serveur. - -C. Il est préférable d'avoir le LLM sur le serveur. - -[Solution quiz](/lessons/08-mcp-advanced/solution/solution-quiz.md) - -## Résumé - -Dans ce chapitre, vous avez appris les points suivants : - -* Les clients augmentés d'un LLM offrent une meilleure expérience utilisateur. -* Les réponses d’un serveur doivent être converties dans un format que le LLM peut comprendre comme un outil. - -## Ressources d'auto-apprentissage - -* [Construire des serveurs MCP](https://github.com/microsoft/mcp-for-beginners/tree/main/03-GettingStarted/01-first-server/README.md) -* [Construire un client](https://github.com/microsoft/mcp-for-beginners/tree/main/03-GettingStarted/02-client/README.md) -* [Construire un client avec un LLM](https://github.com/microsoft/mcp-for-beginners/blob/main/03-GettingStarted/03-llm-client/README.md) diff --git a/package-lock.json b/package-lock.json index f3e62c8b..15a3854d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,17 +8,17 @@ "name": "genai-js", "version": "1.0.0", "dependencies": { - "@github/copilot-sdk": "^0.1.23", - "openai": "^4.78.1" + "@github/copilot-sdk": "1.0.16", + "openai": "^7.28.0" }, "devDependencies": { - "tsx": "^4.21.0" + "tsx": "^4.23.15" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ "ppc64" ], @@ -33,9 +33,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ "arm" ], @@ -50,9 +50,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ "arm64" ], @@ -67,9 +67,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ "x64" ], @@ -84,9 +84,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -101,9 +101,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], @@ -118,9 +118,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], @@ -135,9 +135,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], @@ -152,9 +152,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], @@ -169,9 +169,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], @@ -186,9 +186,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ "ia32" ], @@ -203,9 +203,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ "loong64" ], @@ -220,9 +220,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ "mips64el" ], @@ -237,9 +237,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], @@ -254,9 +254,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], @@ -271,9 +271,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], @@ -288,9 +288,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], @@ -305,9 +305,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], @@ -322,9 +322,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ "x64" ], @@ -339,9 +339,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ "arm64" ], @@ -356,9 +356,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ "x64" ], @@ -373,9 +373,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "cpu": [ "arm64" ], @@ -390,9 +390,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], @@ -407,9 +407,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ "arm64" ], @@ -424,9 +424,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ "ia32" ], @@ -441,9 +441,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ "x64" ], @@ -457,100 +457,133 @@ "node": ">=18" } }, - "node_modules/@github/copilot": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.403.tgz", - "integrity": "sha512-v5jUdtGJReLmE1rmff/LZf+50nzmYQYAaSRNtVNr9g0j0GkCd/noQExe31i1+PudvWU0ZJjltR0B8pUfDRdA9Q==", - "license": "SEE LICENSE IN LICENSE.md", - "bin": { - "copilot": "npm-loader.js" + "node_modules/@github/copilot-sdk": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-1.0.16.tgz", + "integrity": "sha512-m21UkMs8hkI6nRu2+EoP1kAGLJcEXFnNzAokEjwLcVmPojlO6fdLpfka1c1Nypxr39YZjyI3jHHAXwoe66SO2A==", + "license": "MIT", + "dependencies": { + "koffi": "3.2.1", + "vscode-jsonrpc": "8.2.1", + "zod": "4.3.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "0.0.403", - "@github/copilot-darwin-x64": "0.0.403", - "@github/copilot-linux-arm64": "0.0.403", - "@github/copilot-linux-x64": "0.0.403", - "@github/copilot-win32-arm64": "0.0.403", - "@github/copilot-win32-x64": "0.0.403" + "@github/copilot-sdk-darwin-arm64": "1.0.16", + "@github/copilot-sdk-darwin-x64": "1.0.16", + "@github/copilot-sdk-linux-arm64": "1.0.16", + "@github/copilot-sdk-linux-x64": "1.0.16", + "@github/copilot-sdk-linuxmusl-arm64": "1.0.16", + "@github/copilot-sdk-linuxmusl-x64": "1.0.16", + "@github/copilot-sdk-win32-arm64": "1.0.16", + "@github/copilot-sdk-win32-x64": "1.0.16" } }, - "node_modules/@github/copilot-darwin-arm64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.403.tgz", - "integrity": "sha512-dOw8IleA0d1soHnbr/6wc6vZiYWNTKMgfTe/NET1nCfMzyKDt/0F0I7PT5y+DLujJknTla/ZeEmmBUmliTW4Cg==", + "node_modules/@github/copilot-sdk-darwin-arm64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-arm64/-/copilot-sdk-darwin-arm64-1.0.16.tgz", + "integrity": "sha512-+ACscTN4OuSYDQL7/1yoi1FfDeI8qNlekip+dt2hvb9ZC4rz3YNVrhWkzrZsc7gX1ATD0YHGp71JDKqNAxHmBg==", "cpu": [ "arm64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", "optional": true, "os": [ "darwin" - ], - "bin": { - "copilot-darwin-arm64": "copilot" - } + ] }, - "node_modules/@github/copilot-darwin-x64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.403.tgz", - "integrity": "sha512-aK2jSNWgY8eiZ+TmrvGhssMCPDTKArc0ip6Ul5OaslpytKks8hyXoRbxGD0N9sKioSUSbvKUf+1AqavbDpJO+w==", + "node_modules/@github/copilot-sdk-darwin-x64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-darwin-x64/-/copilot-sdk-darwin-x64-1.0.16.tgz", + "integrity": "sha512-M+R5xi4hqSrzbf+ICloMfiotuDch7URzPwL+5I4q1gy1VlY1EqpD39HW9LbqqB68iOqMRnW3anE+kzb5PYxFjA==", "cpu": [ "x64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", "optional": true, "os": [ "darwin" - ], - "bin": { - "copilot-darwin-x64": "copilot" - } + ] }, - "node_modules/@github/copilot-linux-arm64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.403.tgz", - "integrity": "sha512-KhoR2iR70O6vCkzf0h8/K+p82qAgOvMTgAPm9bVEHvbdGFR7Py9qL5v03bMbPxsA45oNaZAkzDhfTAqWhIAZsQ==", + "node_modules/@github/copilot-sdk-linux-arm64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-arm64/-/copilot-sdk-linux-arm64-1.0.16.tgz", + "integrity": "sha512-AjtL7pnhc0OajCO9NV963gwqFHiwIKIZEwpRd5tmGgB/Yc+Rr6QdRsc5ZMNe75ptagnVqViVqiTr9g/EnRspXw==", "cpu": [ "arm64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", "optional": true, "os": [ "linux" + ] + }, + "node_modules/@github/copilot-sdk-linux-x64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linux-x64/-/copilot-sdk-linux-x64-1.0.16.tgz", + "integrity": "sha512-YLxPU1IdwH4cNvIJhawzyb6nMCbXB6ZfeebUs0kQEJbu6pzsMkyl1uSWCCv1vpAHPuhub1BgMokoY1kX9chElg==", + "cpu": [ + "x64" ], - "bin": { - "copilot-linux-arm64": "copilot" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@github/copilot-linux-x64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.403.tgz", - "integrity": "sha512-eoswUc9vo4TB+/9PgFJLVtzI4dPjkpJXdCsAioVuoqPdNxHxlIHFe9HaVcqMRZxUNY1YHEBZozy+IpUEGjgdfQ==", + "node_modules/@github/copilot-sdk-linuxmusl-arm64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-arm64/-/copilot-sdk-linuxmusl-arm64-1.0.16.tgz", + "integrity": "sha512-hkHq6WSj5ug5JdDKrLxMbDyjp5wx6zbsn3swZcC6fy6lFSJMX2kCG7PivcOq1Uj7o/POiN/DhhJqvQxOGuX1IA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@github/copilot-sdk-linuxmusl-x64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-linuxmusl-x64/-/copilot-sdk-linuxmusl-x64-1.0.16.tgz", + "integrity": "sha512-JWjzUi366WXGqdY9xIJvmJRTB+sCsW8qasVwLXj12X05GxYWMdfHK+7t8CiYTs2L1NKu1NiOYd5rS7D+ChSZyw==", "cpu": [ "x64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", "optional": true, "os": [ "linux" + ] + }, + "node_modules/@github/copilot-sdk-win32-arm64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-arm64/-/copilot-sdk-win32-arm64-1.0.16.tgz", + "integrity": "sha512-nQ//JXHfPfn4sfxtZ3rcLR993Mn8YQkC/RXHW9zu0W4Qtb1kyKec7XSi+VRMsKWPyzL7B7X+SHjTRSmn9AqZnQ==", + "cpu": [ + "arm64" ], - "bin": { - "copilot-linux-x64": "copilot" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@github/copilot-sdk": { - "version": "0.1.23", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-0.1.23.tgz", - "integrity": "sha512-0by81bsBQlDKE5VbcegZfUMvPyPm1aXwSGS2rGaMAFxv3ps+dACf1Voruxik7hQTae0ziVFJjuVrlxZoRaXBLw==", + "node_modules/@github/copilot-sdk-win32-x64": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk-win32-x64/-/copilot-sdk-win32-x64-1.0.16.tgz", + "integrity": "sha512-x9JyZks9NqmMNWkAI1wNcbvuPiMUUOvaU6tWq+nzp/TFdowW/kgaMX3jebnxeNUq/gqi2rPabRHp/cCMCma8eA==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@github/copilot": "^0.0.403", - "vscode-jsonrpc": "^8.2.1", - "zod": "^4.3.6" - }, - "engines": { - "node": ">=24.0.0" - } + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@github/copilot-sdk/node_modules/zod": { "version": "4.3.6", @@ -561,346 +594,426 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/@github/copilot-win32-arm64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.403.tgz", - "integrity": "sha512-djWjzCsp2xPNafMyOZ/ivU328/WvWhdroGie/DugiJBTgQL2SP0quWW1fhTlDwE81a3g9CxfJonaRgOpFTJTcg==", + "node_modules/@koromix/koffi-android-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-android-arm64/-/koffi-android-arm64-3.2.1.tgz", + "integrity": "sha512-1pJQ4jnZlUJduK9u9DC5CGy3aOgDUPvIXpNb6syV3+Dh5Q/ugezAIGCqvY+w+1mgXsve0pd0NVvJRjdZNHQ6MA==", "cpu": [ "arm64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], - "bin": { - "copilot-win32-arm64": "copilot.exe" + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@github/copilot-win32-x64": { - "version": "0.0.403", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.403.tgz", - "integrity": "sha512-lju8cHy2E6Ux7R7tWyLZeksYC2MVZu9i9ocjiBX/qfG2/pNJs7S5OlkwKJ0BSXSbZEHQYq7iHfEWp201bVfk9A==", + "node_modules/@koromix/koffi-android-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-android-x64/-/koffi-android-x64-3.2.1.tgz", + "integrity": "sha512-HH40xGh3gVQifjOBnhwT2tECC0lL1lYe+nxHvWNSzxDIyQNcVPXg38ta7vuONRFpD+uIrw7fqGYLzbZIagkVcg==", "cpu": [ "x64" ], - "license": "SEE LICENSE IN LICENSE.md", + "license": "MIT", "optional": true, "os": [ - "win32" + "android" ], - "bin": { - "copilot-win32-x64": "copilot.exe" + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@types/node": { - "version": "18.19.70", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.70.tgz", - "integrity": "sha512-RE+K0+KZoEpDUbGGctnGdkrLFwi1eYKTlIHNl2Um98mUkGsm1u2Ff6Ltd0e8DktTtC98uy7rSj+hO8t/QuLoVQ==", + "node_modules/@koromix/koffi-darwin-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-arm64/-/koffi-darwin-arm64-3.2.1.tgz", + "integrity": "sha512-Vj4h+xcjc5+Cn0DhPHjgRX4omKAv96Kehtcd+1YgYuY2W7FvQn9vS+3SmzVwhC5Qmg9bIwUZObYQ8T/4hBqQqA==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "node_modules/@koromix/koffi-darwin-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-darwin-x64/-/koffi-darwin-x64-3.2.1.tgz", + "integrity": "sha512-gFCWxNBTZIvxo1p+PURWfsy2Ctj5FGnVVs1f03lTLhBvmxEto70pdIiFztdFLDFkAJ1pmtQmruRKapeK+E8YPA==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "node_modules/@koromix/koffi-freebsd-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-arm64/-/koffi-freebsd-arm64-3.2.1.tgz", + "integrity": "sha512-qj+f1s2e6vULaUG1cdlTcCXmunCq2t+rjxku1+esaMIqVnHpOwj0QzPuInG0AFdXjwBNQhyVR/HpDj8daEwwsQ==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" + "optional": true, + "os": [ + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "node_modules/@koromix/koffi-freebsd-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-ia32/-/koffi-freebsd-ia32-3.2.1.tgz", + "integrity": "sha512-6olHb1Qfgai0jjs6ddlDDD0ZfsCxy7SPi8rMRpuYQWH0qhgtyQu82hw5b1p7z+TJ0zZP3ZeQQ6l+U/MlM1ICHQ==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" + "optional": true, + "os": [ + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/@koromix/koffi-freebsd-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-freebsd-x64/-/koffi-freebsd-x64-3.2.1.tgz", + "integrity": "sha512-Dikhw1ySYNVMkmeFvFVjnU5Wdk6mffNoOjJxm9bTG96vg7OlemylxqdEven47R1YJ3yzNVJn/MlQ207ORWfi2w==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" + "optional": true, + "os": [ + "freebsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/@koromix/koffi-linux-arm": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-arm/-/koffi-linux-arm-3.2.1.tgz", + "integrity": "sha512-OfwUwZylidq95wQKp6ClInULrfB2giu7dqM6Rhe0zAe6lES5I2SXNw15T9+GnRHk3/9hKT2XZ37OZLaKSyWNLA==", + "cpu": [ + "arm" + ], "license": "MIT", - "engines": { - "node": ">=0.4.0" + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/form-data": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", - "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "node_modules/@koromix/koffi-linux-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-arm64/-/koffi-linux-arm64-3.2.1.tgz", + "integrity": "sha512-K+cGUL5iBcDqxmsocrjmlASqDf24gc7artbVW3PewG2c9AqwC63lezgwvB85Nx4lZAQjB6zIFHh9A7t1yGbwhw==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "license": "MIT" + "node_modules/@koromix/koffi-linux-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-ia32/-/koffi-linux-ia32-3.2.1.tgz", + "integrity": "sha512-rxj6UYjU1qd98gxNQOSCdLpc5cPRi5Giq9rNd3jnGuSNIyMkwa6Dxw4cUjmhIBCYESMJtmNt5NWnJp5u9wTfYQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "node_modules/@koromix/koffi-linux-loong64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-loong64/-/koffi-linux-loong64-3.2.1.tgz", + "integrity": "sha512-aHhnHzkPRmT/IHDlGvESJ/Bs32m8N6UE6Ab6kMeJzgk74IN8af2m/81/wZJtybR1M2UxCV4NmlVNUYQQvSAO3Q==", + "cpu": [ + "loong64" + ], "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, + "node_modules/@koromix/koffi-linux-riscv64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-riscv64/-/koffi-linux-riscv64-3.2.1.tgz", + "integrity": "sha512-qtQBsjbm3LiirLJvajWmKkNb7ARk7fvJVXdftJ7NtAnF3Xw8EbDvrtvmvtNI1yLPlYcBmlzCCD71hwhWYk0SIA==", + "cpu": [ + "riscv64" + ], "license": "MIT", "optional": true, "os": [ - "darwin" + "linux" ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/get-tsconfig": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", - "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", - "dev": true, + "node_modules/@koromix/koffi-linux-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-linux-x64/-/koffi-linux-x64-3.2.1.tgz", + "integrity": "sha512-c7hw7Qs/r5gnFRTQLcbifBwRU7wiocj+2pVuDQ5Ahb3r36SZmupmgYbTWcLvTW+hul1jd7SKRV0d14ZJq/tvSw==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "url": "https://liberapay.com/Koromix" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "node_modules/@koromix/koffi-openbsd-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-ia32/-/koffi-openbsd-ia32-3.2.1.tgz", + "integrity": "sha512-mmY8fY8LQ/CB52+h3yrMYmVyoxzW3x08S0yI6VNfHWdfU6yJtZkKCbhjmQCYMrWbYKC4gMvwZwCywIGPAkLyeA==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "ms": "^2.0.0" + "optional": true, + "os": [ + "openbsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/@koromix/koffi-openbsd-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-openbsd-x64/-/koffi-openbsd-x64-3.2.1.tgz", + "integrity": "sha512-k4ig6aAPbFSRATOIIOfdf/KtlOGH4SVls6L9fy0QnTxRJYvY2oSltTsQtBDANgEQldlq8Kl5WnpRa1VSibP4Lw==", + "cpu": [ + "x64" + ], "license": "MIT", - "engines": { - "node": ">= 0.6" + "optional": true, + "os": [ + "openbsd" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/@koromix/koffi-win32-arm64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-arm64/-/koffi-win32-arm64-3.2.1.tgz", + "integrity": "sha512-cTWBJGK//pDMeKQJE/79Aq9MiOAF4H8QyLZHSQ9IWm8czOfwjG4J1AhsQ9DjI9KFOykH77hhnpmQTGVMIubGig==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "node_modules/@koromix/koffi-win32-ia32": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-ia32/-/koffi-win32-ia32-3.2.1.tgz", + "integrity": "sha512-Z50EM6TAZ7CFyMmyX6thv8eNpJchqe9eenhibSIy2Eq/FQYF76gU2VK/LEoaF46L8hfC7TpTp9b10MvReHEyFA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } + "node_modules/@koromix/koffi-win32-x64": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@koromix/koffi-win32-x64/-/koffi-win32-x64-3.2.1.tgz", + "integrity": "sha512-ZmZNiBO6bkOSh3QNzgfvb1cMY0yMobn6ZQrSMqbAce21qyYL8niIbyipz9N/PIRDciGhsV0wUxnZsxIO+yWsHQ==", + "cpu": [ + "x64" ], "license": "MIT", - "engines": { - "node": ">=10.5.0" + "optional": true, + "os": [ + "win32" + ], + "funding": { + "url": "https://liberapay.com/Koromix" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": "4.x || >=6.0.0" + "node": ">=18" }, - "peerDependencies": { - "encoding": "^0.1.0" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/koffi": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koffi/-/koffi-3.2.1.tgz", + "integrity": "sha512-0qE3lZ8jllRqPN4Ob6Ajl7c2bJSJDhQWuKLGP5hIEpHLllJWv1ydHFMhHmHc5p/W9GticKVDbYzZd7TBoQ4CZg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "url": "https://liberapay.com/Koromix" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "optionalDependencies": { + "@koromix/koffi-android-arm64": "3.2.1", + "@koromix/koffi-android-x64": "3.2.1", + "@koromix/koffi-darwin-arm64": "3.2.1", + "@koromix/koffi-darwin-x64": "3.2.1", + "@koromix/koffi-freebsd-arm64": "3.2.1", + "@koromix/koffi-freebsd-ia32": "3.2.1", + "@koromix/koffi-freebsd-x64": "3.2.1", + "@koromix/koffi-linux-arm": "3.2.1", + "@koromix/koffi-linux-arm64": "3.2.1", + "@koromix/koffi-linux-ia32": "3.2.1", + "@koromix/koffi-linux-loong64": "3.2.1", + "@koromix/koffi-linux-riscv64": "3.2.1", + "@koromix/koffi-linux-x64": "3.2.1", + "@koromix/koffi-openbsd-ia32": "3.2.1", + "@koromix/koffi-openbsd-x64": "3.2.1", + "@koromix/koffi-win32-arm64": "3.2.1", + "@koromix/koffi-win32-ia32": "3.2.1", + "@koromix/koffi-win32-x64": "3.2.1" } }, "node_modules/openai": { - "version": "4.78.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.78.1.tgz", - "integrity": "sha512-drt0lHZBd2lMyORckOXFPQTmnGLWSLt8VK0W9BhOKWpMFBEoHMoz5gxMPmVq5icp+sOrsbMnsmZTVHUlKvD1Ow==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-7.28.0.tgz", + "integrity": "sha512-HSY4fFLflQGYe2kvO0atm6/GzhD61gpyiOZZUvO4nptMUhuB1to2K3wwFpFqc5gfEK0SxyIIFiu1yE2MtygWcA==", "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" + "engines": { + "node": ">=22.0.0" }, "peerDependencies": { - "zod": "^3.23.8" + "@aws-sdk/credential-provider-node": ">=3.972.0 <4", + "@smithy/hash-node": ">=4.3.0 <5", + "@smithy/signature-v4": ">=5.4.0 <6", + "undici": ">=5 <9", + "ws": "^8.21.0", + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@smithy/hash-node": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "undici": { + "optional": true + }, + "ws": { + "optional": true + }, "zod": { "optional": true } } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "version": "4.23.15", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -912,12 +1025,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, "node_modules/vscode-jsonrpc": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", @@ -926,31 +1033,6 @@ "engines": { "node": ">=14.0.0" } - }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } } } } diff --git a/package.json b/package.json index 4ee2d438..09fd4935 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,17 @@ "private": true, "type": "module", "dependencies": { - "@github/copilot-sdk": "^0.1.23", - "openai": "^4.78.1" + "@github/copilot-sdk": "1.0.16", + "openai": "^7.28.0" }, "devDependencies": { - "tsx": "^4.21.0" + "tsx": "^4.23.15" }, "scripts": { - "genai:translate": "npx genaiscript@latest run translator \"./lessons/*/README.md\" --no-run-trace --remote https://github.com/pelikhan/action-continuous-translation --remote-install -x glossaryFile=glossary.md -x lang=fr -x \"filenameTemplate={{dirname}}/translations/{{basename}}.{{lang}}{{extname}}\"" + "build": "node scripts/build-docs.mjs", + "build:samples": "npm run build --prefix lessons/07-mcp/code && npm run build --prefix lessons/07-mcp/solution && npm run build --prefix lessons/08-mcp-advanced/solutions", + "test:samples": "npm run build:samples && node --test tests/samples.test.js", + "test:docs": "node --test tests/docs.test.js", + "test": "npm test --prefix app && npm run test:samples && npm run test:docs" } } diff --git a/scripts/build-docs.mjs b/scripts/build-docs.mjs new file mode 100644 index 00000000..a78296ca --- /dev/null +++ b/scripts/build-docs.mjs @@ -0,0 +1,25 @@ +import { cp, mkdir, rm } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const repositoryRoot = fileURLToPath(new URL("../", import.meta.url)); +export async function buildDocs(root = repositoryRoot) { + const output = path.join(root, "dist"); + await rm(output, { recursive: true, force: true }); + await mkdir(output, { recursive: true }); + for (const entry of ["index.html", "_sidebar.md", "README.md", "LICENSE", "docs", "lessons", "app"]) { + await cp(path.join(root, entry), path.join(output, entry), { + recursive: true, + filter(source) { + const parts = path.relative(root, source).split(path.sep); + return !parts.some(part => ["node_modules", ".git", ".genaiscript", "test", "tests"].includes(part) || + ((part.startsWith(".env") || part.endsWith(".env")) && part !== ".env.example")); + } + }); + } + return output; +} +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + await buildDocs(); + console.log("Static course documentation built in dist/."); +} diff --git a/tests/docs.test.js b/tests/docs.test.js new file mode 100644 index 00000000..113df547 --- /dev/null +++ b/tests/docs.test.js @@ -0,0 +1,69 @@ +import assert from "node:assert/strict"; +import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { test } from "node:test"; +import vm from "node:vm"; +import { buildDocs } from "../scripts/build-docs.mjs"; + +function docsify(pathname) { + return readFile(new URL("../index.html", import.meta.url), "utf8").then(html => { + const script = [...html.matchAll(/