# Copilot compatibility acceptance

This change is not complete when a GPT-6-only prototype works or a PR is merely opened. The requested result is a plugin-only, data-driven Copilot integration, including public reasoning summaries, followed by verified delivery and local upgrade.

## Scope

Follow the [plugin-only boundary](../AGENTS.md#plugin-only-implementation-boundary). Reuse the published DSH/pi-ai adapter and OAuth interfaces. Do not modify Core, dependency artifacts, private registries, or shared upstream catalogs. A plugin-owned Copilot route may serve multiple account-advertised models; this is configuration and lifecycle integration, not another implementation of generic wire serialization.

## Required behavior

1. **Account-driven discovery.** Read the provider's model metadata with bounded requests and account-scoped cache lifetime. A new model with sufficient supported metadata must not require another model-ID patch or plugin release. A new model name alone is insufficient evidence of its protocol or capabilities.
2. **Protocol selection from evidence.** Use advertised `supported_endpoints` for Responses, Chat Completions, or Anthropic Messages. Native pi catalog choices may be retained only when compatible with the advertised endpoints. Do not infer a protocol from a GPT, Gemini, Claude, or other name prefix. Missing or unsupported metadata produces an explicit diagnostic, not a guessed route.
3. **Capabilities stay distinct.** Preserve context, input and output limits separately; do not invent large limits or unsupported reasoning levels. Respect disabled/unconfigured policy, picker eligibility and tool/stream support. Provider metadata does not authorize changing account policy.
4. **One credential lifecycle.** All plugin-owned routes share the canonical Host-only Copilot OAuth record. Model discovery, refresh and in-flight requests remain bound to the same account generation. Account switches, entitlement removal, disposal and stale callbacks must fail closed without copying credentials or changing other providers.
5. **Thinking is included.** Preserve public summary deltas and final-only summaries without duplicate text. Validate selected effort and keep native default semantics. Empty or encrypted-only responses must not become fabricated explanations. Native replay, images and file projection retain their owning adapter's behavior; opaque replay is not displayed or rewritten into raw reasoning text.
6. **Current pi 0.87.1 compatibility.** The original acceptance target was 0.85.1; official DSH rc.2 now depends on `^0.87.1`. Pin and test the exact published SDK version, not just a GitHub release label. Its Copilot GPT-6 catalog protocol must not override contrary provider endpoint evidence. An updated catalog entry is not sufficient grounds to retire a correction if the protocol/capabilities are still wrong.
7. **Do not infer temperature support from reasoning metadata.** No verified supplier contract establishes that `capabilities.supports.thinking` means temperature is unsupported or supported. Automatic reviewer-only omission requires authenticated exact official rc.2 root callback identity through the public service-read/Loader/profile package seams and account-proven managed Responses metadata. Clone before native delegation, preserve concurrent ordinary Chat and reject unqualified identity without broad fallback. Test actual Host startup/disposal and the unchanged native reviewer/LLM/adapter pipeline, with explicit nonzero/no-skip fixture collection. The independent `github-copilot.responsesOmitTemperature` override remains default false; explicit true affects all managed Responses. Preserve frozen options, cancellation, other protocols/providers and canonical ownership. Never use prompt/model/error matching, retry a rejection, rewrite a verdict or change permissions. See [the exact public proof and limitations](./auto-review-sampling.md); previously excluded fixture execution claims stay retracted.

## Regression evidence

- Include GPT-6 Astra, Gemini 3.8 Flash and GPT-5.6 Sol Fast metadata fixtures, plus arbitrary unseen IDs on all three supported protocols. The unseen-ID cases must pass without adding those IDs to implementation tables.
- Cover missing/conflicting endpoints, missing/invalid limits, duplicate conflicting IDs, explicit denial, stale account snapshots, unsupported reasoning labels, unknown fields and hostile extra getters.
- Exercise real published adapter/SDK code against local synthetic HTTP for all supported protocols, including tools, public summaries, two-turn opaque replay, images and interruption/retry paths. Synthetic results are not claims of live account availability.
- The exact pinned rc.2 source trace must retain the reviewer's frozen `temperature: 0` request with no `purpose`/`sessionId`. Run that unchanged native reviewer through the managed route and real pi-ai serializer: default-off preserves temperature for thinking `true`, `false`, or absent; explicit opt-in omits it for each value without mutating caller input. Cover allow, deny, malformed verdict, HTTP error and cancellation with one wire attempt; only allow may execute the synthetic tool. Qualify the fixture against tagged source and exact published artifacts. Synthetic metadata proves no supplier temperature semantics.
- Verify managed request-body timeout guidance only for the observed HTTP `408`, exact `user_request_timeout` code and request-body message. Bound clone reads to 8 KiB/250 ms, retain the original native response, and expose only fixed guidance plus final string-body UTF-8 bytes. Preserve failure code, retry metadata, cancellation, unchanged retry payloads and independent dispatches. Do not reclassify 408 as overflow, invent a safe byte threshold, change client timeouts, refresh OAuth, compact automatically or promise elimination of upstream failures.
- Verify byte-aware managed HTTP/SSE liveness through unchanged published and tagged adapters: heartbeat bytes may cross the original semantic deadline before actual output, but endless heartbeat-only output must hit the bounded native deadline. Real header/body stalls retain the original interval and a dispatch-local TIMEOUT; consumer think time, caller/credential cancellation, exact bytes, parser ownership and one native wire attempt remain unchanged. Explicit WebSocket/auto and opt-out retain native-only behavior. Preserve the 20 MiB native image budget by default; explicit smaller budgets must use native offload and retain mapped read-only paths/history, never hidden trimming or a claimed supplier threshold. These synthetic cases do not attest production heartbeat activity or cure HTTP 408.
- Run source/test typechecks, complete repository tests, built Host/Client/Remote smoke, archive verification, and relevant unchanged-Core compatibility fixtures. Do not suppress type or test failures to bridge incompatible SDK versions.

## Explicit public-interface limits

- The published Core model-info contract exposes combined context capacity, not a separate prompt-token budget. The managed route retains truthful capacities and adds provider-scoped estimated admission (`INPUT_LIMIT_ESTIMATED_GUARD`), with optional earlier local pressure using existing stock recovery. This is not exact provider token counting or native dual-limit metadata support. Preserve explicit summary output caps and resolved reasoning; unsupported low-effort controls are not guessed. Already oversized manual summaries still fail without silent trimming or chunking. See [compaction budgets and acceptance boundaries](./copilot-compaction.md); no Core patch or false contextWindow is permitted.
- Advertised reasoning labels that the native SDK cannot express must show `REASONING_EFFORTS_UNSUPPORTED` rather than being silently hidden or guessed. Such a warning need not disable ordinary model requests.
- The Core-facing adapter integration uses the public `streamSimple` path. Its advanced protocol-specific `stream` entry must reject explicitly rather than accepting incompatible client objects from another SDK version. The event-stream compatibility boundary must verify the full public surface at compile time and preserve the original stream object.
- Cached catalog entries do not authorize requests independently. A change in the current grant's model-permission list invalidates old proofs even when the token and account identity are unchanged; a new server-enabled model absent from the earlier grant list remains discoverable without rewriting the grant.
- A plugin-owned credential invalidation or disposal preserves its specific `ABORTED` diagnostic through the native stream boundary. Only the plugin's own abort reason identity authorizes that diagnostic; upstream text never does. This does not repair HTTP 408 request-body timeouts or transport termination, change retries/timeouts, or turn incomplete Responses output into success. SDK error events can contain initialized zero usage without a received measurement; the public usage object lacks a presence flag, so the plugin does not discard zeros or rewrite native context metering.

## Delivery and local acceptance

- Open the plugin PR with tests, evidence limits, migrations and rollback. Merge only after required checks and review are satisfied; the user has explicitly included merge in this task's acceptance scope.
- Publish the aligned version through the protected Release workflow and verify the release/tag/commit/assets/checksum.
- Upgrade the user's actual local profile to that verified plugin artifact; do not infer the profile from a README example. Verify installed bytes and loaded runtime separately.
- The user has requested local upgrade. Explain any session-interrupting restart before doing it and obtain acknowledgement of that interruption. Do not install a modified Core as part of the upgrade.
- Report remaining limitations honestly, including unavailable provider metadata, unsupported protocols, optional summaries and any unperformed live checks.

## Metadata references

- [Microsoft Copilot API types](https://github.com/microsoft/vscode/blob/main/src/typings/copilot-api.d.ts)
- [Endpoint-driven Copilot discovery implementation reference](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/models.ts)
- [pi v0.85.1 release](https://github.com/earendil-works/pi/releases/tag/v0.85.1)
- [pi v0.87.1 release](https://github.com/earendil-works/pi/releases/tag/v0.87.1)

These references guide parsing and tests; they are not a guarantee that the upstream discovery schema will never change. Schema drift must be surfaced rather than silently guessed.

## Authentication, replay and request diagnostics

These failures have different owners; do not repair every rejection by signing out.

| Diagnostic | Boundary and safe action |
|---|---|
| `COPILOT_AUTHORIZATION_BEGIN_FAILED` | Sign-in did not complete; interaction milestones are not a credential commit or root-cause proof |
| `COPILOT_ROUTE_REPAIR_FAILED` | Authentication is retained; review legacy configuration and use explicit repair |
| Native AUTH / HTTP 401 | Exact managed proof may be retired; a later explicit request/discovery can renew natively, not replay the failed message |
| `COPILOT_RESPONSES_REPLAY_SCOPE_MISMATCH` | Exact uncoded item-scope rejection is request-local INVALID_REQUEST, without invalidating shared proof/other requests |
| `COPILOT_RESPONSES_REPLAY_UNSUPPORTED` | Incomplete/reference-only historical payload lacks safe reconstruction evidence |
| `COPILOT_REQUEST_BODY_TIMEOUT` | Strict verified request-body 408; composition/timing is not proof of overflow or network cause |

HTTP auth recovery is bounded by account cooldown, including forced discovery.
An old late response cannot invalidate a newer sign-in. HTTP 403, network errors
and strings mentioning 401 are not auth evidence; malformed/ambiguous/oversized
bodies retain native handling. HTTP tests do not prove WebSocket recovery.

### Responses replay compatibility

The public SDK payload hook normalizes only direct IDs on complete assistant
messages, function calls and encrypted reasoning items. A narrowly recognized
empty completed reasoning shell can be omitted from outgoing requests only with
empty summary/content, no encrypted content and no unknown fields. Durable
history, public summaries, opaque bytes, nested IDs, phase and `call_id` pairing
stay unchanged.

The source-checkout follow-up for #279 adds bounded structure evidence to an
exact verified scope rejection: total items, direct ID fields, reference items,
encrypted reasoning items, unrecognized items, `previous_response_id`/`store`
state and presence of the two native session/request headers. It observes the
final HTTP dispatch, not the earlier SDK payload before caller replacement.
Only fixed labels and counts enter the separate `dispatchEvidence` diagnostic
and Host warning; the main error stays concise and points to explicit recovery
or a new conversation. No IDs, header values, message text, arguments, results
or opaque bytes are retained. The shared
16 MiB/depth/work diagnostic limits apply; unavailable evidence never becomes
zero counts. Historical errors are not rewritten; absence of captured evidence
is not zero counts.
Structure counts alone do not repair cross-turn scope rejection: zero direct
IDs does not prove encrypted replay is portable.

Switching from account A to B does not make old account/connection-bound
encrypted reasoning portable. An exact scope rejection is not proof that
sign-in failed or that a particular encrypted item was rejected. Recovery
is an explicitly authorized, lossy continuation, not a portability repair.
Starting a new conversation avoids replaying the old history.

### Explicit session replay recovery

After an exact verified managed Responses scope rejection, **Replay recovery**
above that session's composer automatically offers the failed request's old
encrypted items on opening the eligible session, when its native `running`
snapshot settles, or when its `lastAgentError` changes while idle. These public
Session snapshot changes trigger read-only Host evidence reads, not model calls,
history reads, new durable events or polling. Normal sessions show no notice.
Dismissal is local to the mounted model/session and evidence revision; a new
failure can reappear. Read errors remain distinct from missing evidence.

Context evidence and recovery use the public full-width
`conversation.input.dock` with shared native secondary typography for body
text and controls. They stack above the composer rather than squeezing into
its non-wrapping statistics row. Native meters, statistics and parent DOM stay
unchanged; authorization itself still sends no message.

The normal Client offers one persistent Session policy. When off, the notice
explains the loss and offers **Enable visible-history continuation** or Cancel.
Enabling writes the same revision-checked Session policy used by account controls;
it persists across accounts/restarts until disabled, without an hourly consent.
When already on, show status and diagnostic guidance, not another authorization.
Use native Retry separately. Missing or failed policy reads have an explicit
diagnostic and Retry read, never a guessed off state or legacy authorization
fallback. No authorization duration radios or next-turn-only actions are shown.
Controls are disabled during a native turn.

The following temporary recovery mechanism and strict Remotes remain for
compatibility, not new normal UI authorization. Their one-hour evidence/proof
lifetime is not the lifetime of the persistent Session policy:

This is lossy recovery, not a supplier-scope repair. Only complete normalized
encrypted reasoning items whose entire serialized fingerprints match that
failed request are omitted from later outgoing requests in the same session
and model. Their hidden state **and their item summaries** are no longer sent;
displayed messages, tool calls/results and stored history are not modified.
New or changed reasoning items remain native-owned. Another scope failure
requires fresh confirmation rather than broadening enabled omission silently.

Evidence is bounded to 64 sessions, 512 distinct items per session, and one
hour in the current Host. Only hashes, a model label and confirmation metadata
are retained, never opaque payloads. The Client receives count/model/state,
an opaque revision, optional duration and evidence expiry only. The original
`get`/`setEnabled` descriptors retain their identities; `setEnabled(true)` still
means bounded session consent, and additive `authorize` selects a duration.
A one-shot expiry read updates the mounted notice, not a polling timer.
Token/account/metadata proof discontinuity, expiry,
session disposal or Host restart revokes evidence; an old error on disk cannot
create a candidate. Re-read status after these changes. Disabling affects
future requests and does not undo answers already generated.

Admission requires the initiating native Agent request signal and exact
session; unbound requests, compaction, titles, classifier requests, other
models and other sessions cannot borrow consent. Active-turn writes are
rejected. Public native Client/Host gateways enforce explicit Agent lookup;
no ambient Client session projection is added. No Core changes, credentials
reset, history rewrite, general wire adapter or automatic business retry is
introduced. This is not a remedy for HTTP 408, quota or context limits.

Two explicitly authorized real-history diagnostic calls reproduced HTTP 401
with 12 old encrypted items and returned HTTP 200 after omitting exactly those
items in a disposable request copy, preserving all 80 tool pairs. This
implicates the old item set, not any specific item, token rotation or backend
expiry rule. Sequential calls cannot freeze hidden supplier state. Synthetic
request-chain and native gateway tests qualify the opt-in mechanism; they do
not establish that every rejected history is recoverable or that an installed
runtime has enabled it.

After actual HTTP 408, a newly prepared attempt in the same native step can
reuse exact normalized payload only when transcript/every other field agree,
with matching references and no conflicts. Evidence is bound to Session/model/
account proof and original signal, expires in 60 seconds, is limited to 2 MiB
and clears on next step/turn, concurrency, cancellation or account discontinuity.
This creates no retry or cold-history reconstruction. Canonical/other routes,
protocols and native retry policy stay unchanged. Synthetic adapter tests are
not acceptance proof for an old conversation. Never discard opaque reasoning,
rewrite history, replay business work or reset credentials automatically.

For TLS/quota, see [trust boundaries](./copilot-usage.md#account-data-boundary).
For HTTP 408 and oversized summaries, see
[budget and recovery limits](./copilot-compaction.md#existing-oversized-history-and-recovery-limits).
