@@ -52142,6 +52142,9 @@ var CopilotApiGateway = class {
5214252142 const normalized = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 10;
5214352143 await this.updateServerConfig({ maxConnectionsPerIp: normalized });
5214452144 }
52145+ async setCloudflaredPath(path4) {
52146+ await this.updateServerConfig({ cloudflaredPath: path4 });
52147+ }
5214552148 async setMaxConcurrency(limit) {
5214652149 const normalized = Number.isFinite(limit) ? Math.max(1, Math.floor(limit)) : 4;
5214752150 await this.updateServerConfig({ maxConcurrentRequests: normalized });
@@ -52184,22 +52187,43 @@ var CopilotApiGateway = class {
5218452187 if (!fs4.existsSync(globalStoragePath)) {
5218552188 fs4.mkdirSync(globalStoragePath, { recursive: true });
5218652189 }
52187- const cloudflaredBin = path4.join(
52188- globalStoragePath,
52189- process.platform === "win32" ? "cloudflared.exe" : "cloudflared"
52190- );
52191- if (!fs4.existsSync(cloudflaredBin)) {
52192- this.logInfo("Cloudflared binary not found, downloading...");
52190+ let cloudflaredBin = "";
52191+ const customPath = this.config.cloudflaredPath;
52192+ if (customPath && fs4.existsSync(customPath)) {
52193+ this.logInfo(`Using custom cloudflared path from configuration: ${customPath}`);
52194+ cloudflaredBin = customPath;
52195+ } else {
52196+ const { execSync } = await import("child_process");
5219352197 try {
52194- await install(cloudflaredBin);
52195- this.logInfo("Cloudflared binary downloaded successfully");
52196- } catch (downloadError) {
52197- const errMsg = downloadError instanceof Error ? downloadError.message : String(downloadError);
52198- this.logError("Failed to download cloudflared binary", downloadError);
52199- return { success: false, error: `Failed to download cloudflared: ${errMsg}. Check your internet connection.` };
52198+ const sysPath = execSync(process.platform === "win32" ? "where cloudflared" : "which cloudflared").toString().split("\n")[0].trim();
52199+ if (sysPath && fs4.existsSync(sysPath)) {
52200+ this.logInfo(`Found cloudflared in system PATH: ${sysPath}`);
52201+ cloudflaredBin = sysPath;
52202+ }
52203+ } catch (e) {
52204+ }
52205+ }
52206+ if (!cloudflaredBin) {
52207+ cloudflaredBin = path4.join(
52208+ globalStoragePath,
52209+ process.platform === "win32" ? "cloudflared.exe" : "cloudflared"
52210+ );
52211+ if (!fs4.existsSync(cloudflaredBin)) {
52212+ this.logInfo("Cloudflared binary not found locally or in PATH, downloading...");
52213+ try {
52214+ await install(cloudflaredBin);
52215+ this.logInfo("Cloudflared binary downloaded successfully");
52216+ } catch (downloadError) {
52217+ const errMsg = downloadError instanceof Error ? downloadError.message : String(downloadError);
52218+ this.logError("Failed to download cloudflared binary", downloadError);
52219+ return { success: false, error: `Failed to download cloudflared: ${errMsg}. Check your internet connection.` };
52220+ }
52221+ } else {
52222+ this.logInfo(`Using cached cloudflared binary: ${cloudflaredBin}`);
5220052223 }
52224+ } else {
52225+ this.logInfo(`Using chosen cloudflared binary: ${cloudflaredBin}`);
5220152226 }
52202- this.logInfo(`Using cloudflared binary: ${cloudflaredBin}`);
5220352227 use(cloudflaredBin);
5220452228 const localUrl = `http://${this.config.host === "0.0.0.0" ? "127.0.0.1" : this.config.host}:${this.config.port}`;
5220552229 const tunnelInstance = Tunnel.quick(localUrl);
@@ -52973,16 +52997,22 @@ var CopilotApiGateway = class {
5297352997 if (cts.token.isCancellationRequested) {
5297452998 break;
5297552999 }
53000+ let textValue;
5297653001 if (part instanceof vscode4.LanguageModelTextPart) {
52977- totalContent += part.value;
53002+ textValue = part.value;
53003+ } else if (!(part instanceof vscode4.LanguageModelToolCallPart)) {
53004+ textValue = this.extractTextFromPart(part);
53005+ }
53006+ if (textValue) {
53007+ totalContent += textValue;
5297853008 if (!firstPart) {
5297953009 res.write(",\n");
5298053010 }
5298153011 const chunk = {
5298253012 candidates: [{
5298353013 content: {
5298453014 role: "model",
52985- parts: [{ text: part.value }]
53015+ parts: [{ text: textValue }]
5298653016 },
5298753017 finishReason: "STOP",
5298853018 index: 0
@@ -53167,6 +53197,19 @@ data: ${JSON.stringify({
5316753197data: ${JSON.stringify({ type: "content_block_stop", index: contentBlockIndex })}
5316853198
5316953199`);
53200+ } else {
53201+ const textValue = this.extractTextFromPart(part);
53202+ if (textValue) {
53203+ totalContent += textValue;
53204+ res.write(`event: content_block_delta
53205+ data: ${JSON.stringify({
53206+ type: "content_block_delta",
53207+ index: contentBlockIndex,
53208+ delta: { type: "text_delta", text: textValue }
53209+ })}
53210+
53211+ `);
53212+ }
5317053213 }
5317153214 }
5317253215 if (!cts.token.isCancellationRequested) {
@@ -53363,6 +53406,25 @@ data: ${JSON.stringify({ type: "error", error: { type: apiError.code || "api_err
5336353406
5336453407`);
5336553408 toolCallIndex++;
53409+ } else {
53410+ const textValue = this.extractTextFromPart(part);
53411+ if (textValue) {
53412+ totalContent += textValue;
53413+ const chunk = {
53414+ id: requestId,
53415+ object: "chat.completion.chunk",
53416+ created,
53417+ model,
53418+ choices: [{
53419+ index: 0,
53420+ delta: { content: textValue },
53421+ finish_reason: null
53422+ }]
53423+ };
53424+ res.write(`data: ${JSON.stringify(chunk)}
53425+
53426+ `);
53427+ }
5336653428 }
5336753429 }
5336853430 const finishReason = toolCalls.length > 0 ? "tool_calls" : "stop";
@@ -53516,6 +53578,11 @@ data: ${JSON.stringify({ type: "error", error: { type: apiError.code || "api_err
5351653578 for await (const part of response.stream) {
5351753579 if (part instanceof vscode4.LanguageModelTextPart) {
5351853580 result += part.value;
53581+ } else if (!(part instanceof vscode4.LanguageModelToolCallPart)) {
53582+ const textValue = this.extractTextFromPart(part);
53583+ if (textValue) {
53584+ result += textValue;
53585+ }
5351953586 }
5352053587 }
5352153588 return result;
@@ -53718,14 +53785,20 @@ data: ${JSON.stringify({
5371853785 if (cts.token.isCancellationRequested) {
5371953786 break;
5372053787 }
53788+ let textValue;
5372153789 if (part instanceof vscode4.LanguageModelTextPart) {
53722- totalContent += part.value;
53790+ textValue = part.value;
53791+ } else if (!(part instanceof vscode4.LanguageModelToolCallPart)) {
53792+ textValue = this.extractTextFromPart(part);
53793+ }
53794+ if (textValue) {
53795+ totalContent += textValue;
5372353796 res.write(`event: response.output_text.delta
5372453797data: ${JSON.stringify({
5372553798 type: "response.output_text.delta",
5372653799 item_id: messageId,
5372753800 content_index: 0,
53728- delta: part.value
53801+ delta: textValue
5372953802 })}
5373053803
5373153804`);
@@ -53881,6 +53954,11 @@ data: ${JSON.stringify({
5388153954 for await (const part of result.stream) {
5388253955 if (part instanceof vscode4.LanguageModelTextPart) {
5388353956 output += part.value;
53957+ } else if (!(part instanceof vscode4.LanguageModelToolCallPart)) {
53958+ const textValue = this.extractTextFromPart(part);
53959+ if (textValue) {
53960+ output += textValue;
53961+ }
5388453962 }
5388553963 }
5388653964 return output;
@@ -53954,6 +54032,11 @@ data: ${JSON.stringify({
5395454032 for await (const part of result.stream) {
5395554033 if (part instanceof vscode4.LanguageModelTextPart) {
5395654034 output += part.value;
54035+ } else if (!(part instanceof vscode4.LanguageModelToolCallPart)) {
54036+ const textValue = this.extractTextFromPart(part);
54037+ if (textValue) {
54038+ output += textValue;
54039+ }
5395754040 }
5395854041 }
5395954042 return output;
@@ -54237,6 +54320,11 @@ IMPORTANT: You MUST respond with valid JSON only.No markdown, no explanation, ju
5423754320 name: part.name,
5423854321 arguments: part.input
5423954322 });
54323+ } else {
54324+ const textValue = this.extractTextFromPart(part);
54325+ if (textValue) {
54326+ textContent += textValue;
54327+ }
5424054328 }
5424154329 }
5424254330 return {
@@ -54343,16 +54431,22 @@ IMPORTANT: You MUST respond with valid JSON only.No markdown, no explanation, ju
5434354431 if (cts.token.isCancellationRequested) {
5434454432 break;
5434554433 }
54434+ let textValue;
5434654435 if (part instanceof vscode4.LanguageModelTextPart) {
54347- totalContent += part.value;
54436+ textValue = part.value;
54437+ } else if (!(part instanceof vscode4.LanguageModelToolCallPart)) {
54438+ textValue = this.extractTextFromPart(part);
54439+ }
54440+ if (textValue) {
54441+ totalContent += textValue;
5434854442 const chunk = {
5434954443 id: completionId,
5435054444 object: "text_completion",
5435154445 created,
5435254446 model,
5435354447 choices: [{
5435454448 index: 0,
54355- text: part.value ,
54449+ text: textValue ,
5435654450 finish_reason: null,
5435754451 logprobs: null
5435854452 }]
@@ -54677,6 +54771,25 @@ ${text} `;
5467754771 }
5467854772 return String(content);
5467954773 }
54774+ /**
54775+ * Extract text from an unknown stream part (e.g. LanguageModelThinkingPart or future part types).
54776+ * The VS Code LM API stream type is `AsyncIterable<LanguageModelTextPart | LanguageModelToolCallPart | unknown>`,
54777+ * meaning newer part types (like thinking parts from reasoning models such as gpt-5-mini) may appear
54778+ * as `unknown`. This method duck-types them to extract any text content they carry.
54779+ */
54780+ extractTextFromPart(part) {
54781+ if (!part || typeof part !== "object") {
54782+ return void 0;
54783+ }
54784+ const p = part;
54785+ if (typeof p.value === "string") {
54786+ return p.value;
54787+ }
54788+ if (typeof p.text === "string") {
54789+ return p.text;
54790+ }
54791+ return void 0;
54792+ }
5468054793 resolveModel(model) {
5468154794 if (typeof model === "string" && model.trim()) {
5468254795 return model.trim();
@@ -56092,6 +56205,9 @@ ${text} `;
5609256205 if (patch.maxConnectionsPerIp !== void 0) {
5609356206 updates.push(Promise.resolve(config2.update("server.maxConnectionsPerIp", patch.maxConnectionsPerIp, vscode4.ConfigurationTarget.Global)));
5609456207 }
56208+ if (patch.cloudflaredPath !== void 0) {
56209+ updates.push(Promise.resolve(config2.update("tunnel.cloudflaredPath", patch.cloudflaredPath, vscode4.ConfigurationTarget.Global)));
56210+ }
5609556211 if (patch.redactionPatterns !== void 0) {
5609656212 updates.push(Promise.resolve(config2.update("server.redactionPatterns", patch.redactionPatterns, vscode4.ConfigurationTarget.Global)));
5609756213 }
@@ -56285,6 +56401,7 @@ function getServerConfig() {
5628556401 const maxPayloadSizeMb = configuration.get("server.maxPayloadSizeMb", 1);
5628656402 const maxConnectionsPerIp = configuration.get("server.maxConnectionsPerIp", 10);
5628756403 const mcpEnabled = vscode4.workspace.getConfiguration("githubCopilotApi.mcp").get("enabled", true);
56404+ const cloudflaredPath = vscode4.workspace.getConfiguration("githubCopilotApi.tunnel").get("cloudflaredPath", "").trim();
5628856405 return {
5628956406 enabled,
5629056407 enableHttp,
@@ -56305,7 +56422,8 @@ function getServerConfig() {
5630556422 requestTimeoutSeconds,
5630656423 maxPayloadSizeMb,
5630756424 maxConnectionsPerIp,
56308- mcpEnabled
56425+ mcpEnabled,
56426+ cloudflaredPath
5630956427 };
5631056428}
5631156429function getErrorMessage(error2) {
@@ -56974,6 +57092,15 @@ for await (const chunk of stream) {
5697457092 });
5697557093 }
5697657094 break;
57095+ case "setCloudflaredPath":
57096+ if (typeof data.value === "string") {
57097+ void gateway2.setCloudflaredPath(data.value).then(async () => {
57098+ if (_CopilotPanel.currentPanel) {
57099+ _CopilotPanel.currentPanel.webview.html = await _CopilotPanel.getPanelHtml(_CopilotPanel.currentPanel.webview, gateway2);
57100+ }
57101+ });
57102+ }
57103+ break;
5697757104 case "setMaxConcurrency":
5697857105 if (typeof data.value === "number") {
5697957106 void gateway2.setMaxConcurrency(data.value).then(async () => {
@@ -58323,6 +58450,14 @@ print(response.choices[0].message.content)\`;
5832358450 </ul>
5832458451 </div>
5832558452
58453+ <div style="margin-bottom: 16px; border-top: 1px solid var(--vscode-widget-border); padding-top: 16px;">
58454+ <div style="font-size: 11px; font-weight: 600; margin-bottom: 8px; opacity: 0.8;">CLOUDFLARED BINARY PATH (OPTIONAL)</div>
58455+ <div style="display: flex; gap: 8px; align-items: center; flex-wrap: wrap;">
58456+ <input type="text" id="cloudflared-path-input" value="${config2.cloudflaredPath || ""}" placeholder="Leave empty for auto-download or PATH" style="flex: 1; min-width: 200px;">
58457+ <button class="secondary" id="btn-set-cloudflared-path" style="width: auto; padding: 4px 12px; font-size: 11px;">Save Path</button>
58458+ </div>
58459+ </div>
58460+
5832658461 <div id="tunnel-status-area" style="margin-bottom: 16px;">
5832758462 ${status.tunnel?.running ? status.tunnel?.url ? `
5832858463 <div style="background: color-mix(in srgb, var(--vscode-testing-iconPassed) 15%, transparent); border: 1px solid var(--vscode-testing-iconPassed); border-radius: 8px; padding: 16px;">
@@ -58975,6 +59110,14 @@ print(response.choices[0].message.content)\`;
5897559110 vscode.postMessage({ type: 'setMaxConnectionsPerIp', value: Number(val) });
5897659111 };
5897759112 }
59113+
59114+ const btnSetCloudflaredPath = document.getElementById('btn-set-cloudflared-path');
59115+ if (btnSetCloudflaredPath) {
59116+ btnSetCloudflaredPath.onclick = function() {
59117+ var val = document.getElementById('cloudflared-path-input').value;
59118+ vscode.postMessage({ type: 'setCloudflaredPath', value: String(val).trim() });
59119+ };
59120+ }
5897859121 document.getElementById('btn-set-concurrency').onclick = function() {
5897959122 var val = document.getElementById('concurrency-input').value;
5898059123 vscode.postMessage({ type: 'setMaxConcurrency', value: Number(val) });
0 commit comments