(
workspaceId: WorkspaceId,
streamInfo: WorkspaceStreamInfo
)
| 2328 | } |
| 2329 | |
| 2330 | private async handleTruncatedStreamCompletion( |
| 2331 | workspaceId: WorkspaceId, |
| 2332 | streamInfo: WorkspaceStreamInfo |
| 2333 | ): Promise<void> { |
| 2334 | const workspaceLog = this.getWorkspaceLogger(workspaceId, streamInfo); |
| 2335 | const streamMeta = await this.getStreamMetadata(streamInfo); |
| 2336 | const totalUsage = this.resolveTotalUsageForStreamEnd(streamInfo, streamMeta.totalUsage); |
| 2337 | const contextUsage = streamMeta.contextUsage ?? streamInfo.lastStepUsage; |
| 2338 | const previousResponseId = this.getOpenAIPreviousResponseId(streamInfo.request.providerOptions); |
| 2339 | const providerDisplayName = getStreamProviderDisplayName(streamInfo.model); |
| 2340 | |
| 2341 | // Do not treat iterator EOF as success. Anthropic and OpenAI Responses both |
| 2342 | // have semantic terminal events; without the SDK finish part, this may be a |
| 2343 | // clean proxy/provider drop after partial text was already streamed. |
| 2344 | workspaceLog.error("Stream ended without a terminal finish event", { |
| 2345 | messageId: streamInfo.messageId, |
| 2346 | model: streamInfo.model, |
| 2347 | providerDisplayName, |
| 2348 | durationMs: streamMeta.duration, |
| 2349 | totalUsage, |
| 2350 | contextUsage, |
| 2351 | cumulativeUsage: streamInfo.cumulativeUsage, |
| 2352 | previousResponseId, |
| 2353 | partsCount: streamInfo.parts.length, |
| 2354 | }); |
| 2355 | |
| 2356 | await this.handleStreamFailure( |
| 2357 | workspaceId, |
| 2358 | streamInfo, |
| 2359 | new StreamTruncatedError(providerDisplayName) |
| 2360 | ); |
| 2361 | } |
| 2362 | |
| 2363 | private async retryEmptyStreamBeforeFailure( |
| 2364 | workspaceId: WorkspaceId, |
no test coverage detected