|
1 | | -import { trace } from '@opentelemetry/api' |
| 1 | +import { SpanKind, trace } from '@opentelemetry/api' |
2 | 2 | import { db } from '@sim/db' |
3 | 3 | import { |
4 | 4 | type CopilotAsyncToolStatus, |
@@ -34,6 +34,10 @@ import { |
34 | 34 | } from '@/lib/mothership/async-runs/execution-lease' |
35 | 35 | import { TraceAttr } from '@/lib/mothership/generated/trace-attributes-v1' |
36 | 36 | import { TraceSpan } from '@/lib/mothership/generated/trace-spans-v1' |
| 37 | +import { |
| 38 | + traceMothershipQuery, |
| 39 | + traceMothershipTransaction, |
| 40 | +} from '@/lib/mothership/observability/database' |
37 | 41 | import { markSpanForError } from '@/lib/mothership/request/otel' |
38 | 42 | import { chatSandboxSessionKey } from '@/lib/mothership/tools/sandbox-session-key' |
39 | 43 | import { |
@@ -71,22 +75,28 @@ async function withDbSpan<T>( |
71 | 75 | attrs: Record<string, string | number | boolean | undefined>, |
72 | 76 | fn: () => Promise<T> |
73 | 77 | ): Promise<T> { |
74 | | - const span = getAsyncRunsTracer().startSpan(name, { |
75 | | - attributes: { |
76 | | - [TraceAttr.DbSystem]: 'postgresql', |
77 | | - [TraceAttr.DbOperation]: op, |
78 | | - [TraceAttr.DbSqlTable]: table, |
79 | | - ...filterUndefined(attrs), |
| 78 | + return getAsyncRunsTracer().startActiveSpan( |
| 79 | + name, |
| 80 | + { |
| 81 | + kind: SpanKind.CLIENT, |
| 82 | + attributes: { |
| 83 | + [TraceAttr.DbSystem]: 'postgresql', |
| 84 | + [TraceAttr.DbOperation]: op, |
| 85 | + [TraceAttr.DbSqlTable]: table, |
| 86 | + ...filterUndefined(attrs), |
| 87 | + }, |
80 | 88 | }, |
81 | | - }) |
82 | | - try { |
83 | | - return await fn() |
84 | | - } catch (error) { |
85 | | - markSpanForError(span, error) |
86 | | - throw error |
87 | | - } finally { |
88 | | - span.end() |
89 | | - } |
| 89 | + async (span) => { |
| 90 | + try { |
| 91 | + return await fn() |
| 92 | + } catch (error) { |
| 93 | + markSpanForError(span, error) |
| 94 | + throw error |
| 95 | + } finally { |
| 96 | + span.end() |
| 97 | + } |
| 98 | + } |
| 99 | + ) |
90 | 100 | } |
91 | 101 |
|
92 | 102 | export interface CreateRunSegmentInput { |
@@ -366,20 +376,22 @@ export async function upsertAsyncToolCall(input: { |
366 | 376 | const now = new Date() |
367 | 377 | const args = sanitizeValueForJsonb(input.args ?? {}) |
368 | 378 | const sealedContext = sanitizeValueForJsonb(input.sealedContext) |
369 | | - const [row] = await db |
370 | | - .insert(copilotAsyncToolCalls) |
371 | | - .values({ |
372 | | - runId: effectiveRunId, |
373 | | - checkpointId: input.checkpointId ?? null, |
374 | | - toolCallId: input.toolCallId, |
375 | | - toolName: input.toolName, |
376 | | - args, |
377 | | - status: incomingStatus, |
378 | | - ...(sealedContext !== undefined ? { result: sealedContext } : {}), |
379 | | - updatedAt: now, |
380 | | - }) |
381 | | - .onConflictDoNothing() |
382 | | - .returning() |
| 379 | + const [row] = await traceMothershipQuery('INSERT', 'copilot_async_tool_calls', () => |
| 380 | + db |
| 381 | + .insert(copilotAsyncToolCalls) |
| 382 | + .values({ |
| 383 | + runId: effectiveRunId, |
| 384 | + checkpointId: input.checkpointId ?? null, |
| 385 | + toolCallId: input.toolCallId, |
| 386 | + toolName: input.toolName, |
| 387 | + args, |
| 388 | + status: incomingStatus, |
| 389 | + ...(sealedContext !== undefined ? { result: sealedContext } : {}), |
| 390 | + updatedAt: now, |
| 391 | + }) |
| 392 | + .onConflictDoNothing() |
| 393 | + .returning() |
| 394 | + ) |
383 | 395 |
|
384 | 396 | return row ?? getAsyncToolCall(input.toolCallId) |
385 | 397 | } |
@@ -491,54 +503,60 @@ export async function claimSimToolExecution( |
491 | 503 | [TraceAttr.RunId]: input.runId, |
492 | 504 | }, |
493 | 505 | () => |
494 | | - db.transaction(async (tx) => { |
495 | | - const [run] = await tx |
496 | | - .select({ |
497 | | - toolExecutionVersion: copilotRuns.toolExecutionVersion, |
498 | | - toolAdmissionClosedAt: copilotRuns.toolAdmissionClosedAt, |
499 | | - status: copilotRuns.status, |
500 | | - }) |
501 | | - .from(copilotRuns) |
502 | | - .where(and(eq(copilotRuns.id, input.runId), eq(copilotRuns.userId, input.userId))) |
503 | | - .for('update') |
| 506 | + traceMothershipTransaction<SimToolExecutionClaim>('claim_tool', async (tx) => { |
| 507 | + const [run] = await traceMothershipQuery('SELECT FOR UPDATE', 'copilot_runs', () => |
| 508 | + tx |
| 509 | + .select({ |
| 510 | + toolExecutionVersion: copilotRuns.toolExecutionVersion, |
| 511 | + toolAdmissionClosedAt: copilotRuns.toolAdmissionClosedAt, |
| 512 | + status: copilotRuns.status, |
| 513 | + }) |
| 514 | + .from(copilotRuns) |
| 515 | + .where(and(eq(copilotRuns.id, input.runId), eq(copilotRuns.userId, input.userId))) |
| 516 | + .for('update') |
| 517 | + ) |
504 | 518 | if (!run || run.toolExecutionVersion !== SIM_TOOL_EXECUTION_VERSION) |
505 | 519 | throw new Error('Tool execution ownership is unavailable for this run') |
506 | 520 | if (run.toolAdmissionClosedAt || TERMINAL_RUN_STATUSES.includes(run.status)) |
507 | 521 | return { outcome: 'closed' } |
508 | 522 | const startedAt = new Date() |
509 | | - const [claimed] = await tx |
510 | | - .update(copilotAsyncToolCalls) |
511 | | - .set({ |
512 | | - status: ASYNC_TOOL_STATUS.running, |
513 | | - claimedBy: 'sim-stream', |
514 | | - claimedAt: startedAt, |
515 | | - executionStartedAt: startedAt, |
516 | | - executionOwnerToken: input.ownerToken, |
517 | | - executionLeaseExpiresAt: sql`clock_timestamp() + ${SIM_TOOL_EXECUTION_LEASE_SECONDS} * interval '1 second'`, |
518 | | - updatedAt: startedAt, |
519 | | - }) |
520 | | - .where( |
521 | | - and( |
522 | | - eq(copilotAsyncToolCalls.toolCallId, input.toolCallId), |
523 | | - eq(copilotAsyncToolCalls.runId, input.runId), |
524 | | - isNull(copilotAsyncToolCalls.executionStartedAt), |
525 | | - inArray(copilotAsyncToolCalls.status, [ |
526 | | - ASYNC_TOOL_STATUS.pending, |
527 | | - ASYNC_TOOL_STATUS.running, |
528 | | - ]) |
| 523 | + const [claimed] = await traceMothershipQuery('UPDATE', 'copilot_async_tool_calls', () => |
| 524 | + tx |
| 525 | + .update(copilotAsyncToolCalls) |
| 526 | + .set({ |
| 527 | + status: ASYNC_TOOL_STATUS.running, |
| 528 | + claimedBy: 'sim-stream', |
| 529 | + claimedAt: startedAt, |
| 530 | + executionStartedAt: startedAt, |
| 531 | + executionOwnerToken: input.ownerToken, |
| 532 | + executionLeaseExpiresAt: sql`clock_timestamp() + ${SIM_TOOL_EXECUTION_LEASE_SECONDS} * interval '1 second'`, |
| 533 | + updatedAt: startedAt, |
| 534 | + }) |
| 535 | + .where( |
| 536 | + and( |
| 537 | + eq(copilotAsyncToolCalls.toolCallId, input.toolCallId), |
| 538 | + eq(copilotAsyncToolCalls.runId, input.runId), |
| 539 | + isNull(copilotAsyncToolCalls.executionStartedAt), |
| 540 | + inArray(copilotAsyncToolCalls.status, [ |
| 541 | + ASYNC_TOOL_STATUS.pending, |
| 542 | + ASYNC_TOOL_STATUS.running, |
| 543 | + ]) |
| 544 | + ) |
529 | 545 | ) |
530 | | - ) |
531 | | - .returning({ id: copilotAsyncToolCalls.id }) |
| 546 | + .returning({ id: copilotAsyncToolCalls.id }) |
| 547 | + ) |
532 | 548 | if (claimed) return { outcome: 'claimed' } |
533 | | - const [record] = await tx |
534 | | - .select({ id: copilotAsyncToolCalls.id }) |
535 | | - .from(copilotAsyncToolCalls) |
536 | | - .where( |
537 | | - and( |
538 | | - eq(copilotAsyncToolCalls.toolCallId, input.toolCallId), |
539 | | - eq(copilotAsyncToolCalls.runId, input.runId) |
| 549 | + const [record] = await traceMothershipQuery('SELECT', 'copilot_async_tool_calls', () => |
| 550 | + tx |
| 551 | + .select({ id: copilotAsyncToolCalls.id }) |
| 552 | + .from(copilotAsyncToolCalls) |
| 553 | + .where( |
| 554 | + and( |
| 555 | + eq(copilotAsyncToolCalls.toolCallId, input.toolCallId), |
| 556 | + eq(copilotAsyncToolCalls.runId, input.runId) |
| 557 | + ) |
540 | 558 | ) |
541 | | - ) |
| 559 | + ) |
542 | 560 | if (!record) throw new Error('Tool execution record is unavailable') |
543 | 561 | return { outcome: 'existing' } |
544 | 562 | }) |
|
0 commit comments