Skip to content

Commit 5789184

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat(webapp)-ai-screen-ui-improvements
2 parents 7a58e22 + c00dae0 commit 5789184

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+821
-57
lines changed

.changeset/mcp-get-span-details.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@trigger.dev/core": patch
3+
"trigger.dev": patch
4+
---
5+
6+
Add `get_span_details` MCP tool for inspecting individual spans within a run trace.
7+
8+
- New `get_span_details` tool returns full span attributes, timing, events, and AI enrichment (model, tokens, cost, speed)
9+
- Span IDs now shown in `get_run_details` trace output for easy discovery
10+
- New API endpoint `GET /api/v1/runs/:runId/spans/:spanId`
11+
- New `retrieveSpan()` method on the API client

.changeset/tame-oranges-change.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@trigger.dev/redis-worker": patch
3+
"@trigger.dev/sdk": patch
4+
"trigger.dev": patch
5+
"@trigger.dev/core": patch
6+
---
7+
8+
Adapted the CLI API client to propagate the trigger source via http headers.

.github/VOUCHED.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ gautamsi
1616
capaj
1717
chengzp
1818
bharathkumar39293
19-
bhekanik
19+
bhekanik
20+
jrossi
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: feature
4+
---
5+
6+
Add API endpoint `GET /api/v1/runs/:runId/spans/:spanId` that returns detailed span information including properties, events, AI enrichment (model, tokens, cost), and triggered child runs.

