Skip to content

feat: support adding GitHub Copilot AI provider via UI#25888

Merged
dannykopping merged 5 commits into
mainfrom
dk/copilot-provider
Jun 1, 2026
Merged

feat: support adding GitHub Copilot AI provider via UI#25888
dannykopping merged 5 commits into
mainfrom
dk/copilot-provider

Conversation

@dannykopping

Copy link
Copy Markdown
Contributor

Copilot is the only AI provider type that could not be added through the /ai/settings UI. The aibridge runtime and the env-var seeding path already supported it, but the runtime CRUD API rejected type=copilot and the UI omitted it entirely. The root cause is that Copilot's auth model (a per-request GitHub OAuth token, with no pre-shared key) does not fit the credential-centric add-provider flow that every other provider uses.

Backend

Allow type=copilot in CreateAIProviderRequest.Validate(), and reject api_keys for Copilot on both create (validation) and update (handler sentinel), mirroring the existing Bedrock guards. Copilot carries no stored credential.

Frontend

Add Copilot to the provider type picker (with the github-copilot.svg icon) and give the form a credential-free branch: name, display name, and a free-text endpoint defaulting to https://api.business.githubcopilot.com, with copy explaining that authentication happens via the user's GitHub token at request time. Copilot maps to the distinct copilot wire type rather than collapsing to openai, and the edit flow recovers it correctly.

The endpoint stays required with a business-tier default; users on the individual or enterprise endpoints edit the field.

🤖 Generated with Claude Code

@dannykopping dannykopping changed the base branch from main to graphite-base/25888 June 1, 2026 08:19
@dannykopping dannykopping force-pushed the dk/copilot-provider branch from ebb97fa to 193fd44 Compare June 1, 2026 08:19
@dannykopping dannykopping changed the base branch from graphite-base/25888 to dk/aibridge-providers-deprecate-seeding-env June 1, 2026 08:19

dannykopping commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-06-01 08:22 UTC by @dannykopping
Spend: $47.19 / $100.00

Review history
  • R1 (2026-06-01): 22 reviewers, 4 Nit, 1 Note, 2 P2, 6 P3, COMMENT. Review

deep-review v0.6.0 | Round 1 | 5af0214..193fd44

Last posted: Round 1, 13 findings (2 P2, 6 P3, 4 Nit, 1 Note), COMMENT. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Open ProviderForm.tsx:442 Submit button permanently disabled on valid copilot create form R1 Nami Yes
CRF-2 P2 Open ProviderForm.tsx:86 UI default endpoint contradicts backend default R1 Pen Botter Yes
CRF-3 P3 Open ProviderForm.tsx:351 Help text omits GitHub external auth prerequisite R1 Pen Botter P2, Luffy P3, Leorio Note Yes
CRF-4 P3 Open UpdateProviderPageView.stories.tsx:49 No edit-mode Storybook story for copilot R1 Bisky P3, Chopper P3, Meruem P3, Pariston P3, Kite Nit, Nami Nit, Luffy Note, Mafuuu Note, Pen Botter Note Yes
CRF-5 P3 Open providerFormApiMap.ts:92 getProviderDisplayType lacks early return for copilot before host matching R1 Hisoka Yes
CRF-6 P3 Open coderd/ai_providers.go:343 10 comments across 7 files repeat same copilot auth explanation R1 Gon P2 Yes
CRF-7 P3 Open ProviderForm.tsx:345 Endpoint field lacks copilot-specific tier guidance R1 Pen Botter Yes
CRF-8 P3 Open providerFormApiMap.ts:154 Three branches duplicate base-field assembly in providerFormValuesToCreate R1 Robin Yes
CRF-9 Nit Open codersdk/aiproviders.go:190 Doc on CreateAIProviderRequest omits Copilot api_keys restriction R1 Chopper, Mafuuu Yes
CRF-10 Nit Open providerFormApiMap.ts:164 Comment says "Bedrock branched out" but copilot also branches early R1 Razor Yes
CRF-11 Nit Open ProviderIcon.stories.tsx:42 No Copilot story for new icon mapping R1 Meruem, Nami, Pariston Yes
CRF-12 Nit Open ProviderForm.stories.tsx:80 AddCopilot story seeds displayName not present in real add flow R1 Nami Yes
CRF-13 Note Open UpdateProviderPageView.tsx:46 providerIsOpenAiAnthropic now misclassifies copilot (behavior correct) R1 Chopper, Hisoka, Kite, Knov, Luffy, Meruem, Razor P3, Pen Botter, Zoro Yes
CRF-14 Nit Dropped by orchestrator (two-case duplication is readable) codersdk/aiproviders.go:250 Bedrock and copilot api_keys rejection guards structurally identical R1 Robin No

Round log

Round 1

Panel. 2 P2, 6 P3, 4 Nit, 1 Note new. 1 dropped. Reviewed against 5af0214..193fd44.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean implementation that mirrors the Bedrock sentinel pattern well. Defense-in-depth for key rejection is solid: SDK validation on create, transaction sentinel on update, frontend schema omitting the field, frontend mapping stripping keys from the wire payload. Four independent layers. The copilot wire type is preserved end-to-end without collapsing to openai, which is the correct architectural call.

