diff --git a/README.md b/README.md index 100b0fd..585bc47 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,27 @@ Optional: Packaging a VSIX npm i -g @vscode/vsce vsce package - Then install the generated .vsix via “Extensions: Install from VSIX…” +## Enabling the VS Code Chat proposed API + +The `vscode.chat.requestChatAccess` API is currently proposed. To use this extension at runtime, enable proposed APIs for this extension: + +- Stable VS Code: + - Start with: `code --enable-proposed-api thinkability.copilot-bridge` +- VS Code Insiders: + - Proposed APIs can be used when running the extension from source (F5) or with the flag above +- Run from source: + - Open this folder in VS Code and press F5 (Extension Development Host) + +When the proposed API is not enabled, the Output (“Copilot Bridge”) will show: +“VS Code Chat proposed API not enabled; start VS Code with: code --enable-proposed-api thinkability.copilot-bridge, or run via F5/Insiders.” + +## Troubleshooting + +- /healthz shows `copilot: "unavailable"` with a `reason`: + - `missing_chat_api`: VS Code Chat proposed API not enabled (use the flag above) + - `copilot_unavailable`: Copilot access not granted (sign in to GitHub Copilot) +- POST /v1/chat/completions returns 503 with `reason` giving the same codes as above. + ## Configuration (bridge.*) @@ -68,6 +89,7 @@ To see verbose logs: - Access acquisition attempts (“Copilot access missing; attempting to acquire…”, “Copilot access acquired.”) - SSE lifecycle (“SSE start …”, “SSE end …”) - Health checks (best-effort access check when verbose is on) + - Proposed API diagnostics (e.g., “VS Code Chat proposed API not enabled…”) - bridge.verbose (boolean; default false): verbose logs to “Copilot Bridge” output channel ## Manual Testing (curl) diff --git a/package.json b/package.json index 565f4c3..2fbf68a 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,13 @@ "engines": { "vscode": "^1.90.0" }, + "enabledApiProposals": [ + "chat", + "chatProvider" + ], + "extensionKind": [ + "ui" + ], "categories": [ "Other" ], diff --git a/src/extension.ts b/src/extension.ts index d35c5a8..3e31eaa 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,6 +8,7 @@ let statusItem: vscode.StatusBarItem | undefined; let output: vscode.OutputChannel | undefined; let running = false; let activeRequests = 0; +let lastReason: string | undefined; export async function activate(ctx: vscode.ExtensionContext) { output = vscode.window.createOutputChannel('Copilot Bridge'); @@ -77,11 +78,13 @@ async function startBridge() { if (req.method === 'GET' && req.url === '/healthz') { const cfgNow = vscode.workspace.getConfiguration('bridge'); const verboseNow = cfgNow.get('verbose') ?? false; + const hasProposal = !!((vscode as any).chat && typeof (vscode as any).chat.requestChatAccess === 'function'); if (!access && verboseNow) { - if (verboseNow) output?.appendLine(`Healthz: access=${access ? 'present' : 'missing'}`); + if (verboseNow) output?.appendLine(`Healthz: access=${access ? 'present' : 'missing'} proposal=${hasProposal ? 'ok' : 'missing'}`); await getAccess(); } - writeJson(res, 200, { ok: true, copilot: access ? 'ok' : 'unavailable', version: vscode.version }); + const unavailableReason = access ? undefined : (!hasProposal ? 'missing_chat_api' : (lastReason || 'copilot_unavailable')); + writeJson(res, 200, { ok: true, copilot: access ? 'ok' : 'unavailable', reason: unavailableReason, version: vscode.version }); return; } @@ -100,7 +103,9 @@ async function startBridge() { await getAccess(); } if (!access) { - writeJson(res, 503, { error: { message: 'Copilot unavailable', type: 'server_error', code: 'copilot_unavailable' } }); + const hasProposal = !!((vscode as any).chat && typeof (vscode as any).chat.requestChatAccess === 'function'); + const reason = !hasProposal ? 'missing_chat_api' : (lastReason || 'copilot_unavailable'); + writeJson(res, 503, { error: { message: 'Copilot unavailable', type: 'server_error', code: 'copilot_unavailable', reason } }); return; } @@ -236,9 +241,22 @@ async function getAccess(force = false): Promise if (!force && access) return access; const cfg = vscode.workspace.getConfiguration('bridge'); const verbose = cfg.get('verbose') ?? false; + + const hasProposal = !!((vscode as any).chat && typeof (vscode as any).chat.requestChatAccess === 'function'); + if (!hasProposal) { + access = undefined; + lastReason = 'missing_chat_api'; + 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('VS Code Chat proposed API not enabled; start VS Code with: code --enable-proposed-api thinkability.copilot-bridge, or run via F5/Insiders.'); + return undefined; + } + try { - const newAccess = await vscode.chat.requestChatAccess('copilot'); + const newAccess = await (vscode as any).chat.requestChatAccess('copilot'); access = newAccess; + lastReason = undefined; const info = server ? server.address() : undefined; const bound = info && typeof info === 'object' ? `${info.address}:${info.port}` : ''; statusItem && (statusItem.text = `Copilot Bridge: OK ${bound ? `@ ${bound}` : ''}`); @@ -246,6 +264,7 @@ async function getAccess(force = false): Promise return access; } catch (e: any) { access = undefined; + lastReason = 'copilot_unavailable'; const info = server ? server.address() : undefined; const bound = info && typeof info === 'object' ? `${info.address}:${info.port}` : ''; statusItem && (statusItem.text = `Copilot Bridge: Unavailable ${bound ? `@ ${bound}` : ''}`);