apps/webapp/app/components/integrations/VercelOnboardingModal.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,9 @@ export function VercelOnboardingModal({
541541
formData.append("atomicBuilds", JSON.stringify(atomicBuilds));
542542
formData.append("discoverEnvVars", JSON.stringify(discoverEnvVars));
543543
formData.append("syncEnvVarsMapping", JSON.stringify(syncEnvVarsMapping));
544+
if (fromMarketplaceContext) {
545+
formData.append("origin", "marketplace");
546+
}
544547
if (nextUrl && fromMarketplaceContext && isGitHubConnectedForOnboarding) {
545548
formData.append("next", nextUrl);
546549
}

apps/webapp/app/models/vercelIntegration.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1294,7 +1294,7 @@ export class VercelIntegrationRepository {
12941294
);
12951295

12961296
if (envVarsResult.isErr()) {
1297-
logger.error("pullEnvVarsFromVercel: Failed to get env vars", {
1297+
logger.warn("pullEnvVarsFromVercel: Failed to get env vars", {
12981298
triggerEnvType: mapping.triggerEnvType,
12991299
vercelTarget: mapping.vercelTarget,
13001300
error: envVarsResult.error.message,
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { json } from "@remix-run/server-runtime";
2+
import { BatchId } from "@trigger.dev/core/v3/isomorphic";
3+
import { z } from "zod";
4+
import { $replica } from "~/db.server";
5+
import { extractAISpanData } from "~/components/runs/v3/ai";
6+
import { createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server";
7+
import { resolveEventRepositoryForStore } from "~/v3/eventRepository/index.server";
8+
import { getTaskEventStoreTableForRun } from "~/v3/taskEventStore.server";
9+
10+
const ParamsSchema = z.object({
11+
runId: z.string(),
12+
spanId: z.string(),
13+
});
14+
15+
export const loader = createLoaderApiRoute(
16+
{
17+
params: ParamsSchema,
18+
allowJWT: true,
19+
corsStrategy: "all",
20+
findResource: (params, auth) => {
21+
return $replica.taskRun.findFirst({
22+
where: {
23+
friendlyId: params.runId,
24+
runtimeEnvironmentId: auth.environment.id,
25+
},
26+
});
27+
},
28+
shouldRetryNotFound: true,
29+
authorization: {
30+
action: "read",
31+
resource: (run) => ({
32+
runs: run.friendlyId,
33+
tags: run.runTags,
34+
batch: run.batchId ? BatchId.toFriendlyId(run.batchId) : undefined,
35+
tasks: run.taskIdentifier,
36+
}),
37+
superScopes: ["read:runs", "read:all", "admin"],
38+
},
39+
},
40+
async ({ params, resource: run, authentication }) => {
41+
const eventRepository = resolveEventRepositoryForStore(run.taskEventStore);
42+
const eventStore = getTaskEventStoreTableForRun(run);
43+
44+
const span = await eventRepository.getSpan(
45+
eventStore,
46+
authentication.environment.id,
47+
params.spanId,
48+
run.traceId,
49+
run.createdAt,
50+
run.completedAt ?? undefined
51+
);
52+
53+
if (!span) {
54+
return json({ error: "Span not found" }, { status: 404 });
55+
}
56+
57+
// Duration is nanoseconds from ClickHouse (Postgres store is deprecated)
58+
const durationMs = span.duration / 1_000_000;
59+
60+
const aiData =
61+
span.properties && typeof span.properties === "object"
62+
? extractAISpanData(span.properties as Record<string, unknown>, durationMs)
63+
: undefined;
64+
65+
const triggeredRuns = await $replica.taskRun.findMany({
66+
take: 50,
67+
select: {
68+
friendlyId: true,
69+
taskIdentifier: true,
70+
status: true,
71+
createdAt: true,
72+
},
73+
where: {
74+
runtimeEnvironmentId: authentication.environment.id,
75+
parentSpanId: params.spanId,
76+
},
77+
});
78+
79+
const properties =
80+
span.properties &&
81+
typeof span.properties === "object" &&
82+
Object.keys(span.properties as Record<string, unknown>).length > 0
83+
? (span.properties as Record<string, unknown>)
84+
: undefined;
85+
86+
return json(
87+
{
88+
spanId: span.spanId,
89+
parentId: span.parentId,
90+
runId: run.friendlyId,
91+
message: span.message,
92+
isError: span.isError,
93+
isPartial: span.isPartial,
94+
isCancelled: span.isCancelled,
95+
level: span.level,
96+
startTime: span.startTime,
97+
durationMs,
98+
properties,
99+
events: span.events?.length ? span.events : undefined,
100+
entityType: span.entity.type ?? undefined,
101+
ai: aiData
102+
? {
103+
model: aiData.model,
104+
provider: aiData.provider,
105+
operationName: aiData.operationName,
106+
inputTokens: aiData.inputTokens,
107+
outputTokens: aiData.outputTokens,
108+
totalTokens: aiData.totalTokens,
109+
cachedTokens: aiData.cachedTokens,
110+
reasoningTokens: aiData.reasoningTokens,
111+
inputCost: aiData.inputCost,
112+
outputCost: aiData.outputCost,
113+
totalCost: aiData.totalCost,
114+
tokensPerSecond: aiData.tokensPerSecond,
115+
msToFirstChunk: aiData.msToFirstChunk,
116+
durationMs: aiData.durationMs,
117+
finishReason: aiData.finishReason,
118+
responseText: aiData.responseText,
119+
}
120+
: undefined,
121+
triggeredRuns:
122+
triggeredRuns.length > 0
123+
? triggeredRuns.map((r) => ({
124+
runId: r.friendlyId,
125+
taskIdentifier: r.taskIdentifier,
126+
status: r.status,
127+
createdAt: r.createdAt,
128+
}))
129+
: undefined,
130+
},
131+
{ status: 200 }
132+
);
133+
}
134+
);

apps/webapp/app/routes/api.v1.runs.$runParam.replay.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { prisma } from "~/db.server";
55
import { authenticateApiRequest } from "~/services/apiAuth.server";
66
import { logger } from "~/services/logger.server";
77
import { ReplayTaskRunService } from "~/v3/services/replayTaskRun.server";
8+
import { sanitizeTriggerSource } from "~/utils/triggerSource";
89

910
const ParamsSchema = z.object({
1011
/* This is the run friendly ID */
@@ -41,8 +42,11 @@ export async function action({ request, params }: ActionFunctionArgs) {
4142
return json({ error: "Run not found" }, { status: 404 });
4243
}
4344

45+
const triggerSource =
46+
sanitizeTriggerSource(request.headers.get("x-trigger-source")) ?? "api";
47+
4448
const service = new ReplayTaskRunService();
45-
const newRun = await service.call(taskRun);
49+
const newRun = await service.call(taskRun, { triggerSource });
4650

4751
if (!newRun) {
4852
return json({ error: "Failed to create new run" }, { status: 400 });

apps/webapp/app/routes/api.v1.tasks.$taskId.trigger.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
handleRequestIdempotency,
2020
saveRequestIdempotency,
2121
} from "~/utils/requestIdempotency.server";
22+
import { sanitizeTriggerSource } from "~/utils/triggerSource";
2223
import { ServiceValidationError } from "~/v3/services/baseService.server";
2324
import { OutOfEntitlementError, TriggerTaskService } from "~/v3/services/triggerTask.server";
2425

@@ -36,6 +37,7 @@ export const HeadersSchema = z.object({
3637
"x-trigger-engine-version": RunEngineVersionSchema.nullish(),
3738
"x-trigger-request-idempotency-key": z.string().nullish(),
3839
"x-trigger-realtime-streams-version": z.string().nullish(),
40+
"x-trigger-source": z.string().nullish(),
3941
traceparent: z.string().optional(),
4042
tracestate: z.string().optional(),
4143
});
@@ -67,6 +69,7 @@ const { action, loader } = createActionApiRoute(
6769
"x-trigger-engine-version": engineVersion,
6870
"x-trigger-request-idempotency-key": requestIdempotencyKey,
6971
"x-trigger-realtime-streams-version": realtimeStreamsVersion,
72+
"x-trigger-source": triggerSourceHeader,
7073
} = headers;
7174

7275
const cachedResponse = await handleRequestIdempotency(requestIdempotencyKey, {
@@ -119,6 +122,8 @@ const { action, loader } = createActionApiRoute(
119122
realtimeStreamsVersion: determineRealtimeStreamsVersion(
120123
realtimeStreamsVersion ?? undefined
121124
),
125+
triggerSource: isFromWorker ? "sdk" : sanitizeTriggerSource(triggerSourceHeader) ?? "api",
126+
triggerAction: "trigger",
122127
},
123128
engineVersion ?? undefined
124129
);

apps/webapp/app/routes/api.v1.tasks.batch.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
BatchTriggerV3Service,
1616
} from "~/v3/services/batchTriggerV3.server";
1717
import { OutOfEntitlementError } from "~/v3/services/triggerTask.server";
18+
import { sanitizeTriggerSource } from "~/utils/triggerSource";
1819
import { HeadersSchema } from "./api.v1.tasks.$taskId.trigger";
1920
import { determineRealtimeStreamsVersion } from "~/services/realtime/v1StreamsGlobal.server";
2021
import { extractJwtSigningSecretKey } from "~/services/realtime/jwtAuth.server";
@@ -72,6 +73,7 @@ const { action, loader } = createActionApiRoute(
7273
"x-trigger-engine-version": engineVersion,
7374
"batch-processing-strategy": batchProcessingStrategy,
7475
"x-trigger-realtime-streams-version": realtimeStreamsVersion,
76+
"x-trigger-source": triggerSourceHeader,
7577
traceparent,
7678
tracestate,
7779
} = headers;
@@ -113,6 +115,8 @@ const { action, loader } = createActionApiRoute(
113115
realtimeStreamsVersion: determineRealtimeStreamsVersion(
114116
realtimeStreamsVersion ?? undefined
115117
),
118+
triggerSource: isFromWorker ? "sdk" : sanitizeTriggerSource(triggerSourceHeader) ?? "api",
119+
triggerAction: "trigger",
116120
});
117121

118122
const $responseHeaders = await responseHeaders(

0 commit comments

Comments
 (0)