dsh-github-copilot 0.4.0-alpha.31 → 0.4.0-alpha.32

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/lib/index.js CHANGED
@@ -1,29 +1,29 @@
1
- import { C as currentSearchInitiator, D as GITHUB_COPILOT_CREDENTIAL_KEY, E as readCopilotCatalog, O as GITHUB_COPILOT_PREVIEW_PROVIDER_ID, S as currentChatRoute, T as isPluginPreviewProvider, _ as RESPONSES_WEB_SEARCH_TOOL_TYPE, a as COPILOT_HOSTED_SEARCH_PROVIDER_ID, b as candidatesForRoute, c as abortable$1, d as readBounded, f as applyRequestAuth, h as ANTHROPIC_WEB_SEARCH_TOOL_TYPE, i as describeSearchBackend, k as GITHUB_COPILOT_PROVIDER_ID, l as isAbortError, m as providerRequestHeaders, n as routeSessionSearch, o as GITHUB_COPILOT_HOSTED_SEARCH_PROVIDER_ID, p as normalizeRequestAuth, r as DescribedSearchFallbackError, s as createTraditionalSearchProvider, t as isCopilotSearchSelection, u as providerErrorMessage, v as SearchPlan, w as currentSearchSelection, x as sameCandidates, y as WEB_SEARCH_TOOL_TYPE } from "./search-routing-pFLux0W7.js";
1
+ import { C as currentSearchInitiator, D as GITHUB_COPILOT_CREDENTIAL_KEY, E as readCopilotCatalog, O as GITHUB_COPILOT_PREVIEW_PROVIDER_ID, S as currentChatRoute, T as isPluginPreviewProvider, _ as RESPONSES_WEB_SEARCH_TOOL_TYPE, a as COPILOT_HOSTED_SEARCH_PROVIDER_ID, b as candidatesForRoute, c as abortable$1, d as readBounded, f as applyRequestAuth, h as ANTHROPIC_WEB_SEARCH_TOOL_TYPE, i as describeSearchBackend, k as GITHUB_COPILOT_PROVIDER_ID, l as isAbortError, m as providerRequestHeaders, n as routeSessionSearch, o as GITHUB_COPILOT_HOSTED_SEARCH_PROVIDER_ID, p as normalizeRequestAuth, r as DescribedSearchFallbackError, s as createTraditionalSearchProvider, t as isCopilotSearchSelection, u as providerErrorMessage, v as SearchPlan, w as currentSearchSelection, x as sameCandidates, y as WEB_SEARCH_TOOL_TYPE } from "./search-routing-DqLKem1c.js";
2
2
  import AuthorizationService from "@deepseek-ai/dsh-authorization";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
4
  import * as dshScope from "@deepseek-ai/dsh-scope";
5
5
  import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
6
6
  import z from "@deepseek-ai/schemastery";
7
7
  import { z as z$1 } from "zod";
8
+ import { createModels, getSupportedThinkingLevels, hasApi, lazyStream } from "@earendil-works/pi-ai";
9
+ import { githubCopilotProvider } from "@earendil-works/pi-ai/providers/github-copilot";
10
+ import { getBuiltinModels } from "@earendil-works/pi-ai/providers/all";
8
11
  import * as dshLlm from "@deepseek-ai/dsh-llm";
