UNPKG

copilot-api-node20

Version:

Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code! (Node v20+ fork)

1 lines 6.76 kB
import e from"consola";import t from"node:fs/promises";import n from"node:os";import r from"node:path";import{createHash as i,randomBytes as a,randomUUID as o}from"node:crypto";const s=r.join(n.homedir(),`.local`,`share`,`copilot-api`),c=r.join(s,`github_token`),l=r.join(s,`machine_id`),u=r.join(s,`session_id`),d={APP_DIR:s,GITHUB_TOKEN_PATH:c,MACHINE_ID_PATH:l,SESSION_ID_PATH:u};async function f(){await t.mkdir(d.APP_DIR,{recursive:!0}),await p(d.GITHUB_TOKEN_PATH),await p(d.SESSION_ID_PATH)}async function p(e){try{await t.access(e,t.constants.W_OK)}catch{await t.writeFile(e,``),await t.chmod(e,384)}}const m={accountType:`individual`,manualApprove:!1,rateLimitWait:!1,showToken:!1,connectivity:{enabled:!0,probeEndpoints:[`https://api.github.com`,`https://www.google.com`,`https://1.1.1.1`],fastProbeInterval:5e3,slowProbeInterval:6e4,timeoutMs:5e3,jitterMaxMs:1e3,connectionPooling:!0,dnsCache:!0}},h=()=>({"content-type":`application/json`,accept:`application/json`}),g=`0.32.2025100203`,_=`copilot-chat/${g}`,v=`GitHubCopilotChat/${g}`,y=`2025-08-20`,b=e=>{let t=i(`sha256`);return t.update(process.platform),t.update(process.arch),t.update(process.env.USER||process.env.USERNAME||`anonymous`),t.update(n.hostname()),t.update(e),t.update(Date.now().toString()),t.update(a(16)),t.digest(`hex`)},x=e=>{let t=e.replaceAll(/[^\w.-]/g,`_`).trim();if(t.length===0||t===`.`||t===`..`||/^[_.]+$/.test(t))throw Error(`Invalid GitHub username: cannot be empty or consist only of special characters after sanitization`);return`${d.MACHINE_ID_PATH}_${t}`},S=async e=>{let n=x(e);try{let e=await t.readFile(n,`utf8`);if(e.trim())return e.trim()}catch{}let r=b(e);return await t.writeFile(n,r),await t.chmod(n,384),r},C=async()=>{try{let e=await t.readFile(d.SESSION_ID_PATH,`utf8`);if(e.trim())return e.trim()}catch{}let e=o();return await t.writeFile(d.SESSION_ID_PATH,e),await t.chmod(d.SESSION_ID_PATH,384),e},w=async(e,t)=>{e.machineId||=await S(t),e.sessionId||=await C()},T=e=>e.accountType===`individual`?`https://api.githubcopilot.com`:`https://api.${e.accountType}.githubcopilot.com`,E=(e,t=!1)=>{if(!e.machineId||!e.sessionId)throw Error(`VSCode identifiers not initialized. Call initializeVSCodeIdentifiers() during startup.`);let n={Authorization:`Bearer ${e.copilotToken}`,"Content-Type":`application/json`,accept:`*/*`,"accept-encoding":`br, gzip, deflate`,"accept-language":`*`,"sec-fetch-mode":`cors`,"Copilot-Integration-Id":`vscode-chat`,"Editor-Version":`vscode/${e.vsCodeVersion}`,"Editor-Plugin-Version":_,"User-Agent":v,"VScode-MachineId":e.machineId,"VScode-SessionId":e.sessionId,"OpenAI-Intent":`conversation-agent`,"X-Interaction-Type":`conversation-agent`,"X-VSCode-User-Agent-Library-Version":`node-fetch`,"X-GitHub-Api-Version":y,"X-Interaction-Id":o(),"X-Request-Id":o(),"openai-intent":`conversation-agent`,"x-interaction-type":`conversation-agent`};return t&&(n[`copilot-vision-request`]=`true`),n},D=`https://api.github.com`,O=e=>({...h(),authorization:`token ${e.githubToken}`,"editor-version":`vscode/${e.vsCodeVersion}`,"editor-plugin-version":_,"user-agent":v,"x-github-api-version":y,"x-vscode-user-agent-library-version":`node-fetch`}),k=`https://github.com`,A=`01ab8ac9400c4e429b23`,j=[`user:email`].join(` `),M=e=>e>=1e5||e>=1e4?`${Math.round(e/1e3)}K`:e>=1e3?`${(e/1e3).toFixed(1)}K`:e.toString(),N=(e,t)=>e.padEnd(t),P=(e,t)=>{if(!m.models)return``;let n=m.models.data.find(e=>e.id===t);if(!n)return``;let r=n.capabilities.limits.max_context_window_tokens;if(!r)return``;let i=(e/r*100).toFixed(1);return` (${i}%)`},F=e=>{let t=[];if(e.model){let n=N(e.model,18);t.push(n)}if(e.tokenUsage){let n=e.tokenUsage,r=(n.inputTokens||0)+(n.outputTokens||0),i=e.model?P(r,e.model):``,a=M(n.inputTokens||0).padStart(5),o=M(n.outputTokens||0).padStart(5),s=`↑${a} │ ↓${o}`,c=N(s,18),l=M(r),u=i?`${l}${i.padStart(15-l.length)}`:l.padEnd(15);t.push(`Tokens: ${c} | Context: ${u}`)}else if(e.model){let e=N(`N/A`,18),n=`N/A`.padEnd(15);t.push(`Tokens: ${e} | Context: ${n}`)}if(e.copilotDuration){let n=N(`${Math.round(e.copilotDuration)}ms`,8);t.push(`API: ${n}`)}return t.length>0?` | ${t.join(` | `)}`:``},I={completionCallbacks:new Map,registerCompletion(e,t,n){this.completionCallbacks.set(e,{context:t,startTime:n,requestId:e})},executeCompletion(e){let t=this.completionCallbacks.get(e);t&&(this.logCompletion(t),this.completionCallbacks.delete(e))},logCompletion(t){let{context:n,startTime:r}=t,i=Date.now(),a=i-r,o=n.get(`requestData`),s=N(n.req.method,4),c=N(n.req.path,21),l=N(n.res.status.toString(),3),u=N(`${a}ms`,8),d=` --> ${s}${c}${l} ${u}`;o&&(d+=F(o)),e.info(d)},logRateLimit(t,n){let{context:r,startTime:i}=t,a=Date.now(),o=a-i,s=n.headers.get(`x-ratelimit-exceeded`)||``,c=s.split(`:`)[1]||`unknown`,l=n.headers.get(`retry-after`)||n.headers.get(`x-ratelimit-user-retry-after`)||`?`,u=N(r.req.method,4),d=N(r.req.path,21),f=N(`429`,3),p=N(`${o}ms`,8),m=`⚠ --> ${u}${d}${f} ${p}`;m+=` | Rate limited (${l}s retry) | ${c}`;let h=r.get(`requestData`);if(h?.copilotDuration){let e=N(`${Math.round(h.copilotDuration)}ms`,8);m+=` | API: ${e}`}e.info(m)},cleanup(){let e=1e3,t=Date.now()-300*1e3;for(let[e,n]of this.completionCallbacks)n.startTime<t&&this.completionCallbacks.delete(e);if(this.completionCallbacks.size>e){let t=Array.from(this.completionCallbacks.entries()).sort(([,e],[,t])=>e.startTime-t.startTime),n=t.slice(0,t.length-e);for(let[e]of n)this.completionCallbacks.delete(e)}}};setInterval(()=>I.cleanup(),60*1e3);var L=class extends Error{response;constructor(e,t){super(e),this.response=t}};async function R(t,n){if(n instanceof L&&n.response.status===429){let e=t.get(`requestId`),r=I.completionCallbacks.get(e);r&&(I.logRateLimit(r,n.response),I.completionCallbacks.delete(e));let i=await n.response.text(),a;try{a=JSON.parse(i)}catch{a={error:{message:i,type:`error`}}}return t.json(a,429)}if(e.error(`Error occurred:`,n),n instanceof L){let r=await n.response.text(),i;try{i=JSON.parse(r)}catch{i=r}return e.error(`HTTP error:`,i),t.json({error:{message:r,type:`error`}},n.response.status)}return t.json({error:{message:n.message,type:`error`}},500)}async function z(){let e=new AbortController,t=m.timeoutMs??12e4,n=setTimeout(()=>{e.abort()},t);try{let t=await fetch(`${D}/user`,{headers:{authorization:`token ${m.githubToken}`,...h()},signal:e.signal});if(!t.ok)throw new L(`Failed to get GitHub user`,t);return await t.json()}finally{clearTimeout(n)}}export{I as CompletionLogger,D as GITHUB_API_BASE_URL,j as GITHUB_APP_SCOPES,k as GITHUB_BASE_URL,A as GITHUB_CLIENT_ID,L as HTTPError,d as PATHS,T as copilotBaseUrl,E as copilotHeaders,f as ensurePaths,R as forwardError,z as getGitHubUser,O as githubHeaders,w as initializeVSCodeIdentifiers,h as standardHeaders,m as state};