Skip to content

Commit 9b4ca6f

Browse files
committed
Otel fixes
1 parent c2d9dd9 commit 9b4ca6f

33 files changed

Lines changed: 7657 additions & 8265 deletions

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { withIncomingGoSpan } from '@/lib/copilot/request/otel'
1111
import { isBillingEnabled } from '@/lib/core/config/feature-flags'
1212
import { type AtomicClaimResult, billingIdempotency } from '@/lib/core/idempotency/service'
1313
import { generateRequestId } from '@/lib/core/utils/request'
14+
import { BillingRouteOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
1415

1516
const logger = createLogger('BillingUpdateCostAPI')
1617

@@ -60,7 +61,7 @@ async function updateCostInner(
6061
logger.info(`[${requestId}] Update cost request started`)
6162

6263
if (!isBillingEnabled) {
63-
span.setAttribute(TraceAttr.BillingOutcome, 'billing_disabled')
64+
span.setAttribute(TraceAttr.BillingOutcome, BillingRouteOutcome.BillingDisabled)
6465
span.setAttribute(TraceAttr.HttpStatusCode, 200)
6566
return NextResponse.json({
6667
success: true,
@@ -77,7 +78,7 @@ async function updateCostInner(
7778
const authResult = checkInternalApiKey(req)
7879
if (!authResult.success) {
7980
logger.warn(`[${requestId}] Authentication failed: ${authResult.error}`)
80-
span.setAttribute(TraceAttr.BillingOutcome, 'auth_failed')
81+
span.setAttribute(TraceAttr.BillingOutcome, BillingRouteOutcome.AuthFailed)
8182
span.setAttribute(TraceAttr.HttpStatusCode, 401)
8283
return NextResponse.json(
8384
{
@@ -96,7 +97,7 @@ async function updateCostInner(
9697
errors: validation.error.issues,
9798
body,
9899
})
99-
span.setAttribute(TraceAttr.BillingOutcome, 'invalid_body')
100+
span.setAttribute(TraceAttr.BillingOutcome, BillingRouteOutcome.InvalidBody)
100101
span.setAttribute(TraceAttr.HttpStatusCode, 400)
101102
return NextResponse.json(
102103
{
@@ -133,7 +134,7 @@ async function updateCostInner(
133134
userId,
134135
source,
135136
})
136-
span.setAttribute(TraceAttr.BillingOutcome, 'duplicate_idempotency_key')
137+
span.setAttribute(TraceAttr.BillingOutcome, BillingRouteOutcome.DuplicateIdempotencyKey)
137138
span.setAttribute(TraceAttr.HttpStatusCode, 409)
138139
return NextResponse.json(
139140
{
@@ -199,7 +200,7 @@ async function updateCostInner(
199200
cost,
200201
})
201202

202-
span.setAttribute(TraceAttr.BillingOutcome, 'billed')
203+
span.setAttribute(TraceAttr.BillingOutcome, BillingRouteOutcome.Billed)
203204
span.setAttribute(TraceAttr.HttpStatusCode, 200)
204205
span.setAttribute(TraceAttr.BillingDurationMs, duration)
205206
return NextResponse.json({
@@ -236,7 +237,7 @@ async function updateCostInner(
236237
)
237238
}
238239

239-
span.setAttribute(TraceAttr.BillingOutcome, 'internal_error')
240+
span.setAttribute(TraceAttr.BillingOutcome, BillingRouteOutcome.InternalError)
240241
span.setAttribute(TraceAttr.HttpStatusCode, 500)
241242
span.setAttribute(TraceAttr.BillingDurationMs, duration)
242243
return NextResponse.json(

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
99
import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1'
1010
import { checkInternalApiKey } from '@/lib/copilot/request/http'
1111
import { withIncomingGoSpan } from '@/lib/copilot/request/otel'
12+
import { CopilotValidateOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
1213

1314
const logger = createLogger('CopilotApiKeysValidate')
1415

@@ -32,7 +33,7 @@ export async function POST(req: NextRequest) {
3233
try {
3334
const auth = checkInternalApiKey(req)
3435
if (!auth.success) {
35-
span.setAttribute(TraceAttr.CopilotValidateOutcome, 'internal_auth_failed')
36+
span.setAttribute(TraceAttr.CopilotValidateOutcome, CopilotValidateOutcome.InternalAuthFailed)
3637
span.setAttribute(TraceAttr.HttpStatusCode, 401)
3738
return new NextResponse(null, { status: 401 })
3839
}
@@ -41,7 +42,7 @@ export async function POST(req: NextRequest) {
4142
const validationResult = ValidateApiKeySchema.safeParse(body)
4243
if (!validationResult.success) {
4344
logger.warn('Invalid validation request', { errors: validationResult.error.errors })
44-
span.setAttribute(TraceAttr.CopilotValidateOutcome, 'invalid_body')
45+
span.setAttribute(TraceAttr.CopilotValidateOutcome, CopilotValidateOutcome.InvalidBody)
4546
span.setAttribute(TraceAttr.HttpStatusCode, 400)
4647
return NextResponse.json(
4748
{
@@ -58,7 +59,7 @@ export async function POST(req: NextRequest) {
5859
const [existingUser] = await db.select().from(user).where(eq(user.id, userId)).limit(1)
5960
if (!existingUser) {
6061
logger.warn('[API VALIDATION] userId does not exist', { userId })
61-
span.setAttribute(TraceAttr.CopilotValidateOutcome, 'user_not_found')
62+
span.setAttribute(TraceAttr.CopilotValidateOutcome, CopilotValidateOutcome.UserNotFound)
6263
span.setAttribute(TraceAttr.HttpStatusCode, 403)
6364
return NextResponse.json({ error: 'User not found' }, { status: 403 })
6465
}
@@ -80,17 +81,17 @@ export async function POST(req: NextRequest) {
8081

8182
if (isExceeded) {
8283
logger.info('[API VALIDATION] Usage exceeded', { userId, currentUsage, limit })
83-
span.setAttribute(TraceAttr.CopilotValidateOutcome, 'usage_exceeded')
84+
span.setAttribute(TraceAttr.CopilotValidateOutcome, CopilotValidateOutcome.UsageExceeded)
8485
span.setAttribute(TraceAttr.HttpStatusCode, 402)
8586
return new NextResponse(null, { status: 402 })
8687
}
8788

88-
span.setAttribute(TraceAttr.CopilotValidateOutcome, 'ok')
89+
span.setAttribute(TraceAttr.CopilotValidateOutcome, CopilotValidateOutcome.Ok)
8990
span.setAttribute(TraceAttr.HttpStatusCode, 200)
9091
return new NextResponse(null, { status: 200 })
9192
} catch (error) {
9293
logger.error('Error validating usage limit', { error })
93-
span.setAttribute(TraceAttr.CopilotValidateOutcome, 'internal_error')
94+
span.setAttribute(TraceAttr.CopilotValidateOutcome, CopilotValidateOutcome.InternalError)
9495
span.setAttribute(TraceAttr.HttpStatusCode, 500)
9596
return NextResponse.json({ error: 'Failed to validate usage' }, { status: 500 })
9697
}

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request/htt
99
import { withCopilotSpan, withIncomingGoSpan } from '@/lib/copilot/request/otel'
1010
import { abortActiveStream, waitForPendingChatStream } from '@/lib/copilot/request/session'
1111
import { env } from '@/lib/core/config/env'
12+
import { CopilotAbortOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
1213

1314
const logger = createLogger('CopilotChatAbortAPI')
1415
const GO_EXPLICIT_ABORT_TIMEOUT_MS = 3000
@@ -34,7 +35,7 @@ export async function POST(request: Request) {
3435
await authenticateCopilotRequestSessionOnly()
3536

3637
if (!isAuthenticated || !authenticatedUserId) {
37-
rootSpan.setAttribute(TraceAttr.CopilotAbortOutcome, 'unauthorized')
38+
rootSpan.setAttribute(TraceAttr.CopilotAbortOutcome, CopilotAbortOutcome.Unauthorized)
3839
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
3940
}
4041

@@ -48,7 +49,7 @@ export async function POST(request: Request) {
4849
let chatId = typeof body.chatId === 'string' ? body.chatId : ''
4950

5051
if (!streamId) {
51-
rootSpan.setAttribute(TraceAttr.CopilotAbortOutcome, 'missing_stream_id')
52+
rootSpan.setAttribute(TraceAttr.CopilotAbortOutcome, CopilotAbortOutcome.MissingStreamId)
5253
return NextResponse.json({ error: 'streamId is required' }, { status: 400 })
5354
}
5455
rootSpan.setAttributes({
@@ -139,17 +140,17 @@ export async function POST(request: Request) {
139140
}
140141
)
141142
if (!settled) {
142-
rootSpan.setAttribute(TraceAttr.CopilotAbortOutcome, 'settle_timeout')
143+
rootSpan.setAttribute(TraceAttr.CopilotAbortOutcome, CopilotAbortOutcome.SettleTimeout)
143144
return NextResponse.json(
144145
{ error: 'Previous response is still shutting down', aborted, settled: false },
145146
{ status: 409 }
146147
)
147148
}
148-
rootSpan.setAttribute(TraceAttr.CopilotAbortOutcome, 'settled')
149+
rootSpan.setAttribute(TraceAttr.CopilotAbortOutcome, CopilotAbortOutcome.Settled)
149150
return NextResponse.json({ aborted, settled: true })
150151
}
151152

152-
rootSpan.setAttribute(TraceAttr.CopilotAbortOutcome, 'no_chat_id')
153+
rootSpan.setAttribute(TraceAttr.CopilotAbortOutcome, CopilotAbortOutcome.NoChatId)
153154
return NextResponse.json({ aborted })
154155
}
155156
)

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { TraceSpan } from '@/lib/copilot/generated/trace-spans-v1'
1111
import { withIncomingGoSpan } from '@/lib/copilot/request/otel'
1212
import { taskPubSub } from '@/lib/copilot/tasks'
1313
import { generateId } from '@/lib/core/utils/uuid'
14+
import { CopilotStopOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
1415

1516
const logger = createLogger('CopilotChatStopAPI')
1617

@@ -79,7 +80,7 @@ export async function POST(req: NextRequest) {
7980
try {
8081
const session = await getSession()
8182
if (!session?.user?.id) {
82-
span.setAttribute(TraceAttr.CopilotStopOutcome, 'unauthorized')
83+
span.setAttribute(TraceAttr.CopilotStopOutcome, CopilotStopOutcome.Unauthorized)
8384
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
8485
}
8586

@@ -102,7 +103,7 @@ export async function POST(req: NextRequest) {
102103
.limit(1)
103104

104105
if (!row) {
105-
span.setAttribute(TraceAttr.CopilotStopOutcome, 'chat_not_found')
106+
span.setAttribute(TraceAttr.CopilotStopOutcome, CopilotStopOutcome.ChatNotFound)
106107
return NextResponse.json({ success: true })
107108
}
108109

@@ -160,15 +161,18 @@ export async function POST(req: NextRequest) {
160161
})
161162
}
162163

163-
span.setAttribute(TraceAttr.CopilotStopOutcome, updated ? 'persisted' : 'no_matching_row')
164+
span.setAttribute(
165+
TraceAttr.CopilotStopOutcome,
166+
updated ? CopilotStopOutcome.Persisted : CopilotStopOutcome.NoMatchingRow
167+
)
164168
return NextResponse.json({ success: true })
165169
} catch (error) {
166170
if (error instanceof z.ZodError) {
167-
span.setAttribute(TraceAttr.CopilotStopOutcome, 'validation_error')
171+
span.setAttribute(TraceAttr.CopilotStopOutcome, CopilotStopOutcome.ValidationError)
168172
return NextResponse.json({ error: 'Invalid request' }, { status: 400 })
169173
}
170174
logger.error('Error stopping chat stream:', error)
171-
span.setAttribute(TraceAttr.CopilotStopOutcome, 'internal_error')
175+
span.setAttribute(TraceAttr.CopilotStopOutcome, CopilotStopOutcome.InternalError)
172176
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
173177
}
174178
}

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

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import {
77
MothershipStreamV1EventType,
88
} from '@/lib/copilot/generated/mothership-stream-v1'
99
import { TraceAttr } from '@/lib/copilot/generated/trace-attributes-v1'
10+
import {
11+
CopilotResumeOutcome,
12+
CopilotTransport,
13+
} from '@/lib/copilot/generated/trace-attribute-values-v1'
14+
import { contextFromRequestHeaders } from '@/lib/copilot/request/go/propagation'
1015
import { authenticateCopilotRequestSessionOnly } from '@/lib/copilot/request/http'
1116
import { getCopilotTracer } from '@/lib/copilot/request/otel'
1217
import {
@@ -125,15 +130,31 @@ export async function GET(request: NextRequest) {
125130
// manually, capture its context, and re-enter that context inside the
126131
// stream callback so every nested `withCopilotSpan` / `withDbSpan` call
127132
// attaches to this root.
128-
const rootSpan = getCopilotTracer().startSpan('copilot.resume.request', {
129-
attributes: {
130-
[TraceAttr.CopilotTransport]: batchMode ? 'batch' : 'stream',
131-
[TraceAttr.StreamId]: streamId,
132-
[TraceAttr.UserId]: authenticatedUserId,
133-
[TraceAttr.CopilotResumeAfterCursor]: afterCursor || '0',
133+
//
134+
// `contextFromRequestHeaders` extracts the W3C `traceparent` the
135+
// client echoed (set via `streamTraceparentRef` on Sim's chat POST
136+
// response), so the resume span becomes a child of the original
137+
// chat's `gen_ai.agent.execute` trace instead of a disconnected
138+
// new root. On reconnects after page reload (client ref was wiped)
139+
// the header is absent and extraction leaves the ambient context
140+
// alone → the resume span becomes its own root. Same as pre-
141+
// linking behavior; no regression.
142+
const incomingContext = contextFromRequestHeaders(request.headers)
143+
const rootSpan = getCopilotTracer().startSpan(
144+
'copilot.resume.request',
145+
{
146+
attributes: {
147+
[TraceAttr.CopilotTransport]: batchMode
148+
? CopilotTransport.Batch
149+
: CopilotTransport.Stream,
150+
[TraceAttr.StreamId]: streamId,
151+
[TraceAttr.UserId]: authenticatedUserId,
152+
[TraceAttr.CopilotResumeAfterCursor]: afterCursor || '0',
153+
},
134154
},
135-
})
136-
const rootContext = trace.setSpan(otelContext.active(), rootSpan)
155+
incomingContext
156+
)
157+
const rootContext = trace.setSpan(incomingContext, rootSpan)
137158

138159
try {
139160
return await otelContext.with(rootContext, () =>
@@ -190,7 +211,7 @@ async function handleResumeRequestBody({
190211
runStatus: run?.status,
191212
})
192213
if (!run) {
193-
rootSpan.setAttribute(TraceAttr.CopilotResumeOutcome, 'stream_not_found')
214+
rootSpan.setAttribute(TraceAttr.CopilotResumeOutcome, CopilotResumeOutcome.StreamNotFound)
194215
rootSpan.end()
195216
return NextResponse.json({ error: 'Stream not found' }, { status: 404 })
196217
}
@@ -217,7 +238,7 @@ async function handleResumeRequestBody({
217238
runStatus: run.status,
218239
})
219240
rootSpan.setAttributes({
220-
[TraceAttr.CopilotResumeOutcome]: 'batch_delivered',
241+
[TraceAttr.CopilotResumeOutcome]: CopilotResumeOutcome.BatchDelivered,
221242
[TraceAttr.CopilotResumeEventCount]: batchEvents.length,
222243
[TraceAttr.CopilotResumePreviewSessionCount]: previewSessions.length,
223244
})
@@ -411,10 +432,10 @@ async function handleResumeRequestBody({
411432
closeController()
412433
rootSpan.setAttributes({
413434
[TraceAttr.CopilotResumeOutcome]: sawTerminalEvent
414-
? 'terminal_delivered'
435+
? CopilotResumeOutcome.TerminalDelivered
415436
: controllerClosed
416-
? 'client_disconnected'
417-
: 'ended_without_terminal',
437+
? CopilotResumeOutcome.ClientDisconnected
438+
: CopilotResumeOutcome.EndedWithoutTerminal,
418439
[TraceAttr.CopilotResumeEventCount]: totalEventsFlushed,
419440
[TraceAttr.CopilotResumePollIterations]: pollIterations,
420441
[TraceAttr.CopilotResumeDurationMs]: Date.now() - startTime,

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
createUnauthorizedResponse,
2626
} from '@/lib/copilot/request/http'
2727
import { withIncomingGoSpan } from '@/lib/copilot/request/otel'
28+
import { CopilotConfirmOutcome } from '@/lib/copilot/generated/trace-attribute-values-v1'
2829

2930
const logger = createLogger('CopilotConfirmAPI')
3031

@@ -140,7 +141,7 @@ export async function POST(req: NextRequest) {
140141
await authenticateCopilotRequestSessionOnly()
141142

142143
if (!isAuthenticated || !authenticatedUserId) {
143-
span.setAttribute(TraceAttr.CopilotConfirmOutcome, 'unauthorized')
144+
span.setAttribute(TraceAttr.CopilotConfirmOutcome, CopilotConfirmOutcome.Unauthorized)
144145
return createUnauthorizedResponse()
145146
}
146147

@@ -161,7 +162,7 @@ export async function POST(req: NextRequest) {
161162
})
162163

163164
if (!existing) {
164-
span.setAttribute(TraceAttr.CopilotConfirmOutcome, 'tool_call_not_found')
165+
span.setAttribute(TraceAttr.CopilotConfirmOutcome, CopilotConfirmOutcome.ToolCallNotFound)
165166
return createNotFoundResponse('Tool call not found')
166167
}
167168
if (existing.toolName) span.setAttribute(TraceAttr.ToolName, existing.toolName)
@@ -175,11 +176,11 @@ export async function POST(req: NextRequest) {
175176
return null
176177
})
177178
if (!run) {
178-
span.setAttribute(TraceAttr.CopilotConfirmOutcome, 'run_not_found')
179+
span.setAttribute(TraceAttr.CopilotConfirmOutcome, CopilotConfirmOutcome.RunNotFound)
179180
return createNotFoundResponse('Tool call run not found')
180181
}
181182
if (run.userId !== authenticatedUserId) {
182-
span.setAttribute(TraceAttr.CopilotConfirmOutcome, 'forbidden')
183+
span.setAttribute(TraceAttr.CopilotConfirmOutcome, CopilotConfirmOutcome.Forbidden)
183184
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
184185
}
185186

@@ -193,13 +194,13 @@ export async function POST(req: NextRequest) {
193194
internalStatus: status,
194195
message,
195196
})
196-
span.setAttribute(TraceAttr.CopilotConfirmOutcome, 'update_failed')
197+
span.setAttribute(TraceAttr.CopilotConfirmOutcome, CopilotConfirmOutcome.UpdateFailed)
197198
return createBadRequestResponse(
198199
'Failed to update tool call status or tool call not found'
199200
)
200201
}
201202

202-
span.setAttribute(TraceAttr.CopilotConfirmOutcome, 'delivered')
203+
span.setAttribute(TraceAttr.CopilotConfirmOutcome, CopilotConfirmOutcome.Delivered)
203204
return NextResponse.json({
204205
success: true,
205206
message: message || `Tool call ${toolCallId} has been ${status.toLowerCase()}`,
@@ -214,7 +215,7 @@ export async function POST(req: NextRequest) {
214215
duration,
215216
errors: error.errors,
216217
})
217-
span.setAttribute(TraceAttr.CopilotConfirmOutcome, 'validation_error')
218+
span.setAttribute(TraceAttr.CopilotConfirmOutcome, CopilotConfirmOutcome.ValidationError)
218219
return createBadRequestResponse(
219220
`Invalid request data: ${error.errors.map((e) => e.message).join(', ')}`
220221
)
@@ -226,7 +227,7 @@ export async function POST(req: NextRequest) {
226227
stack: error instanceof Error ? error.stack : undefined,
227228
})
228229

229-
span.setAttribute(TraceAttr.CopilotConfirmOutcome, 'internal_error')
230+
span.setAttribute(TraceAttr.CopilotConfirmOutcome, CopilotConfirmOutcome.InternalError)
230231
return createInternalServerErrorResponse(
231232
error instanceof Error ? error.message : 'Internal server error'
232233
)

0 commit comments

Comments
 (0)