Skip to content

Commit de6ec5f

Browse files
authored
feat: Bump support for temporal (#2748)
1 parent 43d1245 commit de6ec5f

5 files changed

Lines changed: 418 additions & 163 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@braintrust/temporal": minor
3+
---
4+
5+
feat: Bump support for temporal

‎integrations/temporal-js/package.json‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@
3131
"author": "Braintrust Data Inc.",
3232
"license": "MIT",
3333
"devDependencies": {
34-
"@temporalio/activity": "^1.14.1",
35-
"@temporalio/client": "^1.14.1",
36-
"@temporalio/common": "^1.14.1",
37-
"@temporalio/worker": "^1.14.1",
38-
"@temporalio/workflow": "^1.14.1",
34+
"@temporalio/activity": "^1.25.0",
35+
"@temporalio/client": "^1.25.0",
36+
"@temporalio/common": "^1.25.0",
37+
"@temporalio/worker": "^1.25.0",
38+
"@temporalio/workflow": "^1.25.0",
3939
"@types/node": "^22.15.21",
4040
"braintrust": "workspace:*",
4141
"tsup": "^8.5.0",
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type { Context } from "@temporalio/activity";
2+
import { beforeEach, describe, expect, test, vi } from "vitest";
3+
import { createBraintrustActivityInterceptor } from "./interceptors";
4+
import { BRAINTRUST_SPAN_HEADER, serializeHeaderValue } from "./utils";
5+
6+
const mock = vi.hoisted(() => {
7+
const span = { log: vi.fn(), end: vi.fn() };
8+
return { span, startSpan: vi.fn(() => span) };
9+
});
10+
11+
vi.mock("braintrust", () => ({
12+
startSpan: mock.startSpan,
13+
withCurrent: <R>(_span: unknown, callback: () => R): R => callback(),
14+
}));
15+
16+
beforeEach(() => vi.clearAllMocks());
17+
18+
describe.each([
19+
{
20+
name: "workflow",
21+
workflowExecution: { workflowId: "workflow", runId: "run" },
22+
},
23+
{ name: "standalone", workflowExecution: undefined },
24+
])("$name activity", ({ workflowExecution }) => {
25+
const context = {
26+
info: {
27+
activityId: "activity",
28+
activityType: "generate",
29+
workflowExecution,
30+
},
31+
} as Context;
32+
33+
test("runs the activity and preserves parent context and output", async () => {
34+
const interceptor = createBraintrustActivityInterceptor(context).inbound!;
35+
const input = {
36+
args: ["prompt"],
37+
headers: { [BRAINTRUST_SPAN_HEADER]: serializeHeaderValue("parent") },
38+
};
39+
const output = { text: "result" };
40+
const next = vi.fn().mockResolvedValue(output);
41+
42+
await expect(interceptor.execute!(input, next)).resolves.toBe(output);
43+
expect(next).toHaveBeenCalledExactlyOnceWith(input);
44+
expect(mock.startSpan).toHaveBeenCalledWith(
45+
expect.objectContaining({
46+
parent: "parent",
47+
event: {
48+
metadata: {
49+
"temporal.activity_type": "generate",
50+
"temporal.activity_id": "activity",
51+
"temporal.workflow_id": workflowExecution?.workflowId,
52+
"temporal.workflow_run_id": workflowExecution?.runId,
53+
},
54+
},
55+
}),
56+
);
57+
expect(mock.span.log).toHaveBeenCalledWith({ output });
58+
expect(mock.span.end).toHaveBeenCalledOnce();
59+
});
60+
61+
test("preserves activity errors and ends the span", async () => {
62+
const interceptor = createBraintrustActivityInterceptor(context).inbound!;
63+
const error = new Error("activity failed");
64+
const next = vi.fn().mockRejectedValue(error);
65+
66+
await expect(
67+
interceptor.execute!({ args: [], headers: {} }, next),
68+
).rejects.toBe(error);
69+
expect(next).toHaveBeenCalledOnce();
70+
expect(mock.span.log).toHaveBeenCalledWith({ error: String(error) });
71+
expect(mock.span.end).toHaveBeenCalledOnce();
72+
});
73+
});

‎integrations/temporal-js/src/interceptors.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,13 @@ class BraintrustActivityInterceptor implements ActivityInboundCallsInterceptor {
7777
next: Next<ActivityInboundCallsInterceptor, "execute">,
7878
): Promise<unknown> {
7979
const info = this.ctx.info;
80-
const runId = info.workflowExecution.runId;
80+
const runId = info.workflowExecution?.runId;
8181

8282
// Try to get workflow span export - first check local Map, then headers
8383
let parent: string | undefined;
8484

8585
// Check if we have the workflow span export locally (same worker as workflow)
86-
const spanExportPromise = getWorkflowSpanExport(runId);
86+
const spanExportPromise = runId ? getWorkflowSpanExport(runId) : undefined;
8787
if (spanExportPromise) {
8888
try {
8989
parent = await spanExportPromise;
@@ -159,7 +159,7 @@ class BraintrustActivityInterceptor implements ActivityInboundCallsInterceptor {
159159
metadata: {
160160
"temporal.activity_type": info.activityType,
161161
"temporal.activity_id": info.activityId,
162-
"temporal.workflow_id": info.workflowExecution.workflowId,
162+
"temporal.workflow_id": info.workflowExecution?.workflowId,
163163
"temporal.workflow_run_id": runId,
164164
},
165165
},

0 commit comments

Comments
 (0)