( scope: CopilotOtelScope )
| 430 | } |
| 431 | |
| 432 | export function startCopilotOtelRoot( |
| 433 | scope: CopilotOtelScope |
| 434 | ): CopilotOtelRoot & { requestId: string } { |
| 435 | // TRUE root — don't inherit from Next's HTTP handler span (the |
| 436 | // sampler drops those; we'd orphan the whole mothership tree). |
| 437 | const parentContext = ROOT_CONTEXT |
| 438 | // Start with a placeholder `requestId`, then overwrite using the |
| 439 | // span's actual trace ID so the UI copy-button value pastes |
| 440 | // directly into Grafana. |
| 441 | const span = getTracer().startSpan( |
| 442 | TraceSpan.GenAiAgentExecute, |
| 443 | { attributes: buildAgentSpanAttributes({ ...scope, requestId: '' }) }, |
| 444 | parentContext |
| 445 | ) |
| 446 | const carrierSpan = isValidSpanContext(span.spanContext()) |
| 447 | ? span |
| 448 | : trace.wrapSpanContext(createFallbackSpanContext()) |
| 449 | const spanContext = carrierSpan.spanContext() |
| 450 | const requestId = |
| 451 | scope.requestId ?? |
| 452 | (spanContext.traceId && spanContext.traceId.length === 32 ? spanContext.traceId : '') |
| 453 | span.setAttribute(TraceAttr.RequestId, requestId) |
| 454 | span.setAttribute(TraceAttr.SimRequestId, requestId) |
| 455 | const rootContext = trace.setSpan(parentContext, carrierSpan) |
| 456 | |
| 457 | let finished = false |
| 458 | const finish: CopilotOtelRoot['finish'] = (outcome, error, cancelReason) => { |
| 459 | if (finished) return |
| 460 | finished = true |
| 461 | const resolvedOutcome = outcome ?? RequestTraceV1Outcome.success |
| 462 | span.setAttribute(TraceAttr.CopilotRequestOutcome, resolvedOutcome) |
| 463 | // Policy: `explicit_stop` is the ONLY cancellation we treat as |
| 464 | // expected (status unset → dashboards see it as OK). Everything |
| 465 | // else — client_disconnect, unknown reason, bug-case cancels — |
| 466 | // escalates to ERROR so it shows up on error panels. |
| 467 | const isExplicitStop = cancelReason === CopilotRequestCancelReason.ExplicitStop |
| 468 | if (error) { |
| 469 | markSpanForError(span, error) |
| 470 | if (isExplicitStop || isExplicitUserStopError(error)) { |
| 471 | span.setStatus({ code: SpanStatusCode.OK }) |
| 472 | } |
| 473 | } else if (resolvedOutcome === RequestTraceV1Outcome.success) { |
| 474 | span.setStatus({ code: SpanStatusCode.OK }) |
| 475 | } else if (resolvedOutcome === RequestTraceV1Outcome.cancelled) { |
| 476 | if (isExplicitStop) { |
| 477 | span.setStatus({ code: SpanStatusCode.OK }) |
| 478 | } else { |
| 479 | span.setStatus({ |
| 480 | code: SpanStatusCode.ERROR, |
| 481 | message: `cancelled: ${cancelReason ?? 'unknown'}`, |
| 482 | }) |
| 483 | } |
| 484 | } |
| 485 | span.end() |
| 486 | } |
| 487 | |
| 488 | return { |
| 489 | span, |
no test coverage detected