Repository navigation
fix(copilot): terminate requests on negotiated model scope denial #7026
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,6 +56,11 @@ import { stampOAuthAccountLabel, usesApiKeyAccount } from "../../providers/label | |
| import { resolveProviderTransport } from "../../providers/xai-transport"; | ||
| import { captureRouteStaticPolicy } from "../../router"; | ||
| import { CopilotAutoHttpError, resolveCopilotAuto } from "../../providers/github-copilot-auto"; | ||
| import { | ||
| AdmissionModelDeniedError, | ||
| admissionModelDeniedResponse, | ||
| assertRouteAllowedByScope, | ||
| } from "../admission-model-scope"; | ||
| import { renameRoutedIdentityInContext } from "../../adapters/identity"; | ||
| import { resolveCopilotApiBaseUrl } from "../../oauth/github-copilot"; | ||
| import { | ||
|
|
@@ -109,6 +114,8 @@ export async function prepareResponsesTransport( | |
| | "inboundWire" | ||
| | "selectedForwardHeaders" | ||
| | "translatorBudget" | ||
| | "inboundSelector" | ||
| | "admissionScope" | ||
| >, | ||
| ) { | ||
| const { config, logCtx, options, req } = requestContext; | ||
|
|
@@ -297,6 +304,7 @@ export async function prepareResponsesTransport( | |
| const copilotRequestedModel = route.modelId; | ||
| /** Project only bounded Copilot refusal metadata; upstream bodies and session tokens never reach the client. */ | ||
| const copilotRefusalResponse = (error: unknown): Response | undefined => { | ||
| if (error instanceof AdmissionModelDeniedError) return admissionModelDeniedResponse(error); | ||
| if (!(error instanceof CopilotAutoHttpError) || ![401, 403, 429].includes(error.status)) return; | ||
| const response = formatErrorResponse(error.status, error.status === 429 ? "rate_limit_error" : "authentication_error", | ||
| error.message); | ||
|
|
@@ -357,6 +365,13 @@ export async function prepareResponsesTransport( | |
| replayOAuthCredentialSnapshot = { accountId: refreshed.accountId, generation: refreshed.generation }; | ||
| selected = await negotiate(); | ||
| } | ||
| assertRouteAllowedByScope( | ||
| // The scope captured at admission: a key edited or removed while negotiation was in | ||
| // flight must not widen this request. The refusal names the caller's own selector. | ||
| requestState.admissionScope, | ||
| requestState.inboundSelector, | ||
| { providerName: route.providerName, modelId: selected.modelId }, | ||
|
coderabbitai[bot] marked this conversation as resolved.
Comment on lines
+368
to
+373
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a scoped Copilot Auto request is using the web-search or image sidecar and the routed inference gets a 429, this new assertion can throw during the sidecar's key/OAuth rotation; however, AGENTS.md reference: src/AGENTS.md:L17-L19 Useful? React with 👍 / 👎.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @- |
||
| ); | ||
| copilotSessionExpiresAt = selected.expiresAt ?? Infinity; | ||
| route.provider = selected.provider; | ||
| route.modelId = selected.modelId; | ||
|
|
@@ -725,6 +740,7 @@ export async function prepareResponsesTransport( | |
| let nextAdapter: ProviderAdapter; | ||
| try { nextAdapter = await refreshDispatchAdapter(requestParsed); } | ||
| catch (error) { | ||
| if (error instanceof AdmissionModelDeniedError) throw error; | ||
| const refusal = copilotRefusalResponse(error); | ||
| if (refusal) return refusal; | ||
| throw error; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When Copilot Auto is a combo target and negotiation denies its concrete model, this marker correctly stops the combo hop, but
consumeComboFailurestill consumes the response and rebuilds it aserror.type = "upstream_error"with the original JSON embedded in the message. Consequently clients cannot branch on the documented stablemodel_not_allowed_for_keytype (the new test only checks for a substring); preserve the local admission response or carry a structured code through combo normalization.AGENTS.md reference: src/AGENTS.md:L19-L19
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@-