@alvin0/ai-agent-sdk-provider-copilot 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/index.d.ts +17 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["malformed","declaredEndpointOf","AgentSdkError","REDACTED","id","NULL_LOGGER","COPILOT_LOGIN_COMMAND"],"sources":["../src/common/error-codes.ts","../src/errors.ts","../src/auth.ts","../src/common/identity.ts","../src/common/no-follow.ts","../src/common/http.ts","../src/catalog.ts","../src/common/store-capture.ts","../src/dual-protocol.ts","../src/exchange.ts","../src/router.ts","../src/adapter.ts","../src/oauth.ts"],"sourcesContent":["/**\n * The Copilot error codes, in the leaf layer so the shared HTTP modules can reach\n * them.\n *\n * The taxonomy BELONGS to `../errors.ts` — that module is the public door, and it\n * re-exports everything here. The definition lives one layer down for a structural\n * reason: `common/` is a leaf, and `common/http.ts` needs\n * `ENDPOINT_ORIGIN_INVALID` and `COPILOT_REDIRECT_REJECTED` to throw. Importing\n * them from a root module would make `common/` depend on the root while the root\n * already depends on `common/`, which is the source-ownership cycle the repo's\n * package-graph check forbids. Duplicating the two strings instead would be worse:\n * a code that exists in two places is a code that can disagree with itself.\n *\n * Read `../errors.ts` for the taxonomy's rationale, including the three situations\n * that deliberately get an EXISTING SDK code rather than a Copilot one.\n *\n * @module ai-agent-sdk/providers/copilot/error-codes\n */\n\n/**\n * Stable codes for the failures that are specific to Copilot.\n *\n * Frozen, and flat strings rather than a TS enum, for the same reason the core\n * taxonomy is: a consumer routes on the value, and the value has to survive\n * serialization into a log line.\n */\nexport const COPILOT_ERROR_CODES = Object.freeze({\n /** The token-exchange surface rejected the credential: a PAT, or a non-allowlisted OAuth App. */\n CREDENTIAL_REJECTED: 'COPILOT_CREDENTIAL_REJECTED',\n /** Token exchange failed for a reason that is not the credential. */\n TOKEN_EXCHANGE_FAILED: 'COPILOT_TOKEN_EXCHANGE_FAILED',\n /** The token-exchange response carried no readable `expires_at`, or was not JSON. */\n TOKEN_MALFORMED: 'COPILOT_TOKEN_MALFORMED',\n /** A `*.ghe.com` data-residency tenant has no token-exchange surface. */\n TENANT_UNSUPPORTED: 'COPILOT_TENANT_UNSUPPORTED',\n /** The endpoint rejected the request for missing `Editor_Headers`. */\n EDITOR_HEADERS_MISSING: 'COPILOT_EDITOR_HEADERS_MISSING',\n /** The target URL is not on the same origin as the configured issuer/base URL. */\n ENDPOINT_ORIGIN_INVALID: 'COPILOT_ENDPOINT_ORIGIN_INVALID',\n /** The response was a redirect; this SDK does not follow it. */\n REDIRECT_REJECTED: 'COPILOT_REDIRECT_REJECTED',\n /** Device flow: the user denied the request. */\n DEVICE_LOGIN_DENIED: 'COPILOT_DEVICE_LOGIN_DENIED',\n /** Device flow: the code expired server-side. */\n DEVICE_LOGIN_EXPIRED: 'COPILOT_DEVICE_LOGIN_EXPIRED',\n /** Device flow: the absolute 15-minute bound passed without approval. */\n DEVICE_LOGIN_TIMEOUT: 'COPILOT_DEVICE_LOGIN_TIMEOUT',\n /** Device flow: failed for any other reason. */\n DEVICE_LOGIN_FAILED: 'COPILOT_DEVICE_LOGIN_FAILED',\n /** A credential commit found a revision other than the expected one. */\n CREDENTIAL_REVISION_CONFLICT: 'COPILOT_CREDENTIAL_REVISION_CONFLICT',\n /** The `/models` response was the wrong shape at the structural level. */\n CATALOG_MALFORMED: 'COPILOT_CATALOG_MALFORMED',\n /** `endpointOverrides` pinned a model to an endpoint that does not exist. */\n ENDPOINT_OVERRIDE_INVALID: 'COPILOT_ENDPOINT_OVERRIDE_INVALID',\n} as const)\n\n/** One of the codes {@link COPILOT_ERROR_CODES} owns. */\nexport type CopilotErrorCode = (typeof COPILOT_ERROR_CODES)[keyof typeof COPILOT_ERROR_CODES]\n","/**\n * The Copilot error taxonomy: the codes this provider owns, the two error\n * classes that carry a machine-readable classification beside them, and the one\n * door through which every credential-path error is built.\n *\n * ## What is deliberately NOT here\n *\n * Three situations get an EXISTING code rather than a Copilot one, because a\n * second code for the same situation forces every consumer to write a second\n * branch for it:\n *\n * - **No credential at all** — `MISSING_CREDENTIAL_CODE` from `packages/core`,\n * with a message naming the login command (Requirement 13.4).\n * - **Abort** — the SDK's existing abort code, {@link MODEL_ERROR_CODES.ABORTED}\n * (Requirement 4.6). `CopilotDeviceLoginError` with `reason: 'aborted'` maps to\n * it rather than minting a Copilot abort code.\n * - **HTTP failures of the generation/embedding endpoints** — `MODEL_ERROR_CODES`\n * plus `HTTP_PROVIDER_ERROR_CODES`. In particular there is no\n * `COPILOT_RATE_LIMIT`: a 429 from Copilot is `RATE_LIMIT`, the same as from\n * every other provider (Requirements 13.5, 15.5).\n *\n * @module ai-agent-sdk/providers/copilot/errors\n */\n\nimport {\n AgentSdkError,\n MODEL_ERROR_CODES,\n ModelError,\n safeErrorRecord,\n type SafeErrorRecord,\n} from '@alvin0/ai-agent-sdk-core'\nimport { COPILOT_ERROR_CODES, type CopilotErrorCode } from './common/error-codes.ts'\n\n/**\n * The codes this module owns, defined in `./common/error-codes.ts` and re-exported\n * here.\n *\n * The definition sits one layer down only so `common/http.ts` can throw\n * `ENDPOINT_ORIGIN_INVALID` and `REDIRECT_REJECTED` without `common/` importing a\n * root module — `common/` is a leaf, and the root already imports it. This module\n * remains the door consumers read.\n */\nexport { COPILOT_ERROR_CODES, type CopilotErrorCode } from './common/error-codes.ts'\n\n/**\n * Mirrors `safeProviderFailure` from `provider-http`: a {@link ModelError} keeps\n * its stable code and status and LOSES its message, because a provider message\n * is the one field that can have echoed a request header back at us.\n * @param failure - the serializable twin carried by a {@link ModelError}.\n * @returns a frozen record with no provider-authored text in it.\n */\nfunction safeModelFailure(failure: ModelError['failure']): SafeErrorRecord {\n return Object.freeze({\n type: 'ModelError',\n message: 'provider attempt failed; inspect the stable code and request ID',\n code: failure.code,\n ...failure.status === undefined ? {} : { status: failure.status },\n })\n}\n\n/** A sanitized cause plus the message it is allowed to travel with. */\nexport interface CopilotCredentialFailure {\n /** SDK-authored text. Never carries a token value, because nothing interpolates one in. */\n readonly message: string\n /** The cause, reduced to serializable facts; `undefined` when there was none. */\n readonly cause: SafeErrorRecord | undefined\n}\n\n/**\n * Build the inputs for an error on the Copilot credential path.\n *\n * This is the ONLY door: {@link CopilotTokenExchangeError} and\n * {@link CopilotDeviceLoginError} take a {@link CopilotCredentialFailure} rather\n * than a raw `cause`, so there is no code path that can attach an unfiltered\n * value to a credential-path error. `message` is SDK-authored text; a\n * `GitHub_User_Token` or a `Copilot_Api_Token` is never interpolated into it, and\n * response bodies reach `cause` only after the bounded read has replaced every\n * occurrence of the tokens held in memory with `[REDACTED]` (Requirement 13.7).\n * @param message - SDK-authored, actionable text. No token values.\n * @param cause - the caught value, if any; filtered before it is retained.\n * @returns the sanitized pair an error class accepts.\n */\nexport function credentialFailure(message: string, cause?: unknown): CopilotCredentialFailure {\n return Object.freeze({\n message,\n cause: cause === undefined\n ? undefined\n // A ModelError already carries the serializable twin, so route it through\n // the same reduction the provider attempt ledger uses. Anything else keeps\n // only bounded facts, and never a stack.\n : cause instanceof ModelError ? safeModelFailure(cause.failure) : safeErrorRecord(cause),\n })\n}\n\n/** Whether a token-exchange failure can ever succeed on a retry. */\nexport type CopilotTokenExchangeFailureKind = 'permanent' | 'transient'\n\n/**\n * A `Copilot_Token_Exchange` that did not produce a token.\n *\n * `kind` exists because `code` alone does not answer the only question a caller\n * has to answer next: `TOKEN_EXCHANGE_FAILED` covers both a 5xx worth waiting out\n * and a 4xx that will fail identically forever.\n */\nexport class CopilotTokenExchangeError extends AgentSdkError {\n /** Retry classification for this failure. */\n readonly kind: CopilotTokenExchangeFailureKind\n\n /**\n * @param failure - message and filtered cause from {@link credentialFailure}.\n * @param code - the Copilot code for this row of the classification table.\n * @param kind - whether a retry could ever succeed.\n */\n constructor(\n failure: CopilotCredentialFailure,\n code: CopilotErrorCode,\n kind: CopilotTokenExchangeFailureKind,\n ) {\n super(failure.message, code, failure.cause === undefined ? undefined : { cause: failure.cause })\n this.kind = kind\n }\n}\n\n/** Why a device login ended without a token. */\nexport type CopilotDeviceLoginReason = 'denied' | 'expired' | 'timeout' | 'aborted' | 'failed'\n\n/**\n * Codes for the four device-flow outcomes this provider owns.\n *\n * `denied` and `expired` are separate on purpose: \"you just declined this\" and\n * \"the code ran out\" lead to different next steps. `aborted` is absent because it\n * maps to the SDK's existing abort code instead.\n */\nconst DEVICE_LOGIN_CODES = Object.freeze({\n denied: COPILOT_ERROR_CODES.DEVICE_LOGIN_DENIED,\n expired: COPILOT_ERROR_CODES.DEVICE_LOGIN_EXPIRED,\n timeout: COPILOT_ERROR_CODES.DEVICE_LOGIN_TIMEOUT,\n failed: COPILOT_ERROR_CODES.DEVICE_LOGIN_FAILED,\n} as const)\n\n/**\n * A device login that ended without a `GitHub_User_Token`.\n *\n * The `code` is derived from `reason` rather than passed in, so the two can never\n * disagree — a caller reading `code` and a caller reading `reason` always see the\n * same outcome.\n */\nexport class CopilotDeviceLoginError extends AgentSdkError {\n /** The distinguishable reason the flow ended. */\n readonly reason: CopilotDeviceLoginReason\n\n /**\n * @param failure - message and filtered cause from {@link credentialFailure}.\n * @param reason - the outcome; decides the `code`, with `aborted` mapping to\n * {@link MODEL_ERROR_CODES.ABORTED} rather than a Copilot-specific code.\n */\n constructor(failure: CopilotCredentialFailure, reason: CopilotDeviceLoginReason) {\n super(\n failure.message,\n reason === 'aborted' ? MODEL_ERROR_CODES.ABORTED : DEVICE_LOGIN_CODES[reason],\n failure.cause === undefined ? undefined : { cause: failure.cause },\n )\n this.reason = reason\n }\n}\n","/**\n * The Universal half of `Copilot_Auth`: the in-memory store doubles, the\n * credential snapshot an operation carries, the one function that turns \"no\n * credential\" into an actionable error, and the pure predicate that decides\n * whether a token exchange is due.\n *\n * Storage is injected. Paths, the filesystem, and the environment belong to the\n * Node auth package, never this Universal one (Requirement 6.1).\n *\n * ## Two tiers, one of which lives here\n *\n * The long-lived `GitHub_User_Token` is what a store persists; the short-lived\n * `Copilot_Api_Token` obtained from it never reaches a store and lives only in\n * the process cache (Requirements 3.1, 3.3). Exchanging does not consume the\n * long-lived token, so the persisted value is left exactly as it was\n * (Requirement 3.4) — this module has no write path at all for that reason.\n *\n * @module ai-agent-sdk/providers/copilot/auth\n */\n\nimport { AgentSdkError, MISSING_CREDENTIAL_CODE } from '@alvin0/ai-agent-sdk-core'\nimport { defineCredentialStore } from '@alvin0/ai-agent-sdk-core/provider'\nimport { COPILOT_ERROR_CODES } from './errors.ts'\nimport type {\n CopilotAuthFile,\n CopilotAuthStore,\n CopilotCredentialStore,\n CopilotGitHubToken,\n} from './common/store-types.ts'\n\nexport type {\n CopilotAccountIdentity,\n CopilotAuthFile,\n CopilotAuthStore,\n CopilotCredentialStore,\n CopilotGitHubToken,\n} from './common/store-types.ts'\n\n/**\n * The command that produces a credential, named in every message that tells a\n * caller how to fix a credential problem.\n *\n * Exported so the token-exchange path names the SAME command: a 401 and a 403\n * there both end in \"sign in again\", and two copies of that string are two\n * strings that can drift apart.\n */\nexport const COPILOT_LOGIN_COMMAND = 'npm run provider:copilot:login-device'\n\n/**\n * An in-memory {@link CopilotAuthStore} — the read/write variant (Requirement 6.4).\n *\n * The read/write variant exists for symmetry with Codex; normal runtime\n * composition uses {@link memoryCopilotCredentialStore}, the compare-and-swap\n * variant.\n * @param initial - the file the store starts with, or nothing for an empty store.\n * @returns a store backed by a single mutable slot.\n */\nexport function memoryCopilotAuthStore(initial?: CopilotAuthFile): CopilotAuthStore {\n let current = initial\n return {\n location: '<memory>',\n read: () => Promise.resolve(current),\n write: (file) => {\n current = file\n return Promise.resolve()\n },\n }\n}\n\n/**\n * An in-memory compare-and-swap store, for deterministic runtime and tests\n * (Requirements 6.2, 6.4).\n *\n * Values are `structuredClone`d in BOTH directions, which is the point of this\n * double: a caller that mutates the object it wrote, or the object it read, must\n * not be able to change what the store holds. Without the clone a test could pass\n * for the wrong reason — the store and the caller sharing one object rather than\n * the store having committed anything.\n *\n * A commit whose `expectedRevision` disagrees with the current revision raises\n * {@link COPILOT_ERROR_CODES.CREDENTIAL_REVISION_CONFLICT} (Requirement 6.3), the\n * Copilot-owned code, so exactly one of two concurrent commits wins and the loser\n * can tell why it lost.\n * @param initial - the file the store starts with, or nothing for an empty store.\n * @returns a compare-and-swap store over a single mutable slot.\n */\nexport function memoryCopilotCredentialStore(initial?: CopilotAuthFile): CopilotCredentialStore {\n let current = initial === undefined ? undefined : structuredClone(initial)\n let revision = 0\n return defineCredentialStore<CopilotAuthFile>({\n id: 'copilot-memory-credentials',\n label: '<memory>',\n async read({ signal }) {\n signal.throwIfAborted()\n return current === undefined\n ? undefined\n : { value: structuredClone(current), revision: String(revision) }\n },\n async commit(input, { signal }) {\n signal.throwIfAborted()\n const expected = current === undefined ? null : String(revision)\n if (input.expectedRevision !== expected) {\n throw new AgentSdkError(\n 'Copilot credential revision changed before commit',\n COPILOT_ERROR_CODES.CREDENTIAL_REVISION_CONFLICT,\n )\n }\n current = structuredClone(input.value)\n revision++\n return { revision: String(revision) }\n },\n })\n}\n\n/**\n * One read of the credential store, carried through a single operation.\n *\n * `revision` travels with `file` rather than being re-read later, because the\n * token cache keys on the pair: a file that changed under us has a different\n * revision even when the token value happens to be identical.\n */\nexport interface CopilotCredentialSnapshot {\n readonly file: CopilotAuthFile\n /** The revision at read time, or `null` for a store with no revisions. */\n readonly revision: string | null\n /** The store's human-readable location, named in diagnostics. */\n readonly label: string\n}\n\n/**\n * Require a usable `GitHub_User_Token`, with a message that says how to get one.\n *\n * Three shapes of \"there is no credential\" — an empty store, a file with no\n * `github` field, and a `github.token` that is the empty string — collapse into\n * the SAME code, because to the person reading the error they are one problem\n * with one fix. That code is the SDK's own {@link MISSING_CREDENTIAL_CODE} rather\n * than a Copilot-specific one, so a consumer does not have to write a second\n * branch for a situation it already handles, and the message carries the command\n * to run `Copilot_Login_Cli` (Requirement 13.4).\n *\n * The token value is never interpolated into the message; only the store's label\n * is (Requirement 13.7).\n * @param file - the credential file, or `undefined` when the store was empty.\n * @param label - the store location named in the diagnostic.\n * @returns the long-lived GitHub token.\n */\nexport function requireGitHubToken(\n file: CopilotAuthFile | undefined,\n label: string,\n): CopilotGitHubToken {\n const github = file?.github\n if (github === undefined || github === null\n || typeof github.token !== 'string' || github.token.length === 0) {\n throw new AgentSdkError(\n `no GitHub Copilot credentials at ${label}; run \\`${COPILOT_LOGIN_COMMAND}\\` to sign in`,\n MISSING_CREDENTIAL_CODE,\n )\n }\n return github\n}\n\n/** Exchange this long before the `Copilot_Api_Token` actually expires. */\nexport const COPILOT_TOKEN_EXCHANGE_MARGIN_MS = 5 * 60 * 1_000\n\n/**\n * The part of a `Copilot_Api_Token` that {@link shouldExchange} reads.\n *\n * Declared here rather than imported so this module owns no dependency on the\n * exchange module: expiry arithmetic is the whole of what the decision needs, and\n * `CopilotApiToken` — which carries the token value and the declared endpoint\n * besides — satisfies this shape structurally, so `shouldExchange` accepts one\n * with no conversion and there is only ever one declaration of the full type.\n */\nexport interface CopilotTokenExpiry {\n /** Expiry instant in epoch MILLISECONDS, derived from the endpoint's `expires_at`. */\n readonly expiresAtMs: number\n /** The endpoint's `refresh_in` hint, in seconds, when it sent one. Advisory. */\n readonly refreshInSeconds?: number\n}\n\n/**\n * Whether a token exchange has to happen before the next request.\n *\n * A pure function of three values that reads no global clock, so a property test\n * can place `now` at every boundary without a fake timer (Requirements 5.2, 5.3).\n * `undefined` — no token yet — is always `true`.\n *\n * `expires_at` is the authority and `refresh_in` is advisory: the hint may only\n * SHORTEN the refresh moment, never lengthen it. The endpoint is allowed to ask\n * for an earlier exchange; it is not allowed to ask this SDK to hold a token past\n * the expiry it announced itself.\n *\n * There is deliberately no fallback branch. `shouldRefresh` in\n * `provider-codex/src/auth.ts` decodes a JWT for `exp` and falls back to a\n * `last_refresh` age when it cannot; a `Copilot_Api_Token` is not a JWT this SDK\n * has any business reading, and the expiry is stated outright in the exchange\n * response body. With no second source, a fallback would have to invent a\n * lifetime, and an invented lifetime violates the no-inference principle.\n *\n * Because the decision is made BEFORE dispatch, a 401 from the Copilot base URL\n * always means the credential is genuinely dead rather than \"the token expired\n * mid-flight\" — which is what lets auth failures stay non-retryable\n * (Requirement 5.8).\n * @param api - the cached token, or `undefined` when there is none.\n * @param now - current time in epoch milliseconds.\n * @param marginMs - exchange this long before expiry.\n * @returns true when an exchange is due.\n */\nexport function shouldExchange(\n api: CopilotTokenExpiry | undefined,\n now: number,\n marginMs = COPILOT_TOKEN_EXCHANGE_MARGIN_MS,\n): boolean {\n if (api === undefined) return true\n const advisory = api.refreshInSeconds === undefined\n ? Number.POSITIVE_INFINITY\n : api.expiresAtMs - api.refreshInSeconds * 1_000\n return Math.min(api.expiresAtMs - marginMs, advisory) <= now\n}\n","/**\n * The two Copilot endpoint constants and the editor-header override type, in the\n * leaf layer so every module that has to speak to the Copilot surface can reach\n * them.\n *\n * They BELONG to `../adapter.ts` — that module is the public door, documents the\n * client-identity tradeoff, and re-exports everything here. The values live one\n * layer down for the same structural reason `COPILOT_ERROR_CODES` does:\n * `../catalog.ts` needs {@link COPILOT_BASE_URL} and `../exchange.ts` needs the\n * two editor headers, while `../adapter.ts` builds the catalog reader and the\n * token cache. Declaring the constants in `../adapter.ts` would make that edge\n * run both ways, which the repo's circular-dependency check forbids. Copying the\n * strings instead would be worse: a client identity that exists in two places is\n * a client identity that can disagree with itself.\n *\n * Read `../adapter.ts` for what these values mean, why they are overridable\n * options rather than hidden constants, and what is still outstanding on each.\n *\n * @module ai-agent-sdk/providers/copilot/identity\n */\n\n/** The Copilot API base. Documented on the re-export in `../adapter.ts`. */\nexport const COPILOT_BASE_URL = 'https://api.githubcopilot.com'\n\n/** Default `Editor-Version`. Documented on the re-export in `../adapter.ts`. */\nexport const COPILOT_EDITOR_VERSION = 'vscode/1.99.0'\n\n/** Default `Editor-Plugin-Version`. Documented on the re-export in `../adapter.ts`. */\nexport const COPILOT_EDITOR_PLUGIN_VERSION = 'copilot-chat/0.26.0'\n\n/**\n * Overrides for the two editor headers.\n *\n * Each field is independent: leaving one undefined keeps that header's exported\n * default rather than dropping the header, because a dropped header is an HTTP\n * 400 rather than a lenient request.\n */\nexport interface CopilotEditorHeaders {\n /** Overrides {@link COPILOT_EDITOR_VERSION}. */\n readonly editorVersion?: string\n /** Overrides {@link COPILOT_EDITOR_PLUGIN_VERSION}. */\n readonly editorPluginVersion?: string\n}\n\n/** Both editor headers resolved, with every field present. */\nexport interface ResolvedCopilotEditorHeaders {\n /** The value sent as `editor-version`. */\n readonly editorVersion: string\n /** The value sent as `editor-plugin-version`. */\n readonly editorPluginVersion: string\n}\n\n/**\n * Resolve each editor header from the override first and the exported default\n * second.\n *\n * Per field rather than per object: an override of one header leaves the other at\n * its default instead of dropping it, because a dropped editor header is an HTTP\n * 400 (Requirement 2.4).\n * @param headers - the caller's overrides, when they set any.\n * @returns both header values, neither of them empty.\n */\nexport function resolveCopilotEditorHeaders(\n headers: CopilotEditorHeaders | undefined,\n): ResolvedCopilotEditorHeaders {\n return Object.freeze({\n editorVersion: headers?.editorVersion ?? COPILOT_EDITOR_VERSION,\n editorPluginVersion: headers?.editorPluginVersion ?? COPILOT_EDITOR_PLUGIN_VERSION,\n })\n}\n","/**\n * The redirect guard every Copilot HTTP call passes through.\n *\n * `redirect: 'manual'` on the request is only half of a no-follow policy: it\n * stops the runtime from following a hop, but it does not stop the CALLER from\n * treating the result as a normal response. This module is the other half — it\n * turns every shape a redirect can take into a structured error before a second\n * request can be dispatched.\n *\n * There are four shapes, and a check for only one of them is a hole:\n *\n * - **A 3xx status** — the ordinary case, visible because `redirect: 'manual'`\n * surfaces the response instead of following it.\n * - **`type === 'opaqueredirect'`** — what a browser returns instead of the 3xx,\n * with the status flattened to `0` and the headers stripped. A status-only\n * check misses this entirely.\n * - **`redirected === true`** — a hop that was already followed, by a runtime or\n * an intermediary that ignored `redirect: 'manual'`.\n * - **`response.url` differing from the requested URL** — the last resort, for a\n * runtime that reports neither of the flags above but still moved the request.\n *\n * The body is RELEASED before the error is thrown. A rejected response whose body\n * is never cancelled holds a socket open for as long as the runtime keeps the\n * stream alive, so the guard cannot leave that to the caller's `finally`.\n *\n * @module ai-agent-sdk/providers/copilot/no-follow\n */\n\nimport { AgentSdkError, waitForSettlement } from '@alvin0/ai-agent-sdk-core'\nimport { COPILOT_ERROR_CODES } from './error-codes.ts'\n\n/**\n * The Copilot HTTP call sites, named so an error says which one refused the hop.\n *\n * All seven are listed because the redirect guard covers all seven: the two\n * device-flow legs, the token exchange, the catalog, both generation endpoints\n * and the embedding endpoint (Requirements 3.8, 7.8). A call site that is not on\n * this list has no name to report, which is the point — adding an endpoint means\n * naming it here, and naming it here means it went through {@link\n * rejectCopilotRedirect}.\n */\nexport type CopilotHttpOperation =\n | 'device code'\n | 'device token'\n | 'token exchange'\n | 'model catalog'\n | 'responses'\n | 'chat completions'\n | 'embeddings'\n\n/**\n * Reject every redirect shape Web fetch exposes, before any second request.\n *\n * @param response - the response as returned by a `redirect: 'manual'` fetch.\n * @param requestedUrl - the absolute URL that was requested, for the\n * `response.url` comparison.\n * @param operation - which Copilot call site is refusing the hop.\n * @param teardownTimeoutMs - bound on the body cancellation, so a stream that\n * never settles cannot hold the rejection open forever.\n * @returns nothing when the response is not a redirect in any of its four shapes.\n * @throws AgentSdkError with `COPILOT_REDIRECT_REJECTED` when it is.\n */\nexport async function rejectCopilotRedirect(\n response: Response,\n requestedUrl: string,\n operation: CopilotHttpOperation,\n teardownTimeoutMs: number,\n): Promise<void> {\n const redirectStatus = response.status >= 300 && response.status < 400\n const responseUrlChanged = response.url.length > 0 && response.url !== requestedUrl\n if (response.type !== 'opaqueredirect' && response.redirected !== true\n && !redirectStatus && !responseUrlChanged) return\n if (response.body !== null) {\n await waitForSettlement(response.body.cancel().catch(() => undefined), teardownTimeoutMs)\n }\n throw new AgentSdkError(\n `Copilot ${operation} rejected a redirect before following it`,\n COPILOT_ERROR_CODES.REDIRECT_REJECTED,\n )\n}\n","/**\n * The one HTTP door for every Copilot call: origin pinning, no-follow, bounded\n * reads, and a caller signal that wins immediately.\n *\n * This is the counterpart of `oauthFetch` in `provider-codex/src/oauth.ts`, kept\n * deliberately close to it — the same guarantees, in the same order, so a reader\n * who knows one knows the other. What differs is scope: Codex has one auth\n * issuer, Copilot has THREE origins, and each is its own option.\n *\n * ## Three origins, three independent pins\n *\n * `oauthIssuer` (`https://github.com`), `githubApiBaseUrl`\n * (`https://api.github.com`) and `baseUrl` (`https://api.githubcopilot.com`) are\n * three separate options, pinned separately by three separate calls to {@link\n * issuerOf}. No module may dispatch a request to an origin other than its own\n * pinned one — the device flow cannot reach the Copilot surface, the Copilot\n * surface cannot reach the token exchange. That is why {@link copilotFetch}\n * demands a {@link CopilotOrigin} rather than reading an origin off a shared\n * options bag: there is no options bag that holds all three, so there is no way\n * to pass the wrong one by forgetting which field applies.\n *\n * The pin is compared BEFORE the request is dispatched (Requirement 3.7). A\n * post-hoc check on the response would already have leaked the `Authorization`\n * header to whatever origin the URL named.\n *\n * ## What is bounded, and why each bound exists\n *\n * - **`redirect: 'manual'` plus {@link rejectCopilotRedirect}** — a followed hop\n * re-sends the credential headers to the redirect target (Requirements 3.8, 7.8).\n * - **A per-request deadline** — a server that accepts the connection and then\n * says nothing must not hang a CLI.\n * - **Bytes AND chunk count on every read** — bytes alone still lets a stream of\n * one-byte chunks pin the event loop, so both are checked (Requirements 4.7, 13.6).\n * - **{@link raceAbort}** — `fetch` honours a signal, but a pending read does not\n * necessarily reject the instant it aborts. Racing makes the caller's signal win\n * immediately rather than eventually (Requirement 4.6).\n * - **{@link positiveSafeInteger} on every configured limit** — a `0`, a `NaN` or\n * a float silently disables a bound, which is worse than rejecting the config.\n *\n * @module ai-agent-sdk/providers/copilot/http\n */\n\nimport { AgentSdkError, MODEL_ERROR_CODES, waitForSettlement } from '@alvin0/ai-agent-sdk-core'\nimport { COPILOT_ERROR_CODES } from './error-codes.ts'\nimport { rejectCopilotRedirect, type CopilotHttpOperation } from './no-follow.ts'\n\n/** Deadline for one Copilot HTTP request when the caller configures none. */\nexport const COPILOT_DEFAULT_REQUEST_TIMEOUT_MS = 30_000\n\n/** Maximum response bytes retained or parsed when the caller configures none. */\nexport const COPILOT_DEFAULT_MAX_RESPONSE_BYTES = 1024 * 1024\n\n/** Maximum response chunks accepted when the caller configures none. */\nexport const COPILOT_DEFAULT_MAX_RESPONSE_CHUNKS = 10_000\n\n/** Bound on releasing a body that is being discarded. Never a caller-visible wait. */\nconst TEARDOWN_TIMEOUT_MS = 30_000\n\n/**\n * Which of the three configurable origins a pin came from.\n *\n * The field name travels with the pin so an origin error can name the option the\n * caller has to fix, rather than saying \"origin invalid\" about one of three\n * settings.\n */\nexport type CopilotOriginField = 'oauthIssuer' | 'githubApiBaseUrl' | 'baseUrl'\n\n/** A validated, pinned origin: the only thing {@link copilotFetch} accepts as a target. */\nexport interface CopilotOrigin {\n /** The option this pin came from. */\n readonly field: CopilotOriginField\n /** Normalized base URL with trailing slashes removed. Safe to concatenate a path onto. */\n readonly href: string\n /** The serialized origin every request URL is compared against. */\n readonly origin: string\n}\n\n/** Shared HTTP settings. Every field is optional and every default is a bound, not \"unlimited\". */\nexport interface CopilotHttpOptions {\n /** Cancellation for the request and for the body read. */\n readonly signal?: AbortSignal\n /** HTTP implementation, for tests and non-browser runtimes. */\n readonly fetch?: typeof globalThis.fetch\n /** Deadline for one request. Defaults to {@link COPILOT_DEFAULT_REQUEST_TIMEOUT_MS}. */\n readonly requestTimeoutMs?: number\n /** Maximum response bytes. Defaults to {@link COPILOT_DEFAULT_MAX_RESPONSE_BYTES}. */\n readonly maxResponseBytes?: number\n /** Maximum response chunks. Defaults to {@link COPILOT_DEFAULT_MAX_RESPONSE_CHUNKS}. */\n readonly maxResponseChunks?: number\n /**\n * Permit an `http:` origin for a trusted local test endpoint. Defaults to false.\n *\n * A separate, explicitly enabled option rather than a lenient default, because\n * cleartext HTTP here carries a bearer token (Requirement 2.2).\n */\n readonly allowInsecureIssuer?: boolean\n}\n\n/** One request, carrying the origin it is pinned to and the call site it belongs to. */\nexport interface CopilotRequest {\n /** The pin from {@link issuerOf}. The URL must be on this origin. */\n readonly pinned: CopilotOrigin\n /** Absolute target URL; build it with {@link copilotUrl} to keep it on the pin. */\n readonly url: string | URL\n /** Which call site this is, for the redirect error. */\n readonly operation: CopilotHttpOperation\n /** Method, headers and body. `signal` and `redirect` are set by this module. */\n readonly init: RequestInit\n}\n\n/**\n * Validate and pin one of the three configurable origins.\n *\n * Two rejections, each for a concrete reason:\n *\n * - **Userinfo** (`https://user:pass@host`) — credentials in a URL would be sent\n * as an extra `Authorization` header the caller never wrote, and they end up in\n * logs. There is no legitimate use for them on any of these three origins.\n * - **`http:` without `allowInsecureIssuer`** — see the option's note.\n *\n * @param field - which option is being pinned; appears in the error message.\n * @param configured - the caller's value, or `undefined` to take the default.\n * @param fallback - the exported default for this field.\n * @param options - read for `allowInsecureIssuer` only.\n * @returns the pin to hand to {@link copilotFetch}.\n * @throws AgentSdkError with `COPILOT_ENDPOINT_ORIGIN_INVALID` when the value is\n * unparsable, carries userinfo, or is cleartext without the opt-in.\n */\nexport function issuerOf(\n field: CopilotOriginField,\n configured: string | undefined,\n fallback: string,\n options: Pick<CopilotHttpOptions, 'allowInsecureIssuer'> = {},\n): CopilotOrigin {\n const raw = configured ?? fallback\n let url: URL\n try {\n url = new URL(raw)\n } catch (error: unknown) {\n throw originError(`Copilot ${field} is not an absolute URL`, error)\n }\n if (url.username.length > 0 || url.password.length > 0) {\n throw originError(`Copilot ${field} must not contain credentials`)\n }\n if (url.protocol !== 'https:'\n && !(options.allowInsecureIssuer === true && url.protocol === 'http:')) {\n throw originError(`Copilot ${field} must use https unless allowInsecureIssuer is enabled`)\n }\n return Object.freeze({ field, href: url.href.replace(/\\/+$/, ''), origin: url.origin })\n}\n\n/**\n * Build an absolute URL on a pinned origin.\n *\n * The path has to be absolute-and-rooted: a relative path resolved against a base\n * is exactly how a URL quietly ends up somewhere else, and a path that is itself\n * absolute (`//evil.tld/x` or `https://evil.tld/x`) would replace the origin\n * outright.\n * @param pinned - the pin from {@link issuerOf}.\n * @param path - a path beginning with a single `/`.\n * @returns the absolute URL string, guaranteed to be on `pinned.origin`.\n * @throws AgentSdkError with `COPILOT_ENDPOINT_ORIGIN_INVALID` when the path could\n * move the request off the pin.\n */\nexport function copilotUrl(pinned: CopilotOrigin, path: string): string {\n if (!path.startsWith('/') || path.startsWith('//')) {\n throw originError(`Copilot ${pinned.field} path must start with a single '/'`)\n }\n const url = new URL(`${pinned.href}${path}`)\n if (url.origin !== pinned.origin) {\n throw originError(`Copilot ${pinned.field} path must stay on the pinned origin`)\n }\n return url.href\n}\n\n/**\n * Dispatch one Copilot request with the origin pin, no-follow and deadline applied.\n *\n * Order matters and is the contract: the pin is compared FIRST, so a URL on the\n * wrong origin never receives the headers; then the request goes out with\n * `redirect: 'manual'`; then the response passes the redirect guard before it is\n * handed back. The body is left unread — {@link readCopilotResponseText} is the\n * bounded reader for it.\n * @param request - the pin, the URL, the call site and the init.\n * @param options - signal, fetch implementation and limits.\n * @returns the response, already cleared by the redirect guard.\n * @throws AgentSdkError with `COPILOT_ENDPOINT_ORIGIN_INVALID` when the URL is off\n * the pin, or `COPILOT_REDIRECT_REJECTED` when the response was a redirect.\n */\nexport async function copilotFetch(\n request: CopilotRequest,\n options: CopilotHttpOptions = {},\n): Promise<Response> {\n const url = requestUrl(request)\n const timeoutMs = positiveSafeInteger(\n options.requestTimeoutMs ?? COPILOT_DEFAULT_REQUEST_TIMEOUT_MS,\n 'requestTimeoutMs',\n )\n const timeout = AbortSignal.timeout(timeoutMs)\n const signal = options.signal === undefined ? timeout : AbortSignal.any([options.signal, timeout])\n const fetchImpl = options.fetch ?? globalThis.fetch\n if (typeof fetchImpl !== 'function') throw new TypeError('Copilot HTTP requires fetch')\n const response = await raceAbort(Promise.resolve(fetchImpl(url, {\n ...request.init,\n signal,\n redirect: 'manual',\n })), signal)\n await rejectCopilotRedirect(response, url, request.operation, TEARDOWN_TIMEOUT_MS)\n return response\n}\n\n/**\n * Read a response body as text, bounded on bytes and on chunk count.\n *\n * A declared `content-length` over the limit is refused before a single chunk is\n * read; the running totals then catch a body that lies about its length or sends\n * none. Either way the reader is cancelled rather than abandoned.\n * @param response - a response already cleared by {@link copilotFetch}.\n * @param options - limits and the signal to race the read against.\n * @returns the decoded text, or `''` when there was no body.\n * @throws RangeError when a configured bound is exceeded.\n */\nexport async function readCopilotResponseText(\n response: Response,\n options: CopilotHttpOptions = {},\n): Promise<string> {\n const maxBytes = positiveSafeInteger(\n options.maxResponseBytes ?? COPILOT_DEFAULT_MAX_RESPONSE_BYTES,\n 'maxResponseBytes',\n )\n const maxChunks = positiveSafeInteger(\n options.maxResponseChunks ?? COPILOT_DEFAULT_MAX_RESPONSE_CHUNKS,\n 'maxResponseChunks',\n )\n const declared = Number(response.headers.get('content-length'))\n if (Number.isFinite(declared) && declared > maxBytes) {\n if (response.body !== null) {\n await waitForSettlement(response.body.cancel().catch(() => undefined), TEARDOWN_TIMEOUT_MS)\n }\n throw new RangeError(`Copilot HTTP response exceeds the ${maxBytes}-byte limit`)\n }\n if (response.body === null) return ''\n const timeout = AbortSignal.timeout(positiveSafeInteger(\n options.requestTimeoutMs ?? COPILOT_DEFAULT_REQUEST_TIMEOUT_MS,\n 'requestTimeoutMs',\n ))\n const signal = options.signal === undefined ? timeout : AbortSignal.any([options.signal, timeout])\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let bytes = 0\n let chunks = 0\n let result = ''\n try {\n while (true) {\n const next = await raceAbort(reader.read(), signal)\n if (next.done) return result + decoder.decode()\n if (next.value === undefined) continue\n chunks++\n bytes += next.value.byteLength\n if (chunks > maxChunks || bytes > maxBytes) {\n await waitForSettlement(reader.cancel().catch(() => undefined), TEARDOWN_TIMEOUT_MS)\n throw new RangeError('Copilot HTTP response exceeds its configured resource limit')\n }\n result += decoder.decode(next.value, { stream: true })\n }\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Settle as soon as either the pending work or the signal does.\n *\n * An already-aborted signal rejects synchronously rather than after one turn, so\n * a caller who aborts before the call never dispatches the request at all.\n * @param pending - the work to race.\n * @param signal - the signal that gets to win.\n * @returns the pending value, when it arrives first.\n */\nexport function raceAbort<T>(pending: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(abortReason(signal))\n return new Promise<T>((resolve, reject) => {\n const abort = () => { cleanup(); reject(abortReason(signal)) }\n const cleanup = () => signal.removeEventListener('abort', abort)\n signal.addEventListener('abort', abort, { once: true })\n void pending.then(\n value => { cleanup(); resolve(value) },\n error => { cleanup(); reject(error) },\n )\n })\n}\n\n/**\n * Accept a configured limit only when it can actually bound anything.\n *\n * `0`, a negative, a float and `NaN` all disable a bound silently, so each one is\n * rejected instead of normalized.\n * @param value - the configured number.\n * @param field - the option name, for the message.\n * @returns the value, unchanged.\n * @throws RangeError when the value cannot serve as a bound.\n */\nexport function positiveSafeInteger(value: number, field: string): number {\n if (!Number.isSafeInteger(value) || value < 1) {\n throw new RangeError(`Copilot HTTP ${field} must be a positive safe integer`)\n }\n return value\n}\n\n/**\n * Resolve the request URL and compare it against the pin, before anything is sent.\n *\n * Userinfo is rejected here as well as in {@link issuerOf}: `URL.origin` ignores\n * it, so a URL on the right origin can still carry credentials the caller never\n * intended to send.\n */\nfunction requestUrl(request: CopilotRequest): string {\n let url: URL\n try {\n url = new URL(request.url)\n } catch (error: unknown) {\n throw originError(`Copilot ${request.operation} target is not an absolute URL`, error)\n }\n if (url.username.length > 0 || url.password.length > 0) {\n throw originError(`Copilot ${request.operation} target must not contain credentials`)\n }\n if (url.origin !== request.pinned.origin) {\n throw originError(\n `Copilot ${request.operation} target origin '${url.origin}' is not the pinned `\n + `${request.pinned.field} origin '${request.pinned.origin}'`,\n )\n }\n return url.href\n}\n\nfunction originError(message: string, cause?: unknown): AgentSdkError {\n return new AgentSdkError(\n message,\n COPILOT_ERROR_CODES.ENDPOINT_ORIGIN_INVALID,\n cause === undefined ? undefined : { cause },\n )\n}\n\nfunction abortReason(signal: AbortSignal): unknown {\n return signal.reason ?? new AgentSdkError('Copilot HTTP request aborted', MODEL_ERROR_CODES.ABORTED)\n}\n","/**\n * `Copilot_Catalog`: read `GET /models`, then PARTITION what came back.\n *\n * Discovery is the right default for this surface (Requirement 8.1): which models\n * an account may call depends on its plan, on its organisation's policy, and on\n * the editor identity the request presents, so no hardcoded list is correct for\n * two accounts at once. Passing `models` explicitly skips discovery entirely\n * (Requirement 8.5) — that decision belongs to the adapter, which simply does not\n * call this module in that case.\n *\n * ## Two levels of wrongness, two different answers\n *\n * The defensive read runs in a fixed order, and the order IS the contract:\n *\n * ```text\n * 1. redirect (every shape) ⇒ COPILOT_REDIRECT_REJECTED\n * 2. declared content-length over the limit ⇒ RangeError, body cancelled\n * 3. accumulated bytes/chunks over the limit ⇒ RangeError, reader cancelled\n * 4. body is not JSON, root is not an object,\n * or `data` is not an array ⇒ COPILOT_CATALOG_MALFORMED\n * 5. entry count over maxCatalogModels ⇒ COPILOT_CATALOG_MALFORMED\n * 6. entry: id is not a non-empty string ⇒ omitted 'model-id-missing'\n * 7. entry: capabilities.type unrecognized ⇒ omitted 'capability-type-unrecognized'\n * ```\n *\n * Steps 4 and 5 are STRUCTURAL, and a structural mismatch is an error rather than\n * a starting point for a guess (Requirement 8.8): a model list inferred from a\n * body this SDK could not read is a list nobody can be held to. Steps 6 and 7 are\n * at ENTRY level, and there the entry is dropped while the rest of the catalog\n * survives — one unfamiliar entry must not kill every model that still works.\n *\n * Dropping rather than listing-with-a-flag is the same judgement in the other\n * direction (Requirements 9.4, 9.5): listing a model this SDK cannot dispatch is\n * worse than not listing it, because it shows up in a selector and then fails at\n * call time, far from the cause.\n *\n * ## Metadata is translated, never invented\n *\n * Every field of {@link ProviderCatalogModel} is filled only from a field the\n * endpoint actually supplied (Requirement 8.4). The trap is\n * `inputModalities`: with no vision signal at all the field is ABSENT, NOT\n * `['text']`. An explicit list without `image` is a NEGATIVE claim the registry\n * acts on — it projects images to text — so inventing `['text']` would silently\n * strip images from every request to a model that may well accept them. Absent\n * means unknown, and unknown is what the endpoint said.\n *\n * `declaredEndpoint` follows the same rule and stays `undefined` when the catalog\n * discloses nothing. `undefined` is NOT \"not supported\"; the router treats the two\n * states differently (Requirement 8.6).\n *\n * ## The catalog is advisory\n *\n * `omitted` does not fail anything. A dispatched request to an omitted id still\n * goes out — it just takes the router's default branch — and a real error from the\n * endpoint remains the final word whenever metadata and behaviour disagree\n * (Requirement 8.6).\n *\n * @module ai-agent-sdk/providers/copilot/catalog\n */\n\nimport { AgentSdkError } from '@alvin0/ai-agent-sdk-core'\nimport type { ModelModality } from '@alvin0/ai-agent-sdk-core/provider'\nimport type {\n ProviderCatalogModel,\n RuntimeModelDiscoveryContext,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport { COPILOT_BASE_URL } from './common/identity.ts'\nimport { COPILOT_ERROR_CODES } from './errors.ts'\nimport {\n copilotFetch,\n copilotUrl,\n issuerOf,\n positiveSafeInteger,\n readCopilotResponseText,\n} from './common/http.ts'\n\n/** Path of the catalog surface, relative to the pinned Copilot base URL. */\nexport const COPILOT_CATALOG_PATH = '/models'\n\n/** Maximum raw catalog bytes when the caller configures none. */\nexport const COPILOT_DEFAULT_MAX_CATALOG_BYTES = 4 * 1024 * 1024\n\n/** Maximum catalog entries accepted when the caller configures none. */\nexport const COPILOT_DEFAULT_MAX_CATALOG_MODELS = 2_048\n\n/** Maximum catalog response chunks accepted when the caller configures none. */\nexport const COPILOT_DEFAULT_MAX_CATALOG_CHUNKS = 10_000\n\n/** Catalog request deadline when the caller configures none. */\nexport const COPILOT_DEFAULT_CATALOG_TIMEOUT_MS = 30_000\n\n/**\n * Which endpoint a generation model is dispatched to.\n *\n * Declared HERE rather than in `./router.ts`, where the router's own types live,\n * for one structural reason: `./router.ts` imports {@link CopilotGenerationModel}\n * from this module, so the dependency edge already runs router → catalog. Putting\n * the endpoint union in the router would make it run both ways, which the repo's\n * source-ownership check forbids and which nothing here needs. `./router.ts`\n * re-exports this type, so the router remains the module a reader goes to for\n * endpoint selection.\n */\nexport type CopilotEndpoint = 'responses' | 'chat-completions'\n\n/**\n * One entry of `GET /models`, typed as UNKNOWN at every leaf on purpose.\n *\n * `unknown` rather than the shape the endpoint documents, because this is the one\n * place a response that changed shape arrives: a declared `string` would let a\n * number flow into a catalog field and fail somewhere else entirely.\n */\ninterface WireCopilotModel {\n readonly id?: unknown\n readonly name?: unknown\n readonly capabilities?: {\n readonly type?: unknown\n readonly family?: unknown\n readonly limits?: {\n readonly max_context_window_tokens?: unknown\n readonly max_output_tokens?: unknown\n readonly max_inputs?: unknown\n }\n readonly supports?: Readonly<Record<string, unknown>>\n }\n readonly vision?: unknown\n readonly model_picker_enabled?: unknown\n}\n\n/** Why an entry was left out of both catalogs. */\nexport type CopilotOmitReason =\n /** capabilities.type is not one of the recognized values. */\n | 'capability-type-unrecognized'\n /** No usable id. */\n | 'model-id-missing'\n\n/** One entry that was dropped, with the reason an operator needs to see it. */\nexport interface CopilotOmittedModel {\n /** The entry's id, or `''` when it had none — the reason says which. */\n readonly id: string\n /** Why it was dropped. */\n readonly reason: CopilotOmitReason\n}\n\n/** A generation model, plus whatever the catalog disclosed about its endpoint. */\nexport interface CopilotGenerationModel {\n /** The SDK catalog model, handed to `provider-http` unchanged. */\n readonly model: ProviderCatalogModel\n /**\n * The endpoint the catalog disclosed, when it disclosed one.\n *\n * `undefined` means UNKNOWN, not \"not supported\". The router handles those two\n * states differently (Requirement 8.6).\n */\n readonly declaredEndpoint: CopilotEndpoint | undefined\n}\n\n/**\n * An embedding model, carrying only the facts the catalog stated.\n *\n * Deliberately NOT a {@link ProviderCatalogModel}: an embedding model has no\n * context window or output cap to report, and `Copilot_Embedding_Adapter` needs\n * different facts (batch ceiling, whether a requested dimension count is\n * honoured). Every field but `id` is optional because every one of them is absent\n * from some real entry.\n */\nexport interface CopilotEmbeddingModel {\n /** Wire model id, passed to the endpoint verbatim. */\n readonly id: string\n /** Display label, when the catalog supplied one. */\n readonly name?: string\n /** Model family, when disclosed; embedding compatibility identity is derived from it. */\n readonly family?: string\n /** Token ceiling for one input, from `limits.max_context_window_tokens`. */\n readonly maxInputTokens?: number\n /** Ceiling on inputs per request, from `limits.max_inputs`. */\n readonly maxInputs?: number\n /** Whether `supports.dimensions` was stated, and what it said. */\n readonly supportsDimensions?: boolean\n}\n\n/** The result of one discovery, partitioned. */\nexport interface CopilotCatalogSnapshot {\n /** Models usable for generation, each with its preliminary endpoint disclosure. */\n readonly generation: readonly CopilotGenerationModel[]\n /** Models usable for embedding. */\n readonly embedding: readonly CopilotEmbeddingModel[]\n /** Dropped entries with their reasons — these go to observation, not to a catalog. */\n readonly omitted: readonly CopilotOmittedModel[]\n}\n\n/** Resolved bounds for one catalog read. Every field is a bound, never \"unlimited\". */\nexport interface CopilotCatalogLimits {\n /** Maximum raw response bytes. */\n readonly maxBytes: number\n /** Maximum entries accepted before the response is called malformed. */\n readonly maxModels: number\n /** Maximum response chunks. */\n readonly maxChunks: number\n /** Deadline for the catalog request AND its body read. */\n readonly timeoutMs: number\n /** Permit an `http:` base URL for a trusted local test endpoint. */\n readonly allowInsecureHttp?: boolean\n}\n\n/**\n * The caller-facing catalog options, in the spelling `CopilotProviderOptions` uses.\n *\n * Split into two groups on purpose. The four `max*`/`timeout` values bound ONE\n * read and are resolved here by {@link resolveCopilotCatalogLimits}. The three\n * cache values (TTL, stale TTL, failure backoff) bound how often reads happen at\n * all, and `provider-http` already owns that policy — {@link\n * copilotCatalogCacheOptions} forwards them without a default, so an unset option\n * keeps the runtime's own default instead of this package pinning a second one\n * (Requirement 8.7).\n */\nexport interface CopilotCatalogOptions {\n /** Maximum raw catalog bytes. Defaults to {@link COPILOT_DEFAULT_MAX_CATALOG_BYTES}. */\n readonly maxCatalogBytes?: number\n /** Maximum catalog entries. Defaults to {@link COPILOT_DEFAULT_MAX_CATALOG_MODELS}. */\n readonly maxCatalogModels?: number\n /** Maximum catalog response chunks. Defaults to {@link COPILOT_DEFAULT_MAX_CATALOG_CHUNKS}. */\n readonly maxCatalogChunks?: number\n /** Catalog request deadline. Defaults to {@link COPILOT_DEFAULT_CATALOG_TIMEOUT_MS}. */\n readonly catalogTimeoutMs?: number\n /** How long a discovered catalog stays fresh. */\n readonly catalogTtlMs?: number\n /** How long a stale catalog may still be served while a refresh is attempted. */\n readonly catalogStaleTtlMs?: number\n /** How long to wait before retrying discovery after it failed. */\n readonly catalogFailureBackoffMs?: number\n /** Permit an `http:` base URL for a trusted local test endpoint. */\n readonly allowInsecureHttp?: boolean\n}\n\n/**\n * Resolve the per-read bounds, rejecting a value that cannot bound anything.\n *\n * Validation happens here rather than at the read, so a `0` or a `NaN` in the\n * configuration is a construction-time error instead of a silently disabled limit\n * discovered under load (Requirement 8.2).\n * @param options - the caller's catalog options.\n * @returns the four resolved bounds plus the insecure-HTTP opt-in.\n * @throws RangeError when a configured bound is not a positive safe integer.\n */\nexport function resolveCopilotCatalogLimits(\n options: CopilotCatalogOptions = {},\n): CopilotCatalogLimits {\n return Object.freeze({\n maxBytes: positiveSafeInteger(\n options.maxCatalogBytes ?? COPILOT_DEFAULT_MAX_CATALOG_BYTES,\n 'maxCatalogBytes',\n ),\n maxModels: positiveSafeInteger(\n options.maxCatalogModels ?? COPILOT_DEFAULT_MAX_CATALOG_MODELS,\n 'maxCatalogModels',\n ),\n maxChunks: positiveSafeInteger(\n options.maxCatalogChunks ?? COPILOT_DEFAULT_MAX_CATALOG_CHUNKS,\n 'maxCatalogChunks',\n ),\n timeoutMs: positiveSafeInteger(\n options.catalogTimeoutMs ?? COPILOT_DEFAULT_CATALOG_TIMEOUT_MS,\n 'catalogTimeoutMs',\n ),\n ...(options.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureHttp: options.allowInsecureHttp }),\n })\n}\n\n/**\n * Forward the three cache-policy options, and only the ones that were set.\n *\n * A conditional spread rather than defaults: `provider-http` owns catalog caching,\n * and a default written here would override the runtime's own without anyone\n * asking for it (Requirement 8.7).\n * @param options - the caller's catalog options.\n * @returns an object carrying only the cache options the caller supplied.\n */\nexport function copilotCatalogCacheOptions(options: CopilotCatalogOptions = {}): {\n readonly catalogTtlMs?: number\n readonly catalogStaleTtlMs?: number\n readonly catalogFailureBackoffMs?: number\n} {\n return {\n ...(options.catalogTtlMs === undefined ? {} : { catalogTtlMs: options.catalogTtlMs }),\n ...(options.catalogStaleTtlMs === undefined\n ? {}\n : { catalogStaleTtlMs: options.catalogStaleTtlMs }),\n ...(options.catalogFailureBackoffMs === undefined\n ? {}\n : { catalogFailureBackoffMs: options.catalogFailureBackoffMs }),\n }\n}\n\n/**\n * Read `GET {baseUrl}/models` and partition it.\n *\n * The base URL is re-pinned here from `context.baseUrl` rather than trusted as a\n * string: the catalog is the first Copilot call an adapter makes, and a pin\n * compared before dispatch is the only check that runs before the resolved\n * `Authorization` header leaves the process.\n * @param context - the discovery context `provider-http` supplies: base URL,\n * already-resolved headers, and the operation's signal.\n * @param limits - bounds from {@link resolveCopilotCatalogLimits}.\n * @param fetchImpl - HTTP implementation, injected for tests and non-browser runtimes.\n * @returns the partitioned snapshot; an empty one when the endpoint answered a\n * non-2xx status, because a catalog that could not be fetched is advisory too.\n * @throws AgentSdkError with `COPILOT_REDIRECT_REJECTED` on any redirect shape, or\n * `COPILOT_CATALOG_MALFORMED` when the response is the wrong shape structurally.\n * @throws RangeError when the response exceeds a configured bound.\n */\nexport async function discoverCopilotModels(\n context: RuntimeModelDiscoveryContext,\n limits: CopilotCatalogLimits,\n fetchImpl: typeof globalThis.fetch,\n): Promise<CopilotCatalogSnapshot> {\n const pinned = issuerOf('baseUrl', context.baseUrl.href, COPILOT_BASE_URL, {\n ...(limits.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureIssuer: limits.allowInsecureHttp }),\n })\n const url = copilotUrl(pinned, COPILOT_CATALOG_PATH)\n const http = {\n signal: context.signal,\n fetch: fetchImpl,\n requestTimeoutMs: limits.timeoutMs,\n maxResponseBytes: limits.maxBytes,\n maxResponseChunks: limits.maxChunks,\n ...(limits.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureIssuer: limits.allowInsecureHttp }),\n }\n const response = await copilotFetch(\n { pinned, url, operation: 'model catalog', init: { method: 'GET', headers: context.headers } },\n http,\n )\n if (!response.ok) {\n if (response.body !== null) await response.body.cancel().catch(() => undefined)\n return EMPTY_SNAPSHOT\n }\n const text = await readCopilotResponseText(response, http)\n return partitionCopilotCatalog(parseCatalogBody(text), limits.maxModels)\n}\n\n/**\n * Partition an already-read catalog body.\n *\n * Exported separately from the fetch so the partition is testable — and readable —\n * as what it is: a pure function from a parsed body to three lists.\n * @param body - the parsed root object of the catalog response.\n * @param maxModels - entry-count ceiling; exceeding it is structural, not per-entry.\n * @returns the partitioned snapshot.\n * @throws AgentSdkError with `COPILOT_CATALOG_MALFORMED` when `data` is not an\n * array or holds more than `maxModels` entries.\n */\nexport function partitionCopilotCatalog(\n body: Record<string, unknown>,\n maxModels: number,\n): CopilotCatalogSnapshot {\n const data = body.data\n if (!Array.isArray(data)) {\n throw malformed('Copilot model catalog `data` must be an array')\n }\n if (data.length > maxModels) {\n throw malformed(`Copilot model catalog exceeds the ${maxModels}-model limit`)\n }\n const generation: CopilotGenerationModel[] = []\n const embedding: CopilotEmbeddingModel[] = []\n const omitted: CopilotOmittedModel[] = []\n for (const candidate of data as readonly unknown[]) {\n const entry: WireCopilotModel = isRecord(candidate) ? candidate as WireCopilotModel : {}\n const id = typeof entry.id === 'string' ? entry.id : ''\n if (id.length === 0) {\n omitted.push({ id, reason: 'model-id-missing' })\n continue\n }\n const type = entry.capabilities?.type\n if (type === 'chat') {\n generation.push(generationModel(id, entry))\n continue\n }\n if (type === 'embeddings') {\n embedding.push(embeddingModel(id, entry))\n continue\n }\n omitted.push({ id, reason: 'capability-type-unrecognized' })\n }\n return Object.freeze({\n generation: Object.freeze(generation),\n embedding: Object.freeze(embedding),\n omitted: Object.freeze(omitted),\n })\n}\n\n/** The snapshot returned when there is nothing to report, frozen and shared. */\nconst EMPTY_SNAPSHOT: CopilotCatalogSnapshot = Object.freeze({\n generation: Object.freeze([]),\n embedding: Object.freeze([]),\n omitted: Object.freeze([]),\n})\n\n/** Modalities claimed when — and only when — a vision signal was actually present. */\nconst TEXT_AND_IMAGE: readonly ModelModality[] = Object.freeze(['text', 'image'])\n\n/**\n * Translate one `type: 'chat'` entry, filling only what the endpoint supplied.\n *\n * `name` is not defaulted to `id`: a display label the endpoint did not send is a\n * label this layer would be inventing, and the layer that renders a selector\n * already falls back to the id.\n */\nfunction generationModel(id: string, entry: WireCopilotModel): CopilotGenerationModel {\n const limits = entry.capabilities?.limits\n const supports = entry.capabilities?.supports\n const vision = entry.vision === true || supports?.vision === true\n const contextWindow = positiveInteger(limits?.max_context_window_tokens)\n const maxTokens = positiveInteger(limits?.max_output_tokens)\n return Object.freeze({\n model: Object.freeze({\n id,\n ...(typeof entry.name === 'string' && entry.name.length > 0 ? { name: entry.name } : {}),\n ...(contextWindow === undefined ? {} : { contextWindow }),\n ...(maxTokens === undefined ? {} : { maxTokens }),\n // No vision signal ⇒ ABSENT. `['text']` would be a negative claim about\n // image input that the endpoint never made.\n ...(vision ? { inputModalities: TEXT_AND_IMAGE } : {}),\n }),\n declaredEndpoint: declaredEndpointOf(supports),\n })\n}\n\n/** Translate one `type: 'embeddings'` entry, under the same fill-only-what-was-said rule. */\nfunction embeddingModel(id: string, entry: WireCopilotModel): CopilotEmbeddingModel {\n const capabilities = entry.capabilities\n const limits = capabilities?.limits\n const maxInputTokens = positiveInteger(limits?.max_context_window_tokens)\n const maxInputs = positiveInteger(limits?.max_inputs)\n const dimensions = capabilities?.supports?.dimensions\n return Object.freeze({\n id,\n ...(typeof entry.name === 'string' && entry.name.length > 0 ? { name: entry.name } : {}),\n ...(typeof capabilities?.family === 'string' && capabilities.family.length > 0\n ? { family: capabilities.family }\n : {}),\n ...(maxInputTokens === undefined ? {} : { maxInputTokens }),\n ...(maxInputs === undefined ? {} : { maxInputs }),\n ...(typeof dimensions === 'boolean' ? { supportsDimensions: dimensions } : {}),\n })\n}\n\n/**\n * Read the endpoint disclosure, and only a disclosure.\n *\n * `supports.responses === true` says `/responses`; `false` says `/chat/completions`\n * — the endpoint stated something either way. Anything else, including the field\n * being absent or holding a non-boolean, is UNKNOWN and stays `undefined`, which\n * is a different state from \"not supported\" (Requirement 8.6).\n */\nfunction declaredEndpointOf(\n supports: Readonly<Record<string, unknown>> | undefined,\n): CopilotEndpoint | undefined {\n const responses = supports?.responses\n if (responses === true) return 'responses'\n if (responses === false) return 'chat-completions'\n return undefined\n}\n\n/**\n * Parse the catalog body, treating an unreadable body as structural.\n *\n * Both failures land on the same code because they are the same problem: the\n * response is not a catalog, and there is nothing here to guess a model list from\n * (Requirement 8.8).\n */\nfunction parseCatalogBody(text: string): Record<string, unknown> {\n let parsed: unknown\n try {\n parsed = JSON.parse(text)\n } catch (error: unknown) {\n throw malformed('Copilot model catalog is not valid JSON', error)\n }\n if (!isRecord(parsed)) {\n throw malformed('Copilot model catalog must be a JSON object')\n }\n return parsed\n}\n\n/** Accept a numeric metadata field only when it can serve as a capacity. */\nfunction positiveInteger(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : undefined\n}\n\n/** A JSON object, excluding arrays — `data` being at the root is not a catalog. */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction malformed(message: string, cause?: unknown): AgentSdkError {\n return new AgentSdkError(\n message,\n COPILOT_ERROR_CODES.CATALOG_MALFORMED,\n cause === undefined ? undefined : { cause },\n )\n}\n","/**\n * Store capture: decide which credential store variant the caller passed, and\n * take a snapshot of its identity and methods.\n *\n * The counterpart of `captureCodexStore`, and it holds the same two lines:\n *\n * - **No accessors.** Every property is read through\n * `Object.getOwnPropertyDescriptor`, and a descriptor without a `value` is\n * REJECTED rather than invoked. Telling the two variants apart must not run a\n * line of the caller's code, because a getter here would run during provider\n * construction, in an order the caller cannot see.\n * - **No I/O.** Methods are captured, not called. Nothing touches storage at\n * construction time; the first read happens when an operation asks for a\n * credential.\n *\n * Methods are invoked through `Reflect.apply` with the original object as the\n * receiver, so a store written against `this` keeps working after capture.\n *\n * @module ai-agent-sdk/providers/copilot/store-capture\n */\n\nimport {\n AgentSdkError,\n CREDENTIAL_CAPABILITY_API_VERSION,\n type CredentialCommitInput,\n type CredentialCommitResult,\n type CredentialOperationOptions,\n type CredentialRecord,\n} from '@alvin0/ai-agent-sdk-core/provider'\nimport type {\n CopilotAuthFile,\n CopilotAuthStore,\n CopilotCredentialStore,\n} from './store-types.ts'\n\n/** A captured store, tagged with the variant it came from. */\nexport type CapturedCopilotStore =\n | { readonly kind: 'legacy'; readonly label: string; readonly store: CopilotAuthStore }\n | { readonly kind: 'versioned'; readonly label: string; readonly store: CopilotCredentialStore }\n\n/** Capture store identity and methods without invoking accessors or doing storage I/O. */\nexport function captureCopilotStore(value: unknown): CapturedCopilotStore {\n try {\n if (value === null || typeof value !== 'object') throw new TypeError('store must be an object')\n const marker = dataValue(value, 'kind', false)\n if (marker === undefined) return captureLegacy(value)\n if (marker !== 'credential-store'\n || dataValue(value, 'apiVersion') !== CREDENTIAL_CAPABILITY_API_VERSION) {\n throw new TypeError('unsupported credential-store marker')\n }\n const id = boundedString(dataValue(value, 'id'), 128, 'credential store id')\n const label = boundedString(dataValue(value, 'label'), 256, 'credential store label')\n const read = capturedMethod<\n [CredentialOperationOptions], Promise<CredentialRecord<CopilotAuthFile> | undefined>\n >(value, 'read')\n const commit = capturedMethod<\n [CredentialCommitInput<CopilotAuthFile>, CredentialOperationOptions], Promise<CredentialCommitResult>\n >(value, 'commit')\n return Object.freeze({\n kind: 'versioned',\n label,\n store: Object.freeze({\n kind: 'credential-store',\n apiVersion: CREDENTIAL_CAPABILITY_API_VERSION,\n id,\n label,\n read,\n commit,\n }),\n })\n } catch (error) {\n throw new AgentSdkError(\n 'Copilot authStore credential store is invalid',\n 'CREDENTIAL_STORE_INVALID',\n { cause: error },\n )\n }\n}\n\nfunction captureLegacy(source: object): CapturedCopilotStore {\n const location = boundedString(dataValue(source, 'location'), 1_024, 'Copilot auth store location')\n const read = capturedMethod<[], Promise<CopilotAuthFile | undefined>>(source, 'read')\n const write = capturedMethod<[CopilotAuthFile], Promise<void>>(source, 'write')\n return Object.freeze({\n kind: 'legacy',\n label: location,\n store: Object.freeze({ location, read, write }),\n })\n}\n\nfunction capturedMethod<Args extends readonly unknown[], Result>(\n source: object,\n key: PropertyKey,\n): (...args: Args) => Result {\n const method = dataValue(source, key)\n if (typeof method !== 'function') throw new TypeError(`${String(key)} must be a function`)\n return (...args: Args) => Reflect.apply(method, source, args) as Result\n}\n\n/**\n * Read an own-or-inherited DATA property. An accessor anywhere on the prototype\n * chain is an error: reading it would run caller code during construction.\n */\nfunction dataValue(source: object, key: PropertyKey, required = true): unknown {\n let owner: object | null = source\n while (owner !== null) {\n const descriptor = Object.getOwnPropertyDescriptor(owner, key)\n if (descriptor !== undefined) {\n if (!('value' in descriptor)) throw new TypeError(`${String(key)} must not be an accessor`)\n return descriptor.value\n }\n owner = Object.getPrototypeOf(owner)\n }\n if (!required) return undefined\n throw new TypeError(`missing ${String(key)}`)\n}\n\nfunction boundedString(value: unknown, maxLength: number, label: string): string {\n if (typeof value !== 'string' || value.length === 0 || value.length > maxLength) {\n throw new TypeError(`${label} must be a bounded non-empty string`)\n }\n return value\n}\n","/**\n * `copilotDualProtocol`: one `RuntimeWireProtocol` that speaks two protocols,\n * choosing per model id.\n *\n * ## Why a composite protocol rather than two adapters\n *\n * `RuntimeHttpProviderOptions<Dialect>` takes exactly ONE `protocol` and one\n * `dialect`, while Requirement 9 asks for two protocols on the single `copilot`\n * route, chosen per model. Two adapters would mean two routes\n * (`ModelProviderRegistrar.registerAdapter` maps route → adapter), which is\n * precisely the configuration coupling Requirement 9 exists to remove; adding a\n * `resolveProtocol` hook to `provider-http` would change a public surface\n * (Requirement 18.4) and charge every other provider for a concept only Copilot\n * needs. So the multi-protocol concept lives HERE, behind a protocol object the\n * runtime already knows how to hold (DD-1).\n *\n * What makes it work: all three protocol methods receive a `ProtocolRequest`, and\n * `ProtocolRequest.model` is a `ResolvedModelInfo`. The routing key — `model.id` —\n * is therefore present at EVERY decision point (`endpointPath`, `serialize`,\n * `translate`), so no extra channel has to be threaded through the runtime.\n *\n * ## Two structural rules this module obeys\n *\n * 1. **No `this`.** `captureRuntimeProtocol` re-invokes each method as\n * `Reflect.apply(method, source, args)`, so a method that read `this` would\n * read whatever receiver the runtime happened to capture with. Every method\n * below is a closure over `router` / `responses` / `chat`, which makes\n * rebinding the receiver harmless.\n * 2. **The composite dialect is flat.** `defineWireProtocol` snapshots\n * `defaultDialect` through `snapshotJsonObject` under depth/node limits, and\n * `provider-http` merges caller overrides with a SHALLOW spread. A dialect\n * nesting the two sub-dialects would let a caller overriding one chat flag\n * silently drop every other chat default. {@link CopilotDialect} is primitives\n * plus one string array — depth 2 (DD-2).\n *\n * ## The one thing the composite cannot hide\n *\n * `provider-http` sees a single protocol id, `'copilot-dual'`. The HTTP layer's\n * generic observation reports that id, not `'openai-responses'` or\n * `'openai-chat-completions'`, and `context.startProviderAttempt` takes only an\n * `origin` — which both endpoints share. So Requirement 9.8 is served by\n * {@link CopilotDualProtocolOptions.onDecision}, a channel this package owns,\n * rather than by widening a core type (DD-3).\n *\n * @module ai-agent-sdk/providers/copilot/dual-protocol\n */\n\nimport type {\n ChatCompletionsDialect,\n} from '@alvin0/ai-agent-sdk-protocol-openai-chat-completions'\nimport type { ResponsesDialect } from '@alvin0/ai-agent-sdk-protocol-responses'\nimport {\n defineWireProtocol,\n type ProtocolRequest,\n type ProtocolSseEvent,\n type ProtocolStreamChunk,\n type RuntimeWireProtocol,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport type { CopilotEndpointDecision, CopilotEndpointRouter } from './router.ts'\n\n/** Protocol id the HTTP layer reports for every Copilot request. */\nexport const COPILOT_DUAL_PROTOCOL_ID = 'copilot-dual'\n\n/**\n * The Copilot dialect, FLAT on purpose.\n *\n * Declared in this module rather than in `./adapter.ts` — where the design's file\n * map lists it — for one structural reason: `copilotAdapter` builds the composite,\n * so the source edge already runs adapter → dual-protocol, and the two projection\n * functions are runtime values. Declaring them in `./adapter.ts` would make that\n * edge bidirectional, which the repo's circular-dependency check forbids. The\n * public placement is preserved by re-export: `./adapter.ts` re-exports this type\n * and both projections, the same way `./router.ts` re-exports `CopilotEndpoint`\n * from `./catalog.ts`. DD-2 also puts ownership here —\n * \"the composite owns the two pure projection functions\".\n *\n * Every field is a primitive or a string array. See rule 2 in the module note for\n * why nesting the two sub-dialects instead would be a silent-data-loss bug.\n */\nexport interface CopilotDialect {\n /** Send temperature/top_p. Both endpoints accept them; some models refuse. */\n readonly sampling: boolean\n /** Send the output-token limit. */\n readonly maxOutputTokens: boolean\n /** Send JSON-schema structured output. */\n readonly structuredOutputs: boolean\n /** Declare tools in the request. */\n readonly tools: boolean\n /** Responses only: `store`. */\n readonly store: boolean\n /** Responses only: `include`. */\n readonly include: readonly string[]\n /** Responses only: `reasoning.summary`. `'none'` asks for no summary at all. */\n readonly reasoningSummary: 'auto' | 'concise' | 'detailed' | 'none'\n /** Chat Completions only: `stream_options.include_usage`. */\n readonly streamUsage: boolean\n /** Chat Completions only: the role the system prompt travels under. */\n readonly systemRole: 'system' | 'developer'\n /** Chat Completions only: `parallel_tool_calls`. */\n readonly parallelToolCalls: boolean\n /** Prompt/session cache key, used by BOTH branches. */\n readonly promptCacheKey?: string\n}\n\n/**\n * Conservative defaults, matching each sub-protocol's own defaults where the two\n * agree.\n *\n * `store: false` because retaining prompts on someone else's server is an explicit\n * decision, `include` carries `reasoning.encrypted_content` because without it a\n * reasoning model loses its chain of thought across a tool call, and\n * `parallelToolCalls: false` because older gateways reject the field outright.\n */\nexport const COPILOT_DEFAULT_DIALECT: CopilotDialect = Object.freeze({\n sampling: true,\n maxOutputTokens: true,\n structuredOutputs: true,\n tools: true,\n store: false,\n include: Object.freeze(['reasoning.encrypted_content']),\n reasoningSummary: 'auto',\n streamUsage: true,\n systemRole: 'system',\n parallelToolCalls: false,\n} as const satisfies CopilotDialect)\n\n/**\n * Project the Copilot dialect onto the Responses dialect.\n *\n * PURE and TOTAL: every {@link CopilotDialect} flag has exactly one destination\n * here or none at all. `tools`, `streamUsage`, `systemRole` and\n * `parallelToolCalls` have no Responses destination and are DROPPED rather than\n * bent into a nearby flag — Responses declares tools from the request itself and\n * has no `reasoning_effort`-style neighbour worth guessing at.\n *\n * `reasoningSummary: 'none'` is expressed by ABSENCE, because that is how the\n * Responses serializer spells \"ask for no summary\" (`summary` is only sent when\n * the knob is defined). {@link resolvedResponsesDialect} therefore drops the\n * sub-protocol's own `reasoningSummary` default before merging, so this branch of\n * the projection is not overwritten by it.\n * @param dialect - the resolved Copilot dialect for this request.\n * @returns the Responses knobs this dialect determines, and only those.\n */\nexport function toResponsesDialect(dialect: CopilotDialect): Partial<ResponsesDialect> {\n return {\n sampling: dialect.sampling,\n maxOutputTokens: dialect.maxOutputTokens,\n structuredOutputs: dialect.structuredOutputs,\n store: dialect.store,\n include: [...dialect.include],\n ...(dialect.reasoningSummary === 'none' ? {} : { reasoningSummary: dialect.reasoningSummary }),\n ...(dialect.promptCacheKey === undefined ? {} : { promptCacheKey: dialect.promptCacheKey }),\n }\n}\n\n/**\n * Project the Copilot dialect onto the Chat Completions dialect.\n *\n * PURE and TOTAL, same rule as {@link toResponsesDialect}: `store`, `include` and\n * `reasoningSummary` have no Chat Completions destination and are dropped —\n * `reasoningEffort` is a different knob (how hard to think, not whether to report\n * a summary), so mapping onto it would be a guess dressed as a translation.\n *\n * Two flags change type on the way across, and each mapping is total:\n *\n * | Copilot | Chat Completions |\n * | --- | --- |\n * | `maxOutputTokens: true` | `maxTokensField: 'max_tokens'` |\n * | `maxOutputTokens: false` | `maxTokensField: false` |\n * | `structuredOutputs: true` | `structuredOutputs: 'json-schema'` |\n * | `structuredOutputs: false` | `structuredOutputs: false` |\n *\n * The accepted cost of the first row: a caller cannot reach\n * `'max_completion_tokens'` through {@link CopilotDialect}. Copilot's\n * `/chat/completions` takes `max_tokens`, and the models that demand the newer\n * spelling are the ones the router sends to `/responses` anyway, so the boolean\n * buys a flag a caller can reason about and costs a spelling no Copilot model has\n * been observed to need.\n * @param dialect - the resolved Copilot dialect for this request.\n * @returns the Chat Completions knobs this dialect determines, and only those.\n */\nexport function toChatCompletionsDialect(\n dialect: CopilotDialect,\n): Partial<ChatCompletionsDialect> {\n return {\n sampling: dialect.sampling,\n maxTokensField: dialect.maxOutputTokens ? 'max_tokens' : false,\n structuredOutputs: dialect.structuredOutputs ? 'json-schema' : false,\n tools: dialect.tools,\n streamUsage: dialect.streamUsage,\n systemRole: dialect.systemRole,\n parallelToolCalls: dialect.parallelToolCalls,\n ...(dialect.promptCacheKey === undefined ? {} : { promptCacheKey: dialect.promptCacheKey }),\n }\n}\n\n/**\n * The parts of a sub-protocol the composite uses.\n *\n * Structural rather than an import of either package's own definition type, so\n * that a test can hand in a stub and so that neither sub-protocol's marker fields\n * become part of this contract.\n */\nexport interface CopilotSubProtocol<Dialect extends object> {\n /** Reported on the decision as `protocolId`. */\n readonly id: string\n /** Merged UNDER the projection by the composite; never read by the runtime. */\n readonly defaultDialect: Dialect\n readonly endpointPath: (request: ProtocolRequest, dialect: Dialect) => string\n readonly protocolHeaders?: (dialect: Dialect) => Readonly<Record<string, string>>\n readonly serialize: (\n request: ProtocolRequest,\n dialect: Dialect,\n ) => Readonly<Record<string, unknown>>\n readonly translate: (\n events: AsyncIterable<ProtocolSseEvent>,\n request: ProtocolRequest,\n displayName: string,\n ) => AsyncGenerator<ProtocolStreamChunk>\n}\n\n/** The `/responses` half. `openAiResponsesProtocol` satisfies this. */\nexport type ResponsesProtocolLike = CopilotSubProtocol<ResponsesDialect>\n\n/** The `/chat/completions` half. `openAiChatCompletionsProtocol` satisfies this. */\nexport type ChatCompletionsProtocolLike = CopilotSubProtocol<ChatCompletionsDialect>\n\n/** Construction options for {@link copilotDualProtocol}. */\nexport interface CopilotDualProtocolOptions {\n /** Decides, once per model id, which branch a request takes. */\n readonly router: CopilotEndpointRouter\n /** The protocol serving `/responses`. */\n readonly responses: ResponsesProtocolLike\n /** The protocol serving `/chat/completions`. */\n readonly chat: ChatCompletionsProtocolLike\n /**\n * Synchronous, best-effort observer of every endpoint decision (Requirement\n * 9.8).\n *\n * Carries `{ model, endpoint, protocolId, source }` — no prompt and no\n * credential, because an observer is a diagnostic channel and neither of those\n * is diagnostic. Throwing in here does NOT affect the request: the error is\n * trapped, since a broken log sink must not decide whether a generation runs.\n */\n readonly onDecision?: (decision: CopilotEndpointDecision) => void\n}\n\n/**\n * Build the composite protocol.\n *\n * @param options - the router, the two sub-protocols, and the optional observer.\n * @returns a `RuntimeWireProtocol<CopilotDialect>` with id `'copilot-dual'`.\n */\nexport function copilotDualProtocol(\n options: CopilotDualProtocolOptions,\n): RuntimeWireProtocol<CopilotDialect> {\n const { router, responses, chat } = options\n const onDecision = options.onDecision\n\n /**\n * The Responses dialect for one request.\n *\n * Merged with the SUB-PROTOCOL's defaults, not the composite's: the composite's\n * defaults are already inside `dialect` by the time the runtime calls us, and\n * what is missing is everything the Responses dialect knows about but Copilot\n * does not expose (`messagePhase`). `reasoningSummary` is dropped from the base\n * because the projection owns that key outright — see {@link toResponsesDialect}.\n */\n const resolvedResponsesDialect = (dialect: CopilotDialect): ResponsesDialect => {\n const { reasoningSummary: _ownedByProjection, ...base } = responses.defaultDialect\n return Object.freeze({ ...base, ...toResponsesDialect(dialect) })\n }\n\n /**\n * The Chat Completions dialect for one request.\n *\n * Same rule; the sub-protocol supplies `path`, `stop`, `seed` and\n * `reasoningEffort`, which {@link CopilotDialect} deliberately does not expose.\n */\n const resolvedChatDialect = (dialect: CopilotDialect): ChatCompletionsDialect =>\n Object.freeze({ ...chat.defaultDialect, ...toChatCompletionsDialect(dialect) })\n\n /** Route one request, reporting the decision on the way through. */\n const decide = (request: ProtocolRequest): CopilotEndpointDecision => {\n const decision = router.decide(request.model.id)\n report(decision)\n return decision\n }\n\n /** Hand the decision to the observer, swallowing whatever it does with it. */\n const report = (decision: CopilotEndpointDecision): void => {\n if (onDecision === undefined) return\n try {\n onDecision(decision)\n } catch { /* an observer must not decide whether a request runs */ }\n }\n\n return defineWireProtocol<CopilotDialect>({\n id: COPILOT_DUAL_PROTOCOL_ID,\n defaultDialect: COPILOT_DEFAULT_DIALECT,\n // `endpointPath` is where the decision is REPORTED, because it is the first of\n // the three methods the pipeline calls for a request, and because the router\n // is memoized and append-only: the branch reported here is the branch\n // `serialize` and `translate` will take, retries included (Requirement 9.7).\n endpointPath: (request: ProtocolRequest, dialect: CopilotDialect): string =>\n decide(request).endpoint === 'responses'\n ? responses.endpointPath(request, resolvedResponsesDialect(dialect))\n : chat.endpointPath(request, resolvedChatDialect(dialect)),\n ...protocolHeadersOf(responses, chat),\n serialize: (\n request: ProtocolRequest,\n dialect: CopilotDialect,\n ): Readonly<Record<string, unknown>> =>\n // No report here: one logical call would otherwise emit the same decision\n // two or three times, and the router's memo makes the extra reports\n // information-free.\n router.decide(request.model.id).endpoint === 'responses'\n ? responses.serialize(request, resolvedResponsesDialect(dialect))\n : chat.serialize(request, resolvedChatDialect(dialect)),\n translate: (\n events: AsyncIterable<ProtocolSseEvent>,\n request: ProtocolRequest,\n displayName: string,\n ): AsyncGenerator<ProtocolStreamChunk> =>\n router.decide(request.model.id).endpoint === 'responses'\n ? responses.translate(events, request, displayName)\n : chat.translate(events, request, displayName),\n })\n}\n\n/**\n * The composite's `protocolHeaders`, or nothing.\n *\n * This is the one method whose signature carries NO `ProtocolRequest`, so the\n * routing key that makes the other three work is absent here. Two things follow.\n * The union of both branches' headers is wrong — it would put a header belonging\n * to the branch NOT taken on the wire. And a guess is wrong for the same reason.\n * So the composite exposes a header set only when both branches produce the SAME\n * one, in which case that set is the selected branch's set whichever branch is\n * selected; when they diverge, it exposes none, and a protocol header that only\n * one branch needs has to travel through the adapter's request-scoped header path\n * where the model id is in hand.\n *\n * Today neither sub-protocol declares `protocolHeaders` — Copilot's mandatory\n * headers are endpoint identity (`editor-version`, `editor-plugin-version`), not\n * protocol facts — so this returns nothing and the branch above is the\n * forward-looking half of the rule.\n * @param responses - the `/responses` sub-protocol.\n * @param chat - the `/chat/completions` sub-protocol.\n * @returns a one-key spread carrying `protocolHeaders`, or an empty one.\n */\nfunction protocolHeadersOf(\n responses: ResponsesProtocolLike,\n chat: ChatCompletionsProtocolLike,\n): { protocolHeaders?: (dialect: CopilotDialect) => Readonly<Record<string, string>> } {\n const fromResponses = responses.protocolHeaders\n const fromChat = chat.protocolHeaders\n if (fromResponses === undefined && fromChat === undefined) return {}\n return {\n protocolHeaders: (dialect: CopilotDialect): Readonly<Record<string, string>> => {\n const left = fromResponses?.(Object.freeze({\n ...responses.defaultDialect,\n ...toResponsesDialect(dialect),\n })) ?? {}\n const right = fromChat?.(Object.freeze({\n ...chat.defaultDialect,\n ...toChatCompletionsDialect(dialect),\n })) ?? {}\n return sameHeaders(left, right) ? Object.freeze({ ...left }) : Object.freeze({})\n },\n }\n}\n\n/** Whether two header maps are equal name-for-name and value-for-value. */\nfunction sameHeaders(\n left: Readonly<Record<string, string>>,\n right: Readonly<Record<string, string>>,\n): boolean {\n const names = Object.keys(left)\n if (names.length !== Object.keys(right).length) return false\n return names.every((name) => left[name] === right[name])\n}\n","/**\n * `Copilot_Token_Exchange`: turn the long-lived `GitHub_User_Token` into the\n * short-lived `Copilot_Api_Token` the Copilot surface accepts.\n *\n * ```text\n * GET https://api.github.com/copilot_internal/v2/token\n * Authorization: Bearer ghu_…\n * Accept: application/json\n * Editor-Version / Editor-Plugin-Version\n * → 200 { token, expires_at, refresh_in?, endpoints?: { api?: string }, … }\n * ```\n *\n * ## The classification order is the contract\n *\n * Nine rows, in this order, each for a concrete reason:\n *\n * ```text\n * 1. host is 'ghe.com' or ends with a '.ghe.com' label ⇒ TENANT_UNSUPPORTED (before any I/O)\n * 2. origin is not the pinned githubApiBaseUrl origin ⇒ ENDPOINT_ORIGIN_INVALID (before any I/O)\n * 3. the response is a redirect, in any of its shapes ⇒ REDIRECT_REJECTED\n * 4. HTTP 404 ⇒ TENANT_UNSUPPORTED\n * 5. HTTP 401 ⇒ CREDENTIAL_REJECTED (permanent)\n * 6. HTTP 403 ⇒ CREDENTIAL_REJECTED (permanent)\n * 7. HTTP 429, HTTP 5xx, a network error, or a timeout ⇒ TOKEN_EXCHANGE_FAILED (transient)\n * 8. any remaining 4xx ⇒ TOKEN_EXCHANGE_FAILED (permanent)\n * 9. body is not JSON, or expires_at is unreadable ⇒ TOKEN_MALFORMED\n * ```\n *\n * Rows 1 and 2 run BEFORE a request is dispatched. A data-residency tenant has no\n * token-exchange surface at all, so asking it is pointless; and an origin check\n * performed after the fact would already have handed the bearer token to whatever\n * origin the URL named (Requirements 3.6, 3.7).\n *\n * Row 1 detects the tenant by DOMAIN LABEL SUFFIX, never by substring: with\n * `includes('ghe.com')`, `ghe.com.evil.tld` and `notghe.com` would both be\n * misread as data-residency tenants, one of which is an attacker-chosen host.\n *\n * Row 6 does the most work of the nine. The endpoint answers 403 both for a\n * personal access token and for a token minted by an OAuth App that is not on\n * GitHub's allowlist, and the response does not distinguish the two — so the\n * message names BOTH possibilities alongside the single instruction that helps in\n * either case (Requirements 3.5, 13.2).\n *\n * ## What is read from the body, and what is refused\n *\n * `expires_at` is MANDATORY and has to be a positive finite number: without it\n * there is no second source for the lifetime, and an invented TTL is exactly the\n * inference this SDK does not make. `refresh_in` is advisory and a bad value is\n * dropped rather than fatal — it can only shorten the refresh moment, so losing\n * it costs nothing. `endpoints.api` is read and exposed for diagnostics but is\n * NEVER used as the base URL: a server-designated base URL is a redirect under\n * another name, and this SDK does not follow provider-controlled redirects\n * (DD-6, Requirements 3.8, 7.8).\n *\n * @module ai-agent-sdk/providers/copilot/exchange\n */\n\nimport { AgentSdkError, type ModelInvocationContext } from '@alvin0/ai-agent-sdk-core'\nimport type { CredentialOperationOptions } from '@alvin0/ai-agent-sdk-core/provider'\nimport { observeCredentialOperation } from '@alvin0/ai-agent-sdk-provider-http'\nimport {\n COPILOT_EDITOR_PLUGIN_VERSION,\n COPILOT_EDITOR_VERSION,\n type CopilotEditorHeaders,\n} from './common/identity.ts'\nimport {\n COPILOT_LOGIN_COMMAND,\n COPILOT_TOKEN_EXCHANGE_MARGIN_MS,\n requireGitHubToken,\n shouldExchange,\n type CopilotCredentialSnapshot,\n} from './auth.ts'\nimport { COPILOT_ERROR_CODES } from './common/error-codes.ts'\nimport {\n COPILOT_DEFAULT_REQUEST_TIMEOUT_MS,\n copilotFetch,\n copilotUrl,\n issuerOf,\n positiveSafeInteger,\n raceAbort,\n readCopilotResponseText,\n type CopilotHttpOptions,\n} from './common/http.ts'\nimport type { CopilotGitHubToken } from './common/store-types.ts'\nimport { CopilotTokenExchangeError, credentialFailure } from './errors.ts'\n\n/** GitHub's API base, where the token-exchange surface lives. */\nexport const DEFAULT_GITHUB_API_BASE_URL = 'https://api.github.com'\n\n/** Path of the token-exchange surface. */\nexport const COPILOT_TOKEN_EXCHANGE_PATH = '/copilot_internal/v2/token'\n\n/** Marker used in place of a credential value that appeared in a response body. */\nconst REDACTED = '[REDACTED]'\n\n/** Settings for one token exchange. Every field is optional; every default is a bound. */\nexport interface CopilotExchangeOptions extends CopilotHttpOptions {\n /** Overrides {@link DEFAULT_GITHUB_API_BASE_URL}; pinned as its own origin. */\n readonly githubApiBaseUrl?: string\n /** Overrides for the two mandatory editor headers. */\n readonly editorHeaders?: CopilotEditorHeaders\n /**\n * Further secret values to strike out of any body this exchange retains,\n * beyond the credential it sends itself.\n *\n * Requirement 13.7 is stated over BOTH tokens, not just the one a given request\n * carries, and an exchange knows only its own. The remaining value — the\n * `Copilot_Api_Token` currently held — reaches this path from\n * {@link createCopilotTokenCache}, which is the one component holding both at\n * once. Without it, a body echoing the live API token back would travel into\n * `cause` intact, because the redaction here would be looking for the wrong\n * string.\n */\n readonly additionalSecrets?: readonly string[]\n}\n\n/**\n * The result of one `Copilot_Token_Exchange`, held in process memory only.\n *\n * Structurally satisfies `CopilotTokenExpiry` from `./auth.ts`, so `shouldExchange`\n * accepts one of these with no conversion.\n */\nexport interface CopilotApiToken {\n /** Bearer token for the Copilot API base. Short-lived, ~25 minutes. */\n readonly token: string\n /** Expiry instant in epoch MILLISECONDS, derived from `expires_at` (seconds). */\n readonly expiresAtMs: number\n /** The endpoint's `refresh_in` hint in seconds, when it sent a usable one. ADVISORY. */\n readonly refreshInSeconds?: number\n /**\n * The endpoint's declared `endpoints.api`, when present.\n *\n * MUST NOT be used as a base URL. A server-designated base URL is a redirect\n * under another name, and Requirements 3.8/7.8 settled that this SDK does not\n * follow provider-controlled redirection. This field exists so `--status` can\n * print it and so a configuration drift is visible. See DD-6.\n */\n readonly declaredApiEndpoint?: string\n}\n\n/**\n * Exchange a `GitHub_User_Token` for a `Copilot_Api_Token`.\n *\n * The long-lived credential is NOT consumed: nothing here writes to a store, and\n * the persisted value is left exactly as it was (Requirement 3.4).\n * @param github - the long-lived GitHub user token. Its value never reaches an\n * error message, and any occurrence of it in a response body is redacted before\n * the body is retained as a cause (Requirement 13.7).\n * @param options - base URL override, injected fetch, signal, and the read bounds.\n * @returns the short-lived token plus its expiry and the advisory fields.\n * @throws AgentSdkError with `COPILOT_ENDPOINT_ORIGIN_INVALID` or\n * `COPILOT_REDIRECT_REJECTED`, or {@link CopilotTokenExchangeError} with\n * `COPILOT_TENANT_UNSUPPORTED`, `COPILOT_CREDENTIAL_REJECTED`,\n * `COPILOT_TOKEN_EXCHANGE_FAILED` or `COPILOT_TOKEN_MALFORMED`, per the\n * classification order in the module note.\n */\nexport async function exchangeCopilotToken(\n github: CopilotGitHubToken,\n options: CopilotExchangeOptions = {},\n): Promise<CopilotApiToken> {\n // Row 1, before any I/O: a data-residency tenant has no surface to ask.\n rejectDataResidencyTenant(options.githubApiBaseUrl)\n // Row 2, before any I/O: pin the origin, then build the URL on that pin.\n const pinned = issuerOf(\n 'githubApiBaseUrl',\n options.githubApiBaseUrl,\n DEFAULT_GITHUB_API_BASE_URL,\n options,\n )\n const url = copilotUrl(pinned, COPILOT_TOKEN_EXCHANGE_PATH)\n const host = new URL(pinned.origin).hostname\n let response: Response\n try {\n // Rows 3: `copilotFetch` re-checks the pin and refuses every redirect shape.\n response = await copilotFetch({\n pinned,\n url,\n operation: 'token exchange',\n init: { method: 'GET', headers: exchangeHeaders(github, options.editorHeaders) },\n }, options)\n } catch (error: unknown) {\n throw transportFailure(error, host, options)\n }\n // Every value that must not survive into a retained body: the credential this\n // request carries, plus whatever else the caller knows is live.\n const secrets = [github.token, ...options.additionalSecrets ?? []]\n // Rows 4 through 8.\n if (!response.ok) throw await statusFailure(response, host, secrets, options)\n // Row 9.\n return readApiToken(await readCopilotResponseText(response, options), secrets)\n}\n\n/**\n * Row 1: refuse a `*.ghe.com` tenant by domain label, before anything is sent.\n *\n * An unparsable value is left alone rather than reported here — {@link issuerOf}\n * owns that message, and reporting it as a tenant problem would name the wrong\n * cause.\n * @param configured - the caller's `githubApiBaseUrl`, when they set one.\n * @throws CopilotTokenExchangeError with `COPILOT_TENANT_UNSUPPORTED`, naming the\n * detected domain (Requirement 13.3).\n */\nfunction rejectDataResidencyTenant(configured: string | undefined): void {\n if (configured === undefined) return\n let host: string\n try {\n host = new URL(configured).hostname\n } catch {\n return\n }\n if (!isDataResidencyHost(host)) return\n throw new CopilotTokenExchangeError(\n credentialFailure(tenantMessage(host)),\n COPILOT_ERROR_CODES.TENANT_UNSUPPORTED,\n 'permanent',\n )\n}\n\n/**\n * Whether a hostname belongs to the `ghe.com` data-residency namespace.\n *\n * Matched on DOMAIN LABELS, which is the whole point: `ghe.com.evil.tld` and\n * `notghe.com` are not data-residency hosts, and a substring test would call both\n * of them one.\n * @param host - a hostname, without a port.\n * @returns true for `ghe.com` itself and for any host under it.\n */\nfunction isDataResidencyHost(host: string): boolean {\n const normalized = host.toLowerCase().replace(/\\.$/, '')\n return normalized === 'ghe.com' || normalized.endsWith('.ghe.com')\n}\n\n/**\n * Headers for the exchange request.\n *\n * Both editor headers are mandatory: with either one missing the endpoint answers\n * HTTP 400 and the request never runs. An override of one leaves the other at its\n * exported default rather than dropping it.\n * @param github - the credential whose value goes in `Authorization`.\n * @param headers - per-call overrides for the editor identity.\n * @returns the header map for the request init.\n */\nfunction exchangeHeaders(\n github: CopilotGitHubToken,\n headers: CopilotEditorHeaders | undefined,\n): Record<string, string> {\n return {\n authorization: `Bearer ${github.token}`,\n accept: 'application/json',\n 'editor-version': headers?.editorVersion ?? COPILOT_EDITOR_VERSION,\n 'editor-plugin-version': headers?.editorPluginVersion ?? COPILOT_EDITOR_PLUGIN_VERSION,\n }\n}\n\n/**\n * Rows 3 and 7 on the dispatch path: keep the structural refusals, classify the\n * rest as transient.\n *\n * Four kinds of failure pass through UNCHANGED, because wrapping each one would\n * replace a precise diagnosis with a vaguer one:\n *\n * - the origin refusal and the redirect refusal, which are rows 2 and 3 and\n * already carry their own codes;\n * - a caller abort, which keeps the SDK's abort code rather than becoming a\n * Copilot failure the caller did not ask about (Requirement 4.6);\n * - a `RangeError` from a bound, which names the limit that was exceeded and is\n * neither a network fault nor a server fault (Requirement 13.6).\n *\n * Everything else — DNS, connection reset, TLS, and the per-request deadline —\n * is transient: it is exactly the class of failure that a later attempt can win.\n * @param error - the caught value.\n * @param host - the host that was contacted, for the message.\n * @param options - read for the caller's signal only.\n * @returns the value to throw.\n */\nfunction transportFailure(\n error: unknown,\n host: string,\n options: CopilotExchangeOptions,\n): unknown {\n if (options.signal?.aborted === true) return error\n if (error instanceof RangeError) return error\n if (error instanceof AgentSdkError\n && (error.code === COPILOT_ERROR_CODES.ENDPOINT_ORIGIN_INVALID\n || error.code === COPILOT_ERROR_CODES.REDIRECT_REJECTED)) {\n return error\n }\n return new CopilotTokenExchangeError(\n credentialFailure(\n `Copilot token exchange could not reach ${host}; the request failed before a response`,\n error,\n ),\n COPILOT_ERROR_CODES.TOKEN_EXCHANGE_FAILED,\n 'transient',\n )\n}\n\n/**\n * Rows 4 through 8: classify a response that arrived but was not a success.\n *\n * The body is read through the bounded reader first, so the cause carries the\n * endpoint's own words — with every occurrence of the credential replaced —\n * rather than nothing at all (Requirements 13.6, 13.7). A read that fails is not\n * allowed to hide the status: the classification stands either way.\n * @param response - a non-ok response, already cleared by the redirect guard.\n * @param host - the host that answered, for the tenant message.\n * @param secrets - every live credential value to redact out of the body.\n * @param options - the read bounds and the signal.\n * @returns the classified error to throw.\n */\nasync function statusFailure(\n response: Response,\n host: string,\n secrets: readonly string[],\n options: CopilotExchangeOptions,\n): Promise<CopilotTokenExchangeError> {\n const body = await readFailureBody(response, secrets, options)\n const cause = body === undefined ? undefined : new Error(body)\n if (response.status === 404) {\n return new CopilotTokenExchangeError(\n credentialFailure(tenantMessage(host), cause),\n COPILOT_ERROR_CODES.TENANT_UNSUPPORTED,\n 'permanent',\n )\n }\n if (response.status === 401) {\n return new CopilotTokenExchangeError(\n credentialFailure(\n 'the Copilot token-exchange surface rejected the stored GitHub credential '\n + `(HTTP 401); run \\`${COPILOT_LOGIN_COMMAND}\\` to sign in again`,\n cause,\n ),\n COPILOT_ERROR_CODES.CREDENTIAL_REJECTED,\n 'permanent',\n )\n }\n if (response.status === 403) {\n // The endpoint answers 403 for BOTH a personal access token and a token from\n // a non-allowlisted OAuth App, and the response does not say which — so both\n // are named, with the one instruction that resolves either.\n return new CopilotTokenExchangeError(\n credentialFailure(\n 'the Copilot token-exchange surface refused this credential type (HTTP 403). '\n + 'It accepts only a token minted by an OAuth App on GitHub\\'s allowlist: a '\n + 'personal access token cannot be used here, and neither can a token from an '\n + `OAuth App that is not allowlisted. Run \\`${COPILOT_LOGIN_COMMAND}\\` to sign `\n + 'in with the supported client.',\n cause,\n ),\n COPILOT_ERROR_CODES.CREDENTIAL_REJECTED,\n 'permanent',\n )\n }\n // 429 sits with the 5xx row rather than with the remaining 4xx: the design's\n // classification table gives it `transient`, and it is the one 4xx whose cause\n // a later attempt can actually clear. Every other 4xx fails identically forever.\n const transient = response.status >= 500 || response.status === 429\n return new CopilotTokenExchangeError(\n credentialFailure(\n `Copilot token exchange failed (HTTP ${response.status})`,\n cause,\n ),\n COPILOT_ERROR_CODES.TOKEN_EXCHANGE_FAILED,\n transient ? 'transient' : 'permanent',\n )\n}\n\n/**\n * Read an error body within the configured bounds, redacted, or give up quietly.\n *\n * Giving up quietly is deliberate: the status has already decided the\n * classification, and a body that could not be read must not turn a precise 403\n * into a read error.\n * @param response - the non-ok response.\n * @param secrets - every live credential value to redact.\n * @param options - the read bounds and the signal.\n * @returns the redacted text, or `undefined` when it could not be read.\n */\nasync function readFailureBody(\n response: Response,\n secrets: readonly string[],\n options: CopilotExchangeOptions,\n): Promise<string | undefined> {\n try {\n const text = await readCopilotResponseText(response, options)\n return text.length === 0 ? undefined : redact(text, secrets)\n } catch {\n return undefined\n }\n}\n\n/**\n * Row 9: read the success body, requiring exactly what cannot be inferred.\n *\n * `token` and `expires_at` are mandatory; everything else is advisory and a\n * useless value is dropped rather than raised, because a dropped hint changes\n * nothing about correctness while a raised one would fail an exchange that\n * actually produced a usable token.\n * @param raw - the bounded response text.\n * @param secrets - every live credential value to redact out of the cause.\n * @returns the token this exchange produced.\n * @throws CopilotTokenExchangeError with `COPILOT_TOKEN_MALFORMED` when the body\n * is not a JSON object, `token` is not a non-empty string, or `expires_at` is\n * not a positive finite number.\n */\nfunction readApiToken(raw: string, secrets: readonly string[]): CopilotApiToken {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw) as unknown\n } catch (error: unknown) {\n throw malformed('the Copilot token-exchange response was not JSON', error)\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw malformed('the Copilot token-exchange response was not a JSON object')\n }\n const body = parsed as Record<string, unknown>\n const token = body['token']\n if (typeof token !== 'string' || token.length === 0) {\n throw malformed('the Copilot token-exchange response carried no token')\n }\n const expiresAt = body['expires_at']\n if (typeof expiresAt !== 'number' || !Number.isFinite(expiresAt) || expiresAt <= 0) {\n throw malformed(\n 'the Copilot token-exchange response carried no readable expires_at; '\n + 'this SDK does not invent a token lifetime',\n new Error(redact(raw, secrets)),\n )\n }\n const refreshIn = body['refresh_in']\n const declared = declaredEndpointOf(body['endpoints'])\n return Object.freeze({\n token,\n expiresAtMs: expiresAt * 1_000,\n ...typeof refreshIn === 'number' && Number.isFinite(refreshIn) && refreshIn > 0\n ? { refreshInSeconds: refreshIn }\n : {},\n ...declared === undefined ? {} : { declaredApiEndpoint: declared },\n })\n}\n\n/**\n * Read `endpoints.api` for diagnostics only.\n *\n * Never returned as something to request against — see {@link\n * CopilotApiToken.declaredApiEndpoint} and DD-6.\n * @param endpoints - the `endpoints` member of the response, unvalidated.\n * @returns the declared API endpoint, when it is a non-empty string.\n */\nfunction declaredEndpointOf(endpoints: unknown): string | undefined {\n if (typeof endpoints !== 'object' || endpoints === null) return undefined\n const api = (endpoints as Record<string, unknown>)['api']\n return typeof api === 'string' && api.length > 0 ? api : undefined\n}\n\n/**\n * The one tenant message, so the two paths that reach row 1 and row 4 say the\n * same thing.\n * @param host - the detected host, named in the message (Requirement 13.3).\n * @returns the message text.\n */\nfunction tenantMessage(host: string): string {\n return `'${host}' is a GitHub data-residency tenant, which does not provide the Copilot `\n + 'token-exchange surface; use a github.com account for this provider'\n}\n\n/**\n * Replace every occurrence of every live credential value in text with\n * {@link REDACTED}.\n *\n * A response body is endpoint-authored text, and an endpoint that echoes the\n * `Authorization` header back is exactly how a token ends up in a log. The list\n * is plural because Requirement 13.7 covers BOTH tokens: this request carries one\n * of them, and the other arrives through\n * {@link CopilotExchangeOptions.additionalSecrets}.\n * @param text - the body text.\n * @param secrets - the credential values; empty entries are ignored.\n * @returns the text with every value removed.\n */\nfunction redact(text: string, secrets: readonly string[]): string {\n let result = text\n for (const secret of secrets) {\n if (secret.length === 0) continue\n result = result.split(secret).join(REDACTED)\n }\n return result\n}\n\n/**\n * Build the row-9 error.\n * @param message - SDK-authored text.\n * @param cause - the caught value or the redacted body, when there is one.\n * @returns the malformed-token error.\n */\nfunction malformed(message: string, cause?: unknown): CopilotTokenExchangeError {\n return new CopilotTokenExchangeError(\n credentialFailure(message, cause),\n COPILOT_ERROR_CODES.TOKEN_MALFORMED,\n 'permanent',\n )\n}\n/**\n * A cache entry, bound to exactly the credential that produced it.\n *\n * The pair `(sourceToken, sourceRevision)` is the whole key: signing in as a\n * different account invalidates the entry the moment the store returns a\n * different token value, with no TTL involved and no clock consulted.\n */\nexport interface CopilotTokenCacheEntry {\n /** The token this credential produced. */\n readonly api: CopilotApiToken\n /** The `GitHub_User_Token` value used. Compared with `===`. */\n readonly sourceToken: string\n /** The store revision at read time, or `null` for a store without revisions. */\n readonly sourceRevision: string | null\n}\n\n/**\n * The process-memory cache in front of {@link exchangeCopilotToken}.\n *\n * Owned by an adapter instance, and injectable through the adapter's `tokenCache`\n * option so several routes sharing one unchanged credential exchange once rather\n * than once per route.\n */\nexport interface CopilotTokenCache {\n /**\n * Return a live `Copilot_Api_Token` for this credential, exchanging when due.\n *\n * Concurrent calls that all need an exchange are COALESCED into exactly one\n * in-flight exchange (Requirement 5.4).\n * @param source - one read of the credential store, revision included.\n * @param operation - the calling operation; only its signal is read, and it\n * bounds THIS caller's wait, never the shared exchange.\n * @param context - invocation context for the observation record, when there is one.\n * @returns a token that is live as of the decision moment.\n */\n acquire(\n source: CopilotCredentialSnapshot,\n operation: CredentialOperationOptions,\n context?: ModelInvocationContext,\n ): Promise<CopilotApiToken>\n /** Drop the current entry; used when the API surface rejects a token before its expiry. */\n invalidate(): void\n}\n\n/** Provider name recorded on the credential-operation observation by default. */\nexport const COPILOT_PROVIDER_ID = 'copilot'\n\n/** Settings for {@link createCopilotTokenCache}: the exchange settings, plus a clock. */\nexport interface CopilotTokenCacheOptions extends CopilotExchangeOptions {\n /**\n * Provider name on the observation record. Defaults to {@link COPILOT_PROVIDER_ID}.\n *\n * An adapter with a custom `id` passes it here so the record names the provider\n * the caller configured rather than the family.\n */\n readonly providerId?: string\n /** Exchange this long before expiry. Defaults to `COPILOT_TOKEN_EXCHANGE_MARGIN_MS`. */\n readonly marginMs?: number\n /**\n * The clock the exchange decision reads, injectable so a test places `now`\n * exactly on a boundary instead of waiting for one.\n */\n readonly now?: () => number\n}\n\n/**\n * Build a token cache over one set of exchange settings.\n *\n * ## The mistake this is written to avoid\n *\n * The shared exchange gets its OWN `AbortController` plus its own deadline, and\n * NEVER any single caller's signal. Were the caller's signal handed to it, the\n * first caller to abort would cancel the exchange every other caller is waiting\n * on, and those callers would fail for a reason that has nothing to do with them.\n * Instead each caller — the one that started the exchange included — races the\n * shared promise against its OWN signal: an aborted caller leaves, and the\n * exchange still completes for everyone else (Property 18).\n *\n * The observation therefore counts exchanges actually DISPATCHED rather than\n * callers served, which is what makes the coalescing observable instead of merely\n * claimed (Property 52). It is recorded with the `'refresh'` operation name: that\n * parameter's union is closed at `'resolve' | 'refresh' | 'login'`, widening it\n * would change a public type of `provider-http`, and Requirement 18.4 forbids\n * that — see DD-7.\n *\n * ## Two paths deliberately absent\n *\n * There is no revision-conflict recovery, unlike `provider-codex`. That path\n * exists there because a Codex refresh token rotates and is single-use, so a lost\n * race destroys a credential. A `GitHub_User_Token` does not rotate and an\n * exchange does not consume it, so two racing processes simply exchange twice —\n * and a branch no situation reaches is a branch nothing verifies (DD-8).\n *\n * Nothing here writes to a store. The `Copilot_Api_Token` is never persisted: it\n * lives ~25 minutes, so persisting it would add a second secret on disk, a second\n * write path, and a new state to reason about, to save one request inside a\n * 25-minute window (DD-9, Requirement 3.3).\n *\n * A failure is returned to every waiting caller as-is and never retried here — an\n * endpoint that rejected the credential will reject it again, and this layer has\n * no way to change that (Requirement 5.8, Property 19).\n * @param options - exchange settings, the observation provider name, the margin\n * and the clock. `options.signal` is deliberately IGNORED for the exchange\n * itself; per-caller cancellation travels through `operation.signal`.\n * @returns a cache over a single credential slot.\n */\nexport function createCopilotTokenCache(\n options: CopilotTokenCacheOptions = {},\n): CopilotTokenCache {\n const provider = options.providerId ?? COPILOT_PROVIDER_ID\n const now = options.now ?? (() => Date.now())\n const marginMs = options.marginMs === undefined\n ? COPILOT_TOKEN_EXCHANGE_MARGIN_MS\n : positiveSafeInteger(options.marginMs, 'marginMs')\n let entry: CopilotTokenCacheEntry | undefined\n let inflight: Promise<CopilotApiToken> | undefined\n let inflightToken: string | undefined\n let ticket = 0\n return {\n async acquire(\n source: CopilotCredentialSnapshot,\n operation: CredentialOperationOptions,\n context?: ModelInvocationContext,\n ): Promise<CopilotApiToken> {\n operation.signal.throwIfAborted()\n const github = requireGitHubToken(source.file, source.label)\n const cached = entry\n if (cached !== undefined\n && cached.sourceToken === github.token\n && cached.sourceRevision === source.revision\n && !shouldExchange(cached.api, now(), marginMs)) {\n return cached.api\n }\n // Coalesce: an exchange already flying for THIS credential value serves\n // this caller too, and the caller still leaves on its own signal.\n if (inflight !== undefined && inflightToken === github.token) {\n return await raceAbort(inflight, operation.signal)\n }\n ticket++\n const id = ticket\n const pending = runExchange(id)\n inflight = pending\n inflightToken = github.token\n return await raceAbort(pending, operation.signal)\n\n /**\n * Dispatch the one shared exchange and record its result.\n * @param slot - this exchange's ticket, so a later exchange's teardown does\n * not clear a newer in-flight one.\n * @returns the exchanged token.\n */\n async function runExchange(slot: number): Promise<CopilotApiToken> {\n // The cache is the only place BOTH tokens are known at once, so it is the\n // only place that can tell the exchange about the second one. Without\n // this, a body echoing the API token currently held would reach `cause`\n // intact: the exchange redacts the credential it sends, and that is a\n // different string (Requirement 13.7).\n const held = entry?.api.token\n try {\n const api = await observeCredentialOperation(\n context,\n provider,\n 'refresh',\n () => exchangeCopilotToken(github, {\n ...options,\n ...held === undefined\n ? {}\n : { additionalSecrets: [...options.additionalSecrets ?? [], held] },\n signal: sharedExchangeSignal(options),\n }),\n )\n entry = Object.freeze({\n api,\n sourceToken: github.token,\n sourceRevision: source.revision,\n })\n return api\n } finally {\n if (ticket === slot) {\n inflight = undefined\n inflightToken = undefined\n }\n }\n }\n },\n invalidate(): void {\n // Only the entry goes. An in-flight exchange is left alone: it was started\n // by callers that are still waiting on it, and the token it produces is\n // newer than the one being rejected here.\n entry = undefined\n },\n }\n}\n\n/**\n * The shared exchange's own cancellation source: one controller, driven by one\n * deadline, and reachable by no caller.\n *\n * The deadline is what makes the controller more than ceremony. `copilotFetch`\n * bounds its own dispatch, but the bounded body read afterwards races only the\n * signal it was given — so without a deadline on this signal a stalled read would\n * hold the in-flight slot open indefinitely and every coalesced caller with it.\n * @param options - read for `requestTimeoutMs`.\n * @returns a signal that aborts on the exchange deadline and on nothing else.\n * @throws RangeError when `requestTimeoutMs` cannot serve as a bound.\n */\nfunction sharedExchangeSignal(options: CopilotTokenCacheOptions): AbortSignal {\n const controller = new AbortController()\n const deadline = AbortSignal.timeout(positiveSafeInteger(\n options.requestTimeoutMs ?? COPILOT_DEFAULT_REQUEST_TIMEOUT_MS,\n 'requestTimeoutMs',\n ))\n deadline.addEventListener('abort', () => { controller.abort(deadline.reason) }, { once: true })\n return controller.signal\n}\n","/**\n * `Copilot_Endpoint_Router`: decide, ONCE per model id, which endpoint a\n * generation request is dispatched to.\n *\n * ## The decision order, and what is deliberately missing from it\n *\n * ```text\n * 1. endpointOverrides[modelId] ⇒ source 'override'\n * 2. catalog disclosure (via learn()) ⇒ source 'catalog'\n * 3. responses-model prefix allowlist ⇒ source 'allowlist'\n * 4. /chat/completions ⇒ source 'default'\n * ```\n *\n * What is missing is a PROBE. Trying `/responses` to find out whether a model\n * accepts it was rejected (DD-4): a probe is a real request that spends real\n * quota and needs a real prompt, so it has an observable side effect on the\n * user's account purely to answer a metadata question. Its result is not safely\n * cacheable across accounts either, since which models an account may call\n * depends on its plan.\n *\n * ## Why the default is `/chat/completions`\n *\n * The two ways of guessing wrong are ASYMMETRIC:\n *\n * | Guessed wrong | Consequence |\n * | --- | --- |\n * | Model supports `/responses`, we sent `/chat/completions` | works, minus some Responses-specific features |\n * | Model does not support `/responses`, we sent `/responses` | HTTP 400, dead request |\n *\n * Losing a feature is recoverable at the next call; losing the call is not. So\n * the fallback leans to the endpoint every Copilot generation model answers.\n *\n * ## Why `decisions` is append-only\n *\n * Requirement 9.7 asks that the endpoint chosen for a `Logical_Call` hold for\n * that whole call, retries included. The catalog has a TTL and may refresh\n * between two retries, so a router that recomputed could answer `/responses` on\n * the first attempt and `/chat/completions` on the second — one logical call\n * split across two wire protocols, with a serialized body that no longer matches\n * the endpoint it is going to.\n *\n * This module makes that STRUCTURALLY impossible rather than conventionally\n * avoided: once a model id has a decision, no code path rewrites it.\n * {@link CopilotEndpointRouter.learn} only adds keys that have no decision yet,\n * so a later catalog refresh returning different metadata changes nothing.\n *\n * The accepted cost (DD-5): a model misclassified on the first call keeps that\n * classification for the lifetime of the adapter instance. `endpointOverrides`\n * is the instant fix, `--models` is the discovery path, and rebuilding the\n * runtime is the reset. The trade is an invariant with no exceptions instead of\n * an invariant that holds \"unless the catalog refreshed\".\n *\n * @module ai-agent-sdk/providers/copilot/router\n */\n\nimport { AgentSdkError } from '@alvin0/ai-agent-sdk-core'\nimport { OPENAI_CHAT_COMPLETIONS_PROTOCOL_ID } from '@alvin0/ai-agent-sdk-protocol-openai-chat-completions'\nimport { OPENAI_RESPONSES_PROTOCOL_ID } from '@alvin0/ai-agent-sdk-protocol-responses'\nimport type { CopilotEndpoint, CopilotGenerationModel } from './catalog.ts'\nimport { COPILOT_ERROR_CODES } from './errors.ts'\n\n/**\n * Re-exported from `./catalog.ts`, where the union is declared.\n *\n * The declaration lives there because this module imports\n * {@link CopilotGenerationModel} from it, so the source edge already runs\n * router → catalog and declaring the union here would make it bidirectional.\n * The re-export keeps this module the one a reader opens for endpoint selection.\n */\nexport type { CopilotEndpoint } from './catalog.ts'\n\n/**\n * Model id prefixes dispatched to `/responses` when the catalog says nothing.\n *\n * Exported and overridable for the same reason `COPILOT_EDITOR_VERSION` is: this\n * is a fact about a remote endpoint that WILL go stale, and a user has to be able\n * to correct it without waiting for a release.\n * `CopilotProviderOptions.responsesModelPrefixes` ADDS to this list rather than\n * replacing it, so an override cannot silently drop the prefixes shipped here.\n *\n * Kept deliberately short. A prefix that matches too much pushes models toward\n * the endpoint where guessing wrong costs the request (see the module note), so\n * an absent prefix is the cheaper error.\n */\nexport const COPILOT_RESPONSES_MODEL_PREFIXES: readonly string[] = Object.freeze([\n 'codex-',\n 'gpt-5',\n])\n\n/** The endpoint chosen for one model id, and who chose it. */\nexport interface CopilotEndpointDecision {\n /** The model id the decision is keyed by, verbatim as the caller spelled it. */\n readonly model: string\n /** The endpoint the request goes to. */\n readonly endpoint: CopilotEndpoint\n /** Wire protocol id of the sub-protocol that serves {@link endpoint}. */\n readonly protocolId: string\n /**\n * Which step of the decision order produced this.\n *\n * Reported to observation (Requirement 9.8) so that when a model runs against\n * the wrong endpoint, the log says who decided rather than leaving an operator\n * to reconstruct it.\n */\n readonly source: 'override' | 'catalog' | 'allowlist' | 'default'\n}\n\n/** Memoized, append-only endpoint selection for one adapter instance. */\nexport interface CopilotEndpointRouter {\n /**\n * Decide the endpoint for a model id.\n *\n * MEMOIZED AND APPEND-ONLY: a key that already has a decision is returned\n * unchanged and never recomputed. This is the mechanism that makes\n * Requirement 9.7 hold — no code path can change its mind between two retries.\n * @param modelId - the wire model id of the request being dispatched.\n * @returns the decision for that model, recording it on first sight.\n */\n decide(modelId: string): CopilotEndpointDecision\n /**\n * Feed in discovered catalog metadata.\n *\n * Adds ONLY keys that have no decision yet; an id already decided is skipped\n * even when the metadata now disagrees with the recorded decision.\n * @param models - the generation half of a {@link CopilotGenerationModel} list.\n */\n learn(models: readonly CopilotGenerationModel[]): void\n /**\n * Every decision recorded so far, in the order it was recorded.\n * @returns a frozen snapshot, for `--models` and for the conformance harness.\n */\n snapshot(): readonly CopilotEndpointDecision[]\n}\n\n/** Construction options for {@link createCopilotEndpointRouter}. */\nexport interface CopilotEndpointRouterOptions {\n /**\n * Endpoints pinned by the application, keyed by model id. Wins over every\n * other source, including a catalog disclosure that contradicts it.\n */\n readonly overrides?: Readonly<Record<string, CopilotEndpoint>>\n /**\n * The full responses-prefix allowlist to use.\n *\n * The caller passes the already-merged list — `copilotAdapter` spreads\n * {@link COPILOT_RESPONSES_MODEL_PREFIXES} first and the user's additions\n * after — so the \"adds, never replaces\" rule is visible at the call site\n * instead of hidden in here. Defaults to the shipped list when omitted.\n */\n readonly prefixes?: readonly string[]\n}\n\n/** The two endpoints, so an override value can be checked against something. */\nconst COPILOT_ENDPOINTS: readonly CopilotEndpoint[] = Object.freeze([\n 'responses',\n 'chat-completions',\n])\n\n/** Protocol id per endpoint, the one place the two are tied together. */\nconst PROTOCOL_IDS: Readonly<Record<CopilotEndpoint, string>> = Object.freeze({\n 'responses': OPENAI_RESPONSES_PROTOCOL_ID,\n 'chat-completions': OPENAI_CHAT_COMPLETIONS_PROTOCOL_ID,\n})\n\n/**\n * Build a router for one adapter instance.\n *\n * Overrides are validated HERE, not at dispatch: a typo in\n * `endpointOverrides` is a configuration mistake, and a configuration mistake\n * that surfaces while building the provider is cheaper than one that surfaces on\n * the first request to one particular model.\n * @param options - overrides and the merged prefix allowlist.\n * @returns a router whose `decisions` map only ever grows.\n * @throws AgentSdkError with `COPILOT_ENDPOINT_OVERRIDE_INVALID` when an\n * override pins an endpoint that does not exist.\n */\nexport function createCopilotEndpointRouter(\n options: CopilotEndpointRouterOptions = {},\n): CopilotEndpointRouter {\n const overrides = validateOverrides(options.overrides ?? {})\n const prefixes = normalizePrefixes(options.prefixes ?? COPILOT_RESPONSES_MODEL_PREFIXES)\n // Insertion-ordered, and only ever written through `record`.\n const decisions = new Map<string, CopilotEndpointDecision>()\n\n /** Write a decision for a key that has none. The single mutation point. */\n const record = (\n modelId: string,\n endpoint: CopilotEndpoint,\n source: CopilotEndpointDecision['source'],\n ): CopilotEndpointDecision => {\n const decision: CopilotEndpointDecision = Object.freeze({\n model: modelId,\n endpoint,\n protocolId: PROTOCOL_IDS[endpoint],\n source,\n })\n decisions.set(modelId, decision)\n return decision\n }\n\n /**\n * The decision order for a key with no recorded decision.\n *\n * `declared` is `undefined` for {@link CopilotEndpointRouter.decide}, because a\n * bare dispatch carries no catalog metadata — a disclosure only arrives through\n * {@link CopilotEndpointRouter.learn}. `undefined` is UNKNOWN, so it falls\n * through to the allowlist; `'chat-completions'` is a stated fact and stops\n * there with `source: 'catalog'`.\n */\n const resolve = (\n modelId: string,\n declared: CopilotEndpoint | undefined,\n ): CopilotEndpointDecision => {\n const override = overrides[modelId]\n if (override !== undefined) return record(modelId, override, 'override')\n if (declared !== undefined) return record(modelId, declared, 'catalog')\n if (matchesPrefix(modelId, prefixes)) return record(modelId, 'responses', 'allowlist')\n return record(modelId, 'chat-completions', 'default')\n }\n\n return Object.freeze({\n decide(modelId: string): CopilotEndpointDecision {\n return decisions.get(modelId) ?? resolve(modelId, undefined)\n },\n learn(models: readonly CopilotGenerationModel[]): void {\n for (const entry of models) {\n const modelId = entry.model.id\n if (decisions.has(modelId)) continue\n resolve(modelId, entry.declaredEndpoint)\n }\n },\n snapshot(): readonly CopilotEndpointDecision[] {\n return Object.freeze([...decisions.values()])\n },\n })\n}\n\n/**\n * Copy the overrides and reject any value that is not an endpoint.\n *\n * A copy rather than the caller's object, so a later mutation of what was passed\n * in cannot introduce an unvalidated endpoint after construction.\n */\nfunction validateOverrides(\n overrides: Readonly<Record<string, CopilotEndpoint>>,\n): Readonly<Record<string, CopilotEndpoint>> {\n const validated: Record<string, CopilotEndpoint> = Object.create(null)\n for (const [modelId, endpoint] of Object.entries(overrides)) {\n if (!COPILOT_ENDPOINTS.includes(endpoint)) {\n throw new AgentSdkError(\n `Copilot endpoint override for model '${modelId}' must be one of ` +\n `${COPILOT_ENDPOINTS.map((value) => `'${value}'`).join(', ')}`,\n COPILOT_ERROR_CODES.ENDPOINT_OVERRIDE_INVALID,\n )\n }\n validated[modelId] = endpoint\n }\n return Object.freeze(validated)\n}\n\n/**\n * Lower-case the prefixes and drop the ones that cannot select anything.\n *\n * An empty string is dropped rather than honoured: as a prefix it matches every\n * model id, which would route the whole catalog to `/responses` — the direction\n * where guessing wrong costs the request. Dropping it leaves the shipped\n * prefixes intact, which is what a caller adding to the list asked for.\n */\nfunction normalizePrefixes(prefixes: readonly string[]): readonly string[] {\n const normalized: string[] = []\n for (const prefix of prefixes) {\n if (typeof prefix !== 'string' || prefix.length === 0) continue\n const lower = prefix.toLowerCase()\n if (!normalized.includes(lower)) normalized.push(lower)\n }\n return Object.freeze(normalized)\n}\n\n/** Case-insensitive prefix match; Copilot model ids are lower-case in practice. */\nfunction matchesPrefix(modelId: string, prefixes: readonly string[]): boolean {\n const lower = modelId.toLowerCase()\n return prefixes.some((prefix) => lower.startsWith(prefix))\n}\n","/**\n * The Copilot provider: the Copilot API surface, authenticated with a GitHub user\n * token this project's own credential store holds.\n *\n * ## Configured, not subclassed\n *\n * `copilotAdapter` is built with `createRuntimeHttpProvider` and extends nothing\n * (Requirement 7.1). Everything Copilot needs beyond a plain API-key provider —\n * a two-tier credential, a token exchange with its own cache, two wire protocols\n * on one route, endpoint-driven discovery — is expressed as DATA:\n * `auth: { kind: 'dynamic' }` for the credential path, a composite protocol for\n * the two endpoints, `discoverModels` for the catalog. That is the point of the\n * exercise: the configuration path is proven by the provider with the most\n * demanding requirements in this repository, not by the simplest one.\n *\n * ## Client identity\n *\n * `COPILOT_EDITOR_VERSION` and `COPILOT_EDITOR_PLUGIN_VERSION` are two of the\n * three `Client_Identity_Constants` in this package; the third is\n * `COPILOT_OAUTH_CLIENT_ID` in `./oauth.ts`. Their defaults make this SDK\n * identify itself AS AN EDITOR CLIENT on every request to the Copilot surface.\n *\n * They are EXPORTED, OVERRIDABLE constants — not hidden values — precisely\n * because of that: presenting as another client is something the caller should be\n * able to read off the source and change without forking, so each one is a named\n * option (`editorHeaders`) with a visible default. Same reason\n * `CODEX_CLIENT_VERSION` is an exported constant in `provider-codex`. All three\n * values will also go stale, which is a second reason to keep them where a caller\n * can reach them.\n *\n * Use your own account, and prefer a provider's official first-party surface for\n * production. The README and the \"Client identity\" section of the docs carry the\n * full tradeoff.\n *\n * @module ai-agent-sdk/providers/copilot/adapter\n */\n\nimport { AgentSdkError, MISSING_CREDENTIAL_CODE, type RetryPolicyConfig } from '@alvin0/ai-agent-sdk-core'\nimport {\n defineModelProviderPlugin,\n type ComposableModelProviderPlugin,\n type CredentialOperationOptions,\n type ModelTarget,\n type SdkLogger,\n} from '@alvin0/ai-agent-sdk-core/provider'\nimport {\n createRuntimeHttpProvider,\n type HttpModelAdapter,\n type ProviderCatalogModel,\n type ProviderRequestLogger,\n type RuntimeModelDiscoveryContext,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport { openAiChatCompletionsProtocol } from '@alvin0/ai-agent-sdk-protocol-openai-chat-completions'\nimport { openAiResponsesProtocol } from '@alvin0/ai-agent-sdk-protocol-responses'\nimport { requireGitHubToken, type CopilotCredentialSnapshot } from './auth.ts'\nimport {\n copilotCatalogCacheOptions,\n discoverCopilotModels,\n resolveCopilotCatalogLimits,\n type CopilotEndpoint,\n} from './catalog.ts'\nimport { COPILOT_ERROR_CODES } from './common/error-codes.ts'\nimport {\n COPILOT_BASE_URL,\n resolveCopilotEditorHeaders,\n type CopilotEditorHeaders,\n} from './common/identity.ts'\nimport { captureCopilotStore, type CapturedCopilotStore } from './common/store-capture.ts'\nimport type {\n CopilotAuthFile,\n CopilotAuthStore,\n CopilotCredentialStore,\n} from './common/store-types.ts'\nimport {\n copilotDualProtocol,\n type CopilotDialect,\n} from './dual-protocol.ts'\nimport { createCopilotTokenCache, type CopilotTokenCache } from './exchange.ts'\nimport {\n COPILOT_RESPONSES_MODEL_PREFIXES,\n createCopilotEndpointRouter,\n type CopilotEndpointDecision,\n} from './router.ts'\n\n/**\n * The Copilot API base (Requirement 2.1).\n *\n * Declared in `./common/identity.ts` and re-exported here; that module's note\n * explains why the value sits in the leaf layer while this module stays the door\n * a reader opens.\n */\nexport { COPILOT_BASE_URL } from './common/identity.ts'\n\n/**\n * Default `Editor-Version`.\n *\n * NOT cosmetic: with either editor header missing the endpoint answers HTTP 400\n * and no request runs at all. This is also where the SDK identifies itself as an\n * editor client — see the module note for why it is a named option.\n *\n * ✔ CONFIRMED accepted on a live Copilot account on 2026-09-10 (`sku`\n * `free_educational_quota`). Sent as the only editor headers, together with\n * `COPILOT_EDITOR_PLUGIN_VERSION`, on all four live calls, and none answered\n * HTTP 400: `GET https://api.github.com/copilot_internal/v2/token` → 200,\n * `GET /models` → 200, a streaming `/chat/completions` on `gpt-4o-mini` → a\n * complete stream, `POST /embeddings` → 200.\n *\n * Confirmed, not permanent: this constant's first failure mode is going stale, so\n * re-run those four calls when the surface starts answering 400. The procedure is\n * exactly the one above — an editor header the endpoint rejects and one it never\n * received both surface as HTTP 400.\n */\nexport { COPILOT_EDITOR_VERSION } from './common/identity.ts'\n\n/**\n * Default `Editor-Plugin-Version`.\n *\n * Same contract as `COPILOT_EDITOR_VERSION`: mandatory, and part of the client\n * identity this SDK presents.\n *\n * ✔ CONFIRMED accepted on a live Copilot account on 2026-09-10, in the same run\n * that confirmed `COPILOT_EDITOR_VERSION` — both headers travel on every request,\n * so the one run confirms the pair. See that constant for the four calls and\n * their statuses.\n */\nexport { COPILOT_EDITOR_PLUGIN_VERSION } from './common/identity.ts'\n\n/** Overrides for the two editor headers; each field is independent. */\nexport type { CopilotEditorHeaders } from './common/identity.ts'\n\n/**\n * The Copilot dialect and its two projections, declared in `./dual-protocol.ts`\n * and re-exported here.\n *\n * The design's file map puts them in this module and DD-2 puts ownership with the\n * composite; both hold, because `copilotAdapter` BUILDS the composite. The source\n * edge therefore already runs adapter → dual-protocol, and declaring the runtime\n * projections here would make it bidirectional, which the repo's\n * circular-dependency check forbids. Same shape as `./router.ts` re-exporting\n * `CopilotEndpoint` from `./catalog.ts`.\n */\nexport {\n COPILOT_DEFAULT_DIALECT,\n toChatCompletionsDialect,\n toResponsesDialect,\n} from './dual-protocol.ts'\nexport type { CopilotDialect } from './dual-protocol.ts'\n\n/** Registry id, provider family, and observation label when the caller sets none. */\nexport const COPILOT_ROUTE_ID = 'copilot'\n\n/** Display name reported by the adapter and the plugin. */\nexport const COPILOT_DISPLAY_NAME = 'GitHub Copilot'\n\n/**\n * Everything a Copilot route can be configured with.\n *\n * The `authStore` is REQUIRED and injected: paths, the filesystem and the\n * environment belong to `Copilot_Node_Auth`, so a Universal package cannot supply\n * a default here (Requirement 6.1). Every other field is optional, and an absent\n * one is spread away rather than passed as `undefined` — see\n * {@link copilotAdapter}.\n */\nexport interface CopilotProviderOptions {\n /**\n * Where the credentials live: the compare-and-swap variant.\n *\n * This is the main path. {@link copilotAdapter} also accepts the read/write\n * variant through an overload; `copilotPlugin` does not, because transactional\n * registration and a store with no revisions are a poor pair.\n */\n readonly authStore: CopilotCredentialStore\n /** Endpoint base; defaults to `COPILOT_BASE_URL`. */\n readonly baseUrl?: string\n /**\n * Permit a cleartext `http:` base URL.\n *\n * Explicit opt-in rather than a lenient default, because every request to this\n * surface carries a bearer token (Requirement 2.2).\n */\n readonly allowInsecureHttp?: boolean\n /** Overrides for the two mandatory editor headers (Requirement 2.4). */\n readonly editorHeaders?: CopilotEditorHeaders\n /** Pin an endpoint for specific model ids, overriding the router (Requirement 9.6). */\n readonly endpointOverrides?: Readonly<Record<string, CopilotEndpoint>>\n /**\n * Extra model-id prefixes treated as `/responses`-capable when the catalog says\n * nothing.\n *\n * ADDS to `COPILOT_RESPONSES_MODEL_PREFIXES`; it cannot replace it, so an\n * override never silently drops a prefix this package ships.\n */\n readonly responsesModelPrefixes?: readonly string[]\n /** Synchronous, best-effort observer of every endpoint decision (Requirement 9.8). */\n readonly onEndpointDecision?: (decision: CopilotEndpointDecision) => void\n /**\n * A token cache shared with other routes.\n *\n * Pass one cache to several routes backed by the SAME credential and they\n * exchange once between them instead of once each.\n */\n readonly tokenCache?: CopilotTokenCache\n /** Exchange this long before the API token expires. */\n readonly exchangeMarginMs?: number\n /** GitHub API base, where the token exchange lives; pinned as its own origin. */\n readonly githubApiBaseUrl?: string\n\n /**\n * The model catalog.\n *\n * Left undefined, the adapter DISCOVERS it: which models an account may call\n * depends on its plan, its organisation policy and the editor identity the\n * request presents, so no hardcoded list is right for two accounts at once\n * (Requirement 8.1).\n */\n readonly models?: readonly ProviderCatalogModel[]\n /** Maximum raw catalog bytes. */\n readonly maxCatalogBytes?: number\n /** Maximum catalog entries; more than this is a malformed catalog, not a truncated one. */\n readonly maxCatalogModels?: number\n /** Maximum catalog response chunks. */\n readonly maxCatalogChunks?: number\n /** Catalog request deadline. */\n readonly catalogTimeoutMs?: number\n /** How long a discovered catalog stays fresh. */\n readonly catalogTtlMs?: number\n /** How long a stale catalog may still be served while a refresh runs. */\n readonly catalogStaleTtlMs?: number\n /** How long to wait before retrying discovery after it failed. */\n readonly catalogFailureBackoffMs?: number\n\n /** Dialect overrides, merged shallowly over `COPILOT_DEFAULT_DIALECT`. */\n readonly dialect?: Partial<CopilotDialect>\n /** Output cap when neither caller nor catalog names one. */\n readonly defaultMaxTokens?: number\n /** Context capacity assumed for an uncatalogued model. */\n readonly defaultContextWindow?: number\n /** Idle bound while a stream read is outstanding. */\n readonly streamIdleTimeoutMs?: number\n /** Deadline for one request. */\n readonly requestTimeoutMs?: number\n /** Maximum serialized request bytes. */\n readonly maxRequestBytes?: number\n /** Maximum response bytes. */\n readonly maxResponseBytes?: number\n /** Maximum response chunks. */\n readonly maxResponseChunks?: number\n /** Maximum SSE events in one stream. */\n readonly maxSseEvents?: number\n /** Maximum characters in one SSE event. */\n readonly maxSseEventChars?: number\n /** Maximum bytes read from a non-success response (Requirement 13.6). */\n readonly maxErrorBodyBytes?: number\n /** Deadline granted to {@link requestLogger} before the request proceeds anyway. */\n readonly requestLoggerTimeoutMs?: number\n /** Retry policy this route owns (Requirement 7.7). */\n readonly retryPolicy?: RetryPolicyConfig\n /**\n * Exact wire-request observer.\n *\n * BEST-EFFORT: credentials are redacted by the transport, the logger's deadline\n * is `requestLoggerTimeoutMs`, and a logger that overruns or throws does not\n * stop the request (Requirement 14.5).\n */\n readonly requestLogger?: ProviderRequestLogger\n /** HTTP implementation, for tests and non-browser runtimes. */\n readonly fetch?: typeof globalThis.fetch\n\n /** Registry id; defaults to {@link COPILOT_ROUTE_ID}. */\n readonly id?: string\n /** Routes the plugin installs; defaults to `[id]`. */\n readonly routes?: readonly string[]\n /** Default model; a string form requires exactly one route (Requirement 7.5). */\n readonly defaultModel?: string | ModelTarget\n}\n\n/**\n * The same options against the read/write store variant.\n *\n * Kept for symmetry with `provider-codex` and with the two store contracts\n * (Requirement 7.3). It has no revisions, so a commit cannot be\n * compare-and-swapped — which costs nothing here, since nothing on the Copilot\n * credential path writes.\n */\nexport interface CopilotLegacyProviderOptions extends Omit<CopilotProviderOptions, 'authStore'> {\n /** Where the credentials live: the read/write variant. */\n readonly authStore: CopilotAuthStore\n}\n\n/** Never-aborting logger sink for a resolve that arrives without a context. */\nconst NULL_LOGGER: SdkLogger = Object.freeze({\n child: () => NULL_LOGGER,\n trace: () => undefined,\n debug: () => undefined,\n info: () => undefined,\n warn: () => undefined,\n error: () => undefined,\n fatal: () => undefined,\n})\n\n/** Marker written in place of a token value that appeared in a response body. */\nconst REDACTED = '[REDACTED]'\n\n/**\n * The two transport-owned headers, sent on every request (Requirement 2.3).\n *\n * `content-type` cannot come from the auth layer — `provider-http` owns the name\n * at the transport layer and refuses a second owner — so it is declared here,\n * where it is allowed and where it is visible.\n */\nconst COPILOT_TRANSPORT_HEADERS: Readonly<Record<string, string>> = Object.freeze({\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n})\n\n/** Bytes read from a non-success response when the caller configures no bound. */\nconst DEFAULT_MAX_ERROR_BODY_BYTES = 1024 * 1024\n\n/**\n * Create a Copilot adapter.\n *\n * Both store variants are accepted, and the variant is chosen by INSPECTING THE\n * MARKER through `captureCopilotStore` — which reads data properties only and\n * performs no storage I/O, so building a provider cannot run a line of the\n * caller's code (Requirement 7.3).\n * @param options - credential store, endpoint, catalog, dialect and transport settings.\n * @returns the adapter, ready to register.\n * @throws AgentSdkError with `CREDENTIAL_STORE_INVALID` when `authStore` is\n * neither store variant, or `COPILOT_ENDPOINT_OVERRIDE_INVALID` when\n * `endpointOverrides` pins an endpoint that does not exist.\n */\nexport function copilotAdapter(options: CopilotProviderOptions): HttpModelAdapter\nexport function copilotAdapter(options: CopilotLegacyProviderOptions): HttpModelAdapter\nexport function copilotAdapter(\n options: CopilotProviderOptions | CopilotLegacyProviderOptions,\n): HttpModelAdapter {\n return buildCopilotAdapter(options, captureCopilotStore(options?.authStore))\n}\n\n/**\n * The one adapter body, shared by both store variants and by the plugin.\n *\n * Four things are worth reading closely.\n *\n * **`auth.resolve` is the only place a token enters a request.** It reads the\n * store, demands a long-lived token, then asks the cache — which decides on its\n * own whether an exchange is due. `provider-http` calls `resolve` ONCE PER\n * OPERATION (Requirement 7.2), not once per retry, so the number of store reads\n * equals the number of operations and every attempt of one operation carries the\n * credential and the endpoint from a single snapshot.\n *\n * **`requireGitHubToken` runs BEFORE `cache.acquire`.** With no credential the\n * failure is `MISSING_CREDENTIAL` naming the login command, rather than an HTTP\n * error from an exchange that never had anything to exchange (Requirement 13.4).\n *\n * **`x-request-id` is the CLIENT's id, not the server's.** It exists to line up\n * two logs and carries nothing about the user.\n *\n * **`router.learn` only ADDS.** A catalog refresh never rewrites a decision that\n * already exists, which is how Requirement 9.7 holds structurally rather than by\n * convention.\n *\n * Every absent option is spread away instead of passed as `undefined`: a key\n * carrying `undefined` still overrides the runtime's own default, which turns\n * \"I did not configure this\" into \"I configured this to nothing\"\n * (Requirement 8.7).\n * @param options - the caller's options, either store variant.\n * @param captured - the already-captured store.\n * @returns the configured adapter.\n */\nfunction buildCopilotAdapter(\n options: CopilotProviderOptions | CopilotLegacyProviderOptions,\n captured: CapturedCopilotStore,\n): HttpModelAdapter {\n const router = createCopilotEndpointRouter({\n overrides: options.endpointOverrides ?? {},\n // Merged HERE so \"adds, never replaces\" is visible at the call site.\n prefixes: [...COPILOT_RESPONSES_MODEL_PREFIXES, ...(options.responsesModelPrefixes ?? [])],\n })\n const editorHeaders = resolveCopilotEditorHeaders(options.editorHeaders)\n const secrets = createCopilotSecrets()\n const catalogLimits = resolveCopilotCatalogLimits(options)\n const providerId = options.id ?? COPILOT_ROUTE_ID\n const cache = options.tokenCache ?? createCopilotTokenCache({\n providerId,\n editorHeaders,\n ...(options.githubApiBaseUrl === undefined\n ? {}\n : { githubApiBaseUrl: options.githubApiBaseUrl }),\n ...(options.exchangeMarginMs === undefined ? {} : { marginMs: options.exchangeMarginMs }),\n ...(options.requestTimeoutMs === undefined\n ? {}\n : { requestTimeoutMs: options.requestTimeoutMs }),\n ...(options.maxResponseBytes === undefined\n ? {}\n : { maxResponseBytes: options.maxResponseBytes }),\n ...(options.maxResponseChunks === undefined\n ? {}\n : { maxResponseChunks: options.maxResponseChunks }),\n ...(options.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureIssuer: options.allowInsecureHttp }),\n // The raw fetch, not the redacting wrapper: the exchange path does its own\n // bounded read and its own redaction, and it must not have its error bodies\n // rewritten by a layer that knows nothing about its classification table.\n ...(options.fetch === undefined ? {} : { fetch: options.fetch }),\n })\n const sessionId = options.dialect?.promptCacheKey ?? randomId()\n\n return createRuntimeHttpProvider<CopilotDialect>({\n displayName: COPILOT_DISPLAY_NAME,\n protocol: copilotDualProtocol({\n router,\n responses: openAiResponsesProtocol,\n chat: openAiChatCompletionsProtocol,\n ...(options.onEndpointDecision === undefined\n ? {}\n : { onDecision: options.onEndpointDecision }),\n }),\n baseUrl: options.baseUrl ?? COPILOT_BASE_URL,\n ...(options.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureHttp: options.allowInsecureHttp }),\n dialect: { ...options.dialect, promptCacheKey: sessionId },\n auth: {\n kind: 'dynamic',\n resolve: async ({ signal, context }) => {\n const operation: CredentialOperationOptions = {\n signal,\n logger: context?.logger ?? NULL_LOGGER,\n }\n const snapshot = await readCopilotSnapshot(captured, operation)\n // BEFORE the exchange: an empty store is a missing credential with a\n // command to run, not an HTTP failure.\n const github = requireGitHubToken(snapshot.file, snapshot.label)\n secrets.remember('github', github.token)\n const api = await cache.acquire(snapshot, operation, context)\n secrets.remember('api', api.token)\n return {\n authorization: `Bearer ${api.token}`,\n 'editor-version': editorHeaders.editorVersion,\n 'editor-plugin-version': editorHeaders.editorPluginVersion,\n // `content-type` is NOT returned here even though Requirement 2.3 lists\n // it among the mandatory headers: `provider-http` owns that name at the\n // TRANSPORT layer and rejects any other layer supplying it, which is a\n // good rule — one header, one owner, no last-writer-wins. It is set\n // explicitly through `baseHeaders` below rather than inherited\n // silently, so the requirement is still visible in this file.\n 'x-request-id': randomId(),\n }\n },\n },\n ...(options.models === undefined\n ? {\n discoverModels: async (\n context: RuntimeModelDiscoveryContext,\n ): Promise<readonly ProviderCatalogModel[]> => {\n const snapshot = await discoverCopilotModels(\n context,\n catalogLimits,\n options.fetch ?? globalThis.fetch,\n )\n // Adds only ids that have no decision yet (Requirement 9.7).\n router.learn(snapshot.generation)\n return snapshot.generation.map((entry) => entry.model)\n },\n }\n : { models: options.models }),\n ...copilotCatalogCacheOptions(options),\n ...(options.maxCatalogModels === undefined\n ? {}\n : { maxCatalogModels: options.maxCatalogModels }),\n ...(options.maxCatalogBytes === undefined ? {} : { maxCatalogBytes: options.maxCatalogBytes }),\n ...(options.defaultMaxTokens === undefined ? {} : { defaultMaxTokens: options.defaultMaxTokens }),\n ...(options.defaultContextWindow === undefined\n ? {}\n : { defaultContextWindow: options.defaultContextWindow }),\n ...transportLimits(options),\n // The transport-layer half of Requirement 2.3, stated rather than inherited.\n // `accept` travels with it because both names belong to the same layer and\n // supplying one of a pair while defaulting the other is how a stream ends up\n // asking for JSON.\n baseHeaders: COPILOT_TRANSPORT_HEADERS,\n ...(options.retryPolicy === undefined ? {} : { retryPolicy: options.retryPolicy }),\n ...(options.requestLogger === undefined ? {} : { requestLogger: options.requestLogger }),\n // A 400 for a missing editor header, and ONLY that, gets the Copilot code.\n errorCode: (status: number, detail: string): string | undefined =>\n isMissingEditorHeaderFailure(status, detail)\n ? COPILOT_ERROR_CODES.EDITOR_HEADERS_MISSING\n : undefined,\n fetch: copilotProviderFetch(options, secrets),\n })\n}\n\n/** Plugin options; the CAS store variant only. */\nexport type CopilotPluginOptions = CopilotProviderOptions\n\n/**\n * The transactional plugin for installing the Copilot provider.\n *\n * Composition follows `codexPlugin`: `id` defaults to {@link COPILOT_ROUTE_ID},\n * `family` is `'copilot'`, `routes` defaults to `[id]`, and a string\n * `defaultModel` requires exactly one route so the model target's provider can be\n * inferred (Requirements 7.4, 7.5).\n *\n * One difference from Codex: there is no overload per store variant. The\n * compare-and-swap store is the main path here, the read/write variant exists for\n * symmetry, and {@link copilotAdapter} is where it is accepted (Requirement 6.2).\n * The marker is checked at construction rather than at setup so a wrong store is\n * reported while the runtime is being composed, not on the first generation.\n * @param options - the same options {@link copilotAdapter} takes, CAS store only.\n * @returns a composable plugin registering one Copilot adapter.\n * @throws TypeError when `authStore` is not the compare-and-swap variant, or when\n * a string `defaultModel` is paired with anything but exactly one route.\n */\nexport function copilotPlugin(\n options: CopilotPluginOptions,\n): ComposableModelProviderPlugin & { readonly family: 'copilot' } {\n if (!isCredentialStoreInput(options?.authStore)) {\n throw new TypeError('copilotPlugin requires a Copilot credential store (the CAS variant)')\n }\n const id = options.id ?? COPILOT_ROUTE_ID\n const routes = Object.freeze([...(options.routes ?? [id])])\n return defineModelProviderPlugin({\n id,\n family: 'copilot',\n displayName: COPILOT_DISPLAY_NAME,\n routes,\n ...runtimeDefaultModel(options.defaultModel, routes),\n setup(registrar) {\n const adapter = buildCopilotAdapter(options, captureCopilotStore(options.authStore))\n const remove = registrar.registerAdapter(adapter)\n return () => {\n remove()\n return undefined\n }\n },\n }) as ComposableModelProviderPlugin & { readonly family: 'copilot' }\n}\n\n/**\n * Marker inspection only: no accessor is invoked and no method is captured.\n *\n * Full capture stays deferred to {@link buildCopilotAdapter}, so this check\n * cannot be the thing that runs the caller's code.\n * @param value - the `authStore` as passed in.\n * @returns true when it carries the credential-store marker.\n */\nfunction isCredentialStoreInput(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false\n const marker = Object.getOwnPropertyDescriptor(value, 'kind')\n return marker !== undefined && 'value' in marker && marker.value === 'credential-store'\n}\n\n/**\n * Resolve `defaultModel`, demanding one route for the string form.\n *\n * A string names a model but not a provider, and the provider is inferred from\n * the route. With two routes there is no answer, and picking the first would\n * install a default nobody chose (Requirement 7.5).\n * @param value - the caller's default model, when they set one.\n * @param routes - the routes this plugin installs.\n * @returns a one-key spread carrying `defaultModel`, or an empty one.\n * @throws TypeError when a string is paired with anything but exactly one route.\n */\nfunction runtimeDefaultModel(\n value: string | ModelTarget | undefined,\n routes: readonly string[],\n): { readonly defaultModel?: ModelTarget } {\n if (value === undefined) return {}\n if (typeof value !== 'string') return { defaultModel: value }\n if (routes.length !== 1) {\n throw new TypeError('A string defaultModel requires exactly one Copilot route')\n }\n return { defaultModel: Object.freeze({ provider: routes[0] ?? COPILOT_ROUTE_ID, id: value }) }\n}\n\n/**\n * Read the credential store once, through whichever variant was captured.\n *\n * The read/write variant has no revisions, so its snapshot revision is `null` —\n * which the token cache compares just as strictly as a real revision, it simply\n * never changes on its own.\n * @param captured - the captured store.\n * @param operation - the calling operation, whose signal bounds the read.\n * @returns the file, its revision and the store label, as one snapshot.\n * @throws AgentSdkError with the SDK's missing-credential code when the store is\n * empty (Requirement 13.4).\n */\nasync function readCopilotSnapshot(\n captured: CapturedCopilotStore,\n operation: CredentialOperationOptions,\n): Promise<CopilotCredentialSnapshot> {\n const record = captured.kind === 'versioned'\n ? await captured.store.read(operation)\n : { value: await captured.store.read(), revision: null }\n return Object.freeze({\n file: requireCopilotFile(record?.value, captured.label),\n revision: record?.revision ?? null,\n label: captured.label,\n })\n}\n\n/**\n * Demand a credential file, reusing the one message that says how to get one.\n *\n * `requireGitHubToken` owns the message and the code for all three shapes of \"no\n * credential\", so it is asked first. The throw after it is UNREACHABLE — an\n * absent file already failed there — and exists only so the type narrows without\n * a non-null assertion.\n * @param file - the file the store returned, or `undefined` for an empty store.\n * @param label - the store location named in the diagnostic.\n * @returns the file.\n */\nfunction requireCopilotFile(file: CopilotAuthFile | undefined, label: string): CopilotAuthFile {\n requireGitHubToken(file, label)\n if (file === undefined) {\n throw new AgentSdkError(\n `no GitHub Copilot credentials at ${label}`,\n MISSING_CREDENTIAL_CODE,\n )\n }\n return file\n}\n\n/**\n * The two token values currently held in memory, and the redaction that uses them.\n *\n * Two slots rather than a growing set: there is exactly one long-lived token and\n * one API token in play at a time, and a set that only ever grows would be a\n * credential leak of its own making.\n */\ninterface CopilotSecrets {\n /** Record the current value of one of the two tokens. */\n remember(kind: 'github' | 'api', value: string): void\n /** Replace every occurrence of either token with {@link REDACTED}. */\n redact(text: string): string\n}\n\n/** Build the two-slot secret registry. */\nfunction createCopilotSecrets(): CopilotSecrets {\n let github = ''\n let api = ''\n return {\n remember(kind, value): void {\n if (value.length === 0) return\n if (kind === 'github') github = value\n else api = value\n },\n redact(text): string {\n let result = text\n for (const secret of [github, api]) {\n if (secret.length === 0) continue\n result = result.split(secret).join(REDACTED)\n }\n return result\n },\n }\n}\n\n/**\n * The fetch the provider dispatches through: identical to the injected one,\n * except that an error body is redacted — and, for the one case the endpoint is\n * known to be unhelpful about, explained — before anything retains it.\n *\n * Why here and not in an error mapper: `provider-http` puts the raw error body\n * into the failure's `cause`, and by the time a mapper sees it the text is\n * already retained. Redacting at the transport is the only point that runs BEFORE\n * that, and an endpoint echoing the `Authorization` header back in an error body\n * is something that has actually happened (Requirement 13.7).\n *\n * What is deliberately NOT touched:\n *\n * - **Successful responses.** The body is a live SSE stream and must reach the\n * pipeline unread and unwrapped.\n * - **Redirects, in every shape.** Rebuilding a `Response` loses `type`,\n * `redirected` and `url` — the three signals the transport's redirect guard\n * reads — so anything that is not a 4xx/5xx passes through untouched and the\n * guard still sees the original (Requirement 7.8).\n * @param options - read for the injected fetch and the error-body bound.\n * @param secrets - the live token values to redact.\n * @returns a fetch implementation to hand to the runtime provider.\n */\nfunction copilotProviderFetch(\n options: CopilotProviderOptions | CopilotLegacyProviderOptions,\n secrets: CopilotSecrets,\n): typeof globalThis.fetch {\n const inner = options.fetch ?? globalThis.fetch\n const maxBytes = options.maxErrorBodyBytes ?? DEFAULT_MAX_ERROR_BODY_BYTES\n return async (...args: Parameters<typeof globalThis.fetch>): Promise<Response> => {\n const response = await inner(...args)\n if (response.status < 400 || response.type === 'opaqueredirect' || response.redirected) {\n return response\n }\n let raw: string\n try {\n raw = await readErrorBody(response, maxBytes)\n } catch {\n // A body that could not be read must not replace the status, which is the\n // more reliable signal anyway.\n return response\n }\n const redacted = secrets.redact(raw)\n const body = isMissingEditorHeaderFailure(response.status, redacted)\n ? editorHeaderDiagnostic(redacted)\n : redacted\n const headers = new Headers(response.headers)\n // The length changed, and a stale content-length would fail the bounded read\n // that comes next.\n headers.delete('content-length')\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n\n/**\n * Read an error body up to a byte bound, marking a truncation rather than hiding it.\n * @param response - the non-success response.\n * @param maxBytes - the configured bound (Requirement 13.6).\n * @returns the decoded text, truncated with a note when it hit the bound.\n */\nasync function readErrorBody(response: Response, maxBytes: number): Promise<string> {\n if (response.body === null) return ''\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let bytes = 0\n let text = ''\n try {\n while (true) {\n const next = await reader.read()\n if (next.done) return text + decoder.decode()\n if (next.value === undefined) continue\n const remaining = maxBytes - bytes\n if (remaining <= 0 || next.value.byteLength > remaining) {\n const kept = remaining <= 0 ? undefined : next.value.subarray(0, remaining)\n const partial = kept === undefined ? '' : decoder.decode(kept, { stream: true })\n await reader.cancel().catch(() => undefined)\n return `${text}${partial}${decoder.decode()}\\n[error body truncated at ${maxBytes} bytes]`\n }\n bytes += next.value.byteLength\n text += decoder.decode(next.value, { stream: true })\n }\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Whether a failure looks like the endpoint refusing a request for a missing\n * editor header.\n *\n * Matched BROADLY on purpose. The endpoint's wording is not a contract — it is\n * one sentence that can be rephrased at any time — so this looks for the header\n * names in any plausible spelling, or for the word \"editor\" beside a complaint\n * about a header. It is also bounded to status 400: a 400 that says nothing about\n * editors keeps `REQUEST_INVALID` from the shared mapping rather than being\n * relabelled into a Copilot-specific failure it is not (Requirement 2.5).\n * @param status - the response status.\n * @param detail - the provider's error text, joined by the shared parser.\n * @returns true when the missing-header diagnosis is warranted.\n */\nfunction isMissingEditorHeaderFailure(status: number, detail: string): boolean {\n if (status !== 400) return false\n if (/editor[\\s_-]*(?:plugin[\\s_-]*)?version/i.test(detail)) return true\n return /\\beditor\\b/i.test(detail)\n && /(missing|required|absent|invalid|unsupported|unrecogni[sz]ed|header)/i.test(detail)\n}\n\n/**\n * Wrap the endpoint's 400 in a body that names both headers and how to set them.\n *\n * The endpoint's own text is kept beside it rather than replaced: it is the\n * evidence, and the shared classifier reads it too.\n * @param endpointText - the endpoint's error body, already redacted.\n * @returns a JSON error body carrying the SDK-authored diagnosis.\n */\nfunction editorHeaderDiagnostic(endpointText: string): string {\n return JSON.stringify({\n error: {\n code: COPILOT_ERROR_CODES.EDITOR_HEADERS_MISSING,\n message: 'the Copilot endpoint rejected this request for a missing or unaccepted editor '\n + 'header. Both `Editor-Version` and `Editor-Plugin-Version` are mandatory; configure '\n + 'them with the `editorHeaders` option (`editorVersion`, `editorPluginVersion`), whose '\n + 'defaults are the exported COPILOT_EDITOR_VERSION and '\n + `COPILOT_EDITOR_PLUGIN_VERSION constants. The endpoint said: ${endpointText}`,\n },\n })\n}\n\n/**\n * Forward every transport bound the caller set, and only those.\n * @param options - the caller's options.\n * @returns an object carrying the configured transport limits.\n */\nfunction transportLimits(\n options: CopilotProviderOptions | CopilotLegacyProviderOptions,\n): Readonly<Record<string, number>> {\n return {\n ...(options.streamIdleTimeoutMs === undefined\n ? {}\n : { streamIdleTimeoutMs: options.streamIdleTimeoutMs }),\n ...(options.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: options.requestTimeoutMs }),\n ...(options.maxRequestBytes === undefined ? {} : { maxRequestBytes: options.maxRequestBytes }),\n ...(options.maxResponseBytes === undefined ? {} : { maxResponseBytes: options.maxResponseBytes }),\n ...(options.maxResponseChunks === undefined\n ? {}\n : { maxResponseChunks: options.maxResponseChunks }),\n ...(options.maxSseEvents === undefined ? {} : { maxSseEvents: options.maxSseEvents }),\n ...(options.maxSseEventChars === undefined ? {} : { maxSseEventChars: options.maxSseEventChars }),\n ...(options.maxErrorBodyBytes === undefined\n ? {}\n : { maxErrorBodyBytes: options.maxErrorBodyBytes }),\n ...(options.requestLoggerTimeoutMs === undefined\n ? {}\n : { requestLoggerTimeoutMs: options.requestLoggerTimeoutMs }),\n }\n}\n\n/** A client-side correlation id; carries nothing about the account or the prompt. */\nfunction randomId(): string {\n return globalThis.crypto?.randomUUID?.() ?? `sdk-${Date.now().toString(36)}`\n}\n","/**\n * The OAuth device flow behind the Copilot credential file.\n *\n * Device code rather than a browser redirect for the same reason the Codex\n * provider chose it: this SDK has no business binding a localhost port, and the\n * flow has to work over SSH, in containers, and in CI with no callback server.\n *\n * ## Client identity\n *\n * `COPILOT_OAUTH_CLIENT_ID` is one of three `Client_Identity_Constants` in this\n * package — the other two are `COPILOT_EDITOR_VERSION` and\n * `COPILOT_EDITOR_PLUGIN_VERSION` in `./adapter.ts`. All three default to values\n * that make this SDK identify itself AS AN EDITOR CLIENT when it signs in and\n * when it calls the Copilot surface. That is not a side effect; it is what makes\n * the surface answer at all, because `copilot_internal/v2/token` only accepts a\n * token minted by an OAuth App on GitHub's allowlist and a personal access token\n * cannot stand in for one.\n *\n * Because presenting as another client is a decision the caller should be able\n * to see and change, all three are EXPORTED, OVERRIDABLE constants rather than\n * hidden values buried in a request builder — the same reason\n * `CODEX_CLIENT_VERSION` is an exported constant in `provider-codex`. Each also\n * has a matching named option (`clientId` here, `editorHeaders` on the adapter),\n * so overriding one needs no fork. See the README and the \"Client identity\"\n * section of the docs for the full tradeoff, and prefer a provider's official\n * first-party surface for production.\n *\n * ## The two legs, and what is load-bearing about each\n *\n * ```text\n * POST {issuer}/login/device/code → { device_code, user_code,\n * Accept: application/json verification_uri, expires_in, interval }\n *\n * POST {issuer}/login/oauth/access_token → { access_token, token_type, scope }\n * Accept: application/json or HTTP 200 { error, interval? }\n * ```\n *\n * `Accept: application/json` is mandatory on BOTH legs, and the error channel on\n * the second leg is an HTTP 200 carrying `error` — see\n * {@link pollForCopilotToken} for why each of those changes the shape of the\n * code rather than just its headers.\n *\n * @module ai-agent-sdk/providers/copilot/oauth\n */\n\nimport type { CredentialOperationOptions, SdkLogger } from '@alvin0/ai-agent-sdk-core/provider'\nimport {\n copilotFetch,\n copilotUrl,\n issuerOf,\n readCopilotResponseText,\n type CopilotHttpOptions,\n type CopilotRequest,\n} from './common/http.ts'\nimport { captureCopilotStore, type CapturedCopilotStore } from './common/store-capture.ts'\nimport type {\n CopilotAccountIdentity,\n CopilotAuthFile,\n CopilotAuthStore,\n CopilotCredentialStore,\n} from './common/store-types.ts'\nimport {\n CopilotDeviceLoginError,\n credentialFailure,\n type CopilotDeviceLoginReason,\n} from './errors.ts'\n\n/** GitHub's OAuth issuer. */\nexport const DEFAULT_COPILOT_OAUTH_ISSUER = 'https://github.com'\n\n/**\n * Default OAuth client id. Public, not a secret.\n *\n * This is the client id published in GitHub's own editor-plugin sources (the\n * value `copilot.vim` and the other Copilot editor integrations ship in the\n * clear), which is why it is on the allowlist that\n * `copilot_internal/v2/token` checks. Sending it means this SDK signs in AS that\n * editor client. See the module note for why that makes it a named option\n * instead of a hidden constant.\n *\n * ⚠ UNVERIFIED against a live account. Recorded 2026-09-10 from public editor\n * integration sources only; no sign-in against a real Copilot account has\n * confirmed THIS client id.\n *\n * The 2026-09-10 live run that confirmed the two editor headers did NOT confirm\n * this value, and could not: it was handed an existing `ghu_` user-to-server token\n * out of band, so it exercised the EXCHANGE (which answered 200 for that token)\n * while never running the device flow that would put this `client_id` on the wire.\n * What that run does establish is the shape of the claim still outstanding — the\n * exchange endpoint and the allowlist check are live and reachable, and the only\n * untested link is whether they accept a token minted by this particular app.\n *\n * TODO(copilot-identity): confirm on a real Copilot account, then replace this\n * warning with the confirmation date. To confirm: run the device flow against\n * `https://github.com/login/device/code` with this `client_id` and\n * `scope=read:user`, approve it on a Copilot-enabled account, then exchange the\n * resulting user token at `GET https://api.github.com/copilot_internal/v2/token`.\n * The client id is confirmed when that exchange returns a Copilot token rather\n * than 401/403. A non-allowlisted client id fails at the exchange, not at\n * sign-in, so the device flow succeeding on its own proves nothing — and equally,\n * an exchange that succeeds for a token this flow did not mint proves nothing\n * about this constant.\n */\nexport const COPILOT_OAUTH_CLIENT_ID = 'Iv1.b507a08c87ecfe98'\n\n/** Requested scope; enough to exchange a token and read identity, no more. */\nexport const COPILOT_OAUTH_SCOPE = 'read:user'\n/**\n * The absolute ceiling on one device login, INDEPENDENT of the server's\n * `expires_in`.\n *\n * `expires_in` is honoured when it is shorter — there is no point polling a code\n * the server has already retired. It is not honoured when it is longer: a server\n * that answers `expires_in: 86400` would otherwise hang a CLI for a day, and this\n * SDK is not the right place to hold that terminal hostage (Requirement 4.3).\n */\nexport const COPILOT_DEVICE_CODE_MAX_WAIT_MS = 15 * 60 * 1_000\n\n/** Poll interval used when the device-code response states none. */\nexport const COPILOT_DEFAULT_POLL_INTERVAL_SECONDS = 5\n\n/**\n * Seconds added on every `slow_down`, per RFC 8628 §3.5.\n *\n * The increment is what makes the wait STRICTLY increase even when the server\n * repeats `slow_down` without a new `interval`. Without it, a server that only\n * ever says \"slow down\" would be polled at exactly the rate it just objected to.\n */\nexport const COPILOT_SLOW_DOWN_INCREMENT_SECONDS = 5\n\n/**\n * The warning shown beside the user code, worded exactly as the Codex device\n * prompt words it.\n *\n * A device code is a bearer of authorization that the user types into a page they\n * navigated to themselves. The one attack that works is getting somebody to type\n * an attacker's code, so the prompt has to say so; and it lives here rather than\n * in the CLI so every front end that renders a Copilot prompt renders the same\n * sentence.\n */\nexport const COPILOT_DEVICE_LOGIN_WARNING\n = 'Only continue if YOU started this login. If someone sent you this code, stop.'\n\n/** GitHub's device-authorization leg. */\nconst DEVICE_CODE_PATH = '/login/device/code'\n\n/** GitHub's device-token leg. */\nconst DEVICE_TOKEN_PATH = '/login/oauth/access_token'\n\n/** The device-code grant type, spelled as RFC 8628 requires. */\nconst DEVICE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code'\n\n/** The command that produces a credential, named when a login ends without one. */\nconst COPILOT_LOGIN_COMMAND = 'npm run provider:copilot:login-device'\n\nconst NEVER_ABORTED_SIGNAL = new AbortController().signal\n\nconst NULL_LOGGER: SdkLogger = Object.freeze({\n child: () => NULL_LOGGER,\n trace: () => undefined,\n debug: () => undefined,\n info: () => undefined,\n warn: () => undefined,\n error: () => undefined,\n fatal: () => undefined,\n})\n\n/**\n * The scheduler the poll loop waits on, injectable so no test waits real time.\n *\n * `now` travels with the timer rather than sitting in a second option, because\n * the two are read together on every iteration: a fake timer that advances\n * pending callbacks while `Date.now()` stands still would let a test satisfy the\n * 15-minute bound by accident, in either direction. Handing both through one\n * object makes \"virtual clock\" a single substitution (Properties 11, 12, 14).\n */\nexport interface CopilotTimer {\n /** Schedule `handler` after `ms`; returns whatever handle `clear` accepts. */\n readonly setTimeout: (handler: () => void, ms: number) => unknown\n /** Cancel a handle from {@link CopilotTimer.setTimeout}. */\n readonly clearTimeout: (handle: unknown) => void\n /** Current time in epoch milliseconds. */\n readonly now: () => number\n}\n\n/** The real scheduler: `setTimeout`, `clearTimeout` and `Date.now`. */\nexport const DEFAULT_COPILOT_TIMER: CopilotTimer = Object.freeze({\n setTimeout: (handler: () => void, ms: number) => setTimeout(handler, ms),\n clearTimeout: (handle: unknown) => clearTimeout(handle as ReturnType<typeof setTimeout>),\n now: () => Date.now(),\n})\n\n/**\n * Settings for both device-flow legs.\n *\n * Extends {@link CopilotHttpOptions}, so the issuer pin, the deadline and the two\n * read bounds are the same ones every other Copilot call site uses\n * (Requirement 4.7). `oauthIssuer` is named rather than called `issuer` because\n * this package pins THREE origins independently and the field name is what says\n * which one is being set.\n */\nexport interface CopilotOAuthOptions extends CopilotHttpOptions {\n /** OAuth issuer base URL; defaults to {@link DEFAULT_COPILOT_OAUTH_ISSUER}. */\n readonly oauthIssuer?: string\n /** OAuth client id; defaults to {@link COPILOT_OAUTH_CLIENT_ID}. */\n readonly clientId?: string\n /** Requested scope; defaults to {@link COPILOT_OAUTH_SCOPE}. */\n readonly scope?: string\n /** Scheduler for the poll wait; defaults to {@link DEFAULT_COPILOT_TIMER}. */\n readonly timer?: CopilotTimer\n}\n\n/** A pending device authorization the user has to approve. */\nexport interface CopilotDeviceCode {\n /** URL to open in a browser. Displayed to the user; never fetched by this SDK. */\n readonly verificationUrl: string\n /** One-time code the user types there. */\n readonly userCode: string\n /** Opaque handle this SDK polls with. Never shown to the user. */\n readonly deviceCode: string\n /** Seconds to wait between polls, as the server asked. */\n readonly intervalSeconds: number\n /** Seconds until the server retires the code. */\n readonly expiresInSeconds: number\n}\n\n/** Progress reported while a device login runs. */\nexport interface CopilotLoginProgress {\n /** The code is ready; show it, with {@link COPILOT_DEVICE_LOGIN_WARNING}. */\n readonly onPrompt?: (code: CopilotDeviceCode) => void\n /** Called before each poll, with the interval currently in effect. */\n readonly onPoll?: (elapsedMs: number, intervalSeconds: number) => void\n}\n\n/** Result of a completed device login. */\nexport interface CopilotLoginResult {\n /** Where the credential was written. Always present. */\n readonly location: string\n /** GitHub login, when the endpoint discloses one. */\n readonly login: string | undefined\n /** Numeric account id, when the endpoint discloses one. */\n readonly accountId: number | undefined\n /** Granted scope, when the endpoint discloses it. */\n readonly scope: string | undefined\n}\n\ntype AnyCopilotStore = CopilotAuthStore | CopilotCredentialStore\n\ninterface CopilotStoreSnapshot {\n readonly file: CopilotAuthFile | undefined\n readonly revision: string | null\n}\n\n/** What the token leg hands back once the user approves. */\ninterface CopilotAccessToken {\n readonly accessToken: string\n readonly tokenType: string | undefined\n readonly scope: string | undefined\n readonly account: CopilotAccountIdentity | undefined\n}\n\n/**\n * Start a device authorization.\n *\n * `Accept: application/json` is set here as well as on the token leg. It is\n * load-bearing on the token leg (see {@link pollForCopilotToken}) and harmless\n * here, and setting it on both keeps the pair from drifting into \"one of the two\n * legs parses JSON\".\n * @param options - issuer, client id, scope, cancellation and read bounds.\n * @returns the code, the URL and the timings to show the user.\n * @throws CopilotDeviceLoginError with `reason: 'aborted'` when the caller's\n * signal aborts, or `reason: 'failed'` when the endpoint answers with anything\n * other than a usable device authorization.\n */\nexport async function requestCopilotDeviceCode(\n options: CopilotOAuthOptions = {},\n): Promise<CopilotDeviceCode> {\n const pinned = issuerOf('oauthIssuer', options.oauthIssuer, DEFAULT_COPILOT_OAUTH_ISSUER, options)\n const body = await deviceJson(\n {\n pinned,\n url: copilotUrl(pinned, DEVICE_CODE_PATH),\n operation: 'device code',\n init: {\n method: 'POST',\n headers: { accept: 'application/json', 'content-type': 'application/json' },\n body: JSON.stringify({\n client_id: options.clientId ?? COPILOT_OAUTH_CLIENT_ID,\n scope: options.scope ?? COPILOT_OAUTH_SCOPE,\n }),\n },\n },\n 'the device-code endpoint',\n options,\n )\n if (!body.ok) {\n throw deviceFailure(\n `the device-code endpoint failed (HTTP ${body.status})`,\n 'failed',\n body.parseError,\n )\n }\n return Object.freeze({\n verificationUrl: verificationUrlOf(body.json, options),\n userCode: requireDeviceString(body.json, 'user_code'),\n deviceCode: requireDeviceString(body.json, 'device_code'),\n intervalSeconds: positiveSecondsOf(body.json.interval, COPILOT_DEFAULT_POLL_INTERVAL_SECONDS),\n expiresInSeconds: positiveSecondsOf(\n body.json.expires_in,\n COPILOT_DEVICE_CODE_MAX_WAIT_MS / 1_000,\n ),\n })\n}\n\n/**\n * Run a full device login and persist the resulting `GitHub_User_Token`.\n *\n * The store is read BEFORE the flow starts, so the commit carries the revision\n * that was current when the login began and a concurrent login loses the race\n * loudly instead of silently overwriting. Nothing else is written: the\n * `GitHub_User_Token` does not rotate, so this is the only write in the whole\n * Copilot credential path.\n * @param store - the credential store to write, in either variant.\n * @param options - issuer, client id, scope, cancellation, bounds and timer.\n * @param progress - prompt and poll notifications for a CLI to render.\n * @returns the store location plus whatever identity the endpoint disclosed.\n * @throws CopilotDeviceLoginError with `reason` distinguishing `denied`,\n * `expired`, `timeout`, `aborted` and `failed`.\n */\nexport function runCopilotDeviceLogin(\n store: CopilotCredentialStore,\n options?: CopilotOAuthOptions,\n progress?: CopilotLoginProgress,\n): Promise<CopilotLoginResult>\nexport function runCopilotDeviceLogin(\n store: CopilotAuthStore,\n options?: CopilotOAuthOptions,\n progress?: CopilotLoginProgress,\n): Promise<CopilotLoginResult>\nexport async function runCopilotDeviceLogin(\n store: AnyCopilotStore,\n options: CopilotOAuthOptions = {},\n progress: CopilotLoginProgress = {},\n): Promise<CopilotLoginResult> {\n const captured = captureCopilotStore(store)\n const operation: CredentialOperationOptions = {\n signal: options.signal ?? NEVER_ABORTED_SIGNAL,\n logger: NULL_LOGGER,\n }\n const initial = await readStore(captured, operation)\n const code = await requestCopilotDeviceCode(options)\n notify(() => progress.onPrompt?.(code))\n const token = await pollForCopilotToken(code, options, progress)\n const file: CopilotAuthFile = {\n version: 1,\n github: {\n token: token.accessToken,\n ...token.tokenType === undefined ? {} : { tokenType: token.tokenType },\n ...token.scope === undefined ? {} : { scope: token.scope },\n },\n ...token.account === undefined ? {} : { account: token.account },\n clientId: options.clientId ?? COPILOT_OAUTH_CLIENT_ID,\n obtainedAt: new Date(timerOf(options).now()).toISOString(),\n }\n await commitStore(captured, file, initial.revision, operation)\n return Object.freeze({\n location: captured.label,\n login: token.account?.login,\n accountId: token.account?.id,\n scope: token.scope,\n })\n}\n\n/**\n * Poll the token leg until the user approves, the server refuses, or a bound\n * passes.\n *\n * Two things make this loop different from the Codex one, and both are easy to\n * get wrong:\n *\n * - **`Accept: application/json` is mandatory.** Without it GitHub's token\n * endpoint answers FORM-ENCODED, so a JSON parser meets\n * `error=authorization_pending&interval=10` and throws — which turns the \"not\n * approved yet\" branch into a hard-failure branch, and the flow can then never\n * succeed at all.\n * - **The error channel is HTTP 200 with `error` in the body.** Codex surfaces\n * \"pending\" as 403/404; GitHub surfaces it as a 200. So classification reads the\n * BODY FIRST and the status second. A status-first reader treats every pending\n * poll as a success and then fails looking for `access_token`.\n * @param code - the pending authorization.\n * @param options - issuer, client id, bounds and the injectable timer.\n * @param progress - poll notifications.\n * @returns the access token and whatever the endpoint disclosed beside it.\n */\nasync function pollForCopilotToken(\n code: CopilotDeviceCode,\n options: CopilotOAuthOptions,\n progress: CopilotLoginProgress,\n): Promise<CopilotAccessToken> {\n const pinned = issuerOf('oauthIssuer', options.oauthIssuer, DEFAULT_COPILOT_OAUTH_ISSUER, options)\n const url = copilotUrl(pinned, DEVICE_TOKEN_PATH)\n const timer = timerOf(options)\n const startedAt = timer.now()\n // The 15-minute ceiling is absolute; `expires_in` only ever pulls the deadline\n // in. min() is the whole of that rule.\n const deadlineAt = startedAt + Math.min(\n COPILOT_DEVICE_CODE_MAX_WAIT_MS,\n code.expiresInSeconds * 1_000,\n )\n let intervalSeconds = code.intervalSeconds\n\n while (true) {\n throwIfAborted(options.signal)\n if (timer.now() >= deadlineAt) throw deviceTimeout(startedAt, timer.now())\n notify(() => progress.onPoll?.(timer.now() - startedAt, intervalSeconds))\n\n const body = await deviceJson(\n {\n pinned,\n url,\n operation: 'device token',\n init: {\n method: 'POST',\n headers: { accept: 'application/json', 'content-type': 'application/json' },\n body: JSON.stringify({\n client_id: options.clientId ?? COPILOT_OAUTH_CLIENT_ID,\n device_code: code.deviceCode,\n grant_type: DEVICE_GRANT_TYPE,\n }),\n },\n },\n 'the device-token endpoint',\n options,\n )\n\n // Body first, status second.\n const error = typeof body.json.error === 'string' ? body.json.error : undefined\n if (error === 'authorization_pending' || error === 'slow_down') {\n intervalSeconds = nextIntervalSeconds(intervalSeconds, body.json.interval, error)\n const remaining = deadlineAt - timer.now()\n if (remaining <= 0) throw deviceTimeout(startedAt, timer.now())\n await sleep(Math.min(intervalSeconds * 1_000, remaining), options.signal, timer)\n continue\n }\n if (error === 'access_denied') {\n throw deviceFailure(\n 'the device login was denied on GitHub;'\n + ` run \\`${COPILOT_LOGIN_COMMAND}\\` again if you did mean to approve it`,\n 'denied',\n )\n }\n if (error === 'expired_token') {\n throw deviceFailure(\n `the device code expired before it was approved; run \\`${COPILOT_LOGIN_COMMAND}\\``\n + ' again to request a new code',\n 'expired',\n )\n }\n if (error !== undefined) {\n throw deviceFailure(\n `the device-token endpoint refused the request (${error}, HTTP ${body.status})`,\n 'failed',\n )\n }\n if (!body.ok) {\n throw deviceFailure(\n `the device-token endpoint failed (HTTP ${body.status})`,\n 'failed',\n body.parseError,\n )\n }\n return Object.freeze({\n accessToken: requireDeviceString(body.json, 'access_token'),\n tokenType: optionalString(body.json.token_type),\n scope: optionalString(body.json.scope),\n account: accountIdentityOf(body.json),\n })\n }\n}\n\n/**\n * The effective wait after a `slow_down`, which must STRICTLY increase.\n *\n * `max(current, server-requested, current + 5)` — the third term is what keeps\n * the sequence increasing when the server sends no new `interval`, and taking the\n * max of all three keeps it from ever decreasing when the server sends a smaller\n * one. `authorization_pending` may carry a new interval too; there it is honoured\n * without the increment, so an ordinary pending poll does not back off forever.\n */\nfunction nextIntervalSeconds(\n current: number,\n requested: unknown,\n error: 'authorization_pending' | 'slow_down',\n): number {\n const server = positiveSecondsOf(requested, 0)\n return error === 'slow_down'\n ? Math.max(current, server, current + COPILOT_SLOW_DOWN_INCREMENT_SECONDS)\n : Math.max(current, server)\n}\n\n/**\n * Wait `ms`, losing the race to `signal` the instant it aborts.\n *\n * The timer is injected rather than closed over, so a test can drive the poll\n * loop through fifteen virtual minutes in a millisecond. Aborting rejects instead\n * of resolving early, because a caller who pressed Ctrl-C wants the flow to END,\n * not to take one more turn round the loop.\n */\nfunction sleep(ms: number, signal: AbortSignal | undefined, timer: CopilotTimer): Promise<void> {\n if (signal?.aborted === true) return Promise.reject(deviceAborted())\n return new Promise<void>((resolve, reject) => {\n const onAbort = (): void => {\n timer.clearTimeout(handle)\n reject(deviceAborted())\n }\n const handle = timer.setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n\n/** One bounded read of an OAuth response, plus the status it came with. */\ninterface DeviceJson {\n readonly ok: boolean\n readonly status: number\n readonly json: Record<string, unknown>\n /** Why the body was not usable JSON, when it was not. */\n readonly parseError: unknown\n}\n\n/**\n * Dispatch one OAuth leg and read its body within the configured bounds.\n *\n * A body that is not a JSON object yields an EMPTY object plus `parseError`\n * rather than throwing: the status still has to be classified, and on the token\n * leg an unreadable body is one of the shapes a misconfigured `Accept` header\n * produces. Callers therefore always get to the body-first branch, and reach a\n * hard failure only after it finds no `error`.\n */\nasync function deviceJson(\n request: CopilotRequest,\n what: string,\n options: CopilotOAuthOptions,\n): Promise<DeviceJson> {\n let response: Response\n try {\n response = await copilotFetch(request, options)\n } catch (error: unknown) {\n throwIfAborted(options.signal)\n throw error instanceof CopilotDeviceLoginError\n ? error\n : deviceFailure(`${what} could not be reached`, 'failed', error)\n }\n let raw: string\n try {\n raw = await readCopilotResponseText(response, options)\n } catch (error: unknown) {\n throwIfAborted(options.signal)\n throw deviceFailure(`${what} returned a response beyond the configured limits`, 'failed', error)\n }\n try {\n const parsed = JSON.parse(raw) as unknown\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new TypeError(`${what} returned JSON that is not an object`)\n }\n return {\n ok: response.ok,\n status: response.status,\n json: parsed as Record<string, unknown>,\n parseError: undefined,\n }\n } catch (error: unknown) {\n return { ok: response.ok, status: response.status, json: {}, parseError: error }\n }\n}\n\n/**\n * Read the verification URL the user is told to open.\n *\n * Only the SCHEME is constrained, not the origin. This SDK never fetches this\n * URL — it prints it — and GitHub Enterprise deployments legitimately answer with\n * a host other than the issuer, so an origin pin here would reject working\n * installations to guard a request that is never made. The scheme check remains\n * because a `javascript:` or `data:` URL handed to a browser opener is a real\n * problem, and {@link COPILOT_DEVICE_LOGIN_WARNING} covers the rest.\n */\nfunction verificationUrlOf(\n body: Record<string, unknown>,\n options: CopilotOAuthOptions,\n): string {\n const raw = requireDeviceString(body, 'verification_uri')\n let url: URL\n try {\n url = new URL(raw)\n } catch (error: unknown) {\n throw deviceFailure('the device-code endpoint returned an unusable verification URL', 'failed', error)\n }\n if (url.protocol !== 'https:'\n && !(options.allowInsecureIssuer === true && url.protocol === 'http:')) {\n throw deviceFailure('the device-code verification URL must use https', 'failed')\n }\n return url.href\n}\n\n/** Identity fields, present only when the endpoint disclosed them (Property 16). */\nfunction accountIdentityOf(body: Record<string, unknown>): CopilotAccountIdentity | undefined {\n const login = optionalString(body.login)\n const name = optionalString(body.name)\n const id = typeof body.id === 'number' && Number.isFinite(body.id) ? body.id : undefined\n if (login === undefined && name === undefined && id === undefined) return undefined\n return Object.freeze({\n ...login === undefined ? {} : { login },\n ...name === undefined ? {} : { name },\n ...id === undefined ? {} : { id },\n })\n}\n\nfunction optionalString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nfunction requireDeviceString(body: Record<string, unknown>, key: string): string {\n const value = body[key]\n if (typeof value !== 'string' || value.length === 0) {\n throw deviceFailure(`the device flow response omitted \"${key}\"`, 'failed')\n }\n return value\n}\n\n/**\n * Read a seconds value that the endpoint may send as a number, as a numeric\n * string, or not at all.\n *\n * GitHub has been observed sending `interval` as a string, so both forms are\n * accepted; anything unparsable falls back rather than failing the login, because\n * a bad hint about pacing is not a reason to refuse a working authorization.\n */\nfunction positiveSecondsOf(value: unknown, fallbackSeconds: number): number {\n const parsed = typeof value === 'number'\n ? value\n : typeof value === 'string' ? Number.parseInt(value.trim(), 10) : Number.NaN\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallbackSeconds\n}\n\nfunction timerOf(options: CopilotOAuthOptions): CopilotTimer {\n return options.timer ?? DEFAULT_COPILOT_TIMER\n}\n\n/** Run a progress observer; observers do not own authentication. */\nfunction notify(report: () => void): void {\n try {\n report()\n } catch { /* a CLI's rendering must not decide whether a login succeeds */ }\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted === true) throw deviceAborted()\n}\n\nfunction deviceAborted(): CopilotDeviceLoginError {\n return deviceFailure('the device login was cancelled', 'aborted')\n}\n\nfunction deviceTimeout(startedAt: number, now: number): CopilotDeviceLoginError {\n return deviceFailure(\n `the device login was not approved within ${Math.round((now - startedAt) / 1_000)}s`\n + ` (bound: ${COPILOT_DEVICE_CODE_MAX_WAIT_MS / 60_000} minutes);`\n + ` run \\`${COPILOT_LOGIN_COMMAND}\\` again`,\n 'timeout',\n )\n}\n\nfunction deviceFailure(\n message: string,\n reason: CopilotDeviceLoginReason,\n cause?: unknown,\n): CopilotDeviceLoginError {\n return new CopilotDeviceLoginError(credentialFailure(message, cause), reason)\n}\n\nasync function readStore(\n captured: CapturedCopilotStore,\n operation: CredentialOperationOptions,\n): Promise<CopilotStoreSnapshot> {\n if (captured.kind === 'versioned') {\n const record = await captured.store.read(operation)\n return record === undefined\n ? { file: undefined, revision: null }\n : { file: record.value, revision: record.revision }\n }\n return { file: await captured.store.read(), revision: null }\n}\n\nasync function commitStore(\n captured: CapturedCopilotStore,\n file: CopilotAuthFile,\n expectedRevision: string | null,\n operation: CredentialOperationOptions,\n): Promise<void> {\n if (captured.kind === 'versioned') {\n await captured.store.commit({ value: file, expectedRevision }, operation)\n return\n }\n await captured.store.write(file)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["malformed","declaredEndpointOf","AgentSdkError","REDACTED","id","NULL_LOGGER","NULL_LOGGER","COPILOT_LOGIN_COMMAND"],"sources":["../src/common/error-codes.ts","../src/errors.ts","../src/auth.ts","../src/common/identity.ts","../src/common/no-follow.ts","../src/common/http.ts","../src/catalog.ts","../src/common/store-capture.ts","../src/dual-protocol.ts","../src/exchange.ts","../src/router.ts","../src/adapter.ts","../src/token.ts","../src/oauth.ts"],"sourcesContent":["/**\n * The Copilot error codes, in the leaf layer so the shared HTTP modules can reach\n * them.\n *\n * The taxonomy BELONGS to `../errors.ts` — that module is the public door, and it\n * re-exports everything here. The definition lives one layer down for a structural\n * reason: `common/` is a leaf, and `common/http.ts` needs\n * `ENDPOINT_ORIGIN_INVALID` and `COPILOT_REDIRECT_REJECTED` to throw. Importing\n * them from a root module would make `common/` depend on the root while the root\n * already depends on `common/`, which is the source-ownership cycle the repo's\n * package-graph check forbids. Duplicating the two strings instead would be worse:\n * a code that exists in two places is a code that can disagree with itself.\n *\n * Read `../errors.ts` for the taxonomy's rationale, including the three situations\n * that deliberately get an EXISTING SDK code rather than a Copilot one.\n *\n * @module ai-agent-sdk/providers/copilot/error-codes\n */\n\n/**\n * Stable codes for the failures that are specific to Copilot.\n *\n * Frozen, and flat strings rather than a TS enum, for the same reason the core\n * taxonomy is: a consumer routes on the value, and the value has to survive\n * serialization into a log line.\n */\nexport const COPILOT_ERROR_CODES = Object.freeze({\n /** The token-exchange surface rejected the credential: a PAT, or a non-allowlisted OAuth App. */\n CREDENTIAL_REJECTED: 'COPILOT_CREDENTIAL_REJECTED',\n /** Token exchange failed for a reason that is not the credential. */\n TOKEN_EXCHANGE_FAILED: 'COPILOT_TOKEN_EXCHANGE_FAILED',\n /** The token-exchange response carried no readable `expires_at`, or was not JSON. */\n TOKEN_MALFORMED: 'COPILOT_TOKEN_MALFORMED',\n /** A `*.ghe.com` data-residency tenant has no token-exchange surface. */\n TENANT_UNSUPPORTED: 'COPILOT_TENANT_UNSUPPORTED',\n /** The endpoint rejected the request for missing `Editor_Headers`. */\n EDITOR_HEADERS_MISSING: 'COPILOT_EDITOR_HEADERS_MISSING',\n /** The target URL is not on the same origin as the configured issuer/base URL. */\n ENDPOINT_ORIGIN_INVALID: 'COPILOT_ENDPOINT_ORIGIN_INVALID',\n /** The response was a redirect; this SDK does not follow it. */\n REDIRECT_REJECTED: 'COPILOT_REDIRECT_REJECTED',\n /** Device flow: the user denied the request. */\n DEVICE_LOGIN_DENIED: 'COPILOT_DEVICE_LOGIN_DENIED',\n /** Device flow: the code expired server-side. */\n DEVICE_LOGIN_EXPIRED: 'COPILOT_DEVICE_LOGIN_EXPIRED',\n /** Device flow: the absolute 15-minute bound passed without approval. */\n DEVICE_LOGIN_TIMEOUT: 'COPILOT_DEVICE_LOGIN_TIMEOUT',\n /** Device flow: failed for any other reason. */\n DEVICE_LOGIN_FAILED: 'COPILOT_DEVICE_LOGIN_FAILED',\n /** A credential commit found a revision other than the expected one. */\n CREDENTIAL_REVISION_CONFLICT: 'COPILOT_CREDENTIAL_REVISION_CONFLICT',\n /** The `/models` response was the wrong shape at the structural level. */\n CATALOG_MALFORMED: 'COPILOT_CATALOG_MALFORMED',\n /** `endpointOverrides` pinned a model to an endpoint that does not exist. */\n ENDPOINT_OVERRIDE_INVALID: 'COPILOT_ENDPOINT_OVERRIDE_INVALID',\n} as const)\n\n/** One of the codes {@link COPILOT_ERROR_CODES} owns. */\nexport type CopilotErrorCode = (typeof COPILOT_ERROR_CODES)[keyof typeof COPILOT_ERROR_CODES]\n","/**\n * The Copilot error taxonomy: the codes this provider owns, the two error\n * classes that carry a machine-readable classification beside them, and the one\n * door through which every credential-path error is built.\n *\n * ## What is deliberately NOT here\n *\n * Three situations get an EXISTING code rather than a Copilot one, because a\n * second code for the same situation forces every consumer to write a second\n * branch for it:\n *\n * - **No credential at all** — `MISSING_CREDENTIAL_CODE` from `packages/core`,\n * with a message naming the login command (Requirement 13.4).\n * - **Abort** — the SDK's existing abort code, {@link MODEL_ERROR_CODES.ABORTED}\n * (Requirement 4.6). `CopilotDeviceLoginError` with `reason: 'aborted'` maps to\n * it rather than minting a Copilot abort code.\n * - **HTTP failures of the generation/embedding endpoints** — `MODEL_ERROR_CODES`\n * plus `HTTP_PROVIDER_ERROR_CODES`. In particular there is no\n * `COPILOT_RATE_LIMIT`: a 429 from Copilot is `RATE_LIMIT`, the same as from\n * every other provider (Requirements 13.5, 15.5).\n *\n * @module ai-agent-sdk/providers/copilot/errors\n */\n\nimport {\n AgentSdkError,\n MODEL_ERROR_CODES,\n ModelError,\n safeErrorRecord,\n type SafeErrorRecord,\n} from '@alvin0/ai-agent-sdk-core'\nimport { COPILOT_ERROR_CODES, type CopilotErrorCode } from './common/error-codes.ts'\n\n/**\n * The codes this module owns, defined in `./common/error-codes.ts` and re-exported\n * here.\n *\n * The definition sits one layer down only so `common/http.ts` can throw\n * `ENDPOINT_ORIGIN_INVALID` and `REDIRECT_REJECTED` without `common/` importing a\n * root module — `common/` is a leaf, and the root already imports it. This module\n * remains the door consumers read.\n */\nexport { COPILOT_ERROR_CODES, type CopilotErrorCode } from './common/error-codes.ts'\n\n/**\n * Mirrors `safeProviderFailure` from `provider-http`: a {@link ModelError} keeps\n * its stable code and status and LOSES its message, because a provider message\n * is the one field that can have echoed a request header back at us.\n * @param failure - the serializable twin carried by a {@link ModelError}.\n * @returns a frozen record with no provider-authored text in it.\n */\nfunction safeModelFailure(failure: ModelError['failure']): SafeErrorRecord {\n return Object.freeze({\n type: 'ModelError',\n message: 'provider attempt failed; inspect the stable code and request ID',\n code: failure.code,\n ...failure.status === undefined ? {} : { status: failure.status },\n })\n}\n\n/** A sanitized cause plus the message it is allowed to travel with. */\nexport interface CopilotCredentialFailure {\n /** SDK-authored text. Never carries a token value, because nothing interpolates one in. */\n readonly message: string\n /** The cause, reduced to serializable facts; `undefined` when there was none. */\n readonly cause: SafeErrorRecord | undefined\n}\n\n/**\n * Build the inputs for an error on the Copilot credential path.\n *\n * This is the ONLY door: {@link CopilotTokenExchangeError} and\n * {@link CopilotDeviceLoginError} take a {@link CopilotCredentialFailure} rather\n * than a raw `cause`, so there is no code path that can attach an unfiltered\n * value to a credential-path error. `message` is SDK-authored text; a\n * `GitHub_User_Token` or a `Copilot_Api_Token` is never interpolated into it, and\n * response bodies reach `cause` only after the bounded read has replaced every\n * occurrence of the tokens held in memory with `[REDACTED]` (Requirement 13.7).\n * @param message - SDK-authored, actionable text. No token values.\n * @param cause - the caught value, if any; filtered before it is retained.\n * @returns the sanitized pair an error class accepts.\n */\nexport function credentialFailure(message: string, cause?: unknown): CopilotCredentialFailure {\n return Object.freeze({\n message,\n cause: cause === undefined\n ? undefined\n // A ModelError already carries the serializable twin, so route it through\n // the same reduction the provider attempt ledger uses. Anything else keeps\n // only bounded facts, and never a stack.\n : cause instanceof ModelError ? safeModelFailure(cause.failure) : safeErrorRecord(cause),\n })\n}\n\n/** Whether a token-exchange failure can ever succeed on a retry. */\nexport type CopilotTokenExchangeFailureKind = 'permanent' | 'transient'\n\n/**\n * A `Copilot_Token_Exchange` that did not produce a token.\n *\n * `kind` exists because `code` alone does not answer the only question a caller\n * has to answer next: `TOKEN_EXCHANGE_FAILED` covers both a 5xx worth waiting out\n * and a 4xx that will fail identically forever.\n */\nexport class CopilotTokenExchangeError extends AgentSdkError {\n /** Retry classification for this failure. */\n readonly kind: CopilotTokenExchangeFailureKind\n\n /**\n * @param failure - message and filtered cause from {@link credentialFailure}.\n * @param code - the Copilot code for this row of the classification table.\n * @param kind - whether a retry could ever succeed.\n */\n constructor(\n failure: CopilotCredentialFailure,\n code: CopilotErrorCode,\n kind: CopilotTokenExchangeFailureKind,\n ) {\n super(failure.message, code, failure.cause === undefined ? undefined : { cause: failure.cause })\n this.kind = kind\n }\n}\n\n/** Why a device login ended without a token. */\nexport type CopilotDeviceLoginReason = 'denied' | 'expired' | 'timeout' | 'aborted' | 'failed'\n\n/**\n * Codes for the four device-flow outcomes this provider owns.\n *\n * `denied` and `expired` are separate on purpose: \"you just declined this\" and\n * \"the code ran out\" lead to different next steps. `aborted` is absent because it\n * maps to the SDK's existing abort code instead.\n */\nconst DEVICE_LOGIN_CODES = Object.freeze({\n denied: COPILOT_ERROR_CODES.DEVICE_LOGIN_DENIED,\n expired: COPILOT_ERROR_CODES.DEVICE_LOGIN_EXPIRED,\n timeout: COPILOT_ERROR_CODES.DEVICE_LOGIN_TIMEOUT,\n failed: COPILOT_ERROR_CODES.DEVICE_LOGIN_FAILED,\n} as const)\n\n/**\n * A device login that ended without a `GitHub_User_Token`.\n *\n * The `code` is derived from `reason` rather than passed in, so the two can never\n * disagree — a caller reading `code` and a caller reading `reason` always see the\n * same outcome.\n */\nexport class CopilotDeviceLoginError extends AgentSdkError {\n /** The distinguishable reason the flow ended. */\n readonly reason: CopilotDeviceLoginReason\n\n /**\n * @param failure - message and filtered cause from {@link credentialFailure}.\n * @param reason - the outcome; decides the `code`, with `aborted` mapping to\n * {@link MODEL_ERROR_CODES.ABORTED} rather than a Copilot-specific code.\n */\n constructor(failure: CopilotCredentialFailure, reason: CopilotDeviceLoginReason) {\n super(\n failure.message,\n reason === 'aborted' ? MODEL_ERROR_CODES.ABORTED : DEVICE_LOGIN_CODES[reason],\n failure.cause === undefined ? undefined : { cause: failure.cause },\n )\n this.reason = reason\n }\n}\n","/**\n * The Universal half of `Copilot_Auth`: the in-memory store doubles, the\n * credential snapshot an operation carries, the one function that turns \"no\n * credential\" into an actionable error, and the pure predicate that decides\n * whether a token exchange is due.\n *\n * Storage is injected. Paths, the filesystem, and the environment belong to the\n * Node auth package, never this Universal one (Requirement 6.1).\n *\n * ## Two tiers, one of which lives here\n *\n * The long-lived `GitHub_User_Token` is what a store persists; the short-lived\n * `Copilot_Api_Token` obtained from it never reaches a store and lives only in\n * the process cache (Requirements 3.1, 3.3). Exchanging does not consume the\n * long-lived token, so the persisted value is left exactly as it was\n * (Requirement 3.4) — this module has no write path at all for that reason.\n *\n * @module ai-agent-sdk/providers/copilot/auth\n */\n\nimport { AgentSdkError, MISSING_CREDENTIAL_CODE } from '@alvin0/ai-agent-sdk-core'\nimport { defineCredentialStore } from '@alvin0/ai-agent-sdk-core/provider'\nimport { COPILOT_ERROR_CODES } from './errors.ts'\nimport type {\n CopilotAuthFile,\n CopilotAuthStore,\n CopilotCredentialStore,\n CopilotGitHubToken,\n} from './common/store-types.ts'\n\nexport type {\n CopilotAccountIdentity,\n CopilotAuthFile,\n CopilotAuthStore,\n CopilotCredentialStore,\n CopilotGitHubToken,\n} from './common/store-types.ts'\n\n/**\n * The command that produces a credential, named in every message that tells a\n * caller how to fix a credential problem.\n *\n * Exported so the token-exchange path names the SAME command: a 401 and a 403\n * there both end in \"sign in again\", and two copies of that string are two\n * strings that can drift apart.\n */\nexport const COPILOT_LOGIN_COMMAND = 'npm run provider:copilot:login-device'\n\n/**\n * An in-memory {@link CopilotAuthStore} — the read/write variant (Requirement 6.4).\n *\n * The read/write variant exists for symmetry with Codex; normal runtime\n * composition uses {@link memoryCopilotCredentialStore}, the compare-and-swap\n * variant.\n * @param initial - the file the store starts with, or nothing for an empty store.\n * @returns a store backed by a single mutable slot.\n */\nexport function memoryCopilotAuthStore(initial?: CopilotAuthFile): CopilotAuthStore {\n let current = initial\n return {\n location: '<memory>',\n read: () => Promise.resolve(current),\n write: (file) => {\n current = file\n return Promise.resolve()\n },\n }\n}\n\n/**\n * An in-memory compare-and-swap store, for deterministic runtime and tests\n * (Requirements 6.2, 6.4).\n *\n * Values are `structuredClone`d in BOTH directions, which is the point of this\n * double: a caller that mutates the object it wrote, or the object it read, must\n * not be able to change what the store holds. Without the clone a test could pass\n * for the wrong reason — the store and the caller sharing one object rather than\n * the store having committed anything.\n *\n * A commit whose `expectedRevision` disagrees with the current revision raises\n * {@link COPILOT_ERROR_CODES.CREDENTIAL_REVISION_CONFLICT} (Requirement 6.3), the\n * Copilot-owned code, so exactly one of two concurrent commits wins and the loser\n * can tell why it lost.\n * @param initial - the file the store starts with, or nothing for an empty store.\n * @returns a compare-and-swap store over a single mutable slot.\n */\nexport function memoryCopilotCredentialStore(initial?: CopilotAuthFile): CopilotCredentialStore {\n let current = initial === undefined ? undefined : structuredClone(initial)\n let revision = 0\n return defineCredentialStore<CopilotAuthFile>({\n id: 'copilot-memory-credentials',\n label: '<memory>',\n async read({ signal }) {\n signal.throwIfAborted()\n return current === undefined\n ? undefined\n : { value: structuredClone(current), revision: String(revision) }\n },\n async commit(input, { signal }) {\n signal.throwIfAborted()\n const expected = current === undefined ? null : String(revision)\n if (input.expectedRevision !== expected) {\n throw new AgentSdkError(\n 'Copilot credential revision changed before commit',\n COPILOT_ERROR_CODES.CREDENTIAL_REVISION_CONFLICT,\n )\n }\n current = structuredClone(input.value)\n revision++\n return { revision: String(revision) }\n },\n })\n}\n\n/**\n * One read of the credential store, carried through a single operation.\n *\n * `revision` travels with `file` rather than being re-read later, because the\n * token cache keys on the pair: a file that changed under us has a different\n * revision even when the token value happens to be identical.\n */\nexport interface CopilotCredentialSnapshot {\n readonly file: CopilotAuthFile\n /** The revision at read time, or `null` for a store with no revisions. */\n readonly revision: string | null\n /** The store's human-readable location, named in diagnostics. */\n readonly label: string\n}\n\n/**\n * Require a usable `GitHub_User_Token`, with a message that says how to get one.\n *\n * Three shapes of \"there is no credential\" — an empty store, a file with no\n * `github` field, and a `github.token` that is the empty string — collapse into\n * the SAME code, because to the person reading the error they are one problem\n * with one fix. That code is the SDK's own {@link MISSING_CREDENTIAL_CODE} rather\n * than a Copilot-specific one, so a consumer does not have to write a second\n * branch for a situation it already handles, and the message carries the command\n * to run `Copilot_Login_Cli` (Requirement 13.4).\n *\n * The token value is never interpolated into the message; only the store's label\n * is (Requirement 13.7).\n * @param file - the credential file, or `undefined` when the store was empty.\n * @param label - the store location named in the diagnostic.\n * @returns the long-lived GitHub token.\n */\nexport function requireGitHubToken(\n file: CopilotAuthFile | undefined,\n label: string,\n): CopilotGitHubToken {\n const github = file?.github\n if (github === undefined || github === null\n || typeof github.token !== 'string' || github.token.length === 0) {\n throw new AgentSdkError(\n `no GitHub Copilot credentials at ${label}; run \\`${COPILOT_LOGIN_COMMAND}\\` to sign in`,\n MISSING_CREDENTIAL_CODE,\n )\n }\n return github\n}\n\n/** Exchange this long before the `Copilot_Api_Token` actually expires. */\nexport const COPILOT_TOKEN_EXCHANGE_MARGIN_MS = 5 * 60 * 1_000\n\n/**\n * The part of a `Copilot_Api_Token` that {@link shouldExchange} reads.\n *\n * Declared here rather than imported so this module owns no dependency on the\n * exchange module: expiry arithmetic is the whole of what the decision needs, and\n * `CopilotApiToken` — which carries the token value and the declared endpoint\n * besides — satisfies this shape structurally, so `shouldExchange` accepts one\n * with no conversion and there is only ever one declaration of the full type.\n */\nexport interface CopilotTokenExpiry {\n /** Expiry instant in epoch MILLISECONDS, derived from the endpoint's `expires_at`. */\n readonly expiresAtMs: number\n /** The endpoint's `refresh_in` hint, in seconds, when it sent one. Advisory. */\n readonly refreshInSeconds?: number\n}\n\n/**\n * Whether a token exchange has to happen before the next request.\n *\n * A pure function of three values that reads no global clock, so a property test\n * can place `now` at every boundary without a fake timer (Requirements 5.2, 5.3).\n * `undefined` — no token yet — is always `true`.\n *\n * `expires_at` is the authority and `refresh_in` is advisory: the hint may only\n * SHORTEN the refresh moment, never lengthen it. The endpoint is allowed to ask\n * for an earlier exchange; it is not allowed to ask this SDK to hold a token past\n * the expiry it announced itself.\n *\n * There is deliberately no fallback branch. `shouldRefresh` in\n * `provider-codex/src/auth.ts` decodes a JWT for `exp` and falls back to a\n * `last_refresh` age when it cannot; a `Copilot_Api_Token` is not a JWT this SDK\n * has any business reading, and the expiry is stated outright in the exchange\n * response body. With no second source, a fallback would have to invent a\n * lifetime, and an invented lifetime violates the no-inference principle.\n *\n * Because the decision is made BEFORE dispatch, a 401 from the Copilot base URL\n * always means the credential is genuinely dead rather than \"the token expired\n * mid-flight\" — which is what lets auth failures stay non-retryable\n * (Requirement 5.8).\n * @param api - the cached token, or `undefined` when there is none.\n * @param now - current time in epoch milliseconds.\n * @param marginMs - exchange this long before expiry.\n * @returns true when an exchange is due.\n */\nexport function shouldExchange(\n api: CopilotTokenExpiry | undefined,\n now: number,\n marginMs = COPILOT_TOKEN_EXCHANGE_MARGIN_MS,\n): boolean {\n if (api === undefined) return true\n const advisory = api.refreshInSeconds === undefined\n ? Number.POSITIVE_INFINITY\n : api.expiresAtMs - api.refreshInSeconds * 1_000\n return Math.min(api.expiresAtMs - marginMs, advisory) <= now\n}\n","/**\n * The two Copilot endpoint constants and the editor-header override type, in the\n * leaf layer so every module that has to speak to the Copilot surface can reach\n * them.\n *\n * They BELONG to `../adapter.ts` — that module is the public door, documents the\n * client-identity tradeoff, and re-exports everything here. The values live one\n * layer down for the same structural reason `COPILOT_ERROR_CODES` does:\n * `../catalog.ts` needs {@link COPILOT_BASE_URL} and `../exchange.ts` needs the\n * two editor headers, while `../adapter.ts` builds the catalog reader and the\n * token cache. Declaring the constants in `../adapter.ts` would make that edge\n * run both ways, which the repo's circular-dependency check forbids. Copying the\n * strings instead would be worse: a client identity that exists in two places is\n * a client identity that can disagree with itself.\n *\n * Read `../adapter.ts` for what these values mean, why they are overridable\n * options rather than hidden constants, and what is still outstanding on each.\n *\n * @module ai-agent-sdk/providers/copilot/identity\n */\n\n/** The Copilot API base. Documented on the re-export in `../adapter.ts`. */\nexport const COPILOT_BASE_URL = 'https://api.githubcopilot.com'\n\n/** Default `Editor-Version`. Documented on the re-export in `../adapter.ts`. */\nexport const COPILOT_EDITOR_VERSION = 'vscode/1.99.0'\n\n/** Default `Editor-Plugin-Version`. Documented on the re-export in `../adapter.ts`. */\nexport const COPILOT_EDITOR_PLUGIN_VERSION = 'copilot-chat/0.26.0'\n\n/**\n * Overrides for the two editor headers.\n *\n * Each field is independent: leaving one undefined keeps that header's exported\n * default rather than dropping the header, because a dropped header is an HTTP\n * 400 rather than a lenient request.\n */\nexport interface CopilotEditorHeaders {\n /** Overrides {@link COPILOT_EDITOR_VERSION}. */\n readonly editorVersion?: string\n /** Overrides {@link COPILOT_EDITOR_PLUGIN_VERSION}. */\n readonly editorPluginVersion?: string\n}\n\n/** Both editor headers resolved, with every field present. */\nexport interface ResolvedCopilotEditorHeaders {\n /** The value sent as `editor-version`. */\n readonly editorVersion: string\n /** The value sent as `editor-plugin-version`. */\n readonly editorPluginVersion: string\n}\n\n/**\n * Resolve each editor header from the override first and the exported default\n * second.\n *\n * Per field rather than per object: an override of one header leaves the other at\n * its default instead of dropping it, because a dropped editor header is an HTTP\n * 400 (Requirement 2.4).\n * @param headers - the caller's overrides, when they set any.\n * @returns both header values, neither of them empty.\n */\nexport function resolveCopilotEditorHeaders(\n headers: CopilotEditorHeaders | undefined,\n): ResolvedCopilotEditorHeaders {\n return Object.freeze({\n editorVersion: headers?.editorVersion ?? COPILOT_EDITOR_VERSION,\n editorPluginVersion: headers?.editorPluginVersion ?? COPILOT_EDITOR_PLUGIN_VERSION,\n })\n}\n","/**\n * The redirect guard every Copilot HTTP call passes through.\n *\n * `redirect: 'manual'` on the request is only half of a no-follow policy: it\n * stops the runtime from following a hop, but it does not stop the CALLER from\n * treating the result as a normal response. This module is the other half — it\n * turns every shape a redirect can take into a structured error before a second\n * request can be dispatched.\n *\n * There are four shapes, and a check for only one of them is a hole:\n *\n * - **A 3xx status** — the ordinary case, visible because `redirect: 'manual'`\n * surfaces the response instead of following it.\n * - **`type === 'opaqueredirect'`** — what a browser returns instead of the 3xx,\n * with the status flattened to `0` and the headers stripped. A status-only\n * check misses this entirely.\n * - **`redirected === true`** — a hop that was already followed, by a runtime or\n * an intermediary that ignored `redirect: 'manual'`.\n * - **`response.url` differing from the requested URL** — the last resort, for a\n * runtime that reports neither of the flags above but still moved the request.\n *\n * The body is RELEASED before the error is thrown. A rejected response whose body\n * is never cancelled holds a socket open for as long as the runtime keeps the\n * stream alive, so the guard cannot leave that to the caller's `finally`.\n *\n * @module ai-agent-sdk/providers/copilot/no-follow\n */\n\nimport { AgentSdkError, waitForSettlement } from '@alvin0/ai-agent-sdk-core'\nimport { COPILOT_ERROR_CODES } from './error-codes.ts'\n\n/**\n * The Copilot HTTP call sites, named so an error says which one refused the hop.\n *\n * All seven are listed because the redirect guard covers all seven: the two\n * device-flow legs, the token exchange, the catalog, both generation endpoints\n * and the embedding endpoint (Requirements 3.8, 7.8). A call site that is not on\n * this list has no name to report, which is the point — adding an endpoint means\n * naming it here, and naming it here means it went through {@link\n * rejectCopilotRedirect}.\n */\nexport type CopilotHttpOperation =\n | 'device code'\n | 'device token'\n | 'token exchange'\n | 'model catalog'\n | 'responses'\n | 'chat completions'\n | 'embeddings'\n\n/**\n * Reject every redirect shape Web fetch exposes, before any second request.\n *\n * @param response - the response as returned by a `redirect: 'manual'` fetch.\n * @param requestedUrl - the absolute URL that was requested, for the\n * `response.url` comparison.\n * @param operation - which Copilot call site is refusing the hop.\n * @param teardownTimeoutMs - bound on the body cancellation, so a stream that\n * never settles cannot hold the rejection open forever.\n * @returns nothing when the response is not a redirect in any of its four shapes.\n * @throws AgentSdkError with `COPILOT_REDIRECT_REJECTED` when it is.\n */\nexport async function rejectCopilotRedirect(\n response: Response,\n requestedUrl: string,\n operation: CopilotHttpOperation,\n teardownTimeoutMs: number,\n): Promise<void> {\n const redirectStatus = response.status >= 300 && response.status < 400\n const responseUrlChanged = response.url.length > 0 && response.url !== requestedUrl\n if (response.type !== 'opaqueredirect' && response.redirected !== true\n && !redirectStatus && !responseUrlChanged) return\n if (response.body !== null) {\n await waitForSettlement(response.body.cancel().catch(() => undefined), teardownTimeoutMs)\n }\n throw new AgentSdkError(\n `Copilot ${operation} rejected a redirect before following it`,\n COPILOT_ERROR_CODES.REDIRECT_REJECTED,\n )\n}\n","/**\n * The one HTTP door for every Copilot call: origin pinning, no-follow, bounded\n * reads, and a caller signal that wins immediately.\n *\n * This is the counterpart of `oauthFetch` in `provider-codex/src/oauth.ts`, kept\n * deliberately close to it — the same guarantees, in the same order, so a reader\n * who knows one knows the other. What differs is scope: Codex has one auth\n * issuer, Copilot has THREE origins, and each is its own option.\n *\n * ## Three origins, three independent pins\n *\n * `oauthIssuer` (`https://github.com`), `githubApiBaseUrl`\n * (`https://api.github.com`) and `baseUrl` (`https://api.githubcopilot.com`) are\n * three separate options, pinned separately by three separate calls to {@link\n * issuerOf}. No module may dispatch a request to an origin other than its own\n * pinned one — the device flow cannot reach the Copilot surface, the Copilot\n * surface cannot reach the token exchange. That is why {@link copilotFetch}\n * demands a {@link CopilotOrigin} rather than reading an origin off a shared\n * options bag: there is no options bag that holds all three, so there is no way\n * to pass the wrong one by forgetting which field applies.\n *\n * The pin is compared BEFORE the request is dispatched (Requirement 3.7). A\n * post-hoc check on the response would already have leaked the `Authorization`\n * header to whatever origin the URL named.\n *\n * ## What is bounded, and why each bound exists\n *\n * - **`redirect: 'manual'` plus {@link rejectCopilotRedirect}** — a followed hop\n * re-sends the credential headers to the redirect target (Requirements 3.8, 7.8).\n * - **A per-request deadline** — a server that accepts the connection and then\n * says nothing must not hang a CLI.\n * - **Bytes AND chunk count on every read** — bytes alone still lets a stream of\n * one-byte chunks pin the event loop, so both are checked (Requirements 4.7, 13.6).\n * - **{@link raceAbort}** — `fetch` honours a signal, but a pending read does not\n * necessarily reject the instant it aborts. Racing makes the caller's signal win\n * immediately rather than eventually (Requirement 4.6).\n * - **{@link positiveSafeInteger} on every configured limit** — a `0`, a `NaN` or\n * a float silently disables a bound, which is worse than rejecting the config.\n *\n * @module ai-agent-sdk/providers/copilot/http\n */\n\nimport { AgentSdkError, MODEL_ERROR_CODES, waitForSettlement } from '@alvin0/ai-agent-sdk-core'\nimport { COPILOT_ERROR_CODES } from './error-codes.ts'\nimport { rejectCopilotRedirect, type CopilotHttpOperation } from './no-follow.ts'\n\n/** Deadline for one Copilot HTTP request when the caller configures none. */\nexport const COPILOT_DEFAULT_REQUEST_TIMEOUT_MS = 30_000\n\n/** Maximum response bytes retained or parsed when the caller configures none. */\nexport const COPILOT_DEFAULT_MAX_RESPONSE_BYTES = 1024 * 1024\n\n/** Maximum response chunks accepted when the caller configures none. */\nexport const COPILOT_DEFAULT_MAX_RESPONSE_CHUNKS = 10_000\n\n/** Bound on releasing a body that is being discarded. Never a caller-visible wait. */\nconst TEARDOWN_TIMEOUT_MS = 30_000\n\n/**\n * Which of the three configurable origins a pin came from.\n *\n * The field name travels with the pin so an origin error can name the option the\n * caller has to fix, rather than saying \"origin invalid\" about one of three\n * settings.\n */\nexport type CopilotOriginField = 'oauthIssuer' | 'githubApiBaseUrl' | 'baseUrl'\n\n/** A validated, pinned origin: the only thing {@link copilotFetch} accepts as a target. */\nexport interface CopilotOrigin {\n /** The option this pin came from. */\n readonly field: CopilotOriginField\n /** Normalized base URL with trailing slashes removed. Safe to concatenate a path onto. */\n readonly href: string\n /** The serialized origin every request URL is compared against. */\n readonly origin: string\n}\n\n/** Shared HTTP settings. Every field is optional and every default is a bound, not \"unlimited\". */\nexport interface CopilotHttpOptions {\n /** Cancellation for the request and for the body read. */\n readonly signal?: AbortSignal\n /** HTTP implementation, for tests and non-browser runtimes. */\n readonly fetch?: typeof globalThis.fetch\n /** Deadline for one request. Defaults to {@link COPILOT_DEFAULT_REQUEST_TIMEOUT_MS}. */\n readonly requestTimeoutMs?: number\n /** Maximum response bytes. Defaults to {@link COPILOT_DEFAULT_MAX_RESPONSE_BYTES}. */\n readonly maxResponseBytes?: number\n /** Maximum response chunks. Defaults to {@link COPILOT_DEFAULT_MAX_RESPONSE_CHUNKS}. */\n readonly maxResponseChunks?: number\n /**\n * Permit an `http:` origin for a trusted local test endpoint. Defaults to false.\n *\n * A separate, explicitly enabled option rather than a lenient default, because\n * cleartext HTTP here carries a bearer token (Requirement 2.2).\n */\n readonly allowInsecureIssuer?: boolean\n}\n\n/** One request, carrying the origin it is pinned to and the call site it belongs to. */\nexport interface CopilotRequest {\n /** The pin from {@link issuerOf}. The URL must be on this origin. */\n readonly pinned: CopilotOrigin\n /** Absolute target URL; build it with {@link copilotUrl} to keep it on the pin. */\n readonly url: string | URL\n /** Which call site this is, for the redirect error. */\n readonly operation: CopilotHttpOperation\n /** Method, headers and body. `signal` and `redirect` are set by this module. */\n readonly init: RequestInit\n}\n\n/**\n * Validate and pin one of the three configurable origins.\n *\n * Two rejections, each for a concrete reason:\n *\n * - **Userinfo** (`https://user:pass@host`) — credentials in a URL would be sent\n * as an extra `Authorization` header the caller never wrote, and they end up in\n * logs. There is no legitimate use for them on any of these three origins.\n * - **`http:` without `allowInsecureIssuer`** — see the option's note.\n *\n * @param field - which option is being pinned; appears in the error message.\n * @param configured - the caller's value, or `undefined` to take the default.\n * @param fallback - the exported default for this field.\n * @param options - read for `allowInsecureIssuer` only.\n * @returns the pin to hand to {@link copilotFetch}.\n * @throws AgentSdkError with `COPILOT_ENDPOINT_ORIGIN_INVALID` when the value is\n * unparsable, carries userinfo, or is cleartext without the opt-in.\n */\nexport function issuerOf(\n field: CopilotOriginField,\n configured: string | undefined,\n fallback: string,\n options: Pick<CopilotHttpOptions, 'allowInsecureIssuer'> = {},\n): CopilotOrigin {\n const raw = configured ?? fallback\n let url: URL\n try {\n url = new URL(raw)\n } catch (error: unknown) {\n throw originError(`Copilot ${field} is not an absolute URL`, error)\n }\n if (url.username.length > 0 || url.password.length > 0) {\n throw originError(`Copilot ${field} must not contain credentials`)\n }\n if (url.protocol !== 'https:'\n && !(options.allowInsecureIssuer === true && url.protocol === 'http:')) {\n throw originError(`Copilot ${field} must use https unless allowInsecureIssuer is enabled`)\n }\n return Object.freeze({ field, href: url.href.replace(/\\/+$/, ''), origin: url.origin })\n}\n\n/**\n * Build an absolute URL on a pinned origin.\n *\n * The path has to be absolute-and-rooted: a relative path resolved against a base\n * is exactly how a URL quietly ends up somewhere else, and a path that is itself\n * absolute (`//evil.tld/x` or `https://evil.tld/x`) would replace the origin\n * outright.\n * @param pinned - the pin from {@link issuerOf}.\n * @param path - a path beginning with a single `/`.\n * @returns the absolute URL string, guaranteed to be on `pinned.origin`.\n * @throws AgentSdkError with `COPILOT_ENDPOINT_ORIGIN_INVALID` when the path could\n * move the request off the pin.\n */\nexport function copilotUrl(pinned: CopilotOrigin, path: string): string {\n if (!path.startsWith('/') || path.startsWith('//')) {\n throw originError(`Copilot ${pinned.field} path must start with a single '/'`)\n }\n const url = new URL(`${pinned.href}${path}`)\n if (url.origin !== pinned.origin) {\n throw originError(`Copilot ${pinned.field} path must stay on the pinned origin`)\n }\n return url.href\n}\n\n/**\n * Dispatch one Copilot request with the origin pin, no-follow and deadline applied.\n *\n * Order matters and is the contract: the pin is compared FIRST, so a URL on the\n * wrong origin never receives the headers; then the request goes out with\n * `redirect: 'manual'`; then the response passes the redirect guard before it is\n * handed back. The body is left unread — {@link readCopilotResponseText} is the\n * bounded reader for it.\n * @param request - the pin, the URL, the call site and the init.\n * @param options - signal, fetch implementation and limits.\n * @returns the response, already cleared by the redirect guard.\n * @throws AgentSdkError with `COPILOT_ENDPOINT_ORIGIN_INVALID` when the URL is off\n * the pin, or `COPILOT_REDIRECT_REJECTED` when the response was a redirect.\n */\nexport async function copilotFetch(\n request: CopilotRequest,\n options: CopilotHttpOptions = {},\n): Promise<Response> {\n const url = requestUrl(request)\n const timeoutMs = positiveSafeInteger(\n options.requestTimeoutMs ?? COPILOT_DEFAULT_REQUEST_TIMEOUT_MS,\n 'requestTimeoutMs',\n )\n const timeout = AbortSignal.timeout(timeoutMs)\n const signal = options.signal === undefined ? timeout : AbortSignal.any([options.signal, timeout])\n const fetchImpl = options.fetch ?? globalThis.fetch\n if (typeof fetchImpl !== 'function') throw new TypeError('Copilot HTTP requires fetch')\n const response = await raceAbort(Promise.resolve(fetchImpl(url, {\n ...request.init,\n signal,\n redirect: 'manual',\n })), signal)\n await rejectCopilotRedirect(response, url, request.operation, TEARDOWN_TIMEOUT_MS)\n return response\n}\n\n/**\n * Read a response body as text, bounded on bytes and on chunk count.\n *\n * A declared `content-length` over the limit is refused before a single chunk is\n * read; the running totals then catch a body that lies about its length or sends\n * none. Either way the reader is cancelled rather than abandoned.\n * @param response - a response already cleared by {@link copilotFetch}.\n * @param options - limits and the signal to race the read against.\n * @returns the decoded text, or `''` when there was no body.\n * @throws RangeError when a configured bound is exceeded.\n */\nexport async function readCopilotResponseText(\n response: Response,\n options: CopilotHttpOptions = {},\n): Promise<string> {\n const maxBytes = positiveSafeInteger(\n options.maxResponseBytes ?? COPILOT_DEFAULT_MAX_RESPONSE_BYTES,\n 'maxResponseBytes',\n )\n const maxChunks = positiveSafeInteger(\n options.maxResponseChunks ?? COPILOT_DEFAULT_MAX_RESPONSE_CHUNKS,\n 'maxResponseChunks',\n )\n const declared = Number(response.headers.get('content-length'))\n if (Number.isFinite(declared) && declared > maxBytes) {\n if (response.body !== null) {\n await waitForSettlement(response.body.cancel().catch(() => undefined), TEARDOWN_TIMEOUT_MS)\n }\n throw new RangeError(`Copilot HTTP response exceeds the ${maxBytes}-byte limit`)\n }\n if (response.body === null) return ''\n const timeout = AbortSignal.timeout(positiveSafeInteger(\n options.requestTimeoutMs ?? COPILOT_DEFAULT_REQUEST_TIMEOUT_MS,\n 'requestTimeoutMs',\n ))\n const signal = options.signal === undefined ? timeout : AbortSignal.any([options.signal, timeout])\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let bytes = 0\n let chunks = 0\n let result = ''\n try {\n while (true) {\n const next = await raceAbort(reader.read(), signal)\n if (next.done) return result + decoder.decode()\n if (next.value === undefined) continue\n chunks++\n bytes += next.value.byteLength\n if (chunks > maxChunks || bytes > maxBytes) {\n await waitForSettlement(reader.cancel().catch(() => undefined), TEARDOWN_TIMEOUT_MS)\n throw new RangeError('Copilot HTTP response exceeds its configured resource limit')\n }\n result += decoder.decode(next.value, { stream: true })\n }\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Settle as soon as either the pending work or the signal does.\n *\n * An already-aborted signal rejects synchronously rather than after one turn, so\n * a caller who aborts before the call never dispatches the request at all.\n * @param pending - the work to race.\n * @param signal - the signal that gets to win.\n * @returns the pending value, when it arrives first.\n */\nexport function raceAbort<T>(pending: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) {\n void pending.catch(() => undefined)\n return Promise.reject(abortReason(signal))\n }\n return new Promise<T>((resolve, reject) => {\n const abort = () => { cleanup(); reject(abortReason(signal)) }\n const cleanup = () => signal.removeEventListener('abort', abort)\n signal.addEventListener('abort', abort, { once: true })\n void pending.then(\n value => { cleanup(); resolve(value) },\n error => { cleanup(); reject(error) },\n )\n })\n}\n\n/**\n * Accept a configured limit only when it can actually bound anything.\n *\n * `0`, a negative, a float and `NaN` all disable a bound silently, so each one is\n * rejected instead of normalized.\n * @param value - the configured number.\n * @param field - the option name, for the message.\n * @returns the value, unchanged.\n * @throws RangeError when the value cannot serve as a bound.\n */\nexport function positiveSafeInteger(value: number, field: string): number {\n if (!Number.isSafeInteger(value) || value < 1) {\n throw new RangeError(`Copilot HTTP ${field} must be a positive safe integer`)\n }\n return value\n}\n\n/**\n * Resolve the request URL and compare it against the pin, before anything is sent.\n *\n * Userinfo is rejected here as well as in {@link issuerOf}: `URL.origin` ignores\n * it, so a URL on the right origin can still carry credentials the caller never\n * intended to send.\n */\nfunction requestUrl(request: CopilotRequest): string {\n let url: URL\n try {\n url = new URL(request.url)\n } catch (error: unknown) {\n throw originError(`Copilot ${request.operation} target is not an absolute URL`, error)\n }\n if (url.username.length > 0 || url.password.length > 0) {\n throw originError(`Copilot ${request.operation} target must not contain credentials`)\n }\n if (url.origin !== request.pinned.origin) {\n throw originError(\n `Copilot ${request.operation} target origin '${url.origin}' is not the pinned `\n + `${request.pinned.field} origin '${request.pinned.origin}'`,\n )\n }\n return url.href\n}\n\nfunction originError(message: string, cause?: unknown): AgentSdkError {\n return new AgentSdkError(\n message,\n COPILOT_ERROR_CODES.ENDPOINT_ORIGIN_INVALID,\n cause === undefined ? undefined : { cause },\n )\n}\n\nfunction abortReason(signal: AbortSignal): unknown {\n return signal.reason ?? new AgentSdkError('Copilot HTTP request aborted', MODEL_ERROR_CODES.ABORTED)\n}\n","/**\n * `Copilot_Catalog`: read `GET /models`, then PARTITION what came back.\n *\n * Discovery is the right default for this surface (Requirement 8.1): which models\n * an account may call depends on its plan, on its organisation's policy, and on\n * the editor identity the request presents, so no hardcoded list is correct for\n * two accounts at once. Passing `models` explicitly skips discovery entirely\n * (Requirement 8.5) — that decision belongs to the adapter, which simply does not\n * call this module in that case.\n *\n * ## Two levels of wrongness, two different answers\n *\n * The defensive read runs in a fixed order, and the order IS the contract:\n *\n * ```text\n * 1. redirect (every shape) ⇒ COPILOT_REDIRECT_REJECTED\n * 2. declared content-length over the limit ⇒ RangeError, body cancelled\n * 3. accumulated bytes/chunks over the limit ⇒ RangeError, reader cancelled\n * 4. body is not JSON, root is not an object,\n * or `data` is not an array ⇒ COPILOT_CATALOG_MALFORMED\n * 5. entry count over maxCatalogModels ⇒ COPILOT_CATALOG_MALFORMED\n * 6. entry: id is not a non-empty string ⇒ omitted 'model-id-missing'\n * 7. entry: capabilities.type unrecognized ⇒ omitted 'capability-type-unrecognized'\n * ```\n *\n * Steps 4 and 5 are STRUCTURAL, and a structural mismatch is an error rather than\n * a starting point for a guess (Requirement 8.8): a model list inferred from a\n * body this SDK could not read is a list nobody can be held to. Steps 6 and 7 are\n * at ENTRY level, and there the entry is dropped while the rest of the catalog\n * survives — one unfamiliar entry must not kill every model that still works.\n *\n * Dropping rather than listing-with-a-flag is the same judgement in the other\n * direction (Requirements 9.4, 9.5): listing a model this SDK cannot dispatch is\n * worse than not listing it, because it shows up in a selector and then fails at\n * call time, far from the cause.\n *\n * ## Metadata is translated, never invented\n *\n * Every field of {@link ProviderCatalogModel} is filled only from a field the\n * endpoint actually supplied (Requirement 8.4). The trap is\n * `inputModalities`: with no vision signal at all the field is ABSENT, NOT\n * `['text']`. An explicit list without `image` is a NEGATIVE claim the registry\n * acts on — it projects images to text — so inventing `['text']` would silently\n * strip images from every request to a model that may well accept them. Absent\n * means unknown, and unknown is what the endpoint said.\n *\n * `declaredEndpoint` follows the same rule and stays `undefined` when the catalog\n * discloses nothing. `undefined` is NOT \"not supported\"; the router treats the two\n * states differently (Requirement 8.6).\n *\n * ## The catalog is advisory\n *\n * `omitted` does not fail anything. A dispatched request to an omitted id still\n * goes out — it just takes the router's default branch — and a real error from the\n * endpoint remains the final word whenever metadata and behaviour disagree\n * (Requirement 8.6).\n *\n * @module ai-agent-sdk/providers/copilot/catalog\n */\n\nimport { AgentSdkError } from '@alvin0/ai-agent-sdk-core'\nimport type { ModelModality } from '@alvin0/ai-agent-sdk-core/provider'\nimport type {\n ProviderCatalogModel,\n RuntimeModelDiscoveryContext,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport { COPILOT_BASE_URL } from './common/identity.ts'\nimport { COPILOT_ERROR_CODES } from './errors.ts'\nimport {\n copilotFetch,\n copilotUrl,\n issuerOf,\n positiveSafeInteger,\n readCopilotResponseText,\n} from './common/http.ts'\n\n/** Path of the catalog surface, relative to the pinned Copilot base URL. */\nexport const COPILOT_CATALOG_PATH = '/models'\n\n/** Maximum raw catalog bytes when the caller configures none. */\nexport const COPILOT_DEFAULT_MAX_CATALOG_BYTES = 4 * 1024 * 1024\n\n/** Maximum catalog entries accepted when the caller configures none. */\nexport const COPILOT_DEFAULT_MAX_CATALOG_MODELS = 2_048\n\n/** Maximum catalog response chunks accepted when the caller configures none. */\nexport const COPILOT_DEFAULT_MAX_CATALOG_CHUNKS = 10_000\n\n/** Catalog request deadline when the caller configures none. */\nexport const COPILOT_DEFAULT_CATALOG_TIMEOUT_MS = 30_000\n\n/**\n * Which endpoint a generation model is dispatched to.\n *\n * Declared HERE rather than in `./router.ts`, where the router's own types live,\n * for one structural reason: `./router.ts` imports {@link CopilotGenerationModel}\n * from this module, so the dependency edge already runs router → catalog. Putting\n * the endpoint union in the router would make it run both ways, which the repo's\n * source-ownership check forbids and which nothing here needs. `./router.ts`\n * re-exports this type, so the router remains the module a reader goes to for\n * endpoint selection.\n */\nexport type CopilotEndpoint = 'responses' | 'chat-completions'\n\n/**\n * One entry of `GET /models`, typed as UNKNOWN at every leaf on purpose.\n *\n * `unknown` rather than the shape the endpoint documents, because this is the one\n * place a response that changed shape arrives: a declared `string` would let a\n * number flow into a catalog field and fail somewhere else entirely.\n */\ninterface WireCopilotModel {\n readonly id?: unknown\n readonly name?: unknown\n readonly capabilities?: {\n readonly type?: unknown\n readonly family?: unknown\n readonly limits?: {\n readonly max_context_window_tokens?: unknown\n readonly max_output_tokens?: unknown\n readonly max_inputs?: unknown\n }\n readonly supports?: Readonly<Record<string, unknown>>\n }\n readonly vision?: unknown\n readonly model_picker_enabled?: unknown\n}\n\n/** Why an entry was left out of both catalogs. */\nexport type CopilotOmitReason =\n /** capabilities.type is not one of the recognized values. */\n | 'capability-type-unrecognized'\n /** No usable id. */\n | 'model-id-missing'\n\n/** One entry that was dropped, with the reason an operator needs to see it. */\nexport interface CopilotOmittedModel {\n /** The entry's id, or `''` when it had none — the reason says which. */\n readonly id: string\n /** Why it was dropped. */\n readonly reason: CopilotOmitReason\n}\n\n/** A generation model, plus whatever the catalog disclosed about its endpoint. */\nexport interface CopilotGenerationModel {\n /** The SDK catalog model, handed to `provider-http` unchanged. */\n readonly model: ProviderCatalogModel\n /**\n * The endpoint the catalog disclosed, when it disclosed one.\n *\n * `undefined` means UNKNOWN, not \"not supported\". The router handles those two\n * states differently (Requirement 8.6).\n */\n readonly declaredEndpoint: CopilotEndpoint | undefined\n}\n\n/**\n * An embedding model, carrying only the facts the catalog stated.\n *\n * Deliberately NOT a {@link ProviderCatalogModel}: an embedding model has no\n * context window or output cap to report, and `Copilot_Embedding_Adapter` needs\n * different facts (batch ceiling, whether a requested dimension count is\n * honoured). Every field but `id` is optional because every one of them is absent\n * from some real entry.\n */\nexport interface CopilotEmbeddingModel {\n /** Wire model id, passed to the endpoint verbatim. */\n readonly id: string\n /** Display label, when the catalog supplied one. */\n readonly name?: string\n /** Model family, when disclosed; embedding compatibility identity is derived from it. */\n readonly family?: string\n /** Token ceiling for one input, from `limits.max_context_window_tokens`. */\n readonly maxInputTokens?: number\n /** Ceiling on inputs per request, from `limits.max_inputs`. */\n readonly maxInputs?: number\n /** Whether `supports.dimensions` was stated, and what it said. */\n readonly supportsDimensions?: boolean\n}\n\n/** The result of one discovery, partitioned. */\nexport interface CopilotCatalogSnapshot {\n /** Models usable for generation, each with its preliminary endpoint disclosure. */\n readonly generation: readonly CopilotGenerationModel[]\n /** Models usable for embedding. */\n readonly embedding: readonly CopilotEmbeddingModel[]\n /** Dropped entries with their reasons — these go to observation, not to a catalog. */\n readonly omitted: readonly CopilotOmittedModel[]\n}\n\n/** Resolved bounds for one catalog read. Every field is a bound, never \"unlimited\". */\nexport interface CopilotCatalogLimits {\n /** Maximum raw response bytes. */\n readonly maxBytes: number\n /** Maximum entries accepted before the response is called malformed. */\n readonly maxModels: number\n /** Maximum response chunks. */\n readonly maxChunks: number\n /** Deadline for the catalog request AND its body read. */\n readonly timeoutMs: number\n /** Permit an `http:` base URL for a trusted local test endpoint. */\n readonly allowInsecureHttp?: boolean\n}\n\n/**\n * The caller-facing catalog options, in the spelling `CopilotProviderOptions` uses.\n *\n * Split into two groups on purpose. The four `max*`/`timeout` values bound ONE\n * read and are resolved here by {@link resolveCopilotCatalogLimits}. The three\n * cache values (TTL, stale TTL, failure backoff) bound how often reads happen at\n * all, and `provider-http` already owns that policy — {@link\n * copilotCatalogCacheOptions} forwards them without a default, so an unset option\n * keeps the runtime's own default instead of this package pinning a second one\n * (Requirement 8.7).\n */\nexport interface CopilotCatalogOptions {\n /** Maximum raw catalog bytes. Defaults to {@link COPILOT_DEFAULT_MAX_CATALOG_BYTES}. */\n readonly maxCatalogBytes?: number\n /** Maximum catalog entries. Defaults to {@link COPILOT_DEFAULT_MAX_CATALOG_MODELS}. */\n readonly maxCatalogModels?: number\n /** Maximum catalog response chunks. Defaults to {@link COPILOT_DEFAULT_MAX_CATALOG_CHUNKS}. */\n readonly maxCatalogChunks?: number\n /** Catalog request deadline. Defaults to {@link COPILOT_DEFAULT_CATALOG_TIMEOUT_MS}. */\n readonly catalogTimeoutMs?: number\n /** How long a discovered catalog stays fresh. */\n readonly catalogTtlMs?: number\n /** How long a stale catalog may still be served while a refresh is attempted. */\n readonly catalogStaleTtlMs?: number\n /** How long to wait before retrying discovery after it failed. */\n readonly catalogFailureBackoffMs?: number\n /** Permit an `http:` base URL for a trusted local test endpoint. */\n readonly allowInsecureHttp?: boolean\n}\n\n/**\n * Resolve the per-read bounds, rejecting a value that cannot bound anything.\n *\n * Validation happens here rather than at the read, so a `0` or a `NaN` in the\n * configuration is a construction-time error instead of a silently disabled limit\n * discovered under load (Requirement 8.2).\n * @param options - the caller's catalog options.\n * @returns the four resolved bounds plus the insecure-HTTP opt-in.\n * @throws RangeError when a configured bound is not a positive safe integer.\n */\nexport function resolveCopilotCatalogLimits(\n options: CopilotCatalogOptions = {},\n): CopilotCatalogLimits {\n return Object.freeze({\n maxBytes: positiveSafeInteger(\n options.maxCatalogBytes ?? COPILOT_DEFAULT_MAX_CATALOG_BYTES,\n 'maxCatalogBytes',\n ),\n maxModels: positiveSafeInteger(\n options.maxCatalogModels ?? COPILOT_DEFAULT_MAX_CATALOG_MODELS,\n 'maxCatalogModels',\n ),\n maxChunks: positiveSafeInteger(\n options.maxCatalogChunks ?? COPILOT_DEFAULT_MAX_CATALOG_CHUNKS,\n 'maxCatalogChunks',\n ),\n timeoutMs: positiveSafeInteger(\n options.catalogTimeoutMs ?? COPILOT_DEFAULT_CATALOG_TIMEOUT_MS,\n 'catalogTimeoutMs',\n ),\n ...(options.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureHttp: options.allowInsecureHttp }),\n })\n}\n\n/**\n * Forward the three cache-policy options, and only the ones that were set.\n *\n * A conditional spread rather than defaults: `provider-http` owns catalog caching,\n * and a default written here would override the runtime's own without anyone\n * asking for it (Requirement 8.7).\n * @param options - the caller's catalog options.\n * @returns an object carrying only the cache options the caller supplied.\n */\nexport function copilotCatalogCacheOptions(options: CopilotCatalogOptions = {}): {\n readonly catalogTtlMs?: number\n readonly catalogStaleTtlMs?: number\n readonly catalogFailureBackoffMs?: number\n} {\n return {\n ...(options.catalogTtlMs === undefined ? {} : { catalogTtlMs: options.catalogTtlMs }),\n ...(options.catalogStaleTtlMs === undefined\n ? {}\n : { catalogStaleTtlMs: options.catalogStaleTtlMs }),\n ...(options.catalogFailureBackoffMs === undefined\n ? {}\n : { catalogFailureBackoffMs: options.catalogFailureBackoffMs }),\n }\n}\n\n/**\n * Read `GET {baseUrl}/models` and partition it.\n *\n * The base URL is re-pinned here from `context.baseUrl` rather than trusted as a\n * string: the catalog is the first Copilot call an adapter makes, and a pin\n * compared before dispatch is the only check that runs before the resolved\n * `Authorization` header leaves the process.\n * @param context - the discovery context `provider-http` supplies: base URL,\n * already-resolved headers, and the operation's signal.\n * @param limits - bounds from {@link resolveCopilotCatalogLimits}.\n * @param fetchImpl - HTTP implementation, injected for tests and non-browser runtimes.\n * @returns the partitioned snapshot; an empty one when the endpoint answered a\n * non-2xx status, because a catalog that could not be fetched is advisory too.\n * @throws AgentSdkError with `COPILOT_REDIRECT_REJECTED` on any redirect shape, or\n * `COPILOT_CATALOG_MALFORMED` when the response is the wrong shape structurally.\n * @throws RangeError when the response exceeds a configured bound.\n */\nexport async function discoverCopilotModels(\n context: RuntimeModelDiscoveryContext,\n limits: CopilotCatalogLimits,\n fetchImpl: typeof globalThis.fetch,\n): Promise<CopilotCatalogSnapshot> {\n const pinned = issuerOf('baseUrl', context.baseUrl.href, COPILOT_BASE_URL, {\n ...(limits.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureIssuer: limits.allowInsecureHttp }),\n })\n const url = copilotUrl(pinned, COPILOT_CATALOG_PATH)\n const http = {\n signal: context.signal,\n fetch: fetchImpl,\n requestTimeoutMs: limits.timeoutMs,\n maxResponseBytes: limits.maxBytes,\n maxResponseChunks: limits.maxChunks,\n ...(limits.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureIssuer: limits.allowInsecureHttp }),\n }\n const response = await copilotFetch(\n { pinned, url, operation: 'model catalog', init: { method: 'GET', headers: context.headers } },\n http,\n )\n if (!response.ok) {\n if (response.body !== null) await response.body.cancel().catch(() => undefined)\n return EMPTY_SNAPSHOT\n }\n const text = await readCopilotResponseText(response, http)\n return partitionCopilotCatalog(parseCatalogBody(text), limits.maxModels)\n}\n\n/**\n * Partition an already-read catalog body.\n *\n * Exported separately from the fetch so the partition is testable — and readable —\n * as what it is: a pure function from a parsed body to three lists.\n * @param body - the parsed root object of the catalog response.\n * @param maxModels - entry-count ceiling; exceeding it is structural, not per-entry.\n * @returns the partitioned snapshot.\n * @throws AgentSdkError with `COPILOT_CATALOG_MALFORMED` when `data` is not an\n * array or holds more than `maxModels` entries.\n */\nexport function partitionCopilotCatalog(\n body: Record<string, unknown>,\n maxModels: number,\n): CopilotCatalogSnapshot {\n const data = body.data\n if (!Array.isArray(data)) {\n throw malformed('Copilot model catalog `data` must be an array')\n }\n if (data.length > maxModels) {\n throw malformed(`Copilot model catalog exceeds the ${maxModels}-model limit`)\n }\n const generation: CopilotGenerationModel[] = []\n const embedding: CopilotEmbeddingModel[] = []\n const omitted: CopilotOmittedModel[] = []\n for (const candidate of data as readonly unknown[]) {\n const entry: WireCopilotModel = isRecord(candidate) ? candidate as WireCopilotModel : {}\n const id = typeof entry.id === 'string' ? entry.id : ''\n if (id.length === 0) {\n omitted.push({ id, reason: 'model-id-missing' })\n continue\n }\n const type = entry.capabilities?.type\n if (type === 'chat') {\n generation.push(generationModel(id, entry))\n continue\n }\n if (type === 'embeddings') {\n embedding.push(embeddingModel(id, entry))\n continue\n }\n omitted.push({ id, reason: 'capability-type-unrecognized' })\n }\n return Object.freeze({\n generation: Object.freeze(generation),\n embedding: Object.freeze(embedding),\n omitted: Object.freeze(omitted),\n })\n}\n\n/** The snapshot returned when there is nothing to report, frozen and shared. */\nconst EMPTY_SNAPSHOT: CopilotCatalogSnapshot = Object.freeze({\n generation: Object.freeze([]),\n embedding: Object.freeze([]),\n omitted: Object.freeze([]),\n})\n\n/** Modalities claimed when — and only when — a vision signal was actually present. */\nconst TEXT_AND_IMAGE: readonly ModelModality[] = Object.freeze(['text', 'image'])\n\n/**\n * Translate one `type: 'chat'` entry, filling only what the endpoint supplied.\n *\n * `name` is not defaulted to `id`: a display label the endpoint did not send is a\n * label this layer would be inventing, and the layer that renders a selector\n * already falls back to the id.\n */\nfunction generationModel(id: string, entry: WireCopilotModel): CopilotGenerationModel {\n const limits = entry.capabilities?.limits\n const supports = entry.capabilities?.supports\n const vision = entry.vision === true || supports?.vision === true\n const contextWindow = positiveInteger(limits?.max_context_window_tokens)\n const maxTokens = positiveInteger(limits?.max_output_tokens)\n return Object.freeze({\n model: Object.freeze({\n id,\n ...(typeof entry.name === 'string' && entry.name.length > 0 ? { name: entry.name } : {}),\n ...(contextWindow === undefined ? {} : { contextWindow }),\n ...(maxTokens === undefined ? {} : { maxTokens }),\n // No vision signal ⇒ ABSENT. `['text']` would be a negative claim about\n // image input that the endpoint never made.\n ...(vision ? { inputModalities: TEXT_AND_IMAGE } : {}),\n }),\n declaredEndpoint: declaredEndpointOf(supports),\n })\n}\n\n/** Translate one `type: 'embeddings'` entry, under the same fill-only-what-was-said rule. */\nfunction embeddingModel(id: string, entry: WireCopilotModel): CopilotEmbeddingModel {\n const capabilities = entry.capabilities\n const limits = capabilities?.limits\n const maxInputTokens = positiveInteger(limits?.max_context_window_tokens)\n const maxInputs = positiveInteger(limits?.max_inputs)\n const dimensions = capabilities?.supports?.dimensions\n return Object.freeze({\n id,\n ...(typeof entry.name === 'string' && entry.name.length > 0 ? { name: entry.name } : {}),\n ...(typeof capabilities?.family === 'string' && capabilities.family.length > 0\n ? { family: capabilities.family }\n : {}),\n ...(maxInputTokens === undefined ? {} : { maxInputTokens }),\n ...(maxInputs === undefined ? {} : { maxInputs }),\n ...(typeof dimensions === 'boolean' ? { supportsDimensions: dimensions } : {}),\n })\n}\n\n/**\n * Read the endpoint disclosure, and only a disclosure.\n *\n * `supports.responses === true` says `/responses`; `false` says `/chat/completions`\n * — the endpoint stated something either way. Anything else, including the field\n * being absent or holding a non-boolean, is UNKNOWN and stays `undefined`, which\n * is a different state from \"not supported\" (Requirement 8.6).\n */\nfunction declaredEndpointOf(\n supports: Readonly<Record<string, unknown>> | undefined,\n): CopilotEndpoint | undefined {\n const responses = supports?.responses\n if (responses === true) return 'responses'\n if (responses === false) return 'chat-completions'\n return undefined\n}\n\n/**\n * Parse the catalog body, treating an unreadable body as structural.\n *\n * Both failures land on the same code because they are the same problem: the\n * response is not a catalog, and there is nothing here to guess a model list from\n * (Requirement 8.8).\n */\nfunction parseCatalogBody(text: string): Record<string, unknown> {\n let parsed: unknown\n try {\n parsed = JSON.parse(text)\n } catch (error: unknown) {\n throw malformed('Copilot model catalog is not valid JSON', error)\n }\n if (!isRecord(parsed)) {\n throw malformed('Copilot model catalog must be a JSON object')\n }\n return parsed\n}\n\n/** Accept a numeric metadata field only when it can serve as a capacity. */\nfunction positiveInteger(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : undefined\n}\n\n/** A JSON object, excluding arrays — `data` being at the root is not a catalog. */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction malformed(message: string, cause?: unknown): AgentSdkError {\n return new AgentSdkError(\n message,\n COPILOT_ERROR_CODES.CATALOG_MALFORMED,\n cause === undefined ? undefined : { cause },\n )\n}\n","/**\n * Store capture: decide which credential store variant the caller passed, and\n * take a snapshot of its identity and methods.\n *\n * The counterpart of `captureCodexStore`, and it holds the same two lines:\n *\n * - **No accessors.** Every property is read through\n * `Object.getOwnPropertyDescriptor`, and a descriptor without a `value` is\n * REJECTED rather than invoked. Telling the two variants apart must not run a\n * line of the caller's code, because a getter here would run during provider\n * construction, in an order the caller cannot see.\n * - **No I/O.** Methods are captured, not called. Nothing touches storage at\n * construction time; the first read happens when an operation asks for a\n * credential.\n *\n * Methods are invoked through `Reflect.apply` with the original object as the\n * receiver, so a store written against `this` keeps working after capture.\n *\n * @module ai-agent-sdk/providers/copilot/store-capture\n */\n\nimport {\n AgentSdkError,\n CREDENTIAL_CAPABILITY_API_VERSION,\n type CredentialCommitInput,\n type CredentialCommitResult,\n type CredentialOperationOptions,\n type CredentialRecord,\n} from '@alvin0/ai-agent-sdk-core/provider'\nimport type {\n CopilotAuthFile,\n CopilotAuthStore,\n CopilotCredentialStore,\n} from './store-types.ts'\n\n/** A captured store, tagged with the variant it came from. */\nexport type CapturedCopilotStore =\n | { readonly kind: 'legacy'; readonly label: string; readonly store: CopilotAuthStore }\n | { readonly kind: 'versioned'; readonly label: string; readonly store: CopilotCredentialStore }\n\n/** Capture store identity and methods without invoking accessors or doing storage I/O. */\nexport function captureCopilotStore(value: unknown): CapturedCopilotStore {\n try {\n if (value === null || typeof value !== 'object') throw new TypeError('store must be an object')\n const marker = dataValue(value, 'kind', false)\n if (marker === undefined) return captureLegacy(value)\n if (marker !== 'credential-store'\n || dataValue(value, 'apiVersion') !== CREDENTIAL_CAPABILITY_API_VERSION) {\n throw new TypeError('unsupported credential-store marker')\n }\n const id = boundedString(dataValue(value, 'id'), 128, 'credential store id')\n const label = boundedString(dataValue(value, 'label'), 256, 'credential store label')\n const read = capturedMethod<\n [CredentialOperationOptions], Promise<CredentialRecord<CopilotAuthFile> | undefined>\n >(value, 'read')\n const commit = capturedMethod<\n [CredentialCommitInput<CopilotAuthFile>, CredentialOperationOptions], Promise<CredentialCommitResult>\n >(value, 'commit')\n return Object.freeze({\n kind: 'versioned',\n label,\n store: Object.freeze({\n kind: 'credential-store',\n apiVersion: CREDENTIAL_CAPABILITY_API_VERSION,\n id,\n label,\n read,\n commit,\n }),\n })\n } catch (error) {\n throw new AgentSdkError(\n 'Copilot authStore credential store is invalid',\n 'CREDENTIAL_STORE_INVALID',\n { cause: error },\n )\n }\n}\n\nfunction captureLegacy(source: object): CapturedCopilotStore {\n const location = boundedString(dataValue(source, 'location'), 1_024, 'Copilot auth store location')\n const read = capturedMethod<[], Promise<CopilotAuthFile | undefined>>(source, 'read')\n const write = capturedMethod<[CopilotAuthFile], Promise<void>>(source, 'write')\n return Object.freeze({\n kind: 'legacy',\n label: location,\n store: Object.freeze({ location, read, write }),\n })\n}\n\nfunction capturedMethod<Args extends readonly unknown[], Result>(\n source: object,\n key: PropertyKey,\n): (...args: Args) => Result {\n const method = dataValue(source, key)\n if (typeof method !== 'function') throw new TypeError(`${String(key)} must be a function`)\n return (...args: Args) => Reflect.apply(method, source, args) as Result\n}\n\n/**\n * Read an own-or-inherited DATA property. An accessor anywhere on the prototype\n * chain is an error: reading it would run caller code during construction.\n */\nfunction dataValue(source: object, key: PropertyKey, required = true): unknown {\n let owner: object | null = source\n while (owner !== null) {\n const descriptor = Object.getOwnPropertyDescriptor(owner, key)\n if (descriptor !== undefined) {\n if (!('value' in descriptor)) throw new TypeError(`${String(key)} must not be an accessor`)\n return descriptor.value\n }\n owner = Object.getPrototypeOf(owner)\n }\n if (!required) return undefined\n throw new TypeError(`missing ${String(key)}`)\n}\n\nfunction boundedString(value: unknown, maxLength: number, label: string): string {\n if (typeof value !== 'string' || value.length === 0 || value.length > maxLength) {\n throw new TypeError(`${label} must be a bounded non-empty string`)\n }\n return value\n}\n","/**\n * `copilotDualProtocol`: one `RuntimeWireProtocol` that speaks two protocols,\n * choosing per model id.\n *\n * ## Why a composite protocol rather than two adapters\n *\n * `RuntimeHttpProviderOptions<Dialect>` takes exactly ONE `protocol` and one\n * `dialect`, while Requirement 9 asks for two protocols on the single `copilot`\n * route, chosen per model. Two adapters would mean two routes\n * (`ModelProviderRegistrar.registerAdapter` maps route → adapter), which is\n * precisely the configuration coupling Requirement 9 exists to remove; adding a\n * `resolveProtocol` hook to `provider-http` would change a public surface\n * (Requirement 18.4) and charge every other provider for a concept only Copilot\n * needs. So the multi-protocol concept lives HERE, behind a protocol object the\n * runtime already knows how to hold (DD-1).\n *\n * What makes it work: all three protocol methods receive a `ProtocolRequest`, and\n * `ProtocolRequest.model` is a `ResolvedModelInfo`. The routing key — `model.id` —\n * is therefore present at EVERY decision point (`endpointPath`, `serialize`,\n * `translate`), so no extra channel has to be threaded through the runtime.\n *\n * ## Two structural rules this module obeys\n *\n * 1. **No `this`.** `captureRuntimeProtocol` re-invokes each method as\n * `Reflect.apply(method, source, args)`, so a method that read `this` would\n * read whatever receiver the runtime happened to capture with. Every method\n * below is a closure over `router` / `responses` / `chat`, which makes\n * rebinding the receiver harmless.\n * 2. **The composite dialect is flat.** `defineWireProtocol` snapshots\n * `defaultDialect` through `snapshotJsonObject` under depth/node limits, and\n * `provider-http` merges caller overrides with a SHALLOW spread. A dialect\n * nesting the two sub-dialects would let a caller overriding one chat flag\n * silently drop every other chat default. {@link CopilotDialect} is primitives\n * plus one string array — depth 2 (DD-2).\n *\n * ## The one thing the composite cannot hide\n *\n * `provider-http` sees a single protocol id, `'copilot-dual'`. The HTTP layer's\n * generic observation reports that id, not `'openai-responses'` or\n * `'openai-chat-completions'`, and `context.startProviderAttempt` takes only an\n * `origin` — which both endpoints share. So Requirement 9.8 is served by\n * {@link CopilotDualProtocolOptions.onDecision}, a channel this package owns,\n * rather than by widening a core type (DD-3).\n *\n * @module ai-agent-sdk/providers/copilot/dual-protocol\n */\n\nimport type {\n ChatCompletionsDialect,\n} from '@alvin0/ai-agent-sdk-protocol-openai-chat-completions'\nimport type { ResponsesDialect } from '@alvin0/ai-agent-sdk-protocol-responses'\nimport {\n defineWireProtocol,\n type ProtocolRequest,\n type ProtocolSseEvent,\n type ProtocolStreamChunk,\n type RuntimeWireProtocol,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport type { CopilotEndpointDecision, CopilotEndpointRouter } from './router.ts'\n\n/** Protocol id the HTTP layer reports for every Copilot request. */\nexport const COPILOT_DUAL_PROTOCOL_ID = 'copilot-dual'\n\n/**\n * The Copilot dialect, FLAT on purpose.\n *\n * Declared in this module rather than in `./adapter.ts` — where the design's file\n * map lists it — for one structural reason: `copilotAdapter` builds the composite,\n * so the source edge already runs adapter → dual-protocol, and the two projection\n * functions are runtime values. Declaring them in `./adapter.ts` would make that\n * edge bidirectional, which the repo's circular-dependency check forbids. The\n * public placement is preserved by re-export: `./adapter.ts` re-exports this type\n * and both projections, the same way `./router.ts` re-exports `CopilotEndpoint`\n * from `./catalog.ts`. DD-2 also puts ownership here —\n * \"the composite owns the two pure projection functions\".\n *\n * Every field is a primitive or a string array. See rule 2 in the module note for\n * why nesting the two sub-dialects instead would be a silent-data-loss bug.\n */\nexport interface CopilotDialect {\n /** Send temperature/top_p. Both endpoints accept them; some models refuse. */\n readonly sampling: boolean\n /** Send the output-token limit. */\n readonly maxOutputTokens: boolean\n /** Send JSON-schema structured output. */\n readonly structuredOutputs: boolean\n /** Declare tools in the request. */\n readonly tools: boolean\n /** Responses only: `store`. */\n readonly store: boolean\n /** Responses only: `include`. */\n readonly include: readonly string[]\n /** Responses only: `reasoning.summary`. `'none'` asks for no summary at all. */\n readonly reasoningSummary: 'auto' | 'concise' | 'detailed' | 'none'\n /** Chat Completions only: `stream_options.include_usage`. */\n readonly streamUsage: boolean\n /** Chat Completions only: the role the system prompt travels under. */\n readonly systemRole: 'system' | 'developer'\n /** Chat Completions only: `parallel_tool_calls`. */\n readonly parallelToolCalls: boolean\n /** Prompt/session cache key, used by BOTH branches. */\n readonly promptCacheKey?: string\n}\n\n/**\n * Conservative defaults, matching each sub-protocol's own defaults where the two\n * agree.\n *\n * `store: false` because retaining prompts on someone else's server is an explicit\n * decision, `include` carries `reasoning.encrypted_content` because without it a\n * reasoning model loses its chain of thought across a tool call, and\n * `parallelToolCalls: false` because older gateways reject the field outright.\n */\nexport const COPILOT_DEFAULT_DIALECT: CopilotDialect = Object.freeze({\n sampling: true,\n maxOutputTokens: true,\n structuredOutputs: true,\n tools: true,\n store: false,\n include: Object.freeze(['reasoning.encrypted_content']),\n reasoningSummary: 'auto',\n streamUsage: true,\n systemRole: 'system',\n parallelToolCalls: false,\n} as const satisfies CopilotDialect)\n\n/**\n * Project the Copilot dialect onto the Responses dialect.\n *\n * PURE and TOTAL: every {@link CopilotDialect} flag has exactly one destination\n * here or none at all. `tools`, `streamUsage`, `systemRole` and\n * `parallelToolCalls` have no Responses destination and are DROPPED rather than\n * bent into a nearby flag — Responses declares tools from the request itself and\n * has no `reasoning_effort`-style neighbour worth guessing at.\n *\n * `reasoningSummary: 'none'` is expressed by ABSENCE, because that is how the\n * Responses serializer spells \"ask for no summary\" (`summary` is only sent when\n * the knob is defined). {@link resolvedResponsesDialect} therefore drops the\n * sub-protocol's own `reasoningSummary` default before merging, so this branch of\n * the projection is not overwritten by it.\n * @param dialect - the resolved Copilot dialect for this request.\n * @returns the Responses knobs this dialect determines, and only those.\n */\nexport function toResponsesDialect(dialect: CopilotDialect): Partial<ResponsesDialect> {\n return {\n sampling: dialect.sampling,\n maxOutputTokens: dialect.maxOutputTokens,\n structuredOutputs: dialect.structuredOutputs,\n store: dialect.store,\n include: [...dialect.include],\n ...(dialect.reasoningSummary === 'none' ? {} : { reasoningSummary: dialect.reasoningSummary }),\n ...(dialect.promptCacheKey === undefined ? {} : { promptCacheKey: dialect.promptCacheKey }),\n }\n}\n\n/**\n * Project the Copilot dialect onto the Chat Completions dialect.\n *\n * PURE and TOTAL, same rule as {@link toResponsesDialect}: `store`, `include` and\n * `reasoningSummary` have no Chat Completions destination and are dropped —\n * `reasoningEffort` is a different knob (how hard to think, not whether to report\n * a summary), so mapping onto it would be a guess dressed as a translation.\n *\n * Two flags change type on the way across, and each mapping is total:\n *\n * | Copilot | Chat Completions |\n * | --- | --- |\n * | `maxOutputTokens: true` | `maxTokensField: 'max_tokens'` |\n * | `maxOutputTokens: false` | `maxTokensField: false` |\n * | `structuredOutputs: true` | `structuredOutputs: 'json-schema'` |\n * | `structuredOutputs: false` | `structuredOutputs: false` |\n *\n * The accepted cost of the first row: a caller cannot reach\n * `'max_completion_tokens'` through {@link CopilotDialect}. Copilot's\n * `/chat/completions` takes `max_tokens`, and the models that demand the newer\n * spelling are the ones the router sends to `/responses` anyway, so the boolean\n * buys a flag a caller can reason about and costs a spelling no Copilot model has\n * been observed to need.\n * @param dialect - the resolved Copilot dialect for this request.\n * @returns the Chat Completions knobs this dialect determines, and only those.\n */\nexport function toChatCompletionsDialect(\n dialect: CopilotDialect,\n): Partial<ChatCompletionsDialect> {\n return {\n sampling: dialect.sampling,\n maxTokensField: dialect.maxOutputTokens ? 'max_tokens' : false,\n structuredOutputs: dialect.structuredOutputs ? 'json-schema' : false,\n tools: dialect.tools,\n streamUsage: dialect.streamUsage,\n systemRole: dialect.systemRole,\n parallelToolCalls: dialect.parallelToolCalls,\n ...(dialect.promptCacheKey === undefined ? {} : { promptCacheKey: dialect.promptCacheKey }),\n }\n}\n\n/**\n * The parts of a sub-protocol the composite uses.\n *\n * Structural rather than an import of either package's own definition type, so\n * that a test can hand in a stub and so that neither sub-protocol's marker fields\n * become part of this contract.\n */\nexport interface CopilotSubProtocol<Dialect extends object> {\n /** Reported on the decision as `protocolId`. */\n readonly id: string\n /** Merged UNDER the projection by the composite; never read by the runtime. */\n readonly defaultDialect: Dialect\n readonly endpointPath: (request: ProtocolRequest, dialect: Dialect) => string\n readonly protocolHeaders?: (dialect: Dialect) => Readonly<Record<string, string>>\n readonly serialize: (\n request: ProtocolRequest,\n dialect: Dialect,\n ) => Readonly<Record<string, unknown>>\n readonly translate: (\n events: AsyncIterable<ProtocolSseEvent>,\n request: ProtocolRequest,\n displayName: string,\n ) => AsyncGenerator<ProtocolStreamChunk>\n}\n\n/** The `/responses` half. `openAiResponsesProtocol` satisfies this. */\nexport type ResponsesProtocolLike = CopilotSubProtocol<ResponsesDialect>\n\n/** The `/chat/completions` half. `openAiChatCompletionsProtocol` satisfies this. */\nexport type ChatCompletionsProtocolLike = CopilotSubProtocol<ChatCompletionsDialect>\n\n/** Construction options for {@link copilotDualProtocol}. */\nexport interface CopilotDualProtocolOptions {\n /** Decides, once per model id, which branch a request takes. */\n readonly router: CopilotEndpointRouter\n /** The protocol serving `/responses`. */\n readonly responses: ResponsesProtocolLike\n /** The protocol serving `/chat/completions`. */\n readonly chat: ChatCompletionsProtocolLike\n /**\n * Synchronous, best-effort observer of every endpoint decision (Requirement\n * 9.8).\n *\n * Carries `{ model, endpoint, protocolId, source }` — no prompt and no\n * credential, because an observer is a diagnostic channel and neither of those\n * is diagnostic. Throwing in here does NOT affect the request: the error is\n * trapped, since a broken log sink must not decide whether a generation runs.\n */\n readonly onDecision?: (decision: CopilotEndpointDecision) => void\n}\n\n/**\n * Build the composite protocol.\n *\n * @param options - the router, the two sub-protocols, and the optional observer.\n * @returns a `RuntimeWireProtocol<CopilotDialect>` with id `'copilot-dual'`.\n */\nexport function copilotDualProtocol(\n options: CopilotDualProtocolOptions,\n): RuntimeWireProtocol<CopilotDialect> {\n const { router, responses, chat } = options\n const onDecision = options.onDecision\n\n /**\n * The Responses dialect for one request.\n *\n * Merged with the SUB-PROTOCOL's defaults, not the composite's: the composite's\n * defaults are already inside `dialect` by the time the runtime calls us, and\n * what is missing is everything the Responses dialect knows about but Copilot\n * does not expose (`messagePhase`). `reasoningSummary` is dropped from the base\n * because the projection owns that key outright — see {@link toResponsesDialect}.\n */\n const resolvedResponsesDialect = (dialect: CopilotDialect): ResponsesDialect => {\n const { reasoningSummary: _ownedByProjection, ...base } = responses.defaultDialect\n return Object.freeze({ ...base, ...toResponsesDialect(dialect) })\n }\n\n /**\n * The Chat Completions dialect for one request.\n *\n * Same rule; the sub-protocol supplies `path`, `stop`, `seed` and\n * `reasoningEffort`, which {@link CopilotDialect} deliberately does not expose.\n */\n const resolvedChatDialect = (dialect: CopilotDialect): ChatCompletionsDialect =>\n Object.freeze({ ...chat.defaultDialect, ...toChatCompletionsDialect(dialect) })\n\n /** Route one request, reporting the decision on the way through. */\n const decide = (request: ProtocolRequest): CopilotEndpointDecision => {\n const decision = router.decide(request.model.id)\n report(decision)\n return decision\n }\n\n /** Hand the decision to the observer, swallowing whatever it does with it. */\n const report = (decision: CopilotEndpointDecision): void => {\n if (onDecision === undefined) return\n try {\n onDecision(decision)\n } catch { /* an observer must not decide whether a request runs */ }\n }\n\n return defineWireProtocol<CopilotDialect>({\n id: COPILOT_DUAL_PROTOCOL_ID,\n defaultDialect: COPILOT_DEFAULT_DIALECT,\n // `endpointPath` is where the decision is REPORTED, because it is the first of\n // the three methods the pipeline calls for a request, and because the router\n // is memoized and append-only: the branch reported here is the branch\n // `serialize` and `translate` will take, retries included (Requirement 9.7).\n endpointPath: (request: ProtocolRequest, dialect: CopilotDialect): string =>\n decide(request).endpoint === 'responses'\n ? responses.endpointPath(request, resolvedResponsesDialect(dialect))\n : chat.endpointPath(request, resolvedChatDialect(dialect)),\n ...protocolHeadersOf(responses, chat),\n serialize: (\n request: ProtocolRequest,\n dialect: CopilotDialect,\n ): Readonly<Record<string, unknown>> =>\n // No report here: one logical call would otherwise emit the same decision\n // two or three times, and the router's memo makes the extra reports\n // information-free.\n router.decide(request.model.id).endpoint === 'responses'\n ? responses.serialize(request, resolvedResponsesDialect(dialect))\n : chat.serialize(request, resolvedChatDialect(dialect)),\n translate: (\n events: AsyncIterable<ProtocolSseEvent>,\n request: ProtocolRequest,\n displayName: string,\n ): AsyncGenerator<ProtocolStreamChunk> =>\n router.decide(request.model.id).endpoint === 'responses'\n ? responses.translate(events, request, displayName)\n : chat.translate(events, request, displayName),\n })\n}\n\n/**\n * The composite's `protocolHeaders`, or nothing.\n *\n * This is the one method whose signature carries NO `ProtocolRequest`, so the\n * routing key that makes the other three work is absent here. Two things follow.\n * The union of both branches' headers is wrong — it would put a header belonging\n * to the branch NOT taken on the wire. And a guess is wrong for the same reason.\n * So the composite exposes a header set only when both branches produce the SAME\n * one, in which case that set is the selected branch's set whichever branch is\n * selected; when they diverge, it exposes none, and a protocol header that only\n * one branch needs has to travel through the adapter's request-scoped header path\n * where the model id is in hand.\n *\n * Today neither sub-protocol declares `protocolHeaders` — Copilot's mandatory\n * headers are endpoint identity (`editor-version`, `editor-plugin-version`), not\n * protocol facts — so this returns nothing and the branch above is the\n * forward-looking half of the rule.\n * @param responses - the `/responses` sub-protocol.\n * @param chat - the `/chat/completions` sub-protocol.\n * @returns a one-key spread carrying `protocolHeaders`, or an empty one.\n */\nfunction protocolHeadersOf(\n responses: ResponsesProtocolLike,\n chat: ChatCompletionsProtocolLike,\n): { protocolHeaders?: (dialect: CopilotDialect) => Readonly<Record<string, string>> } {\n const fromResponses = responses.protocolHeaders\n const fromChat = chat.protocolHeaders\n if (fromResponses === undefined && fromChat === undefined) return {}\n return {\n protocolHeaders: (dialect: CopilotDialect): Readonly<Record<string, string>> => {\n const left = fromResponses?.(Object.freeze({\n ...responses.defaultDialect,\n ...toResponsesDialect(dialect),\n })) ?? {}\n const right = fromChat?.(Object.freeze({\n ...chat.defaultDialect,\n ...toChatCompletionsDialect(dialect),\n })) ?? {}\n return sameHeaders(left, right) ? Object.freeze({ ...left }) : Object.freeze({})\n },\n }\n}\n\n/** Whether two header maps are equal name-for-name and value-for-value. */\nfunction sameHeaders(\n left: Readonly<Record<string, string>>,\n right: Readonly<Record<string, string>>,\n): boolean {\n const names = Object.keys(left)\n if (names.length !== Object.keys(right).length) return false\n return names.every((name) => left[name] === right[name])\n}\n","/**\n * `Copilot_Token_Exchange`: turn the long-lived `GitHub_User_Token` into the\n * short-lived `Copilot_Api_Token` the Copilot surface accepts.\n *\n * ```text\n * GET https://api.github.com/copilot_internal/v2/token\n * Authorization: Bearer ghu_…\n * Accept: application/json\n * Editor-Version / Editor-Plugin-Version\n * → 200 { token, expires_at, refresh_in?, endpoints?: { api?: string }, … }\n * ```\n *\n * ## The classification order is the contract\n *\n * Nine rows, in this order, each for a concrete reason:\n *\n * ```text\n * 1. host is 'ghe.com' or ends with a '.ghe.com' label ⇒ TENANT_UNSUPPORTED (before any I/O)\n * 2. origin is not the pinned githubApiBaseUrl origin ⇒ ENDPOINT_ORIGIN_INVALID (before any I/O)\n * 3. the response is a redirect, in any of its shapes ⇒ REDIRECT_REJECTED\n * 4. HTTP 404 ⇒ TENANT_UNSUPPORTED\n * 5. HTTP 401 ⇒ CREDENTIAL_REJECTED (permanent)\n * 6. HTTP 403 ⇒ CREDENTIAL_REJECTED (permanent)\n * 7. HTTP 429, HTTP 5xx, a network error, or a timeout ⇒ TOKEN_EXCHANGE_FAILED (transient)\n * 8. any remaining 4xx ⇒ TOKEN_EXCHANGE_FAILED (permanent)\n * 9. body is not JSON, or expires_at is unreadable ⇒ TOKEN_MALFORMED\n * ```\n *\n * Rows 1 and 2 run BEFORE a request is dispatched. A data-residency tenant has no\n * token-exchange surface at all, so asking it is pointless; and an origin check\n * performed after the fact would already have handed the bearer token to whatever\n * origin the URL named (Requirements 3.6, 3.7).\n *\n * Row 1 detects the tenant by DOMAIN LABEL SUFFIX, never by substring: with\n * `includes('ghe.com')`, `ghe.com.evil.tld` and `notghe.com` would both be\n * misread as data-residency tenants, one of which is an attacker-chosen host.\n *\n * Row 6 does the most work of the nine. The endpoint answers 403 both for a\n * personal access token and for a token minted by an OAuth App that is not on\n * GitHub's allowlist, and the response does not distinguish the two — so the\n * message names BOTH possibilities alongside the single instruction that helps in\n * either case (Requirements 3.5, 13.2).\n *\n * ## What is read from the body, and what is refused\n *\n * `expires_at` is MANDATORY and has to be a positive finite number: without it\n * there is no second source for the lifetime, and an invented TTL is exactly the\n * inference this SDK does not make. `refresh_in` is advisory and a bad value is\n * dropped rather than fatal — it can only shorten the refresh moment, so losing\n * it costs nothing. `endpoints.api` is read and exposed for diagnostics but is\n * NEVER used as the base URL: a server-designated base URL is a redirect under\n * another name, and this SDK does not follow provider-controlled redirects\n * (DD-6, Requirements 3.8, 7.8).\n *\n * @module ai-agent-sdk/providers/copilot/exchange\n */\n\nimport { AgentSdkError, type ModelInvocationContext } from '@alvin0/ai-agent-sdk-core'\nimport type { CredentialOperationOptions } from '@alvin0/ai-agent-sdk-core/provider'\nimport { observeCredentialOperation } from '@alvin0/ai-agent-sdk-provider-http'\nimport {\n COPILOT_EDITOR_PLUGIN_VERSION,\n COPILOT_EDITOR_VERSION,\n type CopilotEditorHeaders,\n} from './common/identity.ts'\nimport {\n COPILOT_LOGIN_COMMAND,\n COPILOT_TOKEN_EXCHANGE_MARGIN_MS,\n requireGitHubToken,\n shouldExchange,\n type CopilotCredentialSnapshot,\n} from './auth.ts'\nimport { COPILOT_ERROR_CODES } from './common/error-codes.ts'\nimport {\n COPILOT_DEFAULT_REQUEST_TIMEOUT_MS,\n copilotFetch,\n copilotUrl,\n issuerOf,\n positiveSafeInteger,\n raceAbort,\n readCopilotResponseText,\n type CopilotHttpOptions,\n} from './common/http.ts'\nimport type { CopilotGitHubToken } from './common/store-types.ts'\nimport { CopilotTokenExchangeError, credentialFailure } from './errors.ts'\n\n/** GitHub's API base, where the token-exchange surface lives. */\nexport const DEFAULT_GITHUB_API_BASE_URL = 'https://api.github.com'\n\n/** Path of the token-exchange surface. */\nexport const COPILOT_TOKEN_EXCHANGE_PATH = '/copilot_internal/v2/token'\n\n/** Marker used in place of a credential value that appeared in a response body. */\nconst REDACTED = '[REDACTED]'\n\n/** Settings for one token exchange. Every field is optional; every default is a bound. */\nexport interface CopilotExchangeOptions extends CopilotHttpOptions {\n /** Overrides {@link DEFAULT_GITHUB_API_BASE_URL}; pinned as its own origin. */\n readonly githubApiBaseUrl?: string\n /** Overrides for the two mandatory editor headers. */\n readonly editorHeaders?: CopilotEditorHeaders\n /**\n * Further secret values to strike out of any body this exchange retains,\n * beyond the credential it sends itself.\n *\n * Requirement 13.7 is stated over BOTH tokens, not just the one a given request\n * carries, and an exchange knows only its own. The remaining value — the\n * `Copilot_Api_Token` currently held — reaches this path from\n * {@link createCopilotTokenCache}, which is the one component holding both at\n * once. Without it, a body echoing the live API token back would travel into\n * `cause` intact, because the redaction here would be looking for the wrong\n * string.\n */\n readonly additionalSecrets?: readonly string[]\n}\n\n/**\n * The result of one `Copilot_Token_Exchange`, held in process memory only.\n *\n * Structurally satisfies `CopilotTokenExpiry` from `./auth.ts`, so `shouldExchange`\n * accepts one of these with no conversion.\n */\nexport interface CopilotApiToken {\n /** Bearer token for the Copilot API base. Short-lived, ~25 minutes. */\n readonly token: string\n /** Expiry instant in epoch MILLISECONDS, derived from `expires_at` (seconds). */\n readonly expiresAtMs: number\n /** The endpoint's `refresh_in` hint in seconds, when it sent a usable one. ADVISORY. */\n readonly refreshInSeconds?: number\n /**\n * The endpoint's declared `endpoints.api`, when present.\n *\n * MUST NOT be used as a base URL. A server-designated base URL is a redirect\n * under another name, and Requirements 3.8/7.8 settled that this SDK does not\n * follow provider-controlled redirection. This field exists so `--status` can\n * print it and so a configuration drift is visible. See DD-6.\n */\n readonly declaredApiEndpoint?: string\n}\n\n/**\n * Exchange a `GitHub_User_Token` for a `Copilot_Api_Token`.\n *\n * The long-lived credential is NOT consumed: nothing here writes to a store, and\n * the persisted value is left exactly as it was (Requirement 3.4).\n * @param github - the long-lived GitHub user token. Its value never reaches an\n * error message, and any occurrence of it in a response body is redacted before\n * the body is retained as a cause (Requirement 13.7).\n * @param options - base URL override, injected fetch, signal, and the read bounds.\n * @returns the short-lived token plus its expiry and the advisory fields.\n * @throws AgentSdkError with `COPILOT_ENDPOINT_ORIGIN_INVALID` or\n * `COPILOT_REDIRECT_REJECTED`, or {@link CopilotTokenExchangeError} with\n * `COPILOT_TENANT_UNSUPPORTED`, `COPILOT_CREDENTIAL_REJECTED`,\n * `COPILOT_TOKEN_EXCHANGE_FAILED` or `COPILOT_TOKEN_MALFORMED`, per the\n * classification order in the module note.\n */\nexport async function exchangeCopilotToken(\n github: CopilotGitHubToken,\n options: CopilotExchangeOptions = {},\n): Promise<CopilotApiToken> {\n // Row 1, before any I/O: a data-residency tenant has no surface to ask.\n rejectDataResidencyTenant(options.githubApiBaseUrl)\n // Row 2, before any I/O: pin the origin, then build the URL on that pin.\n const pinned = issuerOf(\n 'githubApiBaseUrl',\n options.githubApiBaseUrl,\n DEFAULT_GITHUB_API_BASE_URL,\n options,\n )\n const url = copilotUrl(pinned, COPILOT_TOKEN_EXCHANGE_PATH)\n const host = new URL(pinned.origin).hostname\n let response: Response\n try {\n // Rows 3: `copilotFetch` re-checks the pin and refuses every redirect shape.\n response = await copilotFetch({\n pinned,\n url,\n operation: 'token exchange',\n init: { method: 'GET', headers: exchangeHeaders(github, options.editorHeaders) },\n }, options)\n } catch (error: unknown) {\n throw transportFailure(error, host, options)\n }\n // Every value that must not survive into a retained body: the credential this\n // request carries, plus whatever else the caller knows is live.\n const secrets = [github.token, ...options.additionalSecrets ?? []]\n // Rows 4 through 8.\n if (!response.ok) throw await statusFailure(response, host, secrets, options)\n // Row 9.\n return readApiToken(await readCopilotResponseText(response, options), secrets)\n}\n\n/**\n * Row 1: refuse a `*.ghe.com` tenant by domain label, before anything is sent.\n *\n * An unparsable value is left alone rather than reported here — {@link issuerOf}\n * owns that message, and reporting it as a tenant problem would name the wrong\n * cause.\n * @param configured - the caller's `githubApiBaseUrl`, when they set one.\n * @throws CopilotTokenExchangeError with `COPILOT_TENANT_UNSUPPORTED`, naming the\n * detected domain (Requirement 13.3).\n */\nfunction rejectDataResidencyTenant(configured: string | undefined): void {\n if (configured === undefined) return\n let host: string\n try {\n host = new URL(configured).hostname\n } catch {\n return\n }\n if (!isDataResidencyHost(host)) return\n throw new CopilotTokenExchangeError(\n credentialFailure(tenantMessage(host)),\n COPILOT_ERROR_CODES.TENANT_UNSUPPORTED,\n 'permanent',\n )\n}\n\n/**\n * Whether a hostname belongs to the `ghe.com` data-residency namespace.\n *\n * Matched on DOMAIN LABELS, which is the whole point: `ghe.com.evil.tld` and\n * `notghe.com` are not data-residency hosts, and a substring test would call both\n * of them one.\n * @param host - a hostname, without a port.\n * @returns true for `ghe.com` itself and for any host under it.\n */\nfunction isDataResidencyHost(host: string): boolean {\n const normalized = host.toLowerCase().replace(/\\.$/, '')\n return normalized === 'ghe.com' || normalized.endsWith('.ghe.com')\n}\n\n/**\n * Headers for the exchange request.\n *\n * Both editor headers are mandatory: with either one missing the endpoint answers\n * HTTP 400 and the request never runs. An override of one leaves the other at its\n * exported default rather than dropping it.\n * @param github - the credential whose value goes in `Authorization`.\n * @param headers - per-call overrides for the editor identity.\n * @returns the header map for the request init.\n */\nfunction exchangeHeaders(\n github: CopilotGitHubToken,\n headers: CopilotEditorHeaders | undefined,\n): Record<string, string> {\n return {\n authorization: `Bearer ${github.token}`,\n accept: 'application/json',\n 'editor-version': headers?.editorVersion ?? COPILOT_EDITOR_VERSION,\n 'editor-plugin-version': headers?.editorPluginVersion ?? COPILOT_EDITOR_PLUGIN_VERSION,\n }\n}\n\n/**\n * Rows 3 and 7 on the dispatch path: keep the structural refusals, classify the\n * rest as transient.\n *\n * Four kinds of failure pass through UNCHANGED, because wrapping each one would\n * replace a precise diagnosis with a vaguer one:\n *\n * - the origin refusal and the redirect refusal, which are rows 2 and 3 and\n * already carry their own codes;\n * - a caller abort, which keeps the SDK's abort code rather than becoming a\n * Copilot failure the caller did not ask about (Requirement 4.6);\n * - a `RangeError` from a bound, which names the limit that was exceeded and is\n * neither a network fault nor a server fault (Requirement 13.6).\n *\n * Everything else — DNS, connection reset, TLS, and the per-request deadline —\n * is transient: it is exactly the class of failure that a later attempt can win.\n * @param error - the caught value.\n * @param host - the host that was contacted, for the message.\n * @param options - read for the caller's signal only.\n * @returns the value to throw.\n */\nfunction transportFailure(\n error: unknown,\n host: string,\n options: CopilotExchangeOptions,\n): unknown {\n if (options.signal?.aborted === true) return error\n if (error instanceof RangeError) return error\n if (error instanceof AgentSdkError\n && (error.code === COPILOT_ERROR_CODES.ENDPOINT_ORIGIN_INVALID\n || error.code === COPILOT_ERROR_CODES.REDIRECT_REJECTED)) {\n return error\n }\n return new CopilotTokenExchangeError(\n credentialFailure(\n `Copilot token exchange could not reach ${host}; the request failed before a response`,\n error,\n ),\n COPILOT_ERROR_CODES.TOKEN_EXCHANGE_FAILED,\n 'transient',\n )\n}\n\n/**\n * Rows 4 through 8: classify a response that arrived but was not a success.\n *\n * The body is read through the bounded reader first, so the cause carries the\n * endpoint's own words — with every occurrence of the credential replaced —\n * rather than nothing at all (Requirements 13.6, 13.7). A read that fails is not\n * allowed to hide the status: the classification stands either way.\n * @param response - a non-ok response, already cleared by the redirect guard.\n * @param host - the host that answered, for the tenant message.\n * @param secrets - every live credential value to redact out of the body.\n * @param options - the read bounds and the signal.\n * @returns the classified error to throw.\n */\nasync function statusFailure(\n response: Response,\n host: string,\n secrets: readonly string[],\n options: CopilotExchangeOptions,\n): Promise<CopilotTokenExchangeError> {\n const body = await readFailureBody(response, secrets, options)\n const cause = body === undefined ? undefined : new Error(body)\n if (response.status === 404) {\n return new CopilotTokenExchangeError(\n credentialFailure(tenantMessage(host), cause),\n COPILOT_ERROR_CODES.TENANT_UNSUPPORTED,\n 'permanent',\n )\n }\n if (response.status === 401) {\n return new CopilotTokenExchangeError(\n credentialFailure(\n 'the Copilot token-exchange surface rejected the stored GitHub credential '\n + `(HTTP 401); run \\`${COPILOT_LOGIN_COMMAND}\\` to sign in again`,\n cause,\n ),\n COPILOT_ERROR_CODES.CREDENTIAL_REJECTED,\n 'permanent',\n )\n }\n if (response.status === 403) {\n // The endpoint answers 403 for BOTH a personal access token and a token from\n // a non-allowlisted OAuth App, and the response does not say which — so both\n // are named, with the one instruction that resolves either.\n return new CopilotTokenExchangeError(\n credentialFailure(\n 'the Copilot token-exchange surface refused this credential type (HTTP 403). '\n + 'It accepts only a token minted by an OAuth App on GitHub\\'s allowlist: a '\n + 'personal access token cannot be used here, and neither can a token from an '\n + `OAuth App that is not allowlisted. Run \\`${COPILOT_LOGIN_COMMAND}\\` to sign `\n + 'in with the supported client.',\n cause,\n ),\n COPILOT_ERROR_CODES.CREDENTIAL_REJECTED,\n 'permanent',\n )\n }\n // 429 sits with the 5xx row rather than with the remaining 4xx: the design's\n // classification table gives it `transient`, and it is the one 4xx whose cause\n // a later attempt can actually clear. Every other 4xx fails identically forever.\n const transient = response.status >= 500 || response.status === 429\n return new CopilotTokenExchangeError(\n credentialFailure(\n `Copilot token exchange failed (HTTP ${response.status})`,\n cause,\n ),\n COPILOT_ERROR_CODES.TOKEN_EXCHANGE_FAILED,\n transient ? 'transient' : 'permanent',\n )\n}\n\n/**\n * Read an error body within the configured bounds, redacted, or give up quietly.\n *\n * Giving up quietly is deliberate: the status has already decided the\n * classification, and a body that could not be read must not turn a precise 403\n * into a read error.\n * @param response - the non-ok response.\n * @param secrets - every live credential value to redact.\n * @param options - the read bounds and the signal.\n * @returns the redacted text, or `undefined` when it could not be read.\n */\nasync function readFailureBody(\n response: Response,\n secrets: readonly string[],\n options: CopilotExchangeOptions,\n): Promise<string | undefined> {\n try {\n const text = await readCopilotResponseText(response, options)\n return text.length === 0 ? undefined : redact(text, secrets)\n } catch {\n return undefined\n }\n}\n\n/**\n * Row 9: read the success body, requiring exactly what cannot be inferred.\n *\n * `token` and `expires_at` are mandatory; everything else is advisory and a\n * useless value is dropped rather than raised, because a dropped hint changes\n * nothing about correctness while a raised one would fail an exchange that\n * actually produced a usable token.\n * @param raw - the bounded response text.\n * @param secrets - every live credential value to redact out of the cause.\n * @returns the token this exchange produced.\n * @throws CopilotTokenExchangeError with `COPILOT_TOKEN_MALFORMED` when the body\n * is not a JSON object, `token` is not a non-empty string, or `expires_at` is\n * not a positive finite number.\n */\nfunction readApiToken(raw: string, secrets: readonly string[]): CopilotApiToken {\n let parsed: unknown\n try {\n parsed = JSON.parse(raw) as unknown\n } catch (error: unknown) {\n throw malformed('the Copilot token-exchange response was not JSON', error)\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw malformed('the Copilot token-exchange response was not a JSON object')\n }\n const body = parsed as Record<string, unknown>\n const token = body['token']\n if (typeof token !== 'string' || token.length === 0) {\n throw malformed('the Copilot token-exchange response carried no token')\n }\n const expiresAt = body['expires_at']\n if (typeof expiresAt !== 'number' || !Number.isFinite(expiresAt) || expiresAt <= 0) {\n throw malformed(\n 'the Copilot token-exchange response carried no readable expires_at; '\n + 'this SDK does not invent a token lifetime',\n new Error(redact(raw, secrets)),\n )\n }\n const refreshIn = body['refresh_in']\n const declared = declaredEndpointOf(body['endpoints'])\n return Object.freeze({\n token,\n expiresAtMs: expiresAt * 1_000,\n ...typeof refreshIn === 'number' && Number.isFinite(refreshIn) && refreshIn > 0\n ? { refreshInSeconds: refreshIn }\n : {},\n ...declared === undefined ? {} : { declaredApiEndpoint: declared },\n })\n}\n\n/**\n * Read `endpoints.api` for diagnostics only.\n *\n * Never returned as something to request against — see {@link\n * CopilotApiToken.declaredApiEndpoint} and DD-6.\n * @param endpoints - the `endpoints` member of the response, unvalidated.\n * @returns the declared API endpoint, when it is a non-empty string.\n */\nfunction declaredEndpointOf(endpoints: unknown): string | undefined {\n if (typeof endpoints !== 'object' || endpoints === null) return undefined\n const api = (endpoints as Record<string, unknown>)['api']\n return typeof api === 'string' && api.length > 0 ? api : undefined\n}\n\n/**\n * The one tenant message, so the two paths that reach row 1 and row 4 say the\n * same thing.\n * @param host - the detected host, named in the message (Requirement 13.3).\n * @returns the message text.\n */\nfunction tenantMessage(host: string): string {\n return `'${host}' is a GitHub data-residency tenant, which does not provide the Copilot `\n + 'token-exchange surface; use a github.com account for this provider'\n}\n\n/**\n * Replace every occurrence of every live credential value in text with\n * {@link REDACTED}.\n *\n * A response body is endpoint-authored text, and an endpoint that echoes the\n * `Authorization` header back is exactly how a token ends up in a log. The list\n * is plural because Requirement 13.7 covers BOTH tokens: this request carries one\n * of them, and the other arrives through\n * {@link CopilotExchangeOptions.additionalSecrets}.\n * @param text - the body text.\n * @param secrets - the credential values; empty entries are ignored.\n * @returns the text with every value removed.\n */\nfunction redact(text: string, secrets: readonly string[]): string {\n let result = text\n for (const secret of secrets) {\n if (secret.length === 0) continue\n result = result.split(secret).join(REDACTED)\n }\n return result\n}\n\n/**\n * Build the row-9 error.\n * @param message - SDK-authored text.\n * @param cause - the caught value or the redacted body, when there is one.\n * @returns the malformed-token error.\n */\nfunction malformed(message: string, cause?: unknown): CopilotTokenExchangeError {\n return new CopilotTokenExchangeError(\n credentialFailure(message, cause),\n COPILOT_ERROR_CODES.TOKEN_MALFORMED,\n 'permanent',\n )\n}\n/**\n * A cache entry, bound to exactly the credential that produced it.\n *\n * The pair `(sourceToken, sourceRevision)` is the whole key: signing in as a\n * different account invalidates the entry the moment the store returns a\n * different token value, with no TTL involved and no clock consulted.\n */\nexport interface CopilotTokenCacheEntry {\n /** The token this credential produced. */\n readonly api: CopilotApiToken\n /** The `GitHub_User_Token` value used. Compared with `===`. */\n readonly sourceToken: string\n /** The store revision at read time, or `null` for a store without revisions. */\n readonly sourceRevision: string | null\n}\n\n/**\n * The process-memory cache in front of {@link exchangeCopilotToken}.\n *\n * Owned by an adapter instance, and injectable through the adapter's `tokenCache`\n * option so several routes sharing one unchanged credential exchange once rather\n * than once per route.\n */\nexport interface CopilotTokenCache {\n /**\n * Return a live `Copilot_Api_Token` for this credential, exchanging when due.\n *\n * Concurrent calls that all need an exchange are COALESCED into exactly one\n * in-flight exchange (Requirement 5.4).\n * @param source - one read of the credential store, revision included.\n * @param operation - the calling operation; only its signal is read, and it\n * bounds THIS caller's wait, never the shared exchange.\n * @param context - invocation context for the observation record, when there is one.\n * @returns a token that is live as of the decision moment.\n */\n acquire(\n source: CopilotCredentialSnapshot,\n operation: CredentialOperationOptions,\n context?: ModelInvocationContext,\n ): Promise<CopilotApiToken>\n /** Drop the current entry; used when the API surface rejects a token before its expiry. */\n invalidate(): void\n}\n\n/** Provider name recorded on the credential-operation observation by default. */\nexport const COPILOT_PROVIDER_ID = 'copilot'\n\n/** Settings for {@link createCopilotTokenCache}: the exchange settings, plus a clock. */\nexport interface CopilotTokenCacheOptions extends CopilotExchangeOptions {\n /**\n * Provider name on the observation record. Defaults to {@link COPILOT_PROVIDER_ID}.\n *\n * An adapter with a custom `id` passes it here so the record names the provider\n * the caller configured rather than the family.\n */\n readonly providerId?: string\n /** Exchange this long before expiry. Defaults to `COPILOT_TOKEN_EXCHANGE_MARGIN_MS`. */\n readonly marginMs?: number\n /**\n * The clock the exchange decision reads, injectable so a test places `now`\n * exactly on a boundary instead of waiting for one.\n */\n readonly now?: () => number\n}\n\n/**\n * Build a token cache over one set of exchange settings.\n *\n * ## The mistake this is written to avoid\n *\n * The shared exchange gets its OWN `AbortController` plus its own deadline, and\n * NEVER any single caller's signal. Were the caller's signal handed to it, the\n * first caller to abort would cancel the exchange every other caller is waiting\n * on, and those callers would fail for a reason that has nothing to do with them.\n * Instead each caller — the one that started the exchange included — races the\n * shared promise against its OWN signal: an aborted caller leaves, and the\n * exchange still completes for everyone else (Property 18).\n *\n * The observation therefore counts exchanges actually DISPATCHED rather than\n * callers served, which is what makes the coalescing observable instead of merely\n * claimed (Property 52). It is recorded with the `'refresh'` operation name: that\n * parameter's union is closed at `'resolve' | 'refresh' | 'login'`, widening it\n * would change a public type of `provider-http`, and Requirement 18.4 forbids\n * that — see DD-7.\n *\n * ## Two paths deliberately absent\n *\n * There is no revision-conflict recovery, unlike `provider-codex`. That path\n * exists there because a Codex refresh token rotates and is single-use, so a lost\n * race destroys a credential. A `GitHub_User_Token` does not rotate and an\n * exchange does not consume it, so two racing processes simply exchange twice —\n * and a branch no situation reaches is a branch nothing verifies (DD-8).\n *\n * Nothing here writes to a store. The `Copilot_Api_Token` is never persisted: it\n * lives ~25 minutes, so persisting it would add a second secret on disk, a second\n * write path, and a new state to reason about, to save one request inside a\n * 25-minute window (DD-9, Requirement 3.3).\n *\n * A failure is returned to every waiting caller as-is and never retried here — an\n * endpoint that rejected the credential will reject it again, and this layer has\n * no way to change that (Requirement 5.8, Property 19).\n * @param options - exchange settings, the observation provider name, the margin\n * and the clock. `options.signal` is deliberately IGNORED for the exchange\n * itself; per-caller cancellation travels through `operation.signal`.\n * @returns a cache over a single credential slot.\n */\nexport function createCopilotTokenCache(\n options: CopilotTokenCacheOptions = {},\n): CopilotTokenCache {\n const provider = options.providerId ?? COPILOT_PROVIDER_ID\n const now = options.now ?? (() => Date.now())\n const marginMs = options.marginMs === undefined\n ? COPILOT_TOKEN_EXCHANGE_MARGIN_MS\n : positiveSafeInteger(options.marginMs, 'marginMs')\n let entry: CopilotTokenCacheEntry | undefined\n let inflight: Promise<CopilotApiToken> | undefined\n let inflightToken: string | undefined\n let ticket = 0\n return {\n async acquire(\n source: CopilotCredentialSnapshot,\n operation: CredentialOperationOptions,\n context?: ModelInvocationContext,\n ): Promise<CopilotApiToken> {\n operation.signal.throwIfAborted()\n const github = requireGitHubToken(source.file, source.label)\n const cached = entry\n if (cached !== undefined\n && cached.sourceToken === github.token\n && cached.sourceRevision === source.revision\n && !shouldExchange(cached.api, now(), marginMs)) {\n return cached.api\n }\n // Coalesce: an exchange already flying for THIS credential value serves\n // this caller too, and the caller still leaves on its own signal.\n if (inflight !== undefined && inflightToken === github.token) {\n return await raceAbort(inflight, operation.signal)\n }\n ticket++\n const id = ticket\n const pending = runExchange(id)\n inflight = pending\n inflightToken = github.token\n return await raceAbort(pending, operation.signal)\n\n /**\n * Dispatch the one shared exchange and record its result.\n * @param slot - this exchange's ticket, so a later exchange's teardown does\n * not clear a newer in-flight one.\n * @returns the exchanged token.\n */\n async function runExchange(slot: number): Promise<CopilotApiToken> {\n // The cache is the only place BOTH tokens are known at once, so it is the\n // only place that can tell the exchange about the second one. Without\n // this, a body echoing the API token currently held would reach `cause`\n // intact: the exchange redacts the credential it sends, and that is a\n // different string (Requirement 13.7).\n const held = entry?.api.token\n try {\n const api = await observeCredentialOperation(\n context,\n provider,\n 'refresh',\n () => exchangeCopilotToken(github, {\n ...options,\n ...held === undefined\n ? {}\n : { additionalSecrets: [...options.additionalSecrets ?? [], held] },\n signal: sharedExchangeSignal(options),\n }),\n )\n entry = Object.freeze({\n api,\n sourceToken: github.token,\n sourceRevision: source.revision,\n })\n return api\n } finally {\n if (ticket === slot) {\n inflight = undefined\n inflightToken = undefined\n }\n }\n }\n },\n invalidate(): void {\n // Only the entry goes. An in-flight exchange is left alone: it was started\n // by callers that are still waiting on it, and the token it produces is\n // newer than the one being rejected here.\n entry = undefined\n },\n }\n}\n\n/**\n * The shared exchange's own cancellation source: one controller, driven by one\n * deadline, and reachable by no caller.\n *\n * The deadline is what makes the controller more than ceremony. `copilotFetch`\n * bounds its own dispatch, but the bounded body read afterwards races only the\n * signal it was given — so without a deadline on this signal a stalled read would\n * hold the in-flight slot open indefinitely and every coalesced caller with it.\n * @param options - read for `requestTimeoutMs`.\n * @returns a signal that aborts on the exchange deadline and on nothing else.\n * @throws RangeError when `requestTimeoutMs` cannot serve as a bound.\n */\nfunction sharedExchangeSignal(options: CopilotTokenCacheOptions): AbortSignal {\n const controller = new AbortController()\n const deadline = AbortSignal.timeout(positiveSafeInteger(\n options.requestTimeoutMs ?? COPILOT_DEFAULT_REQUEST_TIMEOUT_MS,\n 'requestTimeoutMs',\n ))\n deadline.addEventListener('abort', () => { controller.abort(deadline.reason) }, { once: true })\n return controller.signal\n}\n","/**\n * `Copilot_Endpoint_Router`: decide, ONCE per model id, which endpoint a\n * generation request is dispatched to.\n *\n * ## The decision order, and what is deliberately missing from it\n *\n * ```text\n * 1. endpointOverrides[modelId] ⇒ source 'override'\n * 2. catalog disclosure (via learn()) ⇒ source 'catalog'\n * 3. responses-model prefix allowlist ⇒ source 'allowlist'\n * 4. /chat/completions ⇒ source 'default'\n * ```\n *\n * What is missing is a PROBE. Trying `/responses` to find out whether a model\n * accepts it was rejected (DD-4): a probe is a real request that spends real\n * quota and needs a real prompt, so it has an observable side effect on the\n * user's account purely to answer a metadata question. Its result is not safely\n * cacheable across accounts either, since which models an account may call\n * depends on its plan.\n *\n * ## Why the default is `/chat/completions`\n *\n * The two ways of guessing wrong are ASYMMETRIC:\n *\n * | Guessed wrong | Consequence |\n * | --- | --- |\n * | Model supports `/responses`, we sent `/chat/completions` | works, minus some Responses-specific features |\n * | Model does not support `/responses`, we sent `/responses` | HTTP 400, dead request |\n *\n * Losing a feature is recoverable at the next call; losing the call is not. So\n * the fallback leans to the endpoint every Copilot generation model answers.\n *\n * ## Why `decisions` is append-only\n *\n * Requirement 9.7 asks that the endpoint chosen for a `Logical_Call` hold for\n * that whole call, retries included. The catalog has a TTL and may refresh\n * between two retries, so a router that recomputed could answer `/responses` on\n * the first attempt and `/chat/completions` on the second — one logical call\n * split across two wire protocols, with a serialized body that no longer matches\n * the endpoint it is going to.\n *\n * This module makes that STRUCTURALLY impossible rather than conventionally\n * avoided: once a model id has a decision, no code path rewrites it.\n * {@link CopilotEndpointRouter.learn} only adds keys that have no decision yet,\n * so a later catalog refresh returning different metadata changes nothing.\n *\n * The accepted cost (DD-5): a model misclassified on the first call keeps that\n * classification for the lifetime of the adapter instance. `endpointOverrides`\n * is the instant fix, `--models` is the discovery path, and rebuilding the\n * runtime is the reset. The trade is an invariant with no exceptions instead of\n * an invariant that holds \"unless the catalog refreshed\".\n *\n * @module ai-agent-sdk/providers/copilot/router\n */\n\nimport { AgentSdkError } from '@alvin0/ai-agent-sdk-core'\nimport { OPENAI_CHAT_COMPLETIONS_PROTOCOL_ID } from '@alvin0/ai-agent-sdk-protocol-openai-chat-completions'\nimport { OPENAI_RESPONSES_PROTOCOL_ID } from '@alvin0/ai-agent-sdk-protocol-responses'\nimport type { CopilotEndpoint, CopilotGenerationModel } from './catalog.ts'\nimport { COPILOT_ERROR_CODES } from './errors.ts'\n\n/**\n * Re-exported from `./catalog.ts`, where the union is declared.\n *\n * The declaration lives there because this module imports\n * {@link CopilotGenerationModel} from it, so the source edge already runs\n * router → catalog and declaring the union here would make it bidirectional.\n * The re-export keeps this module the one a reader opens for endpoint selection.\n */\nexport type { CopilotEndpoint } from './catalog.ts'\n\n/**\n * Model id prefixes dispatched to `/responses` when the catalog says nothing.\n *\n * Exported and overridable for the same reason `COPILOT_EDITOR_VERSION` is: this\n * is a fact about a remote endpoint that WILL go stale, and a user has to be able\n * to correct it without waiting for a release.\n * `CopilotProviderOptions.responsesModelPrefixes` ADDS to this list rather than\n * replacing it, so an override cannot silently drop the prefixes shipped here.\n *\n * Kept deliberately short. A prefix that matches too much pushes models toward\n * the endpoint where guessing wrong costs the request (see the module note), so\n * an absent prefix is the cheaper error.\n */\nexport const COPILOT_RESPONSES_MODEL_PREFIXES: readonly string[] = Object.freeze([\n 'codex-',\n 'gpt-5',\n])\n\n/** The endpoint chosen for one model id, and who chose it. */\nexport interface CopilotEndpointDecision {\n /** The model id the decision is keyed by, verbatim as the caller spelled it. */\n readonly model: string\n /** The endpoint the request goes to. */\n readonly endpoint: CopilotEndpoint\n /** Wire protocol id of the sub-protocol that serves {@link endpoint}. */\n readonly protocolId: string\n /**\n * Which step of the decision order produced this.\n *\n * Reported to observation (Requirement 9.8) so that when a model runs against\n * the wrong endpoint, the log says who decided rather than leaving an operator\n * to reconstruct it.\n */\n readonly source: 'override' | 'catalog' | 'allowlist' | 'default'\n}\n\n/** Memoized, append-only endpoint selection for one adapter instance. */\nexport interface CopilotEndpointRouter {\n /**\n * Decide the endpoint for a model id.\n *\n * MEMOIZED AND APPEND-ONLY: a key that already has a decision is returned\n * unchanged and never recomputed. This is the mechanism that makes\n * Requirement 9.7 hold — no code path can change its mind between two retries.\n * @param modelId - the wire model id of the request being dispatched.\n * @returns the decision for that model, recording it on first sight.\n */\n decide(modelId: string): CopilotEndpointDecision\n /**\n * Feed in discovered catalog metadata.\n *\n * Adds ONLY keys that have no decision yet; an id already decided is skipped\n * even when the metadata now disagrees with the recorded decision.\n * @param models - the generation half of a {@link CopilotGenerationModel} list.\n */\n learn(models: readonly CopilotGenerationModel[]): void\n /**\n * Every decision recorded so far, in the order it was recorded.\n * @returns a frozen snapshot, for `--models` and for the conformance harness.\n */\n snapshot(): readonly CopilotEndpointDecision[]\n}\n\n/** Construction options for {@link createCopilotEndpointRouter}. */\nexport interface CopilotEndpointRouterOptions {\n /**\n * Endpoints pinned by the application, keyed by model id. Wins over every\n * other source, including a catalog disclosure that contradicts it.\n */\n readonly overrides?: Readonly<Record<string, CopilotEndpoint>>\n /**\n * The full responses-prefix allowlist to use.\n *\n * The caller passes the already-merged list — `copilotAdapter` spreads\n * {@link COPILOT_RESPONSES_MODEL_PREFIXES} first and the user's additions\n * after — so the \"adds, never replaces\" rule is visible at the call site\n * instead of hidden in here. Defaults to the shipped list when omitted.\n */\n readonly prefixes?: readonly string[]\n}\n\n/** The two endpoints, so an override value can be checked against something. */\nconst COPILOT_ENDPOINTS: readonly CopilotEndpoint[] = Object.freeze([\n 'responses',\n 'chat-completions',\n])\n\n/** Protocol id per endpoint, the one place the two are tied together. */\nconst PROTOCOL_IDS: Readonly<Record<CopilotEndpoint, string>> = Object.freeze({\n 'responses': OPENAI_RESPONSES_PROTOCOL_ID,\n 'chat-completions': OPENAI_CHAT_COMPLETIONS_PROTOCOL_ID,\n})\n\n/**\n * Build a router for one adapter instance.\n *\n * Overrides are validated HERE, not at dispatch: a typo in\n * `endpointOverrides` is a configuration mistake, and a configuration mistake\n * that surfaces while building the provider is cheaper than one that surfaces on\n * the first request to one particular model.\n * @param options - overrides and the merged prefix allowlist.\n * @returns a router whose `decisions` map only ever grows.\n * @throws AgentSdkError with `COPILOT_ENDPOINT_OVERRIDE_INVALID` when an\n * override pins an endpoint that does not exist.\n */\nexport function createCopilotEndpointRouter(\n options: CopilotEndpointRouterOptions = {},\n): CopilotEndpointRouter {\n const overrides = validateOverrides(options.overrides ?? {})\n const prefixes = normalizePrefixes(options.prefixes ?? COPILOT_RESPONSES_MODEL_PREFIXES)\n // Insertion-ordered, and only ever written through `record`.\n const decisions = new Map<string, CopilotEndpointDecision>()\n\n /** Write a decision for a key that has none. The single mutation point. */\n const record = (\n modelId: string,\n endpoint: CopilotEndpoint,\n source: CopilotEndpointDecision['source'],\n ): CopilotEndpointDecision => {\n const decision: CopilotEndpointDecision = Object.freeze({\n model: modelId,\n endpoint,\n protocolId: PROTOCOL_IDS[endpoint],\n source,\n })\n decisions.set(modelId, decision)\n return decision\n }\n\n /**\n * The decision order for a key with no recorded decision.\n *\n * `declared` is `undefined` for {@link CopilotEndpointRouter.decide}, because a\n * bare dispatch carries no catalog metadata — a disclosure only arrives through\n * {@link CopilotEndpointRouter.learn}. `undefined` is UNKNOWN, so it falls\n * through to the allowlist; `'chat-completions'` is a stated fact and stops\n * there with `source: 'catalog'`.\n */\n const resolve = (\n modelId: string,\n declared: CopilotEndpoint | undefined,\n ): CopilotEndpointDecision => {\n const override = overrides[modelId]\n if (override !== undefined) return record(modelId, override, 'override')\n if (declared !== undefined) return record(modelId, declared, 'catalog')\n if (matchesPrefix(modelId, prefixes)) return record(modelId, 'responses', 'allowlist')\n return record(modelId, 'chat-completions', 'default')\n }\n\n return Object.freeze({\n decide(modelId: string): CopilotEndpointDecision {\n return decisions.get(modelId) ?? resolve(modelId, undefined)\n },\n learn(models: readonly CopilotGenerationModel[]): void {\n for (const entry of models) {\n const modelId = entry.model.id\n if (decisions.has(modelId)) continue\n resolve(modelId, entry.declaredEndpoint)\n }\n },\n snapshot(): readonly CopilotEndpointDecision[] {\n return Object.freeze([...decisions.values()])\n },\n })\n}\n\n/**\n * Copy the overrides and reject any value that is not an endpoint.\n *\n * A copy rather than the caller's object, so a later mutation of what was passed\n * in cannot introduce an unvalidated endpoint after construction.\n */\nfunction validateOverrides(\n overrides: Readonly<Record<string, CopilotEndpoint>>,\n): Readonly<Record<string, CopilotEndpoint>> {\n const validated: Record<string, CopilotEndpoint> = Object.create(null)\n for (const [modelId, endpoint] of Object.entries(overrides)) {\n if (!COPILOT_ENDPOINTS.includes(endpoint)) {\n throw new AgentSdkError(\n `Copilot endpoint override for model '${modelId}' must be one of ` +\n `${COPILOT_ENDPOINTS.map((value) => `'${value}'`).join(', ')}`,\n COPILOT_ERROR_CODES.ENDPOINT_OVERRIDE_INVALID,\n )\n }\n validated[modelId] = endpoint\n }\n return Object.freeze(validated)\n}\n\n/**\n * Lower-case the prefixes and drop the ones that cannot select anything.\n *\n * An empty string is dropped rather than honoured: as a prefix it matches every\n * model id, which would route the whole catalog to `/responses` — the direction\n * where guessing wrong costs the request. Dropping it leaves the shipped\n * prefixes intact, which is what a caller adding to the list asked for.\n */\nfunction normalizePrefixes(prefixes: readonly string[]): readonly string[] {\n const normalized: string[] = []\n for (const prefix of prefixes) {\n if (typeof prefix !== 'string' || prefix.length === 0) continue\n const lower = prefix.toLowerCase()\n if (!normalized.includes(lower)) normalized.push(lower)\n }\n return Object.freeze(normalized)\n}\n\n/** Case-insensitive prefix match; Copilot model ids are lower-case in practice. */\nfunction matchesPrefix(modelId: string, prefixes: readonly string[]): boolean {\n const lower = modelId.toLowerCase()\n return prefixes.some((prefix) => lower.startsWith(prefix))\n}\n","/**\n * The Copilot provider: the Copilot API surface, authenticated with a GitHub user\n * token this project's own credential store holds.\n *\n * ## Configured, not subclassed\n *\n * `copilotAdapter` is built with `createRuntimeHttpProvider` and extends nothing\n * (Requirement 7.1). Everything Copilot needs beyond a plain API-key provider —\n * a two-tier credential, a token exchange with its own cache, two wire protocols\n * on one route, endpoint-driven discovery — is expressed as DATA:\n * `auth: { kind: 'dynamic' }` for the credential path, a composite protocol for\n * the two endpoints, `discoverModels` for the catalog. That is the point of the\n * exercise: the configuration path is proven by the provider with the most\n * demanding requirements in this repository, not by the simplest one.\n *\n * ## Client identity\n *\n * `COPILOT_EDITOR_VERSION` and `COPILOT_EDITOR_PLUGIN_VERSION` are two of the\n * three `Client_Identity_Constants` in this package; the third is\n * `COPILOT_OAUTH_CLIENT_ID` in `./oauth.ts`. Their defaults make this SDK\n * identify itself AS AN EDITOR CLIENT on every request to the Copilot surface.\n *\n * They are EXPORTED, OVERRIDABLE constants — not hidden values — precisely\n * because of that: presenting as another client is something the caller should be\n * able to read off the source and change without forking, so each one is a named\n * option (`editorHeaders`) with a visible default. Same reason\n * `CODEX_CLIENT_VERSION` is an exported constant in `provider-codex`. All three\n * values will also go stale, which is a second reason to keep them where a caller\n * can reach them.\n *\n * Use your own account, and prefer a provider's official first-party surface for\n * production. The README and the \"Client identity\" section of the docs carry the\n * full tradeoff.\n *\n * @module ai-agent-sdk/providers/copilot/adapter\n */\n\nimport { AgentSdkError, MISSING_CREDENTIAL_CODE, type RetryPolicyConfig } from '@alvin0/ai-agent-sdk-core'\nimport {\n defineModelProviderPlugin,\n type ComposableModelProviderPlugin,\n type CredentialOperationOptions,\n type ModelTarget,\n type SdkLogger,\n} from '@alvin0/ai-agent-sdk-core/provider'\nimport {\n createRuntimeHttpProvider,\n type HttpModelAdapter,\n type ProviderCatalogModel,\n type ProviderRequestLogger,\n type RuntimeModelDiscoveryContext,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport { openAiChatCompletionsProtocol } from '@alvin0/ai-agent-sdk-protocol-openai-chat-completions'\nimport { openAiResponsesProtocol } from '@alvin0/ai-agent-sdk-protocol-responses'\nimport { requireGitHubToken, type CopilotCredentialSnapshot } from './auth.ts'\nimport {\n copilotCatalogCacheOptions,\n discoverCopilotModels,\n resolveCopilotCatalogLimits,\n type CopilotEndpoint,\n} from './catalog.ts'\nimport { COPILOT_ERROR_CODES } from './common/error-codes.ts'\nimport {\n COPILOT_BASE_URL,\n resolveCopilotEditorHeaders,\n type CopilotEditorHeaders,\n} from './common/identity.ts'\nimport { captureCopilotStore, type CapturedCopilotStore } from './common/store-capture.ts'\nimport type {\n CopilotAuthFile,\n CopilotAuthStore,\n CopilotCredentialStore,\n} from './common/store-types.ts'\nimport {\n copilotDualProtocol,\n type CopilotDialect,\n} from './dual-protocol.ts'\nimport { createCopilotTokenCache, type CopilotTokenCache } from './exchange.ts'\nimport {\n COPILOT_RESPONSES_MODEL_PREFIXES,\n createCopilotEndpointRouter,\n type CopilotEndpointDecision,\n} from './router.ts'\n\n/**\n * The Copilot API base (Requirement 2.1).\n *\n * Declared in `./common/identity.ts` and re-exported here; that module's note\n * explains why the value sits in the leaf layer while this module stays the door\n * a reader opens.\n */\nexport { COPILOT_BASE_URL } from './common/identity.ts'\n\n/**\n * Default `Editor-Version`.\n *\n * NOT cosmetic: with either editor header missing the endpoint answers HTTP 400\n * and no request runs at all. This is also where the SDK identifies itself as an\n * editor client — see the module note for why it is a named option.\n *\n * ✔ CONFIRMED accepted on a live Copilot account on 2026-09-10 (`sku`\n * `free_educational_quota`). Sent as the only editor headers, together with\n * `COPILOT_EDITOR_PLUGIN_VERSION`, on all four live calls, and none answered\n * HTTP 400: `GET https://api.github.com/copilot_internal/v2/token` → 200,\n * `GET /models` → 200, a streaming `/chat/completions` on `gpt-4o-mini` → a\n * complete stream, `POST /embeddings` → 200.\n *\n * Confirmed, not permanent: this constant's first failure mode is going stale, so\n * re-run those four calls when the surface starts answering 400. The procedure is\n * exactly the one above — an editor header the endpoint rejects and one it never\n * received both surface as HTTP 400.\n */\nexport { COPILOT_EDITOR_VERSION } from './common/identity.ts'\n\n/**\n * Default `Editor-Plugin-Version`.\n *\n * Same contract as `COPILOT_EDITOR_VERSION`: mandatory, and part of the client\n * identity this SDK presents.\n *\n * ✔ CONFIRMED accepted on a live Copilot account on 2026-09-10, in the same run\n * that confirmed `COPILOT_EDITOR_VERSION` — both headers travel on every request,\n * so the one run confirms the pair. See that constant for the four calls and\n * their statuses.\n */\nexport { COPILOT_EDITOR_PLUGIN_VERSION } from './common/identity.ts'\n\n/** Overrides for the two editor headers; each field is independent. */\nexport type { CopilotEditorHeaders } from './common/identity.ts'\n\n/**\n * The Copilot dialect and its two projections, declared in `./dual-protocol.ts`\n * and re-exported here.\n *\n * The design's file map puts them in this module and DD-2 puts ownership with the\n * composite; both hold, because `copilotAdapter` BUILDS the composite. The source\n * edge therefore already runs adapter → dual-protocol, and declaring the runtime\n * projections here would make it bidirectional, which the repo's\n * circular-dependency check forbids. Same shape as `./router.ts` re-exporting\n * `CopilotEndpoint` from `./catalog.ts`.\n */\nexport {\n COPILOT_DEFAULT_DIALECT,\n toChatCompletionsDialect,\n toResponsesDialect,\n} from './dual-protocol.ts'\nexport type { CopilotDialect } from './dual-protocol.ts'\n\n/** Registry id, provider family, and observation label when the caller sets none. */\nexport const COPILOT_ROUTE_ID = 'copilot'\n\n/** Display name reported by the adapter and the plugin. */\nexport const COPILOT_DISPLAY_NAME = 'GitHub Copilot'\n\n/**\n * Everything a Copilot route can be configured with.\n *\n * The `authStore` is REQUIRED and injected: paths, the filesystem and the\n * environment belong to `Copilot_Node_Auth`, so a Universal package cannot supply\n * a default here (Requirement 6.1). Every other field is optional, and an absent\n * one is spread away rather than passed as `undefined` — see\n * {@link copilotAdapter}.\n */\nexport interface CopilotProviderOptions {\n /**\n * Where the credentials live: the compare-and-swap variant.\n *\n * This is the main path. {@link copilotAdapter} also accepts the read/write\n * variant through an overload; `copilotPlugin` does not, because transactional\n * registration and a store with no revisions are a poor pair.\n */\n readonly authStore: CopilotCredentialStore\n /** Endpoint base; defaults to `COPILOT_BASE_URL`. */\n readonly baseUrl?: string\n /**\n * Permit a cleartext `http:` base URL.\n *\n * Explicit opt-in rather than a lenient default, because every request to this\n * surface carries a bearer token (Requirement 2.2).\n */\n readonly allowInsecureHttp?: boolean\n /** Overrides for the two mandatory editor headers (Requirement 2.4). */\n readonly editorHeaders?: CopilotEditorHeaders\n /** Pin an endpoint for specific model ids, overriding the router (Requirement 9.6). */\n readonly endpointOverrides?: Readonly<Record<string, CopilotEndpoint>>\n /**\n * Extra model-id prefixes treated as `/responses`-capable when the catalog says\n * nothing.\n *\n * ADDS to `COPILOT_RESPONSES_MODEL_PREFIXES`; it cannot replace it, so an\n * override never silently drops a prefix this package ships.\n */\n readonly responsesModelPrefixes?: readonly string[]\n /** Synchronous, best-effort observer of every endpoint decision (Requirement 9.8). */\n readonly onEndpointDecision?: (decision: CopilotEndpointDecision) => void\n /**\n * A token cache shared with other routes.\n *\n * Pass one cache to several routes backed by the SAME credential and they\n * exchange once between them instead of once each.\n */\n readonly tokenCache?: CopilotTokenCache\n /** Exchange this long before the API token expires. */\n readonly exchangeMarginMs?: number\n /** GitHub API base, where the token exchange lives; pinned as its own origin. */\n readonly githubApiBaseUrl?: string\n\n /**\n * The model catalog.\n *\n * Left undefined, the adapter DISCOVERS it: which models an account may call\n * depends on its plan, its organisation policy and the editor identity the\n * request presents, so no hardcoded list is right for two accounts at once\n * (Requirement 8.1).\n */\n readonly models?: readonly ProviderCatalogModel[]\n /** Maximum raw catalog bytes. */\n readonly maxCatalogBytes?: number\n /** Maximum catalog entries; more than this is a malformed catalog, not a truncated one. */\n readonly maxCatalogModels?: number\n /** Maximum catalog response chunks. */\n readonly maxCatalogChunks?: number\n /** Catalog request deadline. */\n readonly catalogTimeoutMs?: number\n /** How long a discovered catalog stays fresh. */\n readonly catalogTtlMs?: number\n /** How long a stale catalog may still be served while a refresh runs. */\n readonly catalogStaleTtlMs?: number\n /** How long to wait before retrying discovery after it failed. */\n readonly catalogFailureBackoffMs?: number\n\n /** Dialect overrides, merged shallowly over `COPILOT_DEFAULT_DIALECT`. */\n readonly dialect?: Partial<CopilotDialect>\n /** Output cap when neither caller nor catalog names one. */\n readonly defaultMaxTokens?: number\n /** Context capacity assumed for an uncatalogued model. */\n readonly defaultContextWindow?: number\n /** Idle bound while a stream read is outstanding. */\n readonly streamIdleTimeoutMs?: number\n /** Deadline for one request. */\n readonly requestTimeoutMs?: number\n /** Maximum serialized request bytes. */\n readonly maxRequestBytes?: number\n /** Maximum response bytes. */\n readonly maxResponseBytes?: number\n /** Maximum response chunks. */\n readonly maxResponseChunks?: number\n /** Maximum SSE events in one stream. */\n readonly maxSseEvents?: number\n /** Maximum characters in one SSE event. */\n readonly maxSseEventChars?: number\n /** Maximum bytes read from a non-success response (Requirement 13.6). */\n readonly maxErrorBodyBytes?: number\n /** Deadline granted to {@link requestLogger} before the request proceeds anyway. */\n readonly requestLoggerTimeoutMs?: number\n /** Retry policy this route owns (Requirement 7.7). */\n readonly retryPolicy?: RetryPolicyConfig\n /**\n * Exact wire-request observer.\n *\n * BEST-EFFORT: credentials are redacted by the transport, the logger's deadline\n * is `requestLoggerTimeoutMs`, and a logger that overruns or throws does not\n * stop the request (Requirement 14.5).\n */\n readonly requestLogger?: ProviderRequestLogger\n /** HTTP implementation, for tests and non-browser runtimes. */\n readonly fetch?: typeof globalThis.fetch\n\n /** Registry id; defaults to {@link COPILOT_ROUTE_ID}. */\n readonly id?: string\n /** Routes the plugin installs; defaults to `[id]`. */\n readonly routes?: readonly string[]\n /** Default model; a string form requires exactly one route (Requirement 7.5). */\n readonly defaultModel?: string | ModelTarget\n}\n\n/**\n * The same options against the read/write store variant.\n *\n * Kept for symmetry with `provider-codex` and with the two store contracts\n * (Requirement 7.3). It has no revisions, so a commit cannot be\n * compare-and-swapped — which costs nothing here, since nothing on the Copilot\n * credential path writes.\n */\nexport interface CopilotLegacyProviderOptions extends Omit<CopilotProviderOptions, 'authStore'> {\n /** Where the credentials live: the read/write variant. */\n readonly authStore: CopilotAuthStore\n}\n\n/** Never-aborting logger sink for a resolve that arrives without a context. */\nconst NULL_LOGGER: SdkLogger = Object.freeze({\n child: () => NULL_LOGGER,\n trace: () => undefined,\n debug: () => undefined,\n info: () => undefined,\n warn: () => undefined,\n error: () => undefined,\n fatal: () => undefined,\n})\n\n/** Marker written in place of a token value that appeared in a response body. */\nconst REDACTED = '[REDACTED]'\n\n/**\n * The two transport-owned headers, sent on every request (Requirement 2.3).\n *\n * `content-type` cannot come from the auth layer — `provider-http` owns the name\n * at the transport layer and refuses a second owner — so it is declared here,\n * where it is allowed and where it is visible.\n */\nconst COPILOT_TRANSPORT_HEADERS: Readonly<Record<string, string>> = Object.freeze({\n 'content-type': 'application/json',\n accept: 'text/event-stream',\n})\n\n/** Bytes read from a non-success response when the caller configures no bound. */\nconst DEFAULT_MAX_ERROR_BODY_BYTES = 1024 * 1024\n\n/**\n * Create a Copilot adapter.\n *\n * Both store variants are accepted, and the variant is chosen by INSPECTING THE\n * MARKER through `captureCopilotStore` — which reads data properties only and\n * performs no storage I/O, so building a provider cannot run a line of the\n * caller's code (Requirement 7.3).\n * @param options - credential store, endpoint, catalog, dialect and transport settings.\n * @returns the adapter, ready to register.\n * @throws AgentSdkError with `CREDENTIAL_STORE_INVALID` when `authStore` is\n * neither store variant, or `COPILOT_ENDPOINT_OVERRIDE_INVALID` when\n * `endpointOverrides` pins an endpoint that does not exist.\n */\nexport function copilotAdapter(options: CopilotProviderOptions): HttpModelAdapter\nexport function copilotAdapter(options: CopilotLegacyProviderOptions): HttpModelAdapter\nexport function copilotAdapter(\n options: CopilotProviderOptions | CopilotLegacyProviderOptions,\n): HttpModelAdapter {\n return buildCopilotAdapter(options, captureCopilotStore(options?.authStore))\n}\n\n/**\n * The one adapter body, shared by both store variants and by the plugin.\n *\n * Four things are worth reading closely.\n *\n * **`auth.resolve` is the only place a token enters a request.** It reads the\n * store, demands a long-lived token, then asks the cache — which decides on its\n * own whether an exchange is due. `provider-http` calls `resolve` ONCE PER\n * OPERATION (Requirement 7.2), not once per retry, so the number of store reads\n * equals the number of operations and every attempt of one operation carries the\n * credential and the endpoint from a single snapshot.\n *\n * **`requireGitHubToken` runs BEFORE `cache.acquire`.** With no credential the\n * failure is `MISSING_CREDENTIAL` naming the login command, rather than an HTTP\n * error from an exchange that never had anything to exchange (Requirement 13.4).\n *\n * **`x-request-id` is the CLIENT's id, not the server's.** It exists to line up\n * two logs and carries nothing about the user.\n *\n * **`router.learn` only ADDS.** A catalog refresh never rewrites a decision that\n * already exists, which is how Requirement 9.7 holds structurally rather than by\n * convention.\n *\n * Every absent option is spread away instead of passed as `undefined`: a key\n * carrying `undefined` still overrides the runtime's own default, which turns\n * \"I did not configure this\" into \"I configured this to nothing\"\n * (Requirement 8.7).\n * @param options - the caller's options, either store variant.\n * @param captured - the already-captured store.\n * @returns the configured adapter.\n */\nfunction buildCopilotAdapter(\n options: CopilotProviderOptions | CopilotLegacyProviderOptions,\n captured: CapturedCopilotStore,\n): HttpModelAdapter {\n const router = createCopilotEndpointRouter({\n overrides: options.endpointOverrides ?? {},\n // Merged HERE so \"adds, never replaces\" is visible at the call site.\n prefixes: [...COPILOT_RESPONSES_MODEL_PREFIXES, ...(options.responsesModelPrefixes ?? [])],\n })\n const editorHeaders = resolveCopilotEditorHeaders(options.editorHeaders)\n const secrets = createCopilotSecrets()\n const catalogLimits = resolveCopilotCatalogLimits(options)\n const providerId = options.id ?? COPILOT_ROUTE_ID\n const cache = options.tokenCache ?? createCopilotTokenCache({\n providerId,\n editorHeaders,\n ...(options.githubApiBaseUrl === undefined\n ? {}\n : { githubApiBaseUrl: options.githubApiBaseUrl }),\n ...(options.exchangeMarginMs === undefined ? {} : { marginMs: options.exchangeMarginMs }),\n ...(options.requestTimeoutMs === undefined\n ? {}\n : { requestTimeoutMs: options.requestTimeoutMs }),\n ...(options.maxResponseBytes === undefined\n ? {}\n : { maxResponseBytes: options.maxResponseBytes }),\n ...(options.maxResponseChunks === undefined\n ? {}\n : { maxResponseChunks: options.maxResponseChunks }),\n ...(options.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureIssuer: options.allowInsecureHttp }),\n // The raw fetch, not the redacting wrapper: the exchange path does its own\n // bounded read and its own redaction, and it must not have its error bodies\n // rewritten by a layer that knows nothing about its classification table.\n ...(options.fetch === undefined ? {} : { fetch: options.fetch }),\n })\n const sessionId = options.dialect?.promptCacheKey ?? randomId()\n\n return createRuntimeHttpProvider<CopilotDialect>({\n displayName: COPILOT_DISPLAY_NAME,\n protocol: copilotDualProtocol({\n router,\n responses: openAiResponsesProtocol,\n chat: openAiChatCompletionsProtocol,\n ...(options.onEndpointDecision === undefined\n ? {}\n : { onDecision: options.onEndpointDecision }),\n }),\n baseUrl: options.baseUrl ?? COPILOT_BASE_URL,\n ...(options.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureHttp: options.allowInsecureHttp }),\n dialect: { ...options.dialect, promptCacheKey: sessionId },\n auth: {\n kind: 'dynamic',\n resolve: async ({ signal, context }) => {\n const operation: CredentialOperationOptions = {\n signal,\n logger: context?.logger ?? NULL_LOGGER,\n }\n const snapshot = await readCopilotSnapshot(captured, operation)\n // BEFORE the exchange: an empty store is a missing credential with a\n // command to run, not an HTTP failure.\n const github = requireGitHubToken(snapshot.file, snapshot.label)\n secrets.remember('github', github.token)\n const api = await cache.acquire(snapshot, operation, context)\n secrets.remember('api', api.token)\n return {\n authorization: `Bearer ${api.token}`,\n 'editor-version': editorHeaders.editorVersion,\n 'editor-plugin-version': editorHeaders.editorPluginVersion,\n // `content-type` is NOT returned here even though Requirement 2.3 lists\n // it among the mandatory headers: `provider-http` owns that name at the\n // TRANSPORT layer and rejects any other layer supplying it, which is a\n // good rule — one header, one owner, no last-writer-wins. It is set\n // explicitly through `baseHeaders` below rather than inherited\n // silently, so the requirement is still visible in this file.\n 'x-request-id': randomId(),\n }\n },\n },\n ...(options.models === undefined\n ? {\n discoverModels: async (\n context: RuntimeModelDiscoveryContext,\n ): Promise<readonly ProviderCatalogModel[]> => {\n const snapshot = await discoverCopilotModels(\n context,\n catalogLimits,\n options.fetch ?? globalThis.fetch,\n )\n // Adds only ids that have no decision yet (Requirement 9.7).\n router.learn(snapshot.generation)\n return snapshot.generation.map((entry) => entry.model)\n },\n }\n : { models: options.models }),\n ...copilotCatalogCacheOptions(options),\n ...(options.maxCatalogModels === undefined\n ? {}\n : { maxCatalogModels: options.maxCatalogModels }),\n ...(options.maxCatalogBytes === undefined ? {} : { maxCatalogBytes: options.maxCatalogBytes }),\n ...(options.defaultMaxTokens === undefined ? {} : { defaultMaxTokens: options.defaultMaxTokens }),\n ...(options.defaultContextWindow === undefined\n ? {}\n : { defaultContextWindow: options.defaultContextWindow }),\n ...transportLimits(options),\n // The transport-layer half of Requirement 2.3, stated rather than inherited.\n // `accept` travels with it because both names belong to the same layer and\n // supplying one of a pair while defaulting the other is how a stream ends up\n // asking for JSON.\n baseHeaders: COPILOT_TRANSPORT_HEADERS,\n ...(options.retryPolicy === undefined ? {} : { retryPolicy: options.retryPolicy }),\n ...(options.requestLogger === undefined ? {} : { requestLogger: options.requestLogger }),\n // A 400 for a missing editor header, and ONLY that, gets the Copilot code.\n errorCode: (status: number, detail: string): string | undefined =>\n isMissingEditorHeaderFailure(status, detail)\n ? COPILOT_ERROR_CODES.EDITOR_HEADERS_MISSING\n : undefined,\n fetch: copilotProviderFetch(options, secrets),\n })\n}\n\n/** Plugin options; the CAS store variant only. */\nexport type CopilotPluginOptions = CopilotProviderOptions\n\n/**\n * The transactional plugin for installing the Copilot provider.\n *\n * Composition follows `codexPlugin`: `id` defaults to {@link COPILOT_ROUTE_ID},\n * `family` is `'copilot'`, `routes` defaults to `[id]`, and a string\n * `defaultModel` requires exactly one route so the model target's provider can be\n * inferred (Requirements 7.4, 7.5).\n *\n * One difference from Codex: there is no overload per store variant. The\n * compare-and-swap store is the main path here, the read/write variant exists for\n * symmetry, and {@link copilotAdapter} is where it is accepted (Requirement 6.2).\n * The marker is checked at construction rather than at setup so a wrong store is\n * reported while the runtime is being composed, not on the first generation.\n * @param options - the same options {@link copilotAdapter} takes, CAS store only.\n * @returns a composable plugin registering one Copilot adapter.\n * @throws TypeError when `authStore` is not the compare-and-swap variant, or when\n * a string `defaultModel` is paired with anything but exactly one route.\n */\nexport function copilotPlugin(\n options: CopilotPluginOptions,\n): ComposableModelProviderPlugin & { readonly family: 'copilot' } {\n if (!isCredentialStoreInput(options?.authStore)) {\n throw new TypeError('copilotPlugin requires a Copilot credential store (the CAS variant)')\n }\n const id = options.id ?? COPILOT_ROUTE_ID\n const routes = Object.freeze([...(options.routes ?? [id])])\n return defineModelProviderPlugin({\n id,\n family: 'copilot',\n displayName: COPILOT_DISPLAY_NAME,\n routes,\n ...runtimeDefaultModel(options.defaultModel, routes),\n setup(registrar) {\n const adapter = buildCopilotAdapter(options, captureCopilotStore(options.authStore))\n const remove = registrar.registerAdapter(adapter)\n return () => {\n remove()\n return undefined\n }\n },\n }) as ComposableModelProviderPlugin & { readonly family: 'copilot' }\n}\n\n/**\n * Marker inspection only: no accessor is invoked and no method is captured.\n *\n * Full capture stays deferred to {@link buildCopilotAdapter}, so this check\n * cannot be the thing that runs the caller's code.\n * @param value - the `authStore` as passed in.\n * @returns true when it carries the credential-store marker.\n */\nfunction isCredentialStoreInput(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false\n const marker = Object.getOwnPropertyDescriptor(value, 'kind')\n return marker !== undefined && 'value' in marker && marker.value === 'credential-store'\n}\n\n/**\n * Resolve `defaultModel`, demanding one route for the string form.\n *\n * A string names a model but not a provider, and the provider is inferred from\n * the route. With two routes there is no answer, and picking the first would\n * install a default nobody chose (Requirement 7.5).\n * @param value - the caller's default model, when they set one.\n * @param routes - the routes this plugin installs.\n * @returns a one-key spread carrying `defaultModel`, or an empty one.\n * @throws TypeError when a string is paired with anything but exactly one route.\n */\nfunction runtimeDefaultModel(\n value: string | ModelTarget | undefined,\n routes: readonly string[],\n): { readonly defaultModel?: ModelTarget } {\n if (value === undefined) return {}\n if (typeof value !== 'string') return { defaultModel: value }\n if (routes.length !== 1) {\n throw new TypeError('A string defaultModel requires exactly one Copilot route')\n }\n return { defaultModel: Object.freeze({ provider: routes[0] ?? COPILOT_ROUTE_ID, id: value }) }\n}\n\n/**\n * Read the credential store once, through whichever variant was captured.\n *\n * The read/write variant has no revisions, so its snapshot revision is `null` —\n * which the token cache compares just as strictly as a real revision, it simply\n * never changes on its own.\n * @param captured - the captured store.\n * @param operation - the calling operation, whose signal bounds the read.\n * @returns the file, its revision and the store label, as one snapshot.\n * @throws AgentSdkError with the SDK's missing-credential code when the store is\n * empty (Requirement 13.4).\n */\nasync function readCopilotSnapshot(\n captured: CapturedCopilotStore,\n operation: CredentialOperationOptions,\n): Promise<CopilotCredentialSnapshot> {\n const record = captured.kind === 'versioned'\n ? await captured.store.read(operation)\n : { value: await captured.store.read(), revision: null }\n return Object.freeze({\n file: requireCopilotFile(record?.value, captured.label),\n revision: record?.revision ?? null,\n label: captured.label,\n })\n}\n\n/**\n * Demand a credential file, reusing the one message that says how to get one.\n *\n * `requireGitHubToken` owns the message and the code for all three shapes of \"no\n * credential\", so it is asked first. The throw after it is UNREACHABLE — an\n * absent file already failed there — and exists only so the type narrows without\n * a non-null assertion.\n * @param file - the file the store returned, or `undefined` for an empty store.\n * @param label - the store location named in the diagnostic.\n * @returns the file.\n */\nfunction requireCopilotFile(file: CopilotAuthFile | undefined, label: string): CopilotAuthFile {\n requireGitHubToken(file, label)\n if (file === undefined) {\n throw new AgentSdkError(\n `no GitHub Copilot credentials at ${label}`,\n MISSING_CREDENTIAL_CODE,\n )\n }\n return file\n}\n\n/**\n * The two token values currently held in memory, and the redaction that uses them.\n *\n * Two slots rather than a growing set: there is exactly one long-lived token and\n * one API token in play at a time, and a set that only ever grows would be a\n * credential leak of its own making.\n */\ninterface CopilotSecrets {\n /** Record the current value of one of the two tokens. */\n remember(kind: 'github' | 'api', value: string): void\n /** Replace every occurrence of either token with {@link REDACTED}. */\n redact(text: string): string\n}\n\n/** Build the two-slot secret registry. */\nfunction createCopilotSecrets(): CopilotSecrets {\n let github = ''\n let api = ''\n return {\n remember(kind, value): void {\n if (value.length === 0) return\n if (kind === 'github') github = value\n else api = value\n },\n redact(text): string {\n let result = text\n for (const secret of [github, api]) {\n if (secret.length === 0) continue\n result = result.split(secret).join(REDACTED)\n }\n return result\n },\n }\n}\n\n/**\n * The fetch the provider dispatches through: identical to the injected one,\n * except that an error body is redacted — and, for the one case the endpoint is\n * known to be unhelpful about, explained — before anything retains it.\n *\n * Why here and not in an error mapper: `provider-http` puts the raw error body\n * into the failure's `cause`, and by the time a mapper sees it the text is\n * already retained. Redacting at the transport is the only point that runs BEFORE\n * that, and an endpoint echoing the `Authorization` header back in an error body\n * is something that has actually happened (Requirement 13.7).\n *\n * What is deliberately NOT touched:\n *\n * - **Successful responses.** The body is a live SSE stream and must reach the\n * pipeline unread and unwrapped.\n * - **Redirects, in every shape.** Rebuilding a `Response` loses `type`,\n * `redirected` and `url` — the three signals the transport's redirect guard\n * reads — so anything that is not a 4xx/5xx passes through untouched and the\n * guard still sees the original (Requirement 7.8).\n * @param options - read for the injected fetch and the error-body bound.\n * @param secrets - the live token values to redact.\n * @returns a fetch implementation to hand to the runtime provider.\n */\nfunction copilotProviderFetch(\n options: CopilotProviderOptions | CopilotLegacyProviderOptions,\n secrets: CopilotSecrets,\n): typeof globalThis.fetch {\n const inner = options.fetch ?? globalThis.fetch\n const maxBytes = options.maxErrorBodyBytes ?? DEFAULT_MAX_ERROR_BODY_BYTES\n return async (...args: Parameters<typeof globalThis.fetch>): Promise<Response> => {\n const response = await inner(...args)\n if (response.status < 400 || response.type === 'opaqueredirect' || response.redirected) {\n return response\n }\n let raw: string\n try {\n raw = await readErrorBody(response, maxBytes)\n } catch {\n // A body that could not be read must not replace the status, which is the\n // more reliable signal anyway.\n return response\n }\n const redacted = secrets.redact(raw)\n const body = isMissingEditorHeaderFailure(response.status, redacted)\n ? editorHeaderDiagnostic(redacted)\n : redacted\n const headers = new Headers(response.headers)\n // The length changed, and a stale content-length would fail the bounded read\n // that comes next.\n headers.delete('content-length')\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n })\n }\n}\n\n/**\n * Read an error body up to a byte bound, marking a truncation rather than hiding it.\n * @param response - the non-success response.\n * @param maxBytes - the configured bound (Requirement 13.6).\n * @returns the decoded text, truncated with a note when it hit the bound.\n */\nasync function readErrorBody(response: Response, maxBytes: number): Promise<string> {\n if (response.body === null) return ''\n const reader = response.body.getReader()\n const decoder = new TextDecoder()\n let bytes = 0\n let text = ''\n try {\n while (true) {\n const next = await reader.read()\n if (next.done) return text + decoder.decode()\n if (next.value === undefined) continue\n const remaining = maxBytes - bytes\n if (remaining <= 0 || next.value.byteLength > remaining) {\n const kept = remaining <= 0 ? undefined : next.value.subarray(0, remaining)\n const partial = kept === undefined ? '' : decoder.decode(kept, { stream: true })\n await reader.cancel().catch(() => undefined)\n return `${text}${partial}${decoder.decode()}\\n[error body truncated at ${maxBytes} bytes]`\n }\n bytes += next.value.byteLength\n text += decoder.decode(next.value, { stream: true })\n }\n } finally {\n reader.releaseLock()\n }\n}\n\n/**\n * Whether a failure looks like the endpoint refusing a request for a missing\n * editor header.\n *\n * Matched BROADLY on purpose. The endpoint's wording is not a contract — it is\n * one sentence that can be rephrased at any time — so this looks for the header\n * names in any plausible spelling, or for the word \"editor\" beside a complaint\n * about a header. It is also bounded to status 400: a 400 that says nothing about\n * editors keeps `REQUEST_INVALID` from the shared mapping rather than being\n * relabelled into a Copilot-specific failure it is not (Requirement 2.5).\n * @param status - the response status.\n * @param detail - the provider's error text, joined by the shared parser.\n * @returns true when the missing-header diagnosis is warranted.\n */\nfunction isMissingEditorHeaderFailure(status: number, detail: string): boolean {\n if (status !== 400) return false\n if (/editor[\\s_-]*(?:plugin[\\s_-]*)?version/i.test(detail)) return true\n return /\\beditor\\b/i.test(detail)\n && /(missing|required|absent|invalid|unsupported|unrecogni[sz]ed|header)/i.test(detail)\n}\n\n/**\n * Wrap the endpoint's 400 in a body that names both headers and how to set them.\n *\n * The endpoint's own text is kept beside it rather than replaced: it is the\n * evidence, and the shared classifier reads it too.\n * @param endpointText - the endpoint's error body, already redacted.\n * @returns a JSON error body carrying the SDK-authored diagnosis.\n */\nfunction editorHeaderDiagnostic(endpointText: string): string {\n return JSON.stringify({\n error: {\n code: COPILOT_ERROR_CODES.EDITOR_HEADERS_MISSING,\n message: 'the Copilot endpoint rejected this request for a missing or unaccepted editor '\n + 'header. Both `Editor-Version` and `Editor-Plugin-Version` are mandatory; configure '\n + 'them with the `editorHeaders` option (`editorVersion`, `editorPluginVersion`), whose '\n + 'defaults are the exported COPILOT_EDITOR_VERSION and '\n + `COPILOT_EDITOR_PLUGIN_VERSION constants. The endpoint said: ${endpointText}`,\n },\n })\n}\n\n/**\n * Forward every transport bound the caller set, and only those.\n * @param options - the caller's options.\n * @returns an object carrying the configured transport limits.\n */\nfunction transportLimits(\n options: CopilotProviderOptions | CopilotLegacyProviderOptions,\n): Readonly<Record<string, number>> {\n return {\n ...(options.streamIdleTimeoutMs === undefined\n ? {}\n : { streamIdleTimeoutMs: options.streamIdleTimeoutMs }),\n ...(options.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: options.requestTimeoutMs }),\n ...(options.maxRequestBytes === undefined ? {} : { maxRequestBytes: options.maxRequestBytes }),\n ...(options.maxResponseBytes === undefined ? {} : { maxResponseBytes: options.maxResponseBytes }),\n ...(options.maxResponseChunks === undefined\n ? {}\n : { maxResponseChunks: options.maxResponseChunks }),\n ...(options.maxSseEvents === undefined ? {} : { maxSseEvents: options.maxSseEvents }),\n ...(options.maxSseEventChars === undefined ? {} : { maxSseEventChars: options.maxSseEventChars }),\n ...(options.maxErrorBodyBytes === undefined\n ? {}\n : { maxErrorBodyBytes: options.maxErrorBodyBytes }),\n ...(options.requestLoggerTimeoutMs === undefined\n ? {}\n : { requestLoggerTimeoutMs: options.requestLoggerTimeoutMs }),\n }\n}\n\n/** A client-side correlation id; carries nothing about the account or the prompt. */\nfunction randomId(): string {\n return globalThis.crypto?.randomUUID?.() ?? `sdk-${Date.now().toString(36)}`\n}\n","import type { CredentialOperationOptions, SdkLogger } from '@alvin0/ai-agent-sdk-core/provider'\nimport { requireGitHubToken, type CopilotAuthStore, type CopilotCredentialStore } from './auth.ts'\nimport { captureCopilotStore } from './common/store-capture.ts'\nimport { raceAbort } from './common/http.ts'\nimport {\n createCopilotTokenCache,\n type CopilotApiToken,\n type CopilotTokenCache,\n type CopilotTokenCacheOptions,\n} from './exchange.ts'\n\nconst NULL_LOGGER: SdkLogger = Object.freeze({\n child: () => NULL_LOGGER,\n trace: () => undefined, debug: () => undefined, info: () => undefined,\n warn: () => undefined, error: () => undefined, fatal: () => undefined,\n})\n\n/** Store-backed token acquisition, with an optional application-owned cache. */\nexport interface GetCopilotTokenOptions extends CopilotTokenCacheOptions {\n /** Reuse the same cache as copilotPlugin; when omitted each call exchanges anew. */\n readonly tokenCache?: CopilotTokenCache\n /** Invalidate the cached entry before acquisition; an in-flight exchange may be reused. */\n readonly forceRefresh?: boolean\n}\n\n/**\n * Read a GitHub credential from any store and acquire a live Copilot API token.\n * The GitHub credential does not rotate. A custom tokenCache can persist API tokens\n * in a database; its acquire/invalidate methods own refresh and cache policy.\n * When tokenCache is supplied, its configuration owns exchange settings.\n */\nexport async function getCopilotToken(\n store: CopilotCredentialStore | CopilotAuthStore,\n options: GetCopilotTokenOptions = {},\n): Promise<CopilotApiToken> {\n const operation: CredentialOperationOptions = {\n signal: options.signal ?? new AbortController().signal,\n logger: NULL_LOGGER,\n }\n operation.signal.throwIfAborted()\n const captured = captureCopilotStore(store)\n const record = captured.kind === 'versioned'\n ? await raceAbort(captured.store.read(operation), operation.signal) : undefined\n const file = captured.kind === 'legacy'\n ? await raceAbort(captured.store.read(), operation.signal) : record?.value\n operation.signal.throwIfAborted()\n requireGitHubToken(file, captured.label)\n // The credential validation above establishes that file is present.\n if (file === undefined) throw new TypeError('Copilot credential is missing')\n const cache = options.tokenCache ?? createCopilotTokenCache(options)\n if (options.forceRefresh === true) cache.invalidate()\n operation.signal.throwIfAborted()\n return raceAbort(\n cache.acquire({ file, revision: record?.revision ?? null, label: captured.label }, operation),\n operation.signal,\n )\n}\n","/**\n * The OAuth device flow behind the Copilot credential file.\n *\n * Device code rather than a browser redirect for the same reason the Codex\n * provider chose it: this SDK has no business binding a localhost port, and the\n * flow has to work over SSH, in containers, and in CI with no callback server.\n *\n * ## Client identity\n *\n * `COPILOT_OAUTH_CLIENT_ID` is one of three `Client_Identity_Constants` in this\n * package — the other two are `COPILOT_EDITOR_VERSION` and\n * `COPILOT_EDITOR_PLUGIN_VERSION` in `./adapter.ts`. All three default to values\n * that make this SDK identify itself AS AN EDITOR CLIENT when it signs in and\n * when it calls the Copilot surface. That is not a side effect; it is what makes\n * the surface answer at all, because `copilot_internal/v2/token` only accepts a\n * token minted by an OAuth App on GitHub's allowlist and a personal access token\n * cannot stand in for one.\n *\n * Because presenting as another client is a decision the caller should be able\n * to see and change, all three are EXPORTED, OVERRIDABLE constants rather than\n * hidden values buried in a request builder — the same reason\n * `CODEX_CLIENT_VERSION` is an exported constant in `provider-codex`. Each also\n * has a matching named option (`clientId` here, `editorHeaders` on the adapter),\n * so overriding one needs no fork. See the README and the \"Client identity\"\n * section of the docs for the full tradeoff, and prefer a provider's official\n * first-party surface for production.\n *\n * ## The two legs, and what is load-bearing about each\n *\n * ```text\n * POST {issuer}/login/device/code → { device_code, user_code,\n * Accept: application/json verification_uri, expires_in, interval }\n *\n * POST {issuer}/login/oauth/access_token → { access_token, token_type, scope }\n * Accept: application/json or HTTP 200 { error, interval? }\n * ```\n *\n * `Accept: application/json` is mandatory on BOTH legs, and the error channel on\n * the second leg is an HTTP 200 carrying `error` — see\n * {@link pollForCopilotToken} for why each of those changes the shape of the\n * code rather than just its headers.\n *\n * @module ai-agent-sdk/providers/copilot/oauth\n */\n\nimport type { CredentialOperationOptions, SdkLogger } from '@alvin0/ai-agent-sdk-core/provider'\nimport {\n copilotFetch,\n copilotUrl,\n issuerOf,\n readCopilotResponseText,\n type CopilotHttpOptions,\n type CopilotRequest,\n} from './common/http.ts'\nimport { captureCopilotStore, type CapturedCopilotStore } from './common/store-capture.ts'\nimport type {\n CopilotAccountIdentity,\n CopilotAuthFile,\n CopilotAuthStore,\n CopilotCredentialStore,\n} from './common/store-types.ts'\nimport {\n CopilotDeviceLoginError,\n credentialFailure,\n type CopilotDeviceLoginReason,\n} from './errors.ts'\n\n/** GitHub's OAuth issuer. */\nexport const DEFAULT_COPILOT_OAUTH_ISSUER = 'https://github.com'\n\n/**\n * Default OAuth client id. Public, not a secret.\n *\n * This is the client id published in GitHub's own editor-plugin sources (the\n * value `copilot.vim` and the other Copilot editor integrations ship in the\n * clear), which is why it is on the allowlist that\n * `copilot_internal/v2/token` checks. Sending it means this SDK signs in AS that\n * editor client. See the module note for why that makes it a named option\n * instead of a hidden constant.\n *\n * ⚠ UNVERIFIED against a live account. Recorded 2026-09-10 from public editor\n * integration sources only; no sign-in against a real Copilot account has\n * confirmed THIS client id.\n *\n * The 2026-09-10 live run that confirmed the two editor headers did NOT confirm\n * this value, and could not: it was handed an existing `ghu_` user-to-server token\n * out of band, so it exercised the EXCHANGE (which answered 200 for that token)\n * while never running the device flow that would put this `client_id` on the wire.\n * What that run does establish is the shape of the claim still outstanding — the\n * exchange endpoint and the allowlist check are live and reachable, and the only\n * untested link is whether they accept a token minted by this particular app.\n *\n * TODO(copilot-identity): confirm on a real Copilot account, then replace this\n * warning with the confirmation date. To confirm: run the device flow against\n * `https://github.com/login/device/code` with this `client_id` and\n * `scope=read:user`, approve it on a Copilot-enabled account, then exchange the\n * resulting user token at `GET https://api.github.com/copilot_internal/v2/token`.\n * The client id is confirmed when that exchange returns a Copilot token rather\n * than 401/403. A non-allowlisted client id fails at the exchange, not at\n * sign-in, so the device flow succeeding on its own proves nothing — and equally,\n * an exchange that succeeds for a token this flow did not mint proves nothing\n * about this constant.\n */\nexport const COPILOT_OAUTH_CLIENT_ID = 'Iv1.b507a08c87ecfe98'\n\n/** Requested scope; enough to exchange a token and read identity, no more. */\nexport const COPILOT_OAUTH_SCOPE = 'read:user'\n/**\n * The absolute ceiling on one device login, INDEPENDENT of the server's\n * `expires_in`.\n *\n * `expires_in` is honoured when it is shorter — there is no point polling a code\n * the server has already retired. It is not honoured when it is longer: a server\n * that answers `expires_in: 86400` would otherwise hang a CLI for a day, and this\n * SDK is not the right place to hold that terminal hostage (Requirement 4.3).\n */\nexport const COPILOT_DEVICE_CODE_MAX_WAIT_MS = 15 * 60 * 1_000\n\n/** Poll interval used when the device-code response states none. */\nexport const COPILOT_DEFAULT_POLL_INTERVAL_SECONDS = 5\n\n/**\n * Seconds added on every `slow_down`, per RFC 8628 §3.5.\n *\n * The increment is what makes the wait STRICTLY increase even when the server\n * repeats `slow_down` without a new `interval`. Without it, a server that only\n * ever says \"slow down\" would be polled at exactly the rate it just objected to.\n */\nexport const COPILOT_SLOW_DOWN_INCREMENT_SECONDS = 5\n\n/**\n * The warning shown beside the user code, worded exactly as the Codex device\n * prompt words it.\n *\n * A device code is a bearer of authorization that the user types into a page they\n * navigated to themselves. The one attack that works is getting somebody to type\n * an attacker's code, so the prompt has to say so; and it lives here rather than\n * in the CLI so every front end that renders a Copilot prompt renders the same\n * sentence.\n */\nexport const COPILOT_DEVICE_LOGIN_WARNING\n = 'Only continue if YOU started this login. If someone sent you this code, stop.'\n\n/** GitHub's device-authorization leg. */\nconst DEVICE_CODE_PATH = '/login/device/code'\n\n/** GitHub's device-token leg. */\nconst DEVICE_TOKEN_PATH = '/login/oauth/access_token'\n\n/** The device-code grant type, spelled as RFC 8628 requires. */\nconst DEVICE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code'\n\n/** The command that produces a credential, named when a login ends without one. */\nconst COPILOT_LOGIN_COMMAND = 'npm run provider:copilot:login-device'\n\nconst NEVER_ABORTED_SIGNAL = new AbortController().signal\n\nconst NULL_LOGGER: SdkLogger = Object.freeze({\n child: () => NULL_LOGGER,\n trace: () => undefined,\n debug: () => undefined,\n info: () => undefined,\n warn: () => undefined,\n error: () => undefined,\n fatal: () => undefined,\n})\n\n/**\n * The scheduler the poll loop waits on, injectable so no test waits real time.\n *\n * `now` travels with the timer rather than sitting in a second option, because\n * the two are read together on every iteration: a fake timer that advances\n * pending callbacks while `Date.now()` stands still would let a test satisfy the\n * 15-minute bound by accident, in either direction. Handing both through one\n * object makes \"virtual clock\" a single substitution (Properties 11, 12, 14).\n */\nexport interface CopilotTimer {\n /** Schedule `handler` after `ms`; returns whatever handle `clear` accepts. */\n readonly setTimeout: (handler: () => void, ms: number) => unknown\n /** Cancel a handle from {@link CopilotTimer.setTimeout}. */\n readonly clearTimeout: (handle: unknown) => void\n /** Current time in epoch milliseconds. */\n readonly now: () => number\n}\n\n/** The real scheduler: `setTimeout`, `clearTimeout` and `Date.now`. */\nexport const DEFAULT_COPILOT_TIMER: CopilotTimer = Object.freeze({\n setTimeout: (handler: () => void, ms: number) => setTimeout(handler, ms),\n clearTimeout: (handle: unknown) => clearTimeout(handle as ReturnType<typeof setTimeout>),\n now: () => Date.now(),\n})\n\n/**\n * Settings for both device-flow legs.\n *\n * Extends {@link CopilotHttpOptions}, so the issuer pin, the deadline and the two\n * read bounds are the same ones every other Copilot call site uses\n * (Requirement 4.7). `oauthIssuer` is named rather than called `issuer` because\n * this package pins THREE origins independently and the field name is what says\n * which one is being set.\n */\nexport interface CopilotOAuthOptions extends CopilotHttpOptions {\n /** OAuth issuer base URL; defaults to {@link DEFAULT_COPILOT_OAUTH_ISSUER}. */\n readonly oauthIssuer?: string\n /** OAuth client id; defaults to {@link COPILOT_OAUTH_CLIENT_ID}. */\n readonly clientId?: string\n /** Requested scope; defaults to {@link COPILOT_OAUTH_SCOPE}. */\n readonly scope?: string\n /** Scheduler for the poll wait; defaults to {@link DEFAULT_COPILOT_TIMER}. */\n readonly timer?: CopilotTimer\n}\n\n/** A pending device authorization the user has to approve. */\nexport interface CopilotDeviceCode {\n /** URL to open in a browser. Displayed to the user; never fetched by this SDK. */\n readonly verificationUrl: string\n /** One-time code the user types there. */\n readonly userCode: string\n /** Opaque handle this SDK polls with. Never shown to the user. */\n readonly deviceCode: string\n /** Seconds to wait between polls, as the server asked. */\n readonly intervalSeconds: number\n /** Seconds until the server retires the code. */\n readonly expiresInSeconds: number\n}\n\n/** Progress reported while a device login runs. */\nexport interface CopilotLoginProgress {\n /** The code is ready; show it, with {@link COPILOT_DEVICE_LOGIN_WARNING}. */\n readonly onPrompt?: (code: CopilotDeviceCode) => void\n /** Called before each poll, with the interval currently in effect. */\n readonly onPoll?: (elapsedMs: number, intervalSeconds: number) => void\n}\n\n/** Result of a completed device login. */\nexport interface CopilotLoginResult {\n /** Where the credential was written. Always present. */\n readonly location: string\n /** GitHub login, when the endpoint discloses one. */\n readonly login: string | undefined\n /** Numeric account id, when the endpoint discloses one. */\n readonly accountId: number | undefined\n /** Granted scope, when the endpoint discloses it. */\n readonly scope: string | undefined\n}\n\ntype AnyCopilotStore = CopilotAuthStore | CopilotCredentialStore\n\ninterface CopilotStoreSnapshot {\n readonly file: CopilotAuthFile | undefined\n readonly revision: string | null\n}\n\n/** What the token leg hands back once the user approves. */\ninterface CopilotAccessToken {\n readonly accessToken: string\n readonly tokenType: string | undefined\n readonly scope: string | undefined\n readonly account: CopilotAccountIdentity | undefined\n}\n\n/**\n * Start a device authorization.\n *\n * `Accept: application/json` is set here as well as on the token leg. It is\n * load-bearing on the token leg (see {@link pollForCopilotToken}) and harmless\n * here, and setting it on both keeps the pair from drifting into \"one of the two\n * legs parses JSON\".\n * @param options - issuer, client id, scope, cancellation and read bounds.\n * @returns the code, the URL and the timings to show the user.\n * @throws CopilotDeviceLoginError with `reason: 'aborted'` when the caller's\n * signal aborts, or `reason: 'failed'` when the endpoint answers with anything\n * other than a usable device authorization.\n */\nexport async function requestCopilotDeviceCode(\n options: CopilotOAuthOptions = {},\n): Promise<CopilotDeviceCode> {\n const pinned = issuerOf('oauthIssuer', options.oauthIssuer, DEFAULT_COPILOT_OAUTH_ISSUER, options)\n const body = await deviceJson(\n {\n pinned,\n url: copilotUrl(pinned, DEVICE_CODE_PATH),\n operation: 'device code',\n init: {\n method: 'POST',\n headers: { accept: 'application/json', 'content-type': 'application/json' },\n body: JSON.stringify({\n client_id: options.clientId ?? COPILOT_OAUTH_CLIENT_ID,\n scope: options.scope ?? COPILOT_OAUTH_SCOPE,\n }),\n },\n },\n 'the device-code endpoint',\n options,\n )\n if (!body.ok) {\n throw deviceFailure(\n `the device-code endpoint failed (HTTP ${body.status})`,\n 'failed',\n body.parseError,\n )\n }\n return Object.freeze({\n verificationUrl: verificationUrlOf(body.json, options),\n userCode: requireDeviceString(body.json, 'user_code'),\n deviceCode: requireDeviceString(body.json, 'device_code'),\n intervalSeconds: positiveSecondsOf(body.json.interval, COPILOT_DEFAULT_POLL_INTERVAL_SECONDS),\n expiresInSeconds: positiveSecondsOf(\n body.json.expires_in,\n COPILOT_DEVICE_CODE_MAX_WAIT_MS / 1_000,\n ),\n })\n}\n\n/**\n * Run a full device login and persist the resulting `GitHub_User_Token`.\n *\n * The store is read BEFORE the flow starts, so the commit carries the revision\n * that was current when the login began and a concurrent login loses the race\n * loudly instead of silently overwriting. Nothing else is written: the\n * `GitHub_User_Token` does not rotate, so this is the only write in the whole\n * Copilot credential path.\n * @param store - the credential store to write, in either variant.\n * @param options - issuer, client id, scope, cancellation, bounds and timer.\n * @param progress - prompt and poll notifications for a CLI to render.\n * @returns the store location plus whatever identity the endpoint disclosed.\n * @throws CopilotDeviceLoginError with `reason` distinguishing `denied`,\n * `expired`, `timeout`, `aborted` and `failed`.\n */\nexport function runCopilotDeviceLogin(\n store: CopilotCredentialStore,\n options?: CopilotOAuthOptions,\n progress?: CopilotLoginProgress,\n): Promise<CopilotLoginResult>\nexport function runCopilotDeviceLogin(\n store: CopilotAuthStore,\n options?: CopilotOAuthOptions,\n progress?: CopilotLoginProgress,\n): Promise<CopilotLoginResult>\nexport async function runCopilotDeviceLogin(\n store: AnyCopilotStore,\n options: CopilotOAuthOptions = {},\n progress: CopilotLoginProgress = {},\n): Promise<CopilotLoginResult> {\n const captured = captureCopilotStore(store)\n const operation: CredentialOperationOptions = {\n signal: options.signal ?? NEVER_ABORTED_SIGNAL,\n logger: NULL_LOGGER,\n }\n const initial = await readStore(captured, operation)\n const code = await requestCopilotDeviceCode(options)\n notify(() => progress.onPrompt?.(code))\n const token = await pollForCopilotToken(code, options, progress)\n const file: CopilotAuthFile = {\n version: 1,\n github: {\n token: token.accessToken,\n ...token.tokenType === undefined ? {} : { tokenType: token.tokenType },\n ...token.scope === undefined ? {} : { scope: token.scope },\n },\n ...token.account === undefined ? {} : { account: token.account },\n clientId: options.clientId ?? COPILOT_OAUTH_CLIENT_ID,\n obtainedAt: new Date(timerOf(options).now()).toISOString(),\n }\n await commitStore(captured, file, initial.revision, operation)\n return Object.freeze({\n location: captured.label,\n login: token.account?.login,\n accountId: token.account?.id,\n scope: token.scope,\n })\n}\n\n/**\n * Poll the token leg until the user approves, the server refuses, or a bound\n * passes.\n *\n * Two things make this loop different from the Codex one, and both are easy to\n * get wrong:\n *\n * - **`Accept: application/json` is mandatory.** Without it GitHub's token\n * endpoint answers FORM-ENCODED, so a JSON parser meets\n * `error=authorization_pending&interval=10` and throws — which turns the \"not\n * approved yet\" branch into a hard-failure branch, and the flow can then never\n * succeed at all.\n * - **The error channel is HTTP 200 with `error` in the body.** Codex surfaces\n * \"pending\" as 403/404; GitHub surfaces it as a 200. So classification reads the\n * BODY FIRST and the status second. A status-first reader treats every pending\n * poll as a success and then fails looking for `access_token`.\n * @param code - the pending authorization.\n * @param options - issuer, client id, bounds and the injectable timer.\n * @param progress - poll notifications.\n * @returns the access token and whatever the endpoint disclosed beside it.\n */\nasync function pollForCopilotToken(\n code: CopilotDeviceCode,\n options: CopilotOAuthOptions,\n progress: CopilotLoginProgress,\n): Promise<CopilotAccessToken> {\n const pinned = issuerOf('oauthIssuer', options.oauthIssuer, DEFAULT_COPILOT_OAUTH_ISSUER, options)\n const url = copilotUrl(pinned, DEVICE_TOKEN_PATH)\n const timer = timerOf(options)\n const startedAt = timer.now()\n // The 15-minute ceiling is absolute; `expires_in` only ever pulls the deadline\n // in. min() is the whole of that rule.\n const deadlineAt = startedAt + Math.min(\n COPILOT_DEVICE_CODE_MAX_WAIT_MS,\n code.expiresInSeconds * 1_000,\n )\n let intervalSeconds = code.intervalSeconds\n\n while (true) {\n throwIfAborted(options.signal)\n if (timer.now() >= deadlineAt) throw deviceTimeout(startedAt, timer.now())\n notify(() => progress.onPoll?.(timer.now() - startedAt, intervalSeconds))\n\n const body = await deviceJson(\n {\n pinned,\n url,\n operation: 'device token',\n init: {\n method: 'POST',\n headers: { accept: 'application/json', 'content-type': 'application/json' },\n body: JSON.stringify({\n client_id: options.clientId ?? COPILOT_OAUTH_CLIENT_ID,\n device_code: code.deviceCode,\n grant_type: DEVICE_GRANT_TYPE,\n }),\n },\n },\n 'the device-token endpoint',\n options,\n )\n\n // Body first, status second.\n const error = typeof body.json.error === 'string' ? body.json.error : undefined\n if (error === 'authorization_pending' || error === 'slow_down') {\n intervalSeconds = nextIntervalSeconds(intervalSeconds, body.json.interval, error)\n const remaining = deadlineAt - timer.now()\n if (remaining <= 0) throw deviceTimeout(startedAt, timer.now())\n await sleep(Math.min(intervalSeconds * 1_000, remaining), options.signal, timer)\n continue\n }\n if (error === 'access_denied') {\n throw deviceFailure(\n 'the device login was denied on GitHub;'\n + ` run \\`${COPILOT_LOGIN_COMMAND}\\` again if you did mean to approve it`,\n 'denied',\n )\n }\n if (error === 'expired_token') {\n throw deviceFailure(\n `the device code expired before it was approved; run \\`${COPILOT_LOGIN_COMMAND}\\``\n + ' again to request a new code',\n 'expired',\n )\n }\n if (error !== undefined) {\n throw deviceFailure(\n `the device-token endpoint refused the request (${error}, HTTP ${body.status})`,\n 'failed',\n )\n }\n if (!body.ok) {\n throw deviceFailure(\n `the device-token endpoint failed (HTTP ${body.status})`,\n 'failed',\n body.parseError,\n )\n }\n return Object.freeze({\n accessToken: requireDeviceString(body.json, 'access_token'),\n tokenType: optionalString(body.json.token_type),\n scope: optionalString(body.json.scope),\n account: accountIdentityOf(body.json),\n })\n }\n}\n\n/**\n * The effective wait after a `slow_down`, which must STRICTLY increase.\n *\n * `max(current, server-requested, current + 5)` — the third term is what keeps\n * the sequence increasing when the server sends no new `interval`, and taking the\n * max of all three keeps it from ever decreasing when the server sends a smaller\n * one. `authorization_pending` may carry a new interval too; there it is honoured\n * without the increment, so an ordinary pending poll does not back off forever.\n */\nfunction nextIntervalSeconds(\n current: number,\n requested: unknown,\n error: 'authorization_pending' | 'slow_down',\n): number {\n const server = positiveSecondsOf(requested, 0)\n return error === 'slow_down'\n ? Math.max(current, server, current + COPILOT_SLOW_DOWN_INCREMENT_SECONDS)\n : Math.max(current, server)\n}\n\n/**\n * Wait `ms`, losing the race to `signal` the instant it aborts.\n *\n * The timer is injected rather than closed over, so a test can drive the poll\n * loop through fifteen virtual minutes in a millisecond. Aborting rejects instead\n * of resolving early, because a caller who pressed Ctrl-C wants the flow to END,\n * not to take one more turn round the loop.\n */\nfunction sleep(ms: number, signal: AbortSignal | undefined, timer: CopilotTimer): Promise<void> {\n if (signal?.aborted === true) return Promise.reject(deviceAborted())\n return new Promise<void>((resolve, reject) => {\n const onAbort = (): void => {\n timer.clearTimeout(handle)\n reject(deviceAborted())\n }\n const handle = timer.setTimeout(() => {\n signal?.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n}\n\n/** One bounded read of an OAuth response, plus the status it came with. */\ninterface DeviceJson {\n readonly ok: boolean\n readonly status: number\n readonly json: Record<string, unknown>\n /** Why the body was not usable JSON, when it was not. */\n readonly parseError: unknown\n}\n\n/**\n * Dispatch one OAuth leg and read its body within the configured bounds.\n *\n * A body that is not a JSON object yields an EMPTY object plus `parseError`\n * rather than throwing: the status still has to be classified, and on the token\n * leg an unreadable body is one of the shapes a misconfigured `Accept` header\n * produces. Callers therefore always get to the body-first branch, and reach a\n * hard failure only after it finds no `error`.\n */\nasync function deviceJson(\n request: CopilotRequest,\n what: string,\n options: CopilotOAuthOptions,\n): Promise<DeviceJson> {\n let response: Response\n try {\n response = await copilotFetch(request, options)\n } catch (error: unknown) {\n throwIfAborted(options.signal)\n throw error instanceof CopilotDeviceLoginError\n ? error\n : deviceFailure(`${what} could not be reached`, 'failed', error)\n }\n let raw: string\n try {\n raw = await readCopilotResponseText(response, options)\n } catch (error: unknown) {\n throwIfAborted(options.signal)\n throw deviceFailure(`${what} returned a response beyond the configured limits`, 'failed', error)\n }\n try {\n const parsed = JSON.parse(raw) as unknown\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new TypeError(`${what} returned JSON that is not an object`)\n }\n return {\n ok: response.ok,\n status: response.status,\n json: parsed as Record<string, unknown>,\n parseError: undefined,\n }\n } catch (error: unknown) {\n return { ok: response.ok, status: response.status, json: {}, parseError: error }\n }\n}\n\n/**\n * Read the verification URL the user is told to open.\n *\n * Only the SCHEME is constrained, not the origin. This SDK never fetches this\n * URL — it prints it — and GitHub Enterprise deployments legitimately answer with\n * a host other than the issuer, so an origin pin here would reject working\n * installations to guard a request that is never made. The scheme check remains\n * because a `javascript:` or `data:` URL handed to a browser opener is a real\n * problem, and {@link COPILOT_DEVICE_LOGIN_WARNING} covers the rest.\n */\nfunction verificationUrlOf(\n body: Record<string, unknown>,\n options: CopilotOAuthOptions,\n): string {\n const raw = requireDeviceString(body, 'verification_uri')\n let url: URL\n try {\n url = new URL(raw)\n } catch (error: unknown) {\n throw deviceFailure('the device-code endpoint returned an unusable verification URL', 'failed', error)\n }\n if (url.protocol !== 'https:'\n && !(options.allowInsecureIssuer === true && url.protocol === 'http:')) {\n throw deviceFailure('the device-code verification URL must use https', 'failed')\n }\n return url.href\n}\n\n/** Identity fields, present only when the endpoint disclosed them (Property 16). */\nfunction accountIdentityOf(body: Record<string, unknown>): CopilotAccountIdentity | undefined {\n const login = optionalString(body.login)\n const name = optionalString(body.name)\n const id = typeof body.id === 'number' && Number.isFinite(body.id) ? body.id : undefined\n if (login === undefined && name === undefined && id === undefined) return undefined\n return Object.freeze({\n ...login === undefined ? {} : { login },\n ...name === undefined ? {} : { name },\n ...id === undefined ? {} : { id },\n })\n}\n\nfunction optionalString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nfunction requireDeviceString(body: Record<string, unknown>, key: string): string {\n const value = body[key]\n if (typeof value !== 'string' || value.length === 0) {\n throw deviceFailure(`the device flow response omitted \"${key}\"`, 'failed')\n }\n return value\n}\n\n/**\n * Read a seconds value that the endpoint may send as a number, as a numeric\n * string, or not at all.\n *\n * GitHub has been observed sending `interval` as a string, so both forms are\n * accepted; anything unparsable falls back rather than failing the login, because\n * a bad hint about pacing is not a reason to refuse a working authorization.\n */\nfunction positiveSecondsOf(value: unknown, fallbackSeconds: number): number {\n const parsed = typeof value === 'number'\n ? value\n : typeof value === 'string' ? Number.parseInt(value.trim(), 10) : Number.NaN\n return Number.isFinite(parsed) && parsed > 0 ? parsed : fallbackSeconds\n}\n\nfunction timerOf(options: CopilotOAuthOptions): CopilotTimer {\n return options.timer ?? DEFAULT_COPILOT_TIMER\n}\n\n/** Run a progress observer; observers do not own authentication. */\nfunction notify(report: () => void): void {\n try {\n report()\n } catch { /* a CLI's rendering must not decide whether a login succeeds */ }\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted === true) throw deviceAborted()\n}\n\nfunction deviceAborted(): CopilotDeviceLoginError {\n return deviceFailure('the device login was cancelled', 'aborted')\n}\n\nfunction deviceTimeout(startedAt: number, now: number): CopilotDeviceLoginError {\n return deviceFailure(\n `the device login was not approved within ${Math.round((now - startedAt) / 1_000)}s`\n + ` (bound: ${COPILOT_DEVICE_CODE_MAX_WAIT_MS / 60_000} minutes);`\n + ` run \\`${COPILOT_LOGIN_COMMAND}\\` again`,\n 'timeout',\n )\n}\n\nfunction deviceFailure(\n message: string,\n reason: CopilotDeviceLoginReason,\n cause?: unknown,\n): CopilotDeviceLoginError {\n return new CopilotDeviceLoginError(credentialFailure(message, cause), reason)\n}\n\nasync function readStore(\n captured: CapturedCopilotStore,\n operation: CredentialOperationOptions,\n): Promise<CopilotStoreSnapshot> {\n if (captured.kind === 'versioned') {\n const record = await captured.store.read(operation)\n return record === undefined\n ? { file: undefined, revision: null }\n : { file: record.value, revision: record.revision }\n }\n return { file: await captured.store.read(), revision: null }\n}\n\nasync function commitStore(\n captured: CapturedCopilotStore,\n file: CopilotAuthFile,\n expectedRevision: string | null,\n operation: CredentialOperationOptions,\n): Promise<void> {\n if (captured.kind === 'versioned') {\n await captured.store.commit({ value: file, expectedRevision }, operation)\n return\n }\n await captured.store.write(file)\n}\n"],"mappings":"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"}
|