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/AGENTS.md +3 -1
- package/CHANGELOG.md +7 -0
- package/README.md +15 -7
- package/README.zh.md +15 -7
- package/deployment-baseline.json +20 -2
- package/docs/copilot-usage.md +47 -0
- package/docs/official-first-016-alpha2.md +6 -0
- package/lib/client.js +665 -42
- package/lib/client.js.map +1 -1
- package/lib/index.js +950 -482
- package/lib/remote.js +94 -2
- package/lib/routed-web.js +1 -1
- package/lib/types/client.d.ts +1 -0
- package/lib/types/copilot-usage-card.d.ts +28 -0
- package/lib/types/copilot-usage-host.d.ts +48 -0
- package/lib/types/copilot-usage-normalize.d.ts +9 -0
- package/lib/types/copilot-usage-remote.d.ts +60 -0
- package/lib/types/copilot-usage-types.d.ts +19 -0
- package/lib/types/copilot-usage-ui.d.ts +4 -0
- package/lib/types/external-link.d.ts +3 -0
- package/package.json +2 -1
- package/lib/{search-routing-pFLux0W7.js → search-routing-DqLKem1c.js} +2 -2
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-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
964
|
-
|
|
965
|
-
|
|
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/
|
|
993
|
+
//#region lib/types/copilot-auth.js
|
|
968
994
|
/**
|
|
969
|
-
*
|
|
970
|
-
*
|
|
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
|
-
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
-
*
|
|
988
|
-
*
|
|
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
|
-
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
|
|
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
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1037
|
-
|
|
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
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
const
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
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
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
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
|
});
|