|
| 1 | +import { AxiosInstance } from "axios" |
1 | 2 | import { spawn } from "child_process" |
2 | 3 | import { Api } from "coder/site/src/api/api" |
3 | 4 | import { ProvisionerJobLog, Workspace } from "coder/site/src/api/typesGenerated" |
| 5 | +import { FetchLikeInit } from "eventsource" |
4 | 6 | import fs from "fs/promises" |
5 | 7 | import { ProxyAgent } from "proxy-agent" |
6 | 8 | import * as vscode from "vscode" |
@@ -120,6 +122,60 @@ export async function makeCoderSdk(baseUrl: string, token: string | undefined, s |
120 | 122 | return restClient |
121 | 123 | } |
122 | 124 |
|
| 125 | +/** |
| 126 | + * Creates a fetch adapter using an Axios instance that returns streaming responses. |
| 127 | + * This can be used with APIs that accept fetch-like interfaces. |
| 128 | + */ |
| 129 | +export function createStreamingFetchAdapter(axiosInstance: AxiosInstance) { |
| 130 | + return async (url: string | URL, init?: FetchLikeInit) => { |
| 131 | + const urlStr = url.toString() |
| 132 | + |
| 133 | + const response = await axiosInstance.request({ |
| 134 | + url: urlStr, |
| 135 | + headers: init?.headers as Record<string, string>, |
| 136 | + responseType: "stream", |
| 137 | + validateStatus: () => true, // Don't throw on any status code |
| 138 | + }) |
| 139 | + const stream = new ReadableStream({ |
| 140 | + start(controller) { |
| 141 | + response.data.on("data", (chunk: Buffer) => { |
| 142 | + controller.enqueue(chunk) |
| 143 | + }) |
| 144 | + |
| 145 | + response.data.on("end", () => { |
| 146 | + controller.close() |
| 147 | + }) |
| 148 | + |
| 149 | + response.data.on("error", (err: Error) => { |
| 150 | + controller.error(err) |
| 151 | + }) |
| 152 | + }, |
| 153 | + |
| 154 | + cancel() { |
| 155 | + response.data.destroy() |
| 156 | + return Promise.resolve() |
| 157 | + }, |
| 158 | + }) |
| 159 | + |
| 160 | + const createReader = () => stream.getReader() |
| 161 | + |
| 162 | + return { |
| 163 | + body: { |
| 164 | + getReader: () => createReader(), |
| 165 | + }, |
| 166 | + url: urlStr, |
| 167 | + status: response.status, |
| 168 | + redirected: response.request.res.responseUrl !== urlStr, |
| 169 | + headers: { |
| 170 | + get: (name: string) => { |
| 171 | + const value = response.headers[name.toLowerCase()] |
| 172 | + return value === undefined ? null : String(value) |
| 173 | + }, |
| 174 | + }, |
| 175 | + } |
| 176 | + } |
| 177 | +} |
| 178 | + |
123 | 179 | /** |
124 | 180 | * Start or update a workspace and return the updated workspace. |
125 | 181 | */ |
@@ -224,6 +280,7 @@ export async function waitForBuild( |
224 | 280 | | undefined, |
225 | 281 | }, |
226 | 282 | followRedirects: true, |
| 283 | + agent: restClient.getAxiosInstance().defaults.httpAgent, |
227 | 284 | }) |
228 | 285 | socket.binaryType = "nodebuffer" |
229 | 286 | socket.on("message", (data) => { |
|
0 commit comments