1
>
/*---------------------------------------------------------------------------------------------
byokLmProxyService.ts
2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import type * as http from 'http';
7
>
import { createDecorator } from '../../../instantiation/common/instantiation.js';
8
>
import { ILogService } from '../../../log/common/log.js';
9
>
import { IByokLmBridgeRegistry } from '../byokLmBridgeRegistry.js';
10
>
import { parseProxyBearer } from '../claude/claudeProxyAuth.js';
11
>
import {
12
>
ILoopbackProxyHandle,
13
>
ILoopbackProxyRuntime,
14
>
IProxyInFlight,
15
>
LoopbackProxyServer,
16
>
readProxyRequestBody,
17
>
} from '../shared/loopbackProxyServer.js';
18
>
import {
19
>
IOpenAiChatRequest,
20
>
OpenAiTranslationError,
21
>
bridgeResultToSseFrames,
22
>
openAiErrorBody,
23
>
openAiRequestToBridge,
24
>
} from './byokOpenAiTranslation.js';
25
>
26
>
// #region Public types
27
>
28
>
/**
29
>
* Handle returned by {@link IByokLmProxyService.start}. Refcounts the shared
30
>
* loopback server (see {@link LoopbackProxyServer}): when every handle is
31
>
* disposed the listener closes and the nonce is destroyed; the next `start()`
32
>
* rebinds with a fresh port and nonce.
33
>
*
34
>
* **Subprocess ownership invariant.** Callers that hand `baseUrl`/`nonce` to
35
>
* the Copilot SDK runtime subprocess MUST kill that subprocess before calling
36
>
* `dispose()` — after disposal the proxy may rebind on a different port and the
37
>
* subprocess would silently lose its endpoint (same contract as the Claude and
38
>
* Codex proxies).
39
>
*/
40
>
export interface IByokLmProxyHandle extends ILoopbackProxyHandle {
41
>
/** e.g. `http://127.0.0.1:54321` — no trailing slash. */
42
>
readonly baseUrl: string;
43
>
/** 256-bit hex string. Combine with a session id as `Bearer <nonce>.<sessionId>`. */
44
>
readonly nonce: string;
45
>
/**
46
>
* Build the provider `baseUrl` for a given BYOK vendor. The vendor is
47
>
* encoded into the path so a single proxy can serve every vendor; the
48
>
* runtime appends `/chat/completions` to this URL.
49
>
*/
50
>
providerBaseUrl(vendor: string): string;
51
>
}
52
>
53
>
export const IByokLmProxyService = createDecorator<IByokLmProxyService>('byokLmProxyService');
54
>
55
>
export interface IByokLmProxyService {
56
>
readonly _serviceBrand: undefined;
57
>
58
>
/** Start the proxy (if not already running) and return a refcounted handle. */
59
>
start(): Promise<IByokLmProxyHandle>;
60
>
61
>
/**
62
>
* Force-close the proxy regardless of refcount and abort in-flight
63
>
* requests. Idempotent; subsequent `start()` calls rebind.
64
>
*/
65
>
dispose(): void;
66
>
}
67
>
68
>
// #endregion
69
>
70
>
const PROXY_USER_FACING_NAME = 'ByokLmProxyService';
71
>
const VENDOR_PATH_PREFIX = '/v/';
72
>
const CHAT_COMPLETIONS_SUFFIX = '/chat/completions';
73
>
74
>
/**
75
>
* The BYOK proxy keeps no per-bind mutable state: the active renderer bridge is
76
>
* resolved from {@link IByokLmBridgeRegistry} at request time, and the nonce
77
>
* lives on the runtime owned by {@link LoopbackProxyServer}.
78
>
*/
79
>
type ByokLmProxyState = undefined;
80
>
81
>
/**
82
>
* Local OpenAI-compatible HTTP proxy that lets the Copilot SDK runtime run
83
>
* BYOK models provided by VS Code extensions. The runtime is configured with a
84
>
* `type: 'openai'`, `wireApi: 'completions'` provider whose `baseUrl` points
85
>
* here; inbound `POST /v/<vendor>/chat/completions` requests are authenticated,
86
>
* translated, and forwarded to the renderer LM API via
87
>
* {@link IByokLmBridgeRegistry}, and the buffered completion is streamed back
88
>
* as OpenAI Chat Completions SSE.
89
>
*
90
>
* The server lifecycle — lazy bind on `127.0.0.1`, nonce minting, refcounted
91
>
* handles, in-flight tracking, and teardown — is inherited from
92
>
* {@link LoopbackProxyServer}; this subclass only implements request routing.
93
>
*/
94
>
export class ByokLmProxyService extends LoopbackProxyServer<ByokLmProxyState> implements IByokLmProxyService {
95
>
96
>
declare readonly _serviceBrand: undefined;
97
>
98
>
constructor(
99
@ILogService logService: ILogService,
100
@IByokLmBridgeRegistry private readonly _bridgeRegistry: IByokLmBridgeRegistry,