9
12
  import { CONTEXT_WINDOW_EXCEEDED_CODE, LlmError, ReasoningEffortId, attributionHeaders, callConfigEquals, contentHasImage, isAgentLoopRequest, resolveImageAttachmentAccess, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
10
13
  import { WebError } from "@deepseek-ai/dsh-web";
11
14
  import * as dshSettings from "@deepseek-ai/dsh-settings";
12
- import { getBuiltinModels } from "@earendil-works/pi-ai/providers/all";
13
- import { createModels, getSupportedThinkingLevels, hasApi, lazyStream } from "@earendil-works/pi-ai";
14
15
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
15
- import { githubCopilotProvider } from "@earendil-works/pi-ai/providers/github-copilot";
16
16
  import { Config as Config$1, PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
17
17
  import { estimateContextTokens, estimateMessageTokens } from "@earendil-works/pi-ai/utils/estimate";
18
18
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "@earendil-works/pi-ai/api/github-copilot-headers";
19
19
  //#region lib/types/dual-model-host.js
20
20
  /** Optional plugin-owned planner/executor sessions; no Core/default-model mutation. */
21
- var __runInitializers$2 = function(thisArg, initializers, value) {
21
+ var __runInitializers$3 = function(thisArg, initializers, value) {
22
22
  var useValue = arguments.length > 2;
23
23
  for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
24
24
  return useValue ? value : void 0;
25
25
  };
26
- var __esDecorate$2 = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
26
+ var __esDecorate$3 = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
27
27
  function accept(f) {
28
28
  if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
29
29
  return f;
@@ -305,7 +305,7 @@ let GitHubCopilotDualModel = (() => {
305
305
  _view_decorators = [Remote];
306
306
  _save_decorators = [Remote];
307
307
  _create_decorators = [Remote];
308
- __esDecorate$2(this, null, _view_decorators, {
308
+ __esDecorate$3(this, null, _view_decorators, {
309
309
  kind: "method",
310
310
  name: "view",
311
311
  static: false,
@@ -316,7 +316,7 @@ let GitHubCopilotDualModel = (() => {
316
316
  },
317
317
  metadata: _metadata
318
318
  }, null, _instanceExtraInitializers);
319
- __esDecorate$2(this, null, _save_decorators, {
319
+ __esDecorate$3(this, null, _save_decorators, {
320
320
  kind: "method",
321
321
  name: "save",
322
322
  static: false,
@@ -327,7 +327,7 @@ let GitHubCopilotDualModel = (() => {
327
327
  },
328
328
  metadata: _metadata
329
329
  }, null, _instanceExtraInitializers);
330
- __esDecorate$2(this, null, _create_decorators, {
330
+ __esDecorate$3(this, null, _create_decorators, {
331
331
  kind: "method",
332
332
  name: "create",
333
333
  static: false,
@@ -345,7 +345,7 @@ let GitHubCopilotDualModel = (() => {
345
345
  value: _metadata
346
346
  });
347
347
  }
348
- owner = __runInitializers$2(this, _instanceExtraInitializers);
348
+ owner = __runInitializers$3(this, _instanceExtraInitializers);
349
349
  lifetime = new AbortController();
350
350
  creates = /* @__PURE__ */ new Map();
351
351
  overlays = /* @__PURE__ */ new Map();
@@ -867,12 +867,12 @@ const SearchProviderCatalogSchema = z$1.object({
867
867
  }).strict();
868
868
  //#endregion
869
869
  //#region lib/types/search-routing-host.js
870
- var __runInitializers$1 = function(thisArg, initializers, value) {
870
+ var __runInitializers$2 = function(thisArg, initializers, value) {
871
871
  var useValue = arguments.length > 2;
872
872
  for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
873
873
  return useValue ? value : void 0;
874
874
  };
875
- var __esDecorate$1 = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
875
+ var __esDecorate$2 = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
876
876
  function accept(f) {
877
877
  if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
878
878
  return f;
@@ -915,7 +915,7 @@ let SearchRoutingController = (() => {
915
915
  static {
916
916
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
917
917
  _providers_decorators = [Remote];
918
- __esDecorate$1(this, null, _providers_decorators, {
918
+ __esDecorate$2(this, null, _providers_decorators, {
919
919
  kind: "method",
920
920
  name: "providers",
921
921
  static: false,
@@ -935,7 +935,7 @@ let SearchRoutingController = (() => {
935
935
  }
936
936
  constructor(ctx) {
937
937
  super(ctx, "githubCopilotSearchRouting");
938
- __runInitializers$1(this, _instanceExtraInitializers);
938
+ __runInitializers$2(this, _instanceExtraInitializers);
939
939
  }
940
940
  providers() {
941
941
  const unavailable = {
@@ -960,131 +960,901 @@ let SearchRoutingController = (() => {
960
960
  };
961
961
  })();
962
962
  //#endregion
963
- //#region package.json
964
- var name$1 = "dsh-github-copilot";
965
- var version = "0.4.0-alpha.31";
963
+ //#region lib/types/copilot-grant.js
964
+ function nonEmptyOAuthString(value, field) {
965
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`github-copilot: OAuth credential field "${field}" must be a non-empty string`);
966
+ return value;
967
+ }
968
+ /**
969
+ * Rebuild pi-ai's GitHub Copilot grant as the exact JSON-safe provider shape.
970
+ * Prototype and unrelated extension members are intentionally not preserved.
971
+ */
972
+ function normalizeGitHubCopilotOAuthCredential(credential) {
973
+ if (typeof credential !== "object" || credential === null) throw new Error("github-copilot: OAuth credential must be an object");
974
+ if (Reflect.get(credential, "type") !== "oauth") throw new Error("github-copilot: OAuth credential type must be \"oauth\"");
975
+ const refresh = nonEmptyOAuthString(Reflect.get(credential, "refresh"), "refresh");
976
+ const access = nonEmptyOAuthString(Reflect.get(credential, "access"), "access");
977
+ const expires = Reflect.get(credential, "expires");
978
+ if (typeof expires !== "number" || !Number.isFinite(expires)) throw new Error("github-copilot: OAuth credential field \"expires\" must be a finite number");
979
+ const enterpriseUrl = Reflect.get(credential, "enterpriseUrl");
980
+ if (enterpriseUrl !== void 0 && (typeof enterpriseUrl !== "string" || enterpriseUrl.trim().length === 0)) throw new Error("github-copilot: OAuth credential field \"enterpriseUrl\" must be a non-empty string");
981
+ const availableModelIds = Reflect.get(credential, "availableModelIds");
982
+ if (availableModelIds !== void 0 && (!Array.isArray(availableModelIds) || !availableModelIds.every((modelId) => typeof modelId === "string" && modelId.trim().length > 0))) throw new Error("github-copilot: OAuth credential field \"availableModelIds\" must be an array of non-empty strings");
983
+ return {
984
+ type: "oauth",
985
+ refresh,
986
+ access,
987
+ expires,
988
+ ...enterpriseUrl === void 0 ? {} : { enterpriseUrl },
989
+ ...availableModelIds === void 0 ? {} : { availableModelIds: [...new Set(availableModelIds)] }
990
+ };
991
+ }
966
992
  //#endregion
967
- //#region lib/types/probe.js
993
+ //#region lib/types/copilot-auth.js
968
994
  /**
969
- * Capability probe: one bounded operation per candidate protocol that forces
970
- * the server-side web tool to run and checks the reply for the structured
971
- * evidence that it did. A protocol claim is trusted only after this passes —
972
- * several "Responses-compatible" gateways accept the `web_search` tool field
973
- * and silently ignore it (DeepSeek documents that unknown tool types are
974
- * ignored), which is exactly the failure mode static protocol detection
975
- * cannot see. The probe is what makes the auto-disable decision honest.
976
- * @module dsh-github-copilot/probe
995
+ * Provider-side GitHub Copilot auth resolution over the same credential record
996
+ * that DSH's built-in llm-pi-ai adapter owns.
977
997
  */
978
- /** Attribution header value sent on probe requests; single-sourced from package.json. */
979
- const USER_AGENT = `dsh-github-copilot/${version}`;
980
- /** Upper bound on generated tokens for a probe reply; the verdict needs none of it. */
981
- const PROBE_MAX_TOKENS = 64;
982
- /** Complete spelling rounds allowed after structurally valid transient replies. */
983
- const RESPONSES_PROBE_ROUNDS = 2;
984
- /** Upper bound on the whole probe (also the fetch signal bound), milliseconds. */
985
- const MAX_PROBE_TIMEOUT_MS = 2147483647;
998
+ function credentialService(ctx) {
999
+ const candidate = ctx.get("credentials");
1000
+ if (typeof candidate !== "object" || candidate === null) throw new Error("github-copilot: DSH credentials service is unavailable");
1001
+ for (const method of [
1002
+ "readRecord",
1003
+ "listRecords",
1004
+ "modifyRecord",
1005
+ "deleteRecord"
1006
+ ]) if (typeof Reflect.get(candidate, method) !== "function") throw new Error(`github-copilot: required DSH credentials API "credentials.${method}" is unavailable`);
1007
+ return candidate;
1008
+ }
1009
+ function toCredential(record) {
1010
+ if (record === void 0) return void 0;
1011
+ if (record.kind === "api-key") return {
1012
+ type: "api_key",
1013
+ ...record.key === void 0 ? {} : { key: record.key },
1014
+ ...record.env === void 0 ? {} : { env: { ...record.env } }
1015
+ };
1016
+ if (typeof record.payload !== "object" || record.payload === null) throw new Error("github-copilot: stored llm-pi-ai GitHub Copilot grant is not an object");
1017
+ return normalizeGitHubCopilotOAuthCredential(record.payload);
1018
+ }
1019
+ function toRecord(credential) {
1020
+ if (credential.type === "api_key") return {
1021
+ kind: "api-key",
1022
+ ...credential.key === void 0 ? {} : { key: credential.key },
1023
+ ...credential.env === void 0 ? {} : { env: { ...credential.env } }
1024
+ };
1025
+ return {
1026
+ kind: "grant",
1027
+ payload: normalizeGitHubCopilotOAuthCredential(credential)
1028
+ };
1029
+ }
1030
+ /** Bind exactly one logical route to the canonical Host-owned credential record. */
1031
+ function createGitHubCopilotCredentialStore(ctx, logicalProviderId = GITHUB_COPILOT_PROVIDER_ID) {
1032
+ const assertProvider = (providerId) => {
1033
+ if (providerId !== logicalProviderId) throw new Error("COPILOT_CREDENTIAL_PROVIDER_MISMATCH");
1034
+ };
1035
+ return {
1036
+ read: async (providerId) => {
1037
+ assertProvider(providerId);
1038
+ return toCredential(await credentialService(ctx).readRecord(GITHUB_COPILOT_CREDENTIAL_KEY));
1039
+ },
1040
+ list: async () => {
1041
+ const record = await credentialService(ctx).readRecord(GITHUB_COPILOT_CREDENTIAL_KEY);
1042
+ return record === void 0 ? [] : [{
1043
+ providerId: logicalProviderId,
1044
+ type: record.kind === "api-key" ? "api_key" : "oauth"
1045
+ }];
1046
+ },
1047
+ modify: async (providerId, mutate) => {
1048
+ assertProvider(providerId);
1049
+ return toCredential(await credentialService(ctx).modifyRecord(GITHUB_COPILOT_CREDENTIAL_KEY, async (current) => {
1050
+ const next = await mutate(toCredential(current));
1051
+ return next === void 0 ? void 0 : toRecord(next);
1052
+ }));
1053
+ },
1054
+ delete: async (providerId) => {
1055
+ assertProvider(providerId);
1056
+ await credentialService(ctx).deleteRecord(GITHUB_COPILOT_CREDENTIAL_KEY);
1057
+ }
1058
+ };
1059
+ }
986
1060
  /**
987
- * Probe one candidate protocol with a bounded request sequence. The verdict is
988
- * structural: a Responses reply must contain a `web_search_call` item and a
989
- * Messages reply must contain a `web_search_tool_result` block; anything else
990
- * — HTTP error, unparseable body, silent tool ignore — is "not supported".
991
- * The timeout bounds the WHOLE probe, including credential resolution; the
992
- * probe never throws — every failure becomes a verdict.
993
- * @param candidate - the resolved endpoint facts to verify.
994
- * @param resolveApiKey - resolves the candidate's credential reference.
995
- * @param timeoutMs - bound on the whole probe.
996
- * @param signal - optional owner lifecycle cancellation; abort forbids later attempts.
997
- * @returns the verdict and a diagnostic detail.
1061
+ * Build one resolver whose pi-ai collection refreshes OAuth credentials inside
1062
+ * the DSH record's serialized modify operation before exposing request auth.
998
1063
  */
999
- async function probeCandidate(candidate, resolveApiKey, timeoutMs, signal) {
1000
- const aborted = () => ({
1001
- supported: false,
1002
- detail: "probe was aborted"
1064
+ function createGitHubCopilotTokenResolver(ctx, onCredentialChanged) {
1065
+ const models = createModels({ credentials: createGitHubCopilotCredentialStore(ctx) });
1066
+ const provider = githubCopilotProvider();
1067
+ if (provider.auth.oauth === void 0) throw new Error("github-copilot: native OAuth method is unavailable");
1068
+ models.setProvider({
1069
+ ...provider,
1070
+ auth: { oauth: provider.auth.oauth }
1003
1071
  });
1004
- if (signal?.aborted === true) return aborted();
1005
- const bound = Math.min(Math.max(Math.trunc(timeoutMs), 1), MAX_PROBE_TIMEOUT_MS);
1006
- const deadline = Date.now() + bound;
1007
- const cancellation = new AbortController();
1008
- let timer;
1009
- let onAbort;
1010
- const cancelled = new Promise((resolve) => {
1011
- onAbort = () => {
1012
- cancellation.abort();
1013
- resolve(aborted());
1072
+ return async (modelId) => {
1073
+ const snapshot = readCopilotCatalog(ctx);
1074
+ const native = new Map(snapshot.models.map((model) => [model.id, model])).get(modelId);
1075
+ if (native === void 0) throw new Error(`github-copilot: pi-ai catalog has no GitHub Copilot model "${modelId}"`);
1076
+ const resolved = await models.getAuth("github-copilot");
1077
+ if (resolved === void 0) return void 0;
1078
+ if (onCredentialChanged !== void 0) try {
1079
+ await onCredentialChanged();
1080
+ } catch (error) {
1081
+ ctx.logger.warn("github-copilot: provider route reconciliation will retry after a later auth resolution");
1082
+ ctx.logger.warn(error);
1083
+ }
1084
+ const stored = await credentialService(ctx).readRecord(GITHUB_COPILOT_CREDENTIAL_KEY);
1085
+ if (!(stored?.kind === "grant" && (normalizeGitHubCopilotOAuthCredential(stored.payload).availableModelIds ?? []).includes(modelId))) throw new Error(`github-copilot: model "${modelId}" is not available for the signed-in Copilot account`);
1086
+ const apiKey = resolved.auth.apiKey;
1087
+ if (apiKey === void 0) return void 0;
1088
+ return {
1089
+ apiKey,
1090
+ baseURL: trustedCopilotBaseUrl(resolved.auth.baseUrl ?? native.baseUrl, enterpriseDomainOf(stored)),
1091
+ ...resolved.auth.headers === void 0 ? {} : { headers: resolved.auth.headers }
1014
1092
  };
1015
- signal?.addEventListener("abort", onAbort, { once: true });
1016
- });
1017
- try {
1018
- return await Promise.race([
1019
- cancelled,
1020
- runProbe(candidate, resolveApiKey, bound, deadline, cancellation.signal),
1021
- new Promise((resolve) => {
1022
- timer = setTimeout(() => {
1023
- cancellation.abort();
1024
- resolve({
1025
- supported: false,
1026
- detail: `probe timed out after ${bound}ms`
1027
- });
1028
- }, bound);
1029
- })
1030
- ]);
1031
- } finally {
1032
- clearTimeout(timer);
1033
- if (onAbort !== void 0) signal?.removeEventListener("abort", onAbort);
1034
- }
1093
+ };
1035
1094
  }
1036
- /** Resolve the key and probe the endpoint under the shared whole-probe deadline. */
1037
- async function runProbe(candidate, resolveApiKey, bound, deadline, cancellation) {
1095
+ function enterpriseDomainOf(record) {
1096
+ if (record?.kind !== "grant" || typeof record.payload !== "object" || record.payload === null) return void 0;
1097
+ const value = Reflect.get(record.payload, "enterpriseUrl");
1098
+ if (typeof value !== "string" || value.trim().length === 0) return void 0;
1038
1099
  try {
1039
- const auth = normalizeRequestAuth(await resolveApiKey(candidate));
1040
- if (auth === void 0 || auth.apiKey.length === 0) return {
1041
- supported: false,
1042
- detail: `no API key for "${candidate.apiKeyEnv}"`
1043
- };
1044
- candidate = applyRequestAuth(candidate, auth);
1045
- const apiKey = auth.apiKey;
1046
- const remaining = deadline - Date.now();
1047
- if (cancellation.aborted || remaining <= 0) return {
1048
- supported: false,
1049
- detail: `probe timed out after ${bound}ms`
1050
- };
1051
- return candidate.protocol === "openai-responses" ? await probeResponses(candidate, apiKey, cancellation) : await probeAnthropic(candidate, apiKey, cancellation);
1052
- } catch (error) {
1053
- if (error instanceof WebError) return {
1054
- supported: false,
1055
- detail: error.message
1056
- };
1057
- return {
1058
- supported: false,
1059
- detail: `probe failed: ${String(error)}`
1060
- };
1100
+ return (value.includes("://") ? new URL(value) : new URL(`https://${value}`)).hostname.toLowerCase();
1101
+ } catch {
1102
+ throw new Error("github-copilot: stored enterpriseUrl is invalid");
1061
1103
  }
1062
1104
  }
1063
- /**
1064
- * Probe the Responses endpoint with a single tool spelling: force the tool
1065
- * (standard `required` or the gateway's object choice) and require a
1066
- * `web_search_call` item as structural evidence.
1067
- * @param candidate - the resolved endpoint facts.
1068
- * @param apiKey - the resolved credential.
1069
- * @param signal - the probe timeout signal.
1070
- * @param spelling - the tool type spelling to exercise.
1071
- * @returns the verdict; `transient` is true only for a valid 2xx reply that
1072
- * omitted the required structural evidence.
1073
- */
1074
- async function probeResponsesSpelling(candidate, apiKey, signal, spelling) {
1075
- const endpoint = `${candidate.baseURL.replace(/\/+$/, "")}/responses`;
1076
- let response;
1077
- try {
1078
- response = await fetch(endpoint, {
1079
- method: "POST",
1080
- redirect: "error",
1081
- headers: {
1082
- authorization: `Bearer ${apiKey}`,
1083
- "content-type": "application/json",
1084
- accept: "application/json",
1085
- "user-agent": USER_AGENT,
1086
- ...providerRequestHeaders(candidate, "user")
1087
- },
1105
+ /** Validate an OAuth-derived endpoint without disclosing its value or credential payload. */
1106
+ function trustedGitHubCopilotBaseUrl(value, grant) {
1107
+ return trustedCopilotBaseUrl(value, enterpriseDomainOf({
1108
+ kind: "grant",
1109
+ payload: normalizeGitHubCopilotOAuthCredential(grant)
1110
+ }));
1111
+ }
1112
+ function trustedCopilotBaseUrl(value, enterpriseDomain) {
1113
+ if (value === void 0) throw new Error("github-copilot: pi-ai resolved no Copilot API base URL");
1114
+ const url = new URL(value);
1115
+ const hostname = url.hostname.toLowerCase();
1116
+ const githubHosted = /^api(?:\.[a-z0-9-]+)+\.githubcopilot\.com$/u.test(hostname);
1117
+ const enterpriseHosted = enterpriseDomain !== void 0 && hostname === `copilot-api.${enterpriseDomain}`;
1118
+ if (!(url.protocol === "https:" && (githubHosted || enterpriseHosted)) || url.username.length > 0 || url.password.length > 0) throw new Error("github-copilot: pi-ai resolved an untrusted Copilot API base URL");
1119
+ return url.origin;
1120
+ }
1121
+ //#endregion
1122
+ //#region lib/types/account-model-auth.js
1123
+ /** Native getAuth renews within five minutes; reserve another thirty seconds for metadata preparation. */
1124
+ const ACCOUNT_MODEL_AUTH_MIN_VALIDITY_MS = 33e4;
1125
+ /** An opaque, process-local account key. Never a substitute for request credentials. */
1126
+ function copilotAccountKey(grant) {
1127
+ return createHash("sha256").update(`${grant.refresh.length}:`).update(grant.refresh).update(grant.enterpriseUrl ?? "").digest("hex");
1128
+ }
1129
+ /** Compare normalized permission snapshots without retaining model credentials. */
1130
+ function copilotEntitlementKey(grant) {
1131
+ const ids = grant.availableModelIds;
1132
+ const normalized = ids === void 0 ? "missing" : JSON.stringify([...new Set(ids)].sort());
1133
+ return createHash("sha256").update(normalized).digest("hex");
1134
+ }
1135
+ function authEntitlementKey(auth) {
1136
+ return copilotEntitlementKey({ ...auth.availableModelIds === void 0 ? {} : { availableModelIds: [...auth.availableModelIds] } });
1137
+ }
1138
+ function active(signal) {
1139
+ if (signal.aborted) throw new Error("COPILOT_ACCOUNT_AUTH_ABORTED");
1140
+ }
1141
+ /**
1142
+ * Bind model discovery to the existing native OAuth lifecycle. No model catalog,
1143
+ * login, environment fallback or separate credential record is introduced here.
1144
+ */
1145
+ function createAccountModelAuth(credentials, renewRejectedCredential) {
1146
+ const native = githubCopilotProvider();
1147
+ const oauth = native.auth.oauth;
1148
+ if (oauth === void 0) throw new Error("COPILOT_ACCOUNT_OAUTH_UNAVAILABLE");
1149
+ const readGrant = async (signal) => {
1150
+ active(signal);
1151
+ const stored = await credentials.read(GITHUB_COPILOT_PROVIDER_ID);
1152
+ active(signal);
1153
+ if (stored?.type !== "oauth") throw new Error("COPILOT_ACCOUNT_OAUTH_REQUIRED");
1154
+ return normalizeGitHubCopilotOAuthCredential(stored);
1155
+ };
1156
+ const matches = (grant, key) => {
1157
+ if (copilotAccountKey(grant) !== key) throw new Error("COPILOT_ACCOUNT_CHANGED");
1158
+ };
1159
+ const current = async (auth, signal) => {
1160
+ active(signal);
1161
+ const grant = await readGrant(signal);
1162
+ matches(grant, auth.accountKey);
1163
+ if (grant.access !== auth.apiKey || grant.expires <= Date.now()) throw new Error("COPILOT_ACCOUNT_TOKEN_CHANGED");
1164
+ if (copilotEntitlementKey(grant) !== authEntitlementKey(auth)) throw new Error("COPILOT_ACCOUNT_ENTITLEMENT_CHANGED");
1165
+ const derived = await oauth.toAuth(grant);
1166
+ active(signal);
1167
+ if (derived.apiKey !== auth.apiKey || trustedGitHubCopilotBaseUrl(derived.baseUrl, grant) !== auth.baseURL) throw new Error("COPILOT_ACCOUNT_AUTH_CHANGED");
1168
+ const latest = await readGrant(signal);
1169
+ matches(latest, auth.accountKey);
1170
+ if (latest.access !== auth.apiKey || latest.expires <= Date.now()) throw new Error("COPILOT_ACCOUNT_TOKEN_CHANGED");
1171
+ if (copilotEntitlementKey(latest) !== authEntitlementKey(auth)) throw new Error("COPILOT_ACCOUNT_ENTITLEMENT_CHANGED");
1172
+ };
1173
+ return {
1174
+ async resolveAuth(signal) {
1175
+ try {
1176
+ const before = await readGrant(signal);
1177
+ const key = copilotAccountKey(before);
1178
+ const rejectedAccess = renewRejectedCredential?.(before) === true ? before.access : void 0;
1179
+ const forResolution = (grant) => rejectedAccess !== void 0 && grant.access === rejectedAccess ? {
1180
+ ...grant,
1181
+ expires: 0
1182
+ } : grant;
1183
+ const guardedOAuth = {
1184
+ ...oauth,
1185
+ login: async () => {
1186
+ throw new Error("COPILOT_ACCOUNT_USE_CANONICAL_SIGN_IN");
1187
+ },
1188
+ async refresh(credential, requestSignal) {
1189
+ active(signal);
1190
+ matches(normalizeGitHubCopilotOAuthCredential(credential), key);
1191
+ const result = normalizeGitHubCopilotOAuthCredential(await oauth.refresh(credential, requestSignal === void 0 ? signal : AbortSignal.any([signal, requestSignal])));
1192
+ active(signal);
1193
+ matches(result, key);
1194
+ return result;
1195
+ },
1196
+ async toAuth(credential) {
1197
+ active(signal);
1198
+ const grant = normalizeGitHubCopilotOAuthCredential(credential);
1199
+ matches(grant, key);
1200
+ const result = await oauth.toAuth(grant);
1201
+ active(signal);
1202
+ return {
1203
+ ...result,
1204
+ baseUrl: trustedGitHubCopilotBaseUrl(result.baseUrl, grant)
1205
+ };
1206
+ }
1207
+ };
1208
+ const models = createModels({ credentials: {
1209
+ read: async (provider) => {
1210
+ const value = await credentials.read(provider);
1211
+ active(signal);
1212
+ if (value?.type !== "oauth") throw new Error("COPILOT_ACCOUNT_OAUTH_REQUIRED");
1213
+ const grant = normalizeGitHubCopilotOAuthCredential(value);
1214
+ matches(grant, key);
1215
+ return forResolution(grant);
1216
+ },
1217
+ list: () => credentials.list(),
1218
+ modify: (provider, mutate) => credentials.modify(provider, async (value) => {
1219
+ active(signal);
1220
+ if (value?.type !== "oauth") throw new Error("COPILOT_ACCOUNT_OAUTH_REQUIRED");
1221
+ matches(normalizeGitHubCopilotOAuthCredential(value), key);
1222
+ const next = await mutate(forResolution(normalizeGitHubCopilotOAuthCredential(value)));
1223
+ active(signal);
1224
+ if (next !== void 0) matches(normalizeGitHubCopilotOAuthCredential(next), key);
1225
+ return next;
1226
+ }),
1227
+ delete: async () => {
1228
+ throw new Error("COPILOT_ACCOUNT_DISCOVERY_CANNOT_SIGN_OUT");
1229
+ }
1230
+ } });
1231
+ models.setProvider({
1232
+ ...native,
1233
+ auth: { oauth: guardedOAuth }
1234
+ });
1235
+ const resolved = await models.getAuth(GITHUB_COPILOT_PROVIDER_ID, {
1236
+ signal,
1237
+ minOAuthValidityMs: ACCOUNT_MODEL_AUTH_MIN_VALIDITY_MS
1238
+ });
1239
+ active(signal);
1240
+ if (resolved?.auth.apiKey === void 0) throw new Error("COPILOT_ACCOUNT_OAUTH_REQUIRED");
1241
+ const grant = await readGrant(signal);
1242
+ matches(grant, key);
1243
+ if (rejectedAccess !== void 0 && grant.access === rejectedAccess) throw new Error("COPILOT_ACCOUNT_TOKEN_REJECTED");
1244
+ const auth = {
1245
+ apiKey: resolved.auth.apiKey,
1246
+ baseURL: trustedGitHubCopilotBaseUrl(resolved.auth.baseUrl, grant),
1247
+ accountKey: key,
1248
+ ...grant.availableModelIds === void 0 ? {} : { availableModelIds: Object.freeze([...grant.availableModelIds]) }
1249
+ };
1250
+ await current(auth, signal);
1251
+ return Object.freeze(auth);
1252
+ } catch {
1253
+ throw new Error(signal.aborted ? "COPILOT_ACCOUNT_AUTH_ABORTED" : "COPILOT_ACCOUNT_AUTH_FAILED");
1254
+ }
1255
+ },
1256
+ async assertAuthCurrent(auth, signal) {
1257
+ try {
1258
+ await current(auth, signal);
1259
+ } catch {
1260
+ throw new Error(signal.aborted ? "COPILOT_ACCOUNT_AUTH_ABORTED" : "COPILOT_ACCOUNT_AUTH_CHANGED");
1261
+ }
1262
+ }
1263
+ };
1264
+ }
1265
+ //#endregion
1266
+ //#region lib/types/copilot-usage-types.js
1267
+ const COPILOT_USAGE_MAX_AMOUNT = 0xe8d4a51000;
1268
+ const COPILOT_USAGE_MAX_TIMESTAMP = 0xe677d21fdbff;
1269
+ //#endregion
1270
+ //#region lib/types/copilot-usage-normalize.js
1271
+ const amount = z$1.number().finite().min(0).max(COPILOT_USAGE_MAX_AMOUNT);
1272
+ const entitlement = z$1.union([
1273
+ amount,
1274
+ z$1.literal(-1),
1275
+ z$1.string().max(32).regex(/^(?:-1|(?:0|[1-9]\d*)(?:\.\d+)?)$/u)
1276
+ ]).transform(Number).refine((value) => Number.isFinite(value) && value <= 0xe8d4a51000);
1277
+ const snapshot = z$1.object({
1278
+ unlimited: z$1.boolean(),
1279
+ entitlement: entitlement.optional(),
1280
+ percent_remaining: z$1.number().finite().min(0).max(100).optional(),
1281
+ quota_remaining: amount.optional(),
1282
+ credits_used: amount.optional(),
1283
+ has_quota: z$1.boolean().optional(),
1284
+ token_based_billing: z$1.boolean().optional(),
1285
+ quota_reset_at: z$1.number().int().min(0).max(Math.floor(COPILOT_USAGE_MAX_TIMESTAMP / 1e3)).optional()
1286
+ });
1287
+ const response = z$1.object({
1288
+ token_based_billing: z$1.boolean().optional(),
1289
+ access_type_sku: z$1.string().max(128).optional(),
1290
+ quota_reset_date: z$1.string().max(64).optional(),
1291
+ quota_reset_date_utc: z$1.string().max(64).optional(),
1292
+ limited_user_reset_date: z$1.string().max(64).optional(),
1293
+ quota_snapshots: z$1.object({
1294
+ chat: snapshot.optional(),
1295
+ premium_interactions: snapshot.optional()
1296
+ }).optional(),
1297
+ monthly_quotas: z$1.object({ chat: amount }).optional(),
1298
+ limited_user_quotas: z$1.object({ chat: amount }).optional()
1299
+ });
1300
+ function unavailableCopilotUsage(diagnostic, billing = "unknown", budget = "unknown") {
1301
+ return {
1302
+ state: diagnostic === "COPILOT_USAGE_SIGNED_OUT" ? "signed-out" : "unavailable",
1303
+ billing,
1304
+ budget,
1305
+ diagnostic
1306
+ };
1307
+ }
1308
+ function resetDate(value) {
1309
+ if (!/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2}))?$/u.test(value)) return void 0;
1310
+ const date = value.slice(0, 10);
1311
+ const day = Date.parse(`${date}T00:00:00Z`);
1312
+ if (!Number.isFinite(day) || new Date(day).toISOString().slice(0, 10) !== date) return void 0;
1313
+ const parsed = Date.parse(value);
1314
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 0xe677d21fdbff ? parsed : void 0;
1315
+ }
1316
+ /**
1317
+ * Pinned VS Code 44825207: parseQuotas/getQuotaUsage and chatStatusDashboard
1318
+ * display account amounts directly. Only per-turn nano-AIU uses /1e9; it is
1319
+ * intentionally absent here. Pooled credits_used never shares an entitlement.
1320
+ */
1321
+ function normalizeCopilotUsage(input, observedAt) {
1322
+ const parsed = response.safeParse(input);
1323
+ if (!parsed.success) return unavailableCopilotUsage("COPILOT_USAGE_INVALID_RESPONSE");
1324
+ const data = parsed.data;
1325
+ if (data.token_based_billing === void 0) return unavailableCopilotUsage("COPILOT_USAGE_BILLING_UNKNOWN");
1326
+ const billing = data.token_based_billing ? "credits" : "requests";
1327
+ const quota = data.access_type_sku === "free_limited_copilot" ? data.quota_snapshots?.chat : data.quota_snapshots?.premium_interactions;
1328
+ const reset = data.quota_reset_date_utc ?? data.quota_reset_date ?? data.limited_user_reset_date;
1329
+ const accountReset = reset === void 0 ? void 0 : resetDate(reset);
1330
+ if (reset !== void 0 && accountReset === void 0) return unavailableCopilotUsage("COPILOT_USAGE_INVALID_RESPONSE", billing);
1331
+ const resetAt = quota?.quota_reset_at === void 0 ? accountReset : quota.quota_reset_at * 1e3;
1332
+ const dates = {
1333
+ observedAt,
1334
+ ...resetAt === void 0 ? {} : { resetAt }
1335
+ };
1336
+ if (!quota) {
1337
+ const limit = data.monthly_quotas?.chat, remaining = data.limited_user_quotas?.chat;
1338
+ if (billing === "requests" && limit !== void 0 && limit > 0 && remaining !== void 0 && remaining <= limit) return {
1339
+ state: "ready",
1340
+ billing,
1341
+ budget: "individual",
1342
+ limit,
1343
+ remaining,
1344
+ used: limit - remaining,
1345
+ percentUsed: (limit - remaining) / limit * 100,
1346
+ ...dates
1347
+ };
1348
+ return unavailableCopilotUsage("COPILOT_USAGE_SNAPSHOT_MISSING", billing);
1349
+ }
1350
+ if (quota.token_based_billing !== void 0 && quota.token_based_billing !== data.token_based_billing) return unavailableCopilotUsage("COPILOT_USAGE_INVALID_RESPONSE", billing);
1351
+ if (quota.unlimited) {
1352
+ if (quota.has_quota === false) return unavailableCopilotUsage("COPILOT_USAGE_POOLED_EXHAUSTED", billing, "pooled");
1353
+ if (billing !== "credits" || quota.credits_used === void 0) return unavailableCopilotUsage("COPILOT_USAGE_POOLED_UNAVAILABLE", billing, "pooled");
1354
+ return {
1355
+ state: "ready",
1356
+ billing,
1357
+ budget: "pooled",
1358
+ used: quota.credits_used,
1359
+ ...dates
1360
+ };
1361
+ }
1362
+ const limit = quota.entitlement, percent = quota.percent_remaining;
1363
+ if (limit === 0) return unavailableCopilotUsage("COPILOT_USAGE_NO_ALLOCATION", billing, "individual");
1364
+ if (limit !== void 0 && limit < 0 || percent === void 0 || limit !== void 0 && quota.quota_remaining !== void 0 && quota.quota_remaining > limit) return unavailableCopilotUsage("COPILOT_USAGE_INVALID_RESPONSE", billing);
1365
+ if (limit !== void 0 && quota.quota_remaining !== void 0 && Math.abs(quota.quota_remaining / limit * 100 - percent) > 1) return unavailableCopilotUsage("COPILOT_USAGE_INVALID_RESPONSE", billing);
1366
+ const remaining = quota.quota_remaining;
1367
+ return {
1368
+ state: "ready",
1369
+ billing,
1370
+ budget: "individual",
1371
+ percentUsed: 100 - percent,
1372
+ ...dates,
1373
+ ...limit === void 0 ? {} : { limit },
1374
+ ...remaining === void 0 ? {} : { remaining },
1375
+ ...limit === void 0 || remaining === void 0 ? {} : { used: limit - remaining }
1376
+ };
1377
+ }
1378
+ //#endregion
1379
+ //#region lib/types/copilot-usage-host.js
1380
+ var __runInitializers$1 = function(thisArg, initializers, value) {
1381
+ var useValue = arguments.length > 2;
1382
+ for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
1383
+ return useValue ? value : void 0;
1384
+ };
1385
+ var __esDecorate$1 = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
1386
+ function accept(f) {
1387
+ if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
1388
+ return f;
1389
+ }
1390
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
1391
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
1392
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
1393
+ var _, done = false;
1394
+ for (var i = decorators.length - 1; i >= 0; i--) {
1395
+ var context = {};
1396
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
1397
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
1398
+ context.addInitializer = function(f) {
1399
+ if (done) throw new TypeError("Cannot add initializers after decoration has completed");
1400
+ extraInitializers.push(accept(f || null));
1401
+ };
1402
+ var result = (0, decorators[i])(kind === "accessor" ? {
1403
+ get: descriptor.get,
1404
+ set: descriptor.set
1405
+ } : descriptor[key], context);
1406
+ if (kind === "accessor") {
1407
+ if (result === void 0) continue;
1408
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
1409
+ if (_ = accept(result.get)) descriptor.get = _;
1410
+ if (_ = accept(result.set)) descriptor.set = _;
1411
+ if (_ = accept(result.init)) initializers.unshift(_);
1412
+ } else if (_ = accept(result)) {
1413
+ if (kind === "field") initializers.unshift(_);
1414
+ else descriptor[key] = _;
1415
+ }
1416
+ }
1417
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
1418
+ done = true;
1419
+ };
1420
+ const COPILOT_USAGE_ENDPOINT = "https://api.github.com/copilot_internal/user";
1421
+ const TTL_MS = 6e4;
1422
+ const REFRESH_INTERVAL_MS = 1e4;
1423
+ const FAILURE_COOLDOWN_MS = 3e4;
1424
+ const TIMEOUT_MS = 1e4;
1425
+ const MAX_BODY_BYTES = 262144;
1426
+ var UsageFailure = class extends Error {
1427
+ diagnostic;
1428
+ constructor(diagnostic) {
1429
+ super(diagnostic);
1430
+ this.diagnostic = diagnostic;
1431
+ }
1432
+ };
1433
+ function failure$2(diagnostic) {
1434
+ throw new UsageFailure(diagnostic);
1435
+ }
1436
+ async function readBody(response, signal) {
1437
+ const length = response.headers.get("content-length");
1438
+ if (length !== null && (!/^\d+$/u.test(length) || Number(length) > MAX_BODY_BYTES)) {
1439
+ response.body?.cancel().catch(() => void 0);
1440
+ failure$2("COPILOT_USAGE_BODY_TOO_LARGE");
1441
+ }
1442
+ if (!response.body) failure$2("COPILOT_USAGE_INVALID_RESPONSE");
1443
+ const reader = response.body.getReader(), chunks = [];
1444
+ const cancel = () => {
1445
+ reader.cancel().catch(() => void 0);
1446
+ };
1447
+ signal.addEventListener("abort", cancel, { once: true });
1448
+ if (signal.aborted) cancel();
1449
+ let size = 0;
1450
+ try {
1451
+ while (true) {
1452
+ const next = await reader.read();
1453
+ if (next.done) break;
1454
+ size += next.value.byteLength;
1455
+ if (size > MAX_BODY_BYTES) {
1456
+ reader.cancel().catch(() => void 0);
1457
+ failure$2("COPILOT_USAGE_BODY_TOO_LARGE");
1458
+ }
1459
+ chunks.push(next.value);
1460
+ }
1461
+ } finally {
1462
+ signal.removeEventListener("abort", cancel);
1463
+ reader.releaseLock();
1464
+ }
1465
+ const bytes = new Uint8Array(size);
1466
+ let offset = 0;
1467
+ for (const chunk of chunks) {
1468
+ bytes.set(chunk, offset);
1469
+ offset += chunk.byteLength;
1470
+ }
1471
+ try {
1472
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
1473
+ } catch {
1474
+ failure$2("COPILOT_USAGE_INVALID_RESPONSE");
1475
+ }
1476
+ }
1477
+ /** One Host-owned, memory-only account cache. No auth renewal, persistence or model transport. */
1478
+ var CopilotUsageSource = class {
1479
+ dependencies;
1480
+ fetcher;
1481
+ now;
1482
+ generation = 0;
1483
+ disposed = false;
1484
+ key;
1485
+ cached;
1486
+ last;
1487
+ nextRefreshAt = 0;
1488
+ failureUntil = 0;
1489
+ flight;
1490
+ constructor(dependencies) {
1491
+ this.dependencies = dependencies;
1492
+ this.fetcher = dependencies.fetch ?? globalThis.fetch;
1493
+ this.now = dependencies.now ?? Date.now;
1494
+ }
1495
+ invalidate() {
1496
+ this.generation++;
1497
+ this.flight?.abort.abort();
1498
+ this.flight = void 0;
1499
+ this.key = void 0;
1500
+ this.cached = void 0;
1501
+ this.last = void 0;
1502
+ this.nextRefreshAt = 0;
1503
+ this.failureUntil = 0;
1504
+ }
1505
+ dispose() {
1506
+ this.disposed = true;
1507
+ this.invalidate();
1508
+ }
1509
+ get() {
1510
+ return this.load(false);
1511
+ }
1512
+ refresh() {
1513
+ return this.load(true);
1514
+ }
1515
+ active(generation) {
1516
+ if (this.disposed) failure$2("COPILOT_USAGE_DISPOSED");
1517
+ if (generation !== this.generation) failure$2("COPILOT_USAGE_ACCOUNT_CHANGED");
1518
+ }
1519
+ async readAuth(generation) {
1520
+ this.active(generation);
1521
+ let stored;
1522
+ try {
1523
+ stored = await this.dependencies.readCredential();
1524
+ } catch {
1525
+ this.active(generation);
1526
+ failure$2("COPILOT_USAGE_CREDENTIALS_UNAVAILABLE");
1527
+ }
1528
+ this.active(generation);
1529
+ if (stored === void 0) failure$2("COPILOT_USAGE_SIGNED_OUT");
1530
+ let grant;
1531
+ try {
1532
+ grant = normalizeGitHubCopilotOAuthCredential(stored);
1533
+ } catch {
1534
+ failure$2("COPILOT_USAGE_INVALID_GRANT");
1535
+ }
1536
+ if (grant.enterpriseUrl !== void 0) failure$2("COPILOT_USAGE_ENTERPRISE_UNSUPPORTED");
1537
+ return {
1538
+ grant,
1539
+ key: copilotAccountKey(grant)
1540
+ };
1541
+ }
1542
+ async assertCurrent(key, generation) {
1543
+ if ((await this.readAuth(generation)).key !== key) failure$2("COPILOT_USAGE_ACCOUNT_CHANGED");
1544
+ }
1545
+ diagnostic(error) {
1546
+ return error instanceof UsageFailure ? error.diagnostic : "COPILOT_USAGE_NETWORK";
1547
+ }
1548
+ async load(force) {
1549
+ let generation = this.generation;
1550
+ let auth;
1551
+ try {
1552
+ auth = await this.readAuth(generation);
1553
+ this.active(generation);
1554
+ } catch (error) {
1555
+ if (generation === this.generation) this.invalidate();
1556
+ return unavailableCopilotUsage(this.diagnostic(error));
1557
+ }
1558
+ if (this.key !== void 0 && this.key !== auth.key) {
1559
+ this.invalidate();
1560
+ generation = this.generation;
1561
+ }
1562
+ this.key = auth.key;
1563
+ if (this.flight) {
1564
+ const result = await this.flight.promise;
1565
+ if (this.disposed) return unavailableCopilotUsage("COPILOT_USAGE_DISPOSED");
1566
+ if (generation !== this.generation) return unavailableCopilotUsage("COPILOT_USAGE_ACCOUNT_CHANGED");
1567
+ return { ...result };
1568
+ }
1569
+ const now = this.now();
1570
+ if (this.last && now < this.failureUntil) return { ...this.last };
1571
+ if (this.cached && (!force && now - this.cached.observedAt < TTL_MS || now < this.nextRefreshAt)) return { ...this.cached };
1572
+ const abort = new AbortController();
1573
+ const promise = this.request(auth, generation, abort);
1574
+ this.flight = {
1575
+ generation,
1576
+ promise,
1577
+ abort
1578
+ };
1579
+ try {
1580
+ return { ...await promise };
1581
+ } finally {
1582
+ if (this.flight?.promise === promise) this.flight = void 0;
1583
+ }
1584
+ }
1585
+ async fetchQuota(auth, abort) {
1586
+ const response = await this.fetcher(COPILOT_USAGE_ENDPOINT, {
1587
+ method: "GET",
1588
+ redirect: "error",
1589
+ signal: abort.signal,
1590
+ cache: "no-store",
1591
+ headers: {
1592
+ Authorization: `token ${auth.grant.refresh}`,
1593
+ Accept: "application/json",
1594
+ "X-GitHub-Api-Version": "2025-04-01"
1595
+ }
1596
+ });
1597
+ if (response.redirected || response.status >= 300 && response.status < 400) {
1598
+ response.body?.cancel().catch(() => void 0);
1599
+ failure$2("COPILOT_USAGE_REDIRECT");
1600
+ }
1601
+ if (!response.ok) {
1602
+ response.body?.cancel().catch(() => void 0);
1603
+ if (response.status === 401 || response.status === 403) failure$2("COPILOT_USAGE_AUTH_REJECTED");
1604
+ if (response.status === 429) failure$2("COPILOT_USAGE_RATE_LIMITED");
1605
+ failure$2("COPILOT_USAGE_HTTP_ERROR");
1606
+ }
1607
+ return readBody(response, abort.signal);
1608
+ }
1609
+ async request(auth, generation, abort) {
1610
+ let timer;
1611
+ let removeAbort;
1612
+ let value;
1613
+ let error;
1614
+ try {
1615
+ await this.assertCurrent(auth.key, generation);
1616
+ this.active(generation);
1617
+ this.nextRefreshAt = this.now() + REFRESH_INTERVAL_MS;
1618
+ const cancellation = new Promise((_resolve, reject) => {
1619
+ const cancel = () => reject(new UsageFailure(this.disposed ? "COPILOT_USAGE_DISPOSED" : "COPILOT_USAGE_ACCOUNT_CHANGED"));
1620
+ abort.signal.addEventListener("abort", cancel, { once: true });
1621
+ removeAbort = () => abort.signal.removeEventListener("abort", cancel);
1622
+ timer = setTimeout(() => {
1623
+ reject(new UsageFailure("COPILOT_USAGE_TIMEOUT"));
1624
+ abort.abort();
1625
+ }, TIMEOUT_MS);
1626
+ });
1627
+ value = normalizeCopilotUsage(await Promise.race([this.fetchQuota(auth, abort), cancellation]), this.now());
1628
+ } catch (caught) {
1629
+ error = caught;
1630
+ } finally {
1631
+ if (timer !== void 0) clearTimeout(timer);
1632
+ removeAbort?.();
1633
+ }
1634
+ try {
1635
+ await this.assertCurrent(auth.key, generation);
1636
+ this.active(generation);
1637
+ } catch (caught) {
1638
+ if (generation === this.generation) this.invalidate();
1639
+ return unavailableCopilotUsage(this.diagnostic(caught));
1640
+ }
1641
+ if (error !== void 0) {
1642
+ const diagnostic = this.diagnostic(error);
1643
+ if (diagnostic === "COPILOT_USAGE_ACCOUNT_CHANGED" || diagnostic === "COPILOT_USAGE_SIGNED_OUT" || diagnostic === "COPILOT_USAGE_INVALID_GRANT" || diagnostic === "COPILOT_USAGE_ENTERPRISE_UNSUPPORTED" || diagnostic === "COPILOT_USAGE_CREDENTIALS_UNAVAILABLE") {
1644
+ this.invalidate();
1645
+ return unavailableCopilotUsage(diagnostic);
1646
+ }
1647
+ const staleAllowed = [
1648
+ "COPILOT_USAGE_NETWORK",
1649
+ "COPILOT_USAGE_TIMEOUT",
1650
+ "COPILOT_USAGE_HTTP_ERROR",
1651
+ "COPILOT_USAGE_RATE_LIMITED"
1652
+ ].includes(diagnostic);
1653
+ if (!staleAllowed) this.cached = void 0;
1654
+ value = staleAllowed && this.cached ? {
1655
+ ...this.cached,
1656
+ state: "stale",
1657
+ diagnostic
1658
+ } : unavailableCopilotUsage(diagnostic);
1659
+ }
1660
+ if (value === void 0) return unavailableCopilotUsage("COPILOT_USAGE_INVALID_RESPONSE");
1661
+ this.last = value;
1662
+ if (value.state === "ready") {
1663
+ this.cached = value;
1664
+ this.failureUntil = 0;
1665
+ } else {
1666
+ if (value.state !== "stale") this.cached = void 0;
1667
+ this.failureUntil = this.now() + FAILURE_COOLDOWN_MS;
1668
+ }
1669
+ return value;
1670
+ }
1671
+ };
1672
+ let GitHubCopilotUsageController = (() => {
1673
+ let _classSuper = TypertRemoteService;
1674
+ let _instanceExtraInitializers = [];
1675
+ let _get_decorators;
1676
+ let _refresh_decorators;
1677
+ return class GitHubCopilotUsageController extends _classSuper {
1678
+ static {
1679
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
1680
+ _get_decorators = [Remote];
1681
+ _refresh_decorators = [Remote];
1682
+ __esDecorate$1(this, null, _get_decorators, {
1683
+ kind: "method",
1684
+ name: "get",
1685
+ static: false,
1686
+ private: false,
1687
+ access: {
1688
+ has: (obj) => "get" in obj,
1689
+ get: (obj) => obj.get
1690
+ },
1691
+ metadata: _metadata
1692
+ }, null, _instanceExtraInitializers);
1693
+ __esDecorate$1(this, null, _refresh_decorators, {
1694
+ kind: "method",
1695
+ name: "refresh",
1696
+ static: false,
1697
+ private: false,
1698
+ access: {
1699
+ has: (obj) => "refresh" in obj,
1700
+ get: (obj) => obj.refresh
1701
+ },
1702
+ metadata: _metadata
1703
+ }, null, _instanceExtraInitializers);
1704
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
1705
+ enumerable: true,
1706
+ configurable: true,
1707
+ writable: true,
1708
+ value: _metadata
1709
+ });
1710
+ }
1711
+ source = __runInitializers$1(this, _instanceExtraInitializers);
1712
+ constructor(ctx) {
1713
+ super(ctx, "githubCopilotUsage");
1714
+ const credentials = createGitHubCopilotCredentialStore(ctx);
1715
+ this.source = new CopilotUsageSource({ readCredential: () => credentials.read(GITHUB_COPILOT_PROVIDER_ID) });
1716
+ ctx.on("credentials/record-updated", (key) => {
1717
+ if (key === "llm-pi-ai/github-copilot") this.source.invalidate();
1718
+ });
1719
+ ctx.on("settings/updated", (namespace) => {
1720
+ if (namespace === "github-copilot" || namespace === "llm-pi-ai") this.source.invalidate();
1721
+ });
1722
+ ctx.effect(() => () => this.source.dispose());
1723
+ }
1724
+ async get() {
1725
+ return this.source.get();
1726
+ }
1727
+ async refresh() {
1728
+ return this.source.refresh();
1729
+ }
1730
+ };
1731
+ })();
1732
+ //#endregion
1733
+ //#region package.json
1734
+ var name$1 = "dsh-github-copilot";
1735
+ var version = "0.4.0-alpha.32";
1736
+ //#endregion
1737
+ //#region lib/types/probe.js
1738
+ /**
1739
+ * Capability probe: one bounded operation per candidate protocol that forces
1740
+ * the server-side web tool to run and checks the reply for the structured
1741
+ * evidence that it did. A protocol claim is trusted only after this passes —
1742
+ * several "Responses-compatible" gateways accept the `web_search` tool field
1743
+ * and silently ignore it (DeepSeek documents that unknown tool types are
1744
+ * ignored), which is exactly the failure mode static protocol detection
1745
+ * cannot see. The probe is what makes the auto-disable decision honest.
1746
+ * @module dsh-github-copilot/probe
1747
+ */
1748
+ /** Attribution header value sent on probe requests; single-sourced from package.json. */
1749
+ const USER_AGENT = `dsh-github-copilot/${version}`;
1750
+ /** Upper bound on generated tokens for a probe reply; the verdict needs none of it. */
1751
+ const PROBE_MAX_TOKENS = 64;
1752
+ /** Complete spelling rounds allowed after structurally valid transient replies. */
1753
+ const RESPONSES_PROBE_ROUNDS = 2;
1754
+ /** Upper bound on the whole probe (also the fetch signal bound), milliseconds. */
1755
+ const MAX_PROBE_TIMEOUT_MS = 2147483647;
1756
+ /**
1757
+ * Probe one candidate protocol with a bounded request sequence. The verdict is
1758
+ * structural: a Responses reply must contain a `web_search_call` item and a
1759
+ * Messages reply must contain a `web_search_tool_result` block; anything else
1760
+ * — HTTP error, unparseable body, silent tool ignore — is "not supported".
1761
+ * The timeout bounds the WHOLE probe, including credential resolution; the
1762
+ * probe never throws — every failure becomes a verdict.
1763
+ * @param candidate - the resolved endpoint facts to verify.
1764
+ * @param resolveApiKey - resolves the candidate's credential reference.
1765
+ * @param timeoutMs - bound on the whole probe.
1766
+ * @param signal - optional owner lifecycle cancellation; abort forbids later attempts.
1767
+ * @returns the verdict and a diagnostic detail.
1768
+ */
1769
+ async function probeCandidate(candidate, resolveApiKey, timeoutMs, signal) {
1770
+ const aborted = () => ({
1771
+ supported: false,
1772
+ detail: "probe was aborted"
1773
+ });
1774
+ if (signal?.aborted === true) return aborted();
1775
+ const bound = Math.min(Math.max(Math.trunc(timeoutMs), 1), MAX_PROBE_TIMEOUT_MS);
1776
+ const deadline = Date.now() + bound;
1777
+ const cancellation = new AbortController();
1778
+ let timer;
1779
+ let onAbort;
1780
+ const cancelled = new Promise((resolve) => {
1781
+ onAbort = () => {
1782
+ cancellation.abort();
1783
+ resolve(aborted());
1784
+ };
1785
+ signal?.addEventListener("abort", onAbort, { once: true });
1786
+ });
1787
+ try {
1788
+ return await Promise.race([
1789
+ cancelled,
1790
+ runProbe(candidate, resolveApiKey, bound, deadline, cancellation.signal),
1791
+ new Promise((resolve) => {
1792
+ timer = setTimeout(() => {
1793
+ cancellation.abort();
1794
+ resolve({
1795
+ supported: false,
1796
+ detail: `probe timed out after ${bound}ms`
1797
+ });
1798
+ }, bound);
1799
+ })
1800
+ ]);
1801
+ } finally {
1802
+ clearTimeout(timer);
1803
+ if (onAbort !== void 0) signal?.removeEventListener("abort", onAbort);
1804
+ }
1805
+ }
1806
+ /** Resolve the key and probe the endpoint under the shared whole-probe deadline. */
1807
+ async function runProbe(candidate, resolveApiKey, bound, deadline, cancellation) {
1808
+ try {
1809
+ const auth = normalizeRequestAuth(await resolveApiKey(candidate));
1810
+ if (auth === void 0 || auth.apiKey.length === 0) return {
1811
+ supported: false,
1812
+ detail: `no API key for "${candidate.apiKeyEnv}"`
1813
+ };
1814
+ candidate = applyRequestAuth(candidate, auth);
1815
+ const apiKey = auth.apiKey;
1816
+ const remaining = deadline - Date.now();
1817
+ if (cancellation.aborted || remaining <= 0) return {
1818
+ supported: false,
1819
+ detail: `probe timed out after ${bound}ms`
1820
+ };
1821
+ return candidate.protocol === "openai-responses" ? await probeResponses(candidate, apiKey, cancellation) : await probeAnthropic(candidate, apiKey, cancellation);
1822
+ } catch (error) {
1823
+ if (error instanceof WebError) return {
1824
+ supported: false,
1825
+ detail: error.message
1826
+ };
1827
+ return {
1828
+ supported: false,
1829
+ detail: `probe failed: ${String(error)}`
1830
+ };
1831
+ }
1832
+ }
1833
+ /**
1834
+ * Probe the Responses endpoint with a single tool spelling: force the tool
1835
+ * (standard `required` or the gateway's object choice) and require a
1836
+ * `web_search_call` item as structural evidence.
1837
+ * @param candidate - the resolved endpoint facts.
1838
+ * @param apiKey - the resolved credential.
1839
+ * @param signal - the probe timeout signal.
1840
+ * @param spelling - the tool type spelling to exercise.
1841
+ * @returns the verdict; `transient` is true only for a valid 2xx reply that
1842
+ * omitted the required structural evidence.
1843
+ */
1844
+ async function probeResponsesSpelling(candidate, apiKey, signal, spelling) {
1845
+ const endpoint = `${candidate.baseURL.replace(/\/+$/, "")}/responses`;
1846
+ let response;
1847
+ try {
1848
+ response = await fetch(endpoint, {
1849
+ method: "POST",
1850
+ redirect: "error",
1851
+ headers: {
1852
+ authorization: `Bearer ${apiKey}`,
1853
+ "content-type": "application/json",
1854
+ accept: "application/json",
1855
+ "user-agent": USER_AGENT,
1856
+ ...providerRequestHeaders(candidate, "user")
1857
+ },
1088
1858
  body: JSON.stringify({
1089
1859
  model: candidate.model,
1090
1860
  input: [{
@@ -3127,36 +3897,6 @@ function assertDshCompatibility(ctx) {
3127
3897
  if (typeof ctx["plugin"] !== "function") incompatible("context.plugin");
3128
3898
  }
3129
3899
  //#endregion
3130
- //#region lib/types/copilot-grant.js
3131
- function nonEmptyOAuthString(value, field) {
3132
- if (typeof value !== "string" || value.trim().length === 0) throw new Error(`github-copilot: OAuth credential field "${field}" must be a non-empty string`);
3133
- return value;
3134
- }
3135
- /**
3136
- * Rebuild pi-ai's GitHub Copilot grant as the exact JSON-safe provider shape.
3137
- * Prototype and unrelated extension members are intentionally not preserved.
3138
- */
3139
- function normalizeGitHubCopilotOAuthCredential(credential) {
3140
- if (typeof credential !== "object" || credential === null) throw new Error("github-copilot: OAuth credential must be an object");
3141
- if (Reflect.get(credential, "type") !== "oauth") throw new Error("github-copilot: OAuth credential type must be \"oauth\"");
3142
- const refresh = nonEmptyOAuthString(Reflect.get(credential, "refresh"), "refresh");
3143
- const access = nonEmptyOAuthString(Reflect.get(credential, "access"), "access");
3144
- const expires = Reflect.get(credential, "expires");
3145
- if (typeof expires !== "number" || !Number.isFinite(expires)) throw new Error("github-copilot: OAuth credential field \"expires\" must be a finite number");
3146
- const enterpriseUrl = Reflect.get(credential, "enterpriseUrl");
3147
- if (enterpriseUrl !== void 0 && (typeof enterpriseUrl !== "string" || enterpriseUrl.trim().length === 0)) throw new Error("github-copilot: OAuth credential field \"enterpriseUrl\" must be a non-empty string");
3148
- const availableModelIds = Reflect.get(credential, "availableModelIds");
3149
- if (availableModelIds !== void 0 && (!Array.isArray(availableModelIds) || !availableModelIds.every((modelId) => typeof modelId === "string" && modelId.trim().length > 0))) throw new Error("github-copilot: OAuth credential field \"availableModelIds\" must be an array of non-empty strings");
3150
- return {
3151
- type: "oauth",
3152
- refresh,
3153
- access,
3154
- expires,
3155
- ...enterpriseUrl === void 0 ? {} : { enterpriseUrl },
3156
- ...availableModelIds === void 0 ? {} : { availableModelIds: [...new Set(availableModelIds)] }
3157
- };
3158
- }
3159
- //#endregion
3160
3900
  //#region lib/types/temporary-models.js
3161
3901
  const GPT_6_ASTRA = {
3162
3902
  id: "gpt-6-astra",
@@ -4061,189 +4801,60 @@ let GitHubCopilotAuthorizationController = (() => {
4061
4801
  },
4062
4802
  prompt: (prompt) => {
4063
4803
  if (prompt.kind === "text" && /GitHub Enterprise URL\/domain/i.test(prompt.message)) return Promise.resolve("");
4064
- return Promise.reject(/* @__PURE__ */ new Error(`github-copilot: this browser bridge cannot answer authorization prompt "${prompt.message}"`));
4065
- }
4066
- }
4067
- }).then(async (outcome) => {
4068
- if (outcome.status === "authorized") {
4069
- this.notices = [];
4070
- await this.ensureProviderProfile();
4071
- return;
4072
- }
4073
- this.notices = [];
4074
- }).catch((error) => {
4075
- this.notices = [];
4076
- this.failure = messageOf(error);
4077
- this.ctx.logger.error("github-copilot: GitHub Copilot authorization failed");
4078
- this.ctx.logger.error(error);
4079
- }).finally(() => {
4080
- this.attempt = void 0;
4081
- });
4082
- this.attempt = running;
4083
- return this.status();
4084
- }
4085
- async cancel() {
4086
- const authorization = service(this.ctx, "authorization", [
4087
- "describe",
4088
- "begin",
4089
- "cancel"
4090
- ]);
4091
- this.notices = [];
4092
- authorization.cancel(GITHUB_COPILOT_CREDENTIAL_KEY);
4093
- return this.status();
4094
- }
4095
- async signOut() {
4096
- if (service(this.ctx, "authorization", [
4097
- "describe",
4098
- "begin",
4099
- "cancel"
4100
- ]).describe("llm-pi-ai/github-copilot")?.inFlight === true) throw new Error("github-copilot: cancel the active sign-in attempt before signing out");
4101
- await service(this.ctx, "credentials", ["describeRecord", "deleteRecord"]).deleteRecord(GITHUB_COPILOT_CREDENTIAL_KEY);
4102
- this.notices = [];
4103
- this.failure = void 0;
4104
- this.reconciliationFailed = false;
4105
- return this.status();
4106
- }
4107
- async ensureProviderProfile() {
4108
- try {
4109
- await ensureGitHubCopilotProviderProfile(this.ctx);
4110
- this.reconciliationFailed = false;
4111
- } catch {
4112
- this.reconciliationFailed = true;
4113
- this.ctx.logger.warn("github-copilot: route reconciliation failed; review route status before retrying");
4114
- }
4115
- }
4116
- };
4117
- })();
4118
- //#endregion
4119
- //#region lib/types/copilot-auth.js
4120
- /**
4121
- * Provider-side GitHub Copilot auth resolution over the same credential record
4122
- * that DSH's built-in llm-pi-ai adapter owns.
4123
- */
4124
- function credentialService(ctx) {
4125
- const candidate = ctx.get("credentials");
4126
- if (typeof candidate !== "object" || candidate === null) throw new Error("github-copilot: DSH credentials service is unavailable");
4127
- for (const method of [
4128
- "readRecord",
4129
- "listRecords",
4130
- "modifyRecord",
4131
- "deleteRecord"
4132
- ]) if (typeof Reflect.get(candidate, method) !== "function") throw new Error(`github-copilot: required DSH credentials API "credentials.${method}" is unavailable`);
4133
- return candidate;
4134
- }
4135
- function toCredential(record) {
4136
- if (record === void 0) return void 0;
4137
- if (record.kind === "api-key") return {
4138
- type: "api_key",
4139
- ...record.key === void 0 ? {} : { key: record.key },
4140
- ...record.env === void 0 ? {} : { env: { ...record.env } }
4141
- };
4142
- if (typeof record.payload !== "object" || record.payload === null) throw new Error("github-copilot: stored llm-pi-ai GitHub Copilot grant is not an object");
4143
- return normalizeGitHubCopilotOAuthCredential(record.payload);
4144
- }
4145
- function toRecord(credential) {
4146
- if (credential.type === "api_key") return {
4147
- kind: "api-key",
4148
- ...credential.key === void 0 ? {} : { key: credential.key },
4149
- ...credential.env === void 0 ? {} : { env: { ...credential.env } }
4150
- };
4151
- return {
4152
- kind: "grant",
4153
- payload: normalizeGitHubCopilotOAuthCredential(credential)
4154
- };
4155
- }
4156
- /** Bind exactly one logical route to the canonical Host-owned credential record. */
4157
- function createGitHubCopilotCredentialStore(ctx, logicalProviderId = GITHUB_COPILOT_PROVIDER_ID) {
4158
- const assertProvider = (providerId) => {
4159
- if (providerId !== logicalProviderId) throw new Error("COPILOT_CREDENTIAL_PROVIDER_MISMATCH");
4160
- };
4161
- return {
4162
- read: async (providerId) => {
4163
- assertProvider(providerId);
4164
- return toCredential(await credentialService(ctx).readRecord(GITHUB_COPILOT_CREDENTIAL_KEY));
4165
- },
4166
- list: async () => {
4167
- const record = await credentialService(ctx).readRecord(GITHUB_COPILOT_CREDENTIAL_KEY);
4168
- return record === void 0 ? [] : [{
4169
- providerId: logicalProviderId,
4170
- type: record.kind === "api-key" ? "api_key" : "oauth"
4171
- }];
4172
- },
4173
- modify: async (providerId, mutate) => {
4174
- assertProvider(providerId);
4175
- return toCredential(await credentialService(ctx).modifyRecord(GITHUB_COPILOT_CREDENTIAL_KEY, async (current) => {
4176
- const next = await mutate(toCredential(current));
4177
- return next === void 0 ? void 0 : toRecord(next);
4178
- }));
4179
- },
4180
- delete: async (providerId) => {
4181
- assertProvider(providerId);
4182
- await credentialService(ctx).deleteRecord(GITHUB_COPILOT_CREDENTIAL_KEY);
4804
+ return Promise.reject(/* @__PURE__ */ new Error(`github-copilot: this browser bridge cannot answer authorization prompt "${prompt.message}"`));
4805
+ }
4806
+ }
4807
+ }).then(async (outcome) => {
4808
+ if (outcome.status === "authorized") {
4809
+ this.notices = [];
4810
+ await this.ensureProviderProfile();
4811
+ return;
4812
+ }
4813
+ this.notices = [];
4814
+ }).catch((error) => {
4815
+ this.notices = [];
4816
+ this.failure = messageOf(error);
4817
+ this.ctx.logger.error("github-copilot: GitHub Copilot authorization failed");
4818
+ this.ctx.logger.error(error);
4819
+ }).finally(() => {
4820
+ this.attempt = void 0;
4821
+ });
4822
+ this.attempt = running;
4823
+ return this.status();
4183
4824
  }
4184
- };
4185
- }
4186
- /**
4187
- * Build one resolver whose pi-ai collection refreshes OAuth credentials inside
4188
- * the DSH record's serialized modify operation before exposing request auth.
4189
- */
4190
- function createGitHubCopilotTokenResolver(ctx, onCredentialChanged) {
4191
- const models = createModels({ credentials: createGitHubCopilotCredentialStore(ctx) });
4192
- const provider = githubCopilotProvider();
4193
- if (provider.auth.oauth === void 0) throw new Error("github-copilot: native OAuth method is unavailable");
4194
- models.setProvider({
4195
- ...provider,
4196
- auth: { oauth: provider.auth.oauth }
4197
- });
4198
- return async (modelId) => {
4199
- const snapshot = readCopilotCatalog(ctx);
4200
- const native = new Map(snapshot.models.map((model) => [model.id, model])).get(modelId);
4201
- if (native === void 0) throw new Error(`github-copilot: pi-ai catalog has no GitHub Copilot model "${modelId}"`);
4202
- const resolved = await models.getAuth("github-copilot");
4203
- if (resolved === void 0) return void 0;
4204
- if (onCredentialChanged !== void 0) try {
4205
- await onCredentialChanged();
4206
- } catch (error) {
4207
- ctx.logger.warn("github-copilot: provider route reconciliation will retry after a later auth resolution");
4208
- ctx.logger.warn(error);
4825
+ async cancel() {
4826
+ const authorization = service(this.ctx, "authorization", [
4827
+ "describe",
4828
+ "begin",
4829
+ "cancel"
4830
+ ]);
4831
+ this.notices = [];
4832
+ authorization.cancel(GITHUB_COPILOT_CREDENTIAL_KEY);
4833
+ return this.status();
4834
+ }
4835
+ async signOut() {
4836
+ if (service(this.ctx, "authorization", [
4837
+ "describe",
4838
+ "begin",
4839
+ "cancel"
4840
+ ]).describe("llm-pi-ai/github-copilot")?.inFlight === true) throw new Error("github-copilot: cancel the active sign-in attempt before signing out");
4841
+ await service(this.ctx, "credentials", ["describeRecord", "deleteRecord"]).deleteRecord(GITHUB_COPILOT_CREDENTIAL_KEY);
4842
+ this.notices = [];
4843
+ this.failure = void 0;
4844
+ this.reconciliationFailed = false;
4845
+ return this.status();
4846
+ }
4847
+ async ensureProviderProfile() {
4848
+ try {
4849
+ await ensureGitHubCopilotProviderProfile(this.ctx);
4850
+ this.reconciliationFailed = false;
4851
+ } catch {
4852
+ this.reconciliationFailed = true;
4853
+ this.ctx.logger.warn("github-copilot: route reconciliation failed; review route status before retrying");
4854
+ }
4209
4855
  }
4210
- const stored = await credentialService(ctx).readRecord(GITHUB_COPILOT_CREDENTIAL_KEY);
4211
- if (!(stored?.kind === "grant" && (normalizeGitHubCopilotOAuthCredential(stored.payload).availableModelIds ?? []).includes(modelId))) throw new Error(`github-copilot: model "${modelId}" is not available for the signed-in Copilot account`);
4212
- const apiKey = resolved.auth.apiKey;
4213
- if (apiKey === void 0) return void 0;
4214
- return {
4215
- apiKey,
4216
- baseURL: trustedCopilotBaseUrl(resolved.auth.baseUrl ?? native.baseUrl, enterpriseDomainOf(stored)),
4217
- ...resolved.auth.headers === void 0 ? {} : { headers: resolved.auth.headers }
4218
- };
4219
4856
  };
4220
- }
4221
- function enterpriseDomainOf(record) {
4222
- if (record?.kind !== "grant" || typeof record.payload !== "object" || record.payload === null) return void 0;
4223
- const value = Reflect.get(record.payload, "enterpriseUrl");
4224
- if (typeof value !== "string" || value.trim().length === 0) return void 0;
4225
- try {
4226
- return (value.includes("://") ? new URL(value) : new URL(`https://${value}`)).hostname.toLowerCase();
4227
- } catch {
4228
- throw new Error("github-copilot: stored enterpriseUrl is invalid");
4229
- }
4230
- }
4231
- /** Validate an OAuth-derived endpoint without disclosing its value or credential payload. */
4232
- function trustedGitHubCopilotBaseUrl(value, grant) {
4233
- return trustedCopilotBaseUrl(value, enterpriseDomainOf({
4234
- kind: "grant",
4235
- payload: normalizeGitHubCopilotOAuthCredential(grant)
4236
- }));
4237
- }
4238
- function trustedCopilotBaseUrl(value, enterpriseDomain) {
4239
- if (value === void 0) throw new Error("github-copilot: pi-ai resolved no Copilot API base URL");
4240
- const url = new URL(value);
4241
- const hostname = url.hostname.toLowerCase();
4242
- const githubHosted = /^api(?:\.[a-z0-9-]+)+\.githubcopilot\.com$/u.test(hostname);
4243
- const enterpriseHosted = enterpriseDomain !== void 0 && hostname === `copilot-api.${enterpriseDomain}`;
4244
- if (!(url.protocol === "https:" && (githubHosted || enterpriseHosted)) || url.username.length > 0 || url.password.length > 0) throw new Error("github-copilot: pi-ai resolved an untrusted Copilot API base URL");
4245
- return url.origin;
4246
- }
4857
+ })();
4247
4858
  //#endregion
4248
4859
  //#region lib/types/tool-schema-compat.js
4249
4860
  const ESCALATION_FIELDS = /* @__PURE__ */ new Set(["sandbox_permissions", "justification"]);
@@ -4644,150 +5255,6 @@ function createAccountProvider(descriptors, guard, baseURL) {
4644
5255
  };
4645
5256
  }
4646
5257
  //#endregion
4647
- //#region lib/types/account-model-auth.js
4648
- /** Native getAuth renews within five minutes; reserve another thirty seconds for metadata preparation. */
4649
- const ACCOUNT_MODEL_AUTH_MIN_VALIDITY_MS = 33e4;
4650
- /** An opaque, process-local account key. Never a substitute for request credentials. */
4651
- function copilotAccountKey(grant) {
4652
- return createHash("sha256").update(`${grant.refresh.length}:`).update(grant.refresh).update(grant.enterpriseUrl ?? "").digest("hex");
4653
- }
4654
- /** Compare normalized permission snapshots without retaining model credentials. */
4655
- function copilotEntitlementKey(grant) {
4656
- const ids = grant.availableModelIds;
4657
- const normalized = ids === void 0 ? "missing" : JSON.stringify([...new Set(ids)].sort());
4658
- return createHash("sha256").update(normalized).digest("hex");
4659
- }
4660
- function authEntitlementKey(auth) {
4661
- return copilotEntitlementKey({ ...auth.availableModelIds === void 0 ? {} : { availableModelIds: [...auth.availableModelIds] } });
4662
- }
4663
- function active(signal) {
4664
- if (signal.aborted) throw new Error("COPILOT_ACCOUNT_AUTH_ABORTED");
4665
- }
4666
- /**
4667
- * Bind model discovery to the existing native OAuth lifecycle. No model catalog,
4668
- * login, environment fallback or separate credential record is introduced here.
4669
- */
4670
- function createAccountModelAuth(credentials, renewRejectedCredential) {
4671
- const native = githubCopilotProvider();
4672
- const oauth = native.auth.oauth;
4673
- if (oauth === void 0) throw new Error("COPILOT_ACCOUNT_OAUTH_UNAVAILABLE");
4674
- const readGrant = async (signal) => {
4675
- active(signal);
4676
- const stored = await credentials.read(GITHUB_COPILOT_PROVIDER_ID);
4677
- active(signal);
4678
- if (stored?.type !== "oauth") throw new Error("COPILOT_ACCOUNT_OAUTH_REQUIRED");
4679
- return normalizeGitHubCopilotOAuthCredential(stored);
4680
- };
4681
- const matches = (grant, key) => {
4682
- if (copilotAccountKey(grant) !== key) throw new Error("COPILOT_ACCOUNT_CHANGED");
4683
- };
4684
- const current = async (auth, signal) => {
4685
- active(signal);
4686
- const grant = await readGrant(signal);
4687
- matches(grant, auth.accountKey);
4688
- if (grant.access !== auth.apiKey || grant.expires <= Date.now()) throw new Error("COPILOT_ACCOUNT_TOKEN_CHANGED");
4689
- if (copilotEntitlementKey(grant) !== authEntitlementKey(auth)) throw new Error("COPILOT_ACCOUNT_ENTITLEMENT_CHANGED");
4690
- const derived = await oauth.toAuth(grant);
4691
- active(signal);
4692
- if (derived.apiKey !== auth.apiKey || trustedGitHubCopilotBaseUrl(derived.baseUrl, grant) !== auth.baseURL) throw new Error("COPILOT_ACCOUNT_AUTH_CHANGED");
4693
- const latest = await readGrant(signal);
4694
- matches(latest, auth.accountKey);
4695
- if (latest.access !== auth.apiKey || latest.expires <= Date.now()) throw new Error("COPILOT_ACCOUNT_TOKEN_CHANGED");
4696
- if (copilotEntitlementKey(latest) !== authEntitlementKey(auth)) throw new Error("COPILOT_ACCOUNT_ENTITLEMENT_CHANGED");
4697
- };
4698
- return {
4699
- async resolveAuth(signal) {
4700
- try {
4701
- const before = await readGrant(signal);
4702
- const key = copilotAccountKey(before);
4703
- const rejectedAccess = renewRejectedCredential?.(before) === true ? before.access : void 0;
4704
- const forResolution = (grant) => rejectedAccess !== void 0 && grant.access === rejectedAccess ? {
4705
- ...grant,
4706
- expires: 0
4707
- } : grant;
4708
- const guardedOAuth = {
4709
- ...oauth,
4710
- login: async () => {
4711
- throw new Error("COPILOT_ACCOUNT_USE_CANONICAL_SIGN_IN");
4712
- },
4713
- async refresh(credential, requestSignal) {
4714
- active(signal);
4715
- matches(normalizeGitHubCopilotOAuthCredential(credential), key);
4716
- const result = normalizeGitHubCopilotOAuthCredential(await oauth.refresh(credential, requestSignal === void 0 ? signal : AbortSignal.any([signal, requestSignal])));
4717
- active(signal);
4718
- matches(result, key);
4719
- return result;
4720
- },
4721
- async toAuth(credential) {
4722
- active(signal);
4723
- const grant = normalizeGitHubCopilotOAuthCredential(credential);
4724
- matches(grant, key);
4725
- const result = await oauth.toAuth(grant);
4726
- active(signal);
4727
- return {
4728
- ...result,
4729
- baseUrl: trustedGitHubCopilotBaseUrl(result.baseUrl, grant)
4730
- };
4731
- }
4732
- };
4733
- const models = createModels({ credentials: {
4734
- read: async (provider) => {
4735
- const value = await credentials.read(provider);
4736
- active(signal);
4737
- if (value?.type !== "oauth") throw new Error("COPILOT_ACCOUNT_OAUTH_REQUIRED");
4738
- const grant = normalizeGitHubCopilotOAuthCredential(value);
4739
- matches(grant, key);
4740
- return forResolution(grant);
4741
- },
4742
- list: () => credentials.list(),
4743
- modify: (provider, mutate) => credentials.modify(provider, async (value) => {
4744
- active(signal);
4745
- if (value?.type !== "oauth") throw new Error("COPILOT_ACCOUNT_OAUTH_REQUIRED");
4746
- matches(normalizeGitHubCopilotOAuthCredential(value), key);
4747
- const next = await mutate(forResolution(normalizeGitHubCopilotOAuthCredential(value)));
4748
- active(signal);
4749
- if (next !== void 0) matches(normalizeGitHubCopilotOAuthCredential(next), key);
4750
- return next;
4751
- }),
4752
- delete: async () => {
4753
- throw new Error("COPILOT_ACCOUNT_DISCOVERY_CANNOT_SIGN_OUT");
4754
- }
4755
- } });
4756
- models.setProvider({
4757
- ...native,
4758
- auth: { oauth: guardedOAuth }
4759
- });
4760
- const resolved = await models.getAuth(GITHUB_COPILOT_PROVIDER_ID, {
4761
- signal,
4762
- minOAuthValidityMs: ACCOUNT_MODEL_AUTH_MIN_VALIDITY_MS
4763
- });
4764
- active(signal);
4765
- if (resolved?.auth.apiKey === void 0) throw new Error("COPILOT_ACCOUNT_OAUTH_REQUIRED");
4766
- const grant = await readGrant(signal);
4767
- matches(grant, key);
4768
- if (rejectedAccess !== void 0 && grant.access === rejectedAccess) throw new Error("COPILOT_ACCOUNT_TOKEN_REJECTED");
4769
- const auth = {
4770
- apiKey: resolved.auth.apiKey,
4771
- baseURL: trustedGitHubCopilotBaseUrl(resolved.auth.baseUrl, grant),
4772
- accountKey: key,
4773
- ...grant.availableModelIds === void 0 ? {} : { availableModelIds: Object.freeze([...grant.availableModelIds]) }
4774
- };
4775
- await current(auth, signal);
4776
- return Object.freeze(auth);
4777
- } catch {
4778
- throw new Error(signal.aborted ? "COPILOT_ACCOUNT_AUTH_ABORTED" : "COPILOT_ACCOUNT_AUTH_FAILED");
4779
- }
4780
- },
4781
- async assertAuthCurrent(auth, signal) {
4782
- try {
4783
- await current(auth, signal);
4784
- } catch {
4785
- throw new Error(signal.aborted ? "COPILOT_ACCOUNT_AUTH_ABORTED" : "COPILOT_ACCOUNT_AUTH_CHANGED");
4786
- }
4787
- }
4788
- };
4789
- }
4790
- //#endregion
4791
5258
  //#region lib/types/account-model-catalog.js
4792
5259
  /** Pure normalization of account /models metadata; no transport, credentials, or model-name rules. */
4793
5260
  const ACCOUNT_MODEL_CATALOG_LIMITS = Object.freeze({
@@ -6345,6 +6812,7 @@ function activate(ctx, config) {
6345
6812
  ctx.plugin(GitHubCopilotAuthorizationController);
6346
6813
  ctx.plugin(GitHubCopilotDualModel);
6347
6814
  ctx.plugin(SearchRoutingController);
6815
+ ctx.plugin(GitHubCopilotUsageController);
6348
6816
  const resolveGitHubCopilotToken = createGitHubCopilotTokenResolver(ctx, async () => {
6349
6817
  await ensureGitHubCopilotProviderProfile(ctx);
6350
6818
  });