2 P2, 6 P3, 4 Nit, 1 Note.

The two P2s are a functional bug (the submit button is disabled on a valid copilot create form because the dirty gate fires with all defaults pre-filled) and an endpoint default mismatch between the UI and backend. Both are straightforward fixes.

Most P3s cluster around missing Storybook coverage for the copilot edit flow, which 9 reviewers independently flagged. The icon, row, and page-level edit stories all need a copilot variant to match the convention.

"Boring code gets silence. The rest holds up." (Hisoka)


site/src/pages/AISettingsPage/ProvidersPage/components/ProviderForm.tsx:442

P2 [CRF-1] The submit button is permanently disabled on a valid copilot create form.

The button is gated on !form.dirty, and Formik computes dirty by comparing current values against initial values. For every other provider type, at least one mandatory field starts empty (API key for openai/anthropic, access key pair for bedrock), so filling it naturally makes the form dirty. Copilot is the first provider whose providerDefaults (name: "copilot", baseUrl: "https://api.business.githubcopilot.com") satisfy the entire makeCopilotSchema on mount.

Trace: AddProviderPage passes initialValues={{ type: "copilot" }}. ProviderForm merges defaultInitialValues < typeDefaults < initialValues, producing { name: "copilot", baseUrl: "https://api.business.githubcopilot.com", ... }. form.isValid is true, form.dirty is false, button stays disabled. The user sees a complete form they cannot submit without making a throwaway edit.

Fix: either start with name: "" in copilot's providerDefaults (so the user must type a name, making the form dirty), or gate the button on !form.dirty only when editing is true. (Nami)

🤖

site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.ts:92

P3 [CRF-5] getProviderDisplayType falls through to host matching for copilot, where every other non-collapsing type has an early return.

Bedrock (line 95) and Anthropic (line 98) both return before the host-matching logic. Copilot passes through and relies on the provider.type fallback at line 103. With the default api.business.githubcopilot.com this works (no host match). But the PR says users on other tiers will edit the endpoint. If an admin pointed it at a URL whose host coincidentally matched an Azure subdomain, the list view renders the Azure icon instead of Copilot.

Fix: add if (provider.type === "copilot") return "copilot"; after the anthropic guard, consistent with the bedrock/anthropic pattern. (Hisoka)

🤖

site/src/pages/AISettingsPage/ProvidersPage/components/ProviderForm.tsx:345

P3 [CRF-7] The endpoint field gives no copilot-specific guidance on valid values or tier selection.

Copilot has three distinct tiers with three different hostnames (api.individual.githubcopilot.com, api.business.githubcopilot.com, api.enterprise.githubcopilot.com). Compare with Bedrock, which gets a custom description ("In the format of https://bedrock-runtime.{region}.amazonaws.com"). The admin has to know which URL to use from external context; getting it wrong produces a runtime failure, not a validation error.

Consider overriding the endpoint description when the selected type is copilot to list the three valid hostnames, similar to the Bedrock format hint. (Pen Botter)

🤖

site/src/pages/AISettingsPage/ProvidersPage/UpdateProviderPage/UpdateProviderPageView.tsx:46

Note [CRF-13] providerIsOpenAiAnthropic is now inaccurate for copilot.

The variable is !isBedrockProvider(provider), which evaluates to true for copilot. Before this PR, "not Bedrock" was synonymous with "uses API keys." Copilot is the first type to break that equivalence. The behavior is safe today: provider.api_keys.length > 0 independently gates credential seeding (line 112), so openAiAnthropicSavedApiKey resolves to false and no masked credential is seeded. But the variable's contract now lies to the reader.

9 of 22 reviewers independently flagged this. The code is correct; only the naming misleads. A rename to providerUsesApiKeys (with && provider.type !== "copilot") would make the intent explicit, but this file is outside the diff. (Chopper, Hisoka, Kite, Knov, Luffy, Meruem, Razor, Pen Botter, Zoro)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/ai_providers.go Outdated
Comment thread site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.ts Outdated
Comment thread codersdk/aiproviders.go Outdated
Comment thread site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.ts Outdated
dannykopping added a commit that referenced this pull request Jun 1, 2026
Resolve the deep-review findings on PR #25888:

- Gate the submit button's !form.dirty check to edit mode so a valid
  create form (Copilot prefills every required field) is submittable
  without a throwaway edit (CRF-1).
- Align the UI default Copilot endpoint with the backend and docs
  (individual tier) (CRF-2).
- State the GitHub external-auth prerequisite in the Copilot help text
  (CRF-3).
- Add edit-mode Storybook coverage for Copilot (ProviderForm,
  UpdateProviderPageView) and a ProviderIcon Copilot story (CRF-4, CRF-11).
- Return early for Copilot in getProviderDisplayType (CRF-5).
- Trim duplicated Copilot auth comments, keeping the canonical sentinel
  doc and type const doc (CRF-6).
