@gnahz77/opencode-copilot-multi-auth 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@ Add the plugin to your `opencode` config:
12
12
  {
13
13
  "$schema": "https://opencode.ai/config.json",
14
14
  "plugin": [
15
- "@gnahz77/opencode-copilot-multi-auth@0.1.2"
15
+ "@gnahz77/opencode-copilot-multi-auth@0.1.3"
16
16
  ]
17
17
  }
18
18
  ```
@@ -25,7 +25,7 @@ If you also want the TUI command support provided by this package, add the same
25
25
  {
26
26
  "$schema": "https://opencode.ai/tui.json",
27
27
  "plugin": [
28
- "@gnahz77/opencode-copilot-multi-auth@0.1.2"
28
+ "@gnahz77/opencode-copilot-multi-auth@0.1.3"
29
29
  ]
30
30
  }
31
31
  ```
package/dist/auth.d.ts CHANGED
@@ -3,6 +3,7 @@ export declare function getUrls(domain: string): {
3
3
  DEVICE_CODE_URL: string;
4
4
  ACCESS_TOKEN_URL: string;
5
5
  COPILOT_ENTITLEMENT_URL: string;
6
+ COPILOT_TOKEN_URL: string;
6
7
  };
7
8
  export declare function lookupGitHubIdentity(accessToken: string, enterpriseUrl?: string): Promise<{
8
9
  login: any;
@@ -10,7 +11,8 @@ export declare function lookupGitHubIdentity(accessToken: string, enterpriseUrl?
10
11
  }>;
11
12
  export declare function fetchEntitlement(info: AuthInput): Promise<any>;
12
13
  export declare function getBaseURL(info: AuthInput): Promise<any>;
13
- export declare function buildHeaders(init: RequestInit | undefined, info: AuthInput, isVision: boolean, isAgent: boolean): {
14
+ export declare function getCopilotToken(info: AuthInput, forceRefresh?: boolean): Promise<any>;
15
+ export declare function buildHeaders(init: RequestInit | undefined, copilotToken: string, isVision: boolean, isAgent: boolean): {
14
16
  [k: string]: string;
15
17
  };
16
18
  export declare function resolveSelectedPoolAccount(pool: AccountPool, accountKey?: string, requestedRawModelId?: string): PoolAccount | null;
package/dist/auth.js CHANGED
@@ -1,4 +1,4 @@
1
- import { API_VERSION } from "./constants.js";
1
+ import { API_VERSION, COPILOT_TOKEN_EXPIRY_BUFFER_MS, DEFAULT_COPILOT_BASE_URL, VSCODE_HEADERS, } from "./constants.js";
2
2
  import { getSelectedAccountExpiredError, getSelectedAccountMissingError } from "./errors.js";
3
3
  import { readPool, resolveWinnerAccount, writePool } from "./pool.js";
4
4
  import { stripRoutingHeaders } from "./routing.js";
@@ -9,8 +9,17 @@ export function getUrls(domain) {
9
9
  DEVICE_CODE_URL: `https://${domain}/login/device/code`,
10
10
  ACCESS_TOKEN_URL: `https://${domain}/login/oauth/access_token`,
11
11
  COPILOT_ENTITLEMENT_URL: `https://${apiDomain}/copilot_internal/user`,
12
+ COPILOT_TOKEN_URL: `https://${apiDomain}/copilot_internal/v2/token`,
12
13
  };
13
14
  }
15
+ const cachedCopilotTokens = new Map();
16
+ function getCopilotTokenCacheKey(info) {
17
+ const domain = info.enterpriseUrl ? normalizeDomain(info.enterpriseUrl) : "github.com";
18
+ return `${domain}:${info.refresh}`;
19
+ }
20
+ function isReusableCopilotToken(token, expiresAt, now) {
21
+ return Boolean(token && expiresAt && expiresAt - COPILOT_TOKEN_EXPIRY_BUFFER_MS > now);
22
+ }
14
23
  export async function lookupGitHubIdentity(accessToken, enterpriseUrl) {
15
24
  const deployment = enterpriseUrl ? normalizeDomain(enterpriseUrl) : "github.com";
16
25
  const apiDomain = deployment === "github.com" ? "api.github.com" : `api.${deployment}`;
@@ -42,7 +51,7 @@ export async function fetchEntitlement(info) {
42
51
  headers: {
43
52
  Accept: "application/json",
44
53
  Authorization: `Bearer ${info.refresh}`,
45
- "User-Agent": "GithubCopilot/1.155.0",
54
+ ...VSCODE_HEADERS,
46
55
  },
47
56
  });
48
57
  if (!response.ok) {
@@ -54,7 +63,41 @@ export async function getBaseURL(info) {
54
63
  if (info.baseUrl)
55
64
  return info.baseUrl;
56
65
  const entitlement = await fetchEntitlement(info);
57
- return entitlement?.endpoints?.api;
66
+ return entitlement?.endpoints?.api ?? DEFAULT_COPILOT_BASE_URL;
67
+ }
68
+ export async function getCopilotToken(info, forceRefresh = false) {
69
+ const now = Date.now();
70
+ if (!forceRefresh && isReusableCopilotToken(info.access, info.expires, now)) {
71
+ return info.access;
72
+ }
73
+ const cacheKey = getCopilotTokenCacheKey(info);
74
+ const cachedToken = cachedCopilotTokens.get(cacheKey);
75
+ if (!forceRefresh && cachedToken && isReusableCopilotToken(cachedToken.token, cachedToken.expiresAt, now)) {
76
+ return cachedToken.token;
77
+ }
78
+ const domain = info.enterpriseUrl ? normalizeDomain(info.enterpriseUrl) : "github.com";
79
+ const urls = getUrls(domain);
80
+ const response = await fetch(urls.COPILOT_TOKEN_URL, {
81
+ headers: {
82
+ Accept: "application/json",
83
+ Authorization: `Bearer ${info.refresh}`,
84
+ ...VSCODE_HEADERS,
85
+ },
86
+ });
87
+ if (!response.ok) {
88
+ throw new Error(`[opencode-copilot-cli-auth] Copilot token exchange failed: ${response.status}`);
89
+ }
90
+ const data = await response.json();
91
+ const token = typeof data?.token === "string" ? data.token : "";
92
+ const expiresAt = typeof data?.expires_at === "number" ? data.expires_at * 1000 : 0;
93
+ if (!token || !expiresAt) {
94
+ throw new Error("[opencode-copilot-cli-auth] Copilot token exchange returned an invalid payload.");
95
+ }
96
+ cachedCopilotTokens.set(cacheKey, {
97
+ token,
98
+ expiresAt,
99
+ });
100
+ return token;
58
101
  }
59
102
  function getHeader(headers, name) {
60
103
  if (!headers)
@@ -74,14 +117,13 @@ function getHeader(headers, name) {
74
117
  }
75
118
  return undefined;
76
119
  }
77
- export function buildHeaders(init, info, isVision, isAgent) {
120
+ export function buildHeaders(init, copilotToken, isVision, isAgent) {
78
121
  const explicitInitiator = getHeader(init?.headers, "x-initiator");
79
122
  const headers = {
80
123
  ...normalizeHeaderObject(init?.headers),
81
- Authorization: `Bearer ${info.refresh}`,
82
- "Copilot-Integration-Id": "copilot-developer-cli",
124
+ Authorization: `Bearer ${copilotToken}`,
125
+ ...VSCODE_HEADERS,
83
126
  "Openai-Intent": "conversation-agent",
84
- "User-Agent": "opencode-copilot-cli-auth/0.0.16",
85
127
  "X-GitHub-Api-Version": API_VERSION,
86
128
  "X-Initiator": explicitInitiator ?? (isAgent ? "agent" : "user"),
87
129
  "X-Interaction-Id": crypto.randomUUID(),
@@ -93,6 +135,7 @@ export function buildHeaders(init, info, isVision, isAgent) {
93
135
  }
94
136
  delete headers["x-api-key"];
95
137
  delete headers.authorization;
138
+ delete headers["authorization"];
96
139
  delete headers["x-initiator"];
97
140
  return stripRoutingHeaders(headers);
98
141
  }
@@ -170,8 +213,9 @@ export async function refreshSelectedAccountBaseURL(accountKey) {
170
213
  }
171
214
  export async function fetchWithSelectedAccount(input, init, selectedAccount) {
172
215
  const { isVision, isAgent } = getConversationMetadata(init);
173
- const dispatch = async (account) => {
174
- const headers = buildHeaders(init, account.auth, isVision, isAgent);
216
+ const dispatch = async (account, forceRefreshToken = false) => {
217
+ const copilotToken = await getCopilotToken(account.auth, forceRefreshToken);
218
+ const headers = buildHeaders(init, copilotToken, isVision, isAgent);
175
219
  const requestInput = applyBaseURLToRequestInput(input, account.baseUrl);
176
220
  return fetch(requestInput, {
177
221
  ...init,
@@ -187,7 +231,7 @@ export async function fetchWithSelectedAccount(input, init, selectedAccount) {
187
231
  return response;
188
232
  }
189
233
  const refreshedAccount = await refreshSelectedAccountBaseURL(activeAccount.key);
190
- const retryResponse = await dispatch(refreshedAccount);
234
+ const retryResponse = await dispatch(refreshedAccount, true);
191
235
  if ([401, 403].includes(retryResponse.status)) {
192
236
  throw getSelectedAccountExpiredError(refreshedAccount.key);
193
237
  }
@@ -2,8 +2,16 @@ export declare const ACCOUNT_POOL_SCHEMA_VERSION = 1;
2
2
  export declare const ROUTING_ACCOUNT_KEY_HEADER = "x-opencode-copilot-account-key";
3
3
  export declare const ROUTING_SOURCE_HEADER = "x-opencode-copilot-route-source";
4
4
  export declare const INTERNAL_ROUTING_HEADERS: Set<string>;
5
- export declare const CLIENT_ID = "Ov23ctDVkRmgkPke0Mmm";
5
+ export declare const CLIENT_ID = "Iv1.b507a08c87ecfe98";
6
6
  export declare const API_VERSION = "2025-05-01";
7
7
  export declare const OAUTH_POLLING_SAFETY_MARGIN_MS = 3000;
8
8
  export declare const OAUTH_SCOPES = "read:user read:org repo gist";
9
+ export declare const DEFAULT_COPILOT_BASE_URL = "https://api.githubcopilot.com";
10
+ export declare const COPILOT_TOKEN_EXPIRY_BUFFER_MS: number;
11
+ export declare const VSCODE_HEADERS: {
12
+ readonly "User-Agent": "GitHubCopilotChat/0.38.0";
13
+ readonly "Editor-Version": "vscode/1.110.1";
14
+ readonly "Editor-Plugin-Version": "copilot-chat/0.38.0";
15
+ readonly "Copilot-Integration-Id": "vscode-chat";
16
+ };
9
17
  export declare const RESPONSES_API_ALTERNATE_INPUT_TYPES: readonly ["file_search_call", "computer_call", "computer_call_output", "web_search_call", "function_call", "function_call_output", "image_generation_call", "code_interpreter_call", "local_shell_call", "local_shell_call_output", "mcp_list_tools", "mcp_approval_request", "mcp_approval_response", "mcp_call", "reasoning"];
package/dist/constants.js CHANGED
@@ -5,10 +5,18 @@ export const INTERNAL_ROUTING_HEADERS = new Set([
5
5
  ROUTING_ACCOUNT_KEY_HEADER,
6
6
  ROUTING_SOURCE_HEADER,
7
7
  ]);
8
- export const CLIENT_ID = "Ov23ctDVkRmgkPke0Mmm";
8
+ export const CLIENT_ID = "Iv1.b507a08c87ecfe98";
9
9
  export const API_VERSION = "2025-05-01";
10
10
  export const OAUTH_POLLING_SAFETY_MARGIN_MS = 3000;
11
11
  export const OAUTH_SCOPES = "read:user read:org repo gist";
12
+ export const DEFAULT_COPILOT_BASE_URL = "https://api.githubcopilot.com";
13
+ export const COPILOT_TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
14
+ export const VSCODE_HEADERS = {
15
+ "User-Agent": "GitHubCopilotChat/0.38.0",
16
+ "Editor-Version": "vscode/1.110.1",
17
+ "Editor-Plugin-Version": "copilot-chat/0.38.0",
18
+ "Copilot-Integration-Id": "vscode-chat",
19
+ };
12
20
  export const RESPONSES_API_ALTERNATE_INPUT_TYPES = [
13
21
  "file_search_call",
14
22
  "computer_call",
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { CLIENT_ID, OAUTH_POLLING_SAFETY_MARGIN_MS, OAUTH_SCOPES, ROUTING_ACCOUNT_KEY_HEADER, ROUTING_SOURCE_HEADER, } from "./constants.js";
2
- import { buildHeaders, fetchEntitlement, fetchWithSelectedAccount, getBaseURL, getUrls, lookupGitHubIdentity, resolveSelectedPoolAccount, } from "./auth.js";
2
+ import { buildHeaders, fetchEntitlement, fetchWithSelectedAccount, getBaseURL, getCopilotToken, getUrls, lookupGitHubIdentity, resolveSelectedPoolAccount, } from "./auth.js";
3
3
  import { buildPoolBackedModels, normalizeExistingModels, resolveProviderModels } from "./models.js";
4
4
  import { deriveAccountKey, getPoolPath, readPool, resolveWinnerAccount, upsertAccount, writePool, } from "./pool.js";
5
5
  import { injectRoutingHeaders, stripRoutingHeaders } from "./routing.js";
6
- import { getHeader, getConversationMetadata, getRequestedRawModelId, normalizeDomain, resolveClaudeThinkingBudget, } from "./utils.js";
6
+ import { applyBaseURLToRequestInput, getHeader, getConversationMetadata, getRequestedRawModelId, normalizeDomain, resolveClaudeThinkingBudget, } from "./utils.js";
7
7
  export { getPoolPath, readPool, writePool, deriveAccountKey, lookupGitHubIdentity, upsertAccount, resolveWinnerAccount };
8
8
  export { injectRoutingHeaders, stripRoutingHeaders };
9
9
  export const CopilotAuthPlugin = async (input) => {
@@ -69,8 +69,10 @@ export const CopilotAuthPlugin = async (input) => {
69
69
  return fetch(inputRequest, init);
70
70
  }
71
71
  const { isVision, isAgent } = getConversationMetadata(init);
72
- const headers = buildHeaders(init, auth, isVision, isAgent);
73
- return fetch(inputRequest, {
72
+ const copilotToken = await getCopilotToken(auth);
73
+ const headers = buildHeaders(init, copilotToken, isVision, isAgent);
74
+ const requestInput = applyBaseURLToRequestInput(inputRequest, baseURL);
75
+ return fetch(requestInput, {
74
76
  ...init,
75
77
  headers,
76
78
  });
package/dist/models.js CHANGED
@@ -1,14 +1,15 @@
1
- import { getBaseURL } from "./auth.js";
1
+ import { getBaseURL, getCopilotToken } from "./auth.js";
2
+ import { API_VERSION, VSCODE_HEADERS } from "./constants.js";
2
3
  import { resolveWinnerAccount } from "./pool.js";
3
4
  import { getReleaseDate, isPickerModel, zeroCost } from "./utils.js";
4
5
  export async function fetchModels(info, baseURL) {
6
+ const copilotToken = await getCopilotToken(info);
5
7
  const response = await fetch(`${baseURL}/models`, {
6
8
  headers: {
7
- Authorization: `Bearer ${info.refresh}`,
8
- "Copilot-Integration-Id": "copilot-developer-cli",
9
+ Authorization: `Bearer ${copilotToken}`,
10
+ ...VSCODE_HEADERS,
9
11
  "Openai-Intent": "model-access",
10
- "User-Agent": "opencode-copilot-cli-auth/0.0.16",
11
- "X-GitHub-Api-Version": "2025-05-01",
12
+ "X-GitHub-Api-Version": API_VERSION,
12
13
  "X-Interaction-Type": "model-access",
13
14
  "X-Request-Id": crypto.randomUUID(),
14
15
  },
package/dist/types.d.ts CHANGED
@@ -111,6 +111,8 @@ export interface ProviderModel {
111
111
  export type HeaderObject = Headers | Array<[string, string]> | Record<string, string> | undefined;
112
112
  export interface AuthInput {
113
113
  refresh: string;
114
+ access?: string;
115
+ expires?: number;
114
116
  enterpriseUrl?: string | null;
115
117
  baseUrl?: string | null;
116
118
  type?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gnahz77/opencode-copilot-multi-auth",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",