diff --git a/.github/workflows/check-for-spammy-issues.yml b/.github/workflows/check-for-spammy-issues.yml index 9ef657e21967..578706526565 100644 --- a/.github/workflows/check-for-spammy-issues.yml +++ b/.github/workflows/check-for-spammy-issues.yml @@ -1,6 +1,6 @@ name: Check for Spammy Issues -# **What it does**: This action closes low value pull requests in the open-source repository. +# **What it does**: This action closes low value issues in the open-source repository. # **Why we have it**: We get lots of spam in the open-source repository. # **Who does it impact**: Open-source contributors. diff --git a/.github/workflows/check-for-spammy-pr.yml b/.github/workflows/check-for-spammy-pr.yml new file mode 100644 index 000000000000..0334d594750d --- /dev/null +++ b/.github/workflows/check-for-spammy-pr.yml @@ -0,0 +1,54 @@ +name: Check for Spammy PRs + +# **What it does**: This action closes low value pull requests in the open-source repository. +# **Why we have it**: We get lots of spam in the open-source repository. +# **Who does it impact**: Open-source contributors. + +on: + pull_request_target: + types: [opened] + +permissions: + contents: read + pull-requests: write + +jobs: + spammy-pr-check: + name: Label PRs that only delete files or touch a large number of files + if: github.repository == 'github/docs' && github.event_name == 'pull_request_target' + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 + with: + github-token: ${{ secrets.DOCS_BOT_PAT_BASE }} + script: | + const owner = 'github' + const repo = 'docs' + const pull_number = context.payload.pull_request.number + + const { data: files } = await github.rest.pulls.listFiles({ + owner: owner, + repo: repo, + pull_number: pull_number, + }); + + const onlyDeletes = files.length > 0 && files.every(f => f.status === 'removed') + const touchesTooMany = files.length > 10 + + // Close the PR and add the invalid label + if (onlyDeletes || touchesTooMany) { + await github.rest.issues.update({ + owner: owner, + repo: repo, + issue_number: pull_number, + labels: ['invalid'], + }); + + // Comment on the PR + await github.rest.issues.createComment({ + owner: owner, + repo: repo, + issue_number: pull_number, + body: `This pull request may have been opened accidentally. I'm going to close it now, but feel free to check out our [contribution guidelines](https://docs.github.com/en/contributing), or raise a new issue.` + }); + } diff --git a/.github/workflows/link-check-internal.yml b/.github/workflows/link-check-internal.yml index 6a6b1ffa800a..482948c6ab28 100644 --- a/.github/workflows/link-check-internal.yml +++ b/.github/workflows/link-check-internal.yml @@ -47,9 +47,12 @@ jobs: # Manual run: use the provided version and language echo "matrix={\"include\":[{\"version\":\"${INPUT_VERSION}\",\"language\":\"${INPUT_LANGUAGE}\"}]}" >> $GITHUB_OUTPUT else - # Scheduled run: English free-pro-team + English latest enterprise-server - LATEST_GHES=$(npx tsx -e "import { latest } from './src/versions/lib/enterprise-server-releases'; console.log(latest)") - echo "matrix={\"include\":[{\"version\":\"free-pro-team@latest\",\"language\":\"en\"},{\"version\":\"enterprise-server@${LATEST_GHES}\",\"language\":\"en\"}]}" >> $GITHUB_OUTPUT + # Scheduled run: every published version, in English. A link can be broken in + # one version and fine in another, so checking two of eight left most of the + # site unchecked. The report job merges the results, so this does not multiply + # the size of the issue. + MATRIX=$(npx tsx -e "import { allVersions } from './src/versions/lib/all-versions'; console.log(JSON.stringify({ include: Object.keys(allVersions).map((version) => ({ version, language: 'en' })) }))") + echo "matrix=${MATRIX}" >> $GITHUB_OUTPUT fi env: EVENT_NAME: ${{ github.event_name }} @@ -245,6 +248,17 @@ jobs: echo "No broken link reports generated - all links valid!" fi + - name: Upload the combined report + if: steps.combine.outputs.has_reports == 'true' + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + # The issue body caps every long section, and the notes there point at + # "the report attached to the workflow run". Upload it so that is true. + name: combined-link-report + path: combined-report.md + retention-days: 5 + if-no-files-found: error + - name: Create or update the rolling report issue if: | steps.combine.outputs.has_reports == 'true' @@ -268,7 +282,15 @@ jobs: let body = fs.readFileSync('combined-report.md', 'utf8') if (body.length > MAX_BODY_SIZE) { const notice = `\n\n---\n\n*Report truncated. Download the full report from the [workflow run artifacts](${runUrl}).*` - body = body.slice(0, MAX_BODY_SIZE - notice.length) + notice + let cut = body.slice(0, MAX_BODY_SIZE - notice.length) + // Cut at a line boundary so the last thing a reader sees is not half + // a table row, and close any `
` the cut left open, since an + // unclosed one swallows everything after it. + cut = cut.slice(0, cut.lastIndexOf('\n')) + const opened = (cut.match(/
/g) || []).length + const closed = (cut.match(/<\/details>/g) || []).length + cut += '\n
'.repeat(Math.max(0, opened - closed)) + body = cut + notice core.warning(`Report exceeded ${MAX_BODY_SIZE} characters, so it was truncated.`) } diff --git a/assets/images/help/billing/request-budget-flow.png b/assets/images/help/billing/request-budget-flow.png new file mode 100644 index 000000000000..42df6a7e4b39 Binary files /dev/null and b/assets/images/help/billing/request-budget-flow.png differ diff --git a/assets/images/help/copilot/copilot-sdk/features-agent-loop-diagram-1.png b/assets/images/help/copilot/copilot-sdk/features-agent-loop-diagram-1.png index 64dfe9d18b1a..753baed74ec1 100644 Binary files a/assets/images/help/copilot/copilot-sdk/features-agent-loop-diagram-1.png and b/assets/images/help/copilot/copilot-sdk/features-agent-loop-diagram-1.png differ diff --git a/assets/images/help/copilot/copilot-sdk/features-agent-loop-diagram-2.png b/assets/images/help/copilot/copilot-sdk/features-agent-loop-diagram-2.png index d2c1c58ffd69..3060cb1c255f 100644 Binary files a/assets/images/help/copilot/copilot-sdk/features-agent-loop-diagram-2.png and b/assets/images/help/copilot/copilot-sdk/features-agent-loop-diagram-2.png differ diff --git a/assets/images/help/copilot/copilot-sdk/features-hooks-diagram-0.png b/assets/images/help/copilot/copilot-sdk/features-hooks-diagram-0.png index 22b91ea1ecfb..8977ab9a3640 100644 Binary files a/assets/images/help/copilot/copilot-sdk/features-hooks-diagram-0.png and b/assets/images/help/copilot/copilot-sdk/features-hooks-diagram-0.png differ diff --git a/assets/images/help/copilot/copilot-sdk/setup-choosing-a-setup-path-diagram-0.png b/assets/images/help/copilot/copilot-sdk/setup-choosing-a-setup-path-diagram-0.png index ec49fe30e3e5..8d1ed7b19f4c 100644 Binary files a/assets/images/help/copilot/copilot-sdk/setup-choosing-a-setup-path-diagram-0.png and b/assets/images/help/copilot/copilot-sdk/setup-choosing-a-setup-path-diagram-0.png differ diff --git a/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-0.png b/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-0.png index 9b6bb90c3292..a20cb534ffcc 100644 Binary files a/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-0.png and b/assets/images/help/copilot/copilot-sdk/setup-scaling-diagram-0.png differ diff --git a/content/billing/concepts/product-billing/github-code-quality.md b/content/billing/concepts/product-billing/github-code-quality.md index f2d154ff9ef6..b1eb5125d9d0 100644 --- a/content/billing/concepts/product-billing/github-code-quality.md +++ b/content/billing/concepts/product-billing/github-code-quality.md @@ -22,11 +22,13 @@ Use of {% data variables.product.prodname_code_quality_short %} incurs three typ {% data variables.product.prodname_code_quality_short %} scans run as {% data variables.product.prodname_actions %} workflows and consume {% data variables.product.prodname_actions %} minutes, unless you use self-hosted runners. See [AUTOTITLE](/billing/concepts/product-billing/github-actions). +In a detailed usage report, you can identify usage from {% data variables.product.prodname_code_quality_short %} scans by filtering the `workflow_path` field for `{% data variables.code-quality.workflow_name_billing %}`. + ### {% data variables.product.prodname_ai_credits %} {% data variables.product.prodname_code_quality_short %} features that use AI models consume {% data variables.product.prodname_ai_credits_short %} from your shared {% data variables.product.prodname_ai_credits_short %} pool, rather than a separate {% data variables.product.prodname_code_quality_short %} allowance. Each interaction is priced based on the number of tokens consumed, where 1 {% data variables.product.prodname_ai_credit_singular %} = {% data variables.product.prodname_ai_credits_value %}. -{% data reusables.code-quality.model-usage %} +{% data reusables.code-quality.model-usage %} For more information about how {% data variables.product.prodname_ai_credits_short %} work, see [AUTOTITLE](/copilot/concepts/billing/usage-based-billing-for-organizations-and-enterprises). diff --git a/content/code-security/concepts/code-quality/code-quality.md b/content/code-security/concepts/code-quality/code-quality.md index cef2818f5a2d..91261023bc9b 100644 --- a/content/code-security/concepts/code-quality/code-quality.md +++ b/content/code-security/concepts/code-quality/code-quality.md @@ -20,7 +20,7 @@ category: {% data variables.product.prodname_code_quality %} analyzes your code for quality and coverage issues and delivers {% data variables.product.prodname_copilot_short %}-powered fixes you can apply in one click. It runs in two places: -* **On pull requests**, {% data variables.product.prodname_code_quality_short %} uses deterministic {% data variables.product.prodname_codeql %} rules to detect known anti-patterns and posts findings as inline comments before code is merged. If you upload a Cobertura XML coverage report, coverage metrics show whether a change maintains or reduces coverage. You can enforce quality and coverage thresholds with rulesets to block pull requests that don't meet your criteria, so new quality debt doesn't accumulate. +* **On pull requests**, {% data variables.product.prodname_code_quality_short %} uses deterministic {% data variables.product.prodname_codeql %} rules to detect known anti-patterns and posts findings as inline comments before code is merged. If you upload a Cobertura XML coverage report, line coverage metrics show whether a change maintains or reduces coverage. You can enforce quality and coverage thresholds with rulesets to block pull requests that don't meet your criteria, so new quality debt doesn't accumulate. * **On the default branch**, rules-based scans identify existing quality debt across your codebase, with autofixes you can apply directly or assign to {% data variables.copilot.copilot_cloud_agent %} to resolve on your behalf. AI-powered analysis also runs on recently changed files, flagging issues that fall outside existing rule sets, including languages not yet covered by {% data variables.product.prodname_codeql %} queries. > [!NOTE] @@ -32,14 +32,14 @@ Here's what {% data variables.product.prodname_code_quality %} looks like in pra For developers and teams: -* **A developer opens a pull request** that introduces a reliability or maintainability issue. {% data variables.product.prodname_code_quality_short %} posts a comment explaining the issue and offers a one-click fix before the code is merged. The developer also sees a report of coverage metrics, and can tell at a glance whether the pull request improves or reduces coverage compared to the default branch. +* **A developer opens a pull request** that introduces a reliability or maintainability issue. {% data variables.product.prodname_code_quality_short %} posts a comment explaining the issue and offers a one-click fix before the code is merged. The developer also sees a report of line coverage metrics, and can tell at a glance whether the pull request improves or reduces coverage compared to the default branch. * **A team inherits a large codebase** with years of accumulated quality debt. {% data variables.product.prodname_code_quality_short %} scans the default branch, surfaces findings with autofixes on a dashboard, and the team assigns remediation work to {% data variables.copilot.copilot_cloud_agent %} to open fix pull requests automatically. * **A team adopts AI coding assistants** and needs assurance that generated code meets the same bar as hand-written code. AI-powered analysis catches issues in recently changed files that rule-based queries weren't written for, while {% data variables.product.prodname_codeql %} rules cover well-defined anti-patterns. For administrators and leads: * **An engineering lead sets coverage and quality thresholds** using rulesets. Pull requests that don't meet the criteria are blocked from merging, so no new quality or coverage debt accumulates. -* **An administrator needs visibility across repositories** for audits or compliance reporting. {% data variables.product.prodname_code_quality_short %} reports through the security overview alongside security tools, so they can see quality posture across the organization at a glance, identify which repositories need attention, and track improvement metrics using standard {% data variables.product.github %} audit controls and policies. +* **An administrator needs visibility across repositories** for audits or compliance reporting. {% data variables.product.prodname_code_quality_short %} reports through the security overview alongside security tools, so they can see current quality posture across the organization, review how open findings have changed over time, and identify which repositories need attention. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/explore-code-quality). ## Availability and billing diff --git a/content/code-security/how-tos/maintain-quality-code/disable-code-quality.md b/content/code-security/how-tos/maintain-quality-code/disable-code-quality.md index ed8107eff599..03459a499ff3 100644 --- a/content/code-security/how-tos/maintain-quality-code/disable-code-quality.md +++ b/content/code-security/how-tos/maintain-quality-code/disable-code-quality.md @@ -54,7 +54,4 @@ What confirms the change depends on the level you disabled it at. **At the organization level**, open the organization's "{% data variables.code-quality.code_quality_ui %}" settings page and check that **Repository access** shows your selection (for example, **No repositories**) and that **Enforce access** is on if you enforced it. The organization-level {% data variables.product.prodname_code_quality_short %} dashboard also stops showing data for the affected repositories. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/explore-code-quality). -**At the repository level**, the repository's "{% data variables.code-quality.code_quality_ui %}" settings page shows that {% data variables.product.prodname_code_quality_short %} analysis is disabled. If organization or enterprise enforcement applies, the page also shows a message that a policy prevents changing the {% data variables.product.prodname_code_quality_short %} setting. No new {% data variables.product.prodname_code_quality_short %} runs start on later pull requests or pushes; on the **Actions** tab, these runs are labeled by trigger, for example "{% data variables.product.prodname_code_quality_short %}: push on main". - -> [!NOTE] -> These runs use the workflow name {% data variables.product.prodname_codeql %}, the same name {% data variables.product.prodname_code_scanning %} uses, so you can't reliably tell {% data variables.product.prodname_code_quality_short %} and {% data variables.product.prodname_code_scanning %} runs apart by workflow name. Identify {% data variables.product.prodname_code_quality_short %} runs by their {% data variables.product.prodname_actions %} label instead, for example "{% data variables.product.prodname_code_quality_short %}: push on main". +**At the repository level**, the repository's "{% data variables.code-quality.code_quality_ui %}" settings page shows that {% data variables.product.prodname_code_quality_short %} analysis is disabled. If organization or enterprise enforcement applies, the page also shows a message that a policy prevents changing the setting. No new runs start on later pull requests or pushes. On the **Actions** tab, identify existing {% data variables.product.prodname_code_quality_short %} runs by the actor `{% data variables.code-quality.workflow_actor %}` or by a run name such as "{% data variables.product.prodname_code_quality_short %}: push on main." diff --git a/content/code-security/how-tos/maintain-quality-code/explore-code-quality.md b/content/code-security/how-tos/maintain-quality-code/explore-code-quality.md index 00067b3d9685..72ad5e5af369 100644 --- a/content/code-security/how-tos/maintain-quality-code/explore-code-quality.md +++ b/content/code-security/how-tos/maintain-quality-code/explore-code-quality.md @@ -22,33 +22,106 @@ redirect_from: ## Viewing code quality insights for your organization +The organization-level dashboard has two tabs: + +* The **Health** tab shows a snapshot of your organization's current code health. +* The **Trends** tab shows how open findings have changed over a selected period of time, so you can track progress and identify repositories that need attention. + 1. On {% data variables.product.prodname_dotcom %}, navigate to the main page of your organization. For example, from [https://github.com/settings/organizations](https://github.com/settings/organizations?ref_product=github&ref_type=engagement&ref_style=text&utm_campaign=code-quality-ga-july-2026&utm_medium=docs&utm_source=docs-explore-cq-org-settings). {% data reusables.organizations.security-overview %} -1. In the "Insights" section of the sidebar, click {% octicon "code-square" aria-hidden="true" aria-label="code-square" %} **Code quality**. +1. In the "Insights" section of the sidebar, click **{% data variables.code-quality.code_quality_ui_settings %}**. > [!NOTE] > What you see on the dashboard depends on your access: +> > * Organization owners see data for **every** repository that has {% data variables.product.prodname_code_quality_short %} enabled. > * All other organization members see data only for repositories where they can view {% data variables.product.prodname_code_quality_short %} findings (the repository-level pages), up to a maximum of 3,000 repositories. -## Interpreting the score distribution chart +## Filtering dashboard data + +A filter bar at the top of the dashboard applies to both the **Health** and **Trends** tabs. You can filter by: + +* Reliability score +* Maintainability score +* {% data variables.code-quality.all_findings %} +* {% data variables.code-quality.recent_suggestions %} +* Topic +* Team +* Visibility +* Any custom properties defined for your organization + +You can also sort the dashboard data using the **Sort** control in the same filter bar. + +## Viewing current code health + +The **Health** tab shows a snapshot of your organization's code health right now. + +### Interpreting the score distribution chart The score distribution chart provides a visual overview of the code health of your organization. Each bubble represents a collection of repositories with the same maintainability and reliability scores. + * The **position** of each bubble demonstrates the overall health of those repositories. Higher bubbles represent higher maintainability scores, while bubbles further to the right represent higher reliability scores. * The **color and border pattern** of a bubble indicate the severity of the lower score for those repositories. For example, a bubble with a "Poor" score in either category will always be red with a dashed border. * The **size** of each bubble represents the number of repositories with that particular score combination. To view the maintainability score, reliability score, and number of repositories represented by a particular bubble, hover over the bubble. -## Exploring the repository table +### Exploring the repository table Below the bubble chart, there is a table that lists all repositories in your organization. Here, you can view code quality findings, along with more detailed information about those findings. You can sort the repository table in ascending or descending order for any column by clicking the column header. -## Investigating low-scoring repositories +### Investigating low-scoring repositories 1. To filter the dashboard data for the lowest-performing repositories, on the score distribution chart, click the bubble with the lowest combined scores. 1. Scroll down to the repository table. By default, the table is sorted from most to least recent repository scan, helping you prioritize current quality issues. -1. Optionally, to prioritize repositories with the highest number of {% data variables.product.prodname_codeql %} findings, click **Standard Findings** twice. +1. Optionally, to prioritize repositories with the highest number of {% data variables.product.prodname_codeql %} findings, click **{% data variables.code-quality.all_findings %}** twice. 1. To view the repository-level dashboard for a specific repository, click the repository's name. + +## Tracking quality trends over time + +The **Trends** tab shows how open findings across repositories that you have access to and that match the current filters have changed over time, so you can tell whether your code quality work is having an effect and where to focus attention next. + +1. On the organization-level dashboard, click the **Trends** tab. +1. Use the **Period** dropdown to select a time range: the last 7, 14, or 30 days. +1. Review the "Open findings over time" graph, which shows the total number of open findings across applicable repositories for the selected period. +1. Optionally, use the buttons above the graph to group the data by **Health score** or **Severity**. +1. Hover over a point on the graph to see the open finding count for that day. + +### Understanding the trends data + +Keep the following in mind when you interpret the graph: + +* The graph is based on daily snapshots of open findings. If no analysis ran on a given day, there may be no data point for that day. +* Historical data is only available from when {% data variables.product.prodname_code_quality_short %} started taking snapshots, so the available time range may initially be limited. +* The graph tracks the total count of open findings, not individual findings being opened or fixed. A change in the count doesn't necessarily mean developers fixed or introduced problems. +* Enabling {% data variables.product.prodname_code_quality_short %} on additional repositories can increase the finding count shown in the graph. An increase after enabling new repositories doesn't necessarily mean code quality is declining. +* The graph tracks the total count of open findings for the repositories you are currently filtering on. The total count includes: + + * New findings that are introduced by code changes or when code quality analysis is enabled on new repositories + * Findings that are fixed in the code or dismissed by users + +## Identifying repositories that need attention + +Below the trends graph, two tables help you identify which repositories need attention over the selected time period: + +* **Most improved repositories** lists repositories with the largest decrease in open findings over the selected time period. +* **Repositories needing improvement** lists repositories with the largest increase in open findings over the selected time period. + +Both tables include the following columns: + +* **Repository**: The name of the repository. +* **Total open**: The number of open findings for the repository at the end of the selected time period. +* **Net change**: How the open finding count for the repository has changed over the selected time period. +* **Dismissed**: How many findings were dismissed for the repository over the selected time period. + +The number of findings for a repository is affected by findings being fixed and dismissed. You can use the repository-level dashboard to confirm what changed. + +To investigate a repository, click its name to open its repository-level {% data variables.product.prodname_code_quality_short %} dashboard, where you can review individual findings and take remediation action. + +## Next steps + +To understand the code health information available on the repository-level dashboard, see [AUTOTITLE](/code-security/how-tos/maintain-quality-code/interpret-results). + +If you're planning to enable {% data variables.product.prodname_code_quality_short %} across many repositories, see [AUTOTITLE](/code-security/how-tos/maintain-quality-code/roll-out-at-scale). diff --git a/content/code-security/how-tos/maintain-quality-code/restrict-code-coverage.md b/content/code-security/how-tos/maintain-quality-code/restrict-code-coverage.md index 32ccb7b3ab22..f9362006137f 100644 --- a/content/code-security/how-tos/maintain-quality-code/restrict-code-coverage.md +++ b/content/code-security/how-tos/maintain-quality-code/restrict-code-coverage.md @@ -19,6 +19,9 @@ category: * {% data variables.product.prodname_code_quality %} is enabled on the repository. * Code coverage data is uploaded to {% data variables.product.github %} for the pull request branch. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/set-up-code-coverage). +> [!NOTE] +> Coverage thresholds are evaluated against **line coverage**. See [AUTOTITLE](/code-security/reference/code-quality/code-coverage). + ## Creating a coverage threshold rule {% data reusables.repositories.navigate-to-repo %} @@ -28,8 +31,8 @@ category: 1. Under "Branch rules", select **Restrict code coverage**. 1. Expand **Additional settings** to configure thresholds. A value of 0 means that the threshold is disabled. - * **Minimum coverage percentage**: enter a value to block pull requests where aggregated coverage falls below this percentage. - * **Maximum coverage drop**: enter a value to block pull requests where coverage drops by more than this many percentage points relative to the default branch. + * **Minimum line coverage percentage**: enter a value to block pull requests where aggregated line coverage falls below this percentage. + * **Maximum line coverage drop**: enter a value to block pull requests where line coverage drops by more than this many percentage points relative to the default branch. 1. Click **Create** or **Save changes**. diff --git a/content/code-security/how-tos/maintain-quality-code/set-pr-thresholds.md b/content/code-security/how-tos/maintain-quality-code/set-pr-thresholds.md index 36ae5e2ca9b1..2e7b9e8515cc 100644 --- a/content/code-security/how-tos/maintain-quality-code/set-pr-thresholds.md +++ b/content/code-security/how-tos/maintain-quality-code/set-pr-thresholds.md @@ -21,7 +21,7 @@ You can block pull requests that don't meet your code quality standards by addin You can set thresholds for: * **{% data variables.product.prodname_codeql %} findings**, by the lowest severity of results you require to be resolved. -* **Code coverage**, by the minimum percentage of code that must be covered by tests. +* **Code coverage**, by the minimum percentage of lines that must be covered by tests. You can enforce these thresholds at the **repository** level, or at the **organization** level to apply the same standard across many repositories at once. Choose the organization level when you want a consistent quality bar across teams, and the repository level when a single project needs its own standard. {% data variables.product.prodname_code_quality_short %} {% data variables.code-quality.recent_suggestions %} cannot be set as a threshold. @@ -35,7 +35,7 @@ You can enforce these thresholds at the **repository** level, or at the **organi ## Confirming {% data variables.product.prodname_code_quality_short %} runs successfully on pull requests -Before you add or update a ruleset to include a threshold for {% data variables.product.prodname_code_quality_short %}, confirm that the {% data variables.code-quality.workflow_name_actions %} workflow is running and reporting results back to pull requests. Otherwise, the ruleset could block the merging of **all** pull requests. +Before you add or update a ruleset to include a threshold for {% data variables.product.prodname_code_quality_short %}, confirm that the {% data variables.product.prodname_code_quality_short %} workflow is running and reporting results back to pull requests. Otherwise, the ruleset could block the merging of **all** pull requests. 1. Open a recent pull request and scroll to the "Checks" summary at the bottom of the pull request. 1. Confirm that the "{% data variables.code-quality.check_status_name %}" check ran successfully and reported its status. diff --git a/content/code-security/how-tos/maintain-quality-code/set-up-code-coverage.md b/content/code-security/how-tos/maintain-quality-code/set-up-code-coverage.md index dd0e44658efb..488f2a84121f 100644 --- a/content/code-security/how-tos/maintain-quality-code/set-up-code-coverage.md +++ b/content/code-security/how-tos/maintain-quality-code/set-up-code-coverage.md @@ -161,5 +161,5 @@ jobs: 1. Open a pull request (or push to an existing one) that triggers the workflow you configured. 1. After the workflow completes, look for a comment from `{% data variables.code-quality.pr_commenter %}` on the pull request. The comment includes: - * The aggregate coverage percentage for the pull request branch compared to the default branch. + * The aggregate line coverage percentage for the pull request branch compared to the default branch. * A per-file breakdown showing which files gained or lost coverage. diff --git a/content/code-security/how-tos/maintain-quality-code/unblock-your-pr.md b/content/code-security/how-tos/maintain-quality-code/unblock-your-pr.md index 7b2352f53cf2..62a078e07d90 100644 --- a/content/code-security/how-tos/maintain-quality-code/unblock-your-pr.md +++ b/content/code-security/how-tos/maintain-quality-code/unblock-your-pr.md @@ -19,7 +19,7 @@ Repository administrators and organization owners can set quality gates using {% There are two types of blocks: * **Code quality findings**: your changes introduce issues that fall below the required quality threshold. -* **Coverage threshold**: your changes cause code coverage to fall below a required minimum, or cause coverage to drop by more than a permitted amount relative to the default branch. +* **Coverage threshold**: your changes cause code coverage to fall below a required minimum, or cause code coverage to drop by more than a permitted amount relative to the default branch. These checks help maintain a healthy, maintainable codebase and prevent technical debt from accumulating. @@ -41,13 +41,13 @@ To unblock your pull request, you need to fix or dismiss the findings that meet If your pull request is blocked by a coverage threshold rule, you'll see a merge block banner in the "Checks" section with a message describing which threshold was not met. For example: -* "Coverage 22.0% is below minimum 50.0%": your pull request branch coverage is below the minimum coverage percentage configured in the ruleset. -* "Coverage decreased by 2.5%, maximum allowed drop is 1.0%": your changes caused coverage to drop by more than the permitted amount relative to the default branch. +* "Line coverage 22.0% is below minimum 50.0%": the line coverage on your pull request branch is below the minimum line coverage percentage configured in the ruleset. +* "Line coverage decreased by 2.5%, maximum allowed drop is 1.0%": your changes caused line coverage to drop by more than the permitted amount relative to the default branch. -To unblock your pull request, you need to add or modify tests so that more of the codebase is executed: +To unblock your pull request, you need to add or modify tests so that more lines of the codebase are executed: 1. Review the coverage summary comment on your pull request to identify which files or areas lack coverage. -1. Add or update tests to increase execution coverage. {% data variables.product.prodname_copilot_short %} can help you write and update your tests. See [AUTOTITLE](/copilot/tutorials/copilot-cookbook/testing-code). +1. Add or update tests to increase line coverage. {% data variables.product.prodname_copilot_short %} can help you write and update your tests. See [AUTOTITLE](/copilot/tutorials/copilot-cookbook/testing-code). 1. Push your changes. The coverage check will re-run automatically. ## Next steps diff --git a/content/code-security/how-tos/maintain-quality-code/view-coverage-on-prs.md b/content/code-security/how-tos/maintain-quality-code/view-coverage-on-prs.md index f7e7c8894133..be75b69a2182 100644 --- a/content/code-security/how-tos/maintain-quality-code/view-coverage-on-prs.md +++ b/content/code-security/how-tos/maintain-quality-code/view-coverage-on-prs.md @@ -16,13 +16,16 @@ category: * Code coverage is configured for your repository. See [AUTOTITLE](/code-security/how-tos/maintain-quality-code/set-up-code-coverage). +> [!NOTE] +> The coverage summary comment reports **line coverage**. See [AUTOTITLE](/code-security/reference/code-quality/code-coverage). + ## Reading the coverage summary comment After code coverage is configured for your repository, the `{% data variables.code-quality.pr_commenter %}` posts a coverage summary comment on each pull request. The comment includes: -* **Branch-vs-default-branch comparison:** The aggregate coverage percentage for the pull request branch and the default branch (for example, "65% on pull request branch, 44% on default branch"). +* **Branch-vs-default-branch comparison:** The aggregate line coverage percentage for the pull request branch and the default branch (for example, "65% on pull request branch, 44% on default branch"). * **Most impacted files:** The 10 files with the largest coverage changes between the default branch and the pull request branch. This list may include files not directly modified in the pull request. New or modified files are ranked above deleted files. Within each group, files are sorted by the absolute change in line coverage weighted by the number of lines in the file, with coverage magnitude and then filename as tiebreakers. -* **Per-file breakdown:** An expandable section listing each file with its coverage percentage and delta value. A positive delta means the file gained coverage on this branch. A negative delta indicates coverage decreased, which may signal untested code paths introduced by the change. +* **Per-file breakdown:** An expandable section listing each file with its line coverage percentage and delta value. A positive delta means the file gained line coverage on this branch. A negative delta indicates line coverage decreased, which may signal untested code paths introduced by the change. Use the coverage summary to identify files with low or declining coverage and prioritize review attention on untested changes. diff --git a/content/code-security/reference/code-quality/code-coverage.md b/content/code-security/reference/code-quality/code-coverage.md index aef9e718ba10..8a15a685fb89 100644 --- a/content/code-security/reference/code-quality/code-coverage.md +++ b/content/code-security/reference/code-quality/code-coverage.md @@ -1,7 +1,7 @@ --- title: Code coverage reference shortTitle: Code coverage -intro: '{% data variables.product.prodname_code_quality_short %} shows how much of your code your tests actually exercise, so you can find untested code before you merge.' +intro: '{% data variables.product.prodname_code_quality_short %} shows what percentage of the lines of your code your tests actually exercise, so you can find untested code before you merge.' versions: feature: code-quality contentType: reference @@ -9,17 +9,20 @@ category: - Improve code quality --- -Code coverage measures what percentage of your source code is executed when your test suite runs. {% data variables.product.prodname_code_quality_short %} displays a coverage percentage on pull requests after you upload a Cobertura XML coverage report. +Code coverage measures what percentage of the lines in your source code are executed when your test suite runs. {% data variables.product.prodname_code_quality_short %} displays a line coverage percentage on pull requests after you upload a Cobertura XML coverage report. -## How coverage is calculated +> [!NOTE] +> {% data variables.product.prodname_code_quality_short %} reports **line coverage** only. Coverage tools often also report function, branch, or statement coverage. {% data variables.product.prodname_code_quality_short %} does not currently use these metrics, even if your Cobertura XML report includes them, and they are not shown on pull requests or evaluated by coverage threshold rules. -The coverage percentage represents the number of lines covered by tests divided by the total number of lines, expressed as a percentage. {% data variables.product.prodname_code_quality_short %} stores the latest upload for each branch (including the default branch) and compares the pull request branch coverage to the default branch coverage. +## How line coverage is calculated -For example, if your default branch has 44% coverage and your pull request branch has 65% coverage, the pull request gained 21 percentage points of coverage. +The line coverage percentage represents the number of lines covered by tests divided by the total number of lines, expressed as a percentage. {% data variables.product.prodname_code_quality_short %} stores the latest upload for each branch (including the default branch) and compares the pull request branch line coverage to the default branch line coverage. + +For example, if your default branch has 44% line coverage and your pull request branch has 65% line coverage, the pull request gained 21 percentage points of line coverage. ## Per-file delta -The per-file breakdown on pull requests shows how coverage changed for each modified file. A positive delta means the file gained coverage on the pull request branch compared to the default branch. +The per-file breakdown on pull requests shows how line coverage changed for each modified file. A positive delta means the file gained line coverage on the pull request branch compared to the default branch. To set up code coverage for your repository, see [AUTOTITLE](/code-security/how-tos/maintain-quality-code/set-up-code-coverage). diff --git a/content/code-security/reference/code-quality/codeql-detection.md b/content/code-security/reference/code-quality/codeql-detection.md index 7a90531bc013..2eab09a885ab 100644 --- a/content/code-security/reference/code-quality/codeql-detection.md +++ b/content/code-security/reference/code-quality/codeql-detection.md @@ -22,10 +22,6 @@ category: {% data variables.copilot.copilot_autofix_short %} suggestions are provided for findings where possible. -### Scan information - -Each {% data variables.product.prodname_codeql %} analysis will use {% data variables.product.prodname_actions %} minutes and can be seen on the **Actions** tab of the repository. These runs use the workflow name {% data variables.product.prodname_codeql %}, the same name {% data variables.product.prodname_code_scanning %} uses, so you can't reliably tell {% data variables.product.prodname_code_quality_short %} and {% data variables.product.prodname_code_scanning %} runs apart by workflow name. Identify {% data variables.product.prodname_code_quality_short %} runs by their {% data variables.product.prodname_actions %} label instead, for example "Code Quality: push on main" - ### Query lists for supported languages Each {% data variables.product.prodname_code_quality_short %} rule is written as a query in {% data variables.product.prodname_codeql %} and then run using {% data variables.product.prodname_actions %}. @@ -43,9 +39,11 @@ For more information about the {% data variables.product.prodname_codeql %} proj ## Workflow used for code quality analysis -You can see all the workflow runs for {% data variables.product.prodname_code_quality_short %} on the **Actions** tab for your repository. You can identify {% data variables.product.prodname_code_quality_short %} runs by their {% data variables.product.prodname_actions %} label, for example "{% data variables.product.prodname_code_quality_short %}: push on main" +Each {% data variables.product.prodname_codeql %} analysis will use {% data variables.product.prodname_actions %} minutes. You can see all workflow runs for {% data variables.product.prodname_code_quality_short %} on the repository's **Actions** tab. + +{% data variables.product.prodname_code_quality_short %} and {% data variables.product.prodname_code_scanning %} runs both use the workflow name {% data variables.product.prodname_codeql %}. You can identify {% data variables.product.prodname_code_quality_short %} runs by the actor `{% data variables.code-quality.workflow_actor %}` or by their run name, for example, "{% data variables.product.prodname_code_quality_short %}: push on main." -By default, the {% data variables.code-quality.workflow_name_actions %} workflow runs on standard {% data variables.product.github %} runners but you can configure {% data variables.product.prodname_code_quality_short %} to use runners with a specific label. These may be hosted by {% data variables.product.github %} or self-hosted. +By default, the {% data variables.product.prodname_code_quality_short %} workflow runs on standard {% data variables.product.github %} runners but you can configure {% data variables.product.prodname_code_quality_short %} to use runners with a specific label. These may be hosted by {% data variables.product.github %} or self-hosted. If your organization has configured caching of private registries, these will be available for code quality analysis to use to resolve dependencies. diff --git a/content/contributing/collaborating-on-github-docs/about-contributing-to-github-docs.md b/content/contributing/collaborating-on-github-docs/about-contributing-to-github-docs.md index 47aa3ccb0746..91a71daefc30 100644 --- a/content/contributing/collaborating-on-github-docs/about-contributing-to-github-docs.md +++ b/content/contributing/collaborating-on-github-docs/about-contributing-to-github-docs.md @@ -21,7 +21,7 @@ The documentation repository is the place to discuss and collaborate on the docu If you've found something in the documentation content, or something about the docs.github.com website, that should be updated, search the open issues to see if someone else has reported the same thing. If it's something new, open an issue using a [template](https://github.com/github/docs/issues/new/choose). We'll use the issue to have a conversation about the problem you'd like to be fixed. > [!NOTE] -> {% data variables.product.prodname_dotcom %} employees should open issues in the private `docs-content` repository. +> For larger updates, we highly recommend that you open an issue first, to give us the opportunity to review your proposal before you go ahead and raise a pull request. ## Pull requests diff --git a/content/copilot/concepts/agents/cloud-agent/about-cloud-agent.md b/content/copilot/concepts/agents/cloud-agent/about-cloud-agent.md index e33e3bfa5dbf..28421701411e 100644 --- a/content/copilot/concepts/agents/cloud-agent/about-cloud-agent.md +++ b/content/copilot/concepts/agents/cloud-agent/about-cloud-agent.md @@ -48,7 +48,7 @@ When you delegate tasks to {% data variables.copilot.copilot_cloud_agent %}, you While working on a coding task, {% data variables.copilot.copilot_cloud_agent %} has access to its own ephemeral development environment, powered by {% data variables.product.prodname_actions %}, where it can explore your code, make changes, execute automated tests and linters and more. -> [!NOTE] Deep research, planning, and iterating on code changes before creating a pull request are only available with {% data variables.copilot.copilot_cloud_agent %} on {% data variables.product.prodname_dotcom_the_website %}. {% data variables.copilot.copilot_cloud_agent_short_cap_c %} integrations (such as Azure Boards, JIRA, Linear, Slack, or Teams) only support creating a pull request directly. +> [!NOTE] Deep research, planning, and iterating on code changes before creating a pull request are available with {% data variables.copilot.copilot_cloud_agent %} on {% data variables.product.prodname_dotcom_the_website %}, and in {% data variables.release-phases.public_preview %} for the Microsoft Teams integration. Other {% data variables.copilot.copilot_cloud_agent_short %} integrations (such as Azure Boards, JIRA, Linear, or Slack) only support creating a pull request directly. ### Benefits over traditional AI workflows @@ -94,7 +94,11 @@ These metrics can help you track adoption of {% data variables.copilot.copilot_c ## Integrating {% data variables.copilot.copilot_cloud_agent %} with third-party tools -You can also invoke {% data variables.copilot.copilot_cloud_agent %} from external tools, allowing you to assign tasks to {% data variables.product.prodname_copilot_short %}, provide context, and open pull requests without leaving your workflow. See [AUTOTITLE](/copilot/concepts/tools/about-copilot-integrations) +You can invoke {% data variables.copilot.copilot_cloud_agent %} from external tools, allowing you to assign tasks to {% data variables.product.prodname_copilot_short %}, provide context, and open pull requests without leaving your workflow. + +Use {% data variables.copilot.copilot_cloud_agent %} in Microsoft Teams to collaborate with your team on agent-assisted work. You can @mention {% data variables.product.github %} in channels, threads, and direct messages to work alongside teammates and {% data variables.product.prodname_copilot_short %} on research, planning, and coding tasks. Teammates can add context, steer {% data variables.product.prodname_copilot_short %} sessions, monitor progress, and then review the resulting artifacts. + +For more information, see [AUTOTITLE](/copilot/concepts/tools/about-copilot-integrations). ## Making {% data variables.copilot.copilot_cloud_agent %} available diff --git a/content/copilot/concepts/agents/cloud-agent/risks-and-mitigations.md b/content/copilot/concepts/agents/cloud-agent/risks-and-mitigations.md index 8eb2eed04d76..4265328acd53 100644 --- a/content/copilot/concepts/agents/cloud-agent/risks-and-mitigations.md +++ b/content/copilot/concepts/agents/cloud-agent/risks-and-mitigations.md @@ -38,6 +38,7 @@ To mitigate this risk, {% data variables.product.github %}: * **Requires human review before merging.** Draft pull requests created by {% data variables.copilot.copilot_cloud_agent %} must be reviewed and merged by a human. {% data variables.copilot.copilot_cloud_agent %} cannot mark its pull requests as "Ready for review" and cannot approve or merge a pull request. * **Restricts {% data variables.product.prodname_actions %} workflow runs.** By default, workflows are not triggered until {% data variables.copilot.copilot_cloud_agent %}'s code is reviewed and a user with write access to the repository clicks the **Approve and run workflows** button. Optionally, you can configure {% data variables.product.prodname_copilot_short %} to allow workflows to run automatically. See [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/review-copilot-output#manage-github-actions-workflow-runs). * **Prevents the user who asked {% data variables.copilot.copilot_cloud_agent %} to create a pull request from approving it.** This maintains the expected controls in the "Required approvals" rule and branch protection. See [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets). +* **Requires an additional approval when a pull request isn't attributed to a person.** When {% data variables.copilot.copilot_cloud_agent %} opens a pull request under its own app identity, one more approval is required before it can be merged, as long as the repository already requires at least one approval. This is enabled by default in rulesets, where administrators can turn it off, and always applies to branch protection rules. See [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#additional-approval-for-unattributed-copilot-pull-requests). ## {% data variables.copilot.copilot_cloud_agent %} has access to sensitive information diff --git a/content/copilot/concepts/agents/index.md b/content/copilot/concepts/agents/index.md index 53d902eb1e36..cd8858a5f1e6 100644 --- a/content/copilot/concepts/agents/index.md +++ b/content/copilot/concepts/agents/index.md @@ -22,6 +22,7 @@ children: - /anthropic-claude - /about-agent-skills - /enterprise-management + - /opentelemetry contentType: concepts --- diff --git a/content/copilot/concepts/agents/opentelemetry.md b/content/copilot/concepts/agents/opentelemetry.md new file mode 100644 index 000000000000..ac019fd8bbec --- /dev/null +++ b/content/copilot/concepts/agents/opentelemetry.md @@ -0,0 +1,46 @@ +--- +title: OpenTelemetry for agent monitoring +shortTitle: OpenTelemetry +intro: Understand how {% data variables.product.prodname_copilot_short %} agents perform and interact with models and tools. +versions: + feature: copilot +contentType: concepts +category: + - Manage Copilot for a team +--- + +OpenTelemetry (OTel) is an open source observability framework. It provides a standard way to collect telemetry events and metrics and export them to compatible observability tools. For more information, see [What is OpenTelemetry?](https://opentelemetry.io/docs/what-is-opentelemetry/) on the OTel website. + +When you enable OTel monitoring, you can send data from users' {% data variables.product.prodname_copilot_short %} clients to an OTel-compatible backend. This lets you analyze agent sessions and understand agent usage across your enterprise. + +## What data does {% data variables.product.prodname_copilot_short %} send? + +{% data variables.product.prodname_copilot_short %} sends three types of data: + +* **Traces** show the flow of an agent session and connect each step, including model calls and tool use. For example, a trace can show an agent calling a model, using the `readFile` tool, and calling the model again to produce a response. +* **Metrics** are numeric measurements that help you identify patterns over time. For example, token usage metrics track the number of input and output tokens used in model calls. +* **Events** record individual actions at a specific point in time. For example, an edit feedback event records whether a user accepted or rejected an agent edit. + +By default, the data does not include prompts, responses, or tool arguments. You can choose to capture this content, but it may contain sensitive information, such as code, file contents, and user prompts. + +## Enabling OpenTelemetry + +To collect OTel data from users' {% data variables.product.prodname_copilot_short %} clients: + +1. **Set up an observability backend.** Choose a secure backend that supports the OpenTelemetry Protocol (OTLP). Some backends can receive OTLP data directly. For other backends, deploy an [OpenTelemetry Collector](https://opentelemetry.io/docs/collector/) to receive, process, and forward the data. For examples, see [Use with observability backends](https://code.visualstudio.com/docs/agents/guides/monitoring-agents#_use-with-observability-backends) in the {% data variables.product.prodname_vscode_shortname %} documentation. +1. **Configure users' clients.** Set configuration values to enable OTel in each client and configure it to send data to your OTLP endpoint by configuring the endpoint, headers, and authentication token. +1. **Interpret data.** Use the collected data to analyze sessions and identify trends. For an example implementation, see [Monitor AI coding agents with Grafana](https://learn.microsoft.com/en-gb/azure/managed-grafana/grafana-opentelemetry-app-insights) in the Microsoft documentation. + +### Configuring users' clients + +Enterprises can enforce OTel configuration across supported clients with managed settings. These settings are enforced across users' clients and cannot be overridden. The `telemetry` property includes keys for enabling and configuring OpenTelemetry. + +For more information, see [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-managed-settings) and [AUTOTITLE](/copilot/reference/enterprise-administrators/enterprise-managed-settings). + +## Client documentation + +For more details of how OpenTelemetry is used across clients, see: + +* [Monitor agent usage with OpenTelemetry](https://code.visualstudio.com/docs/agents/guides/monitoring-agents) in the {% data variables.product.prodname_vscode_shortname %} documentation +* [AUTOTITLE](/copilot/how-tos/copilot-sdk/observability/opentelemetry) +* [AUTOTITLE](/copilot/reference/copilot-cli-reference/cli-command-reference#opentelemetry-monitoring) diff --git a/content/copilot/concepts/billing/budgets-for-usage-based-billing.md b/content/copilot/concepts/billing/budgets-for-usage-based-billing.md index 4f263f7841c3..010ebbb8fe20 100644 --- a/content/copilot/concepts/billing/budgets-for-usage-based-billing.md +++ b/content/copilot/concepts/billing/budgets-for-usage-based-billing.md @@ -106,6 +106,8 @@ When someone in your enterprise uses {% data variables.product.prodname_copilot_ > [!NOTE] > For additional (metered) usage to occur, the "{% data variables.product.prodname_ai_credit_singular %} paid usage" policy must be enabled in your enterprise or organization settings. If this policy is disabled, usage is blocked when the shared pool is exhausted, regardless of your budget configuration. +![Flowchart of AI credit budget checks: individual budget, then shared pool, then metered charge against the most specific budget.](/assets/images/help/billing/request-budget-flow.png) + Each request for an {% data variables.product.prodname_ai_credit_singular %}-consuming feature goes through these checks: 1. **User-level budget check.** The system first checks whether the user has exceeded their user-level budget. When a user has more than one type of user-level budget, the most specific one applies: an individual budget if set, otherwise the budget for the user's cost center, otherwise the universal budget. If the applicable budget is exceeded, the request is blocked immediately. ULBs are always a hard stop, and no other budget can override or supplement them. If no user-level budget is set, the request continues. diff --git a/content/copilot/concepts/billing/index.md b/content/copilot/concepts/billing/index.md index e5493b84935b..9fc7b824e06c 100644 --- a/content/copilot/concepts/billing/index.md +++ b/content/copilot/concepts/billing/index.md @@ -8,7 +8,6 @@ children: - /usage-based-billing-for-individuals - /usage-based-billing-for-organizations-and-enterprises - /budgets-for-usage-based-billing - - /individual-plans - /organizations-and-enterprises redirect_from: - /managing-copilot/managing-copilot-as-an-individual-subscriber/billing-and-payments diff --git a/content/copilot/concepts/billing/individual-plans.md b/content/copilot/concepts/billing/individual-plans.md deleted file mode 100644 index ea8fdc1a20e5..000000000000 --- a/content/copilot/concepts/billing/individual-plans.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -title: About individual GitHub Copilot plans and benefits -shortTitle: Individual plans -intro: '{% data variables.product.company_short %} offers several {% data variables.product.prodname_copilot_short %} plans for individual developers, each with different features, model access, and usage limits to support a wide range of coding needs.' -versions: - feature: copilot -redirect_from: - - /copilot/managing-copilot/managing-copilot-as-an-individual-subscriber/about-github-copilot-free - - /copilot/managing-copilot/managing-copilot-as-an-individual-subscriber/managing-copilot-free/about-github-copilot-free - - /copilot/managing-copilot/managing-copilot-as-an-individual-subscriber/getting-started-with-copilot-on-your-personal-account/about-github-copilot-free - - /copilot/managing-copilot/managing-copilot-as-an-individual-subscriber/getting-started-with-copilot-on-your-personal-account/about-individual-copilot-plans-and-benefits - - /copilot/concepts/copilot-billing/about-individual-copilot-plans-and-benefits - - /copilot/concepts/copilot-billing/individual-plans -contentType: concepts -category: - - Learn about Copilot ---- - -{% data variables.product.company_short %} offers multiple {% data variables.product.prodname_copilot_short %} plans for individual developers, as well as a dedicated student offering, each designed to meet different needs based on your coding habits, interest in AI models, and desired level of flexibility. - -You can choose from the following plans. - -## {% data variables.copilot.copilot_free %} - -For developers looking to get started with {% data variables.product.prodname_copilot_short %}. - -* Includes up to **2,000 code completions** and an allowance of {% data variables.product.prodname_ai_credits %} -* Limited chat and agent usage with models available through {% data variables.copilot.copilot_auto_model_selection_short %} only -* Designed to give you a limited taste of {% data variables.product.prodname_copilot_short %}'s capabilities -* No subscription or payment required -* Intended for **personal use only**, not for users managed by an organization or enterprise -* Great for developers who want to explore {% data variables.product.prodname_copilot_short %}'s capabilities before upgrading to a paid plan - -## {% data variables.copilot.copilot_student %} - -Verified students can access unlimited completions and additional models at no cost. - -* Includes **unlimited** code completions and an allowance of {% data variables.product.prodname_ai_credits %} -* Limited chat and agent usage with models available through {% data variables.copilot.copilot_auto_model_selection_short %} only -* Free for verified students - -## {% data variables.copilot.copilot_pro %} - -For developers who want more flexibility, including unlimited completions and access to additional models. - -* Includes **unlimited completions** in IDEs -* Access to {% data variables.copilot.copilot_chat_short %} and a selection of models -* A monthly allowance of {% data variables.product.prodname_ai_credits_short %}. See [{% data variables.product.prodname_ai_credits %} allowance by plan](/copilot/concepts/billing/usage-based-billing-for-individuals). -* Free for verified teachers and maintainers of popular open source projects - -## {% data variables.copilot.copilot_pro_plus %} - -For developers who need maximum flexibility, premium access to available models, and expanded limits. - -* Everything in {% data variables.copilot.copilot_pro_short %}, and: - - * Access to premium models - * A higher monthly allowance of {% data variables.product.prodname_ai_credits_short %}. See [{% data variables.product.prodname_ai_credits %} allowance by plan](/copilot/concepts/billing/usage-based-billing-for-individuals). - * Priority access to advanced AI capabilities - -* Ideal for AI power users and developers who want cutting-edge tools - -## {% data variables.copilot.copilot_max %} - -Designed for sustained, high-volume {% data variables.product.prodname_copilot_short %} users. - -* Everything in {% data variables.copilot.copilot_pro_plus_short %}, and: - - * Our highest available monthly allowance of {% data variables.product.prodname_ai_credits_short %}. See [{% data variables.product.prodname_ai_credits %} allowance by plan](/copilot/concepts/billing/usage-based-billing-for-individuals). - * **Priority access** to new models and features - -* Ideal for high-volume AI power users who want access to the most AI credits available to them - -## Comparing plans - -The following table highlights the key differences between individual {% data variables.product.prodname_copilot_short %} plans. - -{% rowheaders %} - -| Feature | {% data variables.copilot.copilot_free_short %} | {% data variables.copilot.copilot_student_short %} | {% data variables.copilot.copilot_pro_short %} | {% data variables.copilot.copilot_pro_plus_short %} | {% data variables.copilot.copilot_max_short %} | -|--------|----------------------------------------------------|----------------------------------------------|--------------------------------------------------|----------------------------------------------------------|----------------------------------------------------------| -| Price | Free | [Free](/copilot/how-tos/copilot-on-github/set-up-copilot/enable-copilot/set-up-for-students) | {% data variables.copilot.cfi_price_per_month %} per month
([free](/copilot/how-tos/copilot-on-github/set-up-copilot/enable-copilot/set-up-for-teachers-and-os-maintainers) for some users) | {% data variables.copilot.cpp_price_per_month %} per month | {% data variables.copilot.cm_price_per_month %} per month | -| Real-time code suggestions with included models | Up to 2,000 per month | Unlimited | Unlimited | Unlimited | Unlimited | -| {% data variables.product.prodname_copilot_short %} interactions[^1] | Limited ({% data variables.copilot.copilot_auto_model_selection_short %} only) | Limited ({% data variables.copilot.copilot_auto_model_selection_short %} only) | Subject to monthly {% data variables.product.prodname_ai_credits_short %} allowance | Subject to monthly {% data variables.product.prodname_ai_credits_short %} allowance | Subject to monthly {% data variables.product.prodname_ai_credits_short %} allowance | -| Access to premium models | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} Full access | {% octicon "check" aria-label="Included" %} Full access | - -{% endrowheaders %} - -[^1]: Response times may vary during periods of high usage. - -### {% data variables.product.prodname_ai_credits %} allowance by plan - -The following table shows what's included with each paid plan. - -{% data reusables.copilot.plans.ai-credits-by-plan %} - -For more information on how {% data variables.product.prodname_ai_credits %} work, see [AUTOTITLE](/copilot/concepts/billing/usage-based-billing-for-individuals) and [AUTOTITLE](/copilot/concepts/billing/usage-based-billing-for-organizations-and-enterprises). - -For a detailed comparison of features and benefits, see [AUTOTITLE](/copilot/get-started/plans). - -## Choosing the right plan - -Consider the following to decide which plan is right for you: - -* **Just getting started?** [Try {% data variables.copilot.copilot_free %}](https://github.com/copilot?ref_product=copilot&ref_type=engagement&ref_style=text&ref_plan=free) to explore basic functionality at no cost. -* **Studying?** Choose {% data variables.copilot.copilot_student_short %} to access premium features at no cost. -* **Coding regularly with AI?** [Subscribe to {% data variables.copilot.copilot_pro %}](https://github.com/github-copilot/signup?ref_product=copilot&ref_type=purchase&ref_style=text&ref_plan=pro) for more flexibility and access to premium features. -* **Want the best performance and premium model access?** [Go with {% data variables.copilot.copilot_pro_plus %}](https://github.com/github-copilot/signup?ref_product=copilot&ref_type=purchase&ref_style=text&ref_plan=pro-plus) to unlock everything {% data variables.product.prodname_copilot_short %} has to offer. -* **Doing sustained high-volume {% data variables.product.prodname_copilot_short %} development?** [Subscribe to {% data variables.copilot.copilot_max %}](https://github.com/github-copilot/signup?ref_product=copilot&ref_type=purchase&ref_style=text&ref_plan=max), which offers the highest monthly allowance of {% data variables.product.prodname_ai_credits_short %} for individual plans. - -To learn how to set up the plan that’s right for you, see [AUTOTITLE](/copilot/how-tos/manage-your-account/get-started-with-a-copilot-plan). - -### Limitations for enterprises - -{% data variables.copilot.copilot_free_short %} is not suitable for enterprises, as it does not include the following features: - -* Access management -* Audit logs -* Policy management -* File exclusion -* Usage data -* Indemnification coverage diff --git a/content/copilot/concepts/billing/usage-based-billing-for-individuals.md b/content/copilot/concepts/billing/usage-based-billing-for-individuals.md index 0e6c5258c327..2a1f6446f654 100644 --- a/content/copilot/concepts/billing/usage-based-billing-for-individuals.md +++ b/content/copilot/concepts/billing/usage-based-billing-for-individuals.md @@ -57,6 +57,8 @@ The following table shows what's included with each paid plan. {% data reusables.copilot.plans.ai-credits-by-plan %} + {% data variables.copilot.copilot_free_short %} and {% data variables.copilot.copilot_student_short %} both have an allowance of {% data variables.product.prodname_ai_credits_short %} and access to models through {% data variables.copilot.copilot_auto_model_selection_short %} only. {% data variables.copilot.copilot_free_short %} includes 2000 code completions per month and {% data variables.copilot.copilot_student_short %} includes unlimited code completions. + If you use everything included in your plan, you can purchase more and keep working. See [What happens if I exceed my included {% data variables.product.prodname_ai_credits_short %}](#what-happens-if-i-exceed-my-included--data-variablesproductprodname_ai_credits_short-). ## What is billed in {% data variables.product.prodname_ai_credits_short %}? diff --git a/content/copilot/concepts/tools/about-copilot-integrations.md b/content/copilot/concepts/tools/about-copilot-integrations.md index 60f4d945ab26..56d9a963294f 100644 --- a/content/copilot/concepts/tools/about-copilot-integrations.md +++ b/content/copilot/concepts/tools/about-copilot-integrations.md @@ -13,7 +13,7 @@ category: ## Overview -{% data variables.copilot.copilot_cloud_agent %} can be integrated with various tools and platforms to enhance its functionality and streamline your development workflow. With integrations, you can trigger {% data variables.copilot.copilot_cloud_agent %} from within your existing tools, providing the cloud agent with the context it needs to assist you effectively. +{% data variables.copilot.copilot_cloud_agent %} can be integrated with various tools and platforms to enhance its functionality and streamline your development workflow. With integrations, you can work with and trigger {% data variables.copilot.copilot_cloud_agent %} from within your existing tools, providing the cloud agent with the context it needs to assist you effectively. For more information about {% data variables.copilot.copilot_cloud_agent %}, see [AUTOTITLE](/copilot/concepts/agents/cloud-agent/about-cloud-agent). @@ -21,7 +21,7 @@ For more information about {% data variables.copilot.copilot_cloud_agent %}, see Currently, {% data variables.copilot.copilot_cloud_agent %} supports integrations with the following tools: -* **Microsoft Teams**: [AUTOTITLE](/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-teams) - Learn how to set up the Microsoft Teams integration to trigger {% data variables.copilot.copilot_cloud_agent %} directly from your Teams channels. +* **Microsoft Teams**: [AUTOTITLE](/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-teams) - Learn how to set up the Microsoft Teams integration to collaborate with {% data variables.copilot.copilot_cloud_agent %} directly in your Teams messages and channels. * **Slack**: [AUTOTITLE](/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-slack) - Learn how to set up the Slack integration to trigger {% data variables.copilot.copilot_cloud_agent %} directly from your Slack workspace. * **Linear**: [AUTOTITLE](/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-linear) - Learn how to set up the Linear integration to trigger {% data variables.copilot.copilot_cloud_agent %} directly from your Linear issues. * **Azure Boards**: [AUTOTITLE](/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-azure-boards) - Learn how to set up the Azure Boards integration to trigger {% data variables.copilot.copilot_cloud_agent %} directly from your Azure Boards work items. @@ -31,10 +31,10 @@ Currently, {% data variables.copilot.copilot_cloud_agent %} supports integration Integrating {% data variables.copilot.copilot_cloud_agent %} with your existing tools offers several benefits: -* **Seamless workflow**: Trigger {% data variables.copilot.copilot_cloud_agent %} directly from the tools you already use, reducing context switching and improving productivity. +* **Seamless workflow**: Work with {% data variables.copilot.copilot_cloud_agent %} directly in the tools you already use, reducing context switching and improving productivity. * **Context-aware assistance**: Provide {% data variables.copilot.copilot_cloud_agent %} with the necessary context from your tools, enabling it to generate more relevant and accurate code suggestions. -* **Collaboration**: Facilitate collaboration among team members by allowing them to trigger {% data variables.copilot.copilot_cloud_agent %} from shared platforms, ensuring everyone benefits from the agent's capabilities. +* **Collaboration**: Facilitate collaboration among team members by allowing them to work with {% data variables.copilot.copilot_cloud_agent %} from shared platforms, ensuring everyone benefits from the agent's capabilities. ## Data usage -When you trigger {% data variables.copilot.copilot_cloud_agent %} through an integration, the agent will capture the entire thread or issue to understand the context in order to assist you effectively. This context is stored in the pull request created by the agent. +When you use {% data variables.copilot.copilot_cloud_agent %} through an integration, the agent will capture the entire thread or issue to understand the context in order to assist you effectively. This context is stored in the artifacts created by the agent. diff --git a/content/copilot/get-started/plans.md b/content/copilot/get-started/plans.md index 16343f093af4..05d15f924def 100644 --- a/content/copilot/get-started/plans.md +++ b/content/copilot/get-started/plans.md @@ -1,6 +1,6 @@ --- title: Plans for GitHub Copilot -intro: 'Learn about the available plans for {% data variables.product.prodname_copilot_short %}.' +intro: 'Discover the plans available for {% data variables.product.prodname_copilot_short %}.' versions: feature: copilot shortTitle: Plans @@ -8,55 +8,270 @@ redirect_from: - /copilot/about-github-copilot/subscription-plans-for-github-copilot - /copilot/about-github-copilot/plans-for-github-copilot - /copilot/get-started/plans-for-github-copilot + - /copilot/concepts/billing/individual-plans contentType: get-started category: - Learn about Copilot --- -> [!IMPORTANT] {% data reusables.copilot.plans.organization-plans-paused %} +## {% data variables.product.prodname_copilot %} plans -{% data variables.product.company_short %} offers several plans for {% data variables.product.prodname_copilot %}, depending on your needs and whether you're using {% data variables.product.prodname_copilot_short %} as an individual or as part of an organization or enterprise. +{% data variables.product.company_short %} offers a variety of plans for {% data variables.product.prodname_copilot_short %}. Choose between them depending on your needs and whether you're using {% data variables.product.prodname_copilot_short %} as an individual or as part of an organization or enterprise. -* **{% data variables.copilot.copilot_free %}** is available to individual developers who don't have access to {% data variables.product.prodname_copilot_short %} through an organization or enterprise. This free plan includes limited access to a selection of {% data variables.product.prodname_copilot_short %} features and models available through {% data variables.copilot.copilot_auto_model_selection_short %} only, allowing you to try AI-powered coding assistance at no cost. +**{% data variables.copilot.copilot_free_short %}**: [Start using {% data variables.copilot.copilot_free_short %}](https://github.com/copilot?ref_product=copilot&ref_type=engagement&ref_style=text&ref_plan=free). -* **{% data variables.copilot.copilot_student %}** is available to verified students. The plan includes unlimited code completions and an allowance of {% data variables.product.prodname_ai_credits %}, plus limited chat and agent usage with models available through {% data variables.copilot.copilot_auto_model_selection_short %} only. +* This plan includes limited access to a selection of {% data variables.product.prodname_copilot_short %} features allowing you to try AI-powered coding assistance at no cost. -* **{% data variables.copilot.copilot_pro %}** is designed for individuals who want more flexibility. This paid plan includes unlimited completions, access to a selection of models, {% data variables.copilot.copilot_cloud_agent %}, and a monthly allowance of {% data variables.product.prodname_ai_credits_short %}. Verified teachers, and maintainers of popular open source projects may be eligible for free access. +**{% data variables.copilot.copilot_student_short %}**: [Get access to {% data variables.copilot.copilot_student_short %}](/copilot/how-tos/copilot-on-github/set-up-copilot/enable-copilot/set-up-for-students). -* **{% data variables.copilot.copilot_pro_plus %}** includes, in addition to everything in {% data variables.copilot.copilot_pro_short %}, a higher monthly allowance of {% data variables.product.prodname_ai_credits_short %}, and access to premium models. Ideal for AI power users who want access to the most advanced capabilities. +* Available to verified students. Get access to {% data variables.product.prodname_copilot_short %}'s features for free. -* **{% data variables.copilot.copilot_max %}** is designed for high-volume {% data variables.product.prodname_copilot_short %} users. This paid plan includes, in addition to everything in {% data variables.copilot.copilot_pro_plus_short %}, our highest individual monthly allowance of {% data variables.product.prodname_ai_credits_short %}, and priority access to new models and features. Ideal for sustained, high-volume AI power users who want access to the most AI credits available to them. +**{% data variables.copilot.copilot_pro %}**: [Subscribe to {% data variables.copilot.copilot_pro_short %}](https://github.com/github-copilot/signup?ref_product=copilot&ref_type=purchase&ref_style=text&ref_plan=pro). -* **{% data variables.copilot.copilot_for_business %}** is for organizations on {% data variables.product.prodname_free_team %} or {% data variables.product.prodname_team %} plan, or enterprises on {% data variables.product.prodname_ghe_cloud %}. This plan includes {% data variables.copilot.copilot_cloud_agent %}, access to a broad model catalog, a monthly pool of {% data variables.product.prodname_ai_credits_short %}, and enables centralized management and {% data variables.product.prodname_copilot_short %} policy control for organization members. +* Designed for individuals who want more flexibility with access to a selection of models and a monthly allowance of {% data variables.product.prodname_ai_credits_short %}. -* **{% data variables.copilot.copilot_enterprise %}** is for enterprises using {% data variables.product.prodname_ghe_cloud %}. It includes all the features of {% data variables.copilot.copilot_business_short %}, priority access to new models and features, a larger monthly pool of {% data variables.product.prodname_ai_credits_short %}, plus additional enterprise-grade capabilities. Enterprise owners can assign {% data variables.copilot.copilot_enterprise_short %} or {% data variables.copilot.copilot_business_short %} to individual organizations, or assign {% data variables.copilot.copilot_business_short %} directly to users and teams. +**{% data variables.copilot.copilot_pro_plus %}**: [Subscribe to {% data variables.copilot.copilot_pro_plus_short %}](https://github.com/github-copilot/signup?ref_product=copilot&ref_type=purchase&ref_style=text&ref_plan=pro-plus). -{% data variables.product.prodname_copilot_short %} is not currently available for {% data variables.product.prodname_ghe_server %}. +* Ideal for AI power users who want access to the most advanced capabilities. This paid plan includes everything in {% data variables.copilot.copilot_pro %} and a higher monthly allowance of {% data variables.product.prodname_ai_credits_short %}. -## Comparing {% data variables.product.prodname_copilot_short %} plans +**{% data variables.copilot.copilot_max %}**: [Upgrade to {% data variables.copilot.copilot_max_short %}](https://github.com/settings/billing/licensing?ref_product=copilot&ref_type=purchase&ref_style=text&ref_plan=max). -The tables below show the features available in each {% data variables.product.prodname_copilot_short %} plan. +* Ideal for sustained, high-volume AI power users who want access to the most AI credits available to them. This paid plan includes everything in {% data variables.copilot.copilot_pro_plus_short %}, plus our highest individual monthly allowance of {% data variables.product.prodname_ai_credits_short %}. -{% data reusables.copilot.differences-cfi-cfb-table %} +> [!IMPORTANT] +> {% data reusables.copilot.plans.organization-plans-paused %} -For more information, see [AUTOTITLE](/copilot/get-started/features). +**{% data variables.copilot.copilot_for_business %}**: To get started, [contact sales](https://github.com/enterprise/contact?ref_product=copilot&ref_type=purchase&ref_style=text). -## Ready to choose a plan? +* Made for organizations an enterprises, this plan offers centralized management and {% data variables.product.prodname_copilot_short %} policy control for organization members. -Start using {% data variables.product.prodname_copilot_short %} by signing up for the plan that best fits your needs. +**{% data variables.copilot.copilot_enterprise %}**: [Contact sales](https://github.com/enterprise/contact?ref_product=copilot&ref_type=purchase&ref_style=text) to get started. -> [!IMPORTANT] {% data reusables.copilot.plans.organization-plans-paused %} +* Designed for enterprises using {% data variables.product.prodname_ghe_cloud %}. This plan includes all the features of {% data variables.copilot.copilot_business_short %}, offers a larger monthly pool of {% data variables.product.prodname_ai_credits_short %}, plus additional enterprise-grade capabilities. -* **{% data variables.copilot.copilot_free_short %}** — Try {% data variables.product.prodname_copilot_short %} with limited features and usage. [Start using {% data variables.copilot.copilot_free_short %}](https://github.com/copilot?ref_product=copilot&ref_type=engagement&ref_style=text&ref_plan=free). +> [!NOTE] +> {% data variables.product.prodname_copilot_short %} is not currently available for {% data variables.product.prodname_ghe_server %}. -* **{% data variables.copilot.copilot_student %}** — Get access to {% data variables.product.prodname_copilot_short %}'s features for free. [Access {% data variables.copilot.copilot_student %}](/copilot/how-tos/copilot-on-github/set-up-copilot/enable-copilot/set-up-for-students). +## {% data variables.product.prodname_copilot_short %} plans overview -* **{% data variables.copilot.copilot_pro_short %}** — Get unlimited completions and access to select models. [Subscribe to {% data variables.copilot.copilot_pro_short %}](https://github.com/github-copilot/signup?ref_product=copilot&ref_type=purchase&ref_style=text&ref_plan=pro). +The table below provides an overview of differences between plans. All plans include {% data variables.copilot.copilot_cli_short %} and {% data variables.copilot.github_copilot_app_short %}. -* **{% data variables.copilot.copilot_pro_plus_short %}** — Unlock premium AI models and extra capabilities. [Subscribe to {% data variables.copilot.copilot_pro_plus_short %}](https://github.com/github-copilot/signup?ref_product=copilot&ref_type=purchase&ref_style=text&ref_plan=pro-plus). +{% rowheaders %} -* **{% data variables.copilot.copilot_max_short %}** — Unlock priority access to new AI models and our highest individual monthly allowance of {% data variables.product.prodname_ai_credits_short %}. [Upgrade to {% data variables.copilot.copilot_max_short %}](https://github.com/settings/billing/licensing?ref_product=copilot&ref_type=purchase&ref_style=text&ref_plan=max). +| Plan | Pricing | {% data variables.product.prodname_ai_credits %} | Agents | Models | +| ----------------------------------------------------- | ----------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------- | --------------------------------- | +| {% data variables.copilot.copilot_free_short %} | Free | An allowance of {% data variables.product.prodname_ai_credits %} | Limited | Auto model selection only | +| {% data variables.copilot.copilot_student_short %} | Free | An allowance of {% data variables.product.prodname_ai_credits %} | {% octicon "check" aria-label="Included" %}
Excludes third-party agents | Auto model selection only | +| {% data variables.copilot.copilot_pro_short %} | {% data variables.copilot.cfi_price_per_month %} per month
(free for some users) | Base: {% data variables.copilot.ai_credits_per_user_pro %} | {% octicon "check" aria-label="Included" %} | A selection of models | +| {% data variables.copilot.copilot_pro_plus_short %} | {% data variables.copilot.cpp_price_per_month %} per month. | Base: {% data variables.copilot.ai_credits_per_user_pro_plus %} | {% octicon "check" aria-label="Included" %} | Access to premium models | +| {% data variables.copilot.copilot_max_short %} | {% data variables.copilot.cm_price_per_month %} per month | Base: {% data variables.copilot.ai_credits_per_user_max %} | {% octicon "check" aria-label="Included" %} | Priority access to premium models | +| {% data variables.copilot.copilot_business_short %} | {% data variables.copilot.cfb_price_per_month %} per granted seat per month | Total per user per month: {% data variables.copilot.ai_credits_per_user_business %} | {% octicon "check" aria-label="Included" %} | Access to premium models | +| {% data variables.copilot.copilot_enterprise_short %} | {% data variables.copilot.ce_price_per_month %} per granted seat per month | Total per user per month: {% data variables.copilot.ai_credits_per_user_enterprise %} | {% octicon "check" aria-label="Included" %} | Priority access to premium models | -* **{% data variables.copilot.copilot_business_short %}** — For teams and organizations. [Contact Sales](https://github.com/enterprise/contact?ref_product=copilot&ref_type=purchase&ref_style=text). +{% endrowheaders %} -* **{% data variables.copilot.copilot_enterprise_short %}** — For enterprises that need advanced features and centralized management. [Contact Sales](https://github.com/enterprise/contact?ref_product=copilot&ref_type=purchase&ref_style=text). +Each plan comes with an allowance of {% data variables.product.prodname_ai_credits %}. For more information, including how {% data variables.product.prodname_ai_credits %} work, see [AUTOTITLE](/copilot/concepts/billing/usage-based-billing-for-individuals) and [AUTOTITLE](/copilot/concepts/billing/usage-based-billing-for-organizations-and-enterprises). + +For more detail on what's uniquely available in each plan, see the following sections: +* [Individual plans](#individual-plans) +* [Organization and enterprise plans](#organization-and-enterprise-plans) + +## Individual plans + +The individual plans available are: +* Free plans including {% data variables.copilot.copilot_free_short %} and {% data variables.copilot.copilot_student_short %}. +* Paid plans including {% data variables.copilot.copilot_pro_short %}, {% data variables.copilot.copilot_pro_plus_short %}, and {% data variables.copilot.copilot_max_short %}. + +With these plans you'll receive access to the following features and capabilities. + +> [!NOTE] +> * {% data variables.copilot.copilot_free_short %} plans are only available to individual developers who don't have access to {% data variables.product.prodname_copilot_short %} through an organization or enterprise. +> * Verified teachers, and maintainers of popular open source projects may be eligible for free access to {% data variables.copilot.copilot_pro_short %}. + +### {% data variables.product.prodname_ai_credits %} allowance by plan + +The following table shows what's included with each paid plan. + +{% data reusables.copilot.plans.ai-credits-by-plan %} + +{% data variables.copilot.copilot_free_short %} and {% data variables.copilot.copilot_student_short %} both have an allowance of {% data variables.product.prodname_ai_credits_short %}. + +For more information on how {% data variables.product.prodname_ai_credits %} work, see [AUTOTITLE](/copilot/concepts/billing/usage-based-billing-for-individuals) + +### Inline suggestions and {% data variables.copilot.copilot_chat_short %} + +Inline suggestions are real-time code suggestions with included models in IDEs and {% data variables.copilot.next_edit_suggestions_caps %}. +* Limited to 2000 completions per month on {% data variables.copilot.copilot_free_short %}. + +**{% data variables.copilot.copilot_chat_short %}** features available include: +* {% data variables.copilot.copilot_chat_short %} in IDEs +* Inline chat +* Slash commands +* {% data variables.copilot.copilot_mobile_short %} +* {% data variables.copilot.copilot_chat_dotcom_short %} +* {% data variables.copilot.copilot_chat_short %} in {% data variables.product.prodname_windows_terminal %} +* {% data variables.copilot.copilot_chat_short %} skills in IDEs.[^1] (Not available in {% data variables.copilot.copilot_free_short %}). + +### Models + +On {% data variables.copilot.copilot_free_short %} and {% data variables.copilot.copilot_student_short %} plans, access to models is available through {% data variables.copilot.copilot_auto_model_selection_short %} only. + +{% rowheaders %} + +| Available models | {% data variables.copilot.copilot_pro_short %} | {% data variables.copilot.copilot_pro_plus_short %} | {% data variables.copilot.copilot_max_short %} | +|---------------------------------------------------------|-------------------------------------------------|-----------------------------------------------------|------------------------------------------------| +| {% for model in tables.copilot.model-supported-plans %} | +| {{ model.name }}{% if model.name == 'GPT-5.4 nano' %}[^gpt54nano]{% endif %}{% if model.name == 'Claude Fable 5' %}[^claude-fable-5]{% endif %} | {% if model.pro == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.pro_plus == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.max == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | +| {% endfor %} | + +{% endrowheaders %} + +### Agents + +{% rowheaders %} + +| Agents | {% data variables.copilot.copilot_free_short %} | {% data variables.copilot.copilot_student_short %} | {% data variables.copilot.copilot_pro_short %} | {% data variables.copilot.copilot_pro_plus_short %} | {% data variables.copilot.copilot_max_short %} | +| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------- | ---------------------------------------------- | --------------------------------------------------- | ---------------------------------------------- | +| {% data variables.copilot.copilot_cloud_agent %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Agent mode | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| {% data variables.product.prodname_copilot_short %} code review | Only "Review selection" in {% data variables.product.prodname_vscode_shortname %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Model Context Protocol (MCP) | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Third-party Agents ({% data variables.release-phases.public_preview %}) | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | + +{% endrowheaders %} + +### Customization + +{% rowheaders %} + +| Customization | {% data variables.copilot.copilot_free_short %} | {% data variables.copilot.copilot_student_short %} | {% data variables.copilot.copilot_pro_short %} | {% data variables.copilot.copilot_pro_plus_short %} | {% data variables.copilot.copilot_max_short %} | +| -------------------------------------------------------------------------------- | ----------------------------------------------- | -------------------------------------------------- | ---------------------------------------------- | --------------------------------------------------- | ---------------------------------------------- | +| Repository and personal custom instructions | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Organization custom instructions | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | +| Prompt files | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Model Context Protocol (MCP) | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Block suggestions matching public code | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Exclude specified files from {% data variables.product.prodname_copilot_short %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | +| Organization-wide policy management | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | + +{% endrowheaders %} + +### Other features + +{% rowheaders %} + +| | {% data variables.copilot.copilot_free_short %} | {% data variables.copilot.copilot_student_short %} | {% data variables.copilot.copilot_pro_short %} | {% data variables.copilot.copilot_pro_plus_short %} | {% data variables.copilot.copilot_max_short %} | +| ------------------------------------------------------------------------------------------------ | ----------------------------------------------- | -------------------------------------------------- | ---------------------------------------------- | --------------------------------------------------- | ---------------------------------------------- | +| {% data variables.copilot.copilot_for_prs %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Audit logs | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Content exclusion | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | +| {% data variables.copilot.copilot_cli_short %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| {% data variables.copilot.github_copilot_app_short %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| {% data variables.product.prodname_spark %} ({% data variables.release-phases.public_preview %}) | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | {% octicon "x" aria-label="Not included" %} | + +{% endrowheaders %} + +## Organization and enterprise plans + +The {% data variables.product.prodname_copilot_short %} plans available for organizations and enterprises are: +* {% data variables.copilot.copilot_business_short %} +* {% data variables.copilot.copilot_enterprise_short %} + +With these plans you'll receive access to the following features and capabilities. + +> [!NOTE] With {% data variables.product.prodname_ghe_cloud %}, an enterprise owner chooses the plan for each organization in the enterprise. For guidance on choosing a plan, see [AUTOTITLE](/copilot/tutorials/roll-out-at-scale/assign-licenses/choose-enterprise-plan). + +### {% data variables.product.prodname_ai_credits %} allowance by plan + +| Plan | Price per granted seat per month | {% data variables.product.prodname_ai_credits %} per user per month | +| --- | --- | --- | +| {% data variables.copilot.copilot_business_short %} | {% data variables.copilot.cfb_price_per_month %} | {% data variables.copilot.ai_credits_per_user_business %} | +| {% data variables.copilot.copilot_enterprise_short %}| {% data variables.copilot.ce_price_per_month %} | {% data variables.copilot.ai_credits_per_user_enterprise %} | + +{% data variables.product.prodname_copilot_short %} usage is measured in {% data variables.product.prodname_ai_credits_short %} under usage-based billing. Each license contributes {% data variables.product.prodname_ai_credits_short %} to a shared enterprise pool, and usage beyond the pool is charged at {% data variables.product.prodname_ai_credits_value %} per {% data variables.product.prodname_ai_credit_singular %}. Code completions and {% data variables.copilot.next_edit_suggestions %} are not billed in {% data variables.product.prodname_ai_credits_short %} and remain unlimited for all paid plans. + +For a full explanation of how {% data variables.product.prodname_ai_credits_short %} work, including pooling, additional usage, and what happens when credits run out, see [AUTOTITLE](/copilot/concepts/billing/usage-based-billing-for-organizations-and-enterprises). + +### Inline suggestions and {% data variables.copilot.copilot_chat_short %} + +**Inline suggestions**: Inline suggestion features are available in all plans: + +* Real-time code suggestions with included models +* {% data variables.copilot.next_edit_suggestions_caps %} + +**{% data variables.copilot.copilot_chat_short %}** features are available in all plans: + +* {% data variables.copilot.copilot_chat_short %} in IDEs +* Inline chat +* Slash commands +* {% data variables.copilot.copilot_mobile_short %} +* {% data variables.copilot.copilot_chat_dotcom_short %} +* {% data variables.copilot.copilot_chat_short %} in {% data variables.product.prodname_windows_terminal %} +* {% data variables.copilot.copilot_chat_short %} skills in IDEs[^1] + +### Models + +{% rowheaders %} + +| Available models | {% data variables.copilot.copilot_business_short %} | {% data variables.copilot.copilot_enterprise_short %} | +|---------------------------------------------------------|-----------------------------------------------------|-------------------------------------------------------| +| {% for model in tables.copilot.model-supported-plans %} | +| {{ model.name }}{% if model.name == 'GPT-5.4 nano' %}[^gpt54nano]{% endif %}{% if model.name == 'Claude Fable 5' %}[^claude-fable-5]{% endif %} | {% if model.business == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | {% if model.enterprise == true %}{% octicon "check" aria-label="Included" %}{% else %}{% octicon "x" aria-label="Not included" %}{% endif %} | +| {% endfor %} | + +{% endrowheaders %} + +[^gpt54nano]: GPT-5.4 nano is currently only available in the Codex {% data variables.product.prodname_vscode %} extension ({% data variables.copilot.copilot_pro_plus_short %} only) and is not available in {% data variables.copilot.copilot_chat_short %}. + +### Agents + +{% rowheaders %} + +| Agents | {% data variables.copilot.copilot_business_short %} | {% data variables.copilot.copilot_enterprise_short %} | +| ----------------------------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------- | +| {% data variables.copilot.copilot_cloud_agent %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Agent mode | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| {% data variables.product.prodname_copilot_short %} code review | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Model Context Protocol (MCP) | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Third-party Agents ({% data variables.release-phases.public_preview %}) | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | + +{% endrowheaders %} + +### Customization + +{% rowheaders %} + +| Customization | {% data variables.copilot.copilot_business_short %} | {% data variables.copilot.copilot_enterprise_short %} | +| -------------------------------------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------- | +| Repository and personal custom instructions | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Organization custom instructions | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Prompt files | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Model Context Protocol (MCP) | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Block suggestions matching public code | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Exclude specified files from {% data variables.product.prodname_copilot_short %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Organization-wide policy management | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | + +{% endrowheaders %} + +### Other features + +{% rowheaders %} + +| | {% data variables.copilot.copilot_business_short %} | {% data variables.copilot.copilot_enterprise_short %} | +| ------------------------------------------------------------------------------------------------ | --------------------------------------------------- | ----------------------------------------------------- | +| {% data variables.copilot.copilot_for_prs %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Audit logs | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| Content exclusion | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| {% data variables.copilot.copilot_cli_short %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| {% data variables.copilot.github_copilot_app_short %} | {% octicon "check" aria-label="Included" %} | {% octicon "check" aria-label="Included" %} | +| {% data variables.product.prodname_spark %} ({% data variables.release-phases.public_preview %}) | {% octicon "x" aria-label="Not included" %} | {% octicon "check" aria-label="Included" %} | + +{% endrowheaders %} + +## Further reading + +* To compare feature support across IDEs, see [AUTOTITLE](/copilot/reference/copilot-feature-matrix). +* To compare models supported across plans, features, and IDEs, see [AUTOTITLE](/copilot/reference/ai-models/supported-models). + +[^1]: {% data variables.copilot.copilot_chat_short %} skills in IDEs is available in {% data variables.product.prodname_vscode %} and {% data variables.product.prodname_vs %}. +[^claude-fable-5]: When {% data variables.copilot.copilot_claude_fable_5 %} is used, Anthropic retains data, including prompts and outputs, to operate safety classifiers that detect harmful use. Other Claude models in {% data variables.product.prodname_copilot %} remain covered by {% data variables.product.github %}'s existing data retention agreements, as documented at [AUTOTITLE](/copilot/reference/ai-models/model-hosting#anthropic-models). Enterprise and business users need to enable the {% data variables.copilot.copilot_claude_fable_5 %} model to make it available for your organization. You can read more about Anthropic's data handling practices for this model under section F of their [Service Specific Terms](https://www.anthropic.com/legal/service-specific-terms). To enable {% data variables.copilot.copilot_claude_fable_5 %}, see [AUTOTITLE](/copilot/how-tos/copilot-on-github/set-up-copilot/configure-access-to-ai-models). diff --git a/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/monitor-agentic-activity.md b/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/monitor-agentic-activity.md index 47e304d35eb2..62c0ea639fd7 100644 --- a/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/monitor-agentic-activity.md +++ b/content/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/monitor-agentic-activity.md @@ -34,3 +34,11 @@ Track agentic activity on {% data variables.product.github %} or through streami To enable streaming for {% data variables.product.prodname_copilot_short %} agent session events and configure a streaming destination from your enterprise audit log settings, see [AUTOTITLE](/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/streaming-the-audit-log-for-your-enterprise#enabling-audit-log-streaming-of-copilot-agent-session-events). In addition to streaming, you can also retrieve Copilot usage data through the REST API. See [AUTOTITLE](/rest/copilot/copilot-usage-metrics#get-copilot-usage-records-for-an-enterprise). + +## Enabling OpenTelemetry + +OpenTelemetry (OTel) is an open source observability framework. It provides a standard way to collect telemetry events and metrics and export them to compatible observability tools. + +When you enable OTel monitoring, you can send data from users' {% data variables.product.prodname_copilot_short %} clients to an OTel-compatible backend. This lets you analyze agent sessions and understand agent usage across your enterprise. + +For more information, see [AUTOTITLE](/copilot/concepts/agents/opentelemetry). diff --git a/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-teams.md b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-teams.md index 79f98ef085bc..5b0c14d23f93 100644 --- a/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-teams.md +++ b/content/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-teams.md @@ -18,17 +18,45 @@ redirect_from: > * This feature is currently in {% data variables.release-phases.public_preview %} and subject to change. > * {% data variables.product.prodname_copilot %} uses AI. Check for mistakes. See [AUTOTITLE](/copilot/responsible-use/agents). -The {% data variables.product.github %} integration in Microsoft Teams allows you to interact with {% data variables.copilot.copilot_cloud_agent %} all from within your Teams channels. From within a Teams thread you can initiate {% data variables.copilot.copilot_cloud_agent_short %} sessions and open pull requests, using the context of your conversation. +The {% data variables.product.github %} integration in Microsoft Teams allows you to interact with {% data variables.copilot.copilot_cloud_agent %} all from within your Teams conversations. Within Teams you can initiate {% data variables.copilot.copilot_cloud_agent_short %} sessions to investigate, plan, write code, and create issues and pull requests, using the context of your conversation. Your team's collaborative decisions stay connected to your code, bridging the gap between where discussions happen and where implementation lives. For information about additional {% data variables.product.prodname_copilot_short %} integrations, see [AUTOTITLE](/copilot/concepts/tools/about-copilot-integrations). -> [!NOTE] -> When you mention @{% data variables.product.github %} in a Teams thread, the agent will capture the entire thread as context for your request, understanding and implementing solutions based on the discussion. This context is stored in the pull request. +## Security considerations + +Before you @mention {% data variables.product.github %} in Teams, consider that {% data variables.copilot.copilot_cloud_agent %} will capture the entire thread as context for your request, understanding and implementing solutions based on the discussion. This context is stored in the artifacts the agent generates. If you want to limit the context, you can send a direct message to the {% data variables.product.github %} app for Teams instead. + +## Understanding collaborative sessions, permissions, and sandboxes + +The identity {% data variables.product.prodname_copilot_short %} uses depends on whether you interact with it in a direct message or a shared context. + +* When you use {% data variables.product.prodname_copilot_short %} in a direct message, it can take actions for you, such as creating pull requests or issues, as well as answer questions. It uses the permissions of your linked {% data variables.product.github %} personal account to take these actions. + +* When you use {% data variables.product.prodname_copilot_short %} in a shared context, such as a group thread or channel, {% data variables.product.prodname_copilot_short %} creates artifacts, such as pull requests, under its app identity rather than your personal account. + + > [!NOTE] + > {% data reusables.copilot.cloud-agent.unattributed-additional-approval-note %} + +Only users with **write** access to a repository can trigger {% data variables.product.prodname_copilot_short %} to make changes, but any conversation participant can provide input. Guest members of a workspace, and outside collaborators to repositories are not able to start or steer a session with {% data variables.product.prodname_copilot_short %} in Teams. + +When {% data variables.copilot.copilot_cloud_agent %} starts work on a task from Teams, {% data variables.product.prodname_copilot_short %} continues working asynchronously in a **secure cloud sandbox**, and posts the result when it's ready. You can keep steering from Teams, or continue the work on the agent-generated artifacts in {% data variables.product.github %}, the terminal, or your preferred code editor. + +{% data variables.product.prodname_copilot_short %} uses all messages in the conversation to inform the work. The entire thread becomes the decision-making context for the artifact. + +When you ask {% data variables.product.prodname_copilot_short %} to perform a task, it will display details about the session, such as the working repository, issue or pull request link, and a task status or summary. + +### Secure cloud sandboxes + +When {% data variables.copilot.copilot_cloud_agent %} starts work on a task from Teams, {% data variables.product.prodname_copilot_short %} continues working asynchronously in a **secure cloud sandbox**, and posts the result when it's ready. + +You can keep steering from Teams, or continue the work on the agent-generated artifacts in {% data variables.product.github %}, the terminal, or your preferred code editor. ## Prerequisites * You must have a {% data variables.product.github %} account with access to {% data variables.product.prodname_copilot_short %} through a paid {% data variables.product.prodname_copilot_short %} plan. -* You must have a Teams account and be a member of a channel. +* You must have a Teams account. +* You must have Microsoft Public Developer Preview enabled for your Microsoft Teams client, see [Public developer preview for Teams](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/dev-preview/developer-preview-intro) in the Microsoft Learn documentation. +{% data reusables.copilot.cloud-agent.cloud-sandboxes-prerequisite-teams %} ## Installing the {% data variables.product.github %} app in Teams @@ -40,32 +68,62 @@ The {% data variables.product.github %} app only needs to be installed once in a ## Connecting the {% data variables.product.github %} app to your {% data variables.product.github %} account -The first time you use the {% data variables.product.github %} app in Teams, you need to connect it to your {% data variables.product.github %} account and set a default repository. The default repository provides the context that {% data variables.product.prodname_copilot_short %} uses when responding to prompts, and it’s also where pull requests created by {% data variables.copilot.copilot_cloud_agent %} sessions will be opened unless you specify a repository in your prompt. +The first time you use the {% data variables.product.github %} app in Teams, you need to connect it to your {% data variables.product.github %} account. Then if prompted, set a default repository. + +The default repository provides the context that {% data variables.product.prodname_copilot_short %} uses when responding to prompts, and it's also where issues and pull requests created by {% data variables.copilot.copilot_cloud_agent %} sessions will be opened unless you specify a repository in your prompt. + +To get started: + +1. In Teams, @mention the app in a message by typing `@{% data variables.product.github %}`. +1. Follow the prompts to connect your {% data variables.product.github %} account, and if prompted, optionally set a default repository. +1. To see what else you can do, in the thread, @mention the app by typing `@{% data variables.product.github %} help`. + +## Starting a {% data variables.product.prodname_copilot %} session from your team conversation + +@Mention the app in any Teams chat by typing `@{% data variables.product.github %}` followed by your task. You can summon the agent for any repository where you have `write` access. The agent responds with a summary of planned changes and a link to the artifacts it creates. + +For example, to ask the agent to create a pull request on a particular branch in a repository, you can type: + +```text +@{% data variables.product.github %} Create a pull request to...YOUR_PROMPT repo=OWNER/REPO_NAME branch=BRANCH_NAME +``` + +The `repo` parameter tells {% data variables.product.prodname_copilot_short %} which repository to use, and the `branch` parameter specifies an existing branch to use as the base branch for a pull request. + +## Iterating on work in the thread + +To refine the pull request, @mention `@{% data variables.product.github %}` in the same thread with your requested changes. {% data variables.product.prodname_copilot_short %} incorporates all messages since the previous @mention to iterate on the work, keeping the discussion and implementation connected. + +## Creating issues with {% data variables.product.prodname_copilot_short %} + +You can ask {% data variables.product.prodname_copilot_short %} to create {% data variables.product.github %} issues directly from Teams, turning conversations into actionable tasks. Just describe what you need in natural language, and {% data variables.product.prodname_copilot_short %} creates the issue for you. + +You can create a single issue or multiple issues at once with child-parent relationships. + +When you @mention the app, it uses the full thread history as context for the issues it creates. To keep the context focused, consider starting a new thread or sending a direct message. + +## Customizing {% data variables.copilot.copilot_cloud_agent %} in Teams -To get started, mention `@{% data variables.product.github %} ` in any Teams thread. The app will guide you through signing in and setting a default repository. Or you can connect your {% data variables.product.github %} account and set the default repository manually by following these steps: +You can customize how {% data variables.copilot.copilot_cloud_agent %} works in your channels and threads using the `settings` parameter. For example, you can set a default repository for a channel. -1. In Teams, mention the app in a thread by typing `@{% data variables.product.github %}`. -1. Click **signin** from the list of suggestions. -1. Follow the prompts to sign in to your {% data variables.product.github %} account. -1. In the thread, mention the app by typing `@{% data variables.product.github %}`. -1. Click **settings** to set the default repository. +1. To see and change your channel settings, mention the app in a message by typing: -## Using the {% data variables.product.prodname_copilot_short %} app in Teams + ```text + @{% data variables.product.github %} settings + ``` -You can interact with the {% data variables.product.github %} app in Teams by mentioning it in a thread. The agent will respond to your messages and perform tasks based on your requests. Only users with **write** access to the default repository—or the repository specified in their prompt—can trigger {% data variables.copilot.copilot_cloud_agent %} to work. Contributors to the thread without **write** access can help guide {% data variables.product.prodname_copilot_short %} by providing input to the conversation, which will be used as context when making changes in the pull request. +1. Then follow the prompts to make your changes. -1. In Teams, mention the app in a thread by typing @{% data variables.product.github %}. -1. Type your message or request, then send it. Optionally, you can specify a repository or branch using the following syntax: +### Setting a default repository for a channel - ```text - @GitHub Add "Hello World" to the README in repo=REPO_OWNER/REPO_NAME branch=BRANCH_NAME - ``` +You can set a default repository for each private or public channel. You cannot set a default repository for direct messages with {% data variables.product.prodname_copilot_short %}. - The `repo` parameter tells {% data variables.copilot.copilot_cloud_agent %} which repository to use for the request, and the `branch` parameter specifies an existing branch of the repository that should be used as the base branch for a pull request. By default, {% data variables.product.prodname_copilot_short %} uses your configured default repository and the repository’s default branch. +If a channel does not have a default repository, {% data variables.product.prodname_copilot_short %} sets the repository you use in your first session in that channel as the channel's default repository. - {% data variables.product.prodname_copilot_short %} will initiate a {% data variables.copilot.copilot_cloud_agent_short %} session and respond with a summary of the changes it plans to make, including a link to the pull request it has created in the repository. +When you do not specify a repository or branch, {% data variables.product.prodname_copilot_short %} uses the channel's default repository and that repository's default branch. -You can continue to iterate on the pull request in the same Teams thread. Mention @{% data variables.product.github %} with your suggested change, and the {% data variables.copilot.copilot_cloud_agent %} will use all of the messages in the thread since the previous mention to iterate on the existing pull request. +1. In the channel, type `@{% data variables.product.github %} settings` and send the message. +1. Follow the prompts to select a default repository for the channel. ## Further reading diff --git a/content/copilot/how-tos/copilot-on-github/use-copilot-agents/research-plan-iterate.md b/content/copilot/how-tos/copilot-on-github/use-copilot-agents/research-plan-iterate.md index 2429cb16b921..bdf874188611 100644 --- a/content/copilot/how-tos/copilot-on-github/use-copilot-agents/research-plan-iterate.md +++ b/content/copilot/how-tos/copilot-on-github/use-copilot-agents/research-plan-iterate.md @@ -23,13 +23,13 @@ redirect_from: Sessions do not create pull requests automatically. To create one immediately, include that in your prompt—for example, "Create a pull request to ...". -> [!NOTE] These capabilities are only available with {% data variables.copilot.copilot_cloud_agent %} on {% data variables.product.prodname_dotcom_the_website %}. {% data variables.copilot.copilot_cloud_agent_short_cap_c %} integrations (such as Azure Boards, JIRA, Linear, Slack, or Teams) only support creating a pull request directly. +> [!NOTE] These capabilities are only available with {% data variables.copilot.copilot_cloud_agent %} on {% data variables.product.prodname_dotcom_the_website %}, and in {% data variables.release-phases.public_preview %} for the Teams integration. Other {% data variables.copilot.copilot_cloud_agent_short_cap_c %} integrations (such as Azure Boards, JIRA, Linear, or Slack) only support creating a pull request directly. ## Perform deep research Ask {% data variables.copilot.copilot_cloud_agent %} questions about a repository to understand how it works, find where to make a change, or confirm assumptions before planning. -1. Start a task from the agents tab, panel, dashboard, or {% data variables.copilot.copilot_chat_short %}. See [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/kick-off-a-task). +1. Start a task from the agents tab, panel, dashboard, {% data variables.copilot.copilot_chat_short %}, or from within a supported integration. See [AUTOTITLE](/copilot/how-tos/copilot-on-github/use-copilot-agents/kick-off-a-task). 1. Ask a question about the repository. For example: `Investigate performance issues in this app and suggest improvements.` diff --git a/content/copilot/how-tos/copilot-sdk/auth/authenticate.md b/content/copilot/how-tos/copilot-sdk/auth/authenticate.md index 622a8be3ac3b..7b6068443725 100644 --- a/content/copilot/how-tos/copilot-sdk/auth/authenticate.md +++ b/content/copilot/how-tos/copilot-sdk/auth/authenticate.md @@ -21,7 +21,7 @@ contentType: how-tos | Method | Use Case | Copilot Subscription Required | |--------|----------|-------------------------------| | [GitHub Signed-in User](#github-signed-in-user) | Interactive apps where users sign in with GitHub | Yes | -| [OAuth GitHub App](#oauth-github-app) | Apps acting on behalf of users via OAuth | Yes | +| [GitHub OAuth App](#github-oauth-app) | Apps acting on behalf of users via OAuth | Yes | | [Environment Variables](#environment-variables) | CI/CD, automation, server-to-server | Yes | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/auth/server-to-server-tokens) | Organization-attributed automation and direct organization billing | No user subscription; organization policy required | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/auth/byok) | Using your own API keys (Microsoft Foundry, OpenAI, and more) | No | @@ -38,67 +38,65 @@ This is the default authentication method when running the Copilot CLI interacti **SDK Configuration:** {% codetabs %} -{% codetab typescript %} +{% codetab dotnet %} -```typescript -import { CopilotClient } from "@github/copilot-sdk"; +```csharp +using GitHub.Copilot; // Default: uses logged-in user credentials -const client = new CopilotClient(); +await using CopilotClient client = new(); ``` {% endcodetab %} -{% codetab python %} +{% codetab go %} -```python -from copilot import CopilotClient +```golang +import copilot "github.com/github/copilot-sdk/go" -# Default: uses logged-in user credentials -client = CopilotClient() -await client.start() +// Default: uses logged-in user credentials +client := copilot.NewClient(nil) ``` {% endcodetab %} -{% codetab go %} - -```golang -package main +{% codetab java %} -import copilot "github.com/github/copilot-sdk/go" +```java +import com.github.copilot.CopilotClient; -func main() { - // Default: uses logged-in user credentials - client := copilot.NewClient(nil) - _ = client -} +// Default: uses logged-in user credentials +var client = new CopilotClient(); +client.start().get(); ``` -```golang -import copilot "github.com/github/copilot-sdk/go" +{% endcodetab %} +{% codetab python %} -// Default: uses logged-in user credentials -client := copilot.NewClient(nil) +```python +from copilot import CopilotClient + +# Default: uses logged-in user credentials +client = CopilotClient() +await client.start() ``` {% endcodetab %} -{% codetab dotnet %} +{% codetab rust %} -```csharp -using GitHub.Copilot; +```rust +use github_copilot_sdk::{Client, ClientOptions}; // Default: uses logged-in user credentials -await using var client = new CopilotClient(); +let client = Client::start(ClientOptions::default()).await?; ``` {% endcodetab %} -{% codetab java %} +{% codetab typescript %} -```java -import com.github.copilot.CopilotClient; +```typescript +import { CopilotClient } from "@github/copilot-sdk"; // Default: uses logged-in user credentials -var client = new CopilotClient(); -client.start().get(); +const client = new CopilotClient(); ``` {% endcodetab %} @@ -109,93 +107,42 @@ client.start().get(); * Development and testing environments * Any scenario where a user can sign in interactively -## OAuth GitHub App +## GitHub OAuth App Use an OAuth GitHub App to authenticate users through your application and pass their credentials to the SDK. This enables your application to make Copilot API requests on behalf of users who authorize your app. **How it works:** 1. User authorizes your OAuth GitHub App 1. Your app receives a user access token (`gho_` or `ghu_` prefix) -1. Pass the token to the SDK via `gitHubToken` option +1. Pass the token to the SDK through its client configuration **SDK Configuration:** {% codetabs %} -{% codetab typescript %} +{% codetab dotnet %} -```typescript -import { CopilotClient } from "@github/copilot-sdk"; +```csharp +using GitHub.Copilot; -const client = new CopilotClient({ - gitHubToken: userAccessToken, // Token from OAuth flow - useLoggedInUser: false, // Don't use stored CLI credentials +await using var client = new CopilotClient(new CopilotClientOptions +{ + GitHubToken = userAccessToken, // Token from OAuth flow + UseLoggedInUser = false, // Don't use stored CLI credentials }); ``` -{% endcodetab %} -{% codetab python %} - -```python -from copilot import CopilotClient - -client = CopilotClient({ - "github_token": user_access_token, # Token from OAuth flow - "use_logged_in_user": False, # Don't use stored CLI credentials -}) -await client.start() -``` - {% endcodetab %} {% codetab go %} -```golang -package main - -import copilot "github.com/github/copilot-sdk/go" - -func main() { - userAccessToken := "token" - client := copilot.NewClient(&copilot.ClientOptions{ - GitHubToken: userAccessToken, - UseLoggedInUser: copilot.Bool(false), - }) - _ = client -} -``` - ```golang import copilot "github.com/github/copilot-sdk/go" client := copilot.NewClient(&copilot.ClientOptions{ - GitHubToken: userAccessToken, // Token from OAuth flow - UseLoggedInUser: copilot.Bool(false), // Don't use stored CLI credentials + GitHubToken: userAccessToken, // Token from OAuth flow + UseLoggedInUser: copilot.Bool(false), // Don't use stored CLI credentials }) ``` -{% endcodetab %} -{% codetab dotnet %} - -```csharp -using GitHub.Copilot; - -var userAccessToken = "token"; -await using var client = new CopilotClient(new CopilotClientOptions -{ - GitHubToken = userAccessToken, - UseLoggedInUser = false, -}); -``` - -```csharp -using GitHub.Copilot; - -await using var client = new CopilotClient(new CopilotClientOptions -{ - GitHubToken = userAccessToken, // Token from OAuth flow - UseLoggedInUser = false, // Don't use stored CLI credentials -}); -``` - {% endcodetab %} {% codetab java %} @@ -212,6 +159,44 @@ var client = new CopilotClient(new CopilotClientOptions() client.start().get(); ``` +{% endcodetab %} +{% codetab python %} + +```python +from copilot import CopilotClient + +client = CopilotClient({ + "github_token": user_access_token, # Token from OAuth flow + "use_logged_in_user": False, # Don't use stored CLI credentials +}) +await client.start() +``` + +{% endcodetab %} +{% codetab rust %} + +```rust +use github_copilot_sdk::{Client, ClientOptions}; + +let client = Client::start( + ClientOptions::default() + .with_github_token(user_access_token) + .with_use_logged_in_user(false), +).await?; +``` + +{% endcodetab %} +{% codetab typescript %} + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +const client = new CopilotClient({ + gitHubToken: userAccessToken, // Token from OAuth flow + useLoggedInUser: false, // Don't use stored CLI credentials +}); +``` + {% endcodetab %} {% endcodetabs %} @@ -228,6 +213,8 @@ client.start().get(); * SaaS applications building on top of Copilot * Any multi-user application where you need to make requests on behalf of different users +For more information, see [AUTOTITLE](/copilot/how-tos/copilot-sdk/setup/github-oauth). + ## Environment variables For automation, CI/CD pipelines, and server-to-server scenarios, you can authenticate using environment variables. @@ -248,13 +235,34 @@ For organization-attributed automation that should not use a user's personal acc No code changes needed—the SDK automatically detects environment variables: {% codetabs %} -{% codetab typescript %} +{% codetab dotnet %} -```typescript -import { CopilotClient } from "@github/copilot-sdk"; +```csharp +using GitHub.Copilot; // Token is read from environment variable automatically -const client = new CopilotClient(); +await using CopilotClient client = new(); +``` + +{% endcodetab %} +{% codetab go %} + +```golang +import copilot "github.com/github/copilot-sdk/go" + +// Token is read from environment variable automatically +client := copilot.NewClient(nil) +``` + +{% endcodetab %} +{% codetab java %} + +```java +import com.github.copilot.CopilotClient; + +// Token is read from environment variable automatically +var client = new CopilotClient(); +client.start().get(); ``` {% endcodetab %} @@ -268,6 +276,26 @@ client = CopilotClient() await client.start() ``` +{% endcodetab %} +{% codetab rust %} + +```rust +use github_copilot_sdk::{Client, ClientOptions}; + +// Token is read from environment variable automatically +let client = Client::start(ClientOptions::default()).await?; +``` + +{% endcodetab %} +{% codetab typescript %} + +```typescript +import { CopilotClient } from "@github/copilot-sdk"; + +// Token is read from environment variable automatically +const client = new CopilotClient(); +``` + {% endcodetab %} {% endcodetabs %} @@ -307,66 +335,27 @@ For multi-user server mode, pass a per-session `gitHubToken` so each session run ## Disabling auto-login -To prevent the SDK from automatically using stored credentials or `gh` CLI auth, use the `useLoggedInUser: false` option: +To prevent the SDK from automatically using stored credentials or `gh` CLI auth, configure it to disable logged-in-user fallback: {% codetabs %} -{% codetab typescript %} +{% codetab dotnet %} -```typescript -const client = new CopilotClient({ - useLoggedInUser: false, // Only use explicit tokens +```csharp +await using var client = new CopilotClient(new CopilotClientOptions +{ + UseLoggedInUser = false, // Only use explicit tokens }); ``` -{% endcodetab %} -{% codetab python %} - -```python -from copilot import CopilotClient - -client = CopilotClient({ - "use_logged_in_user": False, -}) -``` - -```python -client = CopilotClient({ - "use_logged_in_user": False, # Only use explicit tokens -}) -``` - {% endcodetab %} {% codetab go %} -```golang -package main - -import copilot "github.com/github/copilot-sdk/go" - -func main() { - client := copilot.NewClient(&copilot.ClientOptions{ - UseLoggedInUser: copilot.Bool(false), - }) - _ = client -} -``` - ```golang client := copilot.NewClient(&copilot.ClientOptions{ UseLoggedInUser: copilot.Bool(false), // Only use explicit tokens }) ``` -{% endcodetab %} -{% codetab dotnet %} - -```csharp -await using var client = new CopilotClient(new CopilotClientOptions -{ - UseLoggedInUser = false, // Only use explicit tokens -}); -``` - {% endcodetab %} {% codetab java %} @@ -380,6 +369,35 @@ var client = new CopilotClient(new CopilotClientOptions() client.start().get(); ``` +{% endcodetab %} +{% codetab python %} + +```python +client = CopilotClient({ + "use_logged_in_user": False, # Only use explicit tokens +}) +``` + +{% endcodetab %} +{% codetab rust %} + +```rust +use github_copilot_sdk::{Client, ClientOptions}; + +let client = Client::start( + ClientOptions::default().with_use_logged_in_user(false), +).await?; +``` + +{% endcodetab %} +{% codetab typescript %} + +```typescript +const client = new CopilotClient({ + useLoggedInUser: false, // Only use explicit tokens +}); +``` + {% endcodetab %} {% endcodetabs %} diff --git a/content/copilot/how-tos/copilot-sdk/auth/byok.md b/content/copilot/how-tos/copilot-sdk/auth/byok.md index 368a3266da6f..a5ec405399ee 100644 --- a/content/copilot/how-tos/copilot-sdk/auth/byok.md +++ b/content/copilot/how-tos/copilot-sdk/auth/byok.md @@ -534,23 +534,6 @@ const session = await client.createSession({ For Azure OpenAI endpoints (`*.openai.azure.com`), use the correct type: - - -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); -const session = await client.createSession({ - model: "gpt-5.4", - provider: { - type: "azure", - baseUrl: "https://my-resource.openai.azure.com", - }, -}); -``` - - - ```typescript // ❌ Wrong: Using "openai" type with native Azure endpoint provider: { @@ -567,23 +550,6 @@ provider: { However, if your Microsoft Foundry deployment provides an OpenAI-compatible endpoint path (for example, `/openai/v1/`), use `type: "openai"`: - - -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); -const session = await client.createSession({ - model: "gpt-5.4", - provider: { - type: "openai", - baseUrl: "https://your-resource.openai.azure.com/openai/v1/", - }, -}); -``` - - - ```typescript // ✅ Correct: OpenAI-compatible Microsoft Foundry endpoint provider: { diff --git a/content/copilot/how-tos/copilot-sdk/features/citations.md b/content/copilot/how-tos/copilot-sdk/features/citations.md new file mode 100644 index 000000000000..90deb65d39bc --- /dev/null +++ b/content/copilot/how-tos/copilot-sdk/features/citations.md @@ -0,0 +1,448 @@ +--- +title: Citations +shortTitle: Citations +intro: >- + Citations link spans of an assistant response back to the sources that support + them. Turn on `enableCitations` when you create or resume a session, then read + the `citations` payload on `assistant.message` events to render footnotes, + source lists, or inline links. +versions: + fpt: '*' + ghec: '*' +contentType: how-tos +--- + + + + +> [!WARNING] +> Citations are experimental. The option name, event payload, and provider coverage can change in a future release. + +## How citations work + +Citations are produced by the model provider, not by the SDK. The flow has three parts: + +1. Your application supplies citable material, such as a document attachment or a tool result that carries source content. +1. The runtime marks that material as citable on the wire when `enableCitations` is on. For Anthropic models, file attachments are sent as `document` blocks with citations enabled. +1. The model returns citation metadata, and the runtime normalizes it into a provider-agnostic `citations` object on the final `assistant.message` event. + +Provider support is limited. The `provider` field on each source records where the citation came from: + +| Provider value | Meaning | +|---|---| +| `anthropic` | Citation produced by an Anthropic (Claude) model response | +| `openai` | Citation produced by an OpenAI model response | +| `client` | Citation synthesized by the runtime from tool output | + +> [!NOTE] +> Turning on `enableCitations` does not guarantee that a response contains citations. Models emit them only when the response is grounded in citable source material. Always treat the `citations` field as optional. + +## Enable citations on a session + +Set the option on session create, and set it again on resume if you want citations after a restart. + +{% codetabs %} +{% codetab typescript %} + + + +```typescript +const session = await client.createSession({ + onPermissionRequest: approveAll, + enableCitations: true, +}); + +const resumed = await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + enableCitations: true, +}); +``` + +{% endcodetab %} +{% codetab python %} + + + +```python +session = await client.create_session( + on_permission_request=PermissionHandler.approve_all, + enable_citations=True, +) + +resumed = await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + enable_citations=True, +) +``` + +{% endcodetab %} +{% codetab go %} + + + +```golang +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + EnableCitations: copilot.Bool(true), +}) + +resumed, err := client.ResumeSession(ctx, session.SessionID, &copilot.ResumeSessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + EnableCitations: copilot.Bool(true), +}) +``` + +{% endcodetab %} +{% codetab dotnet %} + + + +```csharp +var session = await client.CreateSessionAsync(new SessionConfig +{ + OnPermissionRequest = PermissionHandler.ApproveAll, + EnableCitations = true, +}); + +var resumed = await client.ResumeSessionAsync(session.SessionId, new ResumeSessionConfig +{ + OnPermissionRequest = PermissionHandler.ApproveAll, + EnableCitations = true, +}); +``` + +{% endcodetab %} +{% codetab java %} + + + +```java +CopilotSession session = client + .createSession(new SessionConfig() + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setEnableCitations(true)) + .get(); + +CopilotSession resumed = client + .resumeSession(session.getSessionId(), new ResumeSessionConfig() + .setOnPermissionRequest(PermissionHandler.APPROVE_ALL) + .setEnableCitations(true)) + .get(); +``` + +{% endcodetab %} +{% codetab rust %} + + + +```rust +let session = client + .create_session( + SessionConfig::new() + .approve_all_permissions() + .with_enable_citations(true), + ) + .await?; + +let resumed = client + .resume_session( + ResumeSessionConfig::new(session.id().clone()) + .approve_all_permissions() + .with_enable_citations(true), + ) + .await?; +``` + +{% endcodetab %} +{% endcodetabs %} + +## Read citations from assistant messages + +Citations arrive on the final `assistant.message` event, not on `assistant.message_delta` events. Wait for the final message before you render source markers. + +{% codetabs %} +{% codetab typescript %} + + + +```typescript +session.on((event) => { + if (event.type !== "assistant.message" || !event.data.citations) { + return; + } + + const { sources, spans } = event.data.citations; + const sourceById = new Map(sources.map((source) => [source.id, source])); + + for (const span of spans) { + const quoted = event.data.content.slice(span.startIndex, span.endIndex); + for (const reference of span.references) { + const source = sourceById.get(reference.sourceId); + const label = source?.title ?? source?.url ?? source?.path ?? source?.id; + console.log(`"${quoted}" — ${label}`); + } + } +}); +``` + +{% endcodetab %} +{% codetab python %} + + + +```python +from copilot.session_events import SessionEventType + +def utf16_slice(text: str, start: int, end: int) -> str: + """Slice by UTF-16 code units, which is how span offsets are measured.""" + units = text.encode("utf-16-le") + return units[start * 2 : end * 2].decode("utf-16-le") + +def handle(event): + if event.type != SessionEventType.ASSISTANT_MESSAGE or not event.data.citations: + return + + sources = {source.id: source for source in event.data.citations.sources} + + for span in event.data.citations.spans: + quoted = utf16_slice(event.data.content, span.start_index, span.end_index) + for reference in span.references: + source = sources[reference.source_id] + label = source.title or source.url or source.path or source.id + print(f'"{quoted}" — {label}') + +session.on(handle) +``` + +{% endcodetab %} +{% codetab go %} + + + +```golang +// import "unicode/utf16" + +session.On(func(event copilot.SessionEvent) { + d, ok := event.Data.(*copilot.AssistantMessageData) + if !ok || d.Citations == nil { + return + } + + sources := map[string]copilot.CitationSource{} + for _, source := range d.Citations.Sources { + sources[source.ID] = source + } + + // Span offsets are UTF-16 code units, so index the UTF-16 view of the content. + units := utf16.Encode([]rune(d.Content)) + + for _, span := range d.Citations.Spans { + quoted := string(utf16.Decode(units[span.StartIndex:span.EndIndex])) + for _, reference := range span.References { + source := sources[reference.SourceID] + label := source.ID + switch { + case source.Title != nil: + label = *source.Title + case source.URL != nil: + label = *source.URL + case source.Path != nil: + label = *source.Path + } + fmt.Printf("%q — %s\n", quoted, label) + } + } +}) +``` + +{% endcodetab %} +{% codetab dotnet %} + + + +```csharp +session.On(evt => +{ + if (evt is not AssistantMessageEvent message || message.Data.Citations is null) + { + return; + } + + var sources = message.Data.Citations.Sources.ToDictionary(source => source.Id); + + foreach (var span in message.Data.Citations.Spans) + { + var quoted = message.Data.Content[(int)span.StartIndex..(int)span.EndIndex]; + foreach (var reference in span.References) + { + var source = sources[reference.SourceId]; + var label = source.Title ?? source.Url ?? source.Path ?? source.Id; + Console.WriteLine($"\"{quoted}\" — {label}"); + } + } +}); +``` + +{% endcodetab %} +{% codetab java %} + + + +```java +session.on(AssistantMessageEvent.class, event -> { + Citations citations = event.getData().citations(); + if (citations == null) { + return; + } + + Map sources = citations.sources().stream() + .collect(Collectors.toMap(CitationSource::id, source -> source)); + + for (CitationSpan span : citations.spans()) { + String quoted = event.getData().content() + .substring(span.startIndex().intValue(), span.endIndex().intValue()); + for (CitationReference reference : span.references()) { + CitationSource source = sources.get(reference.sourceId()); + String label = source.title() != null ? source.title() + : source.url() != null ? source.url() + : source.path() != null ? source.path() + : source.id(); + System.out.printf("\"%s\" — %s%n", quoted, label); + } + } +}); +``` + +{% endcodetab %} +{% codetab rust %} + + + +```rust +use github_copilot_sdk::session_events::AssistantMessageData; +use std::collections::HashMap; + +let mut events = session.subscribe(); + +while let Ok(event) = events.recv().await { + if event.event_type != "assistant.message" { + continue; + } + + let Some(data) = event.typed_data::() else { + continue; + }; + let Some(citations) = data.citations.as_ref() else { + continue; + }; + + let sources: HashMap<&str, _> = citations + .sources + .iter() + .map(|source| (source.id.as_str(), source)) + .collect(); + + // Span offsets are UTF-16 code units, so index the UTF-16 view of the content. + let units: Vec = data.content.encode_utf16().collect(); + + for span in &citations.spans { + let quoted = String::from_utf16_lossy( + &units[span.start_index as usize..span.end_index as usize], + ); + for reference in &span.references { + let Some(source) = sources.get(reference.source_id.as_str()) else { + continue; + }; + let label = source + .title + .as_deref() + .or(source.url.as_deref()) + .or(source.path.as_deref()) + .unwrap_or(source.id.as_str()); + println!("\"{quoted}\" — {label}"); + } + } +} +``` + +{% endcodetab %} +{% endcodetabs %} + +## Citation payload reference + +The `citations` object separates deduplicated sources from the spans that reference them, so a source cited five times appears once in `sources`. + +| Type | Field | Description | +|---|---|---| +| `Citations` | `sources` | Deduplicated set of sources referenced by the citation spans | +| `Citations` | `spans` | Spans of generated text annotated with their supporting sources | +| `CitationSource` | `id` | Stable, turn-scoped identifier referenced by `CitationReference.sourceId` | +| `CitationSource` | `provider` | System that produced the citation: `anthropic`, `openai`, or `client` | +| `CitationSource` | `title?` | Human-readable title of the source | +| `CitationSource` | `url?` | URL of the source, when it is a web resource | +| `CitationSource` | `path?` | File path relative to the agent workspace root, when the source is a file | +| `CitationSpan` | `startIndex` | Start offset in the final message content (UTF-16 code units, zero-based, inclusive) | +| `CitationSpan` | `endIndex` | End offset in the final message content (UTF-16 code units, zero-based, exclusive) | +| `CitationSpan` | `references` | The sources that support this span | +| `CitationReference` | `sourceId` | Identifier of the `CitationSource` this reference points to | +| `CitationReference` | `citedText?` | Exact text from the source that supports the span, when the model provides it | +| `CitationReference` | `location?` | Location within the source that supports the span | +| `CitationReference` | `providerMetadata?` | Provider-native correlation data, passed through opaquely | + +> [!TIP] +> Span offsets are measured in UTF-16 code units against the final `content` string. TypeScript, Java, and .NET strings are already UTF-16, so you can slice them directly. Python strings are indexed by Unicode code point and Go and Rust strings are UTF-8, so convert the content to UTF-16 code units before slicing, as the examples above do. + +### Citation locations + +`CitationReference.location` is a discriminated union keyed on `type`: + +| Location type | Fields | Use | +|---|---|---| +| `char` | `startIndex`, `endIndex` | Character range within the source text | +| `page` | `startPage`, `endPage` | Page range within a paginated document | +| `block` | `startBlock`, `endBlock` | Content-block range within a structured document | + +## Provide citable sources + +Citations need source material the model can attribute. There are two ways to supply it. + +### Attach documents to a message + +When citations are enabled and the session uses an Anthropic provider, file attachments are sent as `document` blocks with citations turned on, so the model can cite passages from them. + + + +```typescript +await session.sendAndWait({ + prompt: "Summarize the attached PDF and cite the passages you used.", + attachments: [ + { + type: "blob", + data: pdfBase64, + displayName: "quarterly-report.pdf", + mimeType: "application/pdf", + }, + ], +}); +``` + +See [AUTOTITLE](/copilot/how-tos/copilot-sdk/features/image-input) for the attachment API and the `file` and `blob` attachment shapes. + +### Return citable sources from a tool + +Tool results carry an experimental `citableSources` array. Each entry supplies `content` that the model can cite, along with an `id` and optional `title`, `url`, and `path`. These sources are persisted with the tool result, so they survive session resume, and citations built from them are tagged with the `client` provider. + +## Limitations + +* Citations are experimental in every SDK and are not covered by compatibility guarantees. +* Coverage depends on the model provider. A session configured for a provider without citation support emits no `citations` payload. +* Citations are only present on the final `assistant.message` event, so streaming consumers cannot render them mid-response. +* Public code and IP-duplication citations are not part of this surface. + +## Further reading + +* [AUTOTITLE](/copilot/how-tos/copilot-sdk/features/streaming-events): subscribe to session events and narrow event types +* [AUTOTITLE](/copilot/how-tos/copilot-sdk/features/image-input): attach files and in-memory blobs to a message +* [AUTOTITLE](/copilot/how-tos/copilot-sdk/features/session-persistence): resume sessions and re-apply session options +* [AUTOTITLE](/copilot/how-tos/copilot-sdk/troubleshooting/compatibility): SDK and CLI feature matrix diff --git a/content/copilot/how-tos/copilot-sdk/features/cloud-sessions.md b/content/copilot/how-tos/copilot-sdk/features/cloud-sessions.md index 13b4c39c1d6a..d7eb9ea8a64f 100644 --- a/content/copilot/how-tos/copilot-sdk/features/cloud-sessions.md +++ b/content/copilot/how-tos/copilot-sdk/features/cloud-sessions.md @@ -77,47 +77,6 @@ session = await client.create_session( ### Go - - -```golang -package main - -import ( - "context" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - _ = run(context.Background()) -} - -func run(ctx context.Context) error { - client := copilot.NewClient(nil) - if err := client.Start(ctx); err != nil { - return err - } - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Cloud: &copilot.CloudSessionOptions{ - Repository: &copilot.CloudSessionRepository{ - Owner: "github", - Name: "copilot-sdk", - Branch: "main", - }, - }, - OnPermissionRequest: func(_ copilot.PermissionRequest, _ copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - _ = session - return err -} -``` - - - ```golang client := copilot.NewClient(nil) if err := client.Start(ctx); err != nil { @@ -279,22 +238,6 @@ Use `branch` when the work should start from a specific branch. If your app is c The `cloud` option only applies when creating a new session. To resume an existing cloud session, use the standard resume API for the SDK language: - - -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); -await client.start(); - -const session = await client.resumeSession("session-id", { - onPermissionRequest: async () => ({ kind: "approve-once" }), -}); -void session; -``` - - - ```typescript const session = await client.resumeSession("session-id", { onPermissionRequest: async () => ({ kind: "approve-once" }), @@ -311,37 +254,6 @@ When this happens, the runtime reports a `"policy_blocked"` failure reason for c In TypeScript, check for the reason before retrying: - - -```typescript -import { - CopilotClient, - type CloudSessionRepository, -} from "@github/copilot-sdk"; - -const client = new CopilotClient(); -await client.start(); - -const repository: CloudSessionRepository = { - owner: "github", - name: "copilot-sdk", -}; - -try { - await client.createSession({ - cloud: { repository }, - onPermissionRequest: async () => ({ kind: "approve-once" }), - }); -} catch (error) { - if ((error as { reason?: string }).reason === "policy_blocked") { - // Show an admin-facing message or link to org policy settings. - } - throw error; -} -``` - - - ```typescript try { await client.createSession({ cloud: { repository } }); diff --git a/content/copilot/how-tos/copilot-sdk/features/context-management.md b/content/copilot/how-tos/copilot-sdk/features/context-management.md new file mode 100644 index 000000000000..bc5850860094 --- /dev/null +++ b/content/copilot/how-tos/copilot-sdk/features/context-management.md @@ -0,0 +1,69 @@ +--- +title: Context clearing and terminal tools +shortTitle: Context management +intro: >- + Use `session.history.clearContext` when a host needs to replace the current + conversation context without replacing the session. Typical uses include + handoffs and host-managed context lifecycle policies. +versions: + fpt: '*' + ghec: '*' +contentType: how-tos +--- + + + + +Context clearing is different from creating a new session: it preserves the session identity, system and developer messages, configuration, and event log while removing the model-facing conversation. + +> [!IMPORTANT] +> `clearContext` is a tool-handler primitive. The runtime rejects calls made without a tool call in flight, calls with an empty seed prompt, and calls on remote sessions. + +## Define a context-clearing tool + +A successful context-clearing tool should be terminal. Otherwise, the agent loop may make another model call against the newly cleared window before starting the seeded turn. + +```typescript +import { approveAll, CopilotClient, defineTool } from "@github/copilot-sdk"; +import type { CopilotSession } from "@github/copilot-sdk"; +import { z } from "zod"; + +const client = new CopilotClient(); +let session: CopilotSession; + +session = await client.createSession({ + onPermissionRequest: approveAll, + tools: [ + defineTool("clear_context", { + description: "Clear the conversation and start a fresh context window", + parameters: z.object({ prompt: z.string() }), + isTerminal: true, + defer: "never", + handler: async ({ prompt }) => { + const { messagesCleared } = + await session.rpc.history.clearContext({ prompt }); + return `Cleared ${messagesCleared} messages.`; + }, + }), + ], +}); +``` + +The required `prompt` becomes the first user message in the fresh context. A successful clear emits `session.context_cleared` with the number of removed messages and the initial message. + +## Terminal-tool behavior + +`isTerminal` ends the current agent turn only when the tool succeeds. A failure, denial, rejection, timeout, or input-validation error remains visible to the model so it can recover or retry. + +The option follows each language's naming conventions: + +| SDK | Tool option | +|---|---| +| Node.js | `isTerminal` | +| Python | `is_terminal` | +| Go | `IsTerminal` | +| .NET | `CopilotToolOptions.IsTerminal` | +| Java | `ToolDefinition.isTerminal(true)` or `@CopilotTool(isTerminal = true)` | +| Rust | `with_is_terminal(true)` | + +Use terminality only for tools whose successful completion should end the turn. Ordinary tools should leave it unset. diff --git a/content/copilot/how-tos/copilot-sdk/features/custom-agents.md b/content/copilot/how-tos/copilot-sdk/features/custom-agents.md index 81ba6db1155b..5a299f58dce7 100644 --- a/content/copilot/how-tos/copilot-sdk/features/custom-agents.md +++ b/content/copilot/how-tos/copilot-sdk/features/custom-agents.md @@ -98,46 +98,6 @@ session = await client.create_session( {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - client.Start(ctx) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "gpt-5.4", - CustomAgents: []copilot.CustomAgentConfig{ - { - Name: "researcher", - DisplayName: "Research Agent", - Description: "Explores codebases and answers questions using read-only tools", - Tools: []string{"grep", "glob", "view"}, - Prompt: "You are a research assistant. Analyze code and answer questions. Do not modify any files.", - }, - { - Name: "editor", - DisplayName: "Editor Agent", - Description: "Makes targeted code changes", - Tools: []string{"view", "edit", "bash"}, - Prompt: "You are a code editor. Make minimal, surgical changes to files as requested.", - }, - }, - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - _ = session -} -``` - ```golang ctx := context.Background() client := copilot.NewClient(nil) @@ -513,50 +473,6 @@ response = await session.send_and_wait("Research how authentication works in thi {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - client.Start(ctx) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "gpt-5.4", - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - - session.On(func(event copilot.SessionEvent) { - switch d := event.Data.(type) { - case *copilot.SubagentStartedData: - fmt.Printf("▶ Sub-agent started: %s\n", d.AgentDisplayName) - fmt.Printf(" Description: %s\n", d.AgentDescription) - fmt.Printf(" Tool call ID: %s\n", d.ToolCallID) - case *copilot.SubagentCompletedData: - fmt.Printf("✅ Sub-agent completed: %s\n", d.AgentDisplayName) - case *copilot.SubagentFailedData: - fmt.Printf("❌ Sub-agent failed: %s — %v\n", d.AgentDisplayName, d.Error) - case *copilot.SubagentSelectedData: - fmt.Printf("🎯 Agent selected: %s\n", d.AgentDisplayName) - } - }) - - _, err := session.SendAndWait(ctx, copilot.MessageOptions{ - Prompt: "Research how authentication works in this codebase", - }) - _ = err -} -``` - ```golang session.On(func(event copilot.SessionEvent) { switch d := event.Data.(type) { @@ -581,42 +497,6 @@ _, err := session.SendAndWait(ctx, copilot.MessageOptions{ {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public static class SubAgentEventsExample -{ - public static async Task Example(CopilotSession session) - { - using var subscription = session.On(evt => - { - switch (evt) - { - case SubagentStartedEvent started: - Console.WriteLine($"▶ Sub-agent started: {started.Data.AgentDisplayName}"); - Console.WriteLine($" Description: {started.Data.AgentDescription}"); - Console.WriteLine($" Tool call ID: {started.Data.ToolCallId}"); - break; - case SubagentCompletedEvent completed: - Console.WriteLine($"✅ Sub-agent completed: {completed.Data.AgentDisplayName}"); - break; - case SubagentFailedEvent failed: - Console.WriteLine($"❌ Sub-agent failed: {failed.Data.AgentDisplayName} — {failed.Data.Error}"); - break; - case SubagentSelectedEvent selected: - Console.WriteLine($"🎯 Agent selected: {selected.Data.AgentDisplayName}"); - break; - } - }); - - await session.SendAndWaitAsync(new MessageOptions - { - Prompt = "Research how authentication works in this codebase" - }); - } -} -``` - ```csharp using var subscription = session.On(evt => { diff --git a/content/copilot/how-tos/copilot-sdk/features/fleet-mode.md b/content/copilot/how-tos/copilot-sdk/features/fleet-mode.md index a576c9681e0a..96be8a3ea9a7 100644 --- a/content/copilot/how-tos/copilot-sdk/features/fleet-mode.md +++ b/content/copilot/how-tos/copilot-sdk/features/fleet-mode.md @@ -79,38 +79,6 @@ if result.started: {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - session, err := client.CreateSession(ctx, &copilot.SessionConfig{}) - if err != nil { - return - } - - prompt := "Update each package independently, then report validation results." - result, err := session.RPC.Fleet.Start(ctx, &rpc.FleetStartRequest{ - Prompt: &prompt, - }) - if err != nil { - return - } - if result.Started { - fmt.Println("Fleet mode started") - } -} -``` - ```golang prompt := "Update each package independently, then report validation results." result, err := session.RPC.Fleet.Start(ctx, &rpc.FleetStartRequest{ @@ -127,21 +95,6 @@ if result.Started { {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -await using var client = new CopilotClient(); -await using var session = await client.CreateSessionAsync(new SessionConfig()); - -var result = await session.Rpc.Fleet.StartAsync( - "Audit each project independently, then summarize the findings."); - -if (result.Started) -{ - Console.WriteLine("Fleet mode started"); -} -``` - ```csharp var result = await session.Rpc.Fleet.StartAsync( "Audit each project independently, then summarize the findings."); @@ -268,29 +221,6 @@ session.on((event) => { {% endcodetab %} {% codetab python %} -```python -import asyncio -from copilot import CopilotClient -from copilot.session import PermissionHandler - -async def main(): - client = CopilotClient() - await client.start() - session = await client.create_session( - on_permission_request=PermissionHandler.approve_all, - ) - - def handle_event(event): - if event.type == "subagent.started": - print(f"Started {event.data.agent_display_name}") - elif event.type == "subagent.completed": - print(f"Completed {event.data.agent_display_name}") - - unsubscribe = session.on(handle_event) - -asyncio.run(main()) -``` - ```python def handle_event(event): if event.type == "subagent.started": diff --git a/content/copilot/how-tos/copilot-sdk/features/hooks.md b/content/copilot/how-tos/copilot-sdk/features/hooks.md index a4b4a9e089c0..3958e751636a 100644 --- a/content/copilot/how-tos/copilot-sdk/features/hooks.md +++ b/content/copilot/how-tos/copilot-sdk/features/hooks.md @@ -28,6 +28,7 @@ A hook is a callback you register once when creating a session. The SDK invokes | ------------------------------------------------------------------- | ----------------------------------- | ------------------------------------------ | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/session-lifecycle#session-start) | Session begins (new or resumed) | Inject context, load preferences | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted) | User sends a message | Rewrite prompts, add context, filter input | +| [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/user-prompt-transformed) | Runtime builds the model prompt | Inspect or replace model-facing content | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/pre-tool-use) | Before a tool executes | Allow / deny / modify the call | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/post-tool-use) | After a tool returns (success only) | Transform results, redact secrets, audit | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/post-tool-use#failure-variant) | After a tool returns a failure | Inject retry guidance, log failures | @@ -89,47 +90,6 @@ session = await client.create_session( {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func onSessionStart(input copilot.SessionStartHookInput, inv copilot.HookInvocation) (*copilot.SessionStartHookOutput, error) { - return nil, nil -} - -func onPreToolUse(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) { - return nil, nil -} - -func onPostToolUse(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) { - return nil, nil -} - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - Hooks: &copilot.SessionHooks{ - OnSessionStart: onSessionStart, - OnPreToolUse: onPreToolUse, - OnPostToolUse: onPostToolUse, - }, - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - _ = session - _ = err -} -``` - - ```golang client := copilot.NewClient(nil) @@ -149,39 +109,6 @@ session, err := client.CreateSession(ctx, &copilot.SessionConfig{ {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; -using GitHub.Copilot.Rpc; - -public static class HooksExample -{ - static Task onSessionStart(SessionStartHookInput input, HookInvocation invocation) => - Task.FromResult(null); - static Task onPreToolUse(PreToolUseHookInput input, HookInvocation invocation) => - Task.FromResult(null); - static Task onPostToolUse(PostToolUseHookInput input, HookInvocation invocation) => - Task.FromResult(null); - - public static async Task Main() - { - var client = new CopilotClient(); - - var session = await client.CreateSessionAsync(new SessionConfig - { - Hooks = new SessionHooks - { - OnSessionStart = onSessionStart, - OnPreToolUse = onPreToolUse, - OnPostToolUse = onPostToolUse, - }, - OnPermissionRequest = (req, inv) => - Task.FromResult(PermissionDecision.ApproveOnce()), - }); - } -} -``` - - ```csharp var client = new CopilotClient(); @@ -284,43 +211,6 @@ session = await client.create_session( {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - - readOnlyTools := map[string]bool{"read_file": true, "glob": true, "grep": true, "view": true} - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - Hooks: &copilot.SessionHooks{ - OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) { - if !readOnlyTools[input.ToolName] { - return &copilot.PreToolUseHookOutput{ - PermissionDecision: "deny", - PermissionDecisionReason: fmt.Sprintf("Only read-only tools are allowed. %q was blocked.", input.ToolName), - }, nil - } - return &copilot.PreToolUseHookOutput{PermissionDecision: "allow"}, nil - }, - }, - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - _ = session -} -``` - - ```golang readOnlyTools := map[string]bool{"read_file": true, "glob": true, "grep": true, "view": true} @@ -342,44 +232,6 @@ session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; -using GitHub.Copilot.Rpc; - -public static class PermissionControlExample -{ - public static async Task Main() - { - await using var client = new CopilotClient(); - - var readOnlyTools = new HashSet { "read_file", "glob", "grep", "view" }; - - var session = await client.CreateSessionAsync(new SessionConfig - { - Hooks = new SessionHooks - { - OnPreToolUse = (input, invocation) => - { - if (!readOnlyTools.Contains(input.ToolName)) - { - return Task.FromResult(new PreToolUseHookOutput - { - PermissionDecision = "deny", - PermissionDecisionReason = $"Only read-only tools are allowed. \"{input.ToolName}\" was blocked.", - }); - } - return Task.FromResult( - new PreToolUseHookOutput { PermissionDecision = "allow" }); - }, - }, - OnPermissionRequest = (req, inv) => - Task.FromResult(PermissionDecision.ApproveOnce()), - }); - } -} -``` - - ```csharp var readOnlyTools = new HashSet { "read_file", "glob", "grep", "view" }; @@ -1032,6 +884,7 @@ For full type definitions, input/output field tables, and additional examples fo * [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/pre-tool-use) * [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/post-tool-use) * [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted) +* [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/user-prompt-transformed) * [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/session-lifecycle) * [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/error-handling) diff --git a/content/copilot/how-tos/copilot-sdk/features/image-input.md b/content/copilot/how-tos/copilot-sdk/features/image-input.md index a94084ae0112..cf96d11bca2c 100644 --- a/content/copilot/how-tos/copilot-sdk/features/image-input.md +++ b/content/copilot/how-tos/copilot-sdk/features/image-input.md @@ -87,40 +87,6 @@ await session.send( {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - client.Start(ctx) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "gpt-5.4", - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - - path := "/absolute/path/to/screenshot.png" - session.Send(ctx, copilot.MessageOptions{ - Prompt: "Describe what you see in this image", - Attachments: []copilot.Attachment{ - &copilot.AttachmentFile{ - DisplayName: "screenshot.png", - Path: path, - }, - }, - }) -} -``` - ```golang ctx := context.Background() client := copilot.NewClient(nil) @@ -152,38 +118,6 @@ session.Send(ctx, copilot.MessageOptions{ using GitHub.Copilot; using GitHub.Copilot.Rpc; -public static class ImageInputExample -{ - public static async Task Main() - { - await using var client = new CopilotClient(); - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "gpt-5.4", - OnPermissionRequest = (req, inv) => - Task.FromResult(PermissionDecision.ApproveOnce()), - }); - - await session.SendAsync(new MessageOptions - { - Prompt = "Describe what you see in this image", - Attachments = new List - { - new AttachmentFile - { - Path = "/absolute/path/to/screenshot.png", - DisplayName = "screenshot.png", - }, - }, - }); - } -} -``` - -```csharp -using GitHub.Copilot; -using GitHub.Copilot.Rpc; - await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig { @@ -298,43 +232,6 @@ await session.send( {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - client.Start(ctx) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "gpt-5.4", - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - - base64ImageData := "..." - mimeType := "image/png" - displayName := "screenshot.png" - session.Send(ctx, copilot.MessageOptions{ - Prompt: "Describe what you see in this image", - Attachments: []copilot.Attachment{ - &copilot.AttachmentBlob{ - Data: &base64ImageData, - MIMEType: mimeType, - DisplayName: &displayName, - }, - }, - }) -} -``` - ```golang mimeType := "image/png" displayName := "screenshot.png" @@ -353,40 +250,6 @@ session.Send(ctx, copilot.MessageOptions{ {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; -using GitHub.Copilot.Rpc; - -public static class BlobAttachmentExample -{ - public static async Task Main() - { - await using var client = new CopilotClient(); - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "gpt-5.4", - OnPermissionRequest = (req, inv) => - Task.FromResult(PermissionDecision.ApproveOnce()), - }); - - var base64ImageData = "..."; - await session.SendAsync(new MessageOptions - { - Prompt = "Describe what you see in this image", - Attachments = new List - { - new AttachmentBlob - { - Data = base64ImageData, - MimeType = "image/png", - DisplayName = "screenshot.png", - }, - }, - }); - } -} -``` - ```csharp await session.SendAsync(new MessageOptions { @@ -467,20 +330,6 @@ Not all models support vision. Check the model's capabilities before sending ima ### Vision limits type - - -```typescript -interface VisionCapabilities { - vision?: { - supported_media_types: string[]; - max_prompt_images: number; - max_prompt_image_size: number; // bytes - }; -} -``` - - - ```typescript vision?: { supported_media_types: string[]; diff --git a/content/copilot/how-tos/copilot-sdk/features/index.md b/content/copilot/how-tos/copilot-sdk/features/index.md index 1348d6a3171f..319fb9980c9c 100644 --- a/content/copilot/how-tos/copilot-sdk/features/index.md +++ b/content/copilot/how-tos/copilot-sdk/features/index.md @@ -12,7 +12,9 @@ redirect_from: contentType: how-tos children: - /agent-loop + - /citations - /cloud-sessions + - /context-management - /custom-agents - /fleet-mode - /hooks diff --git a/content/copilot/how-tos/copilot-sdk/features/mcp.md b/content/copilot/how-tos/copilot-sdk/features/mcp.md index a6abc3d437e8..d23b054c0bb9 100644 --- a/content/copilot/how-tos/copilot-sdk/features/mcp.md +++ b/content/copilot/how-tos/copilot-sdk/features/mcp.md @@ -169,6 +169,35 @@ await using var session = await client.CreateSessionAsync(new SessionConfig }); ``` +## Disabling configured servers per session + +Set `disabledMcpServers` to exact MCP server names that must not run in a session. +The setting is scoped to the individual create or resume request; it does not +modify global MCP settings or the server configuration. + +```typescript +const session = await client.createSession({ + mcpServers: { + filesystem: { type: "local", command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem", "."] }, + github: { type: "http", url: "https://api.githubcopilot.com/mcp/" }, + }, + disabledMcpServers: ["github"], +}); +``` + +| SDK | Configuration property | +| --- | --- | +| Node.js | `disabledMcpServers` | +| Python | `disabled_mcp_servers` | +| Go | `DisabledMCPServers` | +| .NET | `DisabledMcpServers` | +| Java | `setDisabledMcpServers(...)` | +| Rust | `with_disabled_mcp_servers(...)` | + +On session creation and a **cold** resume, disabled servers are not started and +the runtime does not initiate their authentication. A resident resume cannot +undo a server that the runtime has already spawned. Names are matched exactly. + ## Tool configuration You can control which tools are available to an MCP server using the `tools` field. diff --git a/content/copilot/how-tos/copilot-sdk/features/plugin-directories.md b/content/copilot/how-tos/copilot-sdk/features/plugin-directories.md index 2aa5601d537b..42543b900978 100644 --- a/content/copilot/how-tos/copilot-sdk/features/plugin-directories.md +++ b/content/copilot/how-tos/copilot-sdk/features/plugin-directories.md @@ -60,25 +60,6 @@ Plugin directories are loaded by passing `--plugin-dir ` to the Copilot CL ```typescript import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk"; -async function main() { - const client = new CopilotClient({ - connection: RuntimeConnection.forStdio({ - args: [ - "--plugin-dir", "./plugins/code-reviewer", - "--plugin-dir", "./plugins/lint-fix", - ], - }), - }); - - await client.start(); -} - -main(); -``` - -```typescript -import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk"; - const client = new CopilotClient({ connection: RuntimeConnection.forStdio({ args: [ @@ -113,31 +94,6 @@ await client.start() {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(&copilot.ClientOptions{ - Connection: copilot.StdioConnection{ - Args: []string{ - "--plugin-dir", "./plugins/code-reviewer", - "--plugin-dir", "./plugins/lint-fix", - }, - }, - }) - if err := client.Start(ctx); err != nil { - return - } -} -``` - ```golang client := copilot.NewClient(&copilot.ClientOptions{ Connection: copilot.StdioConnection{ @@ -173,24 +129,6 @@ await client.StartAsync(); {% endcodetab %} {% codetab java %} -```java -import com.github.copilot.CopilotClient; -import com.github.copilot.rpc.CopilotClientOptions; - -public class PluginDirectoriesExample { - public static void main(String[] args) throws Exception { - var options = new CopilotClientOptions() - .setCliArgs(new String[] { - "--plugin-dir", "./plugins/code-reviewer", - "--plugin-dir", "./plugins/lint-fix", - }); - - var client = new CopilotClient(options); - client.start().get(); - } -} -``` - ```java var options = new CopilotClientOptions() .setCliArgs(new String[] { @@ -208,22 +146,6 @@ client.start().get(); ```rust use github_copilot_sdk::{Client, ClientOptions}; -#[tokio::main] -async fn main() -> Result<(), Box> { - let _client = Client::start( - ClientOptions::new().with_extra_args([ - "--plugin-dir", "./plugins/code-reviewer", - "--plugin-dir", "./plugins/lint-fix", - ]), - ) - .await?; - Ok(()) -} -``` - -```rust -use github_copilot_sdk::{Client, ClientOptions}; - let client = Client::start( ClientOptions::new().with_extra_args([ "--plugin-dir", "./plugins/code-reviewer", @@ -238,6 +160,23 @@ let client = Client::start( > The example above uses an stdio runtime connection — the default when the SDK bundles the CLI. If you connect to an external runtime via a URL (`forUri` / `ForUri`), pass `--plugin-dir` to the long-running CLI server when you start it; the SDK does not forward `--plugin-dir` to runtimes it didn't spawn. +## Trusted host-bundled plugin directories + +Applications that ship their own trusted plugins can register them as a client startup option. The SDK sends the complete ordered set after connecting and verifying the protocol, before `start` returns or any session can be created. Paths must be absolute; leaving the option unset or empty makes no RPC call. + +The equivalent option in each SDK is: + +| SDK | Startup option | +|---|---| +| Node.js / TypeScript | `builtinPluginDirectories: string[]` | +| Python | `builtin_plugin_directories=[...]` | +| Go | `BuiltinPluginDirectories: []string{...}` | +| .NET | `BuiltinPluginDirectories = [...]` | +| Java | `.setBuiltinPluginDirectories(List.of(Path.of(...)))` | +| Rust | `.with_builtin_plugin_directories([...])` | + +This is a trust boundary for plugins bundled and controlled by the host application. It is distinct from `--plugin-dir`, which is a CLI process launch argument for explicitly loading ordinary plugin directories. The startup option also works when connecting to an existing runtime because it is sent over JSON-RPC rather than forwarded as a process argument. + ## What a plugin can contribute Loading a plugin directory makes its extensions visible to every session created by the client. The runtime merges plugin-provided extensions with anything you register inline: @@ -268,26 +207,6 @@ When the host machine may have other plugins installed (marketplace or personal)
Node.js / TypeScript - - -```typescript -import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk"; - -async function main() { - process.env.COPILOT_PLUGIN_DIR_ONLY = "true"; - const client = new CopilotClient({ - connection: RuntimeConnection.forStdio({ - args: ["--plugin-dir", "./plugins/code-reviewer"], - }), - }); - await client.start(); -} - -main(); -``` - - - ```typescript process.env.COPILOT_PLUGIN_DIR_ONLY = "true"; @@ -310,29 +229,6 @@ Once a session is created, list the active plugins to confirm a directory was pi
Node.js / TypeScript - - -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -async function main() { - const client = new CopilotClient(); - await client.start(); - const session = await client.createSession({ - onPermissionRequest: async () => ({ kind: "approve-once" }), - }); - - const plugins = await session.rpc.plugins.list(); - for (const plugin of plugins.plugins) { - console.log(`${plugin.name} (${plugin.enabled ? "enabled" : "disabled"})`); - } -} - -main(); -``` - - - ```typescript const plugins = await session.rpc.plugins.list(); for (const plugin of plugins.plugins) { diff --git a/content/copilot/how-tos/copilot-sdk/features/session-persistence.md b/content/copilot/how-tos/copilot-sdk/features/session-persistence.md index c59d7f2b9218..7f65427211dc 100644 --- a/content/copilot/how-tos/copilot-sdk/features/session-persistence.md +++ b/content/copilot/how-tos/copilot-sdk/features/session-persistence.md @@ -72,36 +72,6 @@ await session.send_and_wait("Analyze my codebase") ### Go - - -```golang -package main - -import ( - "context" - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - SessionID: "user-123-task-456", - Model: "gpt-5.2-codex", - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - - session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Analyze my codebase"}) - _ = session -} -``` - - - ```golang ctx := context.Background() client := copilot.NewClient(nil) @@ -166,29 +136,6 @@ await session.send_and_wait("What did we discuss earlier?") ### Go - - -```golang -package main - -import ( - "context" - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - - session, _ := client.ResumeSession(ctx, "user-123-task-456", nil) - - session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "What did we discuss earlier?"}) - _ = session -} -``` - - - ```golang ctx := context.Background() @@ -201,31 +148,6 @@ session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "What did we discuss ear ### C# (.NET) - - -```csharp -using GitHub.Copilot; -using GitHub.Copilot.Rpc; - -public static class ResumeSessionExample -{ - public static async Task Main() - { - await using var client = new CopilotClient(); - - var session = await client.ResumeSessionAsync("user-123-task-456", new ResumeSessionConfig - { - OnPermissionRequest = (req, inv) => - Task.FromResult(PermissionDecision.ApproveOnce()), - }); - - await session.SendAndWaitAsync(new MessageOptions { Prompt = "What did we discuss earlier?" }); - } -} -``` - - - ```csharp // Resume from a different client instance (or after restart) var session = await client.ResumeSessionAsync("user-123-task-456"); diff --git a/content/copilot/how-tos/copilot-sdk/features/skills.md b/content/copilot/how-tos/copilot-sdk/features/skills.md index 08acedf9f77d..17676dc35f94 100644 --- a/content/copilot/how-tos/copilot-sdk/features/skills.md +++ b/content/copilot/how-tos/copilot-sdk/features/skills.md @@ -208,30 +208,6 @@ session = await client.create_session( {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - SkillDirectories: []string{"./skills"}, - DisabledSkills: []string{"experimental-feature", "deprecated-tool"}, - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - _ = session -} -``` - ```golang session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ SkillDirectories: []string{"./skills"}, @@ -242,27 +218,6 @@ session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; -using GitHub.Copilot.Rpc; - -public static class SkillsExample -{ - public static async Task Main() - { - await using var client = new CopilotClient(); - - var session = await client.CreateSessionAsync(new SessionConfig - { - SkillDirectories = new List { "./skills" }, - DisabledSkills = new List { "experimental-feature", "deprecated-tool" }, - OnPermissionRequest = (req, inv) => - Task.FromResult(PermissionDecision.ApproveOnce()), - }); - } -} -``` - ```csharp var session = await client.CreateSessionAsync(new SessionConfig { diff --git a/content/copilot/how-tos/copilot-sdk/features/steering-and-queueing.md b/content/copilot/how-tos/copilot-sdk/features/steering-and-queueing.md index ea797ef82f23..b1576cef0332 100644 --- a/content/copilot/how-tos/copilot-sdk/features/steering-and-queueing.md +++ b/content/copilot/how-tos/copilot-sdk/features/steering-and-queueing.md @@ -278,43 +278,6 @@ async def main(): {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - client.Start(ctx) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "gpt-5.4", - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - - session.Send(ctx, copilot.MessageOptions{ - Prompt: "Set up the project structure", - }) - - session.Send(ctx, copilot.MessageOptions{ - Prompt: "Add unit tests for the auth module", - Mode: "enqueue", - }) - - session.Send(ctx, copilot.MessageOptions{ - Prompt: "Update the README with setup instructions", - Mode: "enqueue", - }) -} -``` - ```golang // Send an initial task session.Send(ctx, copilot.MessageOptions{ @@ -338,42 +301,6 @@ session.Send(ctx, copilot.MessageOptions{ {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; -using GitHub.Copilot.Rpc; - -public static class QueueingExample -{ - public static async Task Main() - { - await using var client = new CopilotClient(); - await using var session = await client.CreateSessionAsync(new SessionConfig - { - Model = "gpt-5.4", - OnPermissionRequest = (req, inv) => - Task.FromResult(PermissionDecision.ApproveOnce()), - }); - - await session.SendAsync(new MessageOptions - { - Prompt = "Set up the project structure" - }); - - await session.SendAsync(new MessageOptions - { - Prompt = "Add unit tests for the auth module", - Mode = "enqueue" - }); - - await session.SendAsync(new MessageOptions - { - Prompt = "Update the README with setup instructions", - Mode = "enqueue" - }); - } -} -``` - ```csharp // Send an initial task await session.SendAsync(new MessageOptions diff --git a/content/copilot/how-tos/copilot-sdk/features/streaming-events.md b/content/copilot/how-tos/copilot-sdk/features/streaming-events.md index 7f86ac2acbc4..47e136e34e88 100644 --- a/content/copilot/how-tos/copilot-sdk/features/streaming-events.md +++ b/content/copilot/how-tos/copilot-sdk/features/streaming-events.md @@ -64,21 +64,6 @@ session.on("assistant.message_delta", (event) => { {% endcodetab %} {% codetab python %} -```python -from copilot import CopilotClient -from copilot.session_events import SessionEventType - -client = CopilotClient() - -session = None # assume session is created elsewhere - -def handle(event): - if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA: - print(event.data.delta_content, end="", flush=True) - -# session.on(handle) -``` - ```python from copilot.session_events import SessionEventType @@ -92,37 +77,6 @@ session.on(handle) {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - Model: "gpt-5.4", - Streaming: copilot.Bool(true), - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - - session.On(func(event copilot.SessionEvent) { - if d, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok { - fmt.Print(d.DeltaContent) - } - }) - _ = session -} -``` - ```golang session.On(func(event copilot.SessionEvent) { if d, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok { @@ -134,24 +88,6 @@ session.On(func(event copilot.SessionEvent) { {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public static class StreamingEventsExample -{ - public static async Task Example(CopilotSession session) - { - session.On(evt => - { - if (evt is AssistantMessageDeltaEvent delta) - { - Console.Write(delta.Data.DeltaContent); - } - }); - } -} -``` - ```csharp session.On(evt => { @@ -510,7 +446,7 @@ Ephemeral. The agent has finished all processing and is ready for the next messa | Data Field | Type | Required | Description | |------------|------|----------|-------------| -| `backgroundTasks` | `BackgroundTasks` | | Background agents/shells still running when the agent became idle | +| `aborted` | `boolean` | | True when the preceding turn was cancelled via abort signal | ### `session.error` @@ -920,7 +856,7 @@ This table lists key `data` payload fields. Common envelope fields are documente | `tool.execution_partial_result` | ✅ | Tool | `toolCallId`, `partialOutput` | | `tool.execution_progress` | ✅ | Tool | `toolCallId`, `progressMessage` | | `tool.execution_complete` | | Tool | `toolCallId`, `success`, `result?`, `error?` | -| `session.idle` | ✅ | Session | `backgroundTasks?` | +| `session.idle` | ✅ | Session | `aborted?` | | `session.error` | | Session | `errorType`, `message`, `statusCode?` | | `session.compaction_start` | | Session | *(empty)* | | `session.compaction_complete` | | Session | `success`, `preCompactionTokens?`, `summaryContent?` | diff --git a/content/copilot/how-tos/copilot-sdk/features/usage-and-billing.md b/content/copilot/how-tos/copilot-sdk/features/usage-and-billing.md index 5fc22e9d4d6e..1fb8d7297ada 100644 --- a/content/copilot/how-tos/copilot-sdk/features/usage-and-billing.md +++ b/content/copilot/how-tos/copilot-sdk/features/usage-and-billing.md @@ -59,20 +59,6 @@ The example below uses these fields. See [AUTOTITLE](/copilot/how-tos/copilot-sd {% codetabs %} {% codetab typescript %} -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); -const session = await client.createSession({ streaming: true }); - -session.on("assistant.usage", (event) => { - const { model, inputTokens, outputTokens, cost } = event.data; - console.log( - `${model}: in=${inputTokens ?? 0} out=${outputTokens ?? 0} cost=${cost ?? 0}`, - ); -}); -``` - ```typescript session.on("assistant.usage", (event) => { const { model, inputTokens, outputTokens, cost } = event.data; @@ -85,21 +71,6 @@ session.on("assistant.usage", (event) => { {% endcodetab %} {% codetab python %} -```python -from copilot import CopilotClient -from copilot.session_events import SessionEventType - -client = CopilotClient() -session = await client.create_session(streaming=True) - -def on_usage(event): - if event.type == SessionEventType.ASSISTANT_USAGE: - data = event.data - print(f"{data.model}: in={data.input_tokens or 0} out={data.output_tokens or 0} cost={data.cost or 0}") - -session.on(on_usage) -``` - ```python def on_usage(event): if event.type == SessionEventType.ASSISTANT_USAGE: @@ -112,50 +83,6 @@ session.on(on_usage) {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - client.Start(ctx) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - Streaming: copilot.Bool(true), - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - - session.On(func(event copilot.SessionEvent) { - d, ok := event.Data.(*copilot.AssistantUsageData) - if !ok { - return - } - in, out, cost := int64(0), int64(0), float64(0) - if d.InputTokens != nil { - in = *d.InputTokens - } - if d.OutputTokens != nil { - out = *d.OutputTokens - } - if d.Cost != nil { - cost = *d.Cost - } - fmt.Printf("%s: in=%d out=%d cost=%g\n", d.Model, in, out, cost) - }) - _ = session -} -``` - ```golang session.On(func(event copilot.SessionEvent) { d, ok := event.Data.(*copilot.AssistantUsageData) @@ -179,20 +106,6 @@ session.On(func(event copilot.SessionEvent) { {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -await using var client = new CopilotClient(); -await using var session = await client.CreateSessionAsync(new SessionConfig { Streaming = true }); - -session.On(evt => -{ - var data = evt.Data; - Console.WriteLine( - $"{data.Model}: in={data.InputTokens ?? 0} out={data.OutputTokens ?? 0} cost={data.Cost ?? 0}"); -}); -``` - ```csharp session.On(evt => { @@ -258,19 +171,6 @@ The runtime emits a `session.usage_info` event whenever the context-window size {% codetabs %} {% codetab typescript %} -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); -const session = await client.createSession({ streaming: true }); - -session.on("session.usage_info", (event) => { - const { currentTokens, tokenLimit } = event.data; - const pct = Math.round((currentTokens / tokenLimit) * 100); - console.log(`Context: ${currentTokens}/${tokenLimit} (${pct}%)`); -}); -``` - ```typescript session.on("session.usage_info", (event) => { const { currentTokens, tokenLimit } = event.data; @@ -282,22 +182,6 @@ session.on("session.usage_info", (event) => { {% endcodetab %} {% codetab python %} -```python -from copilot import CopilotClient -from copilot.session_events import SessionEventType - -client = CopilotClient() -session = await client.create_session(streaming=True) - -def on_usage_info(event): - if event.type == SessionEventType.SESSION_USAGE_INFO: - data = event.data - pct = round(data.current_tokens / data.token_limit * 100) - print(f"Context: {data.current_tokens}/{data.token_limit} ({pct}%)") - -session.on(on_usage_info) -``` - ```python def on_usage_info(event): if event.type == SessionEventType.SESSION_USAGE_INFO: @@ -311,41 +195,6 @@ session.on(on_usage_info) {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - client.Start(ctx) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - Streaming: copilot.Bool(true), - OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) { - return &rpc.PermissionDecisionApproveOnce{}, nil - }, - }) - - session.On(func(event copilot.SessionEvent) { - d, ok := event.Data.(*copilot.SessionUsageInfoData) - if !ok { - return - } - pct := int(float64(d.CurrentTokens) / float64(d.TokenLimit) * 100) - fmt.Printf("Context: %d/%d (%d%%)\n", d.CurrentTokens, d.TokenLimit, pct) - }) - _ = session -} -``` - ```golang session.On(func(event copilot.SessionEvent) { d, ok := event.Data.(*copilot.SessionUsageInfoData) @@ -360,19 +209,6 @@ session.On(func(event copilot.SessionEvent) { {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -await using var client = new CopilotClient(); -await using var session = await client.CreateSessionAsync(new SessionConfig { Streaming = true }); - -session.On(evt => -{ - var pct = (int)Math.Round((double)evt.Data.CurrentTokens / evt.Data.TokenLimit * 100); - Console.WriteLine($"Context: {evt.Data.CurrentTokens}/{evt.Data.TokenLimit} ({pct}%)"); -}); -``` - ```csharp session.On(evt => { @@ -423,25 +259,6 @@ The result's `contextInfo` is `null` until the session has been initialized (the {% codetabs %} {% codetab typescript %} -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); -const session = await client.createSession({}); - -const { contextInfo } = await session.rpc.metadata.contextInfo({ - promptTokenLimit: 0, - outputTokenLimit: 0, -}); - -if (contextInfo) { - console.log( - `Total ${contextInfo.totalTokens}/${contextInfo.promptTokenLimit} ` + - `(system=${contextInfo.systemTokens}, conversation=${contextInfo.conversationTokens})`, - ); -} -``` - ```typescript const { contextInfo } = await session.rpc.metadata.contextInfo({ promptTokenLimit: 0, @@ -459,25 +276,6 @@ if (contextInfo) { {% endcodetab %} {% codetab python %} -```python -from copilot import CopilotClient -from copilot.rpc import MetadataContextInfoRequest - -client = CopilotClient() -session = await client.create_session() - -result = await session.rpc.metadata.context_info( - MetadataContextInfoRequest(prompt_token_limit=0, output_token_limit=0) -) -info = result.context_info - -if info is not None: - print( - f"Total {info.total_tokens}/{info.prompt_token_limit} " - f"(system={info.system_tokens}, conversation={info.conversation_tokens})" - ) -``` - ```python result = await session.rpc.metadata.context_info( MetadataContextInfoRequest(prompt_token_limit=0, output_token_limit=0) @@ -494,36 +292,6 @@ if info is not None: {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - client.Start(ctx) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{}) - - result, _ := session.RPC.Metadata.ContextInfo(ctx, &rpc.MetadataContextInfoRequest{ - PromptTokenLimit: 0, - OutputTokenLimit: 0, - }) - - if info := result.ContextInfo; info != nil { - fmt.Printf("Total %d/%d (system=%d, conversation=%d)\n", - info.TotalTokens, info.PromptTokenLimit, info.SystemTokens, info.ConversationTokens) - } -} -``` - ```golang result, _ := session.RPC.Metadata.ContextInfo(ctx, &rpc.MetadataContextInfoRequest{ PromptTokenLimit: 0, @@ -539,25 +307,6 @@ if info := result.ContextInfo; info != nil { {% endcodetab %} {% codetab dotnet %} -```csharp -#pragma warning disable GHCP001 -using GitHub.Copilot; - -await using var client = new CopilotClient(); -await using var session = await client.CreateSessionAsync(new SessionConfig()); - -var result = await session.Rpc.Metadata.ContextInfoAsync(promptTokenLimit: 0, outputTokenLimit: 0); -var info = result.ContextInfo; - -if (info is not null) -{ - Console.WriteLine( - $"Total {info.TotalTokens}/{info.PromptTokenLimit} " + - $"(system={info.SystemTokens}, conversation={info.ConversationTokens})"); -} -#pragma warning restore GHCP001 -``` - ```csharp var result = await session.Rpc.Metadata.ContextInfoAsync(promptTokenLimit: 0, outputTokenLimit: 0); var info = result.ContextInfo; @@ -632,27 +381,6 @@ The example uses the fields below. The generated `UsageGetMetricsResult` type is {% codetabs %} {% codetab typescript %} -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); -const session = await client.createSession({}); - -const metrics = await session.rpc.usage.getMetrics(); - -const aiCredits = (metrics.totalNanoAiu ?? 0) / 1e9; -console.log(`AI credits used: ${aiCredits.toFixed(6)}`); -console.log(`Premium requests: ${metrics.totalPremiumRequestCost}`); - -for (const [model, m] of Object.entries(metrics.modelMetrics)) { - if (!m) continue; - console.log( - `${model}: in=${m.usage.inputTokens} out=${m.usage.outputTokens} ` + - `nanoAiu=${m.totalNanoAiu ?? 0}`, - ); -} -``` - ```typescript const metrics = await session.rpc.usage.getMetrics(); @@ -672,22 +400,6 @@ for (const [model, m] of Object.entries(metrics.modelMetrics)) { {% endcodetab %} {% codetab python %} -```python -from copilot import CopilotClient - -client = CopilotClient() -session = await client.create_session() - -metrics = await session.rpc.usage.get_metrics() - -ai_credits = (metrics.total_nano_aiu or 0) / 1e9 -print(f"AI credits used: {ai_credits:.6f}") -print(f"Premium requests: {metrics.total_premium_request_cost}") - -for model, m in metrics.model_metrics.items(): - print(f"{model}: in={m.usage.input_tokens} out={m.usage.output_tokens} nanoAiu={m.total_nano_aiu or 0}") -``` - ```python metrics = await session.rpc.usage.get_metrics() @@ -702,42 +414,6 @@ for model, m in metrics.model_metrics.items(): {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - client.Start(ctx) - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{}) - - metrics, _ := session.RPC.Usage.GetMetrics(ctx) - - aiCredits := float64(0) - if metrics.TotalNanoAiu != nil { - aiCredits = *metrics.TotalNanoAiu / 1e9 - } - fmt.Printf("AI credits used: %.6f\n", aiCredits) - fmt.Printf("Premium requests: %v\n", metrics.TotalPremiumRequestCost) - - for model, m := range metrics.ModelMetrics { - nanoAiu := float64(0) - if m.TotalNanoAiu != nil { - nanoAiu = *m.TotalNanoAiu - } - fmt.Printf("%s: in=%d out=%d nanoAiu=%v\n", model, m.Usage.InputTokens, m.Usage.OutputTokens, nanoAiu) - } -} -``` - ```golang metrics, _ := session.RPC.Usage.GetMetrics(ctx) @@ -760,27 +436,6 @@ for model, m := range metrics.ModelMetrics { {% endcodetab %} {% codetab dotnet %} -```csharp -#pragma warning disable GHCP001 -using GitHub.Copilot; - -await using var client = new CopilotClient(); -await using var session = await client.CreateSessionAsync(new SessionConfig()); - -var metrics = await session.Rpc.Usage.GetMetricsAsync(); - -var aiCredits = (metrics.TotalNanoAiu ?? 0) / 1e9; -Console.WriteLine($"AI credits used: {aiCredits:F6}"); -Console.WriteLine($"Premium requests: {metrics.TotalPremiumRequestCost}"); - -foreach (var (model, m) in metrics.ModelMetrics) -{ - Console.WriteLine( - $"{model}: in={m.Usage.InputTokens} out={m.Usage.OutputTokens} nanoAiu={m.TotalNanoAiu ?? 0}"); -} -#pragma warning restore GHCP001 -``` - ```csharp var metrics = await session.Rpc.Usage.GetMetricsAsync(); @@ -853,23 +508,6 @@ To estimate cost before you run a turn, read each model's token prices from `mod {% codetabs %} {% codetab typescript %} -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); - -const { models } = await client.rpc.models.list({}); - -for (const model of models) { - const prices = model.billing?.tokenPrices; - if (!prices) continue; - console.log( - `${model.id}: input=${prices.inputPrice} output=${prices.outputPrice} ` + - `per ${prices.batchSize} tokens (x${model.billing?.multiplier ?? 1})`, - ); -} -``` - ```typescript const { models } = await client.rpc.models.list({}); @@ -886,25 +524,6 @@ for (const model of models) { {% endcodetab %} {% codetab python %} -```python -from copilot import CopilotClient -from copilot.rpc import ModelsListRequest - -client = CopilotClient() - -result = await client.rpc.models.list(ModelsListRequest()) - -for model in result.models: - prices = model.billing.token_prices if model.billing else None - if prices is None: - continue - multiplier = model.billing.multiplier if model.billing else 1 - print( - f"{model.id}: input={prices.input_price} output={prices.output_price} " - f"per {prices.batch_size} tokens (x{multiplier})" - ) -``` - ```python result = await client.rpc.models.list(ModelsListRequest()) @@ -922,49 +541,6 @@ for model in result.models: {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - client.Start(ctx) - - list, _ := client.RPC.Models.List(ctx, &rpc.ModelsListRequest{}) - - for _, model := range list.Models { - if model.Billing == nil || model.Billing.TokenPrices == nil { - continue - } - prices := model.Billing.TokenPrices - multiplier := 1.0 - if model.Billing.Multiplier != nil { - multiplier = *model.Billing.Multiplier - } - in, out := 0.0, 0.0 - if prices.InputPrice != nil { - in = *prices.InputPrice - } - if prices.OutputPrice != nil { - out = *prices.OutputPrice - } - batch := int64(0) - if prices.BatchSize != nil { - batch = *prices.BatchSize - } - fmt.Printf("%s: input=%v output=%v per %d tokens (x%v)\n", model.ID, in, out, batch, multiplier) - } -} -``` - ```golang list, _ := client.RPC.Models.List(ctx, &rpc.ModelsListRequest{}) @@ -995,23 +571,6 @@ for _, model := range list.Models { {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -await using var client = new CopilotClient(); - -var list = await client.Rpc.Models.ListAsync(); - -foreach (var model in list.Models) -{ - var prices = model.Billing?.TokenPrices; - if (prices is null) continue; - Console.WriteLine( - $"{model.Id}: input={prices.InputPrice} output={prices.OutputPrice} " + - $"per {prices.BatchSize} tokens (x{model.Billing?.Multiplier ?? 1})"); -} -``` - ```csharp var list = await client.Rpc.Models.ListAsync(); @@ -1087,22 +646,6 @@ The example uses the fields below; the generated `AccountQuotaSnapshot` type is {% codetabs %} {% codetab typescript %} -```typescript -import { CopilotClient } from "@github/copilot-sdk"; - -const client = new CopilotClient(); - -const { quotaSnapshots } = await client.rpc.account.getQuota({}); -const premium = quotaSnapshots["premium_interactions"]; - -if (premium) { - console.log( - `Premium interactions: ${premium.usedRequests}/${premium.entitlementRequests} ` + - `(${premium.remainingPercentage.toFixed(1)}% left, resets ${premium.resetDate ?? "n/a"})`, - ); -} -``` - ```typescript const { quotaSnapshots } = await client.rpc.account.getQuota({}); const premium = quotaSnapshots["premium_interactions"]; @@ -1118,22 +661,6 @@ if (premium) { {% endcodetab %} {% codetab python %} -```python -from copilot import CopilotClient -from copilot.rpc import AccountGetQuotaRequest - -client = CopilotClient() - -result = await client.rpc.account.get_quota(AccountGetQuotaRequest()) -premium = result.quota_snapshots.get("premium_interactions") - -if premium is not None: - print( - f"Premium interactions: {premium.used_requests}/{premium.entitlement_requests} " - f"({premium.remaining_percentage:.1f}% left, resets {premium.reset_date or 'n/a'})" - ) -``` - ```python result = await client.rpc.account.get_quota(AccountGetQuotaRequest()) premium = result.quota_snapshots.get("premium_interactions") @@ -1148,36 +675,6 @@ if premium is not None: {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - "time" - - copilot "github.com/github/copilot-sdk/go" - "github.com/github/copilot-sdk/go/rpc" -) - -func main() { - ctx := context.Background() - client := copilot.NewClient(nil) - client.Start(ctx) - - result, _ := client.RPC.Account.GetQuota(ctx, &rpc.AccountGetQuotaRequest{}) - - if premium, ok := result.QuotaSnapshots["premium_interactions"]; ok { - resets := "n/a" - if premium.ResetDate != nil { - resets = premium.ResetDate.Format(time.RFC3339) - } - fmt.Printf("Premium interactions: %d/%d (%.1f%% left, resets %s)\n", - premium.UsedRequests, premium.EntitlementRequests, premium.RemainingPercentage, resets) - } -} -``` - ```golang result, _ := client.RPC.Account.GetQuota(ctx, &rpc.AccountGetQuotaRequest{}) @@ -1194,21 +691,6 @@ if premium, ok := result.QuotaSnapshots["premium_interactions"]; ok { {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -await using var client = new CopilotClient(); - -var result = await client.Rpc.Account.GetQuotaAsync(); - -if (result.QuotaSnapshots.TryGetValue("premium_interactions", out var premium)) -{ - Console.WriteLine( - $"Premium interactions: {premium.UsedRequests}/{premium.EntitlementRequests} " + - $"({premium.RemainingPercentage:F1}% left, resets {premium.ResetDate?.ToString("o") ?? "n/a"})"); -} -``` - ```csharp var result = await client.Rpc.Account.GetQuotaAsync(); diff --git a/content/copilot/how-tos/copilot-sdk/getting-started.md b/content/copilot/how-tos/copilot-sdk/getting-started.md index 228cd563d0d1..0b14f781ee31 100644 --- a/content/copilot/how-tos/copilot-sdk/getting-started.md +++ b/content/copilot/how-tos/copilot-sdk/getting-started.md @@ -650,30 +650,6 @@ unsubscribeIdle(); {% endcodetab %} {% codetab python %} -```python -from copilot import CopilotClient, PermissionDecisionApproveOnce -from copilot.session_events import SessionEvent, SessionEventType - -client = CopilotClient() - -session = await client.create_session(on_permission_request=lambda req, inv: PermissionDecisionApproveOnce()) - -# Subscribe to all events -unsubscribe = session.on(lambda event: print(f"Event: {event.type}")) - -# Filter by event type in your handler -def handle_event(event: SessionEvent) -> None: - if event.type == SessionEventType.SESSION_IDLE: - print("Session is idle") - elif event.type == SessionEventType.ASSISTANT_MESSAGE: - print(f"Message: {event.data.content}") - -unsubscribe = session.on(handle_event) - -# Later, to unsubscribe: -unsubscribe() -``` - ```python # Subscribe to all events unsubscribe = session.on(lambda event: print(f"Event: {event.type}")) @@ -694,39 +670,6 @@ unsubscribe() {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "fmt" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - session := &copilot.Session{} - - // Subscribe to all events - unsubscribe := session.On(func(event copilot.SessionEvent) { - fmt.Println("Event:", event.Type) - }) - - // Filter by event type in your handler - session.On(func(event copilot.SessionEvent) { - switch d := event.Data.(type) { - case *copilot.SessionIdleData: - _ = d - fmt.Println("Session is idle") - case *copilot.AssistantMessageData: - fmt.Println("Message:", d.Content) - } - }) - - // Later, to unsubscribe: - unsubscribe() -} -``` - ```golang // Subscribe to all events unsubscribe := session.On(func(event copilot.SessionEvent) { @@ -774,36 +717,6 @@ tokio::spawn(async move { {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public static class EventSubscriptionExample -{ - public static void Example(CopilotSession session) - { - // Subscribe to all events - var unsubscribe = session.On(ev => Console.WriteLine($"Event: {ev.Type}")); - - // Filter by event type using pattern matching - session.On(ev => - { - switch (ev) - { - case SessionIdleEvent: - Console.WriteLine("Session is idle"); - break; - case AssistantMessageEvent msg: - Console.WriteLine($"Message: {msg.Data.Content}"); - break; - } - }); - - // Later, to unsubscribe: - unsubscribe.Dispose(); - } -} -``` - ```csharp // Subscribe to all events var unsubscribe = session.On(ev => Console.WriteLine($"Event: {ev.Type}")); @@ -1943,35 +1856,6 @@ session = await client.create_session(on_permission_request=PermissionHandler.ap {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "log" - - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - ctx := context.Background() - - client := copilot.NewClient(&copilot.ClientOptions{ - Connection: copilot.URIConnection{URL: "localhost:4321"}, - }) - - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - // Use the client normally - _, _ = client.CreateSession(ctx, &copilot.SessionConfig{ - OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - }) -} -``` - ```golang import copilot "github.com/github/copilot-sdk/go" diff --git a/content/copilot/how-tos/copilot-sdk/hooks/error-handling.md b/content/copilot/how-tos/copilot-sdk/hooks/error-handling.md index d3b3d762752c..a057e68d50d0 100644 --- a/content/copilot/how-tos/copilot-sdk/hooks/error-handling.md +++ b/content/copilot/how-tos/copilot-sdk/hooks/error-handling.md @@ -25,14 +25,6 @@ contentType: how-tos {% codetabs %} {% codetab typescript %} -```typescript -import type { ErrorOccurredHookInput, HookInvocation, ErrorOccurredHookOutput } from "@github/copilot-sdk"; -type ErrorOccurredHandler = ( - input: ErrorOccurredHookInput, - invocation: HookInvocation -) => Promise; -``` - ```typescript type ErrorOccurredHandler = ( input: ErrorOccurredHookInput, @@ -43,16 +35,6 @@ type ErrorOccurredHandler = ( {% endcodetab %} {% codetab python %} -```python -from copilot.session import ErrorOccurredHookInput, ErrorOccurredHookOutput -from typing import Callable, Awaitable - -ErrorOccurredHandler = Callable[ - [ErrorOccurredHookInput, dict[str, str]], - Awaitable[ErrorOccurredHookOutput | None] -] -``` - ```python ErrorOccurredHandler = Callable[ [ErrorOccurredHookInput, dict[str, str]], @@ -63,19 +45,6 @@ ErrorOccurredHandler = Callable[ {% endcodetab %} {% codetab go %} -```golang -package main - -import copilot "github.com/github/copilot-sdk/go" - -type ErrorOccurredHandler func( - input copilot.ErrorOccurredHookInput, - invocation copilot.HookInvocation, -) (*copilot.ErrorOccurredHookOutput, error) - -func main() {} -``` - ```golang type ErrorOccurredHandler func( input ErrorOccurredHookInput, @@ -86,14 +55,6 @@ type ErrorOccurredHandler func( {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public delegate Task ErrorOccurredHandler( - ErrorOccurredHookInput input, - HookInvocation invocation); -``` - ```csharp public delegate Task ErrorOccurredHandler( ErrorOccurredHookInput input, @@ -179,32 +140,6 @@ session = await client.create_session(on_permission_request=PermissionHandler.ap {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient(nil) - session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ - OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Hooks: &copilot.SessionHooks{ - OnErrorOccurred: func(input copilot.ErrorOccurredHookInput, inv copilot.HookInvocation) (*copilot.ErrorOccurredHookOutput, error) { - fmt.Printf("[%s] Error: %s\n", inv.SessionID, input.Error) - fmt.Printf(" Context: %s\n", input.ErrorContext) - fmt.Printf(" Recoverable: %v\n", input.Recoverable) - return nil, nil - }, - }, - }) - _ = session -} -``` - ```golang session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ Hooks: &copilot.SessionHooks{ @@ -221,31 +156,6 @@ session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public static class ErrorHandlingExample -{ - public static async Task Main() - { - await using var client = new CopilotClient(); - var session = await client.CreateSessionAsync(new SessionConfig - { - Hooks = new SessionHooks - { - OnErrorOccurred = (input, invocation) => - { - Console.Error.WriteLine($"[{invocation.SessionId}] Error: {input.Error}"); - Console.Error.WriteLine($" Context: {input.ErrorContext}"); - Console.Error.WriteLine($" Recoverable: {input.Recoverable}"); - return Task.FromResult(null); - }, - }, - }); - } -} -``` - ```csharp var session = await client.CreateSessionAsync(new SessionConfig { diff --git a/content/copilot/how-tos/copilot-sdk/hooks/hooks-overview.md b/content/copilot/how-tos/copilot-sdk/hooks/hooks-overview.md index 074ce5d30e8c..2a5df49b0ab7 100644 --- a/content/copilot/how-tos/copilot-sdk/hooks/hooks-overview.md +++ b/content/copilot/how-tos/copilot-sdk/hooks/hooks-overview.md @@ -29,6 +29,7 @@ contentType: how-tos | [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/post-tool-use) | After a tool executes (success only) | Result transformation, logging | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/post-tool-use#failure-variant) | After a tool execution whose result was a failure | Inject retry guidance, log failures | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted) | When user sends a message | Prompt modification, filtering | +| [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/user-prompt-transformed) | After runtime prompt transformation | Inspect or replace model-facing content | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/session-lifecycle#session-start) | Session begins | Add context, configure session | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/session-lifecycle#session-end) | Session ends | Cleanup, analytics | | [AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/error-handling) | Error happens | Custom error handling | @@ -269,6 +270,7 @@ const session = await client.createSession({ * **[AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/pre-tool-use)** - Control tool execution permissions * **[AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/post-tool-use)** - Transform tool results * **[AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted)** - Modify user prompts +* **[AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/user-prompt-transformed)** - Replace model-facing prompts * **[AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/session-lifecycle)** - Session start and end * **[AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/session-lifecycle#agent-stop)** - Validate completion before the agent stops * **[AUTOTITLE](/copilot/how-tos/copilot-sdk/hooks/error-handling)** - Custom error handling diff --git a/content/copilot/how-tos/copilot-sdk/hooks/index.md b/content/copilot/how-tos/copilot-sdk/hooks/index.md index f7069d5d97df..0b8468f35c71 100644 --- a/content/copilot/how-tos/copilot-sdk/hooks/index.md +++ b/content/copilot/how-tos/copilot-sdk/hooks/index.md @@ -14,6 +14,7 @@ children: - /pre-tool-use - /session-lifecycle - /user-prompt-submitted + - /user-prompt-transformed --- diff --git a/content/copilot/how-tos/copilot-sdk/hooks/post-tool-use.md b/content/copilot/how-tos/copilot-sdk/hooks/post-tool-use.md index c24e40c784d1..822ace998384 100644 --- a/content/copilot/how-tos/copilot-sdk/hooks/post-tool-use.md +++ b/content/copilot/how-tos/copilot-sdk/hooks/post-tool-use.md @@ -28,19 +28,6 @@ contentType: how-tos {% codetabs %} {% codetab typescript %} -```typescript -import type { - PostToolUseHookInput, - HookInvocation, - PostToolUseHookOutput, -} from "@github/copilot-sdk"; -type PostToolUseHandler = ( - input: PostToolUseHookInput, - invocation: HookInvocation, -) => Promise; -``` - - ```typescript type PostToolUseHandler = ( input: PostToolUseHookInput, @@ -51,17 +38,6 @@ type PostToolUseHandler = ( {% endcodetab %} {% codetab python %} -```python -from copilot.session import PostToolUseHookInput, PostToolUseHookOutput -from typing import Callable, Awaitable - -PostToolUseHandler = Callable[ - [PostToolUseHookInput, dict[str, str]], - Awaitable[PostToolUseHookOutput | None] -] -``` - - ```python PostToolUseHandler = Callable[ [PostToolUseHookInput, dict[str, str]], @@ -72,20 +48,6 @@ PostToolUseHandler = Callable[ {% endcodetab %} {% codetab go %} -```golang -package main - -import copilot "github.com/github/copilot-sdk/go" - -type PostToolUseHandler func( - input copilot.PostToolUseHookInput, - invocation copilot.HookInvocation, -) (*copilot.PostToolUseHookOutput, error) - -func main() {} -``` - - ```golang type PostToolUseHandler func( input PostToolUseHookInput, @@ -96,15 +58,6 @@ type PostToolUseHandler func( {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public delegate Task PostToolUseHandler( - PostToolUseHookInput input, - HookInvocation invocation); -``` - - ```csharp public delegate Task PostToolUseHandler( PostToolUseHookInput input, @@ -114,17 +67,6 @@ public delegate Task PostToolUseHandler( {% endcodetab %} {% codetab java %} -```java -import com.github.copilot.rpc.*; -import java.util.concurrent.CompletableFuture; - -public class PostToolUseSignature { - PostToolUseHandler handler = (PostToolUseHookInput input, HookInvocation invocation) -> - CompletableFuture.completedFuture(null); - public static void main(String[] args) {} -} -``` - ```java @FunctionalInterface public interface PostToolUseHandler { @@ -195,33 +137,6 @@ session = await client.create_session(on_permission_request=PermissionHandler.ap {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient(nil) - session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ - OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Hooks: &copilot.SessionHooks{ - OnPostToolUse: func(input copilot.PostToolUseHookInput, inv copilot.HookInvocation) (*copilot.PostToolUseHookOutput, error) { - fmt.Printf("[%s] Tool: %s\n", inv.SessionID, input.ToolName) - fmt.Printf(" Args: %v\n", input.ToolArgs) - fmt.Printf(" Result: %v\n", input.ToolResult) - return nil, nil - }, - }, - }) - _ = session -} -``` - - ```golang session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ Hooks: &copilot.SessionHooks{ @@ -238,32 +153,6 @@ session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public static class PostToolUseExample -{ - public static async Task Main() - { - await using var client = new CopilotClient(); - var session = await client.CreateSessionAsync(new SessionConfig - { - Hooks = new SessionHooks - { - OnPostToolUse = (input, invocation) => - { - Console.WriteLine($"[{invocation.SessionId}] Tool: {input.ToolName}"); - Console.WriteLine($" Args: {input.ToolArgs}"); - Console.WriteLine($" Result: {input.ToolResult}"); - return Task.FromResult(null); - }, - }, - }); - } -} -``` - - ```csharp var session = await client.CreateSessionAsync(new SessionConfig { diff --git a/content/copilot/how-tos/copilot-sdk/hooks/pre-tool-use.md b/content/copilot/how-tos/copilot-sdk/hooks/pre-tool-use.md index a334078c36a9..9e3d0dc37036 100644 --- a/content/copilot/how-tos/copilot-sdk/hooks/pre-tool-use.md +++ b/content/copilot/how-tos/copilot-sdk/hooks/pre-tool-use.md @@ -23,14 +23,6 @@ contentType: how-tos {% codetabs %} {% codetab typescript %} -```typescript -import type { PreToolUseHookInput, HookInvocation, PreToolUseHookOutput } from "@github/copilot-sdk"; -type PreToolUseHandler = ( - input: PreToolUseHookInput, - invocation: HookInvocation -) => Promise; -``` - ```typescript type PreToolUseHandler = ( input: PreToolUseHookInput, @@ -41,16 +33,6 @@ type PreToolUseHandler = ( {% endcodetab %} {% codetab python %} -```python -from copilot.session import PreToolUseHookInput, PreToolUseHookOutput -from typing import Callable, Awaitable - -PreToolUseHandler = Callable[ - [PreToolUseHookInput, dict[str, str]], - Awaitable[PreToolUseHookOutput | None] -] -``` - ```python PreToolUseHandler = Callable[ [PreToolUseHookInput, dict[str, str]], @@ -61,19 +43,6 @@ PreToolUseHandler = Callable[ {% endcodetab %} {% codetab go %} -```golang -package main - -import copilot "github.com/github/copilot-sdk/go" - -type PreToolUseHandler func( - input copilot.PreToolUseHookInput, - invocation copilot.HookInvocation, -) (*copilot.PreToolUseHookOutput, error) - -func main() {} -``` - ```golang type PreToolUseHandler func( input PreToolUseHookInput, @@ -84,14 +53,6 @@ type PreToolUseHandler func( {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public delegate Task PreToolUseHandler( - PreToolUseHookInput input, - HookInvocation invocation); -``` - ```csharp public delegate Task PreToolUseHandler( PreToolUseHookInput input, @@ -101,17 +62,6 @@ public delegate Task PreToolUseHandler( {% endcodetab %} {% codetab java %} -```java -import com.github.copilot.rpc.*; -import java.util.concurrent.CompletableFuture; - -public class PreToolUseSignature { - PreToolUseHandler handler = (PreToolUseHookInput input, HookInvocation invocation) -> - CompletableFuture.completedFuture(PreToolUseHookOutput.allow()); - public static void main(String[] args) {} -} -``` - ```java @FunctionalInterface public interface PreToolUseHandler { @@ -206,33 +156,6 @@ session = await client.create_session(on_permission_request=PermissionHandler.ap {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient(nil) - session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ - OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Hooks: &copilot.SessionHooks{ - OnPreToolUse: func(input copilot.PreToolUseHookInput, inv copilot.HookInvocation) (*copilot.PreToolUseHookOutput, error) { - fmt.Printf("[%s] Calling %s\n", inv.SessionID, input.ToolName) - fmt.Printf(" Args: %v\n", input.ToolArgs) - return &copilot.PreToolUseHookOutput{ - PermissionDecision: "allow", - }, nil - }, - }, - }) - _ = session -} -``` - ```golang session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ Hooks: &copilot.SessionHooks{ @@ -250,32 +173,6 @@ session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public static class PreToolUseExample -{ - public static async Task Main() - { - await using var client = new CopilotClient(); - var session = await client.CreateSessionAsync(new SessionConfig - { - Hooks = new SessionHooks - { - OnPreToolUse = (input, invocation) => - { - Console.WriteLine($"[{invocation.SessionId}] Calling {input.ToolName}"); - Console.WriteLine($" Args: {input.ToolArgs}"); - return Task.FromResult( - new PreToolUseHookOutput { PermissionDecision = "allow" } - ); - }, - }, - }); - } -} -``` - ```csharp var session = await client.CreateSessionAsync(new SessionConfig { diff --git a/content/copilot/how-tos/copilot-sdk/hooks/session-lifecycle.md b/content/copilot/how-tos/copilot-sdk/hooks/session-lifecycle.md index 586a900e6511..3aa7bbd1d865 100644 --- a/content/copilot/how-tos/copilot-sdk/hooks/session-lifecycle.md +++ b/content/copilot/how-tos/copilot-sdk/hooks/session-lifecycle.md @@ -29,14 +29,6 @@ The `onSessionStart` hook is called when a session begins (new or resumed). {% codetabs %} {% codetab typescript %} -```typescript -import type { SessionStartHookInput, HookInvocation, SessionStartHookOutput } from "@github/copilot-sdk"; -type SessionStartHandler = ( - input: SessionStartHookInput, - invocation: HookInvocation -) => Promise; -``` - ```typescript type SessionStartHandler = ( input: SessionStartHookInput, @@ -47,16 +39,6 @@ type SessionStartHandler = ( {% endcodetab %} {% codetab python %} -```python -from copilot.session import SessionStartHookInput, SessionStartHookOutput -from typing import Callable, Awaitable - -SessionStartHandler = Callable[ - [SessionStartHookInput, dict[str, str]], - Awaitable[SessionStartHookOutput | None] -] -``` - ```python SessionStartHandler = Callable[ [SessionStartHookInput, dict[str, str]], @@ -67,19 +49,6 @@ SessionStartHandler = Callable[ {% endcodetab %} {% codetab go %} -```golang -package main - -import copilot "github.com/github/copilot-sdk/go" - -type SessionStartHandler func( - input copilot.SessionStartHookInput, - invocation copilot.HookInvocation, -) (*copilot.SessionStartHookOutput, error) - -func main() {} -``` - ```golang type SessionStartHandler func( input SessionStartHookInput, @@ -90,14 +59,6 @@ type SessionStartHandler func( {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public delegate Task SessionStartHandler( - SessionStartHookInput input, - HookInvocation invocation); -``` - ```csharp public delegate Task SessionStartHandler( SessionStartHookInput input, @@ -107,17 +68,6 @@ public delegate Task SessionStartHandler( {% endcodetab %} {% codetab java %} -```java -import com.github.copilot.rpc.*; -import java.util.concurrent.CompletableFuture; - -public class SessionStartSignature { - SessionStartHandler handler = (SessionStartHookInput input, HookInvocation invocation) -> - CompletableFuture.completedFuture(null); - public static void main(String[] args) {} -} -``` - ```java @FunctionalInterface public interface SessionStartHandler { @@ -269,16 +219,6 @@ type SessionEndHandler = ( {% endcodetab %} {% codetab python %} -```python -from copilot.session import SessionEndHookInput -from typing import Callable, Awaitable - -SessionEndHandler = Callable[ - [SessionEndHookInput, dict[str, str]], - Awaitable[None] -] -``` - ```python SessionEndHandler = Callable[ [SessionEndHookInput, dict[str, str]], @@ -289,19 +229,6 @@ SessionEndHandler = Callable[ {% endcodetab %} {% codetab go %} -```golang -package main - -import copilot "github.com/github/copilot-sdk/go" - -type SessionEndHandler func( - input copilot.SessionEndHookInput, - invocation copilot.HookInvocation, -) error - -func main() {} -``` - ```golang type SessionEndHandler func( input SessionEndHookInput, @@ -321,17 +248,6 @@ public delegate Task SessionEndHandler( {% endcodetab %} {% codetab java %} -```java -import com.github.copilot.rpc.*; -import java.util.concurrent.CompletableFuture; - -public class SessionEndSignature { - SessionEndHandler handler = (SessionEndHookInput input, HookInvocation invocation) -> - CompletableFuture.completedFuture(null); - public static void main(String[] args) {} -} -``` - ```java @FunctionalInterface public interface SessionEndHandler { diff --git a/content/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted.md b/content/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted.md index e13c7017f645..ada14910f7e0 100644 --- a/content/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted.md +++ b/content/copilot/how-tos/copilot-sdk/hooks/user-prompt-submitted.md @@ -25,14 +25,6 @@ contentType: how-tos {% codetabs %} {% codetab typescript %} -```typescript -import type { UserPromptSubmittedHookInput, HookInvocation, UserPromptSubmittedHookOutput } from "@github/copilot-sdk"; -type UserPromptSubmittedHandler = ( - input: UserPromptSubmittedHookInput, - invocation: HookInvocation -) => Promise; -``` - ```typescript type UserPromptSubmittedHandler = ( input: UserPromptSubmittedHookInput, @@ -43,16 +35,6 @@ type UserPromptSubmittedHandler = ( {% endcodetab %} {% codetab python %} -```python -from copilot.session import UserPromptSubmittedHookInput, UserPromptSubmittedHookOutput -from typing import Callable, Awaitable - -UserPromptSubmittedHandler = Callable[ - [UserPromptSubmittedHookInput, dict[str, str]], - Awaitable[UserPromptSubmittedHookOutput | None] -] -``` - ```python UserPromptSubmittedHandler = Callable[ [UserPromptSubmittedHookInput, dict[str, str]], @@ -63,19 +45,6 @@ UserPromptSubmittedHandler = Callable[ {% endcodetab %} {% codetab go %} -```golang -package main - -import copilot "github.com/github/copilot-sdk/go" - -type UserPromptSubmittedHandler func( - input copilot.UserPromptSubmittedHookInput, - invocation copilot.HookInvocation, -) (*copilot.UserPromptSubmittedHookOutput, error) - -func main() {} -``` - ```golang type UserPromptSubmittedHandler func( input UserPromptSubmittedHookInput, @@ -86,14 +55,6 @@ type UserPromptSubmittedHandler func( {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public delegate Task UserPromptSubmittedHandler( - UserPromptSubmittedHookInput input, - HookInvocation invocation); -``` - ```csharp public delegate Task UserPromptSubmittedHandler( UserPromptSubmittedHookInput input, @@ -103,17 +64,6 @@ public delegate Task UserPromptSubmittedHandler( {% endcodetab %} {% codetab java %} -```java -import com.github.copilot.rpc.*; -import java.util.concurrent.CompletableFuture; - -public class UserPromptSubmittedSignature { - UserPromptSubmittedHandler handler = (UserPromptSubmittedHookInput input, HookInvocation invocation) -> - CompletableFuture.completedFuture(null); - public static void main(String[] args) {} -} -``` - ```java @FunctionalInterface public interface UserPromptSubmittedHandler { @@ -178,30 +128,6 @@ session = await client.create_session(on_permission_request=PermissionHandler.ap {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - client := copilot.NewClient(nil) - session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ - OnPermissionRequest: copilot.PermissionHandler.ApproveAll, - Hooks: &copilot.SessionHooks{ - OnUserPromptSubmitted: func(input copilot.UserPromptSubmittedHookInput, inv copilot.HookInvocation) (*copilot.UserPromptSubmittedHookOutput, error) { - fmt.Printf("[%s] User: %s\n", inv.SessionID, input.Prompt) - return nil, nil - }, - }, - }) - _ = session -} -``` - ```golang session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ Hooks: &copilot.SessionHooks{ @@ -216,29 +142,6 @@ session, _ := client.CreateSession(context.Background(), &copilot.SessionConfig{ {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -public static class UserPromptSubmittedExample -{ - public static async Task Main() - { - await using var client = new CopilotClient(); - var session = await client.CreateSessionAsync(new SessionConfig - { - Hooks = new SessionHooks - { - OnUserPromptSubmitted = (input, invocation) => - { - Console.WriteLine($"[{invocation.SessionId}] User: {input.Prompt}"); - return Task.FromResult(null); - }, - }, - }); - } -} -``` - ```csharp var session = await client.CreateSessionAsync(new SessionConfig { diff --git a/content/copilot/how-tos/copilot-sdk/hooks/user-prompt-transformed.md b/content/copilot/how-tos/copilot-sdk/hooks/user-prompt-transformed.md new file mode 100644 index 000000000000..51c366643e64 --- /dev/null +++ b/content/copilot/how-tos/copilot-sdk/hooks/user-prompt-transformed.md @@ -0,0 +1,137 @@ +--- +title: User prompt transformed hook +shortTitle: User prompt transformed +intro: >- + The `userPromptTransformed` hook runs after the runtime adds generated context + to a submitted prompt, but before the resulting content is persisted to + session history or sent to the model. +versions: + fpt: '*' + ghec: '*' +contentType: how-tos +--- + + + + +Use it when you need to inspect or replace the exact model-facing prompt. The `prompt` input contains the user prompt after any `userPromptSubmitted` hooks have run, while `transformedPrompt` also contains runtime-generated context such as ``. + +## Input and output + +| Input field | Type | Description | +| --- | --- | --- | +| `sessionId` | string | Runtime session ID | +| `timestamp` | date/time | Time the hook was invoked | +| `cwd` / `workingDirectory` | string | Current working directory | +| `prompt` | string | Prompt after `userPromptSubmitted` hooks | +| `transformedPrompt` | string | Model-facing prompt after runtime transformations | + +Return no value to leave the transformed prompt unchanged. Return `modifiedTransformedPrompt` to replace the content that is stored in session history and sent to the model. + +## Examples + +{% codetabs %} +{% codetab typescript %} + + + +```typescript +const session = await client.createSession({ + hooks: { + onUserPromptTransformed: async (input) => ({ + modifiedTransformedPrompt: redact(input.transformedPrompt), + }), + }, +}); +``` + +{% endcodetab %} +{% codetab python %} + + + +```python +session = await client.create_session( + hooks={ + "on_user_prompt_transformed": lambda input_data, invocation: { + "modifiedTransformedPrompt": redact(input_data["transformedPrompt"]) + } + } +) +``` + +{% endcodetab %} +{% codetab go %} + + + +```golang +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Hooks: &copilot.SessionHooks{ + OnUserPromptTransformed: func(input copilot.UserPromptTransformedHookInput, invocation copilot.HookInvocation) (*copilot.UserPromptTransformedHookOutput, error) { + return &copilot.UserPromptTransformedHookOutput{ + ModifiedTransformedPrompt: copilot.String(redact(input.TransformedPrompt)), + }, nil + }, + }, +}) +``` + +{% endcodetab %} +{% codetab dotnet %} + + + +```csharp +var session = await client.CreateSessionAsync(new SessionConfig +{ + Hooks = new SessionHooks + { + OnUserPromptTransformed = (input, invocation) => + Task.FromResult(new() + { + ModifiedTransformedPrompt = Redact(input.TransformedPrompt), + }), + }, +}); +``` + +{% endcodetab %} +{% codetab java %} + + + +```java +var hooks = new SessionHooks().setOnUserPromptTransformed((input, invocation) -> + CompletableFuture.completedFuture( + new UserPromptTransformedHookOutput(redact(input.transformedPrompt())))); + +var session = client.createSession(new SessionConfig().setHooks(hooks)).get(); +``` + +{% endcodetab %} +{% codetab rust %} + +```rust +#[async_trait] +impl SessionHooks for MyHooks { + async fn on_user_prompt_transformed( + &self, + input: UserPromptTransformedInput, + _ctx: HookContext, + ) -> Option { + Some(UserPromptTransformedOutput { + modified_transformed_prompt: Some(redact(&input.transformed_prompt)), + }) + } +} + +let session = client + .create_session(SessionConfig::default().with_hooks(Arc::new(MyHooks))) + .await?; +``` + +{% endcodetab %} +{% endcodetabs %} + +The replacement is persisted as the user message content, so resumed sessions replay the modified content unchanged. diff --git a/content/copilot/how-tos/copilot-sdk/observability/opentelemetry.md b/content/copilot/how-tos/copilot-sdk/observability/opentelemetry.md index 23dfdd3c4b96..528ab7dad068 100644 --- a/content/copilot/how-tos/copilot-sdk/observability/opentelemetry.md +++ b/content/copilot/how-tos/copilot-sdk/observability/opentelemetry.md @@ -162,29 +162,36 @@ When the CLI invokes a tool handler, the `traceparent` and `tracestate` from the ```typescript +import { defineTool } from "@github/copilot-sdk"; import { propagation, context, trace } from "@opentelemetry/api"; -session.registerTool(myTool, async (args, invocation) => { - // Restore the CLI's trace context as the active context - const carrier = { - traceparent: invocation.traceparent, - tracestate: invocation.tracestate, - }; - const parentCtx = propagation.extract(context.active(), carrier); - - // Create a child span under the CLI's span - const tracer = trace.getTracer("my-app"); - return context.with(parentCtx, () => - tracer.startActiveSpan("my-tool", async (span) => { - try { - const result = await doWork(args); - return result; - } finally { - span.end(); - } - }) - ); +const myTool = defineTool("my-tool", { + description: "Do work", + handler: async (args, invocation) => { + // Restore the CLI's trace context as the active context + const carrier = { + traceparent: invocation.traceparent, + tracestate: invocation.tracestate, + }; + const parentCtx = propagation.extract(context.active(), carrier); + + // Create a child span under the CLI's span + const tracer = trace.getTracer("my-app"); + return context.with(parentCtx, () => + tracer.startActiveSpan("my-tool", async (span) => { + try { + const result = await doWork(args); + return result; + } finally { + span.end(); + } + }) + ); + }, }); + +// Tool handlers are registered when the session is created. +const session = await client.createSession({ tools: [myTool] }); ``` ### Per-language dependencies diff --git a/content/copilot/how-tos/copilot-sdk/setup/backend-services.md b/content/copilot/how-tos/copilot-sdk/setup/backend-services.md index d5f7672802e2..a70cdf76553f 100644 --- a/content/copilot/how-tos/copilot-sdk/setup/backend-services.md +++ b/content/copilot/how-tos/copilot-sdk/setup/backend-services.md @@ -143,37 +143,6 @@ response = await session.send_and_wait(message) {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - "time" - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - ctx := context.Background() - userID := "user1" - message := "Hello" - - client := copilot.NewClient(&copilot.ClientOptions{ - Connection: copilot.URIConnection{URL: "localhost:4321"}, - }) - client.Start(ctx) - defer client.Stop() - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - SessionID: fmt.Sprintf("user-%s-%d", userID, time.Now().Unix()), - Model: "gpt-5.4", - }) - - response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: message}) - _ = response -} -``` - ```golang client := copilot.NewClient(&copilot.ClientOptions{ Connection: copilot.URIConnection{URL: "localhost:4321"}, @@ -192,27 +161,6 @@ response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: message}) {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -var userId = "user1"; -var message = "Hello"; - -var client = new CopilotClient(new CopilotClientOptions -{ - Connection = RuntimeConnection.ForUri("localhost:4321"), -}); - -await using var session = await client.CreateSessionAsync(new SessionConfig -{ - SessionId = $"user-{userId}-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}", - Model = "gpt-5.4", -}); - -var response = await session.SendAndWaitAsync( - new MessageOptions { Prompt = message }); -``` - ```csharp var client = new CopilotClient(new CopilotClientOptions { diff --git a/content/copilot/how-tos/copilot-sdk/setup/bundled-cli.md b/content/copilot/how-tos/copilot-sdk/setup/bundled-cli.md index 6f783d478388..7f9f3b012772 100644 --- a/content/copilot/how-tos/copilot-sdk/setup/bundled-cli.md +++ b/content/copilot/how-tos/copilot-sdk/setup/bundled-cli.md @@ -78,33 +78,6 @@ await client.stop() > [!NOTE] > Unlike Node.js, Python, and .NET, the Go SDK does not include a CLI as an automatic dependency. With no explicit path, `NewClient(nil)` uses an embedded CLI when available, then falls back to `copilot` on `PATH`. To embed a CLI, run the [bundler tool](https://github.com/github/copilot-sdk/tree/main/go/README.md#distributing-your-application-with-an-embedded-github-copilot-cli) at build time. You can also set `COPILOT_CLI_PATH` or point a `Connection` at an existing binary. See [AUTOTITLE](/copilot/how-tos/copilot-sdk/setup/local-cli) for details. -```golang -package main - -import ( - "context" - "fmt" - "log" - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - ctx := context.Background() - - client := copilot.NewClient(nil) - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-5.4"}) - response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } -} -``` - ```golang client := copilot.NewClient(nil) if err := client.Start(ctx); err != nil { diff --git a/content/copilot/how-tos/copilot-sdk/setup/github-oauth.md b/content/copilot/how-tos/copilot-sdk/setup/github-oauth.md index 326ebc3001cf..3b6f14377bce 100644 --- a/content/copilot/how-tos/copilot-sdk/setup/github-oauth.md +++ b/content/copilot/how-tos/copilot-sdk/setup/github-oauth.md @@ -126,39 +126,6 @@ response = await session.send_and_wait("Hello!") {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - copilot "github.com/github/copilot-sdk/go" -) - -func createClientForUser(userToken string) *copilot.Client { - return copilot.NewClient(&copilot.ClientOptions{ - GitHubToken: userToken, - UseLoggedInUser: copilot.Bool(false), - }) -} - -func main() { - ctx := context.Background() - userID := "user1" - - client := createClientForUser("gho_user_access_token") - client.Start(ctx) - defer client.Stop() - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{ - SessionID: fmt.Sprintf("user-%s-session", userID), - Model: "gpt-5.4", - }) - response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) - _ = response -} -``` - ```golang func createClientForUser(userToken string) *copilot.Client { return copilot.NewClient(&copilot.ClientOptions{ @@ -182,29 +149,6 @@ response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"} {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -CopilotClient CreateClientForUser(string userToken) => - new CopilotClient(new CopilotClientOptions - { - GitHubToken = userToken, - UseLoggedInUser = false, - }); - -var userId = "user1"; - -await using var client = CreateClientForUser("gho_user_access_token"); -await using var session = await client.CreateSessionAsync(new SessionConfig -{ - SessionId = $"user-{userId}-session", - Model = "gpt-5.4", -}); - -var response = await session.SendAndWaitAsync( - new MessageOptions { Prompt = "Hello!" }); -``` - ```csharp CopilotClient CreateClientForUser(string userToken) => new CopilotClient(new CopilotClientOptions diff --git a/content/copilot/how-tos/copilot-sdk/setup/local-cli.md b/content/copilot/how-tos/copilot-sdk/setup/local-cli.md index 6d6757c7951a..541c481c4038 100644 --- a/content/copilot/how-tos/copilot-sdk/setup/local-cli.md +++ b/content/copilot/how-tos/copilot-sdk/setup/local-cli.md @@ -80,37 +80,6 @@ await client.stop() > [!NOTE] > The Go SDK does not ship a CLI automatically. Install `copilot` on `PATH`, set the `COPILOT_CLI_PATH` environment variable, embed a CLI with the [bundler tool](https://github.com/github/copilot-sdk/tree/main/go/README.md#distributing-your-application-with-an-embedded-github-copilot-cli), or point `StdioConnection.Path` at an installed binary. -```golang -package main - -import ( - "context" - "fmt" - "log" - copilot "github.com/github/copilot-sdk/go" -) - -func main() { - ctx := context.Background() - - client := copilot.NewClient(&copilot.ClientOptions{ - Connection: copilot.StdioConnection{Path: "/usr/local/bin/copilot"}, - }) - if err := client.Start(ctx); err != nil { - log.Fatal(err) - } - defer client.Stop() - - session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-5.4"}) - response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) - if response != nil { - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } - } -} -``` - ```golang client := copilot.NewClient(&copilot.ClientOptions{ Connection: copilot.StdioConnection{Path: "/usr/local/bin/copilot"}, diff --git a/content/copilot/how-tos/copilot-sdk/setup/multi-tenancy.md b/content/copilot/how-tos/copilot-sdk/setup/multi-tenancy.md index 0ca22cf23b7f..89201878f19a 100644 --- a/content/copilot/how-tos/copilot-sdk/setup/multi-tenancy.md +++ b/content/copilot/how-tos/copilot-sdk/setup/multi-tenancy.md @@ -96,46 +96,6 @@ session = await client.create_session( {% endcodetab %} {% codetab go %} -```golang -package main - -import ( - "context" - "fmt" - - copilot "github.com/github/copilot-sdk/go" -) - -type appUser struct { - ID string - GitHubToken string -} - -func main() { - ctx := context.Background() - runtimeInstanceID := "instance-1" - runtimeURL := "http://127.0.0.1:8080" - requestID := "req-1" - user := appUser{ID: "alice", GitHubToken: "gho_xxx"} - - client := copilot.NewClient(&copilot.ClientOptions{ - Mode: copilot.ModeEmpty, - BaseDirectory: fmt.Sprintf("/var/lib/my-app/copilot/%s", runtimeInstanceID), - SessionIdleTimeoutSeconds: 900, - Connection: copilot.URIConnection{URL: runtimeURL}, - }) - - session, err := client.CreateSession(ctx, &copilot.SessionConfig{ - SessionID: fmt.Sprintf("user-%s-%s", user.ID, requestID), - Model: "gpt-5.4", - AvailableTools: []string{"custom:lookupOrder", "custom:createTicket"}, - GitHubToken: user.GitHubToken, - }) - _ = session - _ = err -} -``` - ```golang client := copilot.NewClient(&copilot.ClientOptions{ Mode: copilot.ModeEmpty, @@ -155,31 +115,6 @@ session, err := client.CreateSession(ctx, &copilot.SessionConfig{ {% endcodetab %} {% codetab dotnet %} -```csharp -using GitHub.Copilot; - -var runtimeInstanceId = "instance-1"; -var runtimeUrl = "http://127.0.0.1:8080"; -var requestId = "req-1"; -var user = new { Id = "alice", GitHubToken = "gho_xxx" }; - -var client = new CopilotClient(new CopilotClientOptions -{ - Mode = CopilotClientMode.Empty, - BaseDirectory = $"/var/lib/my-app/copilot/{runtimeInstanceId}", - SessionIdleTimeoutSeconds = 900, - Connection = RuntimeConnection.ForUri(runtimeUrl), -}); - -await using var session = await client.CreateSessionAsync(new SessionConfig -{ - SessionId = $"user-{user.Id}-{requestId}", - Model = "gpt-5.4", - AvailableTools = ["custom:lookupOrder", "custom:createTicket"], - GitHubToken = user.GitHubToken, -}); -``` - ```csharp var client = new CopilotClient(new CopilotClientOptions { @@ -201,38 +136,6 @@ await using var session = await client.CreateSessionAsync(new SessionConfig {% endcodetab %} {% codetab java %} -```java -import java.util.List; -import com.github.copilot.CopilotClient; -import com.github.copilot.rpc.CopilotClientOptions; -import com.github.copilot.rpc.CopilotClientMode; -import com.github.copilot.rpc.SessionConfig; - -public class MultiTenancyExample { - record User(String id, String gitHubToken) {} - - public static void main(String[] args) throws Exception { - String runtimeUrl = "http://localhost:4321"; - String requestId = "req-1"; - User user = new User("u1", "ghu_token"); - - // setCopilotHome and setSessionIdleTimeoutSeconds are ignored when - // setCliUrl is used; configure those on the runtime process instead. - var client = new CopilotClient(new CopilotClientOptions() - .setMode(CopilotClientMode.EMPTY) - .setCliUrl(runtimeUrl) - ); - - var session = client.createSession(new SessionConfig() - .setSessionId("user-" + user.id() + "-" + requestId) - .setModel("gpt-5.4") - .setAvailableTools(List.of("custom:lookupOrder", "custom:createTicket")) - .setGitHubToken(user.gitHubToken()) - ).get(); - } -} -``` - ```java // setCopilotHome and setSessionIdleTimeoutSeconds are ignored when // setCliUrl is used; configure those on the runtime process instead. diff --git a/content/copilot/how-tos/copilot-sdk/troubleshooting/compatibility.md b/content/copilot/how-tos/copilot-sdk/troubleshooting/compatibility.md index 1308a1bf5201..4499e127ded7 100644 --- a/content/copilot/how-tos/copilot-sdk/troubleshooting/compatibility.md +++ b/content/copilot/how-tos/copilot-sdk/troubleshooting/compatibility.md @@ -90,17 +90,19 @@ The Copilot SDK communicates with the CLI via JSON-RPC protocol. Features must b | System message | `systemMessage` config | Append or replace | | Custom provider | `provider` config | BYOK support | | Infinite sessions | `infiniteSessions` config | Auto-compaction | -| Permission handler | `onPermissionRequest` | Approve/deny requests | +| Permission handler | `onPermissionRequest` | Approve/deny requests; optionally attach a `decisionContext` for auto-approval telemetry | | User input handler | `onUserInputRequest` | Handle ask_user | | Skills | `skillDirectories` config | Custom skills | | Disabled skills | `disabledSkills` config | Disable specific skills | | Config directory | `configDir` config | Override default config location | | Client name | `clientName` config | Identify app in User-Agent | | Working directory | `workingDirectory` config | Set session cwd | +| Additional directories | `additionalDirectories` config | Grant session access beyond the working directory; re-supply on resume | | **Experimental** | | | | Agent management | `session.rpc.agent.*` | List, select, deselect, get current agent | | Fleet mode | `session.rpc.fleet.start()` | Parallel sub-agent execution; see [AUTOTITLE](/copilot/how-tos/copilot-sdk/features/fleet-mode) | | Manual compaction | `session.rpc.history.compact()` | Trigger compaction on demand | +| Context clearing | `session.rpc.history.clearContext()` | Replace conversation context from a terminal tool | | History truncation | `session.rpc.history.truncate()` | Remove events from a point onward | | Session forking | `server.rpc.sessions.fork()` | Fork a session at a point in history | diff --git a/content/copilot/how-tos/copilot-sdk/troubleshooting/debugging.md b/content/copilot/how-tos/copilot-sdk/troubleshooting/debugging.md index 72d819632cd5..bcd88f945ae6 100644 --- a/content/copilot/how-tos/copilot-sdk/troubleshooting/debugging.md +++ b/content/copilot/how-tos/copilot-sdk/troubleshooting/debugging.md @@ -51,19 +51,6 @@ client = CopilotClient(log_level="debug") {% endcodetab %} {% codetab go %} -```golang -package main - -import copilot "github.com/github/copilot-sdk/go" - -func main() { - client := copilot.NewClient(&copilot.ClientOptions{ - LogLevel: "debug", - }) - _ = client -} -``` - ```golang import copilot "github.com/github/copilot-sdk/go" @@ -138,21 +125,6 @@ const client = new CopilotClient({ {% endcodetab %} {% codetab go %} -```golang -package main - -import copilot "github.com/github/copilot-sdk/go" - -func main() { - client := copilot.NewClient(&copilot.ClientOptions{ - Connection: copilot.StdioConnection{ - Args: []string{"--log-dir", "/path/to/logs"}, - }, - }) - _ = client -} -``` - ```golang client := copilot.NewClient(&copilot.ClientOptions{ Connection: copilot.StdioConnection{ diff --git a/content/copilot/how-tos/copilot-sdk/troubleshooting/mcp-debugging.md b/content/copilot/how-tos/copilot-sdk/troubleshooting/mcp-debugging.md index 963ed27e1800..6c568d242305 100644 --- a/content/copilot/how-tos/copilot-sdk/troubleshooting/mcp-debugging.md +++ b/content/copilot/how-tos/copilot-sdk/troubleshooting/mcp-debugging.md @@ -255,38 +255,6 @@ cd /expected/working/dir #### .NET console apps / tools - - -```csharp -using GitHub.Copilot; - -public static class McpDotnetConfigExample -{ - public static void Main() - { - var servers = new Dictionary - { - ["my-dotnet-server"] = new McpStdioServerConfig - { - Command = @"C:\Tools\MyServer\MyServer.exe", - Args = new List(), - WorkingDirectory = @"C:\Tools\MyServer", - Tools = new List { "*" }, - }, - ["my-dotnet-tool"] = new McpStdioServerConfig - { - Command = "dotnet", - Args = new List { @"C:\Tools\MyTool\MyTool.dll" }, - WorkingDirectory = @"C:\Tools\MyTool", - Tools = new List { "*" }, - } - }; - } -} -``` - - - ```csharp // Correct configuration for .NET exe ["my-dotnet-server"] = new McpStdioServerConfig @@ -309,30 +277,6 @@ public static class McpDotnetConfigExample #### npx commands - - -```csharp -using GitHub.Copilot; - -public static class McpNpxConfigExample -{ - public static void Main() - { - var servers = new Dictionary - { - ["filesystem"] = new McpStdioServerConfig - { - Command = "cmd", - Args = new List { "/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "C:\\allowed\\path" }, - Tools = new List { "*" }, - } - }; - } -} -``` - - - ```csharp // Windows needs cmd /c for npx ["filesystem"] = new McpStdioServerConfig @@ -368,22 +312,6 @@ xattr -d com.apple.quarantine /path/to/mcp-server #### Homebrew paths - - -```typescript -import { MCPStdioServerConfig } from "@github/copilot-sdk"; - -const mcpServers: Record = { - "my-server": { - command: "/opt/homebrew/bin/node", - args: ["/path/to/server.js"], - tools: ["*"], - }, -}; -``` - - - ```typescript // GUI apps may not have /opt/homebrew in PATH mcpServers: { diff --git a/content/copilot/reference/enterprise-administrators/enterprise-managed-settings.md b/content/copilot/reference/enterprise-administrators/enterprise-managed-settings.md index c38a8f497b1c..741ffc89422e 100644 --- a/content/copilot/reference/enterprise-administrators/enterprise-managed-settings.md +++ b/content/copilot/reference/enterprise-administrators/enterprise-managed-settings.md @@ -32,7 +32,7 @@ In {% data variables.copilot.copilot_cli_short %}, the `sandbox` key is an excep | Key | Purpose | {% data variables.copilot.copilot_cli_short %} | {% data variables.product.prodname_vscode_shortname %} | {% data variables.copilot.github_copilot_app %} | {% data variables.copilot.copilot_cloud_agent %} | {% data variables.product.prodname_jetbrains_ides %} | | --- | --- | --- | --- | --- | --- | --- | | `permissions.disableBypassPermissionsMode` | Disables bypass or YOLO-style allow-all behavior | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} | -| `permissions.model` | Sets auto model selection as the default for new conversations | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | +| `model` | Sets auto model selection as the default for new conversations | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | | `enabledPlugins` | Enables or disables specific plugins by key | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | | `extraKnownMarketplaces` | Adds plugin marketplaces that users can access | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | | `strictKnownMarketplaces` | Restricts plugin installation to explicitly listed marketplaces | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | @@ -48,7 +48,7 @@ In {% data variables.copilot.copilot_cli_short %}, the `sandbox` key is an excep For server-managed deployments, the enterprise can apply different governance to groups of users based on their enterprise team membership. The enterprise defines all settings—team membership only determines which users receive a given set of values. -To make a key eligible for team-specific values, mark it as overridable in `{% data variables.copilot.managed_setting_file %}` using the `{ "overridable": }` syntax. An overridable key uses the team's value when set, or falls back to your enterprise default when the team leaves it unset. The `{ "overridable": }` syntax applies to the `permissions.model`, `permissions.disableBypassPermissionsMode`, `allowedMcpServers`, and `deniedMcpServers` keys. Keys not marked overridable remain an enterprise-level decision that teams can't modify. +To make a key eligible for team-specific values, mark it as overridable in `{% data variables.copilot.managed_setting_file %}` using the `{ "overridable": }` syntax. An overridable key uses the team's value when set, or falls back to your enterprise default when the team leaves it unset. The `{ "overridable": }` syntax applies to the `model`, `permissions.disableBypassPermissionsMode`, `allowedMcpServers`, and `deniedMcpServers` keys. Keys not marked overridable remain an enterprise-level decision that teams can't modify. `enabledPlugins` and `extraKnownMarketplaces` work additively. The enterprise `{% data variables.copilot.managed_setting_file %}` sets a baseline, and an enterprise team file can add more plugins and marketplaces on top of it. For the full setup steps, see [AUTOTITLE](/copilot/how-tos/administer-copilot/manage-for-enterprise/manage-agents/configure-enterprise-managed-settings#overriding-settings-for-specific-teams). @@ -58,9 +58,9 @@ The following example shows these keys in one managed settings file. ```json { + "model": "auto", "permissions": { - "disableBypassPermissionsMode": "disable", - "model": "auto" + "disableBypassPermissionsMode": "disable" }, "enabledPlugins": { "my-plugin@agent-skills": true @@ -148,6 +148,16 @@ Restricts plugin installation to only the marketplaces explicitly defined by the * `"hostPattern"` — requires `hostPattern` (regex matching marketplace hosts) * `"pathPattern"` — requires `pathPattern` (regex matching marketplace paths) +## model + +Sets auto model selection as the default for new conversations. See [AUTOTITLE](/copilot/concepts/models/auto-model-selection). + +* When you set `model` to `"auto"`, new sessions use Auto model unless the user specifies a different model on a per-conversation basis. +* This key is overridable by enterprise team mapping. In your `{% data variables.copilot.managed_setting_file %}`, use the `{ "overridable": "auto" }` syntax to specialize the key's configuration on a per-team basis. You can then set `"model": "unmanaged"` in a team settings file, providing a specialization that takes precedence over `{% data variables.copilot.managed_setting_file %}` for members of the subject team. + +> [!NOTE] +> `model` was originally documented as `permissions.model`. Clients still read the nested `permissions.model` value when the top-level `model` key is absent, but you should use the top-level `model` key in new configurations. + ## permissions ### disableBypassPermissionsMode @@ -161,13 +171,6 @@ When you set `disableBypassPermissionsMode` to `"disable"`, users cannot turn on * In the {% data variables.copilot.github_copilot_app %}, the "Allow all" setting for "Tool Permissions" is blocked in the sessions settings. * This key is overridable by enterprise team mapping. In your `{% data variables.copilot.managed_setting_file %}`, use the `{ "overridable": "disable" }` syntax to specialize the key's configuration on a per-team basis. You can then set `"disableBypassPermissionsMode": "unmanaged"` in a team settings file, providing a specialization that takes precedence over `{% data variables.copilot.managed_setting_file %}` for members of the subject team. -### model - -Sets auto model selection as the default for new conversations. See [AUTOTITLE](/copilot/concepts/models/auto-model-selection). - -* When you set `permissions.model` to `"auto"`, new sessions use Auto model unless the user specifies a different model on a per-conversation basis. -* This key is overridable by enterprise team mapping. In your `{% data variables.copilot.managed_setting_file %}`, use the `{ "overridable": "auto" }` syntax to specialize the key's configuration on a per-team basis. You can then set `"model": "unmanaged"` in a team settings file, providing a specialization that takes precedence over `{% data variables.copilot.managed_setting_file %}` for members of the subject team. - ## telemetry Configures OpenTelemetry export, routing {% data variables.product.prodname_copilot_short %} usage data to a collector of your choice. diff --git a/content/integrations/concepts/featured-github-integrations.md b/content/integrations/concepts/featured-github-integrations.md index 5fc755e80854..a79e4ea239e5 100644 --- a/content/integrations/concepts/featured-github-integrations.md +++ b/content/integrations/concepts/featured-github-integrations.md @@ -79,8 +79,12 @@ You can also open and close issues, comment on your issues and pull requests, ap For more information, see [AUTOTITLE](/integrations/how-tos/teams). -{% ifversion fpt or ghec %} +{% ifversion copilot %} + +You can also integrate the {% data variables.copilot.copilot_cloud_agent %} with your Microsoft Teams app, enabling you to use AI-powered coding assistance directly within your team's communication platform. + +Use {% data variables.copilot.copilot_cloud_agent %} in Microsoft Teams to collaborate with your team on agent-assisted work. You can @mention {% data variables.product.github %} in channels, threads, and direct messages to work alongside teammates and {% data variables.product.prodname_copilot_short %} on research, planning, and coding tasks. Teammates can add context, steer {% data variables.product.prodname_copilot_short %} sessions, monitor progress, and then review the resulting artifacts. -You can also integrate the {% data variables.copilot.copilot_cloud_agent %} with your Microsoft Teams app, enabling you to use AI-powered coding assistance directly within your team's communication platform. See [AUTOTITLE](/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-teams) for more information. +See [AUTOTITLE](/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-teams) for more information. {% endif %} diff --git a/content/integrations/how-tos/teams/customize-notifications.md b/content/integrations/how-tos/teams/customize-notifications.md index dfeac8b0b668..d01f0606f26c 100644 --- a/content/integrations/how-tos/teams/customize-notifications.md +++ b/content/integrations/how-tos/teams/customize-notifications.md @@ -15,7 +15,7 @@ You can customize your notifications by subscribing to activity that is relevant ### Notifications enabled by default -The following notifications are enabled by default, but you can disable any of them using the `@GitHub Notifications unsubscribe owner/repo [feature]` command. +The following notifications are enabled by default, but you can disable any of them using the `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} unsubscribe OWNER/REPO [FEATURE]` command. |Feature|Description| |-------|-----------| @@ -31,7 +31,7 @@ The following notifications are enabled by default, but you can disable any of t ### Notifications disabled by default -The following notifications are disabled by default, but you can enable any of them using the `@GitHub Notifications subscribe owner/repo [feature]` command. +The following notifications are disabled by default, but you can enable any of them using the `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe owner/repo [FEATURE]` command. |Feature|Description| |-------|-----------| @@ -39,12 +39,12 @@ The following notifications are disabled by default, but you can enable any of t |`workflows`|{% data variables.product.prodname_actions %} workflow runs and approval notifications.| |`branches`|Branch creation and deletion.| |`discussions`|Discussions created or answered.| -|`+label:"your label"`|Filter issues, pull requests, and comments based on their labels.| +|`+label:"YOUR-LABEL"`|Filter issues, pull requests, and comments based on their labels.| You can subscribe or unsubscribe from multiple settings at once. For example: -* To turn on activity for pull request reviews and comments, use `@GitHub Notifications subscribe owner/repo reviews comments`. -* To turn off activity for issues and pull requests, use `@GitHub Notifications unsubscribe owner/repo issues pulls`. +* To turn on activity for pull request reviews and comments, use `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO reviews comments`. +* To turn off activity for issues and pull requests, use `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} unsubscribe OWNER/REPO issues pulls`. ## Filtering notifications @@ -56,12 +56,12 @@ Branch filters allow you to filter commit notifications based on branch names. B |Example configuration|Description| |-------|-----------| -|`@GitHub Notifications subscribe owner/repo commits`|Receive commit notifications for the default branch.| -|`@GitHub Notifications subscribe owner/repo commits:main`|Only receive commit notifications for the `main` branch.| -|`@GitHub Notifications subscribe owner/repo commits:feature/*`|Receive commit notifications for all branches that start with `feature/`.| -|`@GitHub Notifications subscribe owner/repo commits:*`|Receive commit notifications for all branches.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO commits`|Receive commit notifications for the default branch.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO commits:main`|Only receive commit notifications for the `main` branch.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO commits:feature/*`|Receive commit notifications for all branches that start with `feature/`.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO commits:*`|Receive commit notifications for all branches.| -You can unsubscribe from the commits feature using `@GitHub Notifications unsubscribe owner/repo commits`. +You can unsubscribe from the commits feature using `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} unsubscribe OWNER/REPO commits`. >[!NOTE] You may have previously used the `commits:all` filter to receive commit notifications for all branches. This filter is {% data variables.release-phases.closing_down %}. To receive commit notifications for all branches, use the `commits:*` filter instead. If you have previously set up the `commits:all` filter, it will continue to work until you update your configuration to use the `commits:*` filter. @@ -85,37 +85,37 @@ Currently, it is only possible to have one required label filter per repository. To create a label filter, use the following command format: ```text copy -@GitHub Notifications subscribe [owner/repo] +label:"your label" +@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe [OWNER/REPO] +label:"YOUR-LABEL" ``` -This creates a required-label filter with the value `your label`. Incoming events that support filters are discarded unless they have that label. +This creates a required-label filter with the value `YOUR-LABEL`. Incoming events that support filters are discarded unless they have that label. #### Updating label filters -You can update an existing label filter by specifying a new label value: +You can update an existing label filter by specifying a NEW-LABEL value: ```text copy -@GitHub Notifications subscribe [owner/repo] +label:"new label" +@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe [OWNER/REPO] +label:"NEW-LABEL" ``` -This will replace the "your label" filter with the "new label" filter. +This will replace the "YOUR-LABEL" filter with the "NEW-LABEL" filter. #### Removing label filters You can remove an existing label filter by using the unsubscribe command with the `+label` option: ```text copy -@GitHub Notifications unsubscribe [owner/repo] +label:"new label" +@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} unsubscribe [OWNER/REPO] +label:"NEW-LABEL" ``` -This will remove the "new label" filter, and the channel will receive all notifications for the subscribed events without any label filtering. +This will remove the "NEW-LABEL" filter, and the channel will receive all notifications for the subscribed events without any label filtering. #### Viewing active label filters To view the currently active label filters for a channel, use the following command: ```text copy -@GitHub Notifications subscribe list features +@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe list features ``` #### Valid filters @@ -124,7 +124,7 @@ The {% data variables.product.github %} app in Teams supports the most common sp ## {% data variables.product.prodname_actions %} workflow notifications -You can subscribe to {% data variables.product.prodname_actions %} workflow run notifications from your channel or personal app using "workflows" feature, using the format `@GitHub Notifications subscribe owner/repo workflows`. +You can subscribe to {% data variables.product.prodname_actions %} workflow run notifications from your channel or personal app using "workflows" feature, using the format `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO workflows`. When you are subscribed to "workflows", the following functionality is available: @@ -149,18 +149,18 @@ You can filter workflow notifications by using the following options: You can configure workflow notification filters with the following format: ```text copy -@GitHub Notifications subscribe owner/repo workflows:{name:"your workflow name" event:"workflow event" branch:"branch name" actor:"username"} +@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO workflows:{name:"YOUR-WORKFLOW-NAME" event:"WORKFLOW-EVENT" branch:"BRANCH-NAME" actor:"USERNAME"} ``` You can also pass multiple values for each filter, separated by commas. For example: ```text copy -@GitHub Notifications subscribe owner/repo workflows:{name:"your workflow name","another workflow name" event:"workflow event","another workflow event" branch:"branch name","another branch name" actor:"username","another-username"} +@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO workflows:{name:"YOUR-WORKFLOW-NAME","ANOTHER-WORKFLOW-NAME" event:"WORKFLOW-EVENT","ANOTHER-WORKFLOW-EVENT" branch:"BRANCH-NAME","ANOTHER-BRANCH-NAME" actor:"USERNAME","ANOTHER-USERNAME"} ``` By default, when you configure workflow notifications without passing any filters, it is configured for workflows triggered via pull requests targeting your default branch. You can pass one or multiple entries. -You can unsubscribe from workflow notifications using the command: `@GitHub Notifications unsubscribe owner/repo workflows`. +You can unsubscribe from workflow notifications using the command: `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} unsubscribe OWNER/REPO workflows`. >[!NOTE] To receive {% data variables.product.prodname_actions %} notifications in Teams, the {% data variables.product.github %} app requires additional permissions. When you attempt to subscribe to workflows for the first time, you will be prompted to grant these permissions. @@ -171,8 +171,8 @@ You can also configure separate deployment notifications. These deployments can You can subscribe or unsubscribe to deployment notifications using the following commands: ```text copy -@GitHub Notifications subscribe owner/repo deployments -@GitHub Notifications unsubscribe owner/repo deployments +@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO deployments +@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} unsubscribe OWNER/REPO deployments ``` >[!NOTE] If you are using {% data variables.product.prodname_actions %} and want to track your deployments to environments, the `workflows` feature is recommended, as it provides a more complete picture and the ability to approve your deployments directly from Teams. diff --git a/content/integrations/how-tos/teams/integrate-github-with-teams.md b/content/integrations/how-tos/teams/integrate-github-with-teams.md index 9110289f3071..e75cbd938270 100644 --- a/content/integrations/how-tos/teams/integrate-github-with-teams.md +++ b/content/integrations/how-tos/teams/integrate-github-with-teams.md @@ -13,15 +13,21 @@ category: ## About the {% data variables.product.github %} integration for Teams -The {% data variables.product.github %} integration for Microsoft Teams gives you and your teams visibility into your {% data variables.product.github %} projects directly in Teams channels. You can triage issues, collaborate on pull requests, and keep track of changes without leaving Teams. +The {% data variables.product.github %} integration for Microsoft Teams gives you and your teams visibility into your {% data variables.product.github %} projects directly in Teams channels. You can {% ifversion copilot %}work with {% data variables.copilot.copilot_cloud_agent %} to research, plan and triage in conversations, create artifacts such as issues and pull requests, start and steer agent sessions,{% else %}triage issues, collaborate on pull requests, {% endif %} and keep track of changes without leaving Teams. With the {% data variables.product.github %} integration for Teams, you can: * Get **{% data variables.product.github %} notifications** in Teams channels. * Use **commands** to take actions on {% data variables.product.github %}. * See **previews** when sharing links to {% data variables.product.github %} resources. -{% ifversion fpt or ghec %} -* Initiate a {% data variables.copilot.copilot_cloud_agent %} session from Teams, using the context of a Teams thread. + +{% ifversion copilot %} + +* **Initiate and steer {% data variables.copilot.copilot_cloud_agent %} sessions** in a conversation. Teammates can collaborate with each other and the agent, add context, correct assumptions, continue an agent task, and review the resulting plan, issues, pull requests and other artifacts. + + > [!NOTE] + > * This feature is currently in {% data variables.release-phases.public_preview %} and subject to change. + {% endif %} {% data reusables.integrations.github-teams-permissions %} @@ -33,14 +39,18 @@ To use the {% data variables.product.github %} integration for Teams, you need: * A {% data variables.product.github %} account. * A Teams workspace where you have permission to install apps. -{% ifversion not ghes %} +{% ifversion copilot %} + +* You must have Microsoft Public Developer Preview enabled for your Microsoft Teams client, see [Public developer preview for Teams](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/dev-preview/developer-preview-intro) in the Microsoft Learn documentation. +{% data reusables.copilot.cloud-agent.cloud-sandboxes-prerequisite-teams %} ## Installing the {% data variables.product.github %} integration for Teams in a single workspace 1. Go to the [{% data variables.product.github %} integration for Teams](https://teams.microsoft.com/l/app/ca9e26b7-dce5-44a0-b2b7-a70a3d65ce25) listing in the Teams app store. 1. Click **Add**. 1. Follow the prompts to sign in to Teams and approve access. -1. In Teams, run `@GitHub Notifications signin` and follow the prompts to connect your {% data variables.product.github %} account. +1. In a Teams message or channel, @mention the app by typing `@{% data variables.product.github %}` and follow the prompts to connect your {% data variables.product.github %} account. +1. To see what else you can do, in the thread, @mention the app by typing `@{% data variables.product.github %} help`. {% ifversion ghec %} @@ -99,3 +109,6 @@ To integrate {% data variables.product.prodname_ghe_server %} with Microsoft Tea * [AUTOTITLE](/integrations/how-tos/teams/use-github-in-teams) - Learn how to use the {% data variables.product.github %} integration for Teams. * [AUTOTITLE](/integrations/how-tos/teams/customize-notifications) - Learn how to customize your {% data variables.product.github %} notifications in Teams. +{% ifversion copilot %} +* [AUTOTITLE](/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-teams) - Learn about {% data variables.copilot.copilot_cloud_agent %} with Teams. +{% endif %} diff --git a/content/integrations/how-tos/teams/schedule-reminders.md b/content/integrations/how-tos/teams/schedule-reminders.md index ffeecc6cb1a4..5904ea4de0ff 100644 --- a/content/integrations/how-tos/teams/schedule-reminders.md +++ b/content/integrations/how-tos/teams/schedule-reminders.md @@ -15,12 +15,12 @@ You can schedule reminders for pending pull request reviews in Microsoft Teams c ## Scheduling reminders in a channel -1. In a Teams channel, run `@GitHub Notifications schedule ORGANIZATION`. +1. In a Teams channel, run `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} schedule ORGANIZATION`. 1. Select **Create new reminder**. 1. Configure the days, times, timezone, and repository or team filters for the reminder. 1. Save the reminder. -To edit or remove reminders for the organization, run `@GitHub Notifications schedule ORGANIZATION` again. To list all reminders configured in the channel, run `@GitHub Notifications schedule list`. +To edit or remove reminders for the organization, run `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} schedule ORGANIZATION` again. To list all reminders configured in the channel, run `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} schedule list`. ## Scheduling reminders in the personal app diff --git a/content/integrations/how-tos/teams/use-github-in-teams.md b/content/integrations/how-tos/teams/use-github-in-teams.md index 43f42f79177d..1f578e6005d9 100644 --- a/content/integrations/how-tos/teams/use-github-in-teams.md +++ b/content/integrations/how-tos/teams/use-github-in-teams.md @@ -13,35 +13,59 @@ category: The {% data variables.product.github %} integration for Microsoft Teams lets you connect your {% data variables.product.github %} account to the {% data variables.product.github %} app in Teams. Once connected, you can subscribe to notifications, run commands, and collaborate on issues and pull requests directly within Teams. +{% ifversion copilot %} + +You can also use the {% data variables.product.github %} integration to initiate and steer {% data variables.copilot.copilot_cloud_agent %} sessions in a conversation, including asking {% data variables.product.prodname_copilot_short %} to perform deep research, planning and triage tasks within a thread. Teammates can collaborate with each other and the agent, add context, correct assumptions, continue an agent task, and review the resulting artifacts. + +> [!NOTE] +> * {% data variables.product.prodname_copilot %} in Teams is currently in {% data variables.release-phases.public_preview %} and subject to change. + +{% endif %} + ## Connecting your {% data variables.product.github %} account to the {% data variables.product.github %} app in Teams >[!NOTE] Before you can connect your accounts, an admin for your Teams workspace must have installed the {% data variables.product.github %} app. See [AUTOTITLE](/integrations/how-tos/teams/integrate-github-with-teams). 1. In Teams, open a direct message or personal app conversation with the {% data variables.product.github %} app. -1. Run `@GitHub Notifications signin` and follow the prompts in Teams and in your browser to authorize the connection. +1. Run {% ifversion copilot %}`@{% data variables.product.github %}`{% else %}`@{% data variables.product.github %} Notifications signin`{% endif %} and follow the prompts in Teams and in your browser to authorize the connection. Once your {% data variables.product.github %} account is connected, Teams will show you a list of available commands and features. -## Using commands in Teams +## Using commands for notifications in Teams -In channels, start commands with `@GitHub Notifications`. In the personal app, omit the prefix. For the full list of commands, see [AUTOTITLE](/integrations/reference/teams-command-reference). +In channels, start commands with {% ifversion copilot %}`@{% data variables.product.github %}`{% else %}`@{% data variables.product.github %} Notifications`{% endif %}. In the personal app, omit the prefix. For the full list of commands, see [AUTOTITLE](/integrations/reference/teams-command-reference). |Command|Description| |---|---| -|`@GitHub Notifications subscribe owner/repo`|Subscribes the channel to notifications for the specified repository.| -|`@GitHub Notifications unsubscribe owner/repo`|Unsubscribes the channel from notifications for the specified repository.| -|`@GitHub Notifications subscribe list`|Lists all repositories the channel is subscribed to.| -|`@GitHub Notifications subscribe list features`|Lists all repositories and notification features the channel is subscribed to.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO`|Subscribes the channel to notifications for the specified repository.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} unsubscribe OWNER/REPO`|Unsubscribes the channel from notifications for the specified repository.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe list`|Lists all repositories the channel is subscribed to.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe list features`|Lists all repositories and notification features the channel is subscribed to.| >[!NOTE] When you subscribe a channel to a repository, you may be prompted to install the {% data variables.product.github %} app and grant access to the repository or organization. ## Working with issues and pull requests +{% ifversion copilot %} + +You can create, comment on, and manage issues and pull requests directly from Teams with or without using {% data variables.product.prodname_copilot_short %}. + +To have {% data variables.product.prodname_copilot_short %} perform an action, @mention the app in any Teams chat by typing `@{% data variables.product.github %}` followed by your task, and see [Initiating {% data variables.copilot.copilot_cloud_agent %} sessions within Teams](#initiating--data-variablescopilotcopilot_cloud_agent--sessions-within-teams) below. + +> [!NOTE] +> {% data reusables.copilot.cloud-agent.unattributed-additional-approval-note %} + +For step-by-step instructions to work with issues and pull requests independent of {% data variables.product.prodname_copilot_short %}, see [AUTOTITLE](/integrations/tutorials/teams/create-issues) and [AUTOTITLE](/integrations/tutorials/teams/manage-issues). + +{% else %} + You can create, comment on, and manage issues and pull requests directly from Teams. For step-by-step instructions, see: * [AUTOTITLE](/integrations/tutorials/teams/create-issues) * [AUTOTITLE](/integrations/tutorials/teams/manage-issues) +{% endif %} + ## Mentions in Teams When you subscribe to a repository in Teams, you will see yourself mentioned in notifications for repository events in which you have been referenced. Mentions require you to be logged in to your {% data variables.product.github %} account through the {% data variables.product.github %} app in Teams. @@ -79,17 +103,30 @@ Previews of links will not be shown if any of the following apply: ## Personal app experience -The {% data variables.product.github %} personal app in Teams lets you manage subscriptions and receive notifications in a private chat. In the personal app, commands do not require the `@GitHub Notifications` prefix and notifications are not threaded. +The {% data variables.product.github %} personal app in Teams lets you manage subscriptions and receive notifications in a private chat. In the personal app, commands do not require the {% ifversion copilot %}`@{% data variables.product.github %}`{% else %}`@{% data variables.product.github %} Notifications`{% endif %} prefix and notifications are not threaded. ## Scheduling reminders for pull request reviews You can schedule reminders for pending pull request reviews in channels or in the personal app. For instructions, see [AUTOTITLE](/integrations/how-tos/teams/schedule-reminders). -{% ifversion fpt or ghec %} +{% ifversion copilot %} ## Initiating {% data variables.copilot.copilot_cloud_agent %} sessions within Teams -The {% data variables.product.github %} app integrates {% data variables.copilot.copilot_cloud_agent %} into Teams. You can summon {% data variables.copilot.copilot_cloud_agent %} in threads where discussions are taking place and ask it to make changes based on the context of those discussions. For more information, see [AUTOTITLE](/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-teams). +The {% data variables.product.github %} app integrates {% data variables.copilot.copilot_cloud_agent %} into Teams. You can summon {% data variables.copilot.copilot_cloud_agent %} in threads where discussions are taking place and ask it to make changes based on the context of those discussions. + +Use {% data variables.product.prodname_copilot_short %} in direct messages, threads, and channels. Besides creating issues, pull requests and other artifacts, working with {% data variables.product.prodname_copilot_short %} in Teams allows you to: + +* Move directly from discussion to investigation and implementation. +* Ask questions and investigate failures. +* Plan work before implementation. +* Collaboratively steer an agent with teammates. +* Delegate work from any device. +* Let tasks run asynchronously. +* Review the resulting work in the open. +* Resume work on the agent-generated artifacts outside of Teams, in {% data variables.product.github %}, the terminal, or your preferred code editor. + +For more information, see [AUTOTITLE](/copilot/how-tos/use-copilot-agents/cloud-agent/integrate-cloud-agent-with-teams). {% endif %} diff --git a/content/integrations/reference/teams-command-reference.md b/content/integrations/reference/teams-command-reference.md index 8294babc9b5c..ada7ee382bcd 100644 --- a/content/integrations/reference/teams-command-reference.md +++ b/content/integrations/reference/teams-command-reference.md @@ -12,20 +12,20 @@ category: - Learn about integrations --- -Use these commands in a Microsoft Teams channel by prefixing them with `@GitHub Notifications`. In the {% data variables.product.github %} personal app, omit the prefix. +Use these commands in a Microsoft Teams channel by prefixing them with `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %}`. In the {% data variables.product.github %} personal app, omit the prefix. |Command|Description| |---|---| -|`@GitHub Notifications help`|Display help documentation.| -|`@GitHub Notifications signin`|Connect your {% data variables.product.github %} account.| -|`@GitHub Notifications subscribe owner/repo`|Subscribe a channel to a repository.| -|`@GitHub Notifications subscribe owner/repo [feature]`|Subscribe a channel to specific notification features.| -|`@GitHub Notifications subscribe list`|List subscriptions in the channel.| -|`@GitHub Notifications subscribe list features`|List subscriptions and subscribed features in the channel.| -|`@GitHub Notifications unsubscribe owner/repo`|Unsubscribe a channel from a repository.| -|`@GitHub Notifications unsubscribe owner/repo [feature]`|Unsubscribe a channel from specific features.| -|`@GitHub Notifications schedule ORGANIZATION`|List and manage reminders for the organization in this channel.| -|`@GitHub Notifications schedule list`|List all reminders configured in this channel.| -|`@GitHub Notifications signout`|Disconnect your {% data variables.product.github %} account and remove subscriptions.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} help`|Display help documentation.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} signin`|Connect your {% data variables.product.github %} account.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO`|Subscribe a channel to a repository.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe OWNER/REPO [feature]`|Subscribe a channel to specific notification features.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe list`|List subscriptions in the channel.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} subscribe list features`|List subscriptions and subscribed features in the channel.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} unsubscribe OWNER/REPO`|Unsubscribe a channel from a repository.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} unsubscribe OWNER/REPO [feature]`|Unsubscribe a channel from specific features.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} schedule ORGANIZATION`|List and manage reminders for the organization in this channel.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} schedule list`|List all reminders configured in this channel.| +|`@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %} signout`|Disconnect your {% data variables.product.github %} account and remove subscriptions.| For the list of supported notification features, see [AUTOTITLE](/integrations/how-tos/teams/customize-notifications). diff --git a/content/integrations/reference/teams-permissions.md b/content/integrations/reference/teams-permissions.md index cd4676801d6b..5366ff211cf2 100644 --- a/content/integrations/reference/teams-permissions.md +++ b/content/integrations/reference/teams-permissions.md @@ -21,7 +21,7 @@ When you install the {% data variables.product.github %} app in your Teams works |----------------|--------------| |Access private conversations between you and the App | To message you with instructions. | |Add link previews to {% data variables.product.prodname_dotcom %} to messages| To render rich links to `github.com`.| -|Add {% data variables.product.github %} commands| To add the `@GitHub Notifications` command to your Teams channels. | +|Add {% data variables.product.github %} commands| To add the `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %}` command to your Teams channels. | |View the workspace or organization's name, email domain, and icon| To store subscriptions you set up.| |Post messages as the app| To notify you of activity that happens on {% data variables.product.github %}, in Teams.| @@ -35,7 +35,7 @@ When you connect your {% data variables.product.github %} account to the {% data |Read access to code| To render code snippets in Teams.| |Write access to actions, issues, and pull requests | To take action from Teams with cards and commands.| -{% ifversion fpt or ghec %} +{% ifversion copilot %} ## Additional permissions for {% data variables.copilot.copilot_cloud_agent %} diff --git a/content/integrations/tutorials/teams/create-issues.md b/content/integrations/tutorials/teams/create-issues.md index c91a74ad828b..d8f3cf8d0624 100644 --- a/content/integrations/tutorials/teams/create-issues.md +++ b/content/integrations/tutorials/teams/create-issues.md @@ -25,4 +25,4 @@ With the {% data variables.product.github %} integration in Microsoft Teams, you 1. Click **Create** to create the issue. You will receive a confirmation card in the channel from where you initiated the issue creation. -Alternatively, you can create an issue by invoking `@GitHub Notifications` from the chat in your channel or personal app. +Alternatively, you can create an issue by invoking `@{% data variables.product.github %}{% ifversion ghes %} Notifications{% endif %}` from the chat in your channel or personal app. diff --git a/content/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets.md b/content/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets.md index 4cb29eceb63d..669bec1fb20b 100644 --- a/content/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets.md +++ b/content/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets.md @@ -132,6 +132,23 @@ Optionally, you can require all comments on the pull request to be resolved befo Optionally, you can require a merge type of merge, squash, or rebase. This means the targeted branches may only be merged based on the allowed type. Additionally if the repository has disabled a merge method and the ruleset required a different method, the merge will be blocked. See [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/about-merge-methods-on-github). {% endif %} +{% ifversion repo-rules-copilot-extra-approval %} + +#### Additional approval for unattributed {% data variables.product.prodname_copilot_short %} pull requests + +> [!NOTE] +> This feature is in {% data variables.release-phases.public_preview %} and subject to change. + +**Require an additional approval for unattributed {% data variables.product.prodname_copilot_short %} pull requests** is enabled by default, for both new and existing rulesets. When {% data variables.product.prodname_copilot_short %} opens a pull request that isn't attributed to a person, the ruleset requires one more approval than the number you configured. For example, a ruleset that requires one approval requires two approvals from people with write access. + +Requiring one approval usually means two people are involved in a change: the person who wrote it and the person who approved it. That assumption doesn't hold when {% data variables.product.prodname_copilot_short %} opens a pull request under its own app identity instead of on behalf of a person, for example when you prompt it from a shared context such as a group thread or channel. See [AUTOTITLE](/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-slack) and [AUTOTITLE](/copilot/how-tos/copilot-integrations/integrate-cloud-agent-with-teams). + +This setting has no effect if the ruleset requires zero approvals, so repositories that use pull requests as a record of changes rather than to gate on approvals are unaffected. + +If you clear this setting, these pull requests require only the number of approvals you configured. If you also require an approval from someone other than the last person to push, at least one approval must cover the last push and come from someone other than {% data variables.product.prodname_copilot_short %}. + +{% endif %} + {% ifversion repo-rules-required-reviewer %} #### Required reviewers @@ -225,8 +242,8 @@ If your repository has {% data variables.product.prodname_code_quality %} enable This rule blocks a pull request from being merged when either of two code coverage thresholds is not met: -* **Minimum coverage percentage**: the aggregated code coverage for the pull request branch is below the configured percentage. -* **Maximum coverage drop**: code coverage drops by more than the configured number of percentage points relative to the default branch. +* **Minimum line coverage percentage**: the aggregated line coverage for the pull request branch is below the configured percentage. +* **Maximum line coverage drop**: line coverage drops by more than the configured number of percentage points relative to the default branch. For how to configure the thresholds, the prerequisite for uploading coverage data, and how to roll the rule out safely, see [AUTOTITLE](/code-security/how-tos/maintain-quality-code/restrict-code-coverage). diff --git a/data/features/repo-rules-copilot-extra-approval.yml b/data/features/repo-rules-copilot-extra-approval.yml new file mode 100644 index 000000000000..3738d6cd06f5 --- /dev/null +++ b/data/features/repo-rules-copilot-extra-approval.yml @@ -0,0 +1,6 @@ +# Reference: https://github.com/github/repos-security/discussions/1485 +# Public preview for requiring an additional approval on unattributed Copilot pull requests + +versions: + fpt: '*' + ghec: '*' diff --git a/data/reusables/copilot/cloud-agent/cloud-sandboxes-prerequisite-teams.md b/data/reusables/copilot/cloud-agent/cloud-sandboxes-prerequisite-teams.md new file mode 100644 index 000000000000..8989a1cc6f96 --- /dev/null +++ b/data/reusables/copilot/cloud-agent/cloud-sandboxes-prerequisite-teams.md @@ -0,0 +1,4 @@ +* To use {% data variables.copilot.copilot_cloud_agent %}, you must have cloud sandboxes enabled for your {% data variables.product.prodname_copilot_short %} plan. See [Cloud sandboxing for {% data variables.product.prodname_copilot %}](/copilot/concepts/about-cloud-and-local-sandboxes#cloud-sandboxing). + + > [!NOTE] + > Cloud sandbox policies share the same configuration as {% data variables.copilot.copilot_cloud_agent %} policies. Members of an organization or enterprise, including an {% data variables.enterprise.prodname_emu_enterprise %} may need their owner to enable cloud sandboxes and {% data variables.copilot.copilot_cloud_agent %} before they can use {% data variables.product.prodname_copilot_short %} in Teams. See [AUTOTITLE](/copilot/how-tos/cloud-and-local-sandboxes/enabling-or-disabling-cloud-sandboxes-for-your-organization). diff --git a/data/reusables/copilot/cloud-agent/unattributed-additional-approval-note.md b/data/reusables/copilot/cloud-agent/unattributed-additional-approval-note.md new file mode 100644 index 000000000000..04155dc8895d --- /dev/null +++ b/data/reusables/copilot/cloud-agent/unattributed-additional-approval-note.md @@ -0,0 +1 @@ +Pull requests created in a shared context by {% data variables.product.prodname_copilot_short %} use the app's identity. If you use repository rulesets, because these pull requests aren't attributed to a person, one more approval is required before merging, as long as the repository already requires at least one approval. This is enabled by default. See [AUTOTITLE](/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#additional-approval-for-unattributed-copilot-pull-requests). diff --git a/data/reusables/copilot/plans/ai-credits-by-plan.md b/data/reusables/copilot/plans/ai-credits-by-plan.md index 533f4e8fdb1a..12d4f645f99d 100644 --- a/data/reusables/copilot/plans/ai-credits-by-plan.md +++ b/data/reusables/copilot/plans/ai-credits-by-plan.md @@ -3,5 +3,3 @@ | {% data variables.copilot.copilot_pro_short %} | {% data variables.copilot.cfi_price_per_month %} | {% data variables.copilot.ai_credits_per_user_pro %} | {% data variables.copilot.ai_credits_per_user_pro_flex %} | {% data variables.copilot.ai_credits_per_user_pro_total %} | | {% data variables.copilot.copilot_pro_plus_short %} | {% data variables.copilot.cpp_price_per_month %} | {% data variables.copilot.ai_credits_per_user_pro_plus %} | {% data variables.copilot.ai_credits_per_user_pro_plus_flex %} | {% data variables.copilot.ai_credits_per_user_pro_plus_total %} | | {% data variables.copilot.copilot_max_short %} | {% data variables.copilot.cm_price_per_month %} | {% data variables.copilot.ai_credits_per_user_max %} | {% data variables.copilot.ai_credits_per_user_max_flex %} | {% data variables.copilot.ai_credits_per_user_max_total %} | - -{% data variables.copilot.copilot_free_short %} and {% data variables.copilot.copilot_student_short %} both have an allowance of {% data variables.product.prodname_ai_credits_short %} and access to models through {% data variables.copilot.copilot_auto_model_selection_short %} only. {% data variables.copilot.copilot_free_short %} includes 2000 code completions per month and {% data variables.copilot.copilot_student_short %} includes unlimited code completions. diff --git a/data/variables/code-quality.yml b/data/variables/code-quality.yml index 1f1c4c97dba7..438ee41c5236 100644 --- a/data/variables/code-quality.yml +++ b/data/variables/code-quality.yml @@ -2,10 +2,10 @@ # Variables for CodeQL analysis -workflow_name_actions: 'Code Quality' -workflow_name_billing: 'dynamic/github-code-scanning/codeql' +workflow_name_billing: 'dynamic/github-code-quality/codeql' check_status_name: 'CodeQL - Code Quality' pr_commenter: 'github-code-quality[bot]' +workflow_actor: 'github-code-quality' # Variables for LLM analysis diff --git a/src/languages/lib/correct-translation-content.ts b/src/languages/lib/correct-translation-content.ts index 93cb80520dc4..c2f04154355d 100644 --- a/src/languages/lib/correct-translation-content.ts +++ b/src/languages/lib/correct-translation-content.ts @@ -463,6 +463,16 @@ export function correctTranslatedContentStrings( // `{% indented_data_reference 再利用可能.X.Y spaces=N %}` — translated path content = content.replace(/(\{%-?\s*indented_data_reference\s+)再利用可能\./g, '$1reusables.') + // `{% ifversion コマンド パレット %}` — translated flag name "command palette" = command-palette + content = content.replaceAll( + '{% ifversion コマンド パレット %}', + '{% ifversion command-palette %}', + ) + content = content.replaceAll( + '{%- ifversion コマンド パレット %}', + '{%- ifversion command-palette %}', + ) + // [SCRAPE-6548] Per-file fixes for ja pages whose intro/title/shortTitle // Liquid was structurally scrambled (orphan endif, swapped tag order, // unclosed ifversion). Each replacement is scoped by the unique broken @@ -649,6 +659,10 @@ export function correctTranslatedContentStrings( content = content.replace(/\{%-? (?:ifversion|elsif|if) [^%]*?ou [^%]*?%\}/g, (match) => { return match.replace(/ ou /g, ' or ') }) + // Portuguese "não" for "not" in ifversion/elsif/if tags (e.g. `{% ifversion não ghes %}`) + content = content.replace(/\{%-? (?:ifversion|elsif|if) [^%]*?\bnão\b[^%]*?%\}/g, (match) => { + return match.replace(/\bnão\b/g, 'not') + }) // Fully translated reusable path in audit log article: // `{% dados agrupados por categoria.complemento.audit_log.reference-grouped-by-category %}` content = content.replaceAll( @@ -838,6 +852,16 @@ export function correctTranslatedContentStrings( content = content.replaceAll('{%- 行标头 %}', '{%- rowheaders %}') content = content.replaceAll('{% 行标题 %}', '{% rowheaders %}') content = content.replaceAll('{%- 行标题 %}', '{%- rowheaders %}') + + // `{% ifversion 命令面板 %}` — translated flag name "command panel" = command-palette + content = content.replaceAll('{% ifversion 命令面板 %}', '{% ifversion command-palette %}') + content = content.replaceAll('{%- ifversion 命令面板 %}', '{%- ifversion command-palette %}') + // `{% ifversion 子问题 %}` — translated flag name "sub-issues" (子问题) + content = content.replaceAll('{% ifversion 子问题 %}', '{% ifversion sub-issues %}') + content = content.replaceAll('{%- ifversion 子问题 %}', '{%- ifversion sub-issues %}') + // `{% ifversion 问题类型 %}` — translated flag name "issue types" = issue-types + content = content.replaceAll('{% ifversion 问题类型 %}', '{% ifversion issue-types %}') + content = content.replaceAll('{%- ifversion 问题类型 %}', '{%- ifversion issue-types %}') // `{% 结束行标题 %}` / `{% 结束行标头 %}` / `{% 结束行头 %}` — endrowheaders content = content.replaceAll('{% 结束行标题 %}', '{% endrowheaders %}') content = content.replaceAll('{%- 结束行标题 %}', '{%- endrowheaders %}') @@ -1636,6 +1660,31 @@ export function correctTranslatedContentStrings( content = content.replaceAll('{% 옥티콘 ', '{% octicon ') content = content.replaceAll('{%- 옥티콘 ', '{%- octicon ') + // `{% ifversion 명령 팔레트 %}` — translated flag name "command palette" = command-palette + content = content.replaceAll('{% ifversion 명령 팔레트 %}', '{% ifversion command-palette %}') + content = content.replaceAll('{%- ifversion 명령 팔레트 %}', '{%- ifversion command-palette %}') + // `{% ifversion 하위 문제 %}` — translated flag name "sub-issues" (하위 문제) + content = content.replaceAll('{% ifversion 하위 문제 %}', '{% ifversion sub-issues %}') + content = content.replaceAll('{%- ifversion 하위 문제 %}', '{%- ifversion sub-issues %}') + // `{% ifversion 리포지토리-규칙 관리 %}` — translated flag name "repository-rules management" + content = content.replaceAll( + '{% ifversion 리포지토리-규칙 관리 %}', + '{% ifversion repo-rules-management %}', + ) + content = content.replaceAll( + '{%- ifversion 리포지토리-규칙 관리 %}', + '{%- ifversion repo-rules-management %}', + ) + // `{% ifversion 업데이트 알림 설정-22 %}` — translated flag name "update notification settings-22" + content = content.replaceAll( + '{% ifversion 업데이트 알림 설정-22 %}', + '{% ifversion update-notification-settings-22 %}', + ) + content = content.replaceAll( + '{%- ifversion 업데이트 알림 설정-22 %}', + '{%- ifversion update-notification-settings-22 %}', + ) + // `{% data Variables.` — capital V in "Variables" (Korean translator capitalised the word) content = content.replaceAll('{% data Variables.', '{% data variables.') content = content.replaceAll('{%- data Variables.', '{%- data variables.') @@ -1936,6 +1985,16 @@ export function correctTranslatedContentStrings( // Translated tag name `{% eingerucktes_datenverweis ... %}` → `{% indented_data_reference ... %}` content = content.replaceAll('{% eingerucktes_datenverweis ', '{% indented_data_reference ') content = content.replaceAll('{%- eingerucktes_datenverweis ', '{%- indented_data_reference ') + // `{% ifversion unveränderliche Versionen %}` — translated flag name + // "immutable releases" = immutable-releases + content = content.replaceAll( + '{% ifversion unveränderliche Versionen %}', + '{% ifversion immutable-releases %}', + ) + content = content.replaceAll( + '{%- ifversion unveränderliche Versionen %}', + '{%- ifversion immutable-releases %}', + ) // [SCRAPE-6548] Per-file fix: // organizations/.../permissions-of-custom-organization-roles.md (intro): diff --git a/src/languages/tests/correct-translation-content.ts b/src/languages/tests/correct-translation-content.ts index df683972fbb5..1fc5cd3cf00a 100644 --- a/src/languages/tests/correct-translation-content.ts +++ b/src/languages/tests/correct-translation-content.ts @@ -222,6 +222,14 @@ describe('correctTranslatedContentStrings', () => { expect(fix('{%- ifversion fpt または ghec %}', 'ja')).toBe('{%- ifversion fpt or ghec %}') }) + test('fixes translated command-palette flag name', () => { + expect(fix('{% ifversion コマンド パレット %}', 'ja')).toBe('{% ifversion command-palette %}') + expect(fix('{%- ifversion コマンド パレット %}', 'ja')).toBe( + '{%- ifversion command-palette %}', + ) + expect(fix('{% ifversion command-palette %}', 'ja')).toBe('{% ifversion command-palette %}') + }) + test('fixes trailing quote on YAML value', () => { expect(fix(' asked_too_many_times: some value" ', 'ja')).toBe( ' asked_too_many_times: some value', @@ -404,6 +412,12 @@ describe('correctTranslatedContentStrings', () => { expect(fix('{%– endif %}', 'pt')).toBe('{%- endif %}') }) + test('fixes Portuguese não (not) in ifversion tags', () => { + expect(fix('{% ifversion não ghes %}', 'pt')).toBe('{% ifversion not ghes %}') + expect(fix('{%- ifversion não ghes %}', 'pt')).toBe('{%- ifversion not ghes %}') + expect(fix('{% ifversion not ghes %}', 'pt')).toBe('{% ifversion not ghes %}') + }) + test('fixes datavariables / dadosvariables (no space)', () => { // `{% datavariables` — no space between "data" and "variables" (post-translation) expect(fix('{% datavariables.product.github %}', 'pt')).toBe( @@ -562,6 +576,18 @@ describe('correctTranslatedContentStrings', () => { expect(fix('{ 如果 ghec %}', 'zh')).toBe('{% if ghec %}') }) + test('fixes translated flag names in ifversion tags', () => { + expect(fix('{% ifversion 命令面板 %}', 'zh')).toBe('{% ifversion command-palette %}') + expect(fix('{%- ifversion 命令面板 %}', 'zh')).toBe('{%- ifversion command-palette %}') + expect(fix('{% ifversion 子问题 %}', 'zh')).toBe('{% ifversion sub-issues %}') + expect(fix('{%- ifversion 子问题 %}', 'zh')).toBe('{%- ifversion sub-issues %}') + expect(fix('{% ifversion 问题类型 %}', 'zh')).toBe('{% ifversion issue-types %}') + expect(fix('{%- ifversion 问题类型 %}', 'zh')).toBe('{%- ifversion issue-types %}') + expect(fix('{% ifversion command-palette %}', 'zh')).toBe('{% ifversion command-palette %}') + expect(fix('{% ifversion sub-issues %}', 'zh')).toBe('{% ifversion sub-issues %}') + expect(fix('{% ifversion issue-types %}', 'zh')).toBe('{% ifversion issue-types %}') + }) + test('fixes stray Chinese then merged with HTML', () => { expect(fix(',则为 {%
', 'zh')).toBe('
') }) @@ -1172,6 +1198,33 @@ describe('correctTranslatedContentStrings', () => { ) }) + test('fixes translated flag names in ifversion tags', () => { + expect(fix('{% ifversion 명령 팔레트 %}', 'ko')).toBe('{% ifversion command-palette %}') + expect(fix('{%- ifversion 명령 팔레트 %}', 'ko')).toBe('{%- ifversion command-palette %}') + expect(fix('{% ifversion 하위 문제 %}', 'ko')).toBe('{% ifversion sub-issues %}') + expect(fix('{%- ifversion 하위 문제 %}', 'ko')).toBe('{%- ifversion sub-issues %}') + expect(fix('{% ifversion 리포지토리-규칙 관리 %}', 'ko')).toBe( + '{% ifversion repo-rules-management %}', + ) + expect(fix('{%- ifversion 리포지토리-규칙 관리 %}', 'ko')).toBe( + '{%- ifversion repo-rules-management %}', + ) + expect(fix('{% ifversion 업데이트 알림 설정-22 %}', 'ko')).toBe( + '{% ifversion update-notification-settings-22 %}', + ) + expect(fix('{%- ifversion 업데이트 알림 설정-22 %}', 'ko')).toBe( + '{%- ifversion update-notification-settings-22 %}', + ) + expect(fix('{% ifversion command-palette %}', 'ko')).toBe('{% ifversion command-palette %}') + expect(fix('{% ifversion sub-issues %}', 'ko')).toBe('{% ifversion sub-issues %}') + expect(fix('{% ifversion repo-rules-management %}', 'ko')).toBe( + '{% ifversion repo-rules-management %}', + ) + expect(fix('{% ifversion update-notification-settings-22 %}', 'ko')).toBe( + '{% ifversion update-notification-settings-22 %}', + ) + }) + test('fixes dada → data (via generic)', () => { expect(fix('{% dada variables.product.github %}', 'ko')).toBe( '{% data variables.product.github %}', @@ -1291,6 +1344,18 @@ describe('correctTranslatedContentStrings', () => { expect(fix('{% ifversion fpt oder ghec %}', 'de')).toBe('{% ifversion fpt or ghec %}') }) + test('fixes translated immutable-releases flag name', () => { + expect(fix('{% ifversion unveränderliche Versionen %}', 'de')).toBe( + '{% ifversion immutable-releases %}', + ) + expect(fix('{%- ifversion unveränderliche Versionen %}', 'de')).toBe( + '{%- ifversion immutable-releases %}', + ) + expect(fix('{% ifversion immutable-releases %}', 'de')).toBe( + '{% ifversion immutable-releases %}', + ) + }) + test('fixes translated block tags', () => { expect(fix('{% Hinweis %}', 'de')).toBe('{% note %}') expect(fix('{%- Hinweis %}', 'de')).toBe('{%- note %}') diff --git a/src/links/lib/link-report.ts b/src/links/lib/link-report.ts index adedb620c92d..1368745ff31d 100644 --- a/src/links/lib/link-report.ts +++ b/src/links/lib/link-report.ts @@ -116,9 +116,14 @@ ${summary} const suggestion = group.suggestion ? `💡 ${group.suggestion}\n\n` : '' - const tableRows = group.occurrences + const listedOccurrences = group.occurrences.slice(0, MAX_FILES_PER_GROUP) + const hiddenOccurrences = group.occurrences.length - listedOccurrences.length + const tableRows = listedOccurrences .map((occ) => `| \`${occ.file}\` | ${occ.lines.join(', ')} |`) .join('\n') + const moreFiles = hiddenOccurrences + ? `\n\nAnd ${hiddenOccurrences} more file${hiddenOccurrences === 1 ? '' : 's'}, listed in the report attached to the workflow run.` + : '' return `### ${icon} \`${group.target}\` @@ -126,7 +131,7 @@ ${statusInfo}${suggestion}**Found in ${count} file${plural}:** | File | Line(s) | |------|---------| -${tableRows}` +${tableRows}${moreFiles}` }, // Self-referential links section @@ -550,6 +555,48 @@ export type FixStrategy = 'codemod' | 'versionless' | 'anchor' | 'decide' */ const MAX_LISTED_CODEMOD_PATHS = 8 +/** + * How many rows of the codemod table to print. The codemod does this work, so the full list + * is reference material, not a task list. Printing all of it costs more than half the issue + * body budget, and the complete list is in the workflow artifact either way. + */ +const MAX_CODEMOD_ROWS = 40 + +/** + * How many stale anchors to print. This bucket is real work, but 70-plus entries is more + * than anyone picks up in a week, and each entry costs several times a table row because it + * lists every file the link appears in. The rest are in the workflow artifact. + */ +const MAX_ANCHOR_GROUPS = 25 + +/** + * How many version-only redirects to print. This bucket needs no action at all, so the list + * exists to show what was ruled out, not to be worked through. + */ +const MAX_VERSIONLESS_ROWS = 25 + +/** + * How many files to list under a single broken link. Nothing bounds how many pages reuse + * one link, so without this a single popular link could fill the issue body on its own. The + * busiest link in the current eight-version data appears in 31 files, so this does not + * trigger today. + */ +const MAX_FILES_PER_GROUP = 20 + +/** + * Split a list at a cap and describe what is missing, so no section can grow without bound. + * GitHub rejects issue bodies over 65,536 characters and the workflow truncates at 60,000 + * with a blind slice, which can cut a table in half. + */ +function capGroups( + groups: GroupedBrokenLinks[], + max: number, +): { listed: GroupedBrokenLinks[]; hidden: number } { + // Most-used links first, so the truncated tail is the least interesting part. + const byOccurrences = [...groups].sort((a, b) => b.occurrences.length - a.occurrences.length) + return { listed: byOccurrences.slice(0, max), hidden: Math.max(0, groups.length - max) } +} + export function classifyFixStrategy(group: GroupedBrokenLinks): FixStrategy { const redirectTargets = group.occurrences .map((occ) => occ.redirectTarget) @@ -624,7 +671,10 @@ function renderCodemodSection(groups: GroupedBrokenLinks[], versionsChecked?: st describeVersions(groupVersions(group), versionsChecked) const showVersions = groups.some((group) => versionFor(group)) - const rows = groups + // Most-used links first, so the truncated tail is the least interesting part. + const { listed, hidden } = capGroups(groups, MAX_CODEMOD_ROWS) + + const rows = listed .map((group) => { const target = group.occurrences.find((occ) => occ.redirectTarget)?.redirectTarget ?? '' const cells = [`\`${group.target}\``, `\`${target}\``, `${group.occurrences.length}`] @@ -633,6 +683,10 @@ function renderCodemodSection(groups: GroupedBrokenLinks[], versionsChecked?: st }) .join('\n') + const truncationNote = hidden + ? `\n\nAnd ${hidden} more. The codemod fixes every one of them, so this list is reference only. The full report is attached to the workflow run.` + : '' + const flags = '--keep-stale-fragments --dont-set-autotitle' const paths = codemodPaths(groups) const tooManyToList = paths.length > MAX_LISTED_CODEMOD_PATHS @@ -664,7 +718,7 @@ Review the diff, then open a pull request. | From | To | Occurrences |${showVersions ? ' Versions |' : ''} |------|-----|-------------|${showVersions ? '----------|' : ''} -${rows} +${rows}${truncationNote}
` } @@ -678,13 +732,18 @@ ${rows} * alone and so should writers. */ function renderVersionlessSection(groups: GroupedBrokenLinks[]): string { - const rows = groups + const { listed, hidden } = capGroups(groups, MAX_VERSIONLESS_ROWS) + const rows = listed .map((group) => { const target = group.occurrences.find((occ) => occ.redirectTarget)?.redirectTarget ?? '' return `| \`${group.target}\` | \`${target}\` |` }) .join('\n') + const truncationNote = hidden + ? `\n\nAnd ${hidden} more in the same state. The list is cut short because this bucket is here to show what was ruled out, not to be worked through. The full report is attached to the workflow run.` + : '' + const plural = groups.length === 1 ? '' : 's' const occurrences = occurrenceCount(groups) @@ -701,7 +760,7 @@ should differ per version. | Link | Resolves to | |------|-------------| -${rows} +${rows}${truncationNote}
` } @@ -712,19 +771,24 @@ function renderManualSection( groups: GroupedBrokenLinks[], isExternal: boolean, versionsChecked?: string[], + maxGroups?: number, ): string { - const sections = groups + const { listed, hidden } = capGroups(groups, maxGroups ?? groups.length) + const sections = listed .map((group) => { const versions = describeVersions(groupVersions(group), versionsChecked) const note = versions ? `\n\n**Only in:** ${versions}` : '' return TEMPLATES.group(group, isExternal) + note }) .join('\n\n') + const truncationNote = hidden + ? `\n\nAnd ${hidden} more, listed in the report attached to the workflow run. Only the busiest are shown here, to keep the issue readable. Fixing the ones above moves some of the rest into view on the next run, but a link that is not shown is not fixed: use the artifact to work through the tail.` + : '' return `## ${heading} (${groups.length} link${groups.length === 1 ? '' : 's'}, ${occurrenceCount(groups)} occurrence${occurrenceCount(groups) === 1 ? '' : 's'}) ${blurb} -${sections}` +${sections}${truncationNote}` } /** @@ -771,6 +835,7 @@ Work top to bottom. Bucket 1 is usually most of the report and costs one command anchors, isExternal, versionsChecked, + MAX_ANCHOR_GROUPS, ), ) } diff --git a/src/links/tests/link-report.ts b/src/links/tests/link-report.ts index 23a5665e3fd4..5752df10ce8b 100644 --- a/src/links/tests/link-report.ts +++ b/src/links/tests/link-report.ts @@ -720,6 +720,45 @@ describe('describeVersions', () => { }) }) +describe('codemod table truncation', () => { + const links: BrokenLink[] = [ + // `/old-0` appears in five files, so it should survive truncation. + ...Array.from({ length: 5 }, (_, f) => ({ + href: '/old-0', + file: `actions/busy-${f}.md`, + lines: [1], + isRedirect: true, + redirectTarget: '/new-0', + })), + ...Array.from({ length: 59 }, (_, i) => ({ + href: `/old-${i + 1}`, + file: `actions/page-${i + 1}.md`, + lines: [1], + isRedirect: true, + redirectTarget: `/new-${i + 1}`, + })), + ] + + const markdown = reportToMarkdown(generateInternalLinkReport(links)) + + test('caps the reference table so the report fits in an issue body', () => { + expect(markdown).toContain('And 20 more.') + }) + + test('keeps the most-used links, dropping only the tail', () => { + expect(markdown).toContain('| `/old-0` | `/new-0` | 5 |') + }) + + test('still counts every link in the heading, not just the listed ones', () => { + expect(markdown).toContain('## 1. Run the codemod (60 links') + }) + + test('says nothing about truncation when everything fits', () => { + const few = reportToMarkdown(generateInternalLinkReport(links.slice(0, 3))) + expect(few).not.toContain('more. The codemod fixes every one of them') + }) +}) + describe('version-only redirects', () => { const versionOnly: BrokenLink[] = [ { @@ -771,6 +810,54 @@ describe('version-only redirects', () => { }) }) +describe('section caps', () => { + test('caps stale anchors, keeping the busiest ones and counting the rest', () => { + const anchors: BrokenLink[] = [ + // `/page-0#gone` appears in four files, so it must survive the cut. + ...Array.from({ length: 4 }, (_, f) => ({ + href: '/page-0#gone', + file: `actions/busy-${f}.md`, + lines: [1], + })), + ...Array.from({ length: 39 }, (_, i) => ({ + href: `/page-${i + 1}#gone`, + file: `actions/page-${i + 1}.md`, + lines: [1], + })), + ] + + const markdown = reportToMarkdown(generateInternalLinkReport(anchors)) + + expect(markdown).toContain('## 2. Stale anchors (40 links, 43 occurrences)') + expect(markdown).toContain('/page-0#gone') + expect(markdown).toContain('And 15 more, listed in the report attached to the workflow run.') + }) + + test('caps version-only redirects, which need no action at all', () => { + const versionOnly: BrokenLink[] = Array.from({ length: 30 }, (_, i) => ({ + href: `/admin/page-${i}`, + file: `actions/page-${i}.md`, + lines: [1], + isRedirect: true, + redirectTarget: `/enterprise-cloud@latest/admin/page-${i}`, + })) + + const markdown = reportToMarkdown(generateInternalLinkReport(versionOnly)) + + expect(markdown).toContain('## 4. Version-only redirects (30 links, 30 occurrences)') + expect(markdown).toContain('And 5 more in the same state.') + }) + + test('says nothing about truncation when every section fits', () => { + const markdown = reportToMarkdown( + generateInternalLinkReport([{ href: '/page#gone', file: 'actions/a.md', lines: [1] }]), + ) + + expect(markdown).toContain('## 2. Stale anchors (1 link, 1 occurrence)') + expect(markdown).not.toContain('more, listed in the report attached') + }) +}) + describe('version-only classification across versions', () => { test('a link that is version-only in one version and renamed in another is codemod work', () => { // Merged reports put every version's occurrences in one group. Classifying on the first @@ -820,6 +907,32 @@ describe('version-only classification across versions', () => { }) }) +describe('per-link file list cap', () => { + test('caps the file table under one link and counts the rest', () => { + // Nothing bounds how many pages reuse a link, so one popular link could otherwise + // fill the whole issue body. + const many: BrokenLink[] = Array.from({ length: 30 }, (_, i) => ({ + href: '/page#gone', + file: `actions/page-${i}.md`, + lines: [1], + })) + + const markdown = reportToMarkdown(generateInternalLinkReport(many)) + + expect(markdown).toContain('**Found in 30 files:**') + expect(markdown).toContain('And 10 more files, listed in the report attached') + expect(markdown).not.toContain('actions/page-29.md') + }) + + test('says nothing when every file fits', () => { + const few: BrokenLink[] = [{ href: '/page#gone', file: 'actions/a.md', lines: [1] }] + const markdown = reportToMarkdown(generateInternalLinkReport(few)) + + expect(markdown).toContain('actions/a.md') + expect(markdown).not.toContain('more files, listed in the report attached') + }) +}) + describe('versions checked when some come back clean', () => { const report = (href: string) => generateInternalLinkReport([{ href, file: 'actions/a.md', lines: [1] }])