@temporalio/langsmith 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2021-2025 Temporal Technologies Inc. All rights reserved.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # @temporalio/langsmith — LangSmith tracing for Temporal
2
+
3
+ This plugin makes [LangSmith](https://docs.smith.langchain.com/) observability
4
+ work inside Temporal Workflows and Activities **without changing your existing
5
+ instrumentation**. Code you already trace with LangSmith's native `traceable`
6
+ keeps working when you move it into a Workflow or Activity body — you only add
7
+ the plugin to your `Client` and `Worker`.
8
+
9
+ This plugin is built on Temporal's Plugin API, which is experimental; its APIs
10
+ may change in a future release.
11
+
12
+ It handles the parts that are otherwise hard:
13
+
14
+ - **Replay safety.** Workflows replay history; a naive tracer re-emits every run
15
+ on every replay and floods your project with duplicates. This plugin emits
16
+ runs with deterministic IDs out-of-isolate via a Temporal Sink that does not
17
+ fire during replay.
18
+ - **Run parenting across boundaries.** A trace started on the client threads
19
+ through `workflow → activity → child-workflow → Nexus` so the runs nest the
20
+ way you expect, instead of fragmenting into disconnected roots.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install @temporalio/langsmith langsmith
26
+ ```
27
+
28
+ `langsmith` is a peer dependency — install the version your project already uses.
29
+
30
+ ## Enable tracing
31
+
32
+ Tracing is **off by default**, matching the `langsmith` library: the plugin
33
+ emits nothing unless LangSmith tracing is enabled in the worker/client process
34
+ environment. Enable it with LangSmith's standard environment variables:
35
+
36
+ ```bash
37
+ export LANGSMITH_TRACING=true
38
+ export LANGSMITH_API_KEY="<your key>"
39
+ ```
40
+
41
+ The plugin reads the same flags LangSmith itself uses (`LANGSMITH_TRACING` /
42
+ `LANGSMITH_TRACING_V2` and their `LANGCHAIN_` aliases); with none set to `true`,
43
+ tracing stays off and the plugin is a no-op.
44
+
45
+ ## Hello world
46
+
47
+ Your existing LangSmith instrumentation does not change. Here a `traceable`
48
+ runs inside an Activity:
49
+
50
+ ```typescript
51
+ // activities.ts
52
+ import { traceable } from 'langsmith/traceable';
53
+
54
+ const callModel = traceable(
55
+ async (prompt: string) => {
56
+ // ... your real model call ...
57
+ return `answer to: ${prompt}`;
58
+ },
59
+ { name: 'inner_llm_call' }
60
+ );
61
+
62
+ export async function answer(prompt: string): Promise<string> {
63
+ return callModel(prompt);
64
+ }
65
+ ```
66
+
67
+ ```typescript
68
+ // workflows.ts
69
+ import { proxyActivities } from '@temporalio/workflow';
70
+ import type * as activities from './activities';
71
+
72
+ const { answer } = proxyActivities<typeof activities>({
73
+ startToCloseTimeout: '1 minute',
74
+ });
75
+
76
+ export async function GreetingWorkflow(prompt: string): Promise<string> {
77
+ return answer(prompt);
78
+ }
79
+ ```
80
+
81
+ The only new code is the plugin registration on the `Client` and the `Worker`:
82
+
83
+ ```typescript
84
+ // worker.ts
85
+ import { Worker } from '@temporalio/worker';
86
+ import { Client } from '@temporalio/client';
87
+ import { Client as LangSmithClient } from 'langsmith';
88
+ import { LangSmithPlugin } from '@temporalio/langsmith';
89
+ import * as activities from './activities';
90
+
91
+ const langsmith = new LangSmithClient(); // reads LANGSMITH_API_KEY from the env
92
+
93
+ // In TypeScript the Client and Worker are configured independently, so add the
94
+ // plugin to each. (Construct one plugin instance and share it.)
95
+ const plugin = new LangSmithPlugin({ client: langsmith, addTemporalRuns: true });
96
+
97
+ const client = new Client({ plugins: [plugin] });
98
+
99
+ const worker = await Worker.create({
100
+ taskQueue: 'greeting',
101
+ workflowsPath: require.resolve('./workflows'),
102
+ activities,
103
+ plugins: [plugin],
104
+ });
105
+
106
+ await worker.run();
107
+ ```
108
+
109
+ ```typescript
110
+ // starter.ts — start the workflow from inside your own trace
111
+ import { traceable } from 'langsmith/traceable';
112
+
113
+ const pipeline = traceable(
114
+ async () => {
115
+ return client.workflow.execute(GreetingWorkflow, {
116
+ taskQueue: 'greeting',
117
+ workflowId: 'greeting-1',
118
+ args: ['hello'],
119
+ });
120
+ },
121
+ { name: 'user_pipeline' }
122
+ );
123
+
124
+ await pipeline();
125
+ ```
126
+
127
+ With `addTemporalRuns: true`, the resulting trace nests like this:
128
+
129
+ ```
130
+ user_pipeline
131
+ StartWorkflow:GreetingWorkflow
132
+ RunWorkflow:GreetingWorkflow
133
+ StartActivity:answer
134
+ RunActivity:answer
135
+ inner_llm_call
136
+ ```
137
+
138
+ Set `addTemporalRuns: false` (the default) to emit only your own `traceable`
139
+ runs — the trace context still propagates across boundaries, so they nest
140
+ correctly, but no `StartWorkflow:` / `RunActivity:` scaffolding is added:
141
+
142
+ ```
143
+ user_pipeline
144
+ inner_llm_call
145
+ ```
146
+
147
+ ## Options
148
+
149
+ | Option | Default | Meaning |
150
+ | ----------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
151
+ | `client` | `new Client()` | The LangSmith client runs are emitted to. Lives only in the worker/client process — it is never serialized or sent across a Temporal boundary. |
152
+ | `addTemporalRuns` | `false` | Emit first-class runs for Temporal operations (`StartWorkflow:`, `RunActivity:`, `HandleSignal:`, …) in addition to your `traceable` runs. |
153
+ | `projectName` | LangSmith default | Target LangSmith project for emitted runs. |
154
+ | `tags` | — | Tags attached to every run the plugin emits. |
155
+ | `metadata` | — | Metadata merged into every run the plugin emits. Credential-looking keys are scrubbed before emission. |
156
+
157
+ The LangSmith API key is never accepted as a plugin option and never crosses a
158
+ Temporal boundary. Supply a pre-constructed `Client` (which reads
159
+ `LANGSMITH_API_KEY` from the process environment) or let the plugin build a
160
+ default client from the environment.
161
+
162
+ ## Where `traceable` works
163
+
164
+ `traceable` from `langsmith/traceable` works **unchanged** in every position
165
+ below. Each row is exercised by the plugin's test suite.
166
+
167
+ | Position | Works? | Notes |
168
+ | ------------------------------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
169
+ | Client side, before `client.workflow.start(...)` | ✅ | The trace propagates into the workflow; `RunWorkflow:` nests under your run. |
170
+ | Inside an **Activity** body | ✅ | Runs in the real worker process. Nests under `RunActivity:` (or under the propagated parent when `addTemporalRuns: false`). |
171
+ | Inside a **Workflow** body | ✅ | Replay-safe: deterministic IDs, emitted out-of-isolate, suppressed on replay. Works whether or not a parent trace is propagated in. |
172
+ | Inside **signal / query / update** handlers | ✅ | Handler-body `traceable` runs nest under the handler's run (workflow-body semantics). Temporal-internal queries (`__temporal*`, `__stack_trace`) are never traced. |
173
+
174
+ **Concurrency caveat (workflow body only).** Inside a Workflow, parent
175
+ resolution uses a synchronous context stack rather than `node:async_hooks`
176
+ (which is unavailable in the workflow isolate). Sequential `await inner(...)`
177
+ nesting is exact. Under `Promise.all(...)` fan-out, or for `traceable` calls
178
+ made _after_ an `await` in the same scope, parenting falls back to the workflow
179
+ run. This affects only the visual shape of the trace, never workflow history or
180
+ control flow. Activity-body and client-side `traceable` use LangSmith's real
181
+ async context and are unaffected.
182
+
183
+ ## Process-wide effects to be aware of
184
+
185
+ - **Workflow context provider.** Loading the plugin's workflow interceptor
186
+ module installs a global LangSmith async-context provider inside the workflow
187
+ isolate (via `AsyncLocalStorageProviderSingleton.initializeGlobalInstance`).
188
+ This is what lets unchanged `traceable` calls find their parent inside a
189
+ Workflow. It is scoped to the workflow bundle and does not affect your
190
+ client/worker process context.
191
+
192
+ ## Composing with other plugins
193
+
194
+ Register observability **first** (outermost) so it observes everything beneath
195
+ it, then governance, then agent-framework plugins:
196
+
197
+ ```typescript
198
+ const worker = await Worker.create({
199
+ taskQueue: 'tq',
200
+ workflowsPath: require.resolve('./workflows'),
201
+ activities,
202
+ plugins: [
203
+ new LangSmithPlugin({ client: langsmith }), // observability — first
204
+ // new GovernancePlugin(...),
205
+ // new AgentFrameworkPlugin(...),
206
+ ],
207
+ });
208
+ ```
209
+
210
+ The plugin is safe to register on both the `Client` and the `Worker`; it
211
+ de-duplicates its own interceptors and sinks, so a worker built from a
212
+ plugin-configured client will not double-instrument.
213
+
214
+ ## License
215
+
216
+ MIT
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Activity-side and Nexus-handler-side LangSmith interceptors. Both run in the
3
+ * real Node worker process and install the reconstructed run via `withRunTree`
4
+ * so a user's unchanged body `traceable` calls nest under it.
5
+ *
6
+ * @module
7
+ */
8
+ import type { ActivityInboundCallsInterceptorFactory, NexusInboundCallsInterceptor } from '@temporalio/worker';
9
+ import type { EmitterConfig } from './sinks';
10
+ /**
11
+ * Build the activity-inbound interceptor factory. The worker calls the factory
12
+ * once per activity with that activity's {@link ActivityContext}, from which we
13
+ * read the activity type for the run name.
14
+ */
15
+ export declare function createActivityInboundInterceptor(config: EmitterConfig): ActivityInboundCallsInterceptorFactory;
16
+ /**
17
+ * Build the Nexus-handler interceptor. `startOperation` opens a
18
+ * `RunStartNexusOperationHandler:` run (so workflows the handler starts nest
19
+ * under it); `cancelOperation` opens `RunCancelNexusOperationHandler:`.
20
+ */
21
+ export declare function createNexusInboundInterceptor(config: EmitterConfig): NexusInboundCallsInterceptor;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ /**
3
+ * Activity-side and Nexus-handler-side LangSmith interceptors. Both run in the
4
+ * real Node worker process and install the reconstructed run via `withRunTree`
5
+ * so a user's unchanged body `traceable` calls nest under it.
6
+ *
7
+ * @module
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.createActivityInboundInterceptor = createActivityInboundInterceptor;
11
+ exports.createNexusInboundInterceptor = createNexusInboundInterceptor;
12
+ const langsmith_1 = require("langsmith");
13
+ const run_trees_1 = require("langsmith/run_trees");
14
+ const traceable_1 = require("langsmith/traceable");
15
+ const propagation_1 = require("./propagation");
16
+ const run_tree_1 = require("./run-tree");
17
+ /**
18
+ * Reconstruct the propagated parent run from a LangSmith trace context, wired to
19
+ * the plugin-configured client so descendant runs emit to the right place. The
20
+ * returned run is the *parent* — never posted itself — used only to parent
21
+ * the operation run (or directly as ambient when `addTemporalRuns` is off).
22
+ */
23
+ function reconstructParentRun(config, ctx) {
24
+ const parsed = (0, run_tree_1.runTreeFromContext)(ctx);
25
+ if (!parsed) {
26
+ return undefined;
27
+ }
28
+ return new run_trees_1.RunTree({
29
+ name: parsed.name || 'parent',
30
+ run_type: parsed.run_type || run_tree_1.RUN_TYPE.CHAIN,
31
+ id: parsed.id,
32
+ trace_id: parsed.trace_id,
33
+ dotted_order: parsed.dotted_order,
34
+ parent_run_id: parsed.parent_run_id,
35
+ project_name: config.projectName ?? parsed.project_name,
36
+ client: config.client,
37
+ // Force-enable so nested body `traceable` runs emit; tracing gate is `isTracingEnabled()`.
38
+ tracingEnabled: true,
39
+ });
40
+ }
41
+ /**
42
+ * Run `fn` with `op` installed as the active LangSmith run, emitting the run
43
+ * around the call. Shared by activity and Nexus handlers.
44
+ */
45
+ async function traceOperation(op, fn) {
46
+ await op.postRun();
47
+ try {
48
+ const result = await (0, traceable_1.withRunTree)(op, fn);
49
+ await op.end((0, run_tree_1.asOutputs)(result));
50
+ await op.patchRun();
51
+ return result;
52
+ }
53
+ catch (err) {
54
+ await op.end(undefined, (0, run_tree_1.describeError)(err));
55
+ await op.patchRun();
56
+ throw err;
57
+ }
58
+ }
59
+ /**
60
+ * Build the activity-inbound interceptor factory. The worker calls the factory
61
+ * once per activity with that activity's {@link ActivityContext}, from which we
62
+ * read the activity type for the run name.
63
+ */
64
+ function createActivityInboundInterceptor(config) {
65
+ return (ctx) => ({
66
+ async execute(input, next) {
67
+ if (!(0, langsmith_1.isTracingEnabled)()) {
68
+ return next(input);
69
+ }
70
+ const parent = reconstructParentRun(config, (0, propagation_1.readContextHeader)(input.headers));
71
+ if (!config.addTemporalRuns) {
72
+ // Propagation only: nest the user's body `traceable` runs under the
73
+ // reconstructed parent without emitting a Temporal-operation run.
74
+ // No propagated parent: run the body with no ambient run by design (nothing to attach to).
75
+ return parent ? (0, traceable_1.withRunTree)(parent, () => next(input)) : next(input);
76
+ }
77
+ const activityType = ctx.info.activityType;
78
+ const run = (0, run_tree_1.buildRunTree)(config, {
79
+ name: (0, run_tree_1.runActivityRunName)(activityType),
80
+ runType: run_tree_1.RUN_TYPE.TOOL,
81
+ parent,
82
+ inputs: { args: input.args },
83
+ });
84
+ return traceOperation(run, () => next(input));
85
+ },
86
+ });
87
+ }
88
+ function nexusContext(headers) {
89
+ return (0, propagation_1.decodeContextString)(headers[propagation_1.HEADER_KEY]);
90
+ }
91
+ /**
92
+ * Build the Nexus-handler interceptor. `startOperation` opens a
93
+ * `RunStartNexusOperationHandler:` run (so workflows the handler starts nest
94
+ * under it); `cancelOperation` opens `RunCancelNexusOperationHandler:`.
95
+ */
96
+ function createNexusInboundInterceptor(config) {
97
+ const handle = async (input, next, nameOf) => {
98
+ if (!(0, langsmith_1.isTracingEnabled)()) {
99
+ return next(input);
100
+ }
101
+ const parent = reconstructParentRun(config, nexusContext(input.ctx.headers));
102
+ if (!config.addTemporalRuns) {
103
+ return parent ? (0, traceable_1.withRunTree)(parent, () => next(input)) : next(input);
104
+ }
105
+ const run = (0, run_tree_1.buildRunTree)(config, {
106
+ name: nameOf(input.ctx.service, input.ctx.operation),
107
+ runType: run_tree_1.RUN_TYPE.CHAIN,
108
+ parent,
109
+ });
110
+ return traceOperation(run, () => next(input));
111
+ };
112
+ return {
113
+ startOperation(input, next) {
114
+ return handle(input, next, run_tree_1.runStartNexusHandlerRunName);
115
+ },
116
+ cancelOperation(input, next) {
117
+ return handle(input, next, run_tree_1.runCancelNexusHandlerRunName);
118
+ },
119
+ };
120
+ }
121
+ //# sourceMappingURL=activity-interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activity-interceptor.js","sourceRoot":"","sources":["../src/activity-interceptor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Client-side LangSmith interceptor. Execution ops (`start`) emit a peer marker
3
+ * and propagate the **ambient** context (the remote run is a sibling); messaging
4
+ * ops (`signal`, `query`, `update`, …) emit the marker and propagate the
5
+ * **marker** context (the remote handler nests under it).
6
+ *
7
+ * @module
8
+ */
9
+ import type { WorkflowClientInterceptor } from '@temporalio/client';
10
+ import type { EmitterConfig } from './sinks';
11
+ export declare function createClientInterceptor(config: EmitterConfig): WorkflowClientInterceptor;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ /**
3
+ * Client-side LangSmith interceptor. Execution ops (`start`) emit a peer marker
4
+ * and propagate the **ambient** context (the remote run is a sibling); messaging
5
+ * ops (`signal`, `query`, `update`, …) emit the marker and propagate the
6
+ * **marker** context (the remote handler nests under it).
7
+ *
8
+ * @module
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.createClientInterceptor = createClientInterceptor;
12
+ const langsmith_1 = require("langsmith");
13
+ const traceable_1 = require("langsmith/traceable");
14
+ const propagation_1 = require("./propagation");
15
+ const run_tree_1 = require("./run-tree");
16
+ function createClientInterceptor(config) {
17
+ const peerStart = async (input, next, name, args) => {
18
+ if (!(0, langsmith_1.isTracingEnabled)()) {
19
+ return next(input);
20
+ }
21
+ const ambient = (0, traceable_1.getCurrentRunTree)(true);
22
+ if (config.addTemporalRuns) {
23
+ await (0, run_tree_1.emitMarkerRun)((0, run_tree_1.buildRunTree)(config, { name, runType: run_tree_1.RUN_TYPE.CHAIN, parent: ambient, inputs: { args } }));
24
+ }
25
+ const headers = (0, propagation_1.withContextHeader)(input.headers, (0, run_tree_1.runHeaders)(ambient));
26
+ return next({ ...input, headers });
27
+ };
28
+ const parentMarker = async (input, next, name, args) => {
29
+ if (!(0, langsmith_1.isTracingEnabled)()) {
30
+ return next(input);
31
+ }
32
+ const ambient = (0, traceable_1.getCurrentRunTree)(true);
33
+ let propagate = ambient;
34
+ if (config.addTemporalRuns) {
35
+ const marker = (0, run_tree_1.buildRunTree)(config, { name, runType: run_tree_1.RUN_TYPE.CHAIN, parent: ambient, inputs: { args } });
36
+ await (0, run_tree_1.emitMarkerRun)(marker);
37
+ propagate = marker;
38
+ }
39
+ const headers = (0, propagation_1.withContextHeader)(input.headers, (0, run_tree_1.runHeaders)(propagate));
40
+ return next({ ...input, headers });
41
+ };
42
+ return {
43
+ start(input, next) {
44
+ return peerStart(input, next, (0, run_tree_1.startWorkflowRunName)(input.workflowType), input.options.args);
45
+ },
46
+ startWithDetails(input, next) {
47
+ return peerStart(input, next, (0, run_tree_1.startWorkflowRunName)(input.workflowType), input.options.args);
48
+ },
49
+ signal(input, next) {
50
+ return parentMarker(input, next, (0, run_tree_1.signalWorkflowRunName)(input.signalName), input.args);
51
+ },
52
+ signalWithStart(input, next) {
53
+ return parentMarker(input, next, (0, run_tree_1.signalWithStartRunName)(input.workflowType), input.signalArgs);
54
+ },
55
+ query(input, next) {
56
+ return parentMarker(input, next, (0, run_tree_1.queryWorkflowRunName)(input.queryType), input.args);
57
+ },
58
+ startUpdate(input, next) {
59
+ return parentMarker(input, next, (0, run_tree_1.startWorkflowUpdateRunName)(input.updateName), input.args);
60
+ },
61
+ async startUpdateWithStart(input, next) {
62
+ if (!(0, langsmith_1.isTracingEnabled)()) {
63
+ return next(input);
64
+ }
65
+ const ambient = (0, traceable_1.getCurrentRunTree)(true);
66
+ let propagate = ambient;
67
+ if (config.addTemporalRuns) {
68
+ const marker = (0, run_tree_1.buildRunTree)(config, {
69
+ name: (0, run_tree_1.startUpdateWithStartRunName)(input.updateName),
70
+ runType: run_tree_1.RUN_TYPE.CHAIN,
71
+ parent: ambient,
72
+ inputs: { args: input.updateArgs },
73
+ });
74
+ await (0, run_tree_1.emitMarkerRun)(marker);
75
+ propagate = marker;
76
+ }
77
+ const workflowStartHeaders = (0, propagation_1.withContextHeader)(input.workflowStartHeaders, (0, run_tree_1.runHeaders)(propagate));
78
+ return next({ ...input, workflowStartHeaders });
79
+ },
80
+ };
81
+ }
82
+ //# sourceMappingURL=client-interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-interceptor.js","sourceRoot":"","sources":["../src/client-interceptor.ts"],"names":[],"mappings":"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"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `@temporalio/langsmith` — LangSmith observability for Temporal.
3
+ *
4
+ * @module
5
+ */
6
+ export { LangSmithPlugin } from './plugin';
7
+ export type { LangSmithPluginOptions } from './plugin';
package/lib/index.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /**
3
+ * `@temporalio/langsmith` — LangSmith observability for Temporal.
4
+ *
5
+ * @module
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.LangSmithPlugin = void 0;
9
+ var plugin_1 = require("./plugin");
10
+ Object.defineProperty(exports, "LangSmithPlugin", { enumerable: true, get: function () { return plugin_1.LangSmithPlugin; } });
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,mCAA2C;AAAlC,yGAAA,eAAe,OAAA"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * {@link LangSmithPlugin} — the single object a user adds to their Temporal
3
+ * `Client` and `Worker` to get LangSmith tracing across workflow / activity /
4
+ * child-workflow / Nexus boundaries with no changes to their existing
5
+ * `traceable` instrumentation.
6
+ *
7
+ * The plugin wires four things:
8
+ * 1. a **client interceptor** that propagates trace context out of any
9
+ * `start` / `signal` / `query` / `update` call;
10
+ * 2. **activity + Nexus inbound interceptors** that reconstruct the trace and
11
+ * install it as the active run (so body `traceable` calls nest correctly);
12
+ * 3. the **workflow interceptor module** (loaded into the workflow bundle) plus
13
+ * a bundle-time global carrying the plugin config; and
14
+ * 4. the **LangSmith sink** that performs the actual out-of-isolate emission.
15
+ *
16
+ * On worker shutdown it flushes the LangSmith client so in-flight traces are
17
+ * not lost.
18
+ *
19
+ * @module
20
+ */
21
+ import { Client } from 'langsmith';
22
+ import { SimplePlugin } from '@temporalio/plugin';
23
+ import type { ClientOptions } from '@temporalio/client';
24
+ import type { BundleOptions, ReplayWorkerOptions, Worker, WorkerOptions } from '@temporalio/worker';
25
+ /**
26
+ * Options for {@link LangSmithPlugin}.
27
+ *
28
+ * Secrets (the LangSmith API key) are never accepted here as plaintext and
29
+ * never cross a Temporal boundary — supply a pre-constructed {@link Client}
30
+ * (which reads `LANGSMITH_API_KEY` from the worker/client process env) or let
31
+ * the plugin construct a default client from the environment.
32
+ *
33
+ * @experimental Plugins is an experimental feature; APIs may change without notice.
34
+ */
35
+ export interface LangSmithPluginOptions {
36
+ /**
37
+ * The LangSmith client runs are emitted to. Defaults to `new Client()`, which
38
+ * reads `LANGSMITH_API_KEY` / `LANGSMITH_ENDPOINT` from the process
39
+ * environment. Lives only in the worker/client process — never serialized.
40
+ */
41
+ client?: Client;
42
+ /** Target LangSmith project for every emitted run. */
43
+ projectName?: string;
44
+ /**
45
+ * When `true`, emit first-class runs for Temporal operations themselves
46
+ * (`StartWorkflow:`, `RunActivity:`, `HandleSignal:`, …) in addition to the
47
+ * user's `traceable` runs. When `false` (default), only the user's
48
+ * `traceable` runs are emitted — trace context still propagates across
49
+ * boundaries so they nest correctly.
50
+ */
51
+ addTemporalRuns?: boolean;
52
+ /** Tags attached to every run the plugin emits. */
53
+ tags?: string[];
54
+ /** Metadata merged into every run the plugin emits (credential keys scrubbed). */
55
+ metadata?: Record<string, unknown>;
56
+ }
57
+ /**
58
+ * Temporal plugin that adds LangSmith observability to a Client + Worker.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { traceable } from 'langsmith/traceable';
63
+ * import { LangSmithPlugin } from '@temporalio/langsmith';
64
+ *
65
+ * const plugin = new LangSmithPlugin({ addTemporalRuns: true });
66
+ * const worker = await Worker.create({ workflowsPath, taskQueue: 'tq', plugins: [plugin] });
67
+ * ```
68
+ *
69
+ * @experimental Plugins is an experimental feature; APIs may change without notice.
70
+ */
71
+ export declare class LangSmithPlugin extends SimplePlugin {
72
+ private readonly client;
73
+ private readonly emitter;
74
+ private readonly workflowConfig;
75
+ constructor(options?: LangSmithPluginOptions);
76
+ configureClient(options: ClientOptions): ClientOptions;
77
+ configureWorker(options: WorkerOptions): WorkerOptions;
78
+ configureReplayWorker(options: ReplayWorkerOptions): ReplayWorkerOptions;
79
+ private withLangSmithWorker;
80
+ configureBundler(options: BundleOptions): BundleOptions;
81
+ /**
82
+ * Flush the LangSmith client on worker shutdown so traces that were emitted
83
+ * just before exit are delivered. Wraps the worker run loop and flushes in a
84
+ * `finally`, swallowing flush errors (shutdown must not fail on telemetry).
85
+ */
86
+ runWorker(worker: Worker, next: (worker: Worker) => Promise<void>): Promise<void>;
87
+ private flush;
88
+ }