Add GitHub Copilot as a built-in provider#3076
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review 2 blocking correctness issues found. Diff is too large for automated approval analysis. A human reviewer should evaluate this PR. You can customize Macroscope's approvability policy. Learn more. |
| message: event.data.message.trim(), | ||
| detail: event.data, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🟡 Medium Layers/CopilotAdapter.ts:1855
event.data.message.trim() throws when event.data.message is null or undefined. Other handlers in this file use trimOrUndefined() to safely handle optional SDK messages.
- payload: {
- message: event.data.message.trim(),
- detail: event.data,
- },
+ payload: {
+ message: trimOrUndefined(event.data.message) ?? "",
+ detail: event.data,
+ },🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/CopilotAdapter.ts around lines 1855-1858:
`event.data.message.trim()` throws when `event.data.message` is `null` or `undefined`. Other handlers in this file use `trimOrUndefined()` to safely handle optional SDK messages.
Evidence trail:
apps/server/src/provider/Layers/CopilotAdapter.ts line 1855 (direct .trim() on event.data.message), line 1761 (same field uses trimOrUndefined for session.error), apps/server/src/provider/copilotRuntime.ts lines 63-65 (trimOrUndefined definition with null-safe ?.trim())
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 3bbc092. Configure here.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep sidebarThreadSummaryById owned by shell updates by removing the detail-stream metadata patch and its regression test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ensure Copilot session.error events complete the active turn as failed before clearing active turn state, and cover the lifecycle with a regression test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| toolCallId: input.toolCallId, | ||
| ...(input.toolMeta?.toolName ? { toolName: input.toolMeta.toolName } : {}), | ||
| ...(input.toolMeta?.command ? { command: input.toolMeta.command } : {}), | ||
| ...(input.result ? { result: input.result } : {}), |
There was a problem hiding this comment.
🟢 Low Layers/CopilotAdapter.ts:812
The truthiness check on input.result silently drops falsy tool results like false, 0, '', or null. Since result is unknown and can legitimately be any value from tool execution, these valid return values are lost in telemetry. Consider using input.result !== undefined to preserve all non-undefined results.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/CopilotAdapter.ts around line 812:
The truthiness check on `input.result` silently drops falsy tool results like `false`, `0`, `''`, or `null`. Since `result` is `unknown` and can legitimately be any value from tool execution, these valid return values are lost in telemetry. Consider using `input.result !== undefined` to preserve all non-undefined results.
Evidence trail:
apps/server/src/provider/Layers/CopilotAdapter.ts lines 799-816 (function toolLifecycleData with truthiness check at line 812), line 803 (result typed as `unknown`), lines 2290-2296 (caller at tool.execution_complete also using truthiness check on event.data.result)
| case "url": { | ||
| try { | ||
| const domain = new URL(request.url).hostname.trim(); | ||
| return domain ? { kind: "approve-for-session", domain } : undefined; | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| } |
There was a problem hiding this comment.
🟠 High Layers/CopilotAdapter.ts:496
In the url case, the return statement returns { kind: "approve-for-session", domain } directly instead of using the approve() helper. This produces an object missing the required approval field, so URL permission approvals fail validation as SessionApprovalDecision. All other cases wrap the approval details via approve({ kind: "...", ... }). Change to return domain ? approve({ kind: "url", domain }) : undefined; to match the expected structure.
case "url": {
try {
const domain = new URL(request.url).hostname.trim();
- return domain ? { kind: "approve-for-session", domain } : undefined;
+ return domain ? approve({ kind: "url", domain }) : undefined;
} catch {
return undefined;
}
}🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/CopilotAdapter.ts around lines 496-503:
In the `url` case, the return statement returns `{ kind: "approve-for-session", domain }` directly instead of using the `approve()` helper. This produces an object missing the required `approval` field, so URL permission approvals fail validation as `SessionApprovalDecision`. All other cases wrap the approval details via `approve({ kind: "...", ... })`. Change to `return domain ? approve({ kind: "url", domain }) : undefined;` to match the expected structure.
Evidence trail:
apps/server/src/provider/Layers/CopilotAdapter.ts lines 65-66 (type definitions), lines 467-474 (function signature and approve helper), line 499 (url case returning wrong structure), lines 486-495 and 504-519 (all other cases using approve()), lines 2777-2791 (consumer of the function result)
| const env = { | ||
| ...process.env, | ||
| ...input.env, | ||
| }; |
There was a problem hiding this comment.
🟢 Low provider/copilotRuntime.ts:221
In validateConfiguredCopilotCliPath, process.env is spread before input.env, which re-introduces COPILOT_CLI_PATH_ENV after the caller explicitly deleted it. The function receives a processed environment from buildCopilotClientOptions (which deletes COPILOT_CLI_PATH_ENV and calls normalizeCopilotRuntimeEnvironment), but spreading process.env first loses those changes. Use input.env ?? process.env instead of rebuilding the object.
- const env = {
- ...process.env,
- ...input.env,
- };
+ const env = input.env ?? process.env;🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/copilotRuntime.ts around lines 221-224:
In `validateConfiguredCopilotCliPath`, `process.env` is spread before `input.env`, which re-introduces `COPILOT_CLI_PATH_ENV` after the caller explicitly deleted it. The function receives a processed environment from `buildCopilotClientOptions` (which deletes `COPILOT_CLI_PATH_ENV` and calls `normalizeCopilotRuntimeEnvironment`), but spreading `process.env` first loses those changes. Use `input.env ?? process.env` instead of rebuilding the object.
Evidence trail:
apps/server/src/provider/copilotRuntime.ts line 50: `const COPILOT_CLI_PATH_ENV = "COPILOT_CLI_PATH";`
apps/server/src/provider/copilotRuntime.ts lines 296-303: `buildCopilotClientOptions` creates env from `process.env`, merges `input.env`, then `delete env[COPILOT_CLI_PATH_ENV]` and normalizes.
apps/server/src/provider/copilotRuntime.ts lines 305-309: passes the processed `env` to `validateConfiguredCopilotCliPath`.
apps/server/src/provider/copilotRuntime.ts lines 221-224: `validateConfiguredCopilotCliPath` rebuilds env with `{ ...process.env, ...input.env }`, re-introducing the deleted key from `process.env`.

This PR supersedes #2185. It includes the full Copilot integration from that PR plus the fixes and hardening discovered while testing it against the current codebase.
What Changed
Why
T3 Code supports multiple agent providers, but did not have GitHub Copilot support. This adds Copilot end to end in the same shape as the existing providers, so users can configure Copilot, select Copilot models, use Copilot for git text generation, and rely on the same session/runtime flows as other providers.
The extra hardening keeps Copilot predictable under real usage: turns complete once, tool output and reasoning project correctly, diffs are only emitted when meaningful, permission replies resolve cleanly, checkpoint handling is safer, and provider status/model data stays accurate.
UI Changes
Updated model picker:

Provider settings:

Checklist
Note
High Risk
Large new provider integration with external SDK dependencies plus changes to checkpoint revert ordering and provider rollback recovery, which affect filesystem and conversation state on revert.
Overview
Adds GitHub Copilot as a first-class server provider via
@github/copilot/@github/copilot-sdk, including a newCopilotDriverand a largeCopilotAdaptertest suite covering sessions, permissions, diffs, queued turns, and lifecycle edge cases.Checkpoint revert is reordered and hardened: validate target refs before restore, restore filesystem first, then call provider rollback only after a successful restore; on rollback failure (e.g. Copilot has no thread rollback), attempt to restore the current checkpoint and invalidate workspace entries when recovery fails. Tests cover failed filesystem restore (no provider rollback) and Copilot-specific failure paths.
Orchestration/runtime improvements include projecting
content.deltastreams into reasoning and tool output thread activities; not advancinglatestTurn/latestTurnIdwhile another turn is still running; first-turn title/branch generation on a drainable worker with retries and truncated-seed title replacement; and clearer checkpoint diff errors when a git ref is missing.Reviewed by Cursor Bugbot for commit 65e0a24. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add GitHub Copilot as a built-in provider with full adapter, text generation, and settings support
CopilotDriveras a built-in provider, wiring together a Copilot adapter, text generation layer, and periodic health checks with isolated per-instance home directories.CopilotSettingsschema (binaryPath, serverUrl, customModels, enabled) to server settings with validation and patch support.makeCopilotTextGenerationfor commit messages, PR content, branch names, and thread titles via shared Copilot SDK clients with automatic idle shutdown after 30 seconds.classifyProviderToolItemTypeandclassifyProviderToolRequestTypeinpackages/sharedto normalize provider tool invocations into canonical lifecycle and approval types using name/argument heuristics.promptEffortderivation to only use reasoning-related descriptors (e.g.reasoningEffort) and not unrelated selectors likecontextTier; fixeslatestTurnIdadvancing when a different turn is still running.node_modules/.bin/copilot; misconfigured or missing binaries surface as probe errors rather than startup failures.Macroscope summarized 65e0a24.