316
return {};
317
}
319
>
// reported more than once — e.g. when two renderer bridges are transiently
320
>
// serving during a window hand-off (continuing a chat into a new session) —
321
>
// and the runtime rejects a session config with duplicate BYOK model
322
>
// selection ids ("Duplicate BYOK model selection id ...").
323
>
const seenSelectionIds = new Set<string>();
324
>
byokModels = byokModels.filter(m => {
325
>
const selectionId = `${m.vendor}/${m.id}`;
326
>
if (seenSelectionIds.has(selectionId)) {
327
return false;
328
}
330
>
return true;
331
>
});
332
>
// `startProxy` binds a local loopback listener — unlikely to fail, but it
333
>
// must never break session materialization (which fires the cross-window
334
>
// `sessionAdded` broadcast). Degrade to no BYOK config on failure.
335
>
let handle: IByokLmProxyHandle;
336
>
try {
337
>
handle = await startProxy();
338
>
} catch (err) {
339
logService.warn(`[Copilot:${sessionId}] Failed to start BYOK loopback proxy`, err);
340
return {};
341
}
342
>
const providers: NamedProviderConfig[] = [...new Set(byokModels.map(m => m.vendor))].map(vendor => ({
copilotSessionLauncher.ts
343
>
name: vendor,
344
>
type: 'openai',
345
>
wireApi: 'completions',
346
>
baseUrl: handle.providerBaseUrl(vendor),
347
>
bearerToken: `${handle.nonce}.${sessionId}`,
348
>
}));
349
>
const models: ProviderModelConfig[] = byokModels.map(m => ({
350
>
id: m.id,
351
>
provider: m.vendor,
352
>
...(m.name !== undefined ? { name: m.name } : {}),
353
>
...(m.maxContextWindowTokens !== undefined ? { maxContextWindowTokens: m.maxContextWindowTokens } : {}),
354
>
}));
355
>
logService.info(`[Copilot:${sessionId}] Wired ${models.length} BYOK model(s) across ${providers.length} provider(s) via loopback proxy ${handle.baseUrl}`);
356
>
return { providers, models };
357
>
}
358
359
export class CopilotSessionLauncher implements ICopilotSessionLauncher {