2
0

github-copilot.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import { z } from "zod"
  2. import { Auth } from "./index"
  3. import { NamedError } from "../util/error"
  4. export namespace AuthGithubCopilot {
  5. const CLIENT_ID = "Iv1.b507a08c87ecfe98"
  6. const DEVICE_CODE_URL = "https://github.com/login/device/code"
  7. const ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"
  8. const COPILOT_API_KEY_URL = "https://api.github.com/copilot_internal/v2/token"
  9. interface DeviceCodeResponse {
  10. device_code: string
  11. user_code: string
  12. verification_uri: string
  13. expires_in: number
  14. interval: number
  15. }
  16. interface AccessTokenResponse {
  17. access_token?: string
  18. error?: string
  19. error_description?: string
  20. }
  21. interface CopilotTokenResponse {
  22. token: string
  23. expires_at: number
  24. refresh_in: number
  25. endpoints: {
  26. api: string
  27. }
  28. }
  29. export async function authorize() {
  30. const deviceResponse = await fetch(DEVICE_CODE_URL, {
  31. method: "POST",
  32. headers: {
  33. Accept: "application/json",
  34. "Content-Type": "application/json",
  35. "User-Agent": "GithubCopilot/1.155.0",
  36. },
  37. body: JSON.stringify({
  38. client_id: CLIENT_ID,
  39. scope: "read:user",
  40. }),
  41. })
  42. const deviceData: DeviceCodeResponse = await deviceResponse.json()
  43. return {
  44. device: deviceData.device_code,
  45. user: deviceData.user_code,
  46. verification: deviceData.verification_uri,
  47. interval: deviceData.interval || 5,
  48. expiry: deviceData.expires_in,
  49. }
  50. }
  51. export async function poll(device_code: string) {
  52. const response = await fetch(ACCESS_TOKEN_URL, {
  53. method: "POST",
  54. headers: {
  55. Accept: "application/json",
  56. "Content-Type": "application/json",
  57. "User-Agent": "GithubCopilot/1.155.0",
  58. },
  59. body: JSON.stringify({
  60. client_id: CLIENT_ID,
  61. device_code,
  62. grant_type: "urn:ietf:params:oauth:grant-type:device_code",
  63. }),
  64. })
  65. if (!response.ok) return "failed"
  66. const data: AccessTokenResponse = await response.json()
  67. if (data.access_token) {
  68. // Store the GitHub OAuth token
  69. await Auth.set("github-copilot", {
  70. type: "oauth",
  71. refresh: data.access_token,
  72. access: "",
  73. expires: 0,
  74. })
  75. return "complete"
  76. }
  77. if (data.error === "authorization_pending") return "pending"
  78. if (data.error) return "failed"
  79. return "pending"
  80. }
  81. export async function access() {
  82. const info = await Auth.get("github-copilot")
  83. if (!info || info.type !== "oauth") return
  84. if (info.access && info.expires > Date.now()) return info.access
  85. // Get new Copilot API token
  86. const response = await fetch(COPILOT_API_KEY_URL, {
  87. headers: {
  88. Accept: "application/json",
  89. Authorization: `Bearer ${info.refresh}`,
  90. "User-Agent": "GithubCopilot/1.155.0",
  91. "Editor-Version": "vscode/1.85.1",
  92. "Editor-Plugin-Version": "copilot/1.155.0",
  93. },
  94. })
  95. if (!response.ok) return
  96. const tokenData: CopilotTokenResponse = await response.json()
  97. // Store the Copilot API token
  98. await Auth.set("github-copilot", {
  99. type: "oauth",
  100. refresh: info.refresh,
  101. access: tokenData.token,
  102. expires: tokenData.expires_at * 1000,
  103. })
  104. return tokenData.token
  105. }
  106. export const DeviceCodeError = NamedError.create(
  107. "DeviceCodeError",
  108. z.object({}),
  109. )
  110. export const TokenExchangeError = NamedError.create(
  111. "TokenExchangeError",
  112. z.object({
  113. message: z.string(),
  114. }),
  115. )
  116. export const AuthenticationError = NamedError.create(
  117. "AuthenticationError",
  118. z.object({
  119. message: z.string(),
  120. }),
  121. )
  122. export const CopilotTokenError = NamedError.create(
  123. "CopilotTokenError",
  124. z.object({
  125. message: z.string(),
  126. }),
  127. )
  128. }