Skip to content

Commit 0bb67c9

Browse files
committed
Otel v3
1 parent 792459c commit 0bb67c9

File tree

33 files changed

+8643
-6907
lines changed

33 files changed

+8643
-6907
lines changed

apps/sim/app/api/billing/update-cost/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import { type NextRequest, NextResponse } from 'next/server'
44
import { z } from 'zod'
55
import { recordUsage } from '@/lib/billing/core/usage-log'
66
import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing'
7+
import { BillingRouteOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
78
import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
89
import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1'
910
import { checkInternalApiKey } from '@/lib/copilot/request/http'
1011
import { withIncomingGoSpan } from '@/lib/copilot/request/otel'
1112
import { isBillingEnabled } from '@/lib/core/config/feature-flags'
1213
import { type AtomicClaimResult, billingIdempotency } from '@/lib/core/idempotency/service'
1314
import { generateRequestId } from '@/lib/core/utils/request'
14-
import { BillingRouteOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
1515

1616
const logger = createLogger('BillingUpdateCostAPI')
1717

apps/sim/app/api/copilot/api-keys/validate/route.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { eq } from 'drizzle-orm'
55
import { type NextRequest, NextResponse } from 'next/server'
66
import { z } from 'zod'
77
import { checkServerSideUsageLimits } from '@/lib/billing/calculations/usage-monitor'
8+
import { CopilotValidateOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
89
import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
910
import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1'
1011
import { checkInternalApiKey } from '@/lib/copilot/request/http'
1112
import { withIncomingGoSpan } from '@/lib/copilot/request/otel'
12-
import { CopilotValidateOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
1313

1414
const logger = createLogger('CopilotApiKeysValidate')
1515

@@ -33,7 +33,10 @@ export async function POST(req: NextRequest) {
3333
try {
3434
const auth = checkInternalApiKey(req)
3535
if (!auth.success) {
36-
span.setAttribute(TraceAttr.CopilotValidateOutcome, CopilotValidateOutcome.InternalAuthFailed)
36+
span.setAttribute(
37+
TraceAttr.CopilotValidateOutcome,
38+
CopilotValidateOutcome.InternalAuthFailed
39+
)
3740
span.setAttribute(TraceAttr.HttpStatusCode, 401)
3841
return new NextResponse(null, { status: 401 })
3942
}

apps/sim/app/api/copilot/chat/abort/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import { createLogger } from '@sim/logger'
22
import { NextResponse } from 'next/server'
33
import { getLatestRunForStream } from '@/lib/copilot/async-runs/repository'
44
import { SIM_AGENT_API_URL } from '@/lib/copilot/constants'
5+
import { CopilotAbortOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
56
import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
67
import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1'
78
import { fetchGo } from '@/lib/copilot/request/go/fetch'
89
import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request/http'
910
import { withCopilotSpan, withIncomingGoSpan } from '@/lib/copilot/request/otel'
1011
import { abortActiveStream, waitForPendingChatStream } from '@/lib/copilot/request/session'
1112
import { env } from '@/lib/core/config/env'
12-
import { CopilotAbortOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
1313

1414
const logger = createLogger('CopilotChatAbortAPI')
1515
const GO_EXPLICIT_ABORT_TIMEOUT_MS = 3000

apps/sim/app/api/copilot/chat/stop/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import { type NextRequest, NextResponse } from 'next/server'
66
import { z } from 'zod'
77
import { getSession } from '@/lib/auth'
88
import { normalizeMessage, type PersistedMessage } from '@/lib/copilot/chat/persisted-message'
9+
import { CopilotStopOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
910
import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
1011
import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1'
1112
import { withIncomingGoSpan } from '@/lib/copilot/request/otel'
1213
import { taskPubSub } from '@/lib/copilot/tasks'
1314
import { generateId } from '@/lib/core/utils/uuid'
14-
import { CopilotStopOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
1515

1616
const logger = createLogger('CopilotChatStopAPI')
1717

apps/sim/app/api/copilot/chat/stream/route.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import {
66
MothershipStreamV1CompletionStatus,
77
MothershipStreamV1EventType,
88
} from '@/lib/copilot/generated/mothership-stream-v1'
9-
import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
109
import {
1110
CopilotResumeOutcome,
1211
CopilotTransport,
1312
} from '@/lib/copilot/generated/trace-attribute-values-v1'
13+
import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
1414
import { contextFromRequestHeaders } from '@/lib/copilot/request/go/propagation'
1515
import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request/http'
1616
import { getCopilotTracer } from '@/lib/copilot/request/otel'
@@ -144,9 +144,7 @@ export async function GET(request: NextRequest) {
144144
'copilot.resume.request',
145145
{
146146
attributes: {
147-
[TraceAttr.CopilotTransport]: batchMode
148-
? CopilotTransport.Batch
149-
: CopilotTransport.Stream,
147+
[TraceAttr.CopilotTransport]: batchMode ? CopilotTransport.Batch : CopilotTransport.Stream,
150148
[TraceAttr.StreamId]: streamId,
151149
[TraceAttr.UserId]: authenticatedUserId,
152150
[TraceAttr.CopilotResumeAfterCursor]: afterCursor || '0',

apps/sim/app/api/copilot/confirm/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
getRunSegment,
1414
upsertAsyncToolCall,
1515
} from '@/lib/copilot/async-runs/repository'
16+
import { CopilotConfirmOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
1617
import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
1718
import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1'
1819
import { publishToolConfirmation } from '@/lib/copilot/persistence/tool-confirm'
@@ -25,7 +26,6 @@ import {
2526
createUnauthorizedResponse,
2627
} from '@/lib/copilot/request/http'
2728
import { withIncomingGoSpan } from '@/lib/copilot/request/otel'
28-
import { CopilotConfirmOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
2929

3030
const logger = createLogger('CopilotConfirmAPI')
3131

apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -138,15 +138,16 @@ export function MessageActions({ content, chatId, userQuery, requestId }: Messag
138138
}
139139
}, [])
140140

141-
// Render the action row whenever there's ANYTHING to act on. For a
142-
// normal assistant turn that's `content`; for an aborted / error /
143-
// content-less turn it's the `requestId` alone — users still need to
144-
// be able to grab the trace ID for bug reports in those cases.
145141
const hasContent = Boolean(content)
146-
if (!hasContent && !requestId) return null
147-
148142
const canSubmitFeedback = Boolean(chatId && userQuery)
149143

144+
// Render the action row whenever there's something the user can
145+
// actually act on: copy the message, or open the feedback modal
146+
// (thumbs up / down). Request ID alone is not a reason to render the
147+
// row anymore — it's only exposed from inside the thumbs-down modal,
148+
// which requires both chatId and userQuery.
149+
if (!hasContent && !canSubmitFeedback) return null
150+
150151
return (
151152
<>
152153
<div className='flex items-center gap-0.5'>
@@ -197,27 +198,15 @@ export function MessageActions({ content, chatId, userQuery, requestId }: Messag
197198
</Tooltip.Root>
198199
</>
199200
)}
200-
{requestId && (
201-
<Tooltip.Root>
202-
<Tooltip.Trigger asChild>
203-
<button
204-
type='button'
205-
aria-label='Copy request ID'
206-
onClick={copyRequestId}
207-
className={BUTTON_CLASS}
208-
>
209-
{copiedRequestId ? (
210-
<Check className={ICON_CLASS} />
211-
) : (
212-
<Copy className={ICON_CLASS} />
213-
)}
214-
</button>
215-
</Tooltip.Trigger>
216-
<Tooltip.Content side='top'>
217-
{copiedRequestId ? 'Copied request ID' : 'Copy request ID'}
218-
</Tooltip.Content>
219-
</Tooltip.Root>
220-
)}
201+
{/*
202+
Intentionally NO root-row "Copy request ID" button here — it
203+
rendered as an ambiguous standalone Copy icon next to the
204+
message Copy icon, which was confusing (two indistinguishable
205+
copy buttons side by side). The request ID only needs to be
206+
grabbable from the thumbs-down feedback modal below, which is
207+
the surface we actually want people to use when reporting a
208+
bad response.
209+
*/}
221210
</div>
222211

223212
<Modal open={pendingFeedback !== null} onOpenChange={handleModalClose}>

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,7 +1833,7 @@ export function useChat(
18331833
try {
18341834
const pendingLines: string[] = []
18351835

1836-
readLoop: while (true) {
1836+
while (true) {
18371837
if (pendingLines.length === 0) {
18381838
// Once the terminal `complete` event has been processed,
18391839
// don't read another chunk — we've drained everything
@@ -2955,9 +2955,7 @@ export function useChat(
29552955
method: 'POST',
29562956
headers: {
29572957
'Content-Type': 'application/json',
2958-
...(streamTraceparentRef.current
2959-
? { traceparent: streamTraceparentRef.current }
2960-
: {}),
2958+
...(streamTraceparentRef.current ? { traceparent: streamTraceparentRef.current } : {}),
29612959
},
29622960
body: JSON.stringify({
29632961
chatId,

apps/sim/lib/copilot/chat/post.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ import { generateWorkspaceContext } from '@/lib/copilot/chat/workspace-context'
2121
import { COPILOT_REQUEST_MODES } from '@/lib/copilot/constants'
2222
import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
2323
import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1'
24-
import {
25-
createBadRequestResponse,
26-
createRequestTracker,
27-
createUnauthorizedResponse,
28-
} from '@/lib/copilot/request/http'
24+
import { createBadRequestResponse, createUnauthorizedResponse } from '@/lib/copilot/request/http'
2925
import { createSSEStream, SSE_RESPONSE_HEADERS } from '@/lib/copilot/request/lifecycle/start'
3026
import { startCopilotOtelRoot, withCopilotSpan } from '@/lib/copilot/request/otel'
3127
import {
@@ -609,17 +605,27 @@ async function resolveBranch(params: {
609605
}
610606

611607
export async function handleUnifiedChatPost(req: NextRequest) {
612-
const tracker = createRequestTracker(false)
613608
let actualChatId: string | undefined
614609
let userMessageId = ''
615610
let chatStreamLockAcquired = false
616-
// Started once we know the streamId (= userMessageId). Every subsequent
617-
// span (persistUserMessage, createRunSegment, the whole SSE stream, etc.)
618-
// nests under this root via AsyncLocalStorage / explicit propagation,
619-
// and the stream's terminal code path calls finish() when the request
620-
// actually ends. Errors thrown from the handler before the stream
621-
// starts are finished here in the catch below.
611+
// Started once we've parsed the body (need userMessageId to stamp as
612+
// streamId). Every subsequent span (persistUserMessage,
613+
// createRunSegment, the whole SSE stream, etc.) nests under this
614+
// root via AsyncLocalStorage / explicit propagation, and the stream's
615+
// terminal code path calls finish() when the request actually ends.
616+
// Errors thrown from the handler before the stream starts are
617+
// finished here in the catch below.
622618
let otelRoot: ReturnType<typeof startCopilotOtelRoot> | undefined
619+
// `requestId` is the canonical logical ID for this HTTP request —
620+
// same value that flows into `request.id`/`sim.request_id` span
621+
// attributes, the persisted `msg.requestId`, and eventually the
622+
// Grafana trace-ID search box. Derived from otelRoot.requestId (= the
623+
// OTel trace ID of the root span) as soon as that's created. Stays
624+
// empty only in the narrow window before otelRoot is set — errors in
625+
// that window can't be correlated to any trace anyway, and their log
626+
// line carries the error message + stack which is the actually
627+
// useful info.
628+
let requestId = ''
623629
const executionId = crypto.randomUUID()
624630
const runId = crypto.randomUUID()
625631

@@ -635,7 +641,11 @@ export async function handleUnifiedChatPost(req: NextRequest) {
635641
userMessageId = body.userMessageId || crypto.randomUUID()
636642

637643
otelRoot = startCopilotOtelRoot({
638-
requestId: tracker.requestId,
644+
// No explicit requestId — startCopilotOtelRoot derives it from
645+
// the span's OTel trace ID so `msg.requestId` on the UI side
646+
// ends up being the same value Grafana uses. See the scope
647+
// doc-comment and the call site for why this is the desired
648+
// direction of the unification.
639649
streamId: userMessageId,
640650
executionId,
641651
runId,
@@ -646,6 +656,12 @@ export async function handleUnifiedChatPost(req: NextRequest) {
646656
// by setInputMessages above.
647657
userMessagePreview: body.message,
648658
})
659+
// Promote the OTel-derived ID to the handler-level `requestId` so
660+
// every downstream consumer (logs, orchestrator, onComplete,
661+
// onError, persisted assistant message) uses the same value.
662+
if (otelRoot.requestId) {
663+
requestId = otelRoot.requestId
664+
}
649665
// Emit `gen_ai.input.messages` on the root agent span for OTel
650666
// GenAI spec compliance (Honeycomb's Gen AI view keys off this).
651667
// Gated on OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT
@@ -800,7 +816,7 @@ export async function handleUnifiedChatPost(req: NextRequest) {
800816
message: body.message,
801817
workspaceId,
802818
chatId: actualChatId,
803-
requestId: tracker.requestId,
819+
requestId,
804820
}),
805821
otelRoot!.context
806822
)
@@ -908,7 +924,7 @@ export async function handleUnifiedChatPost(req: NextRequest) {
908924
message: body.message,
909925
titleModel: branch.titleModel,
910926
...(branch.titleProvider ? { titleProvider: branch.titleProvider } : {}),
911-
requestId: tracker.requestId,
927+
requestId,
912928
workspaceId,
913929
otelRoot: otelRoot!,
914930
orchestrateOptions: {
@@ -925,15 +941,15 @@ export async function handleUnifiedChatPost(req: NextRequest) {
925941
onComplete: buildOnComplete({
926942
chatId: actualChatId,
927943
userMessageId,
928-
requestId: tracker.requestId,
944+
requestId,
929945
workspaceId,
930946
notifyWorkspaceStatus: branch.notifyWorkspaceStatus,
931947
otelRoot,
932948
}),
933949
onError: buildOnError({
934950
chatId: actualChatId,
935951
userMessageId,
936-
requestId: tracker.requestId,
952+
requestId,
937953
workspaceId,
938954
notifyWorkspaceStatus: branch.notifyWorkspaceStatus,
939955
}),
@@ -970,7 +986,7 @@ export async function handleUnifiedChatPost(req: NextRequest) {
970986
)
971987
}
972988

973-
logger.error(`[${tracker.requestId}] Error handling unified chat request`, {
989+
logger.error(`[${requestId}] Error handling unified chat request`, {
974990
error: error instanceof Error ? error.message : 'Unknown error',
975991
stack: error instanceof Error ? error.stack : undefined,
976992
})

apps/sim/lib/copilot/chat/terminal-state.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { db } from '@sim/db'
22
import { copilotChats } from '@sim/db/schema'
33
import { and, eq, sql } from 'drizzle-orm'
44
import type { PersistedMessage } from '@/lib/copilot/chat/persisted-message'
5+
import { CopilotChatFinalizeOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
56
import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
67
import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1'
78
import { withCopilotSpan } from '@/lib/copilot/request/otel'
8-
import { CopilotChatFinalizeOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
99

1010
interface FinalizeAssistantTurnParams {
1111
chatId: string
@@ -66,7 +66,10 @@ export async function finalizeAssistantTurn({
6666
messages: sql`${copilotChats.messages} || ${JSON.stringify([assistantMessage])}::jsonb`,
6767
})
6868
.where(updateWhere)
69-
span.setAttribute(TraceAttr.ChatFinalizeOutcome, CopilotChatFinalizeOutcome.AppendedAssistant)
69+
span.setAttribute(
70+
TraceAttr.ChatFinalizeOutcome,
71+
CopilotChatFinalizeOutcome.AppendedAssistant
72+
)
7073
return
7174
}
7275

0 commit comments

Comments
 (0)