diff --git a/content/billing/tutorials/automate-usage-reporting.md b/content/billing/tutorials/automate-usage-reporting.md index 5889edc2a6a1..3d0b60034587 100644 --- a/content/billing/tutorials/automate-usage-reporting.md +++ b/content/billing/tutorials/automate-usage-reporting.md @@ -72,7 +72,7 @@ You must authenticate your request to this endpoint. ```bash curl -L \ -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" \ https://api.github.com/enterprises/ENTERPRISE/settings/billing/usage/summary ``` @@ -82,7 +82,7 @@ Replace `ENTERPRISE` with the enterprise slug and set the `GITHUB_TOKEN` environ ```bash gh api \ - -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" \ /enterprises/ENTERPRISE/settings/billing/usage/summary ``` diff --git a/content/billing/tutorials/control-costs-at-scale.md b/content/billing/tutorials/control-costs-at-scale.md index 9d4b1085460c..91112ccce4c6 100644 --- a/content/billing/tutorials/control-costs-at-scale.md +++ b/content/billing/tutorials/control-costs-at-scale.md @@ -132,7 +132,7 @@ In your terminal, run the following command, replacing `ENTERPRISE` with the slu ```shell copy gh api \ -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" \ /enterprises/ENTERPRISE/settings/billing/cost-centers ``` @@ -174,7 +174,7 @@ In your terminal, run the following command, replacing `ENTERPRISE` and `NAME` w gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" \ /enterprises/ENTERPRISE/settings/billing/cost-centers \ -f 'name=NAME' ``` @@ -200,7 +200,7 @@ In your terminal, run the following command, replacing `COST_CENTER_ID` with the gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" \ /enterprises/ENTERPRISE/settings/billing/cost-centers/COST_CENTER_ID/resource \ --input - <<< '{ "users": [ @@ -244,7 +244,7 @@ In your terminal, run the following command, replacing `ENTERPRISE`, `COST_CENTE gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" \ /enterprises/ENTERPRISE/settings/billing/budgets \ -f budget_type='SkuPricing' \ -f budget_product_sku='copilot_premium_request' \ diff --git a/content/code-security/how-tos/secure-at-scale/configure-enterprise-security/configure-specific-tools/setting-dependabot-to-run-on-github-hosted-runners-using-vnet.md b/content/code-security/how-tos/secure-at-scale/configure-enterprise-security/configure-specific-tools/setting-dependabot-to-run-on-github-hosted-runners-using-vnet.md index 2373684d6794..290286bef037 100644 --- a/content/code-security/how-tos/secure-at-scale/configure-enterprise-security/configure-specific-tools/setting-dependabot-to-run-on-github-hosted-runners-using-vnet.md +++ b/content/code-security/how-tos/secure-at-scale/configure-enterprise-security/configure-specific-tools/setting-dependabot-to-run-on-github-hosted-runners-using-vnet.md @@ -93,7 +93,7 @@ If your Azure VNET environment is configured with a firewall with an IP allowlis curl -L \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer YOUR-TOKEN" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" \ https://api.github.com/meta ``` diff --git a/content/copilot/concepts/agents/copilot-cli/about-copilot-cli.md b/content/copilot/concepts/agents/copilot-cli/about-copilot-cli.md index 35229e38cc52..edff9d8c0931 100644 --- a/content/copilot/concepts/agents/copilot-cli/about-copilot-cli.md +++ b/content/copilot/concepts/agents/copilot-cli/about-copilot-cli.md @@ -307,6 +307,21 @@ You can change the model used by {% data variables.copilot.copilot_cli %} by usi Each time you submit a prompt to {% data variables.product.prodname_copilot_short %} in {% data variables.copilot.copilot_cli_short %}'s interactive interface, and each time you use {% data variables.copilot.copilot_cli_short %} programmatically, your monthly quota of {% data variables.product.prodname_copilot_short %} premium requests is reduced by one, multiplied by the multiplier shown in parentheses in the model list. For example, `Claude Sonnet 4.5 (1x)` indicates that with this model each time you submit a prompt your quota of premium requests is reduced by one. For information about premium requests, see [AUTOTITLE](/copilot/concepts/billing/copilot-requests). +### Using your own model provider + +You can configure {% data variables.copilot.copilot_cli_short %} to use your own model provider instead of {% data variables.product.github %}-hosted models. This lets you connect to an OpenAI-compatible endpoint, Azure OpenAI, or Anthropic, including locally running models such as Ollama. You configure your model provider using environment variables. + +| Environment variable | Description | +|---|---| +| `COPILOT_PROVIDER_BASE_URL` | The base URL of your model provider's API endpoint. | +| `COPILOT_PROVIDER_TYPE` | The provider type: `openai` (default), `azure`, or `anthropic`. The `openai` type works with any OpenAI-compatible endpoint, including Ollama and vLLM. | +| `COPILOT_PROVIDER_API_KEY` | Your API key for authenticating with the provider. Not required for providers that don't use authentication, such as a local Ollama instance. | +| `COPILOT_MODEL` | The model to use (required when using a custom provider). You can also set this with the `--model` command-line option. | + +Models used with {% data variables.copilot.copilot_cli_short %} must support **tool calling** (function calling) and **streaming**. If the model does not support these capabilities, {% data variables.copilot.copilot_cli_short %} will return an error. For best results, the model should have a context window of at least 128k tokens. + +For details on how to configure your model provider, run `copilot help providers` in your terminal. + ## Use {% data variables.copilot.copilot_cli_short %} via ACP ACP (the Agent Client Protocol) is an open standard for interacting with AI agents. It allows you to use {% data variables.copilot.copilot_cli_short %} as an agent in any third-party tools, IDEs, or automation systems that support this protocol. diff --git a/content/copilot/how-tos/copilot-cli/administer-copilot-cli-for-your-enterprise.md b/content/copilot/how-tos/copilot-cli/administer-copilot-cli-for-your-enterprise.md index 8823a8690485..522f107d4850 100644 --- a/content/copilot/how-tos/copilot-cli/administer-copilot-cli-for-your-enterprise.md +++ b/content/copilot/how-tos/copilot-cli/administer-copilot-cli-for-your-enterprise.md @@ -55,6 +55,7 @@ All other controls do **not** affect {% data variables.copilot.copilot_cli_short * **Model Context Protocol (MCP) server policies**: Enterprise policies that control whether MCP servers can be used, or which MCP registry servers are allowed * **IDE-specific policies**: Policies configured for specific IDEs or editor extensions * **Content exclusions**: File path-based content exclusions +* **User-configured model providers (BYOK)**: Users can configure {% data variables.copilot.copilot_cli_short %} to use their own model providers via environment variables. This is configured at the _user level_ and cannot be controlled by enterprise policies. ## Why can't my developers access {% data variables.copilot.copilot_cli_short %}? diff --git a/content/copilot/how-tos/copilot-cli/cli-best-practices.md b/content/copilot/how-tos/copilot-cli/cli-best-practices.md index 14ef0099217a..e8181c46b0df 100644 --- a/content/copilot/how-tos/copilot-cli/cli-best-practices.md +++ b/content/copilot/how-tos/copilot-cli/cli-best-practices.md @@ -104,6 +104,22 @@ Use `/model` to choose from available models based on your task complexity: You can switch models mid-session with `/model` as task complexity changes. +If your organization or enterprise has configured custom models using their own LLM provider API keys, those models also appear in `/model` at the bottom of the list. + +### Use your own model provider + +You can configure {% data variables.copilot.copilot_cli_short %} to use your own model provider instead of {% data variables.product.github %}-hosted models. Run `copilot help providers` for full setup instructions. + +**Key considerations:** + +* Your model must support **tool calling** (function calling) and **streaming**. {% data variables.copilot.copilot_cli_short %} returns an error if either capability is missing. +* For best results, use a model with a context window of at least 128k tokens. +* Built-in sub-agents (`/review`, `/task`, explore, `/fleet`) automatically inherit your provider configuration. +* Premium request cost estimates are hidden when using your own provider. Token usage (input, output, and cache counts) is still displayed. +* `/delegate` only works if you are also signed in to {% data variables.product.github %}. It transfers the session to {% data variables.product.github %}'s server-side {% data variables.product.prodname_copilot_short %}, not your provider. + +See [Using your own model provider](/copilot/concepts/agents/copilot-cli/about-copilot-cli#using-your-own-model-provider). + ## 2. Plan before you code ### Plan mode diff --git a/content/copilot/how-tos/copilot-cli/customize-copilot/index.md b/content/copilot/how-tos/copilot-cli/customize-copilot/index.md index 4111b25508f3..d90f5340ff14 100644 --- a/content/copilot/how-tos/copilot-cli/customize-copilot/index.md +++ b/content/copilot/how-tos/copilot-cli/customize-copilot/index.md @@ -12,6 +12,7 @@ children: - /create-skills - /add-mcp-servers - /create-custom-agents-for-cli + - /use-byok-models - /plugins-finding-installing - /plugins-creating - /plugins-marketplace diff --git a/content/copilot/how-tos/copilot-cli/customize-copilot/use-byok-models.md b/content/copilot/how-tos/copilot-cli/customize-copilot/use-byok-models.md new file mode 100644 index 000000000000..aa989e9f3214 --- /dev/null +++ b/content/copilot/how-tos/copilot-cli/customize-copilot/use-byok-models.md @@ -0,0 +1,121 @@ +--- +title: Using your own LLM models in GitHub Copilot CLI +shortTitle: Use your own model provider +intro: 'Use a model from an external provider of your choice in {% data variables.product.prodname_copilot_short %} by supplying your own API key.' +allowTitleToDifferFromFilename: true +versions: + feature: copilot +contentType: how-tos +category: + - Configure Copilot + - Configure Copilot CLI +--- + +You can configure {% data variables.copilot.copilot_cli_short %} to use your own LLM provider, also called BYOK (Bring Your Own Key), instead of {% data variables.product.github %}-hosted models. This lets you connect to OpenAI-compatible endpoints, Azure OpenAI, or Anthropic, including locally running models such as Ollama. + +## Prerequisites + +* {% data variables.copilot.copilot_cli_short %} is installed. See [AUTOTITLE](/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli). +* You have an API key from a supported LLM provider, or you have a local model running (such as Ollama). + +## Supported providers + +{% data variables.copilot.copilot_cli_short %} supports three provider types: + +| Provider type | Compatible services | +|---|---| +| `openai` | OpenAI, Ollama, vLLM, Foundry Local, and any other OpenAI Chat Completions API-compatible endpoint. This is the default provider type. | +| `azure` | Azure OpenAI Service. | +| `anthropic` | Anthropic (Claude models). | + +For additional examples, run `copilot help providers` in your terminal. + +## Model requirements + +Models must support **tool calling** (also called function calling) and **streaming**. If a model does not support either capability, {% data variables.copilot.copilot_cli_short %} returns an error. For best results, use a model with a context window of at least 128k tokens. + +## Configuring your provider + +You configure your model provider by setting environment variables before starting {% data variables.copilot.copilot_cli_short %}. + +| Environment variable | Required | Description | +|---|---|---| +| `COPILOT_PROVIDER_BASE_URL` | Yes | The base URL of your model provider's API endpoint. | +| `COPILOT_PROVIDER_TYPE` | No | The provider type: `openai` (default), `azure`, or `anthropic`. | +| `COPILOT_PROVIDER_API_KEY` | No | Your API key for the provider. Not required for providers that do not use authentication, such as a local Ollama instance. | +| `COPILOT_MODEL` | Yes | The model identifier to use. You can also set this with the `--model` command-line flag. | + +## Connecting to an OpenAI-compatible endpoint + +Use the following steps if you are connecting to OpenAI, Ollama, vLLM, Foundry Local, or any other endpoint that is compatible with the OpenAI Chat Completions API. + +1. Set environment variables for your provider. For example, for a local Ollama instance: + + ```shell + export COPILOT_PROVIDER_BASE_URL=http://localhost:11434 + export COPILOT_MODEL=YOUR-MODEL-NAME + ``` + + Replace `YOUR-MODEL-NAME` with the name of the model you have pulled in Ollama (for example, `llama3.2`). + +1. For a remote OpenAI endpoint, also set your API key. + + ```shell + export COPILOT_PROVIDER_BASE_URL=https://api.openai.com + export COPILOT_PROVIDER_API_KEY=YOUR-OPENAI-API-KEY + export COPILOT_MODEL=YOUR-MODEL-NAME + ``` + + Replace `YOUR-OPENAI-API-KEY` with your OpenAI API key and `YOUR-MODEL-NAME` with the model you want to use (for example, `gpt-4o`). + +{% data reusables.copilot.copilot-cli.start-cli %} + +## Connecting to Azure OpenAI + +1. Set the environment variables for Azure OpenAI. + + ```shell + export COPILOT_PROVIDER_BASE_URL=https://YOUR-RESOURCE-NAME.openai.azure.com/openai/deployments/YOUR-DEPLOYMENT-NAME + export COPILOT_PROVIDER_TYPE=azure + export COPILOT_PROVIDER_API_KEY=YOUR-AZURE-API-KEY + export COPILOT_MODEL=YOUR-DEPLOYMENT-NAME + ``` + + Replace the following placeholders: + + * `YOUR-RESOURCE-NAME`: your Azure OpenAI resource name + * `YOUR-DEPLOYMENT-NAME`: the name of your model deployment + * `YOUR-AZURE-API-KEY`: your Azure OpenAI API key + +{% data reusables.copilot.copilot-cli.start-cli %} + +## Connecting to Anthropic + +1. Set the environment variables for Anthropic: + + ```shell + export COPILOT_PROVIDER_TYPE=anthropic + export COPILOT_PROVIDER_API_KEY=YOUR-ANTHROPIC-API-KEY + export COPILOT_MODEL=YOUR-MODEL-NAME + ``` + + Replace `YOUR-ANTHROPIC-API-KEY` with your Anthropic API key and YOUR-MODEL-NAME with the Claude model you want to use (for example, `claude-opus-4-5`). + +{% data reusables.copilot.copilot-cli.start-cli %} + +## Running in offline mode + +You can run {% data variables.copilot.copilot_cli_short %} in offline mode to prevent it from contacting {% data variables.product.github %}'s servers. This is designed for isolated environments where the CLI should communicate only with your local or on-premises model provider. + +> [!IMPORTANT] +> Offline mode only guarantees full network isolation if your provider is also local or within the same isolated environment. If `COPILOT_PROVIDER_BASE_URL` points to a remote endpoint, your prompts and code context are still sent over the network to that provider. + +1. Configure your provider environment variables as described in Configuring your provider. + +1. Set the offline mode environment variable: + + ```shell + export COPILOT_OFFLINE=true + ``` + +{% data reusables.copilot.copilot-cli.start-cli %} diff --git a/content/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli.md b/content/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli.md index afe59b6ac872..e053498ea024 100644 --- a/content/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli.md +++ b/content/copilot/how-tos/copilot-cli/set-up-copilot-cli/authenticate-copilot-cli.md @@ -12,7 +12,11 @@ category: ## About authentication -{% data variables.copilot.copilot_cli %} supports three authentication methods. The method you use depends on whether you are working interactively or in an automated environment. +If you use your own LLM provider API keys (BYOK), {% data variables.product.github %} authentication is not required. + +Authentication is required for any other {% data variables.copilot.copilot_cli %} usage. + +When authentication is required, {% data variables.copilot.copilot_cli_short %} supports three methods. The method you use depends on whether you are working interactively or in an automated environment. * **OAuth device flow**: The default and recommended method for interactive use. When you run `/login` in {% data variables.copilot.copilot_cli_short %}, the CLI generates a one-time code and directs you to authenticate in your browser. This is the simplest way to authenticate. * **Environment variables**: Recommended for CI/CD pipelines, containers, and non-interactive environments. You set a supported token as an environment variable (`COPILOT_GITHUB_TOKEN`, `GH_TOKEN`, or `GITHUB_TOKEN`), and the CLI uses it automatically without prompting. @@ -20,6 +24,28 @@ category: Once authenticated, {% data variables.copilot.copilot_cli_short %} remembers your login and automatically uses the token for all {% data variables.product.prodname_copilot_short %} API requests. You can log in with multiple accounts, and the CLI will remember the last-used account. Token lifetime and expiration depend on how the token was created on your account or organization settings. +## Unauthenticated use + +If you configure {% data variables.copilot.copilot_cli_short %} to use your own LLM provider API keys (BYOK), {% data variables.product.github %} authentication is **not required**. {% data variables.copilot.copilot_cli_short %} can connect directly to your configured provider without a {% data variables.product.github %} account or token. + +However, without {% data variables.product.github %} authentication, the following features are **not available**: + +* `/delegate`: Requires {% data variables.copilot.copilot_coding_agent %}, which runs on {% data variables.product.github %}'s servers +* {% data variables.product.github %} MCP server: Requires authentication to access {% data variables.product.github %} APIs +* {% data variables.product.github %} Code Search: Requires authentication to query {% data variables.product.github %}'s search index + +You can combine BYOK with {% data variables.product.github %} authentication to get the best of both: your preferred model for AI responses, plus access to {% data variables.product.github %}-hosted features like `/delegate` and code search. + +### Offline mode + + If you set the `COPILOT_OFFLINE` environment variable to `true`, {% data variables.copilot.copilot_cli_short %} runs without contacting {% data variables.product.github %}'s servers. In offline mode: + +* No {% data variables.product.github %} authentication is attempted. +* The CLI only makes network requests to your configured BYOK provider. +* Telemetry is fully disabled. + +Offline mode is **only fully air-gapped** if your BYOK provider is local or otherwise within the same isolated environment (for example, a model running on-premises with no external network access). If `COPILOT_PROVIDER_BASE_URL` points to a remote or internet-accessible endpoint, prompts and code context will still be sent over the network to that provider. Without offline mode, even when using BYOK without {% data variables.product.github %} authentication, telemetry is still sent normally. + ### Supported token types | Token type | Prefix | Supported | Notes | @@ -50,7 +76,8 @@ When you run a command, {% data variables.copilot.copilot_cli_short %} checks fo 1. GitHub CLI (`gh auth token`) fallback > [!NOTE] -> An environment variable silently overrides a stored OAuth token. If you set `GH_TOKEN` for another tool, the CLI uses that token instead of the OAuth token from `copilot login`. To avoid unexpected behavior, unset environment variables you do not intend the CLI to use. +> * An environment variable silently overrides a stored OAuth token. If you set `GH_TOKEN` for another tool, the CLI uses that token instead of the OAuth token from `copilot login`. To avoid unexpected behavior, unset environment variables you do not intend the CLI to use. +> * When you configure BYOK provider environment variables (for example, `COPILOT_PROVIDER_BASE_URL`, `COPILOT_PROVIDER_API_KEY`), {% data variables.copilot.copilot_cli_short %} uses these for AI model requests regardless of your {% data variables.product.github %} authentication status. {% data variables.product.github %} tokens are only needed for {% data variables.product.github %}-hosted features. ## Authenticating with OAuth diff --git a/content/copilot/responsible-use/copilot-cli.md b/content/copilot/responsible-use/copilot-cli.md index e8e3ba3a49f7..b9fc5deff7c7 100644 --- a/content/copilot/responsible-use/copilot-cli.md +++ b/content/copilot/responsible-use/copilot-cli.md @@ -107,6 +107,30 @@ You can grant {% data variables.copilot.copilot_cli_short %} specific permission For more information about security practices while using {% data variables.copilot.copilot_cli %}, see "Security considerations" in [AUTOTITLE](/copilot/concepts/agents/about-copilot-cli#security-considerations). +## Data handling when using your own model provider + +When you configure {% data variables.copilot.copilot_cli_short %} to use your own model provider, your prompts, code context, and generated responses are sent directly to the provider you configure. They are not routed through {% data variables.product.github %}. You are responsible for reviewing and complying with the terms of service and data handling policies of your chosen provider. + +### Telemetry + +When you use your own model provider without offline mode, {% data variables.copilot.copilot_cli_short %} continues to send telemetry to {% data variables.product.github %} as usual. This telemetry does not include your prompts or code, but it does include usage metadata. + +If you enable offline mode by setting the `COPILOT_OFFLINE` environment variable to `true`, all telemetry is disabled. In offline mode, {% data variables.copilot.copilot_cli_short %} only makes network requests to your configured model provider. + +### Authentication and feature availability + +{% data variables.product.github %} authentication is not required when using your own model provider (BYOK). Without {% data variables.product.github %} authentication, the following features are unavailable: + +* `/delegate`, which hands off the session to {% data variables.product.github %}'s server-side {% data variables.product.prodname_copilot_short %} +* The {% data variables.product.github %} MCP server +* {% data variables.product.github %} Code Search + +In offline mode, web-based tools such as `web_fetch` and {% data variables.product.github %} Code Search are also disabled. + +### No fallback to {% data variables.product.github %}-hosted models + +If your model provider configuration is invalid, {% data variables.copilot.copilot_cli_short %} exits with an error. It does not fall back to {% data variables.product.github %}-hosted models. Common failures, such as connection refused, authentication errors, model not found, and timeouts, produce user-friendly messages with actionable guidance. + ## Limitations of {% data variables.copilot.copilot_cli %} Depending on factors such as your codebase and input data, you may experience different levels of performance when using {% data variables.copilot.copilot_cli %}. The following information is designed to help you understand system limitations and key concepts about performance as they apply to {% data variables.copilot.copilot_cli %}. diff --git a/content/github-models/quickstart.md b/content/github-models/quickstart.md index 192320ab13c1..a03d90f734b0 100644 --- a/content/github-models/quickstart.md +++ b/content/github-models/quickstart.md @@ -45,7 +45,7 @@ To call models programmatically, you’ll need: -X POST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer YOUR_GITHUB_PAT" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" \ -H "Content-Type: application/json" \ https://models.github.ai/inference/chat/completions \ -d '{"model":"openai/gpt-4.1","messages":[{"role":"user","content":"What is the capital of France?"}]}' diff --git a/content/rest/about-the-rest-api/api-versions.md b/content/rest/about-the-rest-api/api-versions.md index 04cadfe83b49..519be84db7ca 100644 --- a/content/rest/about-the-rest-api/api-versions.md +++ b/content/rest/about-the-rest-api/api-versions.md @@ -34,7 +34,7 @@ You should use the `X-GitHub-Api-Version` header to specify an API version. For curl {% data reusables.rest-api.version-header %} https://api.github.com/zen ``` -Requests without the `X-GitHub-Api-Version` header will default to use the `2022-11-28` version. +Requests without the `X-GitHub-Api-Version` header will default to use the `{{ defaultRestApiVersion }}` version. If you specify an API version that is no longer supported, you will receive a `400` error. diff --git a/content/rest/using-the-rest-api/getting-started-with-the-rest-api.md b/content/rest/using-the-rest-api/getting-started-with-the-rest-api.md index 90c3f3e4d699..8ac3854cedc2 100644 --- a/content/rest/using-the-rest-api/getting-started-with-the-rest-api.md +++ b/content/rest/using-the-rest-api/getting-started-with-the-rest-api.md @@ -256,7 +256,7 @@ The following example request uses the ["Get Octocat" endpoint](/rest/meta/meta# ```shell copy gh api --method GET /octocat \ --header 'Accept: application/vnd.github+json' \ ---header "X-GitHub-Api-Version: 2022-11-28" +--header "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" ``` #### Example request using query parameters @@ -275,7 +275,7 @@ The following example uses the ["Create an issue" endpoint](/rest/issues/issues# ```shell copy gh api --method POST /repos/{% ifversion ghes %}REPO-OWNER/REPO-NAME{% else %}octocat/Spoon-Knife{% endif %}/issues \ --header "Accept: application/vnd.github+json" \ ---header "X-GitHub-Api-Version: 2022-11-28" \ +--header "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" \ -f title='Created with the REST API' \ -f body='This is a test issue created by the REST API' \ ``` @@ -332,7 +332,7 @@ The following example request uses the ["Get Octocat" endpoint](/rest/meta/meta# curl --request GET \ --url "https://api.github.com/octocat" \ --header "Accept: application/vnd.github+json" \ ---header "X-GitHub-Api-Version: 2022-11-28" +--header "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" ``` #### Example request using query parameters @@ -343,7 +343,7 @@ The ["List public events" endpoint](/rest/activity/events#list-public-events) re curl --request GET \ --url "{% data variables.product.rest_url %}/events?per_page=2&page=1" \ --header "Accept: application/vnd.github+json" \ ---header "X-GitHub-Api-Version: 2022-11-28" \ +--header "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" \ https://api.github.com/events ``` @@ -359,7 +359,7 @@ curl \ --request POST \ --url "{% data variables.product.rest_url %}/repos/{% ifversion ghes %}REPO-OWNER/REPO-NAME{% else %}octocat/Spoon-Knife{% endif %}/issues" \ --header "Accept: application/vnd.github+json" \ ---header "X-GitHub-Api-Version: 2022-11-28" \ +--header "X-GitHub-Api-Version: {{ defaultRestApiVersion }}" \ --header "Authorization: Bearer YOUR-TOKEN" \ --data '{ "title": "Created with the REST API", diff --git a/data/reusables/copilot/copilot-cli/start-cli.md b/data/reusables/copilot/copilot-cli/start-cli.md new file mode 100644 index 000000000000..b63a883b34d6 --- /dev/null +++ b/data/reusables/copilot/copilot-cli/start-cli.md @@ -0,0 +1,5 @@ +1. Start {% data variables.copilot.copilot_cli_short %}. + +```bash +copilot +``` diff --git a/data/variables/copilot.yml b/data/variables/copilot.yml index d2da278018e6..3aaa08d30264 100644 --- a/data/variables/copilot.yml +++ b/data/variables/copilot.yml @@ -199,4 +199,4 @@ copilot_workspace: 'Copilot Workspace' copilot_workspace_short: 'Workspace' # BYOK -copilot_byok_supported_features: '{% data variables.copilot.copilot_chat %}' +copilot_byok_supported_features: '{% data variables.copilot.copilot_chat %} and {% data variables.copilot.copilot_cli %}' diff --git a/src/content-render/stylesheets/accessibility.scss b/src/content-render/stylesheets/accessibility.scss index 8d5b8de97cb2..a3f11ccfaa30 100644 --- a/src/content-render/stylesheets/accessibility.scss +++ b/src/content-render/stylesheets/accessibility.scss @@ -9,6 +9,38 @@ word-spacing: inherit !important; line-height: inherit !important; } + + /* WCAG 1.4.13: Make tooltip content hoverable with the mouse pointer. + Primer's .tooltipped uses pointer-events:none on the ::after pseudo- + element and a 6px margin gap between the trigger and the tooltip. + This makes it impossible to hover the tooltip content itself. + + Fix: re-enable pointer-events and replace the directional margin with + a transparent border so the hover hit-area is contiguous while the + visual appearance is unchanged. */ + &::after { + pointer-events: auto !important; + } + &.tooltipped-nw::after, + &.tooltipped-n::after, + &.tooltipped-ne::after { + margin-bottom: 0 !important; + border-bottom: 6px solid transparent; + } + &.tooltipped-sw::after, + &.tooltipped-s::after, + &.tooltipped-se::after { + margin-top: 0 !important; + border-top: 6px solid transparent; + } + &.tooltipped-w::after { + margin-right: 0 !important; + border-right: 6px solid transparent; + } + &.tooltipped-e::after { + margin-left: 0 !important; + border-left: 6px solid transparent; + } } /* Enhanced focus indicators for high contrast mode */ diff --git a/src/frame/lib/page-data.ts b/src/frame/lib/page-data.ts index 52cd25aae4b1..aeb0e64cd444 100644 --- a/src/frame/lib/page-data.ts +++ b/src/frame/lib/page-data.ts @@ -1,5 +1,6 @@ import path from 'path' +import { createLogger } from '@/observability/logger' import languages from '@/languages/lib/languages-server' import type { Language } from '@/languages/lib/languages' import type { UnversionedTree, UnversionLanguageTree, SiteTree, Tree } from '@/types' @@ -13,6 +14,8 @@ import Permalink from './permalink' import frontmatterSchema from './frontmatter' import { correctTranslatedContentStrings } from '@/languages/lib/correct-translation-content' +const logger = createLogger(import.meta.url) + interface FileSystemError extends Error { code?: string } @@ -31,7 +34,13 @@ const THROW_TRANSLATION_ERRORS = Boolean( const versions = Object.keys(allVersions) -class FrontmatterParsingError extends Error {} +class FrontmatterParsingError extends Error { + isYmlError: boolean + constructor(message: string, isYmlError = false) { + super(message) + this.isYmlError = isYmlError + } +} // Note! As of Nov 2022, the schema says that 'product' is translatable // which is surprising since only a single page has prose in it. @@ -170,7 +179,7 @@ async function translateTree( // // If this the case throw error so we can lump this error with // how we deal with the file not even being present on disk. - throw new FrontmatterParsingError(JSON.stringify(read.errors)) + throw new FrontmatterParsingError(JSON.stringify(read.errors), true) } for (const { property } of read.errors) { @@ -187,7 +196,7 @@ async function translateTree( const message = `frontmatter error on '${property}' (in ${fullPath}) so falling back to English` if (DEBUG_TRANSLATION_FALLBACKS) { // The object format is so the health report knows which path the issue is on - console.warn({ message, path: relativePath }) + logger.warn(message, { path: relativePath }) } if (THROW_TRANSLATION_ERRORS) { throw new Error(message) @@ -202,10 +211,17 @@ async function translateTree( if ((error as FileSystemError).code === 'ENOENT' || error instanceof FrontmatterParsingError) { data = enData content = enPage.markdown - const message = `Unable to initialize ${fullPath} because translation content file does not exist.` - if (DEBUG_TRANSLATION_FALLBACKS) { - // The object format is so the health report knows which path the issue is on - console.warn({ message, path: relativePath }) + const message = + error instanceof FrontmatterParsingError && error.isYmlError + ? `Unable to parse YAML frontmatter in ${fullPath}, falling back to English. Details: ${error.message}` + : `Unable to initialize ${fullPath} because translation content file does not exist.` + if (error instanceof FrontmatterParsingError && error.isYmlError) { + // YAML parse failures are always logged — they indicate a translation file is corrupt + // and will silently serve English until the translation repo is fixed. + logger.warn(message, { path: relativePath }) + } else if (DEBUG_TRANSLATION_FALLBACKS) { + // Missing translation files are expected and high-volume; only log when opted in. + logger.warn(message, { path: relativePath }) } if (THROW_TRANSLATION_ERRORS) { throw new Error(message) diff --git a/src/frame/lib/read-frontmatter.ts b/src/frame/lib/read-frontmatter.ts index f1b7f8878fdf..23920d1f58fe 100644 --- a/src/frame/lib/read-frontmatter.ts +++ b/src/frame/lib/read-frontmatter.ts @@ -34,7 +34,6 @@ function readFrontmatter(markdown: string, opts: ReadFrontmatterOptions = {}) { if (filepath) error.filepath = filepath const errors = [error] - console.warn(errors) return { errors } } diff --git a/src/frame/middleware/context/context.ts b/src/frame/middleware/context/context.ts index 8cb4feb093b4..e3bda46dc802 100644 --- a/src/frame/middleware/context/context.ts +++ b/src/frame/middleware/context/context.ts @@ -83,6 +83,10 @@ export default async function contextualize( req.context.nonEnterpriseDefaultVersion = nonEnterpriseDefaultVersion req.context.initialRestVersioningReleaseDate = allVersions[nonEnterpriseDefaultVersion].apiVersions[0] + // The default REST API version that requests use when no X-GitHub-Api-Version header is specified + // This is the oldest supported version (last in the sorted descending array) + const apiVersions = allVersions[nonEnterpriseDefaultVersion].apiVersions + req.context.defaultRestApiVersion = apiVersions[apiVersions.length - 1] const restDate = new Date(req.context.initialRestVersioningReleaseDate) req.context.initialRestVersioningReleaseDateLong = restDate.toUTCString().split(' 00:')[0] diff --git a/src/rest/scripts/utils/sync.ts b/src/rest/scripts/utils/sync.ts index 356834bb7b73..481c96578897 100644 --- a/src/rest/scripts/utils/sync.ts +++ b/src/rest/scripts/utils/sync.ts @@ -120,6 +120,12 @@ async function formatRestData(operations: Operation[]): Promise { const restConfigFilename = 'src/rest/lib/config.json' const restConfigData = JSON.parse(await readFile(restConfigFilename, 'utf8')) as Record< @@ -127,28 +133,33 @@ async function updateRestConfigData(schemas: string[]): Promise { any > const restApiVersionData = restConfigData['api-versions'] || {} - // If the version isn't one of the OpenAPI version, - // then it's an api-versioned schema + + // Phase 1: Collect the dates present in the incoming schemas, keyed by + // OpenAPI version name. Only calendar-date schemas contribute — those that + // don't exactly match a base OPENAPI_VERSION_NAMES entry but do start with one. + const incomingDates: Record> = {} + for (const schema of schemas) { const schemaBaseName = path.basename(schema, '.json') if (!OPENAPI_VERSION_NAMES.includes(schemaBaseName)) { - const openApiVer = OPENAPI_VERSION_NAMES.find((ver) => schemaBaseName.startsWith(ver)) + const openApiVer = OPENAPI_VERSION_NAMES.find((ver) => schemaBaseName.startsWith(`${ver}-`)) if (!openApiVer) { throw new Error(`Could not find the OpenAPI version for schema ${schemaBaseName}`) } - const date = schemaBaseName.split(`${openApiVer}-`)[1] - - if (!restApiVersionData[openApiVer]) { - restApiVersionData[openApiVer] = [] - } - if (!restApiVersionData[openApiVer].includes(date)) { - const dates = restApiVersionData[openApiVer] - dates.push(date) - restApiVersionData[openApiVer] = dates - } + const date = schemaBaseName.slice(openApiVer.length + 1) + if (!incomingDates[openApiVer]) incomingDates[openApiVer] = new Set() + incomingDates[openApiVer].add(date) } - restConfigData['api-versions'] = restApiVersionData } + + // Phase 2: For each version key that appeared in this sync run, replace its + // date array with exactly what was synced. This removes any deprecated dates + // that are no longer present in the upstream schemas. + for (const [openApiVer, dates] of Object.entries(incomingDates)) { + restApiVersionData[openApiVer] = [...dates].sort() + } + + restConfigData['api-versions'] = restApiVersionData await writeFile(restConfigFilename, JSON.stringify(restConfigData, null, 2)) } diff --git a/src/types/types.ts b/src/types/types.ts index 20d401c209eb..27884bb0b22b 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -156,6 +156,7 @@ export type Context = { getDottedData?: (dottedPath: string) => unknown initialRestVersioningReleaseDate?: string initialRestVersioningReleaseDateLong?: string + defaultRestApiVersion?: string nonEnterpriseDefaultVersion?: string enterpriseServerVersions?: string[] enterpriseServerReleases?: typeof enterpriseServerReleases