From 862eab2a4aa5d7900508b4787a7f793848a42d3f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:17:07 +0000 Subject: [PATCH] Copilot access auto-reacquire: per-request getAccess(), healthz best-effort under verbose, live status/log updates Co-Authored-By: Lars Baunwall --- README.md | 5 +++++ src/extension.ts | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/README.md b/README.md index 3af42e0..ab2de6b 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,11 @@ Prerequisites: - Node.js 18+ (recommended) - npm +- Auto-recovery: the bridge re-requests Copilot access on each chat request if missing; no restart required after signing in. `/healthz` will best-effort recheck only when `bridge.verbose` is true. + + + + Steps: 1) Install deps and compile: npm install diff --git a/src/extension.ts b/src/extension.ts index 5d54652..eef42e6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -75,6 +75,11 @@ async function startBridge() { } if (req.method === 'GET' && req.url === '/healthz') { + const cfgNow = vscode.workspace.getConfiguration('bridge'); + const verboseNow = cfgNow.get('verbose') ?? false; + if (!access && verboseNow) { + await getAccess(); + } writeJson(res, 200, { ok: true, copilot: access ? 'ok' : 'unavailable', version: vscode.version }); return; } @@ -85,6 +90,14 @@ async function startBridge() { } if (req.method === 'POST' && req.url?.startsWith('/v1/chat/completions')) { + if (!access) { + if (verbose) output?.appendLine('Copilot access missing; attempting to acquire...'); + await getAccess(); + } + if (!access) { + if (verbose) output?.appendLine('Copilot access missing; attempting to acquire...'); + await getAccess(); + } if (!access) { writeJson(res, 503, { error: { message: 'Copilot unavailable', type: 'server_error', code: 'copilot_unavailable' } }); return; @@ -106,6 +119,7 @@ async function startBridge() { const prompt = normalizeMessages(messages, hist); const streamMode = body?.stream !== false; + if (verbose) output?.appendLine('Starting Copilot chat session...'); const session = await access.startSession(); const chatStream = await session.sendRequest({ prompt, attachments: [] }); @@ -162,6 +176,7 @@ async function startBridge() { res.writeHead(404).end(); } catch (e: any) { output?.appendLine(`Error: ${e?.stack || e?.message || String(e)}`); + access = undefined; writeJson(res, 500, { error: { message: e?.message ?? 'internal_error', type: 'server_error', code: 'internal_error' } }); } }); @@ -216,6 +231,28 @@ function normalizeMessages(messages: any[], histWindow: number): string { const sysPart = sys ? `[SYSTEM]\n${toText(sys.content)}\n\n` : ''; return `${sysPart}[DIALOG]\n${dialog}`; } +async function getAccess(force = false): Promise { + if (!force && access) return access; + const cfg = vscode.workspace.getConfiguration('bridge'); + const verbose = cfg.get('verbose') ?? false; + try { + const newAccess = await vscode.chat.requestChatAccess('copilot'); + access = newAccess; + const info = server ? server.address() : undefined; + const bound = info && typeof info === 'object' ? `${info.address}:${info.port}` : ''; + statusItem && (statusItem.text = `Copilot Bridge: OK ${bound ? `@ ${bound}` : ''}`); + if (verbose) output?.appendLine('Copilot access acquired.'); + return access; + } catch (e: any) { + access = undefined; + const info = server ? server.address() : undefined; + const bound = info && typeof info === 'object' ? `${info.address}:${info.port}` : ''; + statusItem && (statusItem.text = `Copilot Bridge: Unavailable ${bound ? `@ ${bound}` : ''}`); + if (verbose) output?.appendLine(`Copilot access request failed: ${e?.message || String(e)}`); + return undefined; + } +} + function readJson(req: http.IncomingMessage): Promise { return new Promise((resolve, reject) => {