langsmith 0.8.12 → 0.10.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.
Files changed (56) hide show
  1. package/dist/_openapi_client/client.cjs +1 -1
  2. package/dist/_openapi_client/client.d.ts +2 -2
  3. package/dist/_openapi_client/client.js +1 -1
  4. package/dist/_openapi_client/internal/detect-platform.cjs +1 -1
  5. package/dist/_openapi_client/internal/detect-platform.js +1 -1
  6. package/dist/_openapi_client/resources/annotation-queues/annotation-queues.d.ts +1 -0
  7. package/dist/_openapi_client/resources/index.d.ts +1 -1
  8. package/dist/_openapi_client/resources/issues.cjs +1 -1
  9. package/dist/_openapi_client/resources/issues.d.ts +18 -1
  10. package/dist/_openapi_client/resources/issues.js +1 -1
  11. package/dist/_openapi_client/resources/online-evaluators.d.ts +2 -2
  12. package/dist/_openapi_client/resources/sandboxes/boxes.cjs +20 -3
  13. package/dist/_openapi_client/resources/sandboxes/boxes.d.ts +40 -9
  14. package/dist/_openapi_client/resources/sandboxes/boxes.js +20 -3
  15. package/dist/_openapi_client/resources/sandboxes/sandboxes.d.ts +46 -3
  16. package/dist/_openapi_client/resources/sandboxes/snapshots.cjs +6 -2
  17. package/dist/_openapi_client/resources/sandboxes/snapshots.d.ts +16 -6
  18. package/dist/_openapi_client/resources/sandboxes/snapshots.js +6 -2
  19. package/dist/client.cjs +56 -83
  20. package/dist/client.d.ts +16 -14
  21. package/dist/client.js +56 -83
  22. package/dist/env.cjs +14 -1
  23. package/dist/env.d.ts +9 -0
  24. package/dist/env.js +12 -0
  25. package/dist/evaluation/evaluate_comparative.cjs +2 -0
  26. package/dist/evaluation/evaluate_comparative.js +2 -0
  27. package/dist/experimental/vercel/telemetry.cjs +19 -6
  28. package/dist/experimental/vercel/telemetry.js +17 -4
  29. package/dist/index.cjs +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +1 -1
  32. package/dist/run_trees.cjs +19 -2
  33. package/dist/run_trees.d.ts +12 -0
  34. package/dist/run_trees.js +19 -2
  35. package/dist/schemas.d.ts +2 -0
  36. package/dist/singletons/constants.cjs +7 -1
  37. package/dist/singletons/constants.d.ts +3 -0
  38. package/dist/singletons/constants.js +5 -0
  39. package/dist/traceable.cjs +46 -15
  40. package/dist/traceable.js +48 -17
  41. package/dist/utils/_uuid.cjs +11 -19
  42. package/dist/utils/_uuid.d.ts +7 -7
  43. package/dist/utils/_uuid.js +11 -19
  44. package/dist/utils/env.cjs +18 -7
  45. package/dist/utils/env.js +18 -7
  46. package/dist/utils/redaction.cjs +38 -0
  47. package/dist/utils/redaction.d.ts +10 -0
  48. package/dist/utils/redaction.js +33 -0
  49. package/dist/utils/sampling.cjs +34 -0
  50. package/dist/utils/sampling.d.ts +10 -0
  51. package/dist/utils/sampling.js +30 -0
  52. package/dist/wrappers/anthropic.cjs +21 -24
  53. package/dist/wrappers/anthropic.js +21 -24
  54. package/dist/wrappers/openai_agents.cjs +2 -1
  55. package/dist/wrappers/openai_agents.js +2 -1
  56. package/package.json +1 -1
package/dist/run_trees.js CHANGED
@@ -134,6 +134,10 @@ class Baggage {
134
134
  return items.join(",");
135
135
  }
136
136
  }
