fix(types): type Copilot WebSocket construction (#9974)

This commit is contained in:
backryun
2026-08-10 12:28:21 +09:00
committed by GitHub
parent 6285cfd26e
commit a13107731f
2 changed files with 34 additions and 14 deletions

View File

@@ -67,6 +67,11 @@ interface CopilotWsEvent {
[key: string]: unknown;
}
type NodeWebSocketConstructor = new (
url: string | URL,
options?: { headers?: Record<string, string> }
) => WebSocket;
// ─── Helpers ────────────────────────────────────────────────────────────────
export function getCopilotMode(model?: string): string {
@@ -106,6 +111,12 @@ export function extractAccessToken(credential: string): string | null {
return credential;
}
/* @testonly */ export function buildCopilotWebSocketHeaders(
accessToken: string
): Record<string, string> {
return { Authorization: `Bearer ${accessToken}` };
}
/**
* Map a token (or absence of one) to an in-memory session-pool key.
*
@@ -292,19 +303,17 @@ export class CopilotWebExecutor extends BaseExecutor {
// Use Node.js built-in WebSocket if available, else dynamic import.
// Pass the access token via Authorization header (not URL) to avoid
// credential exposure in server logs.
let WS = globalThis.WebSocket;
if (!WS) {
const BrowserWebSocket = globalThis.WebSocket;
if (BrowserWebSocket) {
ws = new BrowserWebSocket(wsUrl);
} else {
// @ts-ignore — ws module has no type declarations in this project
WS = (await import("ws")).default as unknown as typeof WebSocket;
if (accessToken) {
// @ts-ignore — ws module supports headers option in second arg
ws = new WS(wsUrl, {
headers: { Authorization: `Bearer ${accessToken}` },
}) as WebSocket;
}
}
if (!ws) {
ws = new WS(wsUrl) as WebSocket;
const NodeWebSocket = (await import("ws"))
.default as unknown as NodeWebSocketConstructor;
ws = new NodeWebSocket(
wsUrl,
accessToken ? { headers: buildCopilotWebSocketHeaders(accessToken) } : undefined
);
}
const timeout = setTimeout(() => abort("Copilot WebSocket timeout"), FETCH_TIMEOUT_MS);

View File

@@ -1,8 +1,13 @@
import test from "node:test";
import assert from "node:assert/strict";
const { getCopilotMode, extractAccessToken, sessionPoolKey, solveHashcash } =
await import("../../open-sse/executors/copilot-web.ts");
const {
buildCopilotWebSocketHeaders,
getCopilotMode,
extractAccessToken,
sessionPoolKey,
solveHashcash,
} = await import("../../open-sse/executors/copilot-web.ts");
test("getCopilotMode maps known models to their Copilot modes", () => {
assert.equal(getCopilotMode("copilot"), "chat");
@@ -43,6 +48,12 @@ test("extractAccessToken returns null for empty input", () => {
assert.equal(extractAccessToken(""), null);
});
test("Copilot WebSocket credentials use the Authorization header", () => {
assert.deepEqual(buildCopilotWebSocketHeaders("access-token"), {
Authorization: "Bearer access-token",
});
});
test("sessionPoolKey produces unique keys per token preventing session sharing", () => {
const key1 = sessionPoolKey("token-user-alice");
const key2 = sessionPoolKey("token-user-bob");