`
+- Display validation errors immediately after the field with `aria-describedby`
+- Use `aria-required="true"` for required fields
+- Provide clear instructions before users start filling out forms
+
+**Error message format:**
+```html
+
+Please enter a valid email address
+```
+
+---
+
+**Code Generation Rule:** Always include accessibility comments explaining ARIA attributes and semantic choices. Test code with keyboard navigation before suggesting it's complete.
+
+````
+
+{% data reusables.copilot.custom-instructions-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/custom-instructions/code-reviewer.md b/content/copilot/tutorials/customization-library/custom-instructions/code-reviewer.md
new file mode 100644
index 000000000000..fbe2c592b49a
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/custom-instructions/code-reviewer.md
@@ -0,0 +1,66 @@
+---
+title: Code reviewer
+intro: 'Instructions for thorough and constructive code reviews.'
+versions:
+ feature: copilot
+category:
+ - Custom instructions
+ - Team collaboration
+complexity:
+ - Simple
+octicon: book
+topics:
+ - Copilot
+contentType: tutorials
+---
+
+{% data reusables.copilot.customization-examples-note %}
+
+The following example shows custom instructions to guide {% data variables.product.prodname_copilot %} to provide thorough, constructive code reviews focused on security, performance, and code quality.
+
+```markdown copy
+When reviewing code, focus on:
+
+## Security Critical Issues
+- Check for hardcoded secrets, API keys, or credentials
+- Look for SQL injection and XSS vulnerabilities
+- Verify proper input validation and sanitization
+- Review authentication and authorization logic
+
+## Performance Red Flags
+- Identify N+1 database query problems
+- Spot inefficient loops and algorithmic issues
+- Check for memory leaks and resource cleanup
+- Review caching opportunities for expensive operations
+
+## Code Quality Essentials
+- Functions should be focused and appropriately sized
+- Use clear, descriptive naming conventions
+- Ensure proper error handling throughout
+
+## Review Style
+- Be specific and actionable in feedback
+- Explain the "why" behind recommendations
+- Acknowledge good patterns when you see them
+- Ask clarifying questions when code intent is unclear
+
+Always prioritize security vulnerabilities and performance issues that could impact users.
+
+Always suggest changes to improve readability. For example, this suggestion seeks to make the code more readable and also makes the validation logic reusable and testable.
+
+// Instead of:
+if (user.email && user.email.includes('@') && user.email.length > 5) {
+ submitButton.enabled = true;
+} else {
+ submitButton.enabled = false;
+}
+
+// Consider:
+function isValidEmail(email) {
+ return email && email.includes('@') && email.length > 5;
+}
+
+submitButton.enabled = isValidEmail(user.email);
+```
+
+{% data reusables.copilot.custom-instructions-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/custom-instructions/concept-explainer.md b/content/copilot/tutorials/customization-library/custom-instructions/concept-explainer.md
new file mode 100644
index 000000000000..64dac75a00e8
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/custom-instructions/concept-explainer.md
@@ -0,0 +1,51 @@
+---
+title: Concept explainer
+intro: 'Instructions for breaking down complex technical concepts.'
+versions:
+ feature: copilot
+category:
+ - Custom instructions
+ - Getting started
+complexity:
+ - Simple
+octicon: book
+topics:
+ - Copilot
+contentType: tutorials
+---
+
+{% data reusables.copilot.customization-examples-note %}
+
+The following example shows custom instructions to guide {% data variables.product.prodname_copilot %} to explain complex technical concepts in clear, beginner-friendly ways with practical examples.
+
+```markdown copy
+When explaining technical concepts:
+
+## Start Simple, Build Up
+- Begin with everyday analogies and familiar examples
+- Introduce technical terms gradually after concepts are clear
+- Build each new idea on what was already explained
+- Use concrete examples before abstract theory
+
+## Make It Practical
+- Include working code examples that demonstrate the concept
+- Show real-world applications and use cases
+- Connect theory to problems developers actually face
+- Provide step-by-step implementation when relevant
+
+## Address Common Confusion
+- Highlight misconceptions that typically trip up learners
+- Explain what NOT to do and why
+- Address edge cases that often cause problems
+- Show debugging approaches when things go wrong
+
+## Check Understanding
+- Ask questions to gauge comprehension
+- Provide simple exercises to reinforce learning
+- Break complex topics into smaller, digestible pieces
+- Adjust complexity based on the learner's responses
+
+Always prioritize clarity and practical understanding over comprehensive coverage.
+```
+
+{% data reusables.copilot.custom-instructions-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/custom-instructions/debugging-tutor.md b/content/copilot/tutorials/customization-library/custom-instructions/debugging-tutor.md
new file mode 100644
index 000000000000..cefe92258369
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/custom-instructions/debugging-tutor.md
@@ -0,0 +1,61 @@
+---
+title: Debugging tutor
+intro: 'Instructions for systematic debugging and troubleshooting.'
+versions:
+ feature: copilot
+category:
+ - Custom instructions
+ - Getting started
+complexity:
+ - Simple
+octicon: book
+topics:
+ - Copilot
+contentType: tutorials
+---
+
+{% data reusables.copilot.customization-examples-note %}
+
+The following example shows custom instructions to guide {% data variables.product.prodname_copilot %} to teach systematic debugging methodology and build independent problem-solving skills.
+
+```markdown copy
+When helping with debugging, guide users through:
+
+## Systematic Approach
+- Start by reproducing the issue consistently
+- Read error messages carefully—they contain crucial clues
+- Use print statements or debugger to trace execution flow
+- Test one change at a time to isolate what fixes the problem
+
+## Key Debugging Questions
+- What exactly is happening vs. what you expected?
+- When did this problem start occurring?
+- What was the last change made before the issue appeared?
+- Can you create a minimal example that reproduces the problem?
+
+## Common Investigation Steps
+1. Check logs and error messages for specific details
+2. Verify inputs and outputs at each step
+3. Use debugging tools (breakpoints, step-through)
+4. Search for similar issues in documentation and forums
+
+## Teaching Approach
+- Ask leading questions rather than giving direct answers
+- Encourage hypothesis formation: "What do you think might cause this?"
+- Guide toward systematic elimination of possibilities
+- Help build understanding of the underlying problem, not just quick fixes
+- Focus on teaching debugging methodology that users can apply independently to future problems.
+- Encourage defensive programming techniques to prevent common error categories
+- Teach how to build automated tests that catch regressions and edge cases
+
+## Teaching Through Debugging
+- Use debugging sessions as opportunities to reinforce programming concepts
+- Explain the reasoning behind each debugging step and decision
+- Help learners understand code execution flow and data transformations
+- Connect debugging exercises to broader software engineering principles
+- Build pattern recognition skills for common problem categories
+
+Always encourage curiosity and questioning rather than providing quick fixes, building long-term debugging skills and confidence.
+```
+
+{% data reusables.copilot.custom-instructions-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/custom-instructions/github-actions-helper.md b/content/copilot/tutorials/customization-library/custom-instructions/github-actions-helper.md
new file mode 100644
index 000000000000..f537dcb64c30
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/custom-instructions/github-actions-helper.md
@@ -0,0 +1,65 @@
+---
+title: GitHub Actions helper
+intro: 'Generate and improve {% data variables.product.prodname_actions %} workflows.'
+versions:
+ feature: copilot
+category:
+ - Custom instructions
+ - GitHub flows
+ - Path-specific
+ - Repository
+complexity:
+ - Simple
+octicon: book
+topics:
+ - Copilot
+ - Actions
+---
+
+{% data reusables.copilot.customization-examples-note %}
+
+The following example shows a path-specific `actions.instructions.md` file that applies only to {% data variables.product.prodname_actions %} workflow files in your repository, using the `applyTo` field. For more information about path-specific instructions files, see [AUTOTITLE](/copilot/how-tos/configure-custom-instructions/add-repository-instructions#using-one-or-more-instructionsmd-files).
+
+````text copy
+---
+applyTo: ".github/workflows/**/*.yml"
+---
+
+When generating or improving {% data variables.product.prodname_actions %} workflows:
+
+## Security First
+- Use {% data variables.product.prodname_dotcom %} secrets for sensitive data, never hardcode credentials
+- Pin third-party actions to specific commits by using the SHA value (e.g., `- uses: owner/some-action@a824008085750b8e136effc585c3cd6082bd575f`)
+- Configure minimal permissions for GITHUB_TOKEN required for the workflow
+
+## Performance Essentials
+- Cache dependencies with `actions/cache` or built-in cache options
+- Add `timeout-minutes` to prevent hung workflows
+- Use matrix strategies for multi-environment testing
+
+## Best Practices
+- Use descriptive names for workflows, jobs, and steps
+- Include appropriate triggers: `push`, `pull_request`, `workflow_dispatch`
+- Add `if: always()` for cleanup steps that must run regardless of failure
+
+## Example Pattern
+```yaml
+name: CI
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - uses: {% data reusables.actions.action-checkout %}
+ - uses: {% data reusables.actions.action-setup-node %}
+ with:
+ node-version: 20
+ cache: npm
+ - run: npm ci
+ - run: npm test
+```
+````
+
+{% data reusables.copilot.custom-instructions-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/custom-instructions/index.md b/content/copilot/tutorials/customization-library/custom-instructions/index.md
new file mode 100644
index 000000000000..5dd24d884c36
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/custom-instructions/index.md
@@ -0,0 +1,20 @@
+---
+title: Custom instructions
+intro: 'Discover a curated collection of custom instructions to enhance your {% data variables.product.prodname_copilot %} experience.'
+allowTitleToDifferFromFilename: true
+versions:
+ feature: copilot
+topics:
+ - Copilot
+children:
+ - /your-first-custom-instructions
+ - /concept-explainer
+ - /debugging-tutor
+ - /code-reviewer
+ - /github-actions-helper
+ - /pull-request-assistant
+ - /issue-manager
+ - /accessibility-auditor
+ - /testing-automation
+contentType: tutorials
+---
diff --git a/content/copilot/tutorials/customization-library/custom-instructions/issue-manager.md b/content/copilot/tutorials/customization-library/custom-instructions/issue-manager.md
new file mode 100644
index 000000000000..3b3cfeb27270
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/custom-instructions/issue-manager.md
@@ -0,0 +1,60 @@
+---
+title: Issue manager
+intro: 'Create well-structured issues and responses.'
+versions:
+ feature: copilot
+category:
+ - Custom instructions
+ - GitHub flows
+complexity:
+ - Simple
+octicon: book
+topics:
+ - Copilot
+ - Issues
+---
+
+{% data reusables.copilot.customization-examples-note %}
+
+The following example shows custom instructions to guide {% data variables.product.prodname_copilot %} to create well-structured, actionable {% data variables.product.prodname_dotcom %} issues and provide effective issue management.
+
+```markdown copy
+When creating or managing {% data variables.product.prodname_dotcom %} issues:
+
+## Bug Report Essentials
+**Description**: Clear, concise summary of the problem
+
+**Steps to Reproduce**: Numbered list of exact actions that cause the issue
+
+**Expected vs Actual Behavior**: What should happen vs what actually happens
+
+**Environment**: OS, browser/client, app version, relevant dependencies
+
+**Additional Context**: Screenshots, error logs, or stack traces
+
+## Feature Request Structure
+**Problem**: What specific problem does this solve?
+
+**Proposed Solution**: Brief description of the suggested approach
+
+**Use Cases**: 2-3 concrete examples of when this would be valuable
+
+**Success Criteria**: How to measure if the feature works
+
+## Issue Management Best Practices
+- Use clear, descriptive titles that summarize the request
+- Apply appropriate labels: bug/feature, priority level, component areas
+- Ask clarifying questions when details are missing
+- Link related issues using #number syntax
+- Provide specific next steps and realistic timelines
+
+## Key Response Guidelines
+- Request reproduction steps for unclear bugs
+- Ask for screenshots/logs when visual issues are reported
+- Explain technical concepts clearly for non-technical users
+- Update issue status regularly with progress information
+
+Focus on making issues actionable and easy for contributors to understand.
+```
+
+{% data reusables.copilot.custom-instructions-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/custom-instructions/pull-request-assistant.md b/content/copilot/tutorials/customization-library/custom-instructions/pull-request-assistant.md
new file mode 100644
index 000000000000..fad836c0757f
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/custom-instructions/pull-request-assistant.md
@@ -0,0 +1,114 @@
+---
+title: Pull request assistant
+intro: 'Generate comprehensive pull request descriptions and reviews.'
+versions:
+ feature: copilot
+category:
+ - Custom instructions
+ - GitHub flows
+complexity:
+ - Simple
+octicon: book
+topics:
+ - Copilot
+ - Pull requests
+---
+
+{% data reusables.copilot.customization-examples-note %}
+
+The following example shows custom instructions to guide {% data variables.product.prodname_copilot %} to create detailed pull request descriptions and provide constructive code reviews.
+
+```markdown copy
+When creating pull request descriptions or reviewing PRs:
+
+## PR Description Template
+**What changed**
+- Clear summary of modifications and affected components
+- Link to related issues or tickets
+
+**Why**
+- Business context and requirements
+- Technical reasoning for approach taken
+
+**Testing**
+- [ ] Unit tests pass and cover new functionality
+- [ ] Manual testing completed for user-facing changes
+- [ ] Performance/security considerations addressed
+
+**Breaking Changes**
+- List any API changes or behavioral modifications
+- Include migration instructions if needed
+
+## Review Focus Areas
+- **Security**: Check for hardcoded secrets, input validation, auth issues
+- **Performance**: Look for database query problems, inefficient loops
+- **Testing**: Ensure adequate test coverage for new functionality
+- **Documentation**: Verify code comments and README updates
+
+## Review Style
+- Be specific and constructive in feedback
+- Acknowledge good patterns and solutions
+- Ask clarifying questions when code intent is unclear
+- Focus on maintainability and readability improvements
+- Always prioritize changes that improve security, performance, or user experience.
+- Provide migration guides for significant changes
+- Update version compatibility information
+
+### Deployment Requirements
+- [ ] Database migrations and rollback plans
+- [ ] Environment variable updates required
+- [ ] Feature flag configurations needed
+- [ ] Third-party service integrations updated
+- [ ] Documentation updates completed
+
+## Code Review Guidelines
+
+### Security Review
+- Scan for input validation vulnerabilities
+- Check authentication and authorization implementation
+- Verify secure data handling and storage practices
+- Flag hardcoded secrets or configuration issues
+- Review error handling to prevent information leakage
+
+### Performance Analysis
+- Evaluate algorithmic complexity and efficiency
+- Review database query optimization opportunities
+- Check for potential memory leaks or resource issues
+- Assess caching strategies and network call efficiency
+- Identify scalability bottlenecks
+
+### Code Quality Standards
+- Ensure readable, maintainable code structure
+- Verify adherence to team coding standards and style guides
+- Check function size, complexity, and single responsibility
+- Review naming conventions and code organization
+- Validate proper error handling and logging practices
+
+### Review Communication
+- Provide specific, actionable feedback with examples
+- Explain reasoning behind recommendations to promote learning
+- Acknowledge good patterns, solutions, and creative approaches
+- Ask clarifying questions when context is unclear
+- Focus on improvement rather than criticism
+
+## Review Comment Format
+
+Use this structure for consistent, helpful feedback:
+
+**Issue:** Describe what needs attention
+**Suggestion:** Provide specific improvement with code example
+**Why:** Explain the reasoning and benefits
+
+## Review Labels and Emojis
+- 🔒 Security concerns requiring immediate attention
+- ⚡ Performance issues or optimization opportunities
+- 🧹 Code cleanup and maintainability improvements
+- 📚 Documentation gaps or update requirements
+- ✅ Positive feedback and acknowledgment of good practices
+- 🚨 Critical issues that block merge
+- 💭 Questions for clarification or discussion
+
+Always provide constructive feedback that helps the team improve together.
+```
+
+{% data reusables.copilot.custom-instructions-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/custom-instructions/testing-automation.md b/content/copilot/tutorials/customization-library/custom-instructions/testing-automation.md
new file mode 100644
index 000000000000..7239107ec896
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/custom-instructions/testing-automation.md
@@ -0,0 +1,64 @@
+---
+title: Testing automation
+intro: 'File-specific instructions for writing unit tests.'
+versions:
+ feature: copilot
+category:
+ - Custom instructions
+ - Development workflows
+ - Path-specific
+ - Repository
+complexity:
+ - Advanced
+octicon: book
+topics:
+ - Copilot
+contentType: tutorials
+---
+
+{% data reusables.copilot.customization-examples-note %}
+
+This example shows a path-specifc `python-tests.instructions.md` file that applies only to Python test files in your repository, using the `applyTo` field. For more information about path-specific instructions files, see [AUTOTITLE](/copilot/how-tos/configure-custom-instructions/add-repository-instructions#using-one-or-more-instructionsmd-files).
+
+````text copy
+---
+applyTo: "tests/**/*.py"
+---
+
+When writing Python tests:
+
+## Test Structure Essentials
+- Use pytest as the primary testing framework
+- Follow AAA pattern: Arrange, Act, Assert
+- Write descriptive test names that explain the behavior being tested
+- Keep tests focused on one specific behavior
+
+## Key Testing Practices
+- Use pytest fixtures for setup and teardown
+- Mock external dependencies (databases, APIs, file operations)
+- Use parameterized tests for testing multiple similar scenarios
+- Test edge cases and error conditions, not just happy paths
+
+## Example Test Pattern
+```python
+import pytest
+from unittest.mock import Mock, patch
+
+class TestUserService:
+ @pytest.fixture
+ def user_service(self):
+ return UserService()
+
+ @pytest.mark.parametrize("invalid_email", ["", "invalid", "@test.com"])
+ def test_should_reject_invalid_emails(self, user_service, invalid_email):
+ with pytest.raises(ValueError, match="Invalid email"):
+ user_service.create_user({"email": invalid_email})
+
+ @patch('src.user_service.email_validator')
+ def test_should_handle_validation_failure(self, mock_validator, user_service):
+ mock_validator.validate.side_effect = ConnectionError()
+
+ with pytest.raises(ConnectionError):
+ user_service.create_user({"email": "test@example.com"})
+```
+````
diff --git a/content/copilot/tutorials/customization-library/custom-instructions/your-first-custom-instructions.md b/content/copilot/tutorials/customization-library/custom-instructions/your-first-custom-instructions.md
new file mode 100644
index 000000000000..74453f1cd5e7
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/custom-instructions/your-first-custom-instructions.md
@@ -0,0 +1,101 @@
+---
+title: Your first custom instructions
+intro: 'Create and test your first custom instruction with this simple example.'
+versions:
+ feature: copilot
+category:
+ - Custom instructions
+ - Getting started
+complexity:
+ - Simple
+octicon: book
+topics:
+ - Copilot
+---
+
+{% data reusables.copilot.customization-examples-note %}
+
+## About customizations
+
+You can customize {% data variables.product.prodname_copilot %}'s responses using two types of files:
+
+* **Custom instructions** provide ongoing guidance for how {% data variables.product.prodname_copilot %} should behave across all your interactions.
+* **Prompt files (public preview)** define reusable prompts for specific tasks that you can invoke when needed. Prompt files are only available in {% data variables.product.prodname_vscode_shortname %}. For an introductory example, see [AUTOTITLE](/copilot/tutorials/customization-library/prompt-files/your-first-prompt-file).
+
+While custom instructions help to add context to each AI workflow, prompt files let you add instructions to a specific chat interaction.
+
+Repository custom instructions are the most commonly used and supported, but you can also define personal and organization custom instructions, only for {% data variables.copilot.copilot_chat_dotcom %}. {% data reusables.copilot.repository-custom-instructions-types %}
+
+## Your first instructions
+
+Start with these core custom instructions that helps {% data variables.product.prodname_copilot %} understand your coding preferences.
+
+### Instructions on writing functions
+
+```markdown copy
+When writing functions, always:
+- Add descriptive JSDoc comments
+- Include input validation
+- Use early returns for error conditions
+- Add meaningful variable names
+- Include at least one example usage in comments
+```
+
+These instructions will change how {% data variables.product.prodname_copilot %} generates functions.
+
+## Test it out
+
+To quickly test out the difference that custom instructions can make, you can use personal custom instructions in {% data variables.copilot.copilot_chat_short %}'s immersive view.
+
+1. First, go to [github.com/copilot](https://github.com/copilot) and enter this prompt:
+
+ `Create a JavaScript function that calculates the area of a circle`
+
+ **Without custom instructions**, you might get:
+
+ ```javascript
+ function areaOfCircle(radius) {
+ if (typeof radius !== 'number' || radius < 0) {
+ throw new Error('Radius must be a non-negative number');
+ }
+ return Math.PI * radius * radius;
+ }
+ ```
+
+1. Now access your personal custom instructions by clicking on your profile picture in the bottom left of the page, then click **Personal instructions**. Paste the above instructions into the text box, then click **Save**.
+
+ Now enter the same prompt that you entered previously.
+
+ **With custom instructions**, you'll get something like:
+
+ ```javascript
+ /**
+ * Calculates the area of a circle given its radius.
+ *
+ * @param {number} radius - The radius of the circle. Must be a positive number.
+ * @returns {number|null} The area of the circle, or null if the input is invalid.
+ *
+ * @example
+ * // returns 78.53981633974483
+ * areaOfCircle(5);
+ *
+ * @example
+ * // returns null (invalid input)
+ * areaOfCircle(-2);
+ */
+ function areaOfCircle(radius) {
+ if (typeof radius !== "number" || isNaN(radius) || radius <= 0) {
+ // Invalid input: radius must be a positive number
+ return null;
+ }
+
+ const area = Math.PI * Math.pow(radius, 2);
+ return area;
+ }
+
+ // Example usage:
+ console.log(areaOfCircle(5)); // 78.53981633974483
+ console.log(areaOfCircle(-2)); // null
+ ```
+
+{% data reusables.copilot.custom-instructions-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/index.md b/content/copilot/tutorials/customization-library/index.md
new file mode 100644
index 000000000000..0e7590b0254d
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/index.md
@@ -0,0 +1,23 @@
+---
+title: Customization library
+intro: 'Discover a curated collection of customizations, including custom instructions and prompt files (VS Code only), to enhance your {% data variables.product.prodname_copilot %} experience.'
+allowTitleToDifferFromFilename: true
+versions:
+ feature: copilot
+topics:
+ - Copilot
+layout: category-landing
+sidebarLink:
+ text: All customizations
+ href: /copilot/tutorials/customization-library
+spotlight:
+ - article: /custom-instructions/your-first-custom-instructions
+ image: /assets/images/copilot-landing/generating_unit_tests.png
+ - article: /prompt-files/your-first-prompt-file
+ image: /assets/images/copilot-landing/improving_code_readability.png
+children:
+ - /custom-instructions
+ - /prompt-files
+contentType: tutorials
+---
+
diff --git a/content/copilot/tutorials/customization-library/prompt-files/create-readme.md b/content/copilot/tutorials/customization-library/prompt-files/create-readme.md
new file mode 100644
index 000000000000..2be651495993
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/prompt-files/create-readme.md
@@ -0,0 +1,76 @@
+---
+title: Create README
+intro: 'Generate comprehensive README files for your projects.'
+versions:
+ feature: copilot
+category:
+ - Prompt files
+ - Getting started
+complexity:
+ - Simple
+octicon: copilot
+topics:
+ - Copilot
+contentType: tutorials
+---
+
+{% data reusables.copilot.prompt-files-preview-note %}
+
+This prompt file creates professional, comprehensive README files by analyzing your entire project structure and codebase.
+
+## README generator prompt
+
+```text copy
+---
+mode: 'agent'
+description: 'Create a comprehensive README.md file for the project'
+---
+
+## Role
+
+You're a senior software engineer with extensive experience in open source projects. You create appealing, informative, and easy-to-read README files.
+
+## Task
+
+1. Review the entire project workspace and codebase
+2. Create a comprehensive README.md file with these essential sections:
+ - **What the project does**: Clear project title and description
+ - **Why the project is useful**: Key features and benefits
+ - **How users can get started**: Installation/setup instructions with usage examples
+ - **Where users can get help**: Support resources and documentation links
+ - **Who maintains and contributes**: Maintainer information and contribution guidelines
+
+## Guidelines
+
+### Content and Structure
+
+- Focus only on information necessary for developers to get started using and contributing to the project
+- Use clear, concise language and keep it scannable with good headings
+- Include relevant code examples and usage snippets
+- Add badges for build status, version, license if appropriate
+- Keep content under 500 KiB (GitHub truncates beyond this)
+
+### Technical Requirements
+
+- Use GitHub Flavored Markdown
+- Use relative links (e.g., `docs/CONTRIBUTING.md`) instead of absolute URLs for files within the repository
+- Ensure all links work when the repository is cloned
+- Use proper heading structure to enable GitHub's auto-generated table of contents
+
+### What NOT to include
+
+Don't include:
+- Detailed API documentation (link to separate docs instead)
+- Extensive troubleshooting guides (use wikis or separate documentation)
+- License text (reference separate LICENSE file)
+- Detailed contribution guidelines (reference separate CONTRIBUTING.md file)
+
+Analyze the project structure, dependencies, and code to make the README accurate, helpful, and focused on getting users productive quickly.
+```
+
+## How to use this prompt file
+
+1. Save the above content as `create-readme.prompt.md` in your `.github/prompts` folder of your repository.
+1. In {% data variables.product.prodname_vscode %}, display the {% data variables.copilot.copilot_chat_short %} view and enter `/create-readme`.
+
+{% data reusables.copilot.prompt-files-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/prompt-files/document-api.md b/content/copilot/tutorials/customization-library/prompt-files/document-api.md
new file mode 100644
index 000000000000..1ef476f6f335
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/prompt-files/document-api.md
@@ -0,0 +1,83 @@
+---
+title: Document API
+intro: 'Generate comprehensive API documentation from your code.'
+versions:
+ feature: copilot
+category:
+ - Prompt files
+ - Development workflows
+complexity:
+ - Advanced
+octicon: copilot
+topics:
+ - Copilot
+contentType: tutorials
+---
+
+{% data reusables.copilot.prompt-files-preview-note %}
+
+This prompt file generates OpenAPI 3.0 specifications for REST API endpoints by analyzing your API code and creating standardized, machine-readable documentation.
+
+## OpenAPI specification prompt
+
+```text copy
+---
+mode: 'agent'
+description: 'Generate OpenAPI 3.0 specification for API endpoints'
+---
+
+## Task
+
+Analyze the API endpoint code and generate a valid OpenAPI 3.0 specification in YAML format.
+
+## OpenAPI Structure
+
+Generate a complete OpenAPI spec including:
+
+1. **OpenAPI Header**
+ - OpenAPI version (3.0.3)
+ - API info (title, description, version)
+ - Server configuration
+
+2. **Path Definitions**
+ - HTTP method and path
+ - Operation summary and description
+ - Tags for organization
+
+3. **Parameters Schema**
+ - Path parameters with type validation
+ - Query parameters with constraints and defaults
+ - Request body schema using proper JSON Schema
+ - Required vs optional parameters
+
+4. **Response Schemas**
+ - Success responses (200, 201, etc.) with schema definitions
+ - Error responses (400, 401, 404, 500) with error schema
+ - Content-Type specifications
+ - Realistic example values
+
+5. **Components Section**
+ - Reusable schemas for request/response models
+ - Security schemes (Bearer token, API key, etc.)
+ - Common parameter definitions
+
+## Requirements
+
+- Generate valid OpenAPI 3.0.3 YAML that passes validation
+- Use proper JSON Schema for all data models
+- Include realistic example values, not placeholders
+- Define reusable components to avoid duplication
+- Add appropriate data validation (required fields, formats, constraints)
+- Include security requirements where applicable
+
+Focus on: ${input:endpoint_focus:Which specific endpoint or endpoints should be documented?}
+
+Generate production-ready OpenAPI specification that can be used with Swagger UI, Postman, and code generators.
+```
+
+## How to use this prompt file
+
+1. Save the above content as `document-api.prompt.md` in your `.github/prompts` folder.
+1. In {% data variables.product.prodname_vscode %}, display the {% data variables.copilot.copilot_chat_short %} view and enter `/document-api`. Optionally, you can also specify what specific endpoint you want documentation for by typing `endpoint_focus=GET /activities`, for example.
+
+{% data reusables.copilot.prompt-files-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/prompt-files/generate-unit-tests.md b/content/copilot/tutorials/customization-library/prompt-files/generate-unit-tests.md
new file mode 100644
index 000000000000..f3cc3bb9766e
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/prompt-files/generate-unit-tests.md
@@ -0,0 +1,84 @@
+---
+title: Generate unit tests
+intro: 'Create focused unit tests for your code.'
+versions:
+ feature: copilot
+category:
+ - Prompt files
+ - Development workflows
+complexity:
+ - Intermediate
+octicon: copilot
+topics:
+ - Copilot
+contentType: tutorials
+---
+
+{% data reusables.copilot.prompt-files-preview-note %}
+
+This prompt file generates focused unit tests for specific functions or methods, emphasizing practical test cases and maintainable code.
+
+## Unit test generation prompt
+
+```text copy
+---
+mode: 'agent'
+description: 'Generate unit tests for selected functions or methods'
+---
+
+## Task
+
+Analyze the selected function/method and generate focused unit tests that thoroughly validate its behavior.
+
+## Test Generation Strategy
+
+1. **Core Functionality Tests**
+ - Test the main purpose/expected behavior
+ - Verify return values with typical inputs
+ - Test with realistic data scenarios
+
+2. **Input Validation Tests**
+ - Test with invalid input types
+ - Test with null/undefined values
+ - Test with empty strings/arrays/objects
+ - Test boundary values (min/max, zero, negative numbers)
+
+3. **Error Handling Tests**
+ - Test expected exceptions are thrown
+ - Verify error messages are meaningful
+ - Test graceful handling of edge cases
+
+4. **Side Effects Tests** (if applicable)
+ - Verify external calls are made correctly
+ - Test state changes
+ - Validate interactions with dependencies
+
+## Test Structure Requirements
+
+- Use existing project testing framework and patterns
+- Follow AAA pattern: Arrange, Act, Assert
+- Write descriptive test names that explain the scenario
+- Group related tests in describe/context blocks
+- Mock external dependencies cleanly
+
+Target function: ${input:function_name:Which function or method should be tested?}
+Testing framework: ${input:framework:Which framework? (jest/vitest/mocha/pytest/rspec/etc)}
+
+## Guidelines
+
+- Generate 5-8 focused test cases covering the most important scenarios
+- Include realistic test data, not just simple examples
+- Add comments for complex test setup or assertions
+- Ensure tests are independent and can run in any order
+- Focus on testing behavior, not implementation details
+
+Create tests that give confidence the function works correctly and help catch regressions.
+```
+
+## How to use this prompt file
+
+1. Save the above content as `generate-unit-tests.prompt.md` in your `.github/prompts` folder.
+1. Open the code file containing the function(s) you want tests for. Optionally, you can highlight a specific function.
+1. In {% data variables.product.prodname_vscode %}, display the {% data variables.copilot.copilot_chat_short %} view and enter `/generate-unit-tests`. Optionally, you can also specify the target function and testing framework by typing `function_name=fetchActivities` and `framework=pytest`, for example.
+
+{% data reusables.copilot.prompt-files-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/prompt-files/index.md b/content/copilot/tutorials/customization-library/prompt-files/index.md
new file mode 100644
index 000000000000..6f073cb760af
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/prompt-files/index.md
@@ -0,0 +1,18 @@
+---
+title: Prompt files
+intro: 'Reusable prompt examples for common development tasks.'
+versions:
+ feature: copilot
+topics:
+ - Copilot
+children:
+ - /your-first-prompt-file
+ - /create-readme
+ - /onboarding-plan
+ - /document-api
+ - /review-code
+ - /generate-unit-tests
+contentType: tutorials
+---
+
+{% data reusables.copilot.prompt-files-preview-note %}
diff --git a/content/copilot/tutorials/customization-library/prompt-files/onboarding-plan.md b/content/copilot/tutorials/customization-library/prompt-files/onboarding-plan.md
new file mode 100644
index 000000000000..c866f3b79964
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/prompt-files/onboarding-plan.md
@@ -0,0 +1,55 @@
+---
+title: Onboarding plan
+intro: 'A prompt file for getting personalized help with team onboarding.'
+versions:
+ feature: copilot
+category:
+ - Prompt files
+ - Team collaboration
+complexity:
+ - Simple
+octicon: copilot
+topics:
+ - Copilot
+contentType: tutorials
+---
+
+{% data reusables.copilot.prompt-files-preview-note %}
+
+## Onboarding plan prompt
+
+```text copy
+---
+mode: 'agent'
+description: 'Help new team members onboard with a phased plan and suggestions for first tasks.'
+---
+
+# Create My Onboarding Plan
+
+I'm a new team member joining ${input:team:Team or project name} and I need help creating a structured onboarding plan.
+
+My background: ${input:background:Briefly describe your experience level - new to tech, experienced developer new to this stack, etc.}
+
+Please create a personalized phased onboarding plan that includes the following phases.
+
+## Phase 1 - Foundation
+
+Environment setup with step-by-step instructions and troubleshooting tips, plus identifying the most important documentation to read first
+
+## Phase 2 - Exploration
+
+Codebase discovery starting with README files, running existing tests/scripts to understand workflows, and finding beginner-friendly first tasks like documentation improvements. If possible, find me specific open issues or tasks that are suitable for my background.
+
+## Phase 3 - Integration
+
+Learning team processes, making first contributions, and building confidence through early wins
+
+For each phase, break down complex topics into manageable steps, recommend relevant resources, provide concrete next steps, and suggest hands-on practice over just reading theory.
+```
+
+## How to use this prompt file
+
+1. Save the above content as `onboarding-plan.prompt.md` in your `.github/prompts` folder.
+1. In {% data variables.product.prodname_vscode %}, display the {% data variables.copilot.copilot_chat_short %} view and enter `/onboarding-plan`. Optionally, you can also specify your experience level by typing `background=experienced developer but new to stack`, for example.
+
+{% data reusables.copilot.prompt-files-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/prompt-files/review-code.md b/content/copilot/tutorials/customization-library/prompt-files/review-code.md
new file mode 100644
index 000000000000..3cd5d7c3d40b
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/prompt-files/review-code.md
@@ -0,0 +1,93 @@
+---
+title: Review code
+intro: 'Perform comprehensive code reviews with structured feedback.'
+versions:
+ feature: copilot
+category:
+ - Prompt files
+ - Development workflows
+complexity:
+ - Advanced
+octicon: copilot
+topics:
+ - Copilot
+contentType: tutorials
+---
+
+{% data reusables.copilot.prompt-files-preview-note %}
+
+This prompt file conducts thorough code reviews and provides structured, actionable feedback as a single comprehensive report in {% data variables.copilot.copilot_chat_short %}.
+
+You can also use {% data variables.copilot.copilot_code-review_short %} in {% data variables.product.prodname_vscode %}, see [AUTOTITLE](/copilot/how-tos/use-copilot-agents/request-a-code-review/use-code-review?tool=vscode). {% data variables.copilot.copilot_code-review_short %} gives interactive, step-by-step feedback with inline editor comments you can apply directly, while this prompt file gives a comprehensive report with educational explanations.
+
+## Code review prompt
+
+```text copy
+---
+mode: 'agent'
+description: 'Perform a comprehensive code review'
+---
+
+## Role
+
+You're a senior software engineer conducting a thorough code review. Provide constructive, actionable feedback.
+
+## Review Areas
+
+Analyze the selected code for:
+
+1. **Security Issues**
+ - Input validation and sanitization
+ - Authentication and authorization
+ - Data exposure risks
+ - Injection vulnerabilities
+
+2. **Performance & Efficiency**
+ - Algorithm complexity
+ - Memory usage patterns
+ - Database query optimization
+ - Unnecessary computations
+
+3. **Code Quality**
+ - Readability and maintainability
+ - Proper naming conventions
+ - Function/class size and responsibility
+ - Code duplication
+
+4. **Architecture & Design**
+ - Design pattern usage
+ - Separation of concerns
+ - Dependency management
+ - Error handling strategy
+
+5. **Testing & Documentation**
+ - Test coverage and quality
+ - Documentation completeness
+ - Comment clarity and necessity
+
+## Output Format
+
+Provide feedback as:
+
+**🔴 Critical Issues** - Must fix before merge
+**🟡 Suggestions** - Improvements to consider
+**✅ Good Practices** - What's done well
+
+For each issue:
+- Specific line references
+- Clear explanation of the problem
+- Suggested solution with code example
+- Rationale for the change
+
+Focus on: ${input:focus:Any specific areas to emphasize in the review?}
+
+Be constructive and educational in your feedback.
+```
+
+## How to use this prompt file
+
+1. Save the above content as `review-code.prompt.md` in your `.github/prompts` folder.
+1. Open the code file you want to review in the editor.
+1. In {% data variables.product.prodname_vscode %}, display the {% data variables.copilot.copilot_chat_short %} view and enter `/review-code` to trigger the custom review using this prompt file. Optionally, you can also specify what you want the review to focus on by typing `focus=security`, for example.
+
+{% data reusables.copilot.prompt-files-further-reading %}
diff --git a/content/copilot/tutorials/customization-library/prompt-files/your-first-prompt-file.md b/content/copilot/tutorials/customization-library/prompt-files/your-first-prompt-file.md
new file mode 100644
index 000000000000..11720189a8c0
--- /dev/null
+++ b/content/copilot/tutorials/customization-library/prompt-files/your-first-prompt-file.md
@@ -0,0 +1,67 @@
+---
+title: Your first prompt file
+intro: 'Create your first {% data variables.product.prodname_copilot_short %} prompt file with this simple code explanation example that works for any programming language.'
+versions:
+ feature: copilot
+category:
+ - Prompt files
+ - Getting started
+complexity:
+ - Simple
+octicon: copilot
+topics:
+ - Copilot
+contentType: tutorials
+---
+
+{% data reusables.copilot.prompt-files-preview-note %}
+
+## About customizations
+
+You can customize {% data variables.product.prodname_copilot %}'s responses using two types of files:
+
+* **Custom instructions** provide ongoing guidance for how {% data variables.product.prodname_copilot %} should behave across all your interactions. For an introductory example, see [AUTOTITLE](/copilot/tutorials/customization-library/custom-instructions/your-first-custom-instructions).
+* **Prompt files (public preview)** define reusable prompts for specific tasks that you can invoke when needed. Prompt files are only available in {% data variables.product.prodname_vscode_shortname %}.
+
+## Your first prompt file
+
+Start with this simple prompt file that helps you write clear, well-documented code explanations.
+
+### Code explanation prompt
+
+```text copy
+---
+mode: 'agent'
+description: 'Generate a clear code explanation with examples'
+---
+
+Explain the following code in a clear, beginner-friendly way:
+
+Code to explain: ${input:code:Paste your code here}
+Target audience: ${input:audience:Who is this explanation for? (e.g., beginners, intermediate developers, etc.)}
+
+Please provide:
+
+* A brief overview of what the code does
+* A step-by-step breakdown of the main parts
+* Explanation of any key concepts or terminology
+* A simple example showing how it works
+* Common use cases or when you might use this approach
+
+Use clear, simple language and avoid unnecessary jargon.
+```
+
+## Test it out
+
+1. Save the prompt file above as `explain-code.prompt.md` in your `.github/prompts` folder.
+1. In {% data variables.product.prodname_vscode %}, display the {% data variables.copilot.copilot_chat_short %} view and enter `/explain-code`.
+
+ {% data variables.product.prodname_copilot_short %} will switch to agent mode, if this is not already selected, and will prompt you to enter some code and an audience type.
+
+1. Enter:
+
+ ```text copy
+ The code is `function fibonacci(n) { return n <= 1 ? n : fibonacci(n-1) + fibonacci(n-2); }`. The audience is beginners.
+ ```
+
+{% data reusables.copilot.prompt-files-further-reading %}
diff --git a/content/copilot/tutorials/index.md b/content/copilot/tutorials/index.md
index 603649fa0803..23637892aa11 100644
--- a/content/copilot/tutorials/index.md
+++ b/content/copilot/tutorials/index.md
@@ -8,6 +8,7 @@ topics:
- Copilot
children:
- /copilot-chat-cookbook
+ - /customization-library
- /coding-agent
- /compare-ai-models
- /enhance-agent-mode-with-mcp
diff --git a/data/reusables/copilot/custom-instructions-further-reading.md b/data/reusables/copilot/custom-instructions-further-reading.md
new file mode 100644
index 000000000000..d251fd5b993a
--- /dev/null
+++ b/data/reusables/copilot/custom-instructions-further-reading.md
@@ -0,0 +1,5 @@
+## Further reading
+
+* [AUTOTITLE](/copilot/concepts/response-customization) - Overview of response customization in GitHub Copilot
+* [AUTOTITLE](/copilot/how-tos/configure-custom-instructions) - How to configure custom instructions
+* [Awesome GitHub Copilot Customizations](https://github.com/github/awesome-copilot/blob/main/README.instructions.md) - Repository of community-contributed custom instructions and other customizations for specific languages and scenarios
diff --git a/data/reusables/copilot/customization-examples-note.md b/data/reusables/copilot/customization-examples-note.md
new file mode 100644
index 000000000000..960b0323b09b
--- /dev/null
+++ b/data/reusables/copilot/customization-examples-note.md
@@ -0,0 +1,4 @@
+> [!NOTE]
+> * The examples in this library are intended for inspiration—you are encouraged to adjust them to be more specific to your projects, languages, and team processes.
+> * For community-contributed examples of custom instructions for specific languages and scenarios, see the [Awesome GitHub Copilot Customizations](https://github.com/github/awesome-copilot/blob/main/README.instructions.md) repository.
+> * You can apply custom instructions across different scopes, depending on the platform or IDE where you are creating them. For more information, see "[AUTOTITLE](/copilot/concepts/response-customization)."
diff --git a/data/reusables/copilot/prompt-files-further-reading.md b/data/reusables/copilot/prompt-files-further-reading.md
new file mode 100644
index 000000000000..b61ce719648c
--- /dev/null
+++ b/data/reusables/copilot/prompt-files-further-reading.md
@@ -0,0 +1,5 @@
+## Further reading
+
+* [Use prompt files in {% data variables.product.prodname_vscode %}](https://code.visualstudio.com/docs/copilot/customization/prompt-files) in the {% data variables.product.prodname_vscode %} documentation - Information on how to create and use prompt files
+* [AUTOTITLE](/copilot/concepts/response-customization) - Overview of response customization in GitHub Copilot
+* [Awesome GitHub Copilot Customizations](https://github.com/github/awesome-copilot/blob/main/README.prompts.md) - Repository of community-contributed custom prompt files and other customizations for specific languages and scenarios
diff --git a/data/reusables/copilot/prompt-files-generic-note.md b/data/reusables/copilot/prompt-files-generic-note.md
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/data/reusables/copilot/prompt-files-preview-note.md b/data/reusables/copilot/prompt-files-preview-note.md
new file mode 100644
index 000000000000..face3ef91eba
--- /dev/null
+++ b/data/reusables/copilot/prompt-files-preview-note.md
@@ -0,0 +1,3 @@
+> [!NOTE]
+> * {% data variables.product.prodname_copilot_short %} prompt files are in {% data variables.release-phases.public_preview %} and subject to change. Prompt files are only available in {% data variables.product.prodname_vscode_shortname %}. See [AUTOTITLE](/copilot/concepts/response-customization?tool=vscode#about-prompt-files).
+> * For community-contributed examples of prompt files for specific languages and scenarios, see the [Awesome GitHub Copilot Customizations](https://github.com/github/awesome-copilot/blob/main/README.prompts.md) repository.
diff --git a/data/reusables/copilot/repository-custom-instructions-types.md b/data/reusables/copilot/repository-custom-instructions-types.md
new file mode 100644
index 000000000000..f3ab16a7985a
--- /dev/null
+++ b/data/reusables/copilot/repository-custom-instructions-types.md
@@ -0,0 +1,4 @@
+You can create repository custom instructions in two ways:
+
+* **Repository-wide instructions**: Create a single `copilot-instructions.md` file at the repository root that applies to all files in the repository.
+* **Path-specific instructions**: Create one or more `.instructions.md` files with an `applyTo` field that apply only to specific files or directories. Path-specific instructions are currently supported for **{% data variables.copilot.copilot_chat_short %}** in {% data variables.product.prodname_vscode %} and **{% data variables.product.prodname_copilot %} coding agent**.
diff --git a/data/reusables/gated-features/secret-risk-assessment-calculators.md b/data/reusables/gated-features/secret-risk-assessment-calculators.md
new file mode 100644
index 000000000000..7075d0a14fb8
--- /dev/null
+++ b/data/reusables/gated-features/secret-risk-assessment-calculators.md
@@ -0,0 +1,2 @@
+
+The calculator is available in organizations on {% data variables.product.prodname_team %}, {% data variables.product.prodname_ghe_cloud %}, and {% data variables.product.prodname_ghe_server %} (_For {% data variables.product.prodname_ghe_server %}, from version 3.20 only_).
diff --git a/data/reusables/permissions/push-protection-roi-calculator.md b/data/reusables/permissions/push-protection-roi-calculator.md
new file mode 100644
index 000000000000..b4395e969fdf
--- /dev/null
+++ b/data/reusables/permissions/push-protection-roi-calculator.md
@@ -0,0 +1 @@
+Organization owners and security managers
diff --git a/data/reusables/secret-protection/product-list.md b/data/reusables/secret-protection/product-list.md
index c33e120f7348..08e6134f0896 100644
--- a/data/reusables/secret-protection/product-list.md
+++ b/data/reusables/secret-protection/product-list.md
@@ -1,6 +1,6 @@
* **{% data variables.product.prodname_secret_scanning_caps %}**: Detect secrets, for example keys and tokens, that have been checked into a repository and receive alerts.
-* **Push protection**: Prevent secret leaks before they happen by blocking commits containing secrets.{% ifversion secret-scanning-ai-generic-secret-detection %}
+* **Push protection**: Prevent secret leaks before they happen by blocking commits containing secrets. {% ifversion fpt or ghec or ghes > 3.19 %} You can calculate how much you can save by using push protection in repositories in your organization with the {% data variables.secret-scanning.roi-calculator %}. See [AUTOTITLE](/code-security/securing-your-organization/understanding-your-organizations-exposure-to-leaked-secrets/calculating-the-cost-savings-of-push-protection).{% endif %}{% ifversion secret-scanning-ai-generic-secret-detection %}
* **{% data variables.secret-scanning.copilot-secret-scanning %}**: Leverage AI to detect unstructured credentials, such as passwords, that have been checked into a repository.{% endif %}
diff --git a/data/ui.yml b/data/ui.yml
index 8c37525f7cf4..ed30ddca4583 100644
--- a/data/ui.yml
+++ b/data/ui.yml
@@ -339,9 +339,9 @@ alerts:
cookbook_landing:
spotlight: Spotlight
- explore_articles: Explore {{ number }} prompt articles
+ explore_articles: Explore {{ number }} examples
reset_filters: Reset filters
- search_articles: Search articles
+ search_articles: Search examples
category: Category
complexity: Complexity
diff --git a/data/variables/secret-scanning.yml b/data/variables/secret-scanning.yml
index 9ed5e89cb24c..294a25693c23 100644
--- a/data/variables/secret-scanning.yml
+++ b/data/variables/secret-scanning.yml
@@ -13,6 +13,8 @@ custom-pattern-regular-expression-generator-caps: 'Regular expression generator'
copilot-secret-scanning: 'Copilot secret scanning'
generic-secret-detection: 'generic secret detection'
generic-secret-detection-caps: 'Generic secret detection'
+roi-calculator: 'ROI calculator'
+pricing-calculator: 'pricing calculator'
# Secret risk assessment call to action links. If changing the links below, also update the hard-coded link in /code-security/index.md
secret-risk-assessment-cta-link: '/code-security/securing-your-organization/understanding-your-organizations-exposure-to-leaked-secrets/viewing-the-secret-risk-assessment-report-for-your-organization#generating-an-initial-secret-risk-assessment'
diff --git a/package-lock.json b/package-lock.json
index 209899e532aa..19bb69d4f042 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -44,7 +44,6 @@
"flat": "^6.0.1",
"github-slugger": "^2.0.0",
"glob": "11.0.2",
- "got": "^14.4.7",
"hast-util-from-parse5": "^8.0.3",
"hast-util-to-string": "^3.0.1",
"hastscript": "^9.0.1",
@@ -3720,11 +3719,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@sec-ant/readable-stream": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
- "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="
- },
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -3886,6 +3880,7 @@
},
"node_modules/@szmarczak/http-timer": {
"version": "5.0.1",
+ "dev": true,
"license": "MIT",
"dependencies": {
"defer-to-connect": "^2.0.1"
@@ -4072,7 +4067,8 @@
"node_modules/@types/http-cache-semantics": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
- "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA=="
+ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
+ "dev": true
},
"node_modules/@types/http-errors": {
"version": "2.0.4",
@@ -5639,6 +5635,7 @@
},
"node_modules/cacheable-lookup": {
"version": "7.0.0",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=14.16"
@@ -6457,6 +6454,7 @@
},
"node_modules/decompress-response": {
"version": "6.0.0",
+ "dev": true,
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
@@ -6470,6 +6468,7 @@
},
"node_modules/decompress-response/node_modules/mimic-response": {
"version": "3.1.0",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -6529,6 +6528,7 @@
},
"node_modules/defer-to-connect": {
"version": "2.0.1",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -8749,102 +8749,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/got": {
- "version": "14.4.7",
- "resolved": "https://registry.npmjs.org/got/-/got-14.4.7.tgz",
- "integrity": "sha512-DI8zV1231tqiGzOiOzQWDhsBmncFW7oQDH6Zgy6pDPrqJuVZMtoSgPLLsBZQj8Jg4JFfwoOsDA8NGtLQLnIx2g==",
- "license": "MIT",
- "dependencies": {
- "@sindresorhus/is": "^7.0.1",
- "@szmarczak/http-timer": "^5.0.1",
- "cacheable-lookup": "^7.0.0",
- "cacheable-request": "^12.0.1",
- "decompress-response": "^6.0.0",
- "form-data-encoder": "^4.0.2",
- "http2-wrapper": "^2.2.1",
- "lowercase-keys": "^3.0.0",
- "p-cancelable": "^4.0.1",
- "responselike": "^3.0.0",
- "type-fest": "^4.26.1"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/got?sponsor=1"
- }
- },
- "node_modules/got/node_modules/@sindresorhus/is": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.0.1.tgz",
- "integrity": "sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/is?sponsor=1"
- }
- },
- "node_modules/got/node_modules/cacheable-request": {
- "version": "12.0.1",
- "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-12.0.1.tgz",
- "integrity": "sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==",
- "dependencies": {
- "@types/http-cache-semantics": "^4.0.4",
- "get-stream": "^9.0.1",
- "http-cache-semantics": "^4.1.1",
- "keyv": "^4.5.4",
- "mimic-response": "^4.0.0",
- "normalize-url": "^8.0.1",
- "responselike": "^3.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/got/node_modules/form-data-encoder": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz",
- "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==",
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/got/node_modules/get-stream": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
- "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
- "dependencies": {
- "@sec-ant/readable-stream": "^0.4.1",
- "is-stream": "^4.0.1"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/got/node_modules/is-stream": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
- "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/got/node_modules/p-cancelable": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz",
- "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==",
- "engines": {
- "node": ">=14.16"
- }
- },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -9340,6 +9244,7 @@
},
"node_modules/http-cache-semantics": {
"version": "4.1.1",
+ "dev": true,
"license": "BSD-2-Clause"
},
"node_modules/http-errors": {
@@ -9431,6 +9336,7 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz",
"integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==",
+ "dev": true,
"dependencies": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.2.0"
@@ -9443,6 +9349,7 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "dev": true,
"engines": {
"node": ">=10"
},
@@ -10276,6 +10183,7 @@
},
"node_modules/json-buffer": {
"version": "3.0.1",
+ "dev": true,
"license": "MIT"
},
"node_modules/json-schema-compare": {
@@ -10396,6 +10304,7 @@
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
"dependencies": {
"json-buffer": "3.0.1"
}
@@ -10828,6 +10737,7 @@
},
"node_modules/lowercase-keys": {
"version": "3.0.0",
+ "dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
@@ -11960,6 +11870,7 @@
},
"node_modules/mimic-response": {
"version": "4.0.0",
+ "dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
@@ -12667,6 +12578,7 @@
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz",
"integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==",
+ "dev": true,
"engines": {
"node": ">=14.16"
},
@@ -13801,6 +13713,7 @@
},
"node_modules/resolve-alpn": {
"version": "1.2.1",
+ "dev": true,
"license": "MIT"
},
"node_modules/resolve-from": {
@@ -13823,6 +13736,7 @@
},
"node_modules/responselike": {
"version": "3.0.0",
+ "dev": true,
"license": "MIT",
"dependencies": {
"lowercase-keys": "^3.0.0"
diff --git a/package.json b/package.json
index 52b1236332d5..7b3607ec466c 100644
--- a/package.json
+++ b/package.json
@@ -191,7 +191,6 @@
"flat": "^6.0.1",
"github-slugger": "^2.0.0",
"glob": "11.0.2",
- "got": "^14.4.7",
"hast-util-from-parse5": "^8.0.3",
"hast-util-to-string": "^3.0.1",
"hastscript": "^9.0.1",
diff --git a/src/fixtures/fixtures/data/ui.yml b/src/fixtures/fixtures/data/ui.yml
index 8c37525f7cf4..ed30ddca4583 100644
--- a/src/fixtures/fixtures/data/ui.yml
+++ b/src/fixtures/fixtures/data/ui.yml
@@ -339,9 +339,9 @@ alerts:
cookbook_landing:
spotlight: Spotlight
- explore_articles: Explore {{ number }} prompt articles
+ explore_articles: Explore {{ number }} examples
reset_filters: Reset filters
- search_articles: Search articles
+ search_articles: Search examples
category: Category
complexity: Complexity
diff --git a/src/frame/lib/fetch-utils.ts b/src/frame/lib/fetch-utils.ts
index f90bc3f6c61f..5b3c57fd9a91 100644
--- a/src/frame/lib/fetch-utils.ts
+++ b/src/frame/lib/fetch-utils.ts
@@ -112,3 +112,24 @@ export async function fetchWithRetry(
throw lastError || new Error('Maximum retries exceeded')
}
+
+/**
+ * Create a streaming fetch request that returns a ReadableStream
+ * This replaces got.stream functionality
+ */
+export async function fetchStream(
+ url: string | URL,
+ init?: RequestInit,
+ options: FetchWithRetryOptions = {},
+): Promise {
+ const { timeout, throwHttpErrors = true } = options
+
+ const response = await fetchWithTimeout(url, init, timeout)
+
+ // Check for HTTP errors if throwHttpErrors is enabled
+ if (throwHttpErrors && !response.ok && response.status >= 400) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
+ }
+
+ return response
+}
diff --git a/src/search/lib/ai-search-proxy.ts b/src/search/lib/ai-search-proxy.ts
index 599ed87dd03f..75a4a42b4cbf 100644
--- a/src/search/lib/ai-search-proxy.ts
+++ b/src/search/lib/ai-search-proxy.ts
@@ -1,6 +1,6 @@
import { Response } from 'express'
import statsd from '@/observability/lib/statsd'
-import got from 'got'
+import { fetchStream } from '@/frame/lib/fetch-utils'
import { getHmacWithEpoch } from '@/search/lib/helpers/get-cse-copilot-auth'
import { getCSECopilotSource } from '@/search/lib/helpers/cse-copilot-docs-versions'
import type { ExtendedRequest } from '@/types'
@@ -56,56 +56,76 @@ export const aiSearchProxy = async (req: ExtendedRequest, res: Response) => {
stream: true,
}
+ let reader: ReadableStreamDefaultReader | null = null
+
try {
// TODO: We temporarily add ?ai_search=1 to use a new pattern in cgs-copilot production
- const stream = got.stream.post(`${process.env.CSE_COPILOT_ENDPOINT}/answers?ai_search=1`, {
- json: body,
- headers: {
- Authorization: getHmacWithEpoch(),
- 'Content-Type': 'application/json',
+ const response = await fetchStream(
+ `${process.env.CSE_COPILOT_ENDPOINT}/answers?ai_search=1`,
+ {
+ method: 'POST',
+ body: JSON.stringify(body),
+ headers: {
+ Authorization: getHmacWithEpoch(),
+ 'Content-Type': 'application/json',
+ },
},
- })
-
- // Listen for data events to count characters
- stream.on('data', (chunk: Buffer | string) => {
- // Ensure we have a string for proper character count
- const dataStr = typeof chunk === 'string' ? chunk : chunk.toString()
- totalChars += dataStr.length
- })
-
- // Handle the upstream response before piping
- stream.on('response', (upstreamResponse) => {
- if (upstreamResponse.statusCode !== 200) {
- const errorMessage = `Upstream server responded with status code ${upstreamResponse.statusCode}`
- console.error(errorMessage)
- statsd.increment('ai-search.stream_response_error', 1, diagnosticTags)
- res.status(upstreamResponse.statusCode).json({
- errors: [{ message: errorMessage }],
- upstreamStatus: upstreamResponse.statusCode,
- })
- stream.destroy()
- } else {
- // Set response headers
- res.setHeader('Content-Type', 'application/x-ndjson')
- res.flushHeaders()
-
- // Pipe the got stream directly to the response
- stream.pipe(res)
+ {
+ throwHttpErrors: false,
+ },
+ )
+
+ if (!response.ok) {
+ const errorMessage = `Upstream server responded with status code ${response.status}`
+ console.error(errorMessage)
+ statsd.increment('ai-search.stream_response_error', 1, diagnosticTags)
+ res.status(response.status).json({
+ errors: [{ message: errorMessage }],
+ upstreamStatus: response.status,
+ })
+ return
+ }
+
+ // Set response headers
+ res.setHeader('Content-Type', 'application/x-ndjson')
+ res.flushHeaders()
+
+ // Stream the response body
+ if (!response.body) {
+ res.status(500).json({ errors: [{ message: 'No response body' }] })
+ return
+ }
+
+ reader = response.body.getReader()
+ const decoder = new TextDecoder()
+
+ try {
+ while (true) {
+ const { done, value } = await reader.read()
+
+ if (done) {
+ break
+ }
+
+ // Decode chunk and count characters
+ const chunk = decoder.decode(value, { stream: true })
+ totalChars += chunk.length
+
+ // Write chunk to response
+ res.write(chunk)
}
- })
- // Handle stream errors
- stream.on('error', (error: any) => {
- console.error('Error streaming from cse-copilot:', error)
+ // Calculate metrics on stream end
+ const totalResponseTime = Date.now() - startTime // in ms
+ const charPerMsRatio = totalResponseTime > 0 ? totalChars / totalResponseTime : 0 // chars per ms
- if (error?.code === 'ERR_NON_2XX_3XX_RESPONSE') {
- const upstreamStatus = error?.response?.statusCode || 500
- return res.status(upstreamStatus).json({
- errors: [{ message: 'Upstream server error' }],
- upstreamStatus,
- })
- }
+ statsd.gauge('ai-search.total_response_time', totalResponseTime, diagnosticTags)
+ statsd.gauge('ai-search.response_chars_per_ms', charPerMsRatio, diagnosticTags)
+ statsd.increment('ai-search.success_stream_end', 1, diagnosticTags)
+ res.end()
+ } catch (streamError) {
+ console.error('Error streaming from cse-copilot:', streamError)
statsd.increment('ai-search.stream_error', 1, diagnosticTags)
if (!res.headersSent) {
@@ -117,22 +137,20 @@ export const aiSearchProxy = async (req: ExtendedRequest, res: Response) => {
res.write(errorMessage)
res.end()
}
- })
-
- // Calculate metrics on stream end
- stream.on('end', () => {
- const totalResponseTime = Date.now() - startTime // in ms
- const charPerMsRatio = totalResponseTime > 0 ? totalChars / totalResponseTime : 0 // chars per ms
-
- statsd.gauge('ai-search.total_response_time', totalResponseTime, diagnosticTags)
- statsd.gauge('ai-search.response_chars_per_ms', charPerMsRatio, diagnosticTags)
-
- statsd.increment('ai-search.success_stream_end', 1, diagnosticTags)
- res.end()
- })
+ } finally {
+ if (reader) {
+ reader.releaseLock()
+ reader = null
+ }
+ }
} catch (error) {
statsd.increment('ai-search.route_error', 1, diagnosticTags)
console.error('Error posting /answers to cse-copilot:', error)
res.status(500).json({ errors: [{ message: 'Internal server error' }] })
+ } finally {
+ // Ensure reader lock is always released
+ if (reader) {
+ reader.releaseLock()
+ }
}
}
diff --git a/src/search/middleware/ai-search-local-proxy.ts b/src/search/middleware/ai-search-local-proxy.ts
index 327ffa991072..f3ff0e069bae 100644
--- a/src/search/middleware/ai-search-local-proxy.ts
+++ b/src/search/middleware/ai-search-local-proxy.ts
@@ -1,8 +1,8 @@
// When in local development we want to proxy to the ai-search route at docs.github.com
import { Router, Request, Response, NextFunction } from 'express'
-import got from 'got'
-import { pipeline } from 'node:stream'
+import { fetchStream } from '@/frame/lib/fetch-utils'
+import { pipeline, Readable } from 'node:stream'
const router = Router()
@@ -18,12 +18,13 @@ const hopByHop = new Set([
])
function filterRequestHeaders(src: Request['headers']) {
- const out: Record = {}
+ const out: Record = {}
for (const [key, value] of Object.entries(src)) {
if (!value) continue
const k = key.toLowerCase()
if (hopByHop.has(k) || k === 'cookie' || k === 'host') continue
- out[key] = value
+ // Convert array values to string
+ out[key] = Array.isArray(value) ? value[0] : value
}
out['accept'] = 'application/x-ndjson'
out['content-type'] = 'application/json'
@@ -31,39 +32,74 @@ function filterRequestHeaders(src: Request['headers']) {
}
router.post('/ai-search/v1', async (req: Request, res: Response, next: NextFunction) => {
+ let reader: ReadableStreamDefaultReader | null = null
+
try {
- const upstream = got.stream.post('https://docs.github.com/api/ai-search/v1', {
- headers: filterRequestHeaders(req.headers),
- body: JSON.stringify(req.body ?? {}),
- decompress: false,
- throwHttpErrors: false,
- retry: { limit: 0 },
- })
+ const response = await fetchStream(
+ 'https://docs.github.com/api/ai-search/v1',
+ {
+ method: 'POST',
+ headers: filterRequestHeaders(req.headers),
+ body: JSON.stringify(req.body ?? {}),
+ },
+ {
+ throwHttpErrors: false,
+ },
+ )
- upstream.on('response', (uRes) => {
- res.status(uRes.statusCode || 500)
+ // Set status code
+ res.status(response.status || 500)
- for (const [k, v] of Object.entries(uRes.headers)) {
- if (!v) continue
- const key = k.toLowerCase()
- // Never forward hop-by-hop; got already handles chunked → strip content-length
- if (hopByHop.has(key) || key === 'content-length') continue
- res.setHeader(k, v as string)
- }
- res.flushHeaders?.()
+ // Forward response headers
+ for (const [k, v] of response.headers.entries()) {
+ if (!v) continue
+ const key = k.toLowerCase()
+ // Never forward hop-by-hop; fetch already handles chunked → strip content-length
+ if (hopByHop.has(key) || key === 'content-length') continue
+ res.setHeader(k, v)
+ }
+ res.flushHeaders?.()
+
+ // Convert fetch ReadableStream to Node.js Readable stream for pipeline
+ if (!response.body) {
+ if (!res.headersSent) res.status(502).end('Bad Gateway')
+ return
+ }
+
+ reader = response.body.getReader()
+ const nodeStream = new Readable({
+ async read() {
+ try {
+ const { done, value } = await reader!.read()
+ if (done) {
+ this.push(null)
+ } else {
+ this.push(Buffer.from(value))
+ }
+ } catch (err) {
+ this.destroy(err as Error)
+ }
+ },
})
- pipeline(upstream, res, (err) => {
+ pipeline(nodeStream, res, (err) => {
if (err) {
console.error('[ai-search proxy] pipeline error:', err)
if (!res.headersSent) res.status(502).end('Bad Gateway')
}
+ if (reader) {
+ reader.releaseLock()
+ reader = null
+ }
})
-
- upstream.on('error', (err) => console.error('[ai-search proxy] upstream error:', err))
} catch (err) {
console.error('[ai-search proxy] request failed:', err)
next(err)
+ } finally {
+ // Ensure reader lock is always released
+ if (reader) {
+ reader.releaseLock()
+ }
}
})
diff --git a/src/search/tests/ai-search-local-proxy.ts b/src/search/tests/ai-search-local-proxy.ts
new file mode 100644
index 000000000000..e301fb07d255
--- /dev/null
+++ b/src/search/tests/ai-search-local-proxy.ts
@@ -0,0 +1,106 @@
+import { expect, test, describe } from 'vitest'
+
+import { get, post } from '@/tests/helpers/e2etest'
+
+describe('AI Search Local Proxy Middleware', () => {
+ test('should successfully proxy to docs.github.com when CSE_COPILOT_ENDPOINT is not localhost', async () => {
+ // In local development, the middleware should proxy to docs.github.com
+ // This test verifies the middleware handles the proxy correctly
+
+ // We can't easily test the actual proxying without setting up a mock for docs.github.com
+ // But we can test that the route exists and handles requests
+ const body = { query: 'test query', version: 'dotcom' }
+ const response = await post('/api/ai-search/v1', {
+ body: JSON.stringify(body),
+ headers: { 'Content-Type': 'application/json' },
+ })
+
+ // The response should either succeed or fail gracefully
+ // depending on whether docs.github.com is reachable
+ expect([200, 500, 502, 503, 504]).toContain(response.statusCode)
+ })
+
+ test('should handle request body correctly in proxy', async () => {
+ const testBody = {
+ query: 'test query with special chars: éñ中文',
+ version: 'dotcom',
+ nested: { key: 'value' },
+ array: [1, 2, 3],
+ }
+
+ const response = await post('/api/ai-search/v1', {
+ body: JSON.stringify(testBody),
+ headers: { 'Content-Type': 'application/json' },
+ })
+
+ // Should handle complex request bodies without crashing
+ expect([200, 500, 502, 503, 504]).toContain(response.statusCode)
+ })
+
+ test('should handle empty request body in proxy', async () => {
+ const response = await post('/api/ai-search/v1', {
+ body: JSON.stringify({}),
+ headers: { 'Content-Type': 'application/json' },
+ })
+
+ // Should handle empty body gracefully
+ expect([200, 400, 500, 502, 503, 504]).toContain(response.statusCode)
+ })
+
+ test('should handle malformed JSON in proxy', async () => {
+ const response = await post('/api/ai-search/v1', {
+ body: '{ invalid json }',
+ headers: { 'Content-Type': 'application/json' },
+ })
+
+ // Should handle malformed JSON gracefully
+ expect([400, 500]).toContain(response.statusCode)
+ })
+
+ test('should preserve important headers in proxy', async () => {
+ const response = await post('/api/ai-search/v1', {
+ body: JSON.stringify({ query: 'test', version: 'dotcom' }),
+ headers: {
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'test-agent/1.0',
+ 'Accept-Language': 'en-US,en;q=0.9',
+ },
+ })
+
+ // Headers should be processed without causing errors
+ expect([200, 500, 502, 503, 504]).toContain(response.statusCode)
+ })
+
+ test('should filter hop-by-hop headers correctly', async () => {
+ const response = await post('/api/ai-search/v1', {
+ body: JSON.stringify({ query: 'test', version: 'dotcom' }),
+ headers: {
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'test-agent',
+ 'X-Custom-Header': 'test-value',
+ // Note: Connection, Transfer-Encoding, Upgrade are forbidden headers in fetch
+ // We test with other headers that should be filtered by the middleware
+ },
+ })
+
+ // Should succeed despite hop-by-hop headers being present
+ expect([200, 500, 502, 503, 504]).toContain(response.statusCode)
+ })
+
+ test('should handle various request methods correctly', async () => {
+ // Test that only POST is supported
+ const getResponse = await get('/api/ai-search/v1')
+ expect([404, 405]).toContain(getResponse.statusCode)
+ })
+
+ test('should handle large request bodies in proxy', async () => {
+ const largeQuery = 'test query '.repeat(1000) // Create a large query string
+ const response = await post('/api/ai-search/v1', {
+ body: JSON.stringify({ query: largeQuery, version: 'dotcom' }),
+ headers: { 'Content-Type': 'application/json' },
+ })
+
+ // Should handle large bodies without crashing
+ expect([200, 413, 500, 502, 503, 504]).toContain(response.statusCode)
+ })
+})
diff --git a/src/search/tests/api-ai-search.ts b/src/search/tests/api-ai-search.ts
index 7512ca23550c..abf9dddcd545 100644
--- a/src/search/tests/api-ai-search.ts
+++ b/src/search/tests/api-ai-search.ts
@@ -123,4 +123,72 @@ describe('AI Search Routes', () => {
{ message: `Missing required key 'query' in request body` },
])
})
+
+ test('should handle streaming response correctly', async () => {
+ // This test verifies the streaming response processing works
+ const body = { query: 'test streaming query', version: 'dotcom' }
+ const response = await fetch('http://localhost:4000/api/ai-search/v1', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(body),
+ })
+
+ expect(response.ok).toBe(true)
+ expect(response.headers.get('content-type')).toBe('application/x-ndjson')
+
+ // Verify we can read the stream without errors
+ if (response.body) {
+ const reader = response.body.getReader()
+ const decoder = new TextDecoder()
+ let chunks = []
+
+ try {
+ while (true) {
+ const { done, value } = await reader.read()
+ if (done) break
+ chunks.push(decoder.decode(value, { stream: true }))
+ }
+ expect(chunks.length).toBeGreaterThan(0)
+ } finally {
+ reader.releaseLock()
+ }
+ }
+ })
+
+ test('should handle invalid version parameter', async () => {
+ const body = { query: 'test query', version: 'invalid-version' }
+ const response = await post('/api/ai-search/v1', {
+ body: JSON.stringify(body),
+ headers: { 'Content-Type': 'application/json' },
+ })
+
+ const responseBody = JSON.parse(response.body)
+
+ expect(response.statusCode).toBe(400)
+ expect(responseBody.errors).toBeDefined()
+ expect(responseBody.errors[0].message).toContain("Invalid 'version' in request body")
+ })
+
+ test('should handle non-string query parameter', async () => {
+ const body = { query: 123, version: 'dotcom' }
+ const response = await post('/api/ai-search/v1', {
+ body: JSON.stringify(body),
+ headers: { 'Content-Type': 'application/json' },
+ })
+
+ const responseBody = JSON.parse(response.body)
+
+ expect(response.statusCode).toBe(400)
+ expect(responseBody.errors).toBeDefined()
+ expect(responseBody.errors[0].message).toBe("Invalid 'query' in request body. Must be a string")
+ })
+
+ test('should handle malformed JSON in request body', async () => {
+ const response = await post('/api/ai-search/v1', {
+ body: '{ invalid json }',
+ headers: { 'Content-Type': 'application/json' },
+ })
+
+ expect(response.statusCode).toBe(400)
+ })
})