Skip to content

Commit de13b29

Browse files
committed
refactor: Improve error handling and token management
1 parent 2f293ab commit de13b29

File tree

7 files changed

+77
-43
lines changed

7 files changed

+77
-43
lines changed

README.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,17 @@ npx copilot-api --port 8080
4040

4141
## Command Line Options
4242

43-
The command accepts several command line options:
43+
The following command line options are available:
4444

45-
| Option | Description | Default |
46-
| ------------- | ------------------------------------ | ------- |
47-
| --port, -p | Port to listen on | 4141 |
48-
| --verbose, -v | Enable verbose logging | false |
49-
| --log-file | File to log request/response details | - |
45+
| Option | Description | Default |
46+
| ------------- | ------------------------ | ------- |
47+
| --port, -p | Port to listen on | 4141 |
48+
| --verbose, -v | Enable verbose logging | false |
5049

51-
Example with options:
50+
Example usage:
5251

5352
```sh
54-
npx copilot-api@latest --port 8080 --verbose --log-file copilot-api.txt
53+
npx copilot-api@latest --port 8080 --verbose
5554
```
5655

5756
## Running from Source
@@ -70,15 +69,18 @@ bun run dev
7069
bun run start
7170
```
7271

73-
## Tips to not hit the rate limit
72+
## Usage Tips
7473

75-
- Use a free model from free provider like Gemini/Mistral/Openrouter for the weak model
76-
- Rarely use architect mode
77-
- Do not enable automatic yes in aider config
78-
- Claude 3.7 thinking mode uses more tokens. Use it sparingly
74+
To avoid rate limiting and optimize your experience:
75+
76+
- Consider using free models (e.g., Gemini, Mistral, Openrouter) as the `weak-model`
77+
- Use architect mode sparingly
78+
- Disable `yes-always` in your aider configuration
79+
- Be mindful that Claude 3.7 thinking mode consume more tokens
7980

8081
## Roadmap
8182

82-
- Manual approval for every request
83-
- Rate limiting (only allow request every X seconds)
84-
- Token counting
83+
- [ ] Manual request approval system
84+
- [ ] Rate limiting implementation
85+
- [ ] Token usage tracking and monitoring
86+
- [ ] Enhanced error handling and recovery

src/lib/api-config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import type { State } from "./state"
22

3+
export const standardHeaders = () => ({
4+
"content-type": "application/json",
5+
accept: "application/json",
6+
})
7+
38
export const COPILOT_API_BASE_URL = "https://api.individual.githubcopilot.com"
49
export const copilotHeaders = (state: State) => ({
510
Authorization: `Bearer ${state.copilotToken}`,
6-
"content-type": "application/json",
11+
"content-type": standardHeaders()["content-type"],
712
"copilot-integration-id": "vscode-chat",
813
"editor-version": `vscode/${state.vsCodeVersion}`,
914
"editor-plugin-version": "copilot-chat/0.24.1",
@@ -15,6 +20,7 @@ export const copilotHeaders = (state: State) => ({
1520

1621
export const GITHUB_API_BASE_URL = "https://api.github.com"
1722
export const githubHeaders = (state: State) => ({
23+
...standardHeaders(),
1824
authorization: `token ${state.githubToken}`,
1925
"editor-version": `vscode/${state.vsCodeVersion}`,
2026
"editor-plugin-version": "copilot-chat/0.24.1",

src/lib/token.ts

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getDeviceCode } from "~/services/github/get-device-code"
77
import { getGitHubUser } from "~/services/github/get-user"
88
import { pollAccessToken } from "~/services/github/poll-access-token"
99

10+
import { HTTPError } from "./http-error"
1011
import { state } from "./state"
1112

1213
const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")
@@ -33,27 +34,38 @@ export const setupCopilotToken = async () => {
3334
}
3435

3536
export async function setupGitHubToken(): Promise<void> {
36-
const githubToken = await readGithubToken()
37+
try {
38+
const githubToken = await readGithubToken()
3739

38-
if (githubToken) {
39-
state.githubToken = githubToken
40-
await logUser()
40+
if (githubToken) {
41+
state.githubToken = githubToken
42+
await logUser()
4143

42-
return
43-
}
44+
return
45+
}
46+
47+
consola.info("Not logged in, getting new access token")
48+
const response = await getDeviceCode()
49+
consola.debug("Device code response:", response)
4450

45-
consola.info("Not logged in, getting new access token")
46-
const response = await getDeviceCode()
51+
consola.info(
52+
`Please enter the code "${response.user_code}" in ${response.verification_uri}`,
53+
)
4754

48-
consola.info(
49-
`Please enter the code "${response.user_code}" in ${response.verification_uri}`,
50-
)
55+
const token = await pollAccessToken(response)
56+
await writeGithubToken(token)
57+
state.githubToken = token
5158

52-
const token = await pollAccessToken(response)
53-
await writeGithubToken(token)
54-
state.githubToken = token
59+
await logUser()
60+
} catch (error) {
61+
if (error instanceof HTTPError) {
62+
consola.error("Failed to get GitHub token:", await error.response.json())
63+
throw error
64+
}
5565

56-
await logUser()
66+
consola.error("Failed to get GitHub token:", error)
67+
throw error
68+
}
5769
}
5870

5971
async function logUser() {

src/services/copilot/get-models.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import { COPILOT_API_BASE_URL } from "~/lib/api-config"
1+
import { COPILOT_API_BASE_URL, copilotHeaders } from "~/lib/api-config"
22
import { HTTPError } from "~/lib/http-error"
33
import { state } from "~/lib/state"
44

55
export const getModels = async () => {
66
const response = await fetch(`${COPILOT_API_BASE_URL}/models`, {
7-
headers: {
8-
authorization: `Bearer ${state.copilotToken}`,
9-
},
7+
headers: copilotHeaders(state),
108
})
119

1210
if (!response.ok) throw new HTTPError("Failed to get models", response)

src/services/github/get-device-code.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import {
22
GITHUB_APP_SCOPES,
33
GITHUB_BASE_URL,
44
GITHUB_CLIENT_ID,
5+
standardHeaders,
56
} from "~/lib/api-config"
67
import { HTTPError } from "~/lib/http-error"
78

89
export async function getDeviceCode(): Promise<DeviceCodeResponse> {
910
const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {
1011
method: "POST",
12+
headers: standardHeaders(),
1113
body: JSON.stringify({
1214
client_id: GITHUB_CLIENT_ID,
1315
scope: GITHUB_APP_SCOPES,

src/services/github/get-user.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { GITHUB_API_BASE_URL } from "~/lib/api-config"
1+
import { GITHUB_API_BASE_URL, standardHeaders } from "~/lib/api-config"
22
import { HTTPError } from "~/lib/http-error"
33
import { state } from "~/lib/state"
44

55
export async function getGitHubUser() {
66
const response = await fetch(`${GITHUB_API_BASE_URL}/user`, {
77
headers: {
88
authorization: `token ${state.githubToken}`,
9+
...standardHeaders(),
910
},
1011
})
1112

src/services/github/poll-access-token.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,28 @@
1-
import { GITHUB_BASE_URL, GITHUB_CLIENT_ID } from "~/lib/api-config"
1+
import consola from "consola"
2+
3+
import {
4+
GITHUB_BASE_URL,
5+
GITHUB_CLIENT_ID,
6+
standardHeaders,
7+
} from "~/lib/api-config"
28
import { sleep } from "~/lib/sleep"
39

410
import type { DeviceCodeResponse } from "./get-device-code"
511

612
export async function pollAccessToken(
713
deviceCode: DeviceCodeResponse,
814
): Promise<string> {
15+
// Interval is in seconds, we need to multiply by 1000 to get milliseconds
16+
// I'm also adding another second, just to be safe
17+
const sleepDuration = (deviceCode.interval + 1) * 1000
18+
consola.debug(`Polling access token with interval of ${sleepDuration}ms`)
19+
920
while (true) {
1021
const response = await fetch(
1122
`${GITHUB_BASE_URL}/login/oauth/access_token`,
1223
{
1324
method: "POST",
25+
headers: standardHeaders(),
1426
body: JSON.stringify({
1527
client_id: GITHUB_CLIENT_ID,
1628
device_code: deviceCode.device_code,
@@ -19,16 +31,17 @@ export async function pollAccessToken(
1931
},
2032
)
2133

22-
// Interval is in seconds, we need to multiply by 1000 to get milliseconds
23-
// I'm also adding another second, just to be safe
24-
const sleepDuration = (deviceCode.interval + 1) * 1000
25-
2634
if (!response.ok) {
2735
await sleep(sleepDuration)
36+
consola.error("Failed to poll access token:", await response.text())
37+
2838
continue
2939
}
3040

31-
const { access_token } = (await response.json()) as AccessTokenResponse
41+
const json = await response.json()
42+
consola.debug("Polling access token response:", json)
43+
44+
const { access_token } = json as AccessTokenResponse
3245

3346
if (access_token) {
3447
return access_token

0 commit comments

Comments
 (0)