@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/lib/plugin.js ADDED
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ /**
3
+ * {@link LangSmithPlugin} — the single object a user adds to their Temporal
4
+ * `Client` and `Worker` to get LangSmith tracing across workflow / activity /
5
+ * child-workflow / Nexus boundaries with no changes to their existing
6
+ * `traceable` instrumentation.
7
+ *
8
+ * The plugin wires four things:
9
+ * 1. a **client interceptor** that propagates trace context out of any
10
+ * `start` / `signal` / `query` / `update` call;
11
+ * 2. **activity + Nexus inbound interceptors** that reconstruct the trace and
12
+ * install it as the active run (so body `traceable` calls nest correctly);
13
+ * 3. the **workflow interceptor module** (loaded into the workflow bundle) plus
14
+ * a bundle-time global carrying the plugin config; and
15
+ * 4. the **LangSmith sink** that performs the actual out-of-isolate emission.
16
+ *
17
+ * On worker shutdown it flushes the LangSmith client so in-flight traces are
18
+ * not lost.
19
+ *
20
+ * @module
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.LangSmithPlugin = void 0;
24
+ const langsmith_1 = require("langsmith");
25
+ const plugin_1 = require("@temporalio/plugin");
26
+ const activity_interceptor_1 = require("./activity-interceptor");
27
+ const client_interceptor_1 = require("./client-interceptor");
28
+ const sinks_1 = require("./sinks");
29
+ /**
30
+ * The module specifier for the workflow-side interceptors.
31
+ */
32
+ const WORKFLOW_INTERCEPTOR_MODULE = '@temporalio/langsmith/workflow-interceptors';
33
+ // Bare identifier (not dotted) so DefinePlugin substitutes textually.
34
+ const CONFIG_GLOBAL = '__TEMPORAL_LANGSMITH_CONFIG__';
35
+ // Bare builtin name added to `ignoreModules`; see {@link aliasAsyncHooks}.
36
+ const ASYNC_HOOKS_MODULE = 'async_hooks';
37
+ /**
38
+ * Temporal plugin that adds LangSmith observability to a Client + Worker.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * import { traceable } from 'langsmith/traceable';
43
+ * import { LangSmithPlugin } from '@temporalio/langsmith';
44
+ *
45
+ * const plugin = new LangSmithPlugin({ addTemporalRuns: true });
46
+ * const worker = await Worker.create({ workflowsPath, taskQueue: 'tq', plugins: [plugin] });
47
+ * ```
48
+ *
49
+ * @experimental Plugins is an experimental feature; APIs may change without notice.
50
+ */
51
+ class LangSmithPlugin extends plugin_1.SimplePlugin {
52
+ client;
53
+ emitter;
54
+ workflowConfig;
55
+ constructor(options = {}) {
56
+ // super() reads options.name immediately
57
+ super({ name: 'langchain.LangSmithPlugin' });
58
+ this.client = options.client ?? new langsmith_1.Client();
59
+ const addTemporalRuns = options.addTemporalRuns ?? false;
60
+ this.emitter = {
61
+ client: this.client,
62
+ addTemporalRuns,
63
+ projectName: options.projectName,
64
+ tags: options.tags,
65
+ metadata: options.metadata,
66
+ };
67
+ this.workflowConfig = {
68
+ addTemporalRuns,
69
+ projectName: options.projectName,
70
+ tags: options.tags,
71
+ metadata: options.metadata,
72
+ };
73
+ }
74
+ configureClient(options) {
75
+ const base = super.configureClient(options);
76
+ const existing = base.interceptors ?? {};
77
+ const workflow = Array.isArray(existing.workflow) ? [...existing.workflow] : [];
78
+ workflow.push((0, client_interceptor_1.createClientInterceptor)(this.emitter));
79
+ return { ...base, interceptors: { ...existing, workflow } };
80
+ }
81
+ configureWorker(options) {
82
+ return this.withLangSmithWorker(super.configureWorker(options));
83
+ }
84
+ configureReplayWorker(options) {
85
+ return this.withLangSmithWorker(super.configureReplayWorker(options));
86
+ }
87
+ withLangSmithWorker(options) {
88
+ const interceptors = options.interceptors ?? {};
89
+ const activityInbound = [...(interceptors.activityInbound ?? [])];
90
+ activityInbound.push((0, activity_interceptor_1.createActivityInboundInterceptor)(this.emitter));
91
+ const workflowModules = [...(interceptors.workflowModules ?? [])];
92
+ if (!workflowModules.includes(WORKFLOW_INTERCEPTOR_MODULE)) {
93
+ workflowModules.push(WORKFLOW_INTERCEPTOR_MODULE);
94
+ }
95
+ const nexusInbound = (0, activity_interceptor_1.createNexusInboundInterceptor)(this.emitter);
96
+ const nexusInterceptors = [...(interceptors.nexus ?? [])];
97
+ nexusInterceptors.push((_ctx) => ({ inbound: nexusInbound }));
98
+ // The user's own Worker sinks take precedence on any key collision.
99
+ const sinks = { ...(0, sinks_1.createLangSmithSinks)(this.client), ...(options.sinks ?? {}) };
100
+ return {
101
+ ...options,
102
+ interceptors: { ...interceptors, activityInbound, workflowModules, nexus: nexusInterceptors },
103
+ sinks,
104
+ };
105
+ }
106
+ configureBundler(options) {
107
+ const base = super.configureBundler(options);
108
+ const workflowInterceptorModules = [...(base.workflowInterceptorModules ?? [])];
109
+ if (!workflowInterceptorModules.includes(WORKFLOW_INTERCEPTOR_MODULE)) {
110
+ workflowInterceptorModules.push(WORKFLOW_INTERCEPTOR_MODULE);
111
+ }
112
+ // Dismiss the SDK bundler's disallowed-builtin guard for `async_hooks` (see aliasAsyncHooks).
113
+ const ignoreModules = [...(base.ignoreModules ?? [])];
114
+ if (!ignoreModules.includes(ASYNC_HOOKS_MODULE)) {
115
+ ignoreModules.push(ASYNC_HOOKS_MODULE);
116
+ }
117
+ const prevHook = base.webpackConfigHook;
118
+ const webpackConfigHook = (config) => {
119
+ const merged = prevHook ? prevHook(config) : config;
120
+ // Double-encode so the injected token is a string literal in the bundle.
121
+ const definitions = { [CONFIG_GLOBAL]: JSON.stringify(JSON.stringify(this.workflowConfig)) };
122
+ const withDefine = injectDefinePlugin(merged, definitions);
123
+ return aliasLangSmithNodeUtils(aliasAsyncHooks(withDefine));
124
+ };
125
+ return { ...base, workflowInterceptorModules, ignoreModules, webpackConfigHook };
126
+ }
127
+ /**
128
+ * Flush the LangSmith client on worker shutdown so traces that were emitted
129
+ * just before exit are delivered. Wraps the worker run loop and flushes in a
130
+ * `finally`, swallowing flush errors (shutdown must not fail on telemetry).
131
+ */
132
+ async runWorker(worker, next) {
133
+ try {
134
+ await next(worker);
135
+ }
136
+ finally {
137
+ await this.flush();
138
+ }
139
+ }
140
+ async flush() {
141
+ try {
142
+ await this.client.awaitPendingTraceBatches();
143
+ }
144
+ catch {
145
+ /* swallow: telemetry flush must never fail worker shutdown */
146
+ }
147
+ }
148
+ }
149
+ exports.LangSmithPlugin = LangSmithPlugin;
150
+ /**
151
+ * Redirect `node:async_hooks` to the workflow interceptor module's isolate-safe
152
+ * shim. webpack routes `node:` requests through a scheme handler before alias
153
+ * resolution, so `resolve.alias` cannot do this; `NormalModuleReplacementPlugin`
154
+ * rewrites the request in `beforeResolve` instead. Must be paired with the
155
+ * `ignoreModules` whitelist (see {@link LangSmithPlugin.configureBundler}). The
156
+ * rewrite target is the package specifier so webpack dedupes to one isolate
157
+ * instance regardless of issuer.
158
+ */
159
+ function aliasAsyncHooks(config) {
160
+ const plugins = [...(config.plugins ?? [])];
161
+ try {
162
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
163
+ const webpack = require('webpack');
164
+ plugins.push(new webpack.NormalModuleReplacementPlugin(/^node:async_hooks$/, (resource) => {
165
+ resource.request = WORKFLOW_INTERCEPTOR_MODULE;
166
+ }));
167
+ }
168
+ catch (err) {
169
+ if (err?.code !== 'MODULE_NOT_FOUND')
170
+ throw err;
171
+ /* webpack genuinely absent (MODULE_NOT_FOUND): leave plugins untouched; any other failure rethrows. */
172
+ }
173
+ return { ...config, plugins };
174
+ }
175
+ /**
176
+ * Redirect langsmith's node-only CJS utilities to its `.browser.cjs` siblings so
177
+ * the workflow bundle keeps node builtins out of the isolate. Scoped to langsmith
178
+ * so a user's own `fs.cjs` is never rewritten.
179
+ */
180
+ function aliasLangSmithNodeUtils(config) {
181
+ const plugins = [...(config.plugins ?? [])];
182
+ const swap = (s) => s.replace(/\.cjs$/, '.browser.cjs');
183
+ try {
184
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
185
+ const webpack = require('webpack');
186
+ plugins.push(new webpack.NormalModuleReplacementPlugin(/[/\\]utils[/\\](?:fs|worker_threads)\.cjs$/, (resource) => {
187
+ // Resolved sibling-relative requires (`./fs.cjs`) carry the absolute
188
+ // path on `createData.resource`; raw `beforeResolve` requests carry it
189
+ // on `request`.
190
+ const createData = resource.createData;
191
+ if (createData && typeof createData.resource === 'string') {
192
+ if (!createData.resource.includes('langsmith') || createData.resource.includes('.browser.cjs')) {
193
+ return;
194
+ }
195
+ createData.resource = swap(createData.resource);
196
+ if (typeof createData.userRequest === 'string') {
197
+ createData.userRequest = swap(createData.userRequest);
198
+ }
199
+ if (typeof createData.request === 'string') {
200
+ createData.request = swap(createData.request);
201
+ }
202
+ return;
203
+ }
204
+ if (!resource.context || !resource.context.includes('langsmith') || resource.request.includes('.browser.cjs')) {
205
+ return;
206
+ }
207
+ resource.request = swap(resource.request);
208
+ }));
209
+ }
210
+ catch (err) {
211
+ if (err?.code !== 'MODULE_NOT_FOUND')
212
+ throw err;
213
+ /* webpack genuinely absent (MODULE_NOT_FOUND): leave plugins untouched; any other failure rethrows. */
214
+ }
215
+ return { ...config, plugins };
216
+ }
217
+ /**
218
+ * Append a `DefinePlugin`-equivalent to a webpack config without a static
219
+ * `webpack` import (webpack is provided transitively by the worker).
220
+ */
221
+ function injectDefinePlugin(config, definitions) {
222
+ const plugins = [...(config.plugins ?? [])];
223
+ try {
224
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
225
+ const webpack = require('webpack');
226
+ plugins.push(new webpack.DefinePlugin(definitions));
227
+ }
228
+ catch (err) {
229
+ if (err?.code !== 'MODULE_NOT_FOUND')
230
+ throw err;
231
+ /* webpack genuinely absent (MODULE_NOT_FOUND): leave plugins untouched, workflow uses defaults; any other failure rethrows. */
232
+ }
233
+ return { ...config, plugins };
234
+ }
235
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Cross-boundary trace-context propagation for the LangSmith Temporal plugin.
3
+ *
4
+ * The LangSmith run context travels across every Temporal boundary
5
+ * (client→workflow, workflow→activity, workflow→child-workflow,
6
+ * workflow→Nexus, continue-as-new) as a single Temporal {@link Header}
7
+ * entry. The wire value is exactly the object produced by
8
+ * `RunTree.toHeaders()` — `{ "langsmith-trace": <dottedOrder>, baggage: <string> }`
9
+ * — which the receiving side feeds straight back into
10
+ * `RunTree.fromHeaders(...)` to reconstruct the parent run.
11
+ *
12
+ * Two encodings are supported because Temporal exposes two header shapes:
13
+ * - The binary/Temporal path (`Headers = Record<string, Payload>`): the
14
+ * context is encoded with the default payload converter.
15
+ * - The Nexus path (`Record<string, string>`): Nexus headers are plain
16
+ * strings with no Payload encoding, so the context is JSON-serialized.
17
+ *
18
+ * @module
19
+ */
20
+ import { type Payload } from '@temporalio/common';
21
+ /**
22
+ * Temporal header key under which the LangSmith trace context is carried.
23
+ *
24
+ * Must byte-match the Python plugin's key so trace context propagates across SDKs.
25
+ */
26
+ export declare const HEADER_KEY = "_temporal-langsmith-context";
27
+ /**
28
+ * The serialized LangSmith trace context that crosses a Temporal boundary.
29
+ *
30
+ * This is structurally the output of `RunTree.toHeaders()`. Keeping it as a
31
+ * plain JSON object lets the same value ride either the Payload transport or
32
+ * the Nexus plain-string transport unchanged.
33
+ */
34
+ export interface LangSmithTraceContext extends Record<string, string> {
35
+ /** The parent run's dotted order — the LangSmith trace pointer. */
36
+ 'langsmith-trace': string;
37
+ /** LangSmith baggage (metadata / tags / project), comma-encoded. */
38
+ baggage: string;
39
+ }
40
+ /** Return a shallow copy of `record` with all credential-bearing keys removed. */
41
+ export declare function scrubSensitive<T = unknown>(record: Record<string, T> | undefined): Record<string, T> | undefined;
42
+ /** Encode a trace context as a plain string for the Nexus header transport. */
43
+ export declare function encodeContextString(context: LangSmithTraceContext): string;
44
+ /** Decode a trace context from a Nexus plain-string header; never throws. */
45
+ export declare function decodeContextString(value: string | undefined): LangSmithTraceContext | undefined;
46
+ /**
47
+ * Inject a trace context into a Payload-keyed Temporal header map, returning a
48
+ * new map (interceptor inputs treat `headers` as immutable). Returns the input
49
+ * unchanged when `context` is `undefined`.
50
+ */
51
+ export declare function withContextHeader(headers: Record<string, Payload>, context: LangSmithTraceContext | undefined): Record<string, Payload>;
52
+ export declare function readContextHeader(headers: Record<string, Payload> | undefined): LangSmithTraceContext | undefined;
53
+ /**
54
+ * Temporal-internal query names that must NOT produce a LangSmith run: any
55
+ * `__temporal`-prefixed query plus the stack-trace introspection queries.
56
+ */
57
+ export declare function isInternalQuery(queryName: string): boolean;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ /**
3
+ * Cross-boundary trace-context propagation for the LangSmith Temporal plugin.
4
+ *
5
+ * The LangSmith run context travels across every Temporal boundary
6
+ * (client→workflow, workflow→activity, workflow→child-workflow,
7
+ * workflow→Nexus, continue-as-new) as a single Temporal {@link Header}
8
+ * entry. The wire value is exactly the object produced by
9
+ * `RunTree.toHeaders()` — `{ "langsmith-trace": <dottedOrder>, baggage: <string> }`
10
+ * — which the receiving side feeds straight back into
11
+ * `RunTree.fromHeaders(...)` to reconstruct the parent run.
12
+ *
13
+ * Two encodings are supported because Temporal exposes two header shapes:
14
+ * - The binary/Temporal path (`Headers = Record<string, Payload>`): the
15
+ * context is encoded with the default payload converter.
16
+ * - The Nexus path (`Record<string, string>`): Nexus headers are plain
17
+ * strings with no Payload encoding, so the context is JSON-serialized.
18
+ *
19
+ * @module
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.HEADER_KEY = void 0;
23
+ exports.scrubSensitive = scrubSensitive;
24
+ exports.encodeContextString = encodeContextString;
25
+ exports.decodeContextString = decodeContextString;
26
+ exports.withContextHeader = withContextHeader;
27
+ exports.readContextHeader = readContextHeader;
28
+ exports.isInternalQuery = isInternalQuery;
29
+ const common_1 = require("@temporalio/common");
30
+ /**
31
+ * Temporal header key under which the LangSmith trace context is carried.
32
+ *
33
+ * Must byte-match the Python plugin's key so trace context propagates across SDKs.
34
+ */
35
+ exports.HEADER_KEY = '_temporal-langsmith-context';
36
+ /** Case-insensitive key prefixes scrubbed from runs and propagated headers. */
37
+ const SENSITIVE_KEY_PREFIXES = [
38
+ 'auth',
39
+ 'api_key',
40
+ 'api-key',
41
+ 'x-api-key',
42
+ 'apikey',
43
+ 'cookie',
44
+ 'set-cookie',
45
+ 'bearer',
46
+ 'secret',
47
+ 'token',
48
+ 'password',
49
+ ];
50
+ /** Returns true when `key` looks like a credential-bearing header/metadata key. */
51
+ function isSensitiveKey(key) {
52
+ const lower = key.toLowerCase();
53
+ return SENSITIVE_KEY_PREFIXES.some((prefix) => lower.startsWith(prefix));
54
+ }
55
+ /** Return a shallow copy of `record` with all credential-bearing keys removed. */
56
+ function scrubSensitive(record) {
57
+ if (record == null) {
58
+ return record;
59
+ }
60
+ const out = {};
61
+ for (const [key, value] of Object.entries(record)) {
62
+ if (!isSensitiveKey(key)) {
63
+ out[key] = value;
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+ /** Encode a trace context into a Temporal `Payload`; never throws (failure → undefined, header omitted). */
69
+ function encodeContextPayload(context) {
70
+ try {
71
+ return common_1.defaultPayloadConverter.toPayload(context);
72
+ }
73
+ catch {
74
+ return undefined;
75
+ }
76
+ }
77
+ /** Decode a trace context from a Temporal `Payload`; never throws (malformed → undefined). */
78
+ function decodeContextPayload(payload) {
79
+ if (payload == null) {
80
+ return undefined;
81
+ }
82
+ try {
83
+ const value = common_1.defaultPayloadConverter.fromPayload(payload);
84
+ return isTraceContext(value) ? value : undefined;
85
+ }
86
+ catch {
87
+ return undefined;
88
+ }
89
+ }
90
+ /** Encode a trace context as a plain string for the Nexus header transport. */
91
+ function encodeContextString(context) {
92
+ return JSON.stringify(context);
93
+ }
94
+ /** Decode a trace context from a Nexus plain-string header; never throws. */
95
+ function decodeContextString(value) {
96
+ if (value == null || value === '') {
97
+ return undefined;
98
+ }
99
+ try {
100
+ const parsed = JSON.parse(value);
101
+ return isTraceContext(parsed) ? parsed : undefined;
102
+ }
103
+ catch {
104
+ return undefined;
105
+ }
106
+ }
107
+ function isTraceContext(value) {
108
+ return (typeof value === 'object' &&
109
+ value !== null &&
110
+ typeof value['langsmith-trace'] === 'string');
111
+ }
112
+ /**
113
+ * Inject a trace context into a Payload-keyed Temporal header map, returning a
114
+ * new map (interceptor inputs treat `headers` as immutable). Returns the input
115
+ * unchanged when `context` is `undefined`.
116
+ */
117
+ function withContextHeader(headers, context) {
118
+ if (context === undefined) {
119
+ return headers;
120
+ }
121
+ const payload = encodeContextPayload(context);
122
+ return payload === undefined ? headers : { ...headers, [exports.HEADER_KEY]: payload };
123
+ }
124
+ function readContextHeader(headers) {
125
+ return decodeContextPayload(headers?.[exports.HEADER_KEY]);
126
+ }
127
+ /**
128
+ * Temporal-internal query names that must NOT produce a LangSmith run: any
129
+ * `__temporal`-prefixed query plus the stack-trace introspection queries.
130
+ */
131
+ function isInternalQuery(queryName) {
132
+ return queryName.startsWith('__temporal') || queryName === '__stack_trace' || queryName === '__enhanced_stack_trace';
133
+ }
134
+ //# sourceMappingURL=propagation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"propagation.js","sourceRoot":"","sources":["../src/propagation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Replay-safe LangSmith run construction for workflow code, plus the pure
3
+ * run-name builders shared by every interceptor.
4
+ *
5
+ * Inside the workflow isolate LangSmith's default `RunTree` is unsafe on two
6
+ * counts that {@link ReplaySafeRunTree} fixes:
7
+ *
8
+ * 1. **Non-deterministic ids.** Its default random-tailed uuid7 would mint a
9
+ * fresh id on every replay, flooding the backend with duplicates.
10
+ * 2. **Network I/O on replay.** `postRun` / `patchRun` would re-POST runs on
11
+ * every history replay.
12
+ *
13
+ * Internal: every export here is consumed only by sibling modules in this
14
+ * package; none is reachable through a package entry point.
15
+ *
16
+ * @module
17
+ * @internal
18
+ */
19
+ import { type LangSmithTraceContext } from './propagation';
20
+ import { type EmitterConfig } from './sinks';
21
+ /** LangSmith run-type constants used for Temporal-operation runs. */
22
+ export declare const RUN_TYPE: {
23
+ /** Workflow / child-workflow / scheduling / handler spans. */
24
+ readonly CHAIN: "chain";
25
+ /** Activity-body execution spans. */
26
+ readonly TOOL: "tool";
27
+ };
28
+ /** `StartWorkflow:<type>` — client/parent-side workflow-start marker. */
29
+ export declare const startWorkflowRunName: (workflowType: string) => string;
30
+ /** `RunWorkflow:<type>` — the workflow-execution span (inbound). */
31
+ export declare const runWorkflowRunName: (workflowType: string) => string;
32
+ /** `StartActivity:<name>` — workflow-side activity-schedule marker. */
33
+ export declare const startActivityRunName: (activityType: string) => string;
34
+ /** `RunActivity:<name>` — the activity-execution span (activity inbound). */
35
+ export declare const runActivityRunName: (activityType: string) => string;
36
+ /** `StartChildWorkflow:<type>` — workflow-side child-start marker. */
37
+ export declare const startChildWorkflowRunName: (workflowType: string) => string;
38
+ /** `StartNexusOperation:<service>/<op>` — workflow-side Nexus-start marker. */
39
+ export declare const startNexusOperationRunName: (service: string, operation: string) => string;
40
+ /** `RunStartNexusOperationHandler:<service>/<op>` — Nexus start-handler span. */
41
+ export declare const runStartNexusHandlerRunName: (service: string, operation: string) => string;
42
+ /** `RunCancelNexusOperationHandler:<service>/<op>` — Nexus cancel-handler span. */
43
+ export declare const runCancelNexusHandlerRunName: (service: string, operation: string) => string;
44
+ /** `HandleSignal:<name>` — inbound signal handler span. */
45
+ export declare const handleSignalRunName: (signalName: string) => string;
46
+ /** `HandleQuery:<name>` — inbound query handler span. */
47
+ export declare const handleQueryRunName: (queryName: string) => string;
48
+ /** `HandleUpdate:<name>` — inbound update handler span. */
49
+ export declare const handleUpdateRunName: (updateName: string) => string;
50
+ /** `ValidateUpdate:<name>` — inbound update validator span. */
51
+ export declare const validateUpdateRunName: (updateName: string) => string;
52
+ /** `SignalWorkflow:<name>` — client/outbound signal marker. */
53
+ export declare const signalWorkflowRunName: (signalName: string) => string;
54
+ /** `SignalChildWorkflow:<name>` — workflow-side signal-child marker. */
55
+ export declare const signalChildWorkflowRunName: (signalName: string) => string;
56
+ /** `SignalExternalWorkflow:<name>` — workflow-side signal-external marker. */
57
+ export declare const signalExternalWorkflowRunName: (signalName: string) => string;
58
+ /** `SignalWithStartWorkflow:<type>` — client signal-with-start marker. */
59
+ export declare const signalWithStartRunName: (workflowType: string) => string;
60
+ /** `QueryWorkflow:<name>` — client query marker. */
61
+ export declare const queryWorkflowRunName: (queryName: string) => string;
62
+ /** `StartWorkflowUpdate:<name>` — client update-start marker. */
63
+ export declare const startWorkflowUpdateRunName: (updateName: string) => string;
64
+ /** `StartUpdateWithStartWorkflow:<name>` — client update-with-start marker. */
65
+ export declare const startUpdateWithStartRunName: (updateName: string) => string;
66
+ /**
67
+ * Render an error into the LangSmith `error` string, honoring Temporal's
68
+ * benign-failure category.
69
+ *
70
+ * A `BENIGN`-category `ApplicationFailure` is an expected, non-alarming outcome
71
+ * (e.g. a control-flow signal), so it leaves the run's `error` unset. All other
72
+ * failures produce `"<type>: <message>"`.
73
+ */
74
+ export declare function describeError(err: unknown): string | undefined;
75
+ /** Coerce an arbitrary operation result into a LangSmith outputs object. */
76
+ export declare function asOutputs(result: unknown): Record<string, unknown>;
77
+ /** The trace context to propagate for `run`, or `undefined` when absent. */
78
+ export declare function runHeaders(run: RunTree | undefined): LangSmithTraceContext | undefined;
79
+ /** Emit a short-lived marker run. */
80
+ export declare function emitMarkerRun(marker: RunTree): Promise<void>;
81
+ /** Reconstruct a propagated parent {@link RunTree} from a trace context; never throws. */
82
+ export declare function runTreeFromContext(ctx: LangSmithTraceContext | undefined): RunTree | undefined;
83
+ interface RunTreeParams {
84
+ name: string;
85
+ runType: string;
86
+ parent: RunTree | undefined;
87
+ inputs?: Record<string, unknown>;
88
+ }
89
+ /**
90
+ * Build an emitter-side {@link RunTree} (client / activity / Nexus); force-enables
91
+ * tracing so nested body `traceable` runs emit — tracing gate is `isTracingEnabled()`.
92
+ */
93
+ export declare function buildRunTree(config: EmitterConfig, params: RunTreeParams): RunTree;
94
+ export {};