langsmith 0.9.0 → 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 (49) 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/schemas.d.ts +2 -0
  33. package/dist/singletons/constants.cjs +7 -1
  34. package/dist/singletons/constants.d.ts +3 -0
  35. package/dist/singletons/constants.js +5 -0
  36. package/dist/traceable.cjs +46 -15
  37. package/dist/traceable.js +48 -17
  38. package/dist/utils/_uuid.cjs +11 -19
  39. package/dist/utils/_uuid.d.ts +7 -7
  40. package/dist/utils/_uuid.js +11 -19
  41. package/dist/utils/redaction.cjs +38 -0
  42. package/dist/utils/redaction.d.ts +10 -0
  43. package/dist/utils/redaction.js +33 -0
  44. package/dist/utils/sampling.cjs +34 -0
  45. package/dist/utils/sampling.d.ts +10 -0
  46. package/dist/utils/sampling.js +30 -0
  47. package/dist/wrappers/anthropic.cjs +21 -24
  48. package/dist/wrappers/anthropic.js +21 -24
  49. package/package.json +1 -1
@@ -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);
@@ -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;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Deterministic trace sampling, kept consistent across LangSmith SDKs.
3
+ */
4
+ /**
5
+ * Decide whether `identifier` is sampled in at `samplingRate`.
6
+ *
7
+ * The decision is a pure function of the identifier, so every process and
8
+ * every SDK agrees on it, and a run's create and patch never disagree.
9
+ */
10
+ export declare const isSampledById: (identifier: string | null | undefined, samplingRate: number | undefined) => boolean;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Deterministic trace sampling, kept consistent across LangSmith SDKs.
3
+ */
4
+ import { XXH3_128 } from "./xxhash/xxhash.js";
5
+ const SAMPLING_HASH_MODULUS = 1000000n;
6
+ // Reused across calls: constructing a TextEncoder per hash costs more than the
7
+ // hash itself.
8
+ const encoder = new TextEncoder();
9
+ /**
10
+ * Decide whether `identifier` is sampled in at `samplingRate`.
11
+ *
12
+ * The decision is a pure function of the identifier, so every process and
13
+ * every SDK agrees on it, and a run's create and patch never disagree.
14
+ */
15
+ export const isSampledById = (identifier, samplingRate) => {
16
+ if (samplingRate === undefined || samplingRate >= 1) {
17
+ return true;
18
+ }
19
+ if (samplingRate <= 0) {
20
+ return false;
21
+ }
22
+ if (identifier == null) {
23
+ return true;
24
+ }
25
+ // XXH3-128 over the UTF-8 bytes, matching the Python SDK's `xxhash.xxh3_128`.
26
+ // The identifier is sampled in when its fraction of the modulus falls below
27
+ // the rate, which is also expressed in [0, 1).
28
+ const bucket = XXH3_128(encoder.encode(identifier.toLowerCase())) % SAMPLING_HASH_MODULUS;
29
+ return Number(bucket) / Number(SAMPLING_HASH_MODULUS) < samplingRate;
30
+ };
@@ -5,7 +5,7 @@ exports.createUsageMetadata = createUsageMetadata;
5
5
  const anthropic_managed_js_1 = require("./utils/anthropic_managed.cjs");
6
6
  const traceable_js_1 = require("../traceable.cjs");
7
7
  const usage_js_1 = require("../utils/usage.cjs");
8
- const index_js_1 = require("../anonymizer/index.cjs");
8
+ const redaction_js_1 = require("../utils/redaction.cjs");
9
9
  const TRACED_INVOCATION_KEYS = ["top_k", "top_p", "stream", "thinking"];
10
10
  /** Keys of an MCP server definition that are safe to trace. */
11
11
  const MCP_SERVER_SAFE_KEYS = new Set([
@@ -14,27 +14,20 @@ const MCP_SERVER_SAFE_KEYS = new Set([
14
14
  "url",
15
15
  "tool_configuration",
16
16
  ]);
17
- /**
18
- * Mask credentials in `mcp_servers` before tracing. Anything outside
19
- * {@link MCP_SERVER_SAFE_KEYS} keeps its name but gets
20
- * {@link SECRET_PLACEHOLDER} as its value, so the trace shows the field was set
21
- * without exposing it, and fields the API adds later are masked by default.
22
- *
23
- * Returns new objects; the caller's are also sent to Anthropic.
24
- */
17
+ /** Mask `mcp_servers` credentials outside {@link MCP_SERVER_SAFE_KEYS}. */
25
18
  function redactMcpServers(servers) {
26
19
  if (!Array.isArray(servers))
27
20
  return servers;
28
- return servers.map((server) => {
29
- if (typeof server !== "object" || server == null || Array.isArray(server)) {
30
- return server;
31
- }
32
- return Object.fromEntries(Object.entries(server).map(([key, value]) => [
33
- key,
34
- MCP_SERVER_SAFE_KEYS.has(key) ? value : index_js_1.SECRET_PLACEHOLDER,
35
- ]));
36
- });
21
+ return servers.map((server) => (0, redaction_js_1.redactOutside)(server, MCP_SERVER_SAFE_KEYS));
37
22
  }
23
+ /** Request-transport keys that are credentials by construction. */
24
+ const TRANSPORT_SECRET_KEYS = new Set([
25
+ "headers",
26
+ "query",
27
+ "extra_headers",
28
+ "extra_body",
29
+ "extra_query",
30
+ ]);
38
31
  /**
39
32
  * Create usage metadata from Anthropic's token usage format.
40
33
  */
@@ -246,17 +239,18 @@ const wrapAnthropic = (anthropic, options) => {
246
239
  ...options,
247
240
  metadata: restMetadata,
248
241
  };
249
- /**
250
- * Transform system parameter into visible message for playground editability.
251
- * This provides parity with the Python SDK behavior and enables system prompts
252
- * to be viewed and edited in the LangSmith playground.
253
- */
254
- function processTracedInputs(params) {
242
+ /** Mask credentials and surface `system` as a message the playground can edit. */
243
+ function redactAnthropicParams(params) {
255
244
  // Copy first: `params` also goes to the API and must keep its real values.
256
245
  const processed = { ...params };
257
246
  if (processed.mcp_servers != null) {
258
247
  processed.mcp_servers = redactMcpServers(processed.mcp_servers);
259
248
  }
249
+ for (const key of TRANSPORT_SECRET_KEYS) {
250
+ if (processed[key] != null) {
251
+ processed[key] = (0, redaction_js_1.mask)(processed[key]);
252
+ }
253
+ }
260
254
  if (!params.system) {
261
255
  return processed;
262
256
  }
@@ -275,6 +269,9 @@ const wrapAnthropic = (anthropic, options) => {
275
269
  delete processed.system;
276
270
  return processed;
277
271
  }
272
+ function processTracedInputs(inputs) {
273
+ return (0, redaction_js_1.overParams)(inputs, redactAnthropicParams);
274
+ }
278
275
  // Common configuration for messages.create
279
276
  const messagesCreateConfig = {
280
277
  name: "ChatAnthropic",