( fetchUrl: string, fetchOptions: RequestInit, context: StreamingContext, execContext: ExecutionContext, options: StreamLoopOptions )
| 120 | * Feature-specific normalization runs through dedicated adapters before the raw event is forwarded. |
| 121 | */ |
| 122 | export async function runStreamLoop( |
| 123 | fetchUrl: string, |
| 124 | fetchOptions: RequestInit, |
| 125 | context: StreamingContext, |
| 126 | execContext: ExecutionContext, |
| 127 | options: StreamLoopOptions |
| 128 | ): Promise<void> { |
| 129 | const { timeout = ORCHESTRATION_TIMEOUT_MS, abortSignal } = options |
| 130 | const filePreviewAdapterState = createFilePreviewAdapterState() |
| 131 | |
| 132 | const pathname = new URL(fetchUrl).pathname |
| 133 | const requestBodyBytes = estimateBodyBytes(fetchOptions.body) |
| 134 | const fetchSpan = context.trace.startSpan(`HTTP Request → ${pathname}`, 'sim.http.fetch', { |
| 135 | url: fetchUrl, |
| 136 | method: fetchOptions.method ?? 'GET', |
| 137 | requestBodyBytes, |
| 138 | }) |
| 139 | const fetchStart = performance.now() |
| 140 | let response: Response |
| 141 | try { |
| 142 | response = await fetchGo(fetchUrl, { |
| 143 | ...fetchOptions, |
| 144 | signal: abortSignal, |
| 145 | otelContext: options.otelContext, |
| 146 | spanName: `sim → go ${pathname}`, |
| 147 | operation: 'stream', |
| 148 | attributes: { |
| 149 | [TraceAttr.CopilotStream]: true, |
| 150 | ...(requestBodyBytes ? { [TraceAttr.HttpRequestContentLength]: requestBodyBytes } : {}), |
| 151 | }, |
| 152 | }) |
| 153 | } catch (error) { |
| 154 | fetchSpan.attributes = { |
| 155 | ...(fetchSpan.attributes ?? {}), |
| 156 | headersMs: Math.round(performance.now() - fetchStart), |
| 157 | } |
| 158 | context.trace.endSpan(fetchSpan, abortSignal?.aborted ? 'cancelled' : 'error') |
| 159 | throw error |
| 160 | } |
| 161 | const headersElapsedMs = Math.round(performance.now() - fetchStart) |
| 162 | fetchSpan.attributes = { |
| 163 | ...(fetchSpan.attributes ?? {}), |
| 164 | status: response.status, |
| 165 | headersMs: headersElapsedMs, |
| 166 | } |
| 167 | |
| 168 | if (!response.ok) { |
| 169 | context.trace.endSpan(fetchSpan, 'error') |
| 170 | const errorText = await response.text().catch(() => '') |
| 171 | |
| 172 | if (response.status === 402) { |
| 173 | throw new BillingLimitError(execContext.userId) |
| 174 | } |
| 175 | |
| 176 | throw new CopilotBackendError( |
| 177 | `Copilot backend error (${response.status}): ${errorText || response.statusText}`, |
| 178 | { status: response.status, body: errorText || response.statusText } |
| 179 | ) |
no test coverage detected