@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed GitHub Copilot device-code login polling to wait before the first token poll, avoiding incorrect device-code failures for some users after browser authorization ([#6187](https://github.com/earendil-works/pi/issues/6187)).
|
||||||
- Fixed OpenAI Codex user-agent construction to synchronously load Node OS metadata, avoiding a startup race that could report `pi (browser)` in Node/Bun.
|
- Fixed OpenAI Codex user-agent construction to synchronously load Node OS metadata, avoiding a startup race that could report `pi (browser)` in Node/Bun.
|
||||||
- Fixed Fireworks GLM 5.2 Fast to use the OpenAI-compatible endpoint and `thinkingLevelMap`, aligning it with GLM 5.2 ([#6195](https://github.com/earendil-works/pi/issues/6195)).
|
- Fixed Fireworks GLM 5.2 Fast to use the OpenAI-compatible endpoint and `thinkingLevelMap`, aligning it with GLM 5.2 ([#6195](https://github.com/earendil-works/pi/issues/6195)).
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export type OAuthDeviceCodePollResult<T> = OAuthDeviceCodeIncompletePollResult |
|
|||||||
export type OAuthDeviceCodePollOptions<T> = {
|
export type OAuthDeviceCodePollOptions<T> = {
|
||||||
intervalSeconds?: number;
|
intervalSeconds?: number;
|
||||||
expiresInSeconds?: number;
|
expiresInSeconds?: number;
|
||||||
|
waitBeforeFirstPoll?: boolean;
|
||||||
poll: () => Promise<OAuthDeviceCodePollResult<T>>;
|
poll: () => Promise<OAuthDeviceCodePollResult<T>>;
|
||||||
signal?: AbortSignal;
|
signal?: AbortSignal;
|
||||||
};
|
};
|
||||||
@@ -53,6 +54,13 @@ export async function pollOAuthDeviceCodeFlow<T>(options: OAuthDeviceCodePollOpt
|
|||||||
);
|
);
|
||||||
|
|
||||||
let slowDownResponses = 0;
|
let slowDownResponses = 0;
|
||||||
|
if (options.waitBeforeFirstPoll) {
|
||||||
|
const remainingMs = deadline - Date.now();
|
||||||
|
if (remainingMs > 0) {
|
||||||
|
await abortableSleep(Math.min(intervalMs, remainingMs), options.signal, CANCEL_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (Date.now() < deadline) {
|
while (Date.now() < deadline) {
|
||||||
if (options.signal?.aborted) {
|
if (options.signal?.aborted) {
|
||||||
throw new Error(CANCEL_MESSAGE);
|
throw new Error(CANCEL_MESSAGE);
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ async function pollForGitHubAccessToken(
|
|||||||
return pollOAuthDeviceCodeFlow<string>({
|
return pollOAuthDeviceCodeFlow<string>({
|
||||||
intervalSeconds: device.interval,
|
intervalSeconds: device.interval,
|
||||||
expiresInSeconds: device.expires_in,
|
expiresInSeconds: device.expires_in,
|
||||||
|
waitBeforeFirstPoll: true,
|
||||||
signal,
|
signal,
|
||||||
poll: async () => {
|
poll: async () => {
|
||||||
const raw = await fetchJson(urls.accessTokenUrl, {
|
const raw = await fetchJson(urls.accessTokenUrl, {
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ describe("GitHub Copilot OAuth device flow", () => {
|
|||||||
await loginPromise;
|
await loginPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("polls immediately and increases the interval after slow_down", async () => {
|
it("waits before polling and increases the interval after slow_down", async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
const startTime = new Date("2026-03-09T00:00:00Z");
|
const startTime = new Date("2026-03-09T00:00:00Z");
|
||||||
vi.setSystemTime(startTime);
|
vi.setSystemTime(startTime);
|
||||||
@@ -315,6 +315,12 @@ describe("GitHub Copilot OAuth device flow", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(0);
|
await vi.advanceTimersByTimeAsync(0);
|
||||||
|
expect(accessTokenPollTimes).toHaveLength(0);
|
||||||
|
|
||||||
|
await vi.advanceTimersByTimeAsync(4999);
|
||||||
|
expect(accessTokenPollTimes).toHaveLength(0);
|
||||||
|
|
||||||
|
await vi.advanceTimersByTimeAsync(1);
|
||||||
expect(accessTokenPollTimes).toHaveLength(1);
|
expect(accessTokenPollTimes).toHaveLength(1);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(4999);
|
await vi.advanceTimersByTimeAsync(4999);
|
||||||
@@ -330,9 +336,9 @@ describe("GitHub Copilot OAuth device flow", () => {
|
|||||||
await loginPromise;
|
await loginPromise;
|
||||||
|
|
||||||
expect(accessTokenPollTimes).toEqual([
|
expect(accessTokenPollTimes).toEqual([
|
||||||
startTime.getTime(),
|
|
||||||
startTime.getTime() + 5000,
|
startTime.getTime() + 5000,
|
||||||
startTime.getTime() + 15000,
|
startTime.getTime() + 10000,
|
||||||
|
startTime.getTime() + 20000,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -383,18 +389,24 @@ describe("GitHub Copilot OAuth device flow", () => {
|
|||||||
/Device flow timed out after one or more slow_down responses/,
|
/Device flow timed out after one or more slow_down responses/,
|
||||||
);
|
);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(5000);
|
await vi.advanceTimersByTimeAsync(4999);
|
||||||
expect(accessTokenPollTimes).toEqual([startTime.getTime()]);
|
expect(accessTokenPollTimes).toEqual([]);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(5000);
|
await vi.advanceTimersByTimeAsync(1);
|
||||||
expect(accessTokenPollTimes).toEqual([startTime.getTime(), startTime.getTime() + 10000]);
|
expect(accessTokenPollTimes).toEqual([startTime.getTime() + 5000]);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(14999);
|
await vi.advanceTimersByTimeAsync(9999);
|
||||||
expect(accessTokenPollTimes).toEqual([startTime.getTime(), startTime.getTime() + 10000]);
|
expect(accessTokenPollTimes).toEqual([startTime.getTime() + 5000]);
|
||||||
|
|
||||||
|
await vi.advanceTimersByTimeAsync(1);
|
||||||
|
expect(accessTokenPollTimes).toEqual([startTime.getTime() + 5000, startTime.getTime() + 15000]);
|
||||||
|
|
||||||
|
await vi.advanceTimersByTimeAsync(9999);
|
||||||
|
expect(accessTokenPollTimes).toEqual([startTime.getTime() + 5000, startTime.getTime() + 15000]);
|
||||||
|
|
||||||
await vi.advanceTimersByTimeAsync(1);
|
await vi.advanceTimersByTimeAsync(1);
|
||||||
await rejection;
|
await rejection;
|
||||||
|
|
||||||
expect(accessTokenPollTimes).toEqual([startTime.getTime(), startTime.getTime() + 10000]);
|
expect(accessTokenPollTimes).toEqual([startTime.getTime() + 5000, startTime.getTime() + 15000]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,6 +38,29 @@ describe("OAuth device-code polling", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("can wait before the first poll", async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(new Date("2026-03-09T00:00:00Z"));
|
||||||
|
|
||||||
|
const pollTimes: number[] = [];
|
||||||
|
const resultPromise = pollOAuthDeviceCodeFlow({
|
||||||
|
intervalSeconds: 2,
|
||||||
|
expiresInSeconds: 30,
|
||||||
|
waitBeforeFirstPoll: true,
|
||||||
|
poll: async () => {
|
||||||
|
pollTimes.push(Date.now());
|
||||||
|
return { status: "complete" as const, value: "token" };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await vi.advanceTimersByTimeAsync(1999);
|
||||||
|
expect(pollTimes).toEqual([]);
|
||||||
|
|
||||||
|
await vi.advanceTimersByTimeAsync(1);
|
||||||
|
await expect(resultPromise).resolves.toBe("token");
|
||||||
|
expect(pollTimes).toEqual([new Date("2026-03-09T00:00:02Z").getTime()]);
|
||||||
|
});
|
||||||
|
|
||||||
it("cancels an in-flight wait", async () => {
|
it("cancels an in-flight wait", async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|||||||
Reference in New Issue
Block a user