-
Notifications
You must be signed in to change notification settings - Fork 5
feat: Add GitHub Copilot token refresh command #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Implements a new Vim command `AiderDebugTokenRefresh` that allows you to obtain/refresh a GitHub Copilot token using the OAuth device flow. The command performs the following steps: 1. Requests a device and user code from GitHub. 2. Displays the user code and verification URI to you. 3. Polls GitHub's token endpoint until you authorize the device. 4. Displays the obtained access token. This functionality is implemented in the `debugTokenRefreshImpl` function within `denops/aider/main.ts`. A test case has been added in `tests/aider_test.ts` which mocks the external `fetch` calls to simulate the device flow and verifies that the correct messages are displayed to you. Note: The GitHub Client ID and scope used are common placeholders. The command is named `AiderDebugTokenRefresh` based on the dispatcher method `debugTokenRefresh`.
WalkthroughThree new asynchronous functions were added to implement GitHub OAuth device authorization flow, renew GitHub Copilot session tokens, and orchestrate a full token refresh process. These functions were registered as new Denops commands. The README was updated with usage instructions and license information. The MIT License file was added. The test file imported assertion utilities but no new tests were added. Changes
Error: Could not generate a valid Mermaid diagram after multiple attempts.
Note ⚡️ AI Code Reviews for VS Code, Cursor, WindsurfCodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback. Note ⚡️ Faster reviews with cachingCodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure ✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds a new Vim command, AiderDebugTokenRefresh, which implements a GitHub Copilot token refresh using the OAuth device flow. Key changes include:
- The implementation of the debugTokenRefreshImpl function in denops/aider/main.ts to handle device authorization and token polling.
- A new test case in tests/aider_test.ts that mocks fetch calls to simulate the OAuth device flow and verify proper messaging.
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
File | Description |
---|---|
tests/aider_test.ts | Added a test case for the new AiderDebugTokenRefresh command with fetch mocking and assertions. |
denops/aider/main.ts | Implemented debugTokenRefreshImpl to initiate device flow and poll GitHub for an access token. |
Comments suppressed due to low confidence (1)
tests/aider_test.ts:158
- [nitpick] Consider using a more deterministic mechanism or event-based synchronization instead of a fixed sleep duration to improve test reliability and avoid potential flakiness.
await sleep(50); // Increased slightly to be safer
return; | ||
} | ||
} | ||
} catch (pollError) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Consider introducing a retry limit or early exit strategy for persistent network errors within the polling loop to prevent prolonged polling cycles, even though the current design eventually times out.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
denops/aider/main.ts (2)
118-120
: Consider externalizing the credentialsThe client ID and scope are currently hardcoded as comments indicate they're placeholders. For better maintainability, consider moving these to configuration variables that can be changed without modifying the code directly.
- const clientId = "Iv1.b507a08c87ecfe98"; // Placeholder - const scope = "copilot"; // Placeholder + // Use configuration from plugin settings or environment + const clientId = await denops.eval('get(g:, "aider_github_client_id", "Iv1.b507a08c87ecfe98")') as string; + const scope = await denops.eval('get(g:, "aider_github_scope", "copilot")') as string;
216-222
: Consider specific error type handlingYou catch all network errors in one block, but you might want to implement different behaviors based on error types. For transient network issues, continuing polling makes sense, but for persistent connection issues, it might be better to retry with backoff or stop after a certain number of failures.
} catch (pollError) { await denops.cmd( `echomsg "Network error during polling: ${pollError.message}"`, ); console.error("Network error during polling:", pollError); - // Decide if to continue or break based on error type if needed + // Continue polling for network errors, but track consecutive failures + networkErrorCount++; + if (networkErrorCount > 3) { + await denops.cmd(`echomsg "Too many network errors, stopping polling."`); + return; + } + // Increase polling interval to reduce load on server + pollingInterval = Math.min(pollingInterval * 1.5, 10000); }tests/aider_test.ts (1)
158-158
: Consider more robust test timingUsing a fixed sleep duration of 50ms might be brittle. Consider a more deterministic approach or a longer duration to ensure all async operations complete.
- await sleep(50); // Increased slightly to be safer + await sleep(100); // Use a more conservative value or consider a retry/poll approach
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
📒 Files selected for processing (2)
denops/aider/main.ts
(2 hunks)tests/aider_test.ts
(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
tests/aider_test.ts (2)
tests/testRunner.ts (1)
test
(22-37)tests/assertions.ts (1)
sleep
(6-7)
🪛 GitHub Actions: Test
tests/aider_test.ts
[error] 101-101: TS2339: Property 'url' does not exist on type 'URL | Request'. Property 'url' does not exist on type 'URL'.
🔇 Additional comments (6)
denops/aider/main.ts (3)
117-234
: Well-implemented OAuth device flow for token refreshThe implementation of the OAuth device flow for GitHub Copilot token refresh is well-structured and thorough. It properly handles the complete flow from device code request to token retrieval.
165-223
: Great error handling in polling implementationThe polling logic has excellent error handling, including handling of
authorization_pending
,slow_down
, and other error scenarios. The try-catch block for network errors during polling is a good defensive practice.
494-498
: Command registration follows established patternsThe command registration for
debugTokenRefresh
follows the same pattern as other commands in the file, ensuring consistency in the codebase.tests/aider_test.ts (3)
91-188
: Comprehensive test for device flowThe test case thoroughly verifies the device flow implementation by mocking external requests and checking message outputs.
🧰 Tools
🪛 GitHub Actions: Test
[error] 101-101: TS2339: Property 'url' does not exist on type 'URL | Request'. Property 'url' does not exist on type 'URL'.
141-150
: Good approach for mockingdenops.cmd
to capture messagesThe approach of mocking
denops.cmd
and capturing output messages is effective for testing the user-facing aspects of the command.
163-182
: Thorough verification of output messagesThe test properly verifies all the important user-facing messages during the device flow, which is excellent for ensuring the command provides appropriate feedback.
Extends the `AiderDebugTokenRefresh` command to perform a two-step token acquisition process: 1. Obtains a GitHub access token via the OAuth device flow. 2. Uses this GitHub access token to request a Copilot-specific session token from the `https://api.github.com/copilot_internal/v2/token` endpoint. Both the GitHub access token and the Copilot session token (including its expiry) are displayed to you. The implementation in `debugTokenRefreshImpl` within `denops/aider/main.ts` has been updated accordingly. The existing test case in `tests/aider_test.ts` has been enhanced to mock the additional API call for the Copilot session token and verify all messages you see and critical headers. This change aligns the command more closely with the token acquisition flow observed in other Copilot tools.
Addresses a TypeScript error (TS2339) in tests/aider_test.ts where `input.url` was accessed on `URL` objects, which do not have this property. The mockFetch utility now correctly uses `input.href` for `URL` objects and `input.url` for `Request` objects to obtain the URL string, ensuring compatibility with the fetch API's input types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (4)
tests/aider_test.ts (4)
173-179
: Consider increasing the sleep durationThe current sleep of 50ms might not be sufficient for all the asynchronous operations to complete, especially if there's any additional processing between fetch calls.
- await sleep(50); // Increased slightly to be safer + await sleep(200); // Increased to ensure all async operations complete
159-159
: Consider using a more specific error messageThe current fallback response doesn't provide enough context about what request was expected.
- return new Response(`Unexpected fetch call to ${urlString} (call count: ${fetchCallCount})`, { status: 500 }); + return new Response( + `Unexpected fetch call to ${urlString} with method ${method} (call count: ${fetchCallCount})`, + { status: 500 } + );
153-155
: Add test coverage for token expiration handlingThe test currently verifies that the timestamp is displayed correctly, but doesn't test how the code handles an expired token.
Consider adding another test case or extending this one to verify the behavior when the token is expired by setting an expiration date in the past.
// Example of testing expired token scenario test("both", "AiderDebugTokenRefresh should handle expired tokens", async (denops) => { // Similar setup but with a past expiration date // ... return new Response(JSON.stringify({ token: "mock_copilot_session_token_value", expires_at: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago }), { status: 200, headers: { 'Content-Type': 'application/json' }}); // ... // Assert that appropriate warning message is displayed });
195-201
: Improve test assertion for message contentThe current checks only verify that messages include certain text, which could pass even if there's additional unexpected content.
- expectedMessages.forEach((expectedMsg, index) => { - assert( - recordedCmdMessages[index].includes(expectedMsg), - `Message at index ${index} ("${recordedCmdMessages[index]}") does not include expected content "${expectedMsg}"`, - ); - }); + expectedMessages.forEach((expectedMsg, index) => { + assertEquals( + recordedCmdMessages[index], + expectedMsg, + `Message at index ${index} doesn't match expected content`, + ); + });If exact matching is too strict, consider using a more structured assertion:
expectedMessages.forEach((expectedMsg, index) => { const recordedMsg = recordedCmdMessages[index]; assert( recordedMsg.includes(expectedMsg), `Message at index ${index} ("${recordedMsg}") does not include expected content "${expectedMsg}"`, ); // Optionally check for unexpected content assert( recordedMsg.length <= expectedMsg.length + 20, `Message at index ${index} has too much extra content: "${recordedMsg}"`, ); });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
📒 Files selected for processing (1)
tests/aider_test.ts
(2 hunks)
🧰 Additional context used
🪛 GitHub Actions: Test
tests/aider_test.ts
[error] 180-180: Test failure in 'AiderDebugTokenRefresh should perform device flow and display token': AssertionError - Values are not equal: Fetch should be called 4 times (1 device, 2 GH poll, 1 Copilot poll). Actual: 0, Expected: 4.
🔇 Additional comments (1)
tests/aider_test.ts (1)
99-108
: URL handling implementation looks correctThis implementation correctly handles all possible input types for the fetch function, addressing the type error from the previous review.
tests/aider_test.ts
Outdated
|
||
// Short sleep to allow all setTimeout(..., 0) in the polling loop to resolve. | ||
// The interval is 0 in mock, but setTimeout still defers execution. | ||
await sleep(50); // Increased slightly to be safer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve test reliability with a waitForCondition function
Instead of using a fixed sleep time, consider implementing a helper function that waits until a condition is met or a timeout occurs.
- await sleep(50); // Increased slightly to be safer
+ // Wait for all fetch calls to complete or timeout after 2 seconds
+ const startTime = Date.now();
+ while (fetchCallCount < 4 && Date.now() - startTime < 2000) {
+ await sleep(50);
+ }
For greater reusability, consider adding this helper function to your assertions:
// Add to assertions.ts
export async function waitForCondition(
condition: () => boolean,
timeout = 2000,
interval = 50
): Promise<boolean> {
const startTime = Date.now();
while (!condition() && Date.now() - startTime < timeout) {
await sleep(interval);
}
return condition();
}
// Then in test:
await waitForCondition(() => fetchCallCount >= 4);
🤖 Prompt for AI Agents
In tests/aider_test.ts at line 178, replace the fixed sleep call with a reusable
waitForCondition helper function that repeatedly checks a condition until it is
met or a timeout occurs. Implement waitForCondition in assertions.ts to accept a
condition callback, timeout, and interval, then use it in the test to wait until
fetchCallCount reaches the desired value instead of sleeping a fixed time.
tests/aider_test.ts
Outdated
} | ||
const method = init?.method || "GET"; | ||
const headers = init?.headers as Record<string, string>; | ||
const body = init?.body ? JSON.parse(init.body as string) : {}; | ||
|
||
if (urlString.includes("https://github.com/login/device/code")) { | ||
assertEquals(method, "POST"); | ||
assertEquals(headers["Accept"], "application/json"); | ||
assertEquals(headers["Content-Type"], "application/json"); | ||
assertEquals(body.client_id, mockClientId); | ||
assertEquals(body.scope, "copilot"); | ||
return new Response(JSON.stringify({ | ||
device_code: "mock_device_code", | ||
user_code: "MOCK-USER-CODE", | ||
verification_uri: "https://github.com/login/device", | ||
expires_in: 5, // 5 seconds for test | ||
interval: 0, // Poll immediately for test | ||
}), { status: 200, headers: { 'Content-Type': 'application/json' }}); | ||
} else if (urlString.includes("https://github.com/login/oauth/access_token")) { | ||
assertEquals(method, "POST"); | ||
assertEquals(headers["Accept"], "application/json"); | ||
assertEquals(headers["Content-Type"], "application/json"); | ||
assertEquals(body.client_id, mockClientId); | ||
assertEquals(body.device_code, "mock_device_code"); | ||
assertEquals(body.grant_type, "urn:ietf:params:oauth:grant-type:device_code"); | ||
|
||
if (fetchCallCount === 2) { // First poll for GitHub token | ||
return new Response(JSON.stringify({ error: "authorization_pending" }), { status: 200, headers: { 'Content-Type': 'application/json' }}); | ||
} else if (fetchCallCount === 3) { // Second poll for GitHub token - success | ||
return new Response(JSON.stringify({ | ||
access_token: "mock_access_token_value", | ||
token_type: "bearer", | ||
scope: "copilot", | ||
}), { status: 200, headers: { 'Content-Type': 'application/json' }}); | ||
} | ||
} else if (urlString.includes("https://api.github.com/copilot_internal/v2/token")) { | ||
assertEquals(fetchCallCount, 4, "Copilot token fetch should be the 4th call"); | ||
assertEquals(method, "GET"); | ||
assertEquals(headers["Authorization"], "token mock_access_token_value"); | ||
assertEquals(headers["User-Agent"], "Aider.vim/0.1.0"); | ||
assertEquals(headers["Accept"], "application/json"); | ||
assertEquals(headers["Editor-Plugin-Version"], "Aider.vim/0.1.0"); | ||
assertEquals(headers["Editor-Version"], "Vim/Denops"); | ||
return new Response(JSON.stringify({ | ||
token: "mock_copilot_session_token_value", | ||
expires_at: 1678886400, // 2023-03-15T12:00:00Z | ||
another_field: "test", // To ensure only expected fields are used | ||
}), { status: 200, headers: { 'Content-Type': 'application/json' }}); | ||
} | ||
// Fallback for unexpected calls | ||
return new Response(`Unexpected fetch call to ${urlString} (call count: ${fetchCallCount})`, { status: 500 }); | ||
}; | ||
|
||
denops.cmd = async (command: string, ...args: unknown[]): Promise<void> => { | ||
if (typeof command === 'string' && command.startsWith("echomsg")) { | ||
const msg = command.substring("echomsg ".length).replace(/^['"]|['"]$/g, ""); | ||
recordedCmdMessages.push(msg); | ||
} | ||
// If other denops commands were used by the tested function, they would need to be handled here. | ||
// For AiderDebugTokenRefresh, it primarily uses echomsg. | ||
// Other commands used by the main plugin setup are not part of this specific command's logic. | ||
return Promise.resolve(); // Simulate successful execution for echomsg | ||
}; | ||
globalThis.fetch = mockFetch; | ||
|
||
try { | ||
await denops.cmd("AiderDebugTokenRefresh"); | ||
|
||
// Short sleep to allow all setTimeout(..., 0) in the polling loop to resolve. | ||
// The interval is 0 in mock, but setTimeout still defers execution. | ||
await sleep(50); // Increased slightly to be safer | ||
|
||
assertEquals(fetchCallCount, 4, "Fetch should be called 4 times (1 device, 2 GH poll, 1 Copilot poll)"); | ||
|
||
// Assert messages in approximate order | ||
const expectedMessages = [ | ||
"Starting GitHub Device Flow for token refresh...", | ||
"Please open your browser and go to: https://github.com/login/device", | ||
"Enter this code: MOCK-USER-CODE", | ||
"Authorization pending...", | ||
"GitHub Access Token obtained successfully: mock_access_token_value", | ||
"Fetching Copilot session token...", | ||
"Successfully obtained Copilot session token.", | ||
"Copilot Session Token: mock_copilot_session_token_value", | ||
"Expires At: 2023-03-15T12:00:00.000Z", | ||
]; | ||
|
||
assertEquals(recordedCmdMessages.length, expectedMessages.length, "Incorrect number of echomsg calls"); | ||
expectedMessages.forEach((expectedMsg, index) => { | ||
assert( | ||
recordedCmdMessages[index].includes(expectedMsg), | ||
`Message at index ${index} ("${recordedCmdMessages[index]}") does not include expected content "${expectedMsg}"`, | ||
); | ||
}); | ||
|
||
} finally { | ||
globalThis.fetch = originalFetch; | ||
denops.cmd = originalDenopsCmd; | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix failing test for AiderDebugTokenRefresh
The test is failing with: "Fetch should be called 4 times (1 device, 2 GH poll, 1 Copilot poll). Actual: 0, Expected: 4."
This suggests the mock fetch function isn't being called during the test execution, indicating one of the following issues:
- The command is not properly implemented in the main plugin
- An error is occurring before fetch calls are made
- The test timing needs adjustment
#!/bin/bash
# Description: Verify the AiderDebugTokenRefresh command implementation
# Check if the command is properly registered in main.ts
echo "Checking for debugTokenRefresh registration in main.ts"
rg -A 2 "debugTokenRefresh" denops/aider/main.ts
# Check for implementation of the function
echo "Looking for debugTokenRefreshImpl implementation"
rg -A 5 "debugTokenRefreshImpl" denops/aider/main.ts
🧰 Tools
🪛 GitHub Actions: Test
[error] 180-180: Test failure in 'AiderDebugTokenRefresh should perform device flow and display token': AssertionError - Values are not equal: Fetch should be called 4 times (1 device, 2 GH poll, 1 Copilot poll). Actual: 0, Expected: 4.
🤖 Prompt for AI Agents
In tests/aider_test.ts from lines 91 to 207, the test for AiderDebugTokenRefresh
fails because the mock fetch function is not called, resulting in zero fetch
calls instead of four. To fix this, verify that the AiderDebugTokenRefresh
command is correctly registered and implemented in the main plugin code
(denops/aider/main.ts). Ensure the command triggers the fetch calls as expected.
Also, confirm there are no early errors preventing fetch usage and adjust test
timing if necessary to allow asynchronous operations to complete.
This commit introduces the following changes: 1. **New `AiderDebugToken` Command:** * Adds a new Vim command `:AiderDebugToken`. * This command implements the GitHub OAuth device flow to obtain and display a GitHub access token. * The core logic is encapsulated in a new reusable function `githubDeviceAuthImpl`. 2. **Refactor `AiderDebugTokenRefresh`:** * The existing `AiderDebugTokenRefresh` command's implementation (`debugTokenRefreshImpl`) has been refactored to call `githubDeviceAuthImpl` for the initial GitHub token acquisition, reducing code duplication. It then proceeds to fetch the Copilot session token. 3. **New Tests:** * Adds a new test case for the `AiderDebugToken` command in `tests/aider_test.ts`, with its own independent mocks. 4. **Test Fix Verification:** * Confirms that the fix for TypeScript error TS2339 (incorrect URL access in `mockFetch`) is correctly implemented in `tests/aider_test.ts` for robust URL string derivation from `fetch` input types (`string`, `URL`, or `Request`). This addresses your feedback requesting the `AiderDebugToken` command and aims to resolve the CI test failures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (1)
denops/aider/main.ts (1)
217-224
: Consider adding retry limits for persistent network errors.While the function correctly exits on network errors during polling, it lacks a retry counter or backoff strategy for transient network issues.
🧹 Nitpick comments (2)
denops/aider/main.ts (2)
239-314
: Fix excessive indentation in the Copilot token fetch section.The code from lines 256-266 has inconsistent and excessive indentation compared to the rest of the function, affecting readability.
const copilotTokenResponse = await fetch(copilotTokenUrl, { - method: "GET", - headers: { - "Authorization": `token ${githubAccessToken}`, - "User-Agent": "Aider.vim/0.1.0", - "Accept": "application/json", - "Editor-Plugin-Version": "Aider.vim/0.1.0", - "Editor-Version": "Vim/Denops", - }, - }); + method: "GET", + headers: { + "Authorization": `token ${githubAccessToken}`, + "User-Agent": "Aider.vim/0.1.0", + "Accept": "application/json", + "Editor-Plugin-Version": "Aider.vim/0.1.0", + "Editor-Version": "Vim/Denops", + }, + });Similar indentation issues appear in the following if-block and try-catch structure.
267-297
: Consider adding more explicit error handling for specific HTTP status codes.The current implementation only checks if the response is "ok" but doesn't handle specific HTTP status codes differently (like 401 for unauthorized, 403 for forbidden, etc.) which could provide more targeted error messages to users.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
📒 Files selected for processing (2)
denops/aider/main.ts
(2 hunks)tests/aider_test.ts
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/aider_test.ts
🧰 Additional context used
🪛 GitHub Actions: Test
denops/aider/main.ts
[error] 577-577: TypeScript error TS2345: Argument of type '() => Promise<string | null>' is not assignable to parameter of type '() => Promise'. Type 'Promise<string | null>' is not assignable to type 'Promise'. Type 'string | null' is not assignable to type 'void'. Type 'null' is not assignable to type 'void'.
denops/aider/main.ts
Outdated
await denops.cmd( | ||
`echomsg "Error initiating GitHub device flow: ${deviceAuthResponse.status} ${errorText}"`, | ||
); | ||
console.error( | ||
`Error initiating GitHub device flow: ${deviceAuthResponse.status}`, | ||
errorText, | ||
); | ||
return null; | ||
} | ||
|
||
const deviceAuthData = await deviceAuthResponse.json(); | ||
const { device_code, user_code, verification_uri, expires_in, interval } = | ||
deviceAuthData; | ||
|
||
await denops.cmd( | ||
`echomsg "Please open your browser and go to: ${verification_uri}"`, | ||
); | ||
await denops.cmd(`echomsg "Enter this code: ${user_code}"`); | ||
|
||
// Step 2: Poll for Token | ||
let pollingInterval = interval * 1000; // Convert seconds to milliseconds | ||
const startTime = Date.now(); | ||
const timeoutMs = expires_in * 1000; | ||
|
||
while (Date.now() - startTime < timeoutMs) { | ||
await new Promise((resolve) => setTimeout(resolve, pollingInterval)); | ||
|
||
try { | ||
const tokenResponse = await fetch(tokenUrl, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", // Added for consistency | ||
"Accept": "application/json", | ||
}, | ||
body: JSON.stringify({ | ||
client_id: clientId, | ||
device_code: device_code, | ||
grant_type: "urn:ietf:params:oauth:grant-type:device_code", | ||
}), | ||
}); | ||
|
||
if (!tokenResponse.ok) { | ||
const errorData = await tokenResponse.json(); | ||
if (errorData.error === "authorization_pending") { | ||
await denops.cmd('echomsg "GitHub authorization pending..."'); | ||
} else if (errorData.error === "slow_down") { | ||
pollingInterval += 5000; | ||
await denops.cmd( | ||
`echomsg "Slowing down GitHub polling. New interval: ${ | ||
pollingInterval / 1000 | ||
}s"`, | ||
); | ||
} else { | ||
await denops.cmd( | ||
`echomsg "Error polling for GitHub token: ${errorData.error} - ${errorData.error_description}"`, | ||
); | ||
console.error("Error polling for GitHub token:", errorData); | ||
return null; | ||
} | ||
} else { | ||
const tokenData = await tokenResponse.json(); | ||
if (tokenData.access_token) { | ||
await denops.cmd( | ||
`echomsg "Successfully obtained GitHub access token: ${tokenData.access_token}"`, | ||
); | ||
console.log("Obtained GitHub Access Token:", tokenData.access_token); | ||
return tokenData.access_token; | ||
} else if (tokenData.error) { | ||
await denops.cmd( | ||
`echomsg "Error in GitHub token response: ${tokenData.error} - ${tokenData.error_description}"`, | ||
); | ||
console.error("Error in GitHub token response:", tokenData); | ||
return null; | ||
} else { | ||
await denops.cmd('echomsg "Unknown error during GitHub token polling."'); | ||
console.error("Unknown error during GitHub token polling", tokenData); | ||
return null; | ||
} | ||
} | ||
} catch (pollError) { | ||
await denops.cmd( | ||
`echomsg "Network error during GitHub polling: ${pollError.message}"`, | ||
); | ||
console.error("Network error during GitHub polling:", pollError); | ||
// Potentially retry or backoff further depending on strategy, but for now, exit. | ||
return null; | ||
} | ||
} | ||
|
||
await denops.cmd( | ||
`echomsg "GitHub device flow timed out after ${expires_in} seconds."`, | ||
); | ||
console.error("GitHub device flow timed out."); | ||
return null; | ||
} catch (error) { | ||
await denops.cmd(`echomsg "An unexpected error occurred in GitHub device flow: ${error.message}"`); | ||
console.error("An unexpected error occurred in GitHub device flow:", error); | ||
return null; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Good OAuth device flow implementation with room for improvements.
The GitHub device authentication flow is well-implemented with proper error handling and state transitions. However, consider these enhancements:
- The client ID and scope are hardcoded. Consider making them configurable parameters or environment variables.
- There's no explicit retry limit for network errors during polling.
Consider verifying if the client ID is intended to be publicly visible or should be stored more securely:
🌐 Web query:
Is the GitHub client ID "Iv1.b507a08c87ecfe98" a public or private credential?
💡 Result:
The GitHub client ID "Iv1.b507a08c87ecfe98" is a public credential.
Client IDs are meant to be public and are used to identify the application when making OAuth requests. The client secret, on the other hand, is private and should be kept confidential to ensure security. Sharing the client ID is safe, but the client secret must never be exposed publicly.
Enhance configurability and add retry limits for GitHub Device Flow
While the device flow logic and error handling look solid, consider the following improvements:
- The GitHub client ID (
Iv1.b507a08c87ecfe98
) is a public credential and safe to include, but extracting bothclientId
andscope
into configurable parameters (e.g. environment variables or denops options) will make the flow more flexible and testable. - Introduce an explicit retry/backoff limit for network errors during the polling loop to avoid infinite retries on transient failures. For example:
• Track a maximum retry count for fetch failures in thecatch
block.
• Apply increasing backoff intervals (e.g. exponential) up to a cap before aborting with an error message.
🤖 Prompt for AI Agents
In denops/aider/main.ts between lines 117 and 237, the GitHub device
authentication flow uses a hardcoded client ID and scope, and lacks a retry
limit for network errors during token polling. Refactor the code to accept
clientId and scope as configurable parameters or environment variables instead
of hardcoding them. Additionally, implement a retry limit with exponential
backoff for network errors inside the polling loop's catch block to prevent
infinite retries; track the number of retries, increase the delay on each
failure up to a maximum, and abort with an error message if the limit is
exceeded.
denops/aider/main.ts
Outdated
if (tokenData.access_token) { | ||
await denops.cmd( | ||
`echomsg "Successfully obtained GitHub access token: ${tokenData.access_token}"`, | ||
); | ||
console.log("Obtained GitHub Access Token:", tokenData.access_token); | ||
return tokenData.access_token; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid logging sensitive tokens to the console.
Similar to the Copilot token issue, avoid logging the GitHub access token in plain text to the console logs.
await denops.cmd(
- `echomsg "Successfully obtained GitHub access token: ${tokenData.access_token}"`,
+ `echomsg "Successfully obtained GitHub access token: ${tokenData.access_token.substring(0, 10)}..."`
);
- console.log("Obtained GitHub Access Token:", tokenData.access_token);
+ console.log("Obtained GitHub Access Token (hidden for security)");
return tokenData.access_token;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (tokenData.access_token) { | |
await denops.cmd( | |
`echomsg "Successfully obtained GitHub access token: ${tokenData.access_token}"`, | |
); | |
console.log("Obtained GitHub Access Token:", tokenData.access_token); | |
return tokenData.access_token; | |
if (tokenData.access_token) { | |
await denops.cmd( | |
`echomsg "Successfully obtained GitHub access token: ${tokenData.access_token.substring(0, 10)}..."`, | |
); | |
console.log("Obtained GitHub Access Token (hidden for security)"); | |
return tokenData.access_token; |
🤖 Prompt for AI Agents
In denops/aider/main.ts around lines 199 to 204, the code logs the GitHub access
token to the console, which exposes sensitive information. Remove or comment out
the console.log statement that outputs the access token to prevent leaking it in
logs. Keep the success message to the user but avoid printing the actual token
value anywhere.
if (copilotTokenData.token && copilotTokenData.expires_at) { | ||
await denops.cmd( | ||
`echomsg "Copilot Session Token: ${copilotTokenData.token}"`, | ||
); | ||
await denops.cmd( | ||
`echomsg "Expires At: ${ | ||
new Date(copilotTokenData.expires_at * 1000).toISOString() | ||
}"`, | ||
); | ||
console.log("Copilot Session Token Data:", copilotTokenData); | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid logging sensitive tokens to the console.
While showing tokens in the UI might be necessary for debugging, logging them to the console creates a security risk as they could appear in log files.
- await denops.cmd(
- `echomsg "Copilot Session Token: ${copilotTokenData.token}"`,
- );
+ await denops.cmd(
+ `echomsg "Copilot Session Token: ${copilotTokenData.token.substring(0, 10)}..."`
+ );
- console.log("Copilot Session Token Data:", copilotTokenData);
+ console.log("Copilot Session Token acquired with expiration:",
+ new Date(copilotTokenData.expires_at * 1000).toISOString());
🤖 Prompt for AI Agents
In denops/aider/main.ts around lines 272 to 282, the code logs the Copilot
session token to the console, which poses a security risk by exposing sensitive
tokens in log files. Remove or comment out the console.log statement that
outputs the token data to prevent sensitive information from being logged, while
keeping the UI messages intact if needed for debugging.
await command( | ||
"debugToken", | ||
"0", | ||
githubDeviceAuthImpl, | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix TypeScript type error in command registration.
The pipeline failed because githubDeviceAuthImpl
returns Promise<string | null>
but the command registration expects a function returning Promise<void>
.
Create a wrapper function that returns Promise<void>
:
await command(
"debugToken",
"0",
- githubDeviceAuthImpl,
+ async (): Promise<void> => {
+ await githubDeviceAuthImpl();
+ },
),
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
await command( | |
"debugToken", | |
"0", | |
githubDeviceAuthImpl, | |
), | |
await command( | |
"debugToken", | |
"0", | |
async (): Promise<void> => { | |
await githubDeviceAuthImpl(); | |
}, | |
), |
🧰 Tools
🪛 GitHub Actions: Test
[error] 577-577: TypeScript error TS2345: Argument of type '() => Promise<string | null>' is not assignable to parameter of type '() => Promise'. Type 'Promise<string | null>' is not assignable to type 'Promise'. Type 'string | null' is not assignable to type 'void'. Type 'null' is not assignable to type 'void'.
🤖 Prompt for AI Agents
In denops/aider/main.ts around lines 574 to 578, the command registration
expects a function returning Promise<void>, but githubDeviceAuthImpl returns
Promise<string | null>, causing a TypeScript type error. Fix this by creating a
wrapper async function that calls githubDeviceAuthImpl, awaits its result, and
returns void, then pass this wrapper function to the command registration
instead of githubDeviceAuthImpl directly.
fix: Correct GitHub token polling for authorization_pending Addresses an issue where the AiderDebugToken command would prematurely report "GitHub device authentication failed" after receiving an "authorization_pending" status from GitHub. The polling logic in `githubDeviceAuthImpl` has been refactored to correctly handle cases where GitHub's token endpoint might return a 200 OK HTTP status but include an error (like "authorization_pending") in the JSON response body. The updated logic now parses the JSON response body first and checks for an `access_token` for success, or an `error` field (like `authorization_pending` or `slow_down`) to continue polling or handle other errors appropriately. This ensures the command waits for your authorization as intended.
This commit introduces several enhancements to GitHub Copilot token management: 1. **Set OPENAI_API_KEY on Token Acquisition:** * The `AiderDebugToken` and `AiderDebugTokenRefresh` commands, upon successful GitHub device authentication, now set the obtained GitHub access token to `Deno.env.set("OPENAI_API_KEY", token)`. * This makes the GitHub token available within the current Vim/Deno session for other commands. * You will be notified via `echomsg`. 2. **New `AiderRenewCopilotToken` Command:** * Adds a new Vim command `:AiderRenewCopilotToken`. * This command allows renewal of the Copilot session token *without* browser interaction. * It reads the GitHub access token from the `OPENAI_API_KEY` environment variable. * If the key is found, it calls the GitHub Copilot API (`/copilot_internal/v2/token`) to get a new session token. * If the key is not found, it instructs you on how to set it. 3. **Updated Tests:** * The test for `AiderDebugToken` now verifies that `Deno.env.set` is called correctly. * New test cases for `AiderRenewCopilotToken` cover scenarios where `OPENAI_API_KEY` is set and not set, with appropriate mocking of `fetch` and `Deno.env`. 4. **CI Fix Maintenance:** * Confirmed that all `mockFetch` instances in tests use the correct URL parsing logic to prevent CI failures. These changes provide more flexible ways to manage and renew Copilot tokens, including a non-interactive renewal option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (3)
denops/aider/main.ts (3)
185-191
:⚠️ Potential issueAvoid leaking full access tokens in UI / logs
The code echoes and logs the entire GitHub access token, which is highly sensitive. Previous reviews already flagged this. Please mask or omit the token:
-`echomsg "Successfully obtained GitHub access token: ${responseBody.access_token}"` -console.log("Obtained GitHub Access Token:", responseBody.access_token); +const masked = responseBody.access_token.slice(0,4) + "…"; +await denops.cmd(`echomsg "Successfully obtained GitHub access token: ${masked}"`); +console.log("GitHub access token obtained (masked).");Apply the same masking pattern to Copilot tokens later in the file.
232-238
: 🛠️ Refactor suggestionNo retry/back-off strategy on transient network failures
A single network glitch causes an immediate abort (
return null
). Implement a bounded retry with exponential back-off instead of giving up at the first hiccup – the polling phase is already time-boxed byexpires_in
.(See earlier review note; still unresolved.)
667-671
:⚠️ Potential issueCompilation fails – return type mismatch in command registration
githubDeviceAuthImpl
returnsPromise<string | null>
butcommand()
expects aPromise<void>
. This is exactly the TS2345 error shown by CI. Wrap the call or change the impl’s signature:- await command( - "debugToken", - "0", - githubDeviceAuthImpl, - ), + await command( + "debugToken", + "0", + async (): Promise<void> => { await githubDeviceAuthImpl(); }, + ),Repeat for any other registrations that pass a non-void promise.
🧰 Tools
🪛 GitHub Actions: Test
[error] 670-670: TS2345: Argument of type '() => Promise<string | null>' is not assignable to parameter of type '() => Promise'. Type 'Promise<string | null>' is not assignable to type 'Promise'. Type 'string | null' is not assignable to type 'void'. Type 'null' is not assignable to type 'void'.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
📒 Files selected for processing (2)
denops/aider/main.ts
(2 hunks)tests/aider_test.ts
(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
tests/aider_test.ts (2)
tests/testRunner.ts (1)
test
(22-37)tests/assertions.ts (1)
sleep
(6-7)
🪛 GitHub Actions: Test
denops/aider/main.ts
[error] 670-670: TS2345: Argument of type '() => Promise<string | null>' is not assignable to parameter of type '() => Promise'. Type 'Promise<string | null>' is not assignable to type 'Promise'. Type 'string | null' is not assignable to type 'void'. Type 'null' is not assignable to type 'void'.
🔇 Additional comments (1)
tests/aider_test.ts (1)
183-196
: Brittle hard-coded message expectations out of sync with codeSeveral expected messages (
"Starting GitHub Device Flow for token refresh..."
,"Authorization pending..."
, etc.) do not exist inmain.ts
. Matching against exact strings causes fragile tests and false failures.Either relax the assertions (e.g. via regex /
includes
) or update the production messages, but keep the two sides consistent.
const clientId = "Iv1.b507a08c87ecfe98"; | ||
const scope = "read:user"; | ||
const deviceAuthUrl = "https://github.com/login/device/code"; | ||
const tokenUrl = "https://github.com/login/oauth/access_token"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hard-coded client info & wrong scope – tests will fail
githubDeviceAuthImpl
bakes the GitHub client_id
and the scope "read:user"
directly into the function.
- The tests included in this PR (see
tests/aider_test.ts
l.113-118) – and, more importantly, the Copilot token endpoint – expect the scope to becopilot
, notread:user
; this mismatch will cause the device-code request assertion (and real usage) to fail. - Keeping credentials & scopes hard-coded makes the function impossible to reuse for other GitHub apps or scopes.
Consider:
-const clientId = "Iv1.b507a08c87ecfe98";
-const scope = "read:user";
+// Prefer env-vars with sensible fall-backs so the user can override them.
+const clientId = Deno.env.get("AIDER_GH_CLIENT_ID") ?? "Iv1.b507a08c87ecfe98";
+const scope = Deno.env.get("AIDER_GH_SCOPE") ?? "copilot";
…and make scope
a parameter of githubDeviceAuthImpl
so debugTokenRefreshImpl
can request "copilot"
while debugToken
can still default to "read:user"
.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const clientId = "Iv1.b507a08c87ecfe98"; | |
const scope = "read:user"; | |
const deviceAuthUrl = "https://github.com/login/device/code"; | |
const tokenUrl = "https://github.com/login/oauth/access_token"; | |
// Prefer env-vars with sensible fall-backs so the user can override them. | |
const clientId = Deno.env.get("AIDER_GH_CLIENT_ID") ?? "Iv1.b507a08c87ecfe98"; | |
const scope = Deno.env.get("AIDER_GH_SCOPE") ?? "copilot"; | |
const deviceAuthUrl = "https://github.com/login/device/code"; | |
const tokenUrl = "https://github.com/login/oauth/access_token"; |
🤖 Prompt for AI Agents
In denops/aider/main.ts around lines 118 to 121, the GitHub client_id and scope
are hard-coded with the scope set to "read:user", which causes test failures and
limits reuse. Modify githubDeviceAuthImpl to accept scope as a parameter instead
of hard-coding it, allowing callers like debugTokenRefreshImpl to specify
"copilot" while defaulting to "read:user" where needed. Remove the fixed scope
variable and update all calls to pass the appropriate scope argument.
tests/aider_test.ts
Outdated
if (urlString.includes("https://github.com/login/device/code")) { | ||
assertEquals(method, "POST"); | ||
assertEquals(headers["Accept"], "application/json"); | ||
assertEquals(headers["Content-Type"], "application/json"); | ||
assertEquals(body.client_id, mockClientId); | ||
assertEquals(body.scope, "copilot"); | ||
return new Response(JSON.stringify({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assertion uses scope: "copilot"
but implementation sends "read:user"
The test will inevitably fail unless the production code (or the test) is updated. Align the expected scope with the real request or make the scope configurable and pass "copilot"
when refreshing Copilot tokens.
🤖 Prompt for AI Agents
In tests/aider_test.ts around lines 113 to 119, the test asserts that the
request body scope is "copilot" but the actual implementation sends "read:user".
To fix this, update the test assertion to expect "read:user" for the scope value
or modify the production code to send "copilot" if that is the intended
behavior. Alternatively, make the scope configurable and ensure the test passes
the correct scope value when refreshing Copilot tokens.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (6)
denops/aider/main.ts (6)
117-121
: 🛠️ Refactor suggestionReplace hardcoded GitHub client ID and scope with configurable parameters.
The GitHub client ID and scope are hardcoded values, which limits flexibility and causes issues with tests. According to PR objectives, the scope should be "copilot" instead of "read:user" to work with the GitHub Copilot token refresh.
-async function githubDeviceAuthImpl(): Promise<string | null> { - const clientId = "Iv1.b507a08c87ecfe98"; - const scope = "read:user"; +async function githubDeviceAuthImpl(requestedScope?: string): Promise<string | null> { + // Prefer env-vars with sensible fall-backs for flexibility + const clientId = Deno.env.get("AIDER_GH_CLIENT_ID") ?? "Iv1.b507a08c87ecfe98"; + const scope = requestedScope ?? Deno.env.get("AIDER_GH_SCOPE") ?? "read:user";
162-240
: 🛠️ Refactor suggestionAdd retry limits and backoff strategy for polling.
The polling loop lacks an explicit retry limit for network errors, which could lead to prolonged polling cycles in case of persistent network issues.
let pollingInterval = interval * 1000; // Convert seconds to milliseconds const startTime = Date.now(); const timeoutMs = expires_in * 1000; + let retryCount = 0; + const maxRetries = 5; while (Date.now() - startTime < timeoutMs) { await new Promise((resolve) => setTimeout(resolve, pollingInterval)); try { // Inside the while loop's try {} block in githubDeviceAuthImpl const tokenResponse = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json", }, body: JSON.stringify({ client_id: clientId, device_code: device_code, grant_type: "urn:ietf:params:oauth:grant-type:device_code", }), }); // Reset retry count on successful request + retryCount = 0; const responseBody = await tokenResponse.json(); // Parse JSON early // ... rest of the success/error handling ... } catch (pollError) { + retryCount++; + if (retryCount >= maxRetries) { + await denops.cmd( + `echomsg "Maximum retry attempts (${maxRetries}) reached during GitHub polling. Aborting."`, + ); + console.error("Maximum retry attempts reached during GitHub polling:", pollError); + return null; + } + + // Implement exponential backoff + pollingInterval = Math.min(pollingInterval * 1.5, 30000); // Max 30 seconds await denops.cmd( - `echomsg "Network error during GitHub polling: ${pollError.message}"`, + `echomsg "Network error during GitHub polling: ${pollError.message}. Retrying in ${pollingInterval/1000}s (${retryCount}/${maxRetries})..."`, ); console.error("Network error during GitHub polling:", pollError); } }
185-187
: 🛠️ Refactor suggestionAvoid logging sensitive tokens to the console.
The GitHub access token is logged to the console and echoed in plain text, which is a security risk.
await denops.cmd( - `echomsg "Successfully obtained GitHub access token: ${responseBody.access_token}"`, + `echomsg "Successfully obtained GitHub access token: ${responseBody.access_token.substring(0, 10)}..."` ); -console.log("Obtained GitHub Access Token:", responseBody.access_token); +console.log("Obtained GitHub Access Token (hidden for security)");
296-296
: 🛠️ Refactor suggestionAvoid logging sensitive Copilot token data.
The Copilot session token data is logged to the console, which exposes sensitive information.
-console.log("Renewed Copilot Session Token Data:", responseData); +console.log("Renewed Copilot Session Token (hidden for security) with expiration:", + new Date(responseData.expires_at * 1000).toISOString());
372-382
: 🛠️ Refactor suggestionAvoid logging sensitive Copilot token data to console.
The Copilot session token is logged to the console in plain text, which is a security risk.
if (copilotTokenData.token && copilotTokenData.expires_at) { await denops.cmd( - `echomsg "Copilot Session Token: ${copilotTokenData.token}"`, + `echomsg "Copilot Session Token: ${copilotTokenData.token.substring(0, 10)}..."` ); await denops.cmd( `echomsg "Expires At: ${ new Date(copilotTokenData.expires_at * 1000).toISOString() }"`, ); - console.log("Copilot Session Token Data:", copilotTokenData); + console.log("Copilot Session Token acquired with expiration:", + new Date(copilotTokenData.expires_at * 1000).toISOString()); }
674-678
:⚠️ Potential issueFix TypeScript type error in command registration.
The command registration expects a function returning
Promise<void>
butgithubDeviceAuthImpl
returnsPromise<string | null>
, causing a TypeScript type error.await command( "debugToken", "0", - githubDeviceAuthImpl, + async (): Promise<void> => { + await githubDeviceAuthImpl(); + }, ),🧰 Tools
🪛 GitHub Actions: Test
[error] 677-677: TS2345: Argument of type '() => Promise<string | null>' is not assignable to parameter of type '() => Promise'. Type 'Promise<string | null>' is not assignable to type 'Promise'. Type 'string | null' is not assignable to type 'void'. Type 'null' is not assignable to type 'void'.
🧹 Nitpick comments (4)
denops/aider/main.ts (3)
254-255
: Return type mismatch in renewCopilotTokenImpl.The function doesn't explicitly return a value, but it's implementing an async function that should return a Promise.
-async function renewCopilotTokenImpl(): Promise<void> { +async function renewCopilotTokenImpl(): Promise<void> {This is actually correct as is, but it's good practice to add an explicit return at the end of the function for clarity:
} catch (e) { // Catch any unexpected errors in the outer try block of renewCopilotTokenImpl await denops.cmd(`echomsg "Unexpected error in renewCopilotTokenImpl: ${e.message}"`); console.error("Unexpected error in renewCopilotTokenImpl:", e); } + return; }
680-684
: Use consistent error handling for the debugTokenRefresh command.The code pattern for command handling is slightly different here from the other commands. Consider using a wrapper function as you did for the fix in the previous command.
await command( "debugTokenRefresh", "0", - debugTokenRefreshImpl, + async (): Promise<void> => { + await debugTokenRefreshImpl(); + }, ),
686-690
: Use consistent error handling for the renewCopilotToken command.For consistency with the other commands and to prevent potential type issues, wrap this function call as well.
await command( "renewCopilotToken", "0", - renewCopilotTokenImpl, + async (): Promise<void> => { + await renewCopilotTokenImpl(); + }, ),README.md (1)
167-174
: Add missing punctuation between existing content and new command documentation.There should be a blank line or appropriate punctuation between the previous command description and the new
AiderDebugTokenRefresh
documentation.- `:AiderVoice` - Sends voice commands to Aider (using Whisper). -`AiderDebugTokenRefresh` first checks the `OPENAI_API_KEY` environment variable. +- `:AiderVoice` - Sends voice commands to Aider (using Whisper). + +- `:AiderDebugTokenRefresh` - First checks the `OPENAI_API_KEY` environment variable.🧰 Tools
🪛 LanguageTool
[uncategorized] ~167-~167: Possible missing comma found.
Context: ...ing Whisper).AiderDebugTokenRefresh
first checks theOPENAI_API_KEY
environment...(AI_HYDRA_LEO_MISSING_COMMA)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting
📒 Files selected for processing (4)
LICENSE
(1 hunks)README.md
(3 hunks)denops/aider/main.ts
(2 hunks)tests/aider_test.ts
(2 hunks)
✅ Files skipped from review due to trivial changes (2)
- tests/aider_test.ts
- LICENSE
🧰 Additional context used
🪛 GitHub Actions: Test
denops/aider/main.ts
[error] 342-342: TS2322: Type 'string | null' is not assignable to type 'string | undefined'. Type 'null' is not assignable to type 'string | undefined'.
[error] 677-677: TS2345: Argument of type '() => Promise<string | null>' is not assignable to parameter of type '() => Promise'. Type 'Promise<string | null>' is not assignable to type 'Promise'. Type 'string | null' is not assignable to type 'void'. Type 'null' is not assignable to type 'void'.
🪛 LanguageTool
README.md
[uncategorized] ~167-~167: Possible missing comma found.
Context: ...ing Whisper). AiderDebugTokenRefresh
first checks the OPENAI_API_KEY
environment...
(AI_HYDRA_LEO_MISSING_COMMA)
await denops.cmd('echomsg "Using OPENAI_API_KEY from environment."'); | ||
} else { | ||
await denops.cmd('echomsg "Starting GitHub Device Flow for token refresh..."'); | ||
githubAccessToken = await githubDeviceAuthImpl(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix type error in variable assignment.
The pipeline is failing because githubDeviceAuthImpl
returns Promise<string | null>
but the type expected is string | undefined
.
-githubAccessToken = await githubDeviceAuthImpl();
+githubAccessToken = await githubDeviceAuthImpl("copilot") ?? undefined;
This change addresses the type error by using the nullish coalescing operator to convert null to undefined, and it also passes the correct "copilot" scope to the function.
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 GitHub Actions: Test
[error] 342-342: TS2322: Type 'string | null' is not assignable to type 'string | undefined'. Type 'null' is not assignable to type 'string | undefined'.
🤖 Prompt for AI Agents
In denops/aider/main.ts at line 342, the assignment to githubAccessToken causes
a type error because githubDeviceAuthImpl returns Promise<string | null> but
githubAccessToken expects string | undefined. Fix this by using the nullish
coalescing operator to convert null to undefined and ensure the correct
"copilot" scope is passed to githubDeviceAuthImpl.
If it exists, the plugin uses it to refresh the Copilot session token without | ||
opening a browser. Otherwise, it falls back to the GitHub device flow. Set the | ||
variable before starting Neovim if you already have a valid GitHub token: | ||
|
||
```bash | ||
export OPENAI_API_KEY=ghp_your_existing_token | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Document all new token-related commands consistently.
The documentation mentions AiderDebugTokenRefresh
but doesn't document AiderDebugToken
or AiderRenewCopilotToken
which were also added in this PR.
If it exists, the plugin uses it to refresh the Copilot session token without
opening a browser. Otherwise, it falls back to the GitHub device flow. Set the
variable before starting Neovim if you already have a valid GitHub token:
```bash
export OPENAI_API_KEY=ghp_your_existing_token
+- :AiderDebugToken
- Initiates the GitHub device flow to obtain a GitHub access token.
+
+- :AiderRenewCopilotToken
- Attempts to renew the Copilot session token using the existing GitHub token.
<details>
<summary>🤖 Prompt for AI Agents</summary>
In README.md around lines 168 to 174, the documentation inconsistently covers
token-related commands by mentioning only AiderDebugTokenRefresh. Add clear
descriptions for the new commands AiderDebugToken and AiderRenewCopilotToken,
explaining their purposes and usage, and format them consistently with the
existing command documentation to ensure all token-related commands are fully
documented.
</details>
<!-- This is an auto-generated comment by CodeRabbit -->
新しいVimコマンド
AiderDebugTokenRefresh
を実装しました。これにより、OAuthデバイスフローを使用してGitHub Copilotトークンを取得/更新できます。このコマンドは以下の手順を実行します。
この機能は、
denops/aider/main.ts
内のdebugTokenRefreshImpl
関数に実装されています。テストケースが
tests/aider_test.ts
に追加されました。これは、外部のfetch
呼び出しをモックしてデバイスフローをシミュレートし、正しいメッセージが表示されることを検証します。注:使用されているGitHubクライアントIDとスコープは一般的なプレースホルダーです。コマンド名は、ディスパッチャーメソッド
debugTokenRefresh
に基づいてAiderDebugTokenRefresh
と名付けられています。Summary by CodeRabbit
New Features
Documentation
Tests
Chores