137
+ function getExcludeInputsOnPatch() {
138
+ const value = getLangSmithEnvironmentVariable("EXCLUDE_INPUTS_ON_PATCH");
139
+ return value === undefined || value.toLowerCase() === "true" || value === "1";
140
+ }
137
141
  export class RunTree {
138
142
  constructor(originalConfig) {
139
143
  Object.defineProperty(this, "id", {
@@ -731,7 +735,20 @@ export class RunTree {
731
735
  console.error(`Error in postRun for run ${this.id}:`, error);
732
736
  }
733
737
  }
738
+ /**
739
+ * Patch the run tree to the API.
740
+ *
741
+ * @param options.excludeInputs - Whether to exclude inputs from the patch
742
+ * request. Defaults to the value of `LANGSMITH_EXCLUDE_INPUTS_ON_PATCH`
743
+ * (or its `LANGCHAIN_` equivalent), which itself defaults to `true`.
744
+ * An explicit value overrides the environment variable for this call.
745
+ *
746
+ * Unless the environment variable is disabled, inputs added after the
747
+ * initial `postRun()` are not persisted unless this option is explicitly set
748
+ * to `false`.
749
+ */
734
750
  async patchRun(options) {
751
+ const excludeInputs = options?.excludeInputs ?? getExcludeInputsOnPatch();
735
752
  if (this.replicas && this.replicas.length > 0) {
736
753
  for (const { projectName, primary, apiKey, apiUrl, workspaceId, updates, reroot, client: replicaClient, } of this.replicas) {
737
754
  const runData = this._remapForProject({
@@ -767,7 +784,7 @@ export class RunTree {
767
784
  // Important that inputs is not a key in the run update
768
785
  // if excluded because it will overwrite the run create if the
769
786
  // two operations are merged during batching
770
- if (!options?.excludeInputs) {
787
+ if (!excludeInputs) {
771
788
  updatePayload.inputs = runData.inputs;
772
789
  }
773
790
  const targetClient = replicaClient ?? this.client;
@@ -800,7 +817,7 @@ export class RunTree {
800
817
  // Important that inputs is not a key in the run update
801
818
  // if excluded because it will overwrite the run create if the
802
819
  // two operations are merged during batching
803
- if (!options?.excludeInputs) {
820
+ if (!excludeInputs) {
804
821
  runUpdate.inputs = this.inputs;
805
822
  }
806
823
  await this.client.updateRun(this.id, runUpdate);
package/dist/schemas.d.ts CHANGED
@@ -288,6 +288,8 @@ export interface FeedbackBase {
288
288
  created_at: string;
289
289
  modified_at: string;
290
290
  run_id: string;
291
+ /** The trace the run belongs to. Equals run_id for a root run. */
292
+ trace_id?: string;
291
293
  key: string;
292
294
  score: ScoreType;
293
295
  value: ValueType;
@@ -1,6 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._REPLICA_TRACE_ROOTS_KEY = exports._LC_CHILD_RUN_END_PROMISES_KEY = exports._LC_CONTEXT_VARIABLES_KEY = void 0;
3
+ exports._processingFailed = exports._PROCESSING_FAILED_PREFIX = exports._PROCESSING_FAILED_KEY = exports._REPLICA_TRACE_ROOTS_KEY = exports._LC_CHILD_RUN_END_PROMISES_KEY = exports._LC_CONTEXT_VARIABLES_KEY = void 0;
4
4
  exports._LC_CONTEXT_VARIABLES_KEY = Symbol.for("lc:context_variables");
5
5
  exports._LC_CHILD_RUN_END_PROMISES_KEY = Symbol.for("lc:child_run_end_promises");
6
6
  exports._REPLICA_TRACE_ROOTS_KEY = Symbol.for("langsmith:replica_trace_roots");
7
+ // Marker for a payload whose redactor threw.
8
+ // Takes the hook name, never the error: reading e.constructor runs user code.
9
+ exports._PROCESSING_FAILED_KEY = "ls_error";
10
+ exports._PROCESSING_FAILED_PREFIX = "Processing failed; ";
11
+ const _processingFailed = (payload, hook) => `${exports._PROCESSING_FAILED_PREFIX}${payload} dropped (${hook})`;
12
+ exports._processingFailed = _processingFailed;
@@ -1,3 +1,6 @@
1
1
  export declare const _LC_CONTEXT_VARIABLES_KEY: unique symbol;
2
2
  export declare const _LC_CHILD_RUN_END_PROMISES_KEY: unique symbol;
3
3
  export declare const _REPLICA_TRACE_ROOTS_KEY: unique symbol;
4
+ export declare const _PROCESSING_FAILED_KEY = "ls_error";
5
+ export declare const _PROCESSING_FAILED_PREFIX = "Processing failed; ";
6
+ export declare const _processingFailed: (payload: "inputs" | "outputs", hook: string) => string;
@@ -1,3 +1,8 @@
1
1
  export const _LC_CONTEXT_VARIABLES_KEY = Symbol.for("lc:context_variables");
2
2
  export const _LC_CHILD_RUN_END_PROMISES_KEY = Symbol.for("lc:child_run_end_promises");
3
3
  export const _REPLICA_TRACE_ROOTS_KEY = Symbol.for("langsmith:replica_trace_roots");
4
+ // Marker for a payload whose redactor threw.
5
+ // Takes the hook name, never the error: reading e.constructor runs user code.
6
+ export const _PROCESSING_FAILED_KEY = "ls_error";
7
+ export const _PROCESSING_FAILED_PREFIX = "Processing failed; ";
8
+ export const _processingFailed = (payload, hook) => `${_PROCESSING_FAILED_PREFIX}${payload} dropped (${hook})`;
@@ -91,12 +91,26 @@ const runInputsToMap = (rawInputs) => {
91
91
  const handleRunInputs = (
92
92
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
93
  inputs, processInputs) => {
94
+ // Fail closed: trace the run, but with a marker instead of raw inputs.
95
+ const dropped = (e) => {
96
+ if ((0, env_js_1.allowUnprocessedPayloads)()) {
97
+ console.warn("Error occurred during processInputs. Tracing unprocessed inputs " +
98
+ "because LANGSMITH_ALLOW_UNPROCESSED_PAYLOADS is set:", e);
99
+ return inputs;
100
+ }
101
+ console.warn("Error occurred during processInputs. Dropping inputs:", e);
102
+ return {
103
+ [constants_js_1._PROCESSING_FAILED_KEY]: (0, constants_js_1._processingFailed)("inputs", "processInputs"),
104
+ };
105
+ };
94
106
  try {
95
- return processInputs(inputs);
107
+ const processed = processInputs(inputs);
108
+ return (0, asserts_js_1.isThenable)(processed)
109
+ ? Promise.resolve(processed).catch(dropped)
110
+ : processed;
96
111
  }
97
112
  catch (e) {
98
- console.error("Error occurred during processInputs. Sending raw inputs:", e);
99
- return inputs;
113
+ return dropped(e);
100
114
  }
101
115
  };
102
116
  const _extractUsage = (runData) => {
@@ -156,12 +170,36 @@ async function handleRunOutputs(params) {
156
170
  else {
157
171
  outputs = { outputs: rawOutputs };
158
172
  }
173
+ // `outputs` is overwritten by the redactor call below; keep the raw value.
174
+ const unprocessed = outputs;
159
175
  const childRunEndPromises = !skipChildPromiseDelay &&
160
176
  (0, run_trees_js_1.isRunTree)(runTree) &&
161
177
  constants_js_1._LC_CHILD_RUN_END_PROMISES_KEY in runTree &&
162
178
  Array.isArray(runTree[constants_js_1._LC_CHILD_RUN_END_PROMISES_KEY])
163
179
  ? Promise.all(runTree[constants_js_1._LC_CHILD_RUN_END_PROMISES_KEY] ?? [])
164
180
  : Promise.resolve();
181
+ // Fail closed, as for inputs: a marker rather than the raw value.
182
+ const droppedOutputs = (e) => {
183
+ if ((0, env_js_1.allowUnprocessedPayloads)()) {
184
+ console.error("Error occurred during processOutputs. Tracing unprocessed outputs " +
185
+ "because LANGSMITH_ALLOW_UNPROCESSED_PAYLOADS is set:", e);
186
+ return unprocessed;
187
+ }
188
+ console.error("Error occurred during processOutputs. Dropping outputs:", e);
189
+ return {
190
+ [constants_js_1._PROCESSING_FAILED_KEY]: (0, constants_js_1._processingFailed)("outputs", "processOutputs"),
191
+ };
192
+ };
193
+ const endOutputsDropped = async (e) => {
194
+ const finalOutputs = droppedOutputs(e);
195
+ try {
196
+ await childRunEndPromises;
197
+ await runTree?.end(finalOutputs);
198
+ }
199
+ catch (endErr) {
200
+ console.error("Error occurred during runTree?.end.", endErr);
201
+ }
202
+ };
165
203
  try {
166
204
  outputs = processOutputsFn(outputs);
167
205
  // TODO: Investigate making this behavior for all returned promises
@@ -173,16 +211,7 @@ async function handleRunOutputs(params) {
173
211
  await childRunEndPromises;
174
212
  await runTree?.end(processedOutputs);
175
213
  })
176
- .catch(async (e) => {
177
- console.error("Error occurred during processOutputs. Sending unprocessed outputs:", e);
178
- try {
179
- await childRunEndPromises;
180
- await runTree?.end(outputs);
181
- }
182
- catch (e) {
183
- console.error("Error occurred during runTree?.end.", e);
184
- }
185
- })
214
+ .catch(endOutputsDropped)
186
215
  .finally(async () => {
187
216
  try {
188
217
  await handleEnd({
@@ -200,7 +229,7 @@ async function handleRunOutputs(params) {
200
229
  }
201
230
  }
202
231
  catch (e) {
203
- console.error("Error occurred during processOutputs. Sending unprocessed outputs:", e);
232
+ outputs = droppedOutputs(e);
204
233
  }
205
234
  _populateUsageMetadataAndOutputs(outputs, runTree);
206
235
  void childRunEndPromises
@@ -238,7 +267,9 @@ const getTracingRunTree = (runTree, inputs, getInvocationParams, processInputs,
238
267
  const [attached, args] = handleRunAttachments(inputs, extractAttachments);
239
268
  runTree.attachments = attached;
240
269
  const processedInputs = handleRunInputs(args, processInputs);
241
- if (isAsyncFn(processInputs)) {
270
+ // Off the value, not the fn shape: non-async redactors return promises too.
271
+ // Client.prepareRunCreateOrUpdateInputs also flattens it; don't rely on that.
272
+ if ((0, asserts_js_1.isThenable)(processedInputs)) {
242
273
  runTree._awaitInputsOnPost = true;
243
274
  }
244
275
  runTree.inputs = processedInputs;
package/dist/traceable.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { RunTree, isRunTree, isRunnableConfigLike, } from "./run_trees.js";
3
- import { isEnvTracingEnabled } from "./env.js";
3
+ import { allowUnprocessedPayloads, isEnvTracingEnabled } from "./env.js";
4
4
  import { ROOT, AsyncLocalStorageProviderSingleton, getCurrentRunTree, } from "./singletons/traceable.js";
5
- import { _LC_CHILD_RUN_END_PROMISES_KEY, _LC_CONTEXT_VARIABLES_KEY, } from "./singletons/constants.js";
5
+ import { _LC_CHILD_RUN_END_PROMISES_KEY, _LC_CONTEXT_VARIABLES_KEY, _PROCESSING_FAILED_KEY, _processingFailed, } from "./singletons/constants.js";
6
6
  import { isKVMap, isReadableStream, isAsyncIterable, isIteratorLike, isThenable, isGenerator, isPromiseMethod, } from "./utils/asserts.js";
7
7
  import { __version__ } from "./index.js";
8
8
  import { getOTELTrace, getOTELContext } from "./singletons/otel.js";
@@ -87,12 +87,26 @@ const runInputsToMap = (rawInputs) => {
87
87
  const handleRunInputs = (
88
88
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
89
  inputs, processInputs) => {
90
+ // Fail closed: trace the run, but with a marker instead of raw inputs.
91
+ const dropped = (e) => {
92
+ if (allowUnprocessedPayloads()) {
93
+ console.warn("Error occurred during processInputs. Tracing unprocessed inputs " +
94
+ "because LANGSMITH_ALLOW_UNPROCESSED_PAYLOADS is set:", e);
95
+ return inputs;
96
+ }
97
+ console.warn("Error occurred during processInputs. Dropping inputs:", e);
98
+ return {
99
+ [_PROCESSING_FAILED_KEY]: _processingFailed("inputs", "processInputs"),
100
+ };
101
+ };
90
102
  try {
91
- return processInputs(inputs);
103
+ const processed = processInputs(inputs);
104
+ return isThenable(processed)
105
+ ? Promise.resolve(processed).catch(dropped)
106
+ : processed;
92
107
  }
93
108
  catch (e) {
94
- console.error("Error occurred during processInputs. Sending raw inputs:", e);
95
- return inputs;
109
+ return dropped(e);
96
110
  }
97
111
  };
98
112
  const _extractUsage = (runData) => {
@@ -152,12 +166,36 @@ async function handleRunOutputs(params) {
152
166
  else {
153
167
  outputs = { outputs: rawOutputs };
154
168
  }
169
+ // `outputs` is overwritten by the redactor call below; keep the raw value.
170
+ const unprocessed = outputs;
155
171
  const childRunEndPromises = !skipChildPromiseDelay &&
156
172
  isRunTree(runTree) &&
157
173
  _LC_CHILD_RUN_END_PROMISES_KEY in runTree &&
158
174
  Array.isArray(runTree[_LC_CHILD_RUN_END_PROMISES_KEY])
159
175
  ? Promise.all(runTree[_LC_CHILD_RUN_END_PROMISES_KEY] ?? [])
160
176
  : Promise.resolve();
177
+ // Fail closed, as for inputs: a marker rather than the raw value.
178
+ const droppedOutputs = (e) => {
179
+ if (allowUnprocessedPayloads()) {
180
+ console.error("Error occurred during processOutputs. Tracing unprocessed outputs " +
181
+ "because LANGSMITH_ALLOW_UNPROCESSED_PAYLOADS is set:", e);
182
+ return unprocessed;
183
+ }
184
+ console.error("Error occurred during processOutputs. Dropping outputs:", e);
185
+ return {
186
+ [_PROCESSING_FAILED_KEY]: _processingFailed("outputs", "processOutputs"),
187
+ };
188
+ };
189
+ const endOutputsDropped = async (e) => {
190
+ const finalOutputs = droppedOutputs(e);
191
+ try {
192
+ await childRunEndPromises;
193
+ await runTree?.end(finalOutputs);
194
+ }
195
+ catch (endErr) {
196
+ console.error("Error occurred during runTree?.end.", endErr);
197
+ }
198
+ };
161
199
  try {
162
200
  outputs = processOutputsFn(outputs);
163
201
  // TODO: Investigate making this behavior for all returned promises
@@ -169,16 +207,7 @@ async function handleRunOutputs(params) {
169
207
  await childRunEndPromises;
170
208
  await runTree?.end(processedOutputs);
171
209
  })
172
- .catch(async (e) => {
173
- console.error("Error occurred during processOutputs. Sending unprocessed outputs:", e);
174
- try {
175
- await childRunEndPromises;
176
- await runTree?.end(outputs);
177
- }
178
- catch (e) {
179
- console.error("Error occurred during runTree?.end.", e);
180
- }
181
- })
210
+ .catch(endOutputsDropped)
182
211
  .finally(async () => {
183
212
  try {
184
213
  await handleEnd({
@@ -196,7 +225,7 @@ async function handleRunOutputs(params) {
196
225
  }
197
226
  }
198
227
  catch (e) {
199
- console.error("Error occurred during processOutputs. Sending unprocessed outputs:", e);
228
+ outputs = droppedOutputs(e);
200
229
  }
201
230
  _populateUsageMetadataAndOutputs(outputs, runTree);
202
231
  void childRunEndPromises
@@ -234,7 +263,9 @@ const getTracingRunTree = (runTree, inputs, getInvocationParams, processInputs,
234
263
  const [attached, args] = handleRunAttachments(inputs, extractAttachments);
235
264
  runTree.attachments = attached;
236
265
  const processedInputs = handleRunInputs(args, processInputs);
237
- if (isAsyncFn(processInputs)) {
266
+ // Off the value, not the fn shape: non-async redactors return promises too.
267
+ // Client.prepareRunCreateOrUpdateInputs also flattens it; don't rely on that.
268
+ if (isThenable(processedInputs)) {
238
269
  runTree._awaitInputsOnPost = true;
239
270
  }
240
271
  runTree.inputs = processedInputs;
@@ -113,14 +113,14 @@ function _fastHash128(str) {
113
113
  /**
114
114
  * Generate a deterministic UUID v7 derived from an original UUID and a key.
115
115
  *
116
- * This function creates a new UUID that:
117
- * - Preserves the timestamp from the original UUID if it's UUID v7
118
- * - Uses current time if the original is not UUID v7
119
- * - Uses deterministic "random" bits derived from hashing the original + key
120
- * - Is valid UUID v7 format
116
+ * A pure function of `originalId` and `key`: it reads no ambient state, so the
117
+ * same pair always yields the same UUID, in any process, at any time. This is
118
+ * used for creating replica ids that are reproducible across distributed
119
+ * systems, including across SDK implementations.
121
120
  *
122
- * This is used for creating replica IDs that maintain time-ordering properties
123
- * while being deterministic across distributed systems.
121
+ * A UUID v7 original keeps its timestamp, so derived ids stay ordered by run
122
+ * start time. Any other original has none to keep, and the field is
123
+ * hash-derived: stable, but not chronological.
124
124
  *
125
125
  * @param originalId - The source UUID string (ideally UUID v7 to preserve timestamp)
126
126
  * @param key - A string key used for deterministic derivation (e.g., project name)
@@ -147,8 +147,6 @@ function nonCryptographicUuid7Deterministic(originalId, key) {
147
147
  // [8] 2 bits: variant (10) + 6 bits rand_b
148
148
  // [9-15] 56 bits: rand_b (continued)
149
149
  const b = new Uint8Array(16);
150
- // Check if original is UUID v7 - if so, preserve its timestamp
151
- // If not, use current time to ensure the derived UUID has a valid timestamp
152
150
  const version = getUuidVersion(originalId);
153
151
  if (version === 7) {
154
152
  // Preserve timestamp from original UUID7 (bytes 0-5)
@@ -156,16 +154,10 @@ function nonCryptographicUuid7Deterministic(originalId, key) {
156
154
  b.set(originalBytes.slice(0, 6), 0);
157
155
  }
158
156
  else {
159
- // Generate fresh timestamp for non-UUID7 inputs
160
- // This matches the uuid npm package's v7 implementation:
161
- // https://github.com/uuidjs/uuid/blob/main/src/v7.ts
162
- const msecs = Date.now();
163
- b[0] = (msecs / 0x10000000000) & 0xff;
164
- b[1] = (msecs / 0x100000000) & 0xff;
165
- b[2] = (msecs / 0x1000000) & 0xff;
166
- b[3] = (msecs / 0x10000) & 0xff;
167
- b[4] = (msecs / 0x100) & 0xff;
168
- b[5] = msecs & 0xff;
157
+ // No timestamp to preserve. Use the six hash bytes left unused below: a
158
+ // clock read here would make this impure, and the same id is derived
159
+ // from several runs, all of which must agree.
160
+ b.set(h.slice(10, 16), 0);
169
161
  }
170
162
  // Set version 7 (0111) in high nibble + 4 bits from hash
171
163
  b[6] = 0x70 | (h[0] & 0x0f);
@@ -22,14 +22,14 @@ export declare function warnIfNotUuidV7(uuidStr: string, _idType: string): void;
22
22
  /**
23
23
  * Generate a deterministic UUID v7 derived from an original UUID and a key.
24
24
  *
25
- * This function creates a new UUID that:
26
- * - Preserves the timestamp from the original UUID if it's UUID v7
27
- * - Uses current time if the original is not UUID v7
28
- * - Uses deterministic "random" bits derived from hashing the original + key
29
- * - Is valid UUID v7 format
25
+ * A pure function of `originalId` and `key`: it reads no ambient state, so the
26
+ * same pair always yields the same UUID, in any process, at any time. This is
27
+ * used for creating replica ids that are reproducible across distributed
28
+ * systems, including across SDK implementations.
30
29
  *
31
- * This is used for creating replica IDs that maintain time-ordering properties
32
- * while being deterministic across distributed systems.
30
+ * A UUID v7 original keeps its timestamp, so derived ids stay ordered by run
31
+ * start time. Any other original has none to keep, and the field is
32
+ * hash-derived: stable, but not chronological.
33
33
  *
34
34
  * @param originalId - The source UUID string (ideally UUID v7 to preserve timestamp)
35
35
  * @param key - A string key used for deterministic derivation (e.g., project name)
@@ -106,14 +106,14 @@ function _fastHash128(str) {
106
106
  /**
107
107
  * Generate a deterministic UUID v7 derived from an original UUID and a key.
108
108
  *
109
- * This function creates a new UUID that:
110
- * - Preserves the timestamp from the original UUID if it's UUID v7
111
- * - Uses current time if the original is not UUID v7
112
- * - Uses deterministic "random" bits derived from hashing the original + key
113
- * - Is valid UUID v7 format
109
+ * A pure function of `originalId` and `key`: it reads no ambient state, so the
110
+ * same pair always yields the same UUID, in any process, at any time. This is
111
+ * used for creating replica ids that are reproducible across distributed
112
+ * systems, including across SDK implementations.
114
113
  *
115
- * This is used for creating replica IDs that maintain time-ordering properties
116
- * while being deterministic across distributed systems.
114
+ * A UUID v7 original keeps its timestamp, so derived ids stay ordered by run
115
+ * start time. Any other original has none to keep, and the field is
116
+ * hash-derived: stable, but not chronological.
117
117
  *
118
118
  * @param originalId - The source UUID string (ideally UUID v7 to preserve timestamp)
119
119
  * @param key - A string key used for deterministic derivation (e.g., project name)
@@ -140,8 +140,6 @@ export function nonCryptographicUuid7Deterministic(originalId, key) {
140
140
  // [8] 2 bits: variant (10) + 6 bits rand_b
141
141
  // [9-15] 56 bits: rand_b (continued)
142
142
  const b = new Uint8Array(16);
143
- // Check if original is UUID v7 - if so, preserve its timestamp
144
- // If not, use current time to ensure the derived UUID has a valid timestamp
145
143
  const version = getUuidVersion(originalId);
146
144
  if (version === 7) {
147
145
  // Preserve timestamp from original UUID7 (bytes 0-5)
@@ -149,16 +147,10 @@ export function nonCryptographicUuid7Deterministic(originalId, key) {
149
147
  b.set(originalBytes.slice(0, 6), 0);
150
148
  }
151
149
  else {
152
- // Generate fresh timestamp for non-UUID7 inputs
153
- // This matches the uuid npm package's v7 implementation:
154
- // https://github.com/uuidjs/uuid/blob/main/src/v7.ts
155
- const msecs = Date.now();
156
- b[0] = (msecs / 0x10000000000) & 0xff;
157
- b[1] = (msecs / 0x100000000) & 0xff;
158
- b[2] = (msecs / 0x1000000) & 0xff;
159
- b[3] = (msecs / 0x10000) & 0xff;
160
- b[4] = (msecs / 0x100) & 0xff;
161
- b[5] = msecs & 0xff;
150
+ // No timestamp to preserve. Use the six hash bytes left unused below: a
151
+ // clock read here would make this impure, and the same id is derived
152
+ // from several runs, all of which must agree.
153
+ b.set(h.slice(10, 16), 0);
162
154
  }
163
155
  // Set version 7 (0111) in high nibble + 4 bits from hash
164
156
  b[6] = 0x70 | (h[0] & 0x0f);
@@ -76,6 +76,22 @@ function getRuntimeEnvironment() {
76
76
  }
77
77
  return runtimeEnvironment;
78
78
  }
79
+ // Substrings marking a variable as secret-bearing or personal; matched
80
+ // case-insensitively against the variable name.
81
+ const EXCLUDED_SUBSTRINGS = [
82
+ "key",
83
+ "secret",
84
+ "token",
85
+ "password",
86
+ "passwd",
87
+ "pwd",
88
+ "credential",
89
+ "email",
90
+ ];
91
+ function isSensitiveEnvVarName(key) {
92
+ const lowered = key.toLowerCase();
93
+ return EXCLUDED_SUBSTRINGS.some((sub) => lowered.includes(sub));
94
+ }
79
95
  /**
80
96
  * Retrieves the LangSmith-specific metadata from the current runtime environment.
81
97
  *
@@ -101,9 +117,7 @@ function getLangSmithEnvVarsMetadata() {
101
117
  for (const [key, value] of Object.entries(allEnvVars)) {
102
118
  if (typeof value === "string" &&
103
119
  !excluded.includes(key) &&
104
- !key.toLowerCase().includes("key") &&
105
- !key.toLowerCase().includes("secret") &&
106
- !key.toLowerCase().includes("token")) {
120
+ !isSensitiveEnvVarName(key)) {
107
121
  if (key === "LANGCHAIN_REVISION_ID") {
108
122
  envVars["revision_id"] = value;
109
123
  }
@@ -131,10 +145,7 @@ function getLangSmithEnvironmentVariables() {
131
145
  for (const [key, value] of Object.entries(process.env)) {
132
146
  if ((key.startsWith("LANGCHAIN_") || key.startsWith("LANGSMITH_")) &&
133
147
  value != null) {
134
- if ((key.toLowerCase().includes("key") ||
135
- key.toLowerCase().includes("secret") ||
136
- key.toLowerCase().includes("token")) &&
137
- typeof value === "string") {
148
+ if (isSensitiveEnvVarName(key) && typeof value === "string") {
138
149
  envVars[key] =
139
150
  value.slice(0, 2) +
140
151
  "*".repeat(value.length - 4) +
package/dist/utils/env.js CHANGED
@@ -58,6 +58,22 @@ export function getRuntimeEnvironment() {
58
58
  }
59
59
  return runtimeEnvironment;
60
60
  }
61
+ // Substrings marking a variable as secret-bearing or personal; matched
62
+ // case-insensitively against the variable name.
63
+ const EXCLUDED_SUBSTRINGS = [
64
+ "key",
65
+ "secret",
66
+ "token",
67
+ "password",
68
+ "passwd",
69
+ "pwd",
70
+ "credential",
71
+ "email",
72
+ ];
73
+ function isSensitiveEnvVarName(key) {
74
+ const lowered = key.toLowerCase();
75
+ return EXCLUDED_SUBSTRINGS.some((sub) => lowered.includes(sub));
76
+ }
61
77
  /**
62
78
  * Retrieves the LangSmith-specific metadata from the current runtime environment.
63
79
  *
@@ -83,9 +99,7 @@ export function getLangSmithEnvVarsMetadata() {
83
99
  for (const [key, value] of Object.entries(allEnvVars)) {
84
100
  if (typeof value === "string" &&
85
101
  !excluded.includes(key) &&
86
- !key.toLowerCase().includes("key") &&
87
- !key.toLowerCase().includes("secret") &&
88
- !key.toLowerCase().includes("token")) {
102
+ !isSensitiveEnvVarName(key)) {
89
103
  if (key === "LANGCHAIN_REVISION_ID") {
90
104
  envVars["revision_id"] = value;
91
105
  }
@@ -113,10 +127,7 @@ export function getLangSmithEnvironmentVariables() {
113
127
  for (const [key, value] of Object.entries(process.env)) {
114
128
  if ((key.startsWith("LANGCHAIN_") || key.startsWith("LANGSMITH_")) &&
115
129
  value != null) {
116
- if ((key.toLowerCase().includes("key") ||
117
- key.toLowerCase().includes("secret") ||
118
- key.toLowerCase().includes("token")) &&
119
- typeof value === "string") {
130
+ if (isSensitiveEnvVarName(key) && typeof value === "string") {
120
131
  envVars[key] =
121
132
  value.slice(0, 2) +
122
133
  "*".repeat(value.length - 4) +
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mask = mask;
4
+ exports.redactOutside = redactOutside;
5
+ exports.overParams = overParams;
6
+ const index_js_1 = require("../anonymizer/index.cjs");
7
+ function isPlainObject(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9
+ }
10
+ /** Mask a value, keeping object key names so a trace shows what was set. */
11
+ function mask(value) {
12
+ if (isPlainObject(value)) {
13
+ return Object.fromEntries(Object.keys(value).map((key) => [key, index_js_1.SECRET_PLACEHOLDER]));
14
+ }
15
+ return index_js_1.SECRET_PLACEHOLDER;
16
+ }
17
+ /** Copy `entry`, masking values outside `safeKeys`, so unknown keys fail closed. */
18
+ function redactOutside(entry, safeKeys) {
19
+ if (!isPlainObject(entry))
20
+ return entry;
21
+ return Object.fromEntries(Object.entries(entry).map(([key, value]) => [
22
+ key,
23
+ safeKeys.has(key) ? value : index_js_1.SECRET_PLACEHOLDER,
24
+ ]));
25
+ }
26
+ /**
27
+ * Apply `redact` to every object argument, since `traceable` shapes a
28
+ * multi-argument call as `{ args: [params, requestOptions] }`.
29
+ */
30
+ function overParams(inputs, redact) {
31
+ if (Array.isArray(inputs?.args)) {
32
+ return {
33
+ ...inputs,
34
+ args: inputs.args.map((arg) => isPlainObject(arg) ? redact(arg) : arg),
35
+ };
36
+ }
37
+ return redact(inputs);
38
+ }
@@ -0,0 +1,10 @@
1
+ import type { KVMap } from "../schemas.js";
2
+ /** Mask a value, keeping object key names so a trace shows what was set. */
3
+ export declare function mask(value: unknown): unknown;
4
+ /** Copy `entry`, masking values outside `safeKeys`, so unknown keys fail closed. */
5
+ export declare function redactOutside(entry: unknown, safeKeys: ReadonlySet<string>): unknown;
6
+ /**
7
+ * Apply `redact` to every object argument, since `traceable` shapes a
8
+ * multi-argument call as `{ args: [params, requestOptions] }`.
9
+ */
10
+ export declare function overParams(inputs: KVMap, redact: (params: KVMap) => KVMap): KVMap;
@@ -0,0 +1,33 @@
1
+ import { SECRET_PLACEHOLDER } from "../anonymizer/index.js";
2
+ function isPlainObject(value) {
3
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4
+ }
5
+ /** Mask a value, keeping object key names so a trace shows what was set. */
6
+ export function mask(value) {
7
+ if (isPlainObject(value)) {
8
+ return Object.fromEntries(Object.keys(value).map((key) => [key, SECRET_PLACEHOLDER]));
9
+ }
10
+ return SECRET_PLACEHOLDER;
11
+ }
12
+ /** Copy `entry`, masking values outside `safeKeys`, so unknown keys fail closed. */
13
+ export function redactOutside(entry, safeKeys) {
14
+ if (!isPlainObject(entry))
15
+ return entry;
16
+ return Object.fromEntries(Object.entries(entry).map(([key, value]) => [
17
+ key,
18
+ safeKeys.has(key) ? value : SECRET_PLACEHOLDER,
19
+ ]));
20
+ }
21
+ /**
22
+ * Apply `redact` to every object argument, since `traceable` shapes a
23
+ * multi-argument call as `{ args: [params, requestOptions] }`.
24
+ */
25
+ export function overParams(inputs, redact) {
26
+ if (Array.isArray(inputs?.args)) {
27
+ return {
28
+ ...inputs,
29
+ args: inputs.args.map((arg) => isPlainObject(arg) ? redact(arg) : arg),
30
+ };
31
+ }
32
+ return redact(inputs);
33
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * Deterministic trace sampling, kept consistent across LangSmith SDKs.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.isSampledById = void 0;
7
+ const xxhash_js_1 = require("./xxhash/xxhash.cjs");
8
+ const SAMPLING_HASH_MODULUS = 1000000n;
9
+ // Reused across calls: constructing a TextEncoder per hash costs more than the
10
+ // hash itself.
11
+ const encoder = new TextEncoder();
12
+ /**
13
+ * Decide whether `identifier` is sampled in at `samplingRate`.
14
+ *
15
+ * The decision is a pure function of the identifier, so every process and
16
+ * every SDK agrees on it, and a run's create and patch never disagree.
17
+ */
18
+ const isSampledById = (identifier, samplingRate) => {
19
+ if (samplingRate === undefined || samplingRate >= 1) {
20
+ return true;
21
+ }
22
+ if (samplingRate <= 0) {
23
+ return false;
24
+ }
25
+ if (identifier == null) {
26
+ return true;
27
+ }
28
+ // XXH3-128 over the UTF-8 bytes, matching the Python SDK's `xxhash.xxh3_128`.
29
+ // The identifier is sampled in when its fraction of the modulus falls below
30
+ // the rate, which is also expressed in [0, 1).
31
+ const bucket = (0, xxhash_js_1.XXH3_128)(encoder.encode(identifier.toLowerCase())) % SAMPLING_HASH_MODULUS;
32
+ return Number(bucket) / Number(SAMPLING_HASH_MODULUS) < samplingRate;
33
+ };
34
+ exports.isSampledById = isSampledById;