(upstreamResponse)
| 366 | return { reasoning, content, residueOnly } |
| 367 | } |
| 368 | |
| 369 | /** |
| 370 | * Error de entrega para la ronda 100% residuo. Misma clase de fallo que el gemelo |
| 371 | * (anthropic.js:2307 -> 502 `invalid_tool_call_error`), con la forma que este camino ya usa: |
| 372 | * `writeOpenAIHttpError` emite JSON si aun no salieron cabeceras y un evento de error SSE si |
| 373 | * ya salieron -- el mismo mecanismo por el que viaja el 422 del gate. |
| 374 | */ |
| 375 | const RESIDUE_ONLY_DETAIL = '整轮内容只有协议残渣,剥离后为空' |
| 376 | const writeResidueOnlyError = (res, label) => { |
| 377 | logger.warn( |
| 378 | `OpenAI ${label} Agent 工具协议失败,放弃交付 (${RESIDUE_ONLY_DETAIL})`, |
| 379 | 'AGENT' |
| 380 | ) |
| 381 | writeOpenAIHttpError(res, { |
| 382 | status: 502, |
| 383 | message: `上游返回了残缺、非法或不存在的工具调用 (${RESIDUE_ONLY_DETAIL})`, |
| 384 | code: 'invalid_tool_call' |
| 385 | }) |
| 386 | } |
| 387 | |
| 388 | const handleOpenAIAgentStream = async ( |
| 389 | res, |
| 390 | response, |
| 391 | enableThinking, |
| 392 | enableWebSearch, |
| 393 | requestBody, |
| 394 | options |
| 395 | ) => { |
| 396 | setResponseHeaders(res, true) |
| 397 | const messageId = generateUUID() |
| 398 | const created = Math.round(Date.now() / 1000) |
| 399 | let firstDelta = true |
| 400 | const writeDelta = (delta) => { |
| 401 | if (!delta || Object.keys(delta).length === 0) return |
| 402 | const normalizedDelta = firstDelta ? { role: 'assistant', ...delta } : delta |
| 403 | firstDelta = false |
| 404 | res.write(`data: ${JSON.stringify({ |
| 405 | id: `chatcmpl-${messageId}`, |
| 406 | object: 'chat.completion.chunk', |
| 407 | created, |
no test coverage detected