Refactor code to make it more readable. Fix model family selection that was not working

This commit is contained in:
Lars Baunwall 2025-08-20 19:57:38 +02:00
parent 7782ff0727
commit 5127dc0b7f
No known key found for this signature in database
11 changed files with 242 additions and 211 deletions

View file

@ -12,13 +12,14 @@ export interface BridgeConfig {
export const getBridgeConfig = (): BridgeConfig => {
const cfg = vscode.workspace.getConfiguration('bridge');
return {
enabled: cfg.get<boolean>('enabled') ?? false,
host: cfg.get<string>('host') ?? '127.0.0.1',
port: cfg.get<number>('port') ?? 0,
token: (cfg.get<string>('token') ?? '').trim(),
historyWindow: cfg.get<number>('historyWindow') ?? 3,
verbose: cfg.get<boolean>('verbose') ?? false,
maxConcurrent: cfg.get<number>('maxConcurrent') ?? 1,
};
const resolved = {
enabled: cfg.get('enabled', false),
host: cfg.get('host', '127.0.0.1'),
port: cfg.get('port', 0),
token: cfg.get('token', '').trim(),
historyWindow: cfg.get('historyWindow', 3),
verbose: cfg.get('verbose', false),
maxConcurrent: cfg.get('maxConcurrent', 1),
} satisfies BridgeConfig;
return resolved;
};

View file

@ -1,4 +1,5 @@
import * as vscode from 'vscode';
import type { AddressInfo } from 'net';
import { getBridgeConfig } from './config';
import { state } from './state';
import { ensureOutput, verbose } from './log';
@ -9,9 +10,9 @@ import { getModel } from './models';
export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
ensureOutput();
ensureStatusBar();
state.statusItem!.text = 'Copilot Bridge: Disabled';
state.statusItem!.show();
ctx.subscriptions.push(state.statusItem!, state.output!);
state.statusBarItem!.text = 'Copilot Bridge: Disabled';
state.statusBarItem!.show();
ctx.subscriptions.push(state.statusBarItem!, state.output!);
ctx.subscriptions.push(vscode.commands.registerCommand('bridge.enable', async () => {
await startBridge();
@ -24,7 +25,9 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
ctx.subscriptions.push(vscode.commands.registerCommand('bridge.status', async () => {
const info = state.server?.address();
const bound = info && typeof info === 'object' ? `${(info as any).address}:${(info as any).port}` : 'n/a';
const bound = (info && typeof info === 'object' && 'address' in info && 'port' in info)
? `${(info as AddressInfo).address}:${(info as AddressInfo).port}`
: 'n/a';
const config = getBridgeConfig();
const hasToken = config.token.length > 0;
vscode.window.showInformationMessage(
@ -47,10 +50,14 @@ async function startBridge(): Promise<void> {
state.running = true;
try {
await startServer();
} catch (error: any) {
} catch (error) {
state.running = false;
state.statusItem!.text = 'Copilot Bridge: Error';
verbose(error?.stack || String(error));
state.statusBarItem!.text = 'Copilot Bridge: Error';
if (error instanceof Error) {
verbose(error.stack || error.message);
} else {
verbose(String(error));
}
throw error;
}
}
@ -63,8 +70,8 @@ async function stopBridge(): Promise<void> {
} finally {
state.server = undefined;
state.modelCache = undefined;
if (state.statusItem) {
state.statusItem.text = 'Copilot Bridge: Disabled';
if (state.statusBarItem) {
state.statusBarItem.text = 'Copilot Bridge: Disabled';
}
verbose('Stopped');
}

View file

@ -2,7 +2,7 @@ import * as vscode from 'vscode';
import type { IncomingMessage, ServerResponse } from 'http';
import { state } from '../../state';
import { getBridgeConfig } from '../../config';
import { extractModelFamily, isChatCompletionRequest, normalizeMessagesLM } from '../../messages';
import { isChatCompletionRequest, normalizeMessagesLM } from '../../messages';
import { getModel, hasLMApi } from '../../models';
import { readJson, writeErrorResponse, writeJson } from '../utils';
import { verbose } from '../../log';
@ -11,67 +11,66 @@ export const handleChatCompletion = async (req: IncomingMessage, res: ServerResp
const config = getBridgeConfig();
state.activeRequests++;
verbose(`Request started (active=${state.activeRequests})`);
try {
const body = await readJson(req);
if (!isChatCompletionRequest(body)) {
writeErrorResponse(res, 400, 'invalid request', 'invalid_request_error', 'invalid_payload');
return;
return writeErrorResponse(res, 400, 'invalid request', 'invalid_request_error', 'invalid_payload');
}
const { model: requestedModel, stream = true } = body;
const familyOverride = extractModelFamily(requestedModel);
const model = await getModel(false, familyOverride);
const requestedModel = body.model;
const stream = body.stream !== false; // default true
const model = await getModel(false, requestedModel);
if (!model) {
const hasLM = hasLMApi();
if (familyOverride && hasLM) {
if (requestedModel && hasLM) {
state.lastReason = 'not_found';
writeErrorResponse(res, 404, 'model not found', 'invalid_request_error', 'model_not_found', 'not_found');
return;
return writeErrorResponse(res, 404, 'model not found', 'invalid_request_error', 'model_not_found', 'not_found');
}
const reason = !hasLM ? 'missing_language_model_api' : (state.lastReason || 'copilot_model_unavailable');
writeErrorResponse(res, 503, 'Copilot unavailable', 'server_error', 'copilot_unavailable', reason);
return;
return writeErrorResponse(res, 503, 'Copilot unavailable', 'server_error', 'copilot_unavailable', reason);
}
const lmMessages = normalizeMessagesLM(body.messages, config.historyWindow);
verbose(`Sending request to Copilot via Language Model API... ${model.family || model.modelFamily || model.name || 'unknown'}`);
const lmMessages = normalizeMessagesLM(body.messages, config.historyWindow) as vscode.LanguageModelChatMessage[];
verbose(`LM request via API model=${model.family || model.id || model.name || 'unknown'}`);
const cts = new vscode.CancellationTokenSource();
const response = await model.sendRequest(lmMessages, {}, cts.token);
if (stream) {
await handleStreamResponse(res, response);
} else {
await handleNonStreamResponse(res, response);
}
await sendResponse(res, response, stream);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
writeErrorResponse(res, 500, msg || 'internal_error', 'server_error', 'internal_error');
} finally {
state.activeRequests--;
verbose(`Request complete (active=${state.activeRequests})`);
}
};
const handleStreamResponse = async (res: ServerResponse, response: any): Promise<void> => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
const id = `cmp_${Math.random().toString(36).slice(2)}`;
verbose(`SSE start id=${id}`);
for await (const fragment of response.text) {
const payload = {
id,
object: 'chat.completion.chunk',
choices: [{ index: 0, delta: { content: fragment } }],
};
res.write(`data: ${JSON.stringify(payload)}\n\n`);
const sendResponse = async (res: ServerResponse, response: vscode.LanguageModelChatResponse, stream: boolean): Promise<void> => {
if (stream) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
const id = `cmp_${Math.random().toString(36).slice(2)}`;
verbose(`SSE start id=${id}`);
for await (const fragment of response.text) {
res.write(`data: ${JSON.stringify({
id,
object: 'chat.completion.chunk',
choices: [{ index: 0, delta: { content: fragment } }],
})}\n\n`);
}
verbose(`SSE end id=${id}`);
res.write('data: [DONE]\n\n');
res.end();
return;
}
verbose(`SSE end id=${id}`);
res.write('data: [DONE]\n\n');
res.end();
};
const handleNonStreamResponse = async (res: ServerResponse, response: any): Promise<void> => {
let content = '';
for await (const fragment of response.text) {
content += fragment;
}
for await (const fragment of response.text) content += fragment;
verbose(`Non-stream complete len=${content.length}`);
writeJson(res, 200, {
id: `cmpl_${Math.random().toString(36).slice(2)}`,

View file

@ -1,7 +1,8 @@
import { writeJson } from '../utils';
import { listCopilotModels } from '../../models';
import type { ServerResponse } from 'http';
export const handleModelsRequest = async (res: any): Promise<void> => {
export const handleModelsRequest = async (res: ServerResponse): Promise<void> => {
try {
const models = await listCopilotModels();
writeJson(res, 200, {
@ -13,13 +14,7 @@ export const handleModelsRequest = async (res: any): Promise<void> => {
});
} catch {
writeJson(res, 200, {
data: [
{
id: 'copilot',
object: 'model',
owned_by: 'vscode-bridge',
},
],
data: [],
});
}
};

View file

@ -1,5 +1,5 @@
const polka = require('polka');
import type { Server } from 'http';
import polka from 'polka';
import type { Server, IncomingMessage, ServerResponse } from 'http';
import { getBridgeConfig } from '../config';
import { state } from '../state';
import { isAuthorized } from './auth';
@ -8,16 +8,30 @@ import { handleModelsRequest } from './routes/models';
import { handleChatCompletion } from './routes/chat';
import { writeErrorResponse } from './utils';
import { ensureOutput, verbose } from '../log';
import { updateStatusAfterStart } from '../status';
import { updateStatus } from '../status';
export const startServer = async (): Promise<void> => {
if (state.server) return;
const config = getBridgeConfig();
ensureOutput();
const app = polka();
const app = polka({
onError: (err, req, res) => {
const msg = err instanceof Error ? err.message : String(err);
verbose(`HTTP error: ${msg}`);
if (!res.headersSent) {
writeErrorResponse(res, 500, msg || 'internal_error', 'server_error', 'internal_error');
} else {
try { res.end(); } catch {/* ignore */}
}
},
onNoMatch: (_req, res) => {
writeErrorResponse(res, 404, 'not found', 'invalid_request_error', 'route_not_found');
},
});
app.use((req: any, res: any, next: any) => {
// Logging + auth middleware
app.use((req: IncomingMessage & { method?: string; url?: string }, res: ServerResponse, next: () => void) => {
verbose(`HTTP ${req.method} ${req.url}`);
if (!isAuthorized(req, config.token)) {
writeErrorResponse(res, 401, 'unauthorized', 'invalid_request_error', 'unauthorized');
@ -26,15 +40,15 @@ export const startServer = async (): Promise<void> => {
next();
});
app.get('/healthz', async (_req: any, res: any) => {
app.get('/health', async (_req: IncomingMessage, res: ServerResponse) => {
await handleHealthCheck(res, config.verbose);
});
app.get('/v1/models', async (_req: any, res: any) => {
app.get('/v1/models', async (_req: IncomingMessage, res: ServerResponse) => {
await handleModelsRequest(res);
});
app.post('/v1/chat/completions', async (req: any, res: any) => {
app.post('/v1/chat/completions', async (req: IncomingMessage, res: ServerResponse) => {
if (state.activeRequests >= config.maxConcurrent) {
res.writeHead(429, { 'Content-Type': 'application/json', 'Retry-After': '1' });
res.end(JSON.stringify({
@ -49,36 +63,25 @@ export const startServer = async (): Promise<void> => {
}
try {
await handleChatCompletion(req, res);
} catch (e: any) {
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
writeErrorResponse(res, 500, msg || 'internal_error', 'server_error', 'internal_error');
}
});
await new Promise<void>((resolve, reject) => {
let resolved = false;
try {
app.listen(config.port, config.host, () => {
const srv: Server | undefined = app.server;
if (!srv) {
reject(new Error('Server failed to start'));
return;
}
const srv = app.server as Server | undefined;
if (!srv) return reject(new Error('Server failed to start'));
state.server = srv;
updateStatusAfterStart();
resolved = true;
updateStatus('start');
resolve();
});
const srv = app.server as Server | undefined;
srv?.on('error', reject);
} catch (err) {
reject(err);
return;
}
const srv: Server | undefined = app.server;
if (srv && typeof (srv as any).on === 'function') {
srv.on('error', reject);
}
if (!resolved && app.server && typeof (app.server as any).on === 'function') {
app.server.on('error', reject);
}
});
};

View file

@ -9,31 +9,44 @@ export interface ErrorResponse {
};
}
export const writeJson = (res: ServerResponse, status: number, body: any): void => {
export const writeJson = <T>(res: ServerResponse, status: number, body: T): void => {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(body));
};
export const writeErrorResponse = (
export function writeErrorResponse(
res: ServerResponse,
status: number,
message: string,
type: string,
code: string
): void;
export function writeErrorResponse(
res: ServerResponse,
status: number,
message: string,
type: string,
code: string,
reason: string
): void;
export function writeErrorResponse(
res: ServerResponse,
status: number,
message: string,
type: string,
code: string,
reason?: string
): void => {
writeJson(res, status, {
error: { message, type, code, ...(reason && { reason }) },
});
};
): void {
writeJson(res, status, { error: { message, type, code, ...(reason ? { reason } : {}) } });
}
export const readJson = (req: IncomingMessage): Promise<any> =>
new Promise((resolve, reject) => {
export const readJson = <T = unknown>(req: IncomingMessage): Promise<T> =>
new Promise<T>((resolve, reject) => {
let data = '';
req.on('data', (c) => (data += c));
req.on('end', () => {
try {
resolve(data ? JSON.parse(data) : {});
resolve((data ? JSON.parse(data) : {}) as T);
} catch (e) {
reject(e);
}

View file

@ -18,30 +18,31 @@ export interface ChatCompletionRequest {
readonly [key: string]: unknown;
}
const isValidRole = (role: unknown): role is 'system' | 'user' | 'assistant' =>
typeof role === 'string' && ['system', 'user', 'assistant'].includes(role);
const VALID_ROLES = ['system', 'user', 'assistant'] as const;
type Role = typeof VALID_ROLES[number];
const isValidRole = (role: unknown): role is Role => typeof role === 'string' && VALID_ROLES.includes(role as Role);
export const isChatMessage = (msg: unknown): msg is ChatMessage =>
typeof msg === 'object' &&
msg !== null &&
'role' in msg &&
'content' in msg &&
isValidRole((msg as any).role) &&
((msg as any).content !== undefined && (msg as any).content !== null);
export const isChatMessage = (msg: unknown): msg is ChatMessage => {
if (typeof msg !== 'object' || msg === null) return false;
const candidate = msg as Record<string, unknown>;
if (!('role' in candidate) || !('content' in candidate)) return false;
return isValidRole(candidate.role) && candidate.content !== undefined && candidate.content !== null;
};
export const isChatCompletionRequest = (body: unknown): body is ChatCompletionRequest =>
typeof body === 'object' &&
body !== null &&
'messages' in body &&
Array.isArray((body as any).messages) &&
(body as any).messages.length > 0 &&
(body as any).messages.every(isChatMessage);
export const isChatCompletionRequest = (body: unknown): body is ChatCompletionRequest => {
if (typeof body !== 'object' || body === null) return false;
const candidate = body as Record<string, unknown>;
if (!('messages' in candidate)) return false;
const messages = candidate.messages;
return Array.isArray(messages) && messages.length > 0 && messages.every(isChatMessage);
};
const toText = (content: unknown): string => {
if (typeof content === 'string') return content;
if (Array.isArray(content)) return content.map(toText).join('\n');
if (content && typeof content === 'object' && 'text' in content && typeof (content as any).text === 'string') {
return (content as any).text;
if (content && typeof content === 'object' && 'text' in content) {
const textVal = (content as { text?: unknown }).text;
if (typeof textVal === 'string') return textVal;
}
try {
return JSON.stringify(content);
@ -50,52 +51,41 @@ const toText = (content: unknown): string => {
}
};
export const normalizeMessagesLM = (messages: ChatMessage[], histWindow: number): unknown[] => {
export const normalizeMessagesLM = (
messages: readonly ChatMessage[],
histWindow: number
): (vscode.LanguageModelChatMessage | { role: 'user' | 'assistant'; content: string })[] => {
const systemMessages = messages.filter((m) => m.role === 'system');
const systemMessage = systemMessages[systemMessages.length - 1];
const conversationMessages = messages
.filter((m) => m.role === 'user' || m.role === 'assistant')
.slice(-histWindow * 2);
const conversationMessages = messages.filter((m) => m.role === 'user' || m.role === 'assistant').slice(-histWindow * 2);
const User = (vscode as any).LanguageModelChatMessage?.User;
const Assistant = (vscode as any).LanguageModelChatMessage?.Assistant;
const lmMsg = (vscode as unknown as { LanguageModelChatMessage?: typeof vscode.LanguageModelChatMessage }).LanguageModelChatMessage;
const UserFactory = lmMsg?.User;
const AssistantFactory = lmMsg?.Assistant;
const result: unknown[] = [];
const result: (vscode.LanguageModelChatMessage | { role: 'user' | 'assistant'; content: string })[] = [];
let firstUserSeen = false;
for (const message of conversationMessages) {
if (message.role === 'user') {
let text = toText(message.content);
for (const m of conversationMessages) {
if (m.role === 'user') {
let text = toText(m.content);
if (!firstUserSeen && systemMessage) {
text = `[SYSTEM]\n${toText(systemMessage.content)}\n\n[DIALOG]\nuser: ${text}`;
firstUserSeen = true;
}
result.push(User ? User(text) : { role: 'user', content: text });
} else if (message.role === 'assistant') {
const text = toText(message.content);
result.push(Assistant ? Assistant(text) : { role: 'assistant', content: text });
result.push(UserFactory ? UserFactory(text) : { role: 'user', content: text });
} else {
const text = toText(m.content);
result.push(AssistantFactory ? AssistantFactory(text) : { role: 'assistant', content: text });
}
}
if (!firstUserSeen && systemMessage) {
const text = `[SYSTEM]\n${toText(systemMessage.content)}`;
result.unshift(User ? User(text) : { role: 'user', content: text });
result.unshift(UserFactory ? UserFactory(text) : { role: 'user', content: text });
}
if (result.length === 0) {
result.push(User ? User('') : { role: 'user', content: '' });
}
if (result.length === 0) result.push(UserFactory ? UserFactory('') : { role: 'user', content: '' });
return result;
};
export const extractModelFamily = (requestedModel?: string): string | undefined => {
if (!requestedModel) return undefined;
if (/-copilot$/i.test(requestedModel)) {
return requestedModel.replace(/-copilot$/i, '');
}
if (requestedModel.toLowerCase() === 'copilot') {
return undefined;
}
return undefined;
};

View file

@ -1,46 +1,49 @@
import * as vscode from 'vscode';
import { state, LanguageModel } from './state';
import { updateStatusWithError, updateStatusWithSuccess } from './status';
import { state } from './state';
import { updateStatus } from './status';
import { verbose } from './log';
const hasLanguageModelAPI = (): boolean =>
!!(vscode as any).lm && typeof (vscode as any).lm.selectChatModels === 'function';
// VS Code Language Model API (see selectChatModels docs in latest VS Code API reference)
const hasLanguageModelAPI = (): boolean => typeof vscode.lm?.selectChatModels === 'function';
export const selectChatModels = async (family?: string): Promise<LanguageModel[]> => {
const lm = (vscode as any).lm;
const selector = family ? { family } : undefined;
const models = await lm.selectChatModels(selector);
return models as unknown as LanguageModel[];
export const selectChatModels = async (family?: string): Promise<vscode.LanguageModelChat[]> => {
const selector: vscode.LanguageModelChatSelector | undefined = family ? { family } : undefined;
return vscode.lm.selectChatModels(selector);
};
export const getModel = async (force = false, family?: string): Promise<LanguageModel | undefined> => {
export const getModel = async (force = false, family?: string): Promise<vscode.LanguageModelChat | undefined> => {
if (!force && state.modelCache && !family) return state.modelCache;
// Mark that we've attempted at least one model fetch (affects status bar messaging)
state.modelAttempted = true;
const hasLM = hasLanguageModelAPI();
if (!hasLM) {
if (!family) state.modelCache = undefined;
state.lastReason = 'missing_language_model_api';
updateStatusWithError();
updateStatus('error');
verbose('VS Code Language Model API not available; update VS Code or enable proposed API (Insiders/F5/--enable-proposed-api).');
return undefined;
}
try {
const models = await selectChatModels(family);
if (!models || models.length === 0) {
// Prefer selecting by vendor 'copilot' if no family specified to reduce unrelated models
const models: vscode.LanguageModelChat[] = family
? await selectChatModels(family)
: await vscode.lm.selectChatModels({ vendor: 'copilot' });
if (models.length === 0) {
if (!family) state.modelCache = undefined;
state.lastReason = family ? 'not_found' : 'copilot_model_unavailable';
updateStatusWithError();
const m = family ? `no models for family ${family}` : 'no copilot models available';
verbose(m);
updateStatus('error');
verbose(family ? `no models for family ${family}` : 'no copilot models available');
return undefined;
}
state.modelCache = models[0];
state.modelCache = models[0]; // keep first for now; future: choose by quality or family preference
state.lastReason = undefined;
updateStatusWithSuccess();
updateStatus('success');
return state.modelCache;
} catch (e: any) {
} catch (e: unknown) {
handleModelSelectionError(e, family);
return undefined;
}
@ -55,7 +58,7 @@ export const handleModelSelectionError = (error: unknown, family?: string): void
} else {
state.lastReason = 'copilot_model_unavailable';
}
updateStatusWithError();
updateStatus('error');
const fam = family ? ` family=${family}` : '';
verbose(`Model selection failed: ${msg}${fam}`);
};
@ -63,9 +66,9 @@ export const handleModelSelectionError = (error: unknown, family?: string): void
export const listCopilotModels = async (): Promise<string[]> => {
try {
const models = await selectChatModels();
const ids = models.map((m: any) => {
const normalized = m.family || m.modelFamily || m.name || 'copilot';
return `${normalized}-copilot`;
const ids = models.map((m: vscode.LanguageModelChat) => {
const normalized = m.family || m.id || m.name || 'copilot';
return `${normalized}`;
});
return ids.length ? ids : ['copilot'];
} catch {

View file

@ -1,28 +1,19 @@
import * as vscode from 'vscode';
import type { Server } from 'http';
export interface LanguageModel {
readonly family?: string;
readonly modelFamily?: string;
readonly name?: string;
readonly sendRequest: (messages: unknown[], options: unknown, token: vscode.CancellationToken) => Promise<LanguageModelResponse>;
}
export interface LanguageModelResponse {
readonly text: AsyncIterable<string>;
}
export interface BridgeState {
server?: Server;
modelCache?: LanguageModel;
statusItem?: vscode.StatusBarItem;
modelCache?: vscode.LanguageModelChat; // official API type
statusBarItem?: vscode.StatusBarItem;
output?: vscode.OutputChannel;
running: boolean;
activeRequests: number;
lastReason?: string;
modelAttempted?: boolean; // whether we've attempted to resolve a model yet
}
export const state: BridgeState = {
running: false,
activeRequests: 0,
modelAttempted: false,
};

View file

@ -5,37 +5,38 @@ import { getBridgeConfig } from './config';
import { info } from './log';
export const ensureStatusBar = (): void => {
if (!state.statusItem) {
state.statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
state.statusItem.text = 'Copilot Bridge: Disabled';
state.statusItem.show();
if (!state.statusBarItem) {
state.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
state.statusBarItem.text = 'Copilot Bridge: Disabled';
state.statusBarItem.show();
}
};
export const updateStatusAfterStart = (): void => {
const cfg = getBridgeConfig();
const addr = state.server?.address() as AddressInfo | null;
const shown = addr ? `${addr.address}:${addr.port}` : `${cfg.host}:${cfg.port}`;
if (state.statusItem) {
state.statusItem.text = `Copilot Bridge: ${state.modelCache ? 'OK' : 'Unavailable'} @ ${shown}`;
}
info(`Started at http://${shown} | Copilot: ${state.modelCache ? 'ok' : 'unavailable'}`);
};
export type BridgeStatusKind = 'start' | 'error' | 'success';
export const updateStatusWithError = (): void => {
export const updateStatus = (kind: BridgeStatusKind): void => {
const cfg = getBridgeConfig();
const addr = state.server?.address() as AddressInfo | null;
const shown = addr ? `${addr.address}:${addr.port}` : `${cfg.host}:${cfg.port}`;
if (state.statusItem) {
state.statusItem.text = `Copilot Bridge: Unavailable @ ${shown}`;
}
};
export const updateStatusWithSuccess = (): void => {
const cfg = getBridgeConfig();
const addr = state.server?.address() as AddressInfo | null;
const shown = addr ? `${addr.address}:${addr.port}` : `${cfg.host}:${cfg.port}`;
if (state.statusItem) {
state.statusItem.text = `Copilot Bridge: OK @ ${shown}`;
if (!state.statusBarItem) return;
switch (kind) {
case 'start': {
const availability = state.modelCache ? 'OK' : (state.modelAttempted ? 'Unavailable' : 'Pending');
state.statusBarItem.text = `Copilot Bridge: ${availability} @ ${shown}`;
info(`Started at http://${shown} | Copilot: ${state.modelCache ? 'ok' : (state.modelAttempted ? 'unavailable' : 'pending')}`);
break;
}
case 'error':
state.statusBarItem.text = `Copilot Bridge: Unavailable @ ${shown}`;
break;
case 'success':
state.statusBarItem.text = `Copilot Bridge: OK @ ${shown}`;
break;
default:
// Exhaustive check in case of future extension
const _never: never = kind;
return _never;
}
};

32
src/types/polka.d.ts vendored
View file

@ -1,2 +1,30 @@
declare function polka(...args: any[]): any;
export = polka;
declare module 'polka' {
import { IncomingMessage, ServerResponse } from 'http';
export interface PolkaRequest extends IncomingMessage {
params?: Record<string, string>;
}
export type Next = () => void;
export type Middleware = (req: PolkaRequest, res: ServerResponse, next: Next) => void;
export type Handler = (req: PolkaRequest, res: ServerResponse) => void;
export interface PolkaOptions {
onError?: (err: Error, req: PolkaRequest, res: ServerResponse, next: Next) => void;
onNoMatch?: (req: PolkaRequest, res: ServerResponse) => void;
}
export interface PolkaInstance {
use(mw: Middleware): PolkaInstance;
use(path: string, mw: Middleware): PolkaInstance;
get(path: string, handler: Handler): PolkaInstance;
post(path: string, handler: Handler): PolkaInstance;
put(path: string, handler: Handler): PolkaInstance;
delete(path: string, handler: Handler): PolkaInstance;
listen(port: number, host: string, cb: () => void): void;
server?: import('http').Server;
}
function polka(options?: PolkaOptions): PolkaInstance;
export default polka;
}