- Add Copilot endpoint tier guidance listing the three hostnames (CRF-7).
- Extract the shared base object in providerFormValuesToCreate (CRF-8).
- Mention Copilot in the CreateAIProviderRequest doc comment (CRF-9).
- Update the "unreachable" comment to mention Copilot (CRF-10).
- Pass only the type in the AddCopilot story to match the real add flow
  (CRF-12).
- Rename providerIsOpenAiAnthropic to providerUsesApiKeys and exclude
  Copilot so the contract is honest (CRF-13).
@dannykopping dannykopping marked this pull request as ready for review June 1, 2026 09:51
@dannykopping dannykopping force-pushed the dk/aibridge-providers-deprecate-seeding-env branch from 5af0214 to 70e8be7 Compare June 1, 2026 10:00
@dannykopping dannykopping force-pushed the dk/copilot-provider branch from 6e916b4 to 23b60ef Compare June 1, 2026 10:00
@dannykopping dannykopping force-pushed the dk/copilot-provider branch from 98e8207 to 23b60ef Compare June 1, 2026 10:07

@ibetitsmike ibetitsmike left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approved with a nit

// maxLoggedErrorBodyBytes bounds how much of an aibridged error response
// body is rendered into a log line, so a large upstream error payload
// cannot blow up log volume.
const maxLoggedErrorBodyBytes = 16 << 10 // 16 KiB

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fancy, but isn't 16*1024 just more readable? or even explicitly adding 16384

@dannykopping dannykopping force-pushed the dk/copilot-provider branch from 23b60ef to 8bea045 Compare June 1, 2026 11:26
@dannykopping dannykopping force-pushed the dk/aibridge-providers-deprecate-seeding-env branch from 70e8be7 to 1e0ff3a Compare June 1, 2026 11:26
@dannykopping dannykopping force-pushed the dk/copilot-provider branch from 8bea045 to 2e2fe02 Compare June 1, 2026 11:44
@dannykopping dannykopping force-pushed the dk/aibridge-providers-deprecate-seeding-env branch from 1e0ff3a to 72fc38b Compare June 1, 2026 11:44

dannykopping commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Jun 1, 1:15 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 1, 1:17 PM UTC: Graphite rebased this pull request as part of a merge.
  • Jun 1, 1:26 PM UTC: @dannykopping merged this pull request with Graphite.

@dannykopping dannykopping changed the base branch from dk/aibridge-providers-deprecate-seeding-env to graphite-base/25888 June 1, 2026 13:15
@dannykopping dannykopping changed the base branch from graphite-base/25888 to main June 1, 2026 13:15
dannykopping and others added 2 commits June 1, 2026 13:16
Copilot was supported by the aibridge runtime and env-var seeding but
blocked from the runtime CRUD API and absent from the /ai/settings UI,
because its auth model (per-request GitHub OAuth tokens, no stored key)
did not fit the credential-centric add-provider flow.

Allow type=copilot in CreateAIProviderRequest.Validate() and reject
api_keys for copilot on both create and update, mirroring the bedrock
guards. Add copilot to the provider type picker and give the form a
credential-free branch defaulting to the business endpoint, mapping it
to the distinct copilot wire type rather than collapsing to openai.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When aibridged returns a 4xx or 5xx response, the proxy now logs the
response body alongside the status. The body is read and then restored
with an equivalent reader so the proxy still forwards it downstream and
the response dumper can read it again. The logged rendering is capped at
16 KiB so a large upstream error payload cannot blow up log volume.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve the deep-review findings on PR #25888:

- Gate the submit button's !form.dirty check to edit mode so a valid
  create form (Copilot prefills every required field) is submittable
  without a throwaway edit (CRF-1).
- Align the UI default Copilot endpoint with the backend and docs
  (individual tier) (CRF-2).
- State the GitHub external-auth prerequisite in the Copilot help text
  (CRF-3).
- Add edit-mode Storybook coverage for Copilot (ProviderForm,
  UpdateProviderPageView) and a ProviderIcon Copilot story (CRF-4, CRF-11).
- Return early for Copilot in getProviderDisplayType (CRF-5).
- Trim duplicated Copilot auth comments, keeping the canonical sentinel
  doc and type const doc (CRF-6).
- Add Copilot endpoint tier guidance listing the three hostnames (CRF-7).
- Extract the shared base object in providerFormValuesToCreate (CRF-8).
- Mention Copilot in the CreateAIProviderRequest doc comment (CRF-9).
- Update the "unreachable" comment to mention Copilot (CRF-10).
- Pass only the type in the AddCopilot story to match the real add flow
  (CRF-12).
- Rename providerIsOpenAiAnthropic to providerUsesApiKeys and exclude
  Copilot so the contract is honest (CRF-13).
Signed-off-by: Danny Kopping <danny@coder.com>
@dannykopping dannykopping force-pushed the dk/copilot-provider branch from 2e2fe02 to 7a08f86 Compare June 1, 2026 13:16
@dannykopping dannykopping merged commit a85462b into main Jun 1, 2026
30 of 32 checks passed
@dannykopping dannykopping deleted the dk/copilot-provider branch June 1, 2026 13:26
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 1, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants