langsmith 0.4.10 → 0.4.12
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/dist/client.cjs +5 -2
- package/dist/client.d.ts +5 -1
- package/dist/client.js +5 -2
- package/dist/evaluation/evaluate_comparative.cjs +5 -0
- package/dist/evaluation/evaluate_comparative.js +5 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/run_trees.cjs +7 -6
- package/dist/run_trees.js +8 -7
- package/dist/schemas.d.ts +1 -0
- package/dist/utils/_uuid.cjs +113 -0
- package/dist/utils/_uuid.d.ts +26 -0
- package/dist/utils/_uuid.js +112 -0
- package/dist/utils/xxhash/xxhash.cjs +331 -0
- package/dist/utils/xxhash/xxhash.d.ts +15 -0
- package/dist/utils/xxhash/xxhash.js +327 -0
- package/dist/wrappers/anthropic.cjs +17 -4
- package/dist/wrappers/anthropic.js +17 -4
- package/dist/wrappers/gemini.cjs +16 -5
- package/dist/wrappers/gemini.js +16 -5
- package/dist/wrappers/openai.cjs +27 -12
- package/dist/wrappers/openai.js +27 -12
- package/package.json +14 -1
- package/wrappers/gemini.cjs +1 -0
- package/wrappers/gemini.d.cts +1 -0
- package/wrappers/gemini.d.ts +1 -0
- package/wrappers/gemini.js +1 -0
package/dist/client.cjs
CHANGED
|
@@ -2949,7 +2949,7 @@ class Client {
|
|
|
2949
2949
|
return res;
|
|
2950
2950
|
});
|
|
2951
2951
|
}
|
|
2952
|
-
async createFeedback(runId, key, { score, value, correction, comment, sourceInfo, feedbackSourceType = "api", sourceRunId, feedbackId, feedbackConfig, projectId, comparativeExperimentId, }) {
|
|
2952
|
+
async createFeedback(runId, key, { score, value, correction, comment, sourceInfo, feedbackSourceType = "api", sourceRunId, feedbackId, feedbackConfig, projectId, comparativeExperimentId, sessionId, startTime, }) {
|
|
2953
2953
|
if (!runId && !projectId) {
|
|
2954
2954
|
throw new Error("One of runId or projectId must be provided");
|
|
2955
2955
|
}
|
|
@@ -2980,7 +2980,8 @@ class Client {
|
|
|
2980
2980
|
feedback_source: feedback_source,
|
|
2981
2981
|
comparative_experiment_id: comparativeExperimentId,
|
|
2982
2982
|
feedbackConfig,
|
|
2983
|
-
session_id: projectId,
|
|
2983
|
+
session_id: sessionId ?? projectId,
|
|
2984
|
+
start_time: startTime,
|
|
2984
2985
|
};
|
|
2985
2986
|
const body = JSON.stringify(feedback);
|
|
2986
2987
|
const url = `${this.apiUrl}/feedback`;
|
|
@@ -3201,6 +3202,8 @@ class Client {
|
|
|
3201
3202
|
sourceRunId: res.sourceRunId,
|
|
3202
3203
|
feedbackConfig: res.feedbackConfig,
|
|
3203
3204
|
feedbackSourceType: "model",
|
|
3205
|
+
sessionId: run?.session_id,
|
|
3206
|
+
startTime: run?.start_time,
|
|
3204
3207
|
}));
|
|
3205
3208
|
}
|
|
3206
3209
|
return [evalResults, feedbacks];
|
package/dist/client.d.ts
CHANGED
|
@@ -822,7 +822,7 @@ export declare class Client implements LangSmithTracingClientInterface {
|
|
|
822
822
|
exampleIds: string[];
|
|
823
823
|
remove?: boolean;
|
|
824
824
|
}): Promise<void>;
|
|
825
|
-
createFeedback(runId: string | null, key: string, { score, value, correction, comment, sourceInfo, feedbackSourceType, sourceRunId, feedbackId, feedbackConfig, projectId, comparativeExperimentId, }: {
|
|
825
|
+
createFeedback(runId: string | null, key: string, { score, value, correction, comment, sourceInfo, feedbackSourceType, sourceRunId, feedbackId, feedbackConfig, projectId, comparativeExperimentId, sessionId, startTime, }: {
|
|
826
826
|
score?: ScoreType;
|
|
827
827
|
value?: ValueType;
|
|
828
828
|
correction?: object;
|
|
@@ -835,6 +835,10 @@ export declare class Client implements LangSmithTracingClientInterface {
|
|
|
835
835
|
eager?: boolean;
|
|
836
836
|
projectId?: string;
|
|
837
837
|
comparativeExperimentId?: string;
|
|
838
|
+
/** The session (project) ID of the run this feedback is for. */
|
|
839
|
+
sessionId?: string;
|
|
840
|
+
/** The start time of the run this feedback is for. Accepts ISO string or epoch ms. */
|
|
841
|
+
startTime?: number | string;
|
|
838
842
|
}): Promise<Feedback>;
|
|
839
843
|
updateFeedback(feedbackId: string, { score, value, correction, comment, }: {
|
|
840
844
|
score?: number | boolean | null;
|
package/dist/client.js
CHANGED
|
@@ -2911,7 +2911,7 @@ export class Client {
|
|
|
2911
2911
|
return res;
|
|
2912
2912
|
});
|
|
2913
2913
|
}
|
|
2914
|
-
async createFeedback(runId, key, { score, value, correction, comment, sourceInfo, feedbackSourceType = "api", sourceRunId, feedbackId, feedbackConfig, projectId, comparativeExperimentId, }) {
|
|
2914
|
+
async createFeedback(runId, key, { score, value, correction, comment, sourceInfo, feedbackSourceType = "api", sourceRunId, feedbackId, feedbackConfig, projectId, comparativeExperimentId, sessionId, startTime, }) {
|
|
2915
2915
|
if (!runId && !projectId) {
|
|
2916
2916
|
throw new Error("One of runId or projectId must be provided");
|
|
2917
2917
|
}
|
|
@@ -2942,7 +2942,8 @@ export class Client {
|
|
|
2942
2942
|
feedback_source: feedback_source,
|
|
2943
2943
|
comparative_experiment_id: comparativeExperimentId,
|
|
2944
2944
|
feedbackConfig,
|
|
2945
|
-
session_id: projectId,
|
|
2945
|
+
session_id: sessionId ?? projectId,
|
|
2946
|
+
start_time: startTime,
|
|
2946
2947
|
};
|
|
2947
2948
|
const body = JSON.stringify(feedback);
|
|
2948
2949
|
const url = `${this.apiUrl}/feedback`;
|
|
@@ -3163,6 +3164,8 @@ export class Client {
|
|
|
3163
3164
|
sourceRunId: res.sourceRunId,
|
|
3164
3165
|
feedbackConfig: res.feedbackConfig,
|
|
3165
3166
|
feedbackSourceType: "model",
|
|
3167
|
+
sessionId: run?.session_id,
|
|
3168
|
+
startTime: run?.start_time,
|
|
3166
3169
|
}));
|
|
3167
3170
|
}
|
|
3168
3171
|
return [evalResults, feedbacks];
|
|
@@ -176,15 +176,20 @@ async function evaluateComparative(experiments, options) {
|
|
|
176
176
|
referenceOutputs: example.outputs || {},
|
|
177
177
|
})
|
|
178
178
|
: await evaluator(runs, example);
|
|
179
|
+
// Build a lookup for run metadata
|
|
180
|
+
const runsById = new Map(runs.map((r) => [r.id, r]));
|
|
179
181
|
for (const [runId, score] of Object.entries(result.scores)) {
|
|
180
182
|
// validate if the run id
|
|
181
183
|
if (!expectedRunIds.has(runId)) {
|
|
182
184
|
throw new Error(`Returning an invalid run id ${runId} from evaluator.`);
|
|
183
185
|
}
|
|
186
|
+
const run = runsById.get(runId);
|
|
184
187
|
await client.createFeedback(runId, result.key, {
|
|
185
188
|
score,
|
|
186
189
|
sourceRunId: result.source_run_id,
|
|
187
190
|
comparativeExperimentId: comparativeExperiment.id,
|
|
191
|
+
sessionId: run?.session_id,
|
|
192
|
+
startTime: run?.start_time,
|
|
188
193
|
});
|
|
189
194
|
}
|
|
190
195
|
return result;
|
|
@@ -170,15 +170,20 @@ export async function evaluateComparative(experiments, options) {
|
|
|
170
170
|
referenceOutputs: example.outputs || {},
|
|
171
171
|
})
|
|
172
172
|
: await evaluator(runs, example);
|
|
173
|
+
// Build a lookup for run metadata
|
|
174
|
+
const runsById = new Map(runs.map((r) => [r.id, r]));
|
|
173
175
|
for (const [runId, score] of Object.entries(result.scores)) {
|
|
174
176
|
// validate if the run id
|
|
175
177
|
if (!expectedRunIds.has(runId)) {
|
|
176
178
|
throw new Error(`Returning an invalid run id ${runId} from evaluator.`);
|
|
177
179
|
}
|
|
180
|
+
const run = runsById.get(runId);
|
|
178
181
|
await client.createFeedback(runId, result.key, {
|
|
179
182
|
score,
|
|
180
183
|
sourceRunId: result.source_run_id,
|
|
181
184
|
comparativeExperimentId: comparativeExperiment.id,
|
|
185
|
+
sessionId: run?.session_id,
|
|
186
|
+
startTime: run?.start_time,
|
|
182
187
|
});
|
|
183
188
|
}
|
|
184
189
|
return result;
|
package/dist/index.cjs
CHANGED
|
@@ -15,4 +15,4 @@ Object.defineProperty(exports, "uuid7FromTime", { enumerable: true, get: functio
|
|
|
15
15
|
var prompts_cache_js_1 = require("./utils/prompts_cache.cjs");
|
|
16
16
|
Object.defineProperty(exports, "Cache", { enumerable: true, get: function () { return prompts_cache_js_1.Cache; } });
|
|
17
17
|
// Update using yarn bump-version
|
|
18
|
-
exports.__version__ = "0.4.
|
|
18
|
+
exports.__version__ = "0.4.12";
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export { overrideFetchImplementation } from "./singletons/fetch.js";
|
|
|
5
5
|
export { getDefaultProjectName } from "./utils/project.js";
|
|
6
6
|
export { uuid7, uuid7FromTime } from "./uuid.js";
|
|
7
7
|
export { Cache, type CacheConfig, type CacheMetrics, } from "./utils/prompts_cache.js";
|
|
8
|
-
export declare const __version__ = "0.4.
|
|
8
|
+
export declare const __version__ = "0.4.12";
|
package/dist/index.js
CHANGED
|
@@ -5,4 +5,4 @@ export { getDefaultProjectName } from "./utils/project.js";
|
|
|
5
5
|
export { uuid7, uuid7FromTime } from "./uuid.js";
|
|
6
6
|
export { Cache, } from "./utils/prompts_cache.js";
|
|
7
7
|
// Update using yarn bump-version
|
|
8
|
-
export const __version__ = "0.4.
|
|
8
|
+
export const __version__ = "0.4.12";
|
package/dist/run_trees.cjs
CHANGED
|
@@ -636,14 +636,15 @@ class RunTree {
|
|
|
636
636
|
}
|
|
637
637
|
}
|
|
638
638
|
}
|
|
639
|
-
// Remap IDs for the replica using
|
|
640
|
-
// This ensures consistency across runs in the same replica
|
|
639
|
+
// Remap IDs for the replica using nonCryptographicUuid7Deterministic
|
|
640
|
+
// This ensures consistency across runs in the same replica while
|
|
641
|
+
// preserving UUID7 properties (time-ordering, monotonicity)
|
|
641
642
|
const oldId = baseRun.id;
|
|
642
|
-
const newId = (0,
|
|
643
|
+
const newId = (0, _uuid_js_1.nonCryptographicUuid7Deterministic)(oldId, projectName);
|
|
643
644
|
// Remap trace_id
|
|
644
645
|
let newTraceId;
|
|
645
646
|
if (baseRun.trace_id) {
|
|
646
|
-
newTraceId = (0,
|
|
647
|
+
newTraceId = (0, _uuid_js_1.nonCryptographicUuid7Deterministic)(baseRun.trace_id, projectName);
|
|
647
648
|
}
|
|
648
649
|
else {
|
|
649
650
|
newTraceId = newId;
|
|
@@ -651,7 +652,7 @@ class RunTree {
|
|
|
651
652
|
// Remap parent_run_id
|
|
652
653
|
let newParentId;
|
|
653
654
|
if (baseRun.parent_run_id) {
|
|
654
|
-
newParentId = (0,
|
|
655
|
+
newParentId = (0, _uuid_js_1.nonCryptographicUuid7Deterministic)(baseRun.parent_run_id, projectName);
|
|
655
656
|
}
|
|
656
657
|
// Remap dotted_order segments
|
|
657
658
|
let newDottedOrder;
|
|
@@ -660,7 +661,7 @@ class RunTree {
|
|
|
660
661
|
const remappedSegs = segs.map((seg) => {
|
|
661
662
|
// Extract the UUID from the segment (last TIMESTAMP_LENGTH characters)
|
|
662
663
|
const segId = seg.slice(-TIMESTAMP_LENGTH);
|
|
663
|
-
const remappedId = (0,
|
|
664
|
+
const remappedId = (0, _uuid_js_1.nonCryptographicUuid7Deterministic)(segId, projectName);
|
|
664
665
|
// Replace the UUID part while keeping the timestamp prefix
|
|
665
666
|
return seg.slice(0, -TIMESTAMP_LENGTH) + remappedId;
|
|
666
667
|
});
|
package/dist/run_trees.js
CHANGED
|
@@ -7,7 +7,7 @@ import { getEnvironmentVariable, getRuntimeEnvironment, } from "./utils/env.js";
|
|
|
7
7
|
import { getDefaultProjectName } from "./utils/project.js";
|
|
8
8
|
import { getLangSmithEnvironmentVariable } from "./utils/env.js";
|
|
9
9
|
import { warnOnce } from "./utils/warn.js";
|
|
10
|
-
import { uuid7FromTime } from "./utils/_uuid.js";
|
|
10
|
+
import { uuid7FromTime, nonCryptographicUuid7Deterministic, } from "./utils/_uuid.js";
|
|
11
11
|
import { v5 as uuidv5 } from "uuid";
|
|
12
12
|
const TIMESTAMP_LENGTH = 36;
|
|
13
13
|
// DNS namespace for UUID v5 (same as Python's uuid.NAMESPACE_DNS)
|
|
@@ -630,14 +630,15 @@ export class RunTree {
|
|
|
630
630
|
}
|
|
631
631
|
}
|
|
632
632
|
}
|
|
633
|
-
// Remap IDs for the replica using
|
|
634
|
-
// This ensures consistency across runs in the same replica
|
|
633
|
+
// Remap IDs for the replica using nonCryptographicUuid7Deterministic
|
|
634
|
+
// This ensures consistency across runs in the same replica while
|
|
635
|
+
// preserving UUID7 properties (time-ordering, monotonicity)
|
|
635
636
|
const oldId = baseRun.id;
|
|
636
|
-
const newId =
|
|
637
|
+
const newId = nonCryptographicUuid7Deterministic(oldId, projectName);
|
|
637
638
|
// Remap trace_id
|
|
638
639
|
let newTraceId;
|
|
639
640
|
if (baseRun.trace_id) {
|
|
640
|
-
newTraceId =
|
|
641
|
+
newTraceId = nonCryptographicUuid7Deterministic(baseRun.trace_id, projectName);
|
|
641
642
|
}
|
|
642
643
|
else {
|
|
643
644
|
newTraceId = newId;
|
|
@@ -645,7 +646,7 @@ export class RunTree {
|
|
|
645
646
|
// Remap parent_run_id
|
|
646
647
|
let newParentId;
|
|
647
648
|
if (baseRun.parent_run_id) {
|
|
648
|
-
newParentId =
|
|
649
|
+
newParentId = nonCryptographicUuid7Deterministic(baseRun.parent_run_id, projectName);
|
|
649
650
|
}
|
|
650
651
|
// Remap dotted_order segments
|
|
651
652
|
let newDottedOrder;
|
|
@@ -654,7 +655,7 @@ export class RunTree {
|
|
|
654
655
|
const remappedSegs = segs.map((seg) => {
|
|
655
656
|
// Extract the UUID from the segment (last TIMESTAMP_LENGTH characters)
|
|
656
657
|
const segId = seg.slice(-TIMESTAMP_LENGTH);
|
|
657
|
-
const remappedId =
|
|
658
|
+
const remappedId = nonCryptographicUuid7Deterministic(segId, projectName);
|
|
658
659
|
// Replace the UUID part while keeping the timestamp prefix
|
|
659
660
|
return seg.slice(0, -TIMESTAMP_LENGTH) + remappedId;
|
|
660
661
|
});
|
package/dist/schemas.d.ts
CHANGED
package/dist/utils/_uuid.cjs
CHANGED
|
@@ -4,10 +4,12 @@ exports.assertUuid = assertUuid;
|
|
|
4
4
|
exports.uuid7FromTime = uuid7FromTime;
|
|
5
5
|
exports.getUuidVersion = getUuidVersion;
|
|
6
6
|
exports.warnIfNotUuidV7 = warnIfNotUuidV7;
|
|
7
|
+
exports.nonCryptographicUuid7Deterministic = nonCryptographicUuid7Deterministic;
|
|
7
8
|
// Relaxed UUID validation regex (allows any valid UUID format including nil UUIDs)
|
|
8
9
|
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9
10
|
const uuid_1 = require("uuid");
|
|
10
11
|
const warn_js_1 = require("./warn.cjs");
|
|
12
|
+
const xxhash_js_1 = require("./xxhash/xxhash.cjs");
|
|
11
13
|
let UUID7_WARNING_EMITTED = false;
|
|
12
14
|
function assertUuid(str, which) {
|
|
13
15
|
// Use relaxed regex validation instead of strict uuid.validate()
|
|
@@ -64,3 +66,114 @@ function warnIfNotUuidV7(uuidStr, _idType) {
|
|
|
64
66
|
`Future versions will require UUID v7.`);
|
|
65
67
|
}
|
|
66
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Convert a UUID string to its 16-byte representation.
|
|
71
|
+
* @param uuidStr - The UUID string (with or without dashes)
|
|
72
|
+
* @returns A Uint8Array containing the 16 bytes of the UUID
|
|
73
|
+
*/
|
|
74
|
+
function uuidToBytes(uuidStr) {
|
|
75
|
+
const hex = uuidStr.replace(/-/g, "");
|
|
76
|
+
const bytes = new Uint8Array(16);
|
|
77
|
+
for (let i = 0; i < 16; i++) {
|
|
78
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
79
|
+
}
|
|
80
|
+
return bytes;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Convert 16 bytes to a UUID string.
|
|
84
|
+
* @param bytes - A Uint8Array containing 16 bytes
|
|
85
|
+
* @returns A UUID string in standard format
|
|
86
|
+
*/
|
|
87
|
+
function bytesToUuid(bytes) {
|
|
88
|
+
const hex = Array.from(bytes)
|
|
89
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
90
|
+
.join("");
|
|
91
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
92
|
+
}
|
|
93
|
+
// Reuse TextEncoder instance for performance
|
|
94
|
+
const _textEncoder = new TextEncoder();
|
|
95
|
+
/**
|
|
96
|
+
* Generates a 16-byte fingerprint for deterministic UUID generation using XXH3-128.
|
|
97
|
+
*
|
|
98
|
+
* XXH3 is an extremely fast, non-cryptographic hash function that provides excellent
|
|
99
|
+
* collision resistance. It's widely used in production systems and compatible with
|
|
100
|
+
* xxHash implementations in other languages.
|
|
101
|
+
*
|
|
102
|
+
* See: https://github.com/Cyan4973/xxHash
|
|
103
|
+
*
|
|
104
|
+
* @param str - The input string to hash
|
|
105
|
+
* @returns A Uint8Array containing 16 bytes of hash output
|
|
106
|
+
*/
|
|
107
|
+
function _fastHash128(str) {
|
|
108
|
+
const data = _textEncoder.encode(str);
|
|
109
|
+
// Compute XXH3-128 hash and convert to bytes
|
|
110
|
+
const hash128 = (0, xxhash_js_1.XXH3_128)(data);
|
|
111
|
+
return (0, xxhash_js_1.xxh128ToBytes)(hash128);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Generate a deterministic UUID v7 derived from an original UUID and a key.
|
|
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
|
|
121
|
+
*
|
|
122
|
+
* This is used for creating replica IDs that maintain time-ordering properties
|
|
123
|
+
* while being deterministic across distributed systems.
|
|
124
|
+
*
|
|
125
|
+
* @param originalId - The source UUID string (ideally UUID v7 to preserve timestamp)
|
|
126
|
+
* @param key - A string key used for deterministic derivation (e.g., project name)
|
|
127
|
+
* @returns A new UUID v7 string with preserved timestamp (if original is v7) and
|
|
128
|
+
* deterministic random bits
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* const original = uuidv7();
|
|
133
|
+
* const replicaId = nonCryptographicUuid7Deterministic(original, "replica-project");
|
|
134
|
+
* // Same inputs always produce same output
|
|
135
|
+
* assert(nonCryptographicUuid7Deterministic(original, "replica-project") === replicaId);
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
function nonCryptographicUuid7Deterministic(originalId, key) {
|
|
139
|
+
// Generate deterministic bytes from hash of original + key
|
|
140
|
+
const hashInput = `${originalId}:${key}`;
|
|
141
|
+
const h = _fastHash128(hashInput);
|
|
142
|
+
// Build new UUID7:
|
|
143
|
+
// UUID7 structure (RFC 9562):
|
|
144
|
+
// [0-5] 48 bits: unix_ts_ms (timestamp in milliseconds)
|
|
145
|
+
// [6] 4 bits: version (0111 = 7) + 4 bits rand_a
|
|
146
|
+
// [7] 8 bits: rand_a (continued)
|
|
147
|
+
// [8] 2 bits: variant (10) + 6 bits rand_b
|
|
148
|
+
// [9-15] 56 bits: rand_b (continued)
|
|
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
|
+
const version = getUuidVersion(originalId);
|
|
153
|
+
if (version === 7) {
|
|
154
|
+
// Preserve timestamp from original UUID7 (bytes 0-5)
|
|
155
|
+
const originalBytes = uuidToBytes(originalId);
|
|
156
|
+
b.set(originalBytes.slice(0, 6), 0);
|
|
157
|
+
}
|
|
158
|
+
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;
|
|
169
|
+
}
|
|
170
|
+
// Set version 7 (0111) in high nibble + 4 bits from hash
|
|
171
|
+
b[6] = 0x70 | (h[0] & 0x0f);
|
|
172
|
+
// rand_a continued (8 bits from hash)
|
|
173
|
+
b[7] = h[1];
|
|
174
|
+
// Set variant (10) in high 2 bits + 6 bits from hash
|
|
175
|
+
b[8] = 0x80 | (h[2] & 0x3f);
|
|
176
|
+
// rand_b (56 bits = 7 bytes from hash)
|
|
177
|
+
b.set(h.slice(3, 10), 9);
|
|
178
|
+
return bytesToUuid(b);
|
|
179
|
+
}
|
package/dist/utils/_uuid.d.ts
CHANGED
|
@@ -19,3 +19,29 @@ export declare function getUuidVersion(uuidStr: string): number | null;
|
|
|
19
19
|
* @param idType - The type of ID (e.g., "run_id", "trace_id") for the warning message
|
|
20
20
|
*/
|
|
21
21
|
export declare function warnIfNotUuidV7(uuidStr: string, _idType: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Generate a deterministic UUID v7 derived from an original UUID and a key.
|
|
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
|
|
30
|
+
*
|
|
31
|
+
* This is used for creating replica IDs that maintain time-ordering properties
|
|
32
|
+
* while being deterministic across distributed systems.
|
|
33
|
+
*
|
|
34
|
+
* @param originalId - The source UUID string (ideally UUID v7 to preserve timestamp)
|
|
35
|
+
* @param key - A string key used for deterministic derivation (e.g., project name)
|
|
36
|
+
* @returns A new UUID v7 string with preserved timestamp (if original is v7) and
|
|
37
|
+
* deterministic random bits
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const original = uuidv7();
|
|
42
|
+
* const replicaId = nonCryptographicUuid7Deterministic(original, "replica-project");
|
|
43
|
+
* // Same inputs always produce same output
|
|
44
|
+
* assert(nonCryptographicUuid7Deterministic(original, "replica-project") === replicaId);
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function nonCryptographicUuid7Deterministic(originalId: string, key: string): string;
|
package/dist/utils/_uuid.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
3
|
import { v7 as uuidv7 } from "uuid";
|
|
4
4
|
import { warnOnce } from "./warn.js";
|
|
5
|
+
import { XXH3_128, xxh128ToBytes } from "./xxhash/xxhash.js";
|
|
5
6
|
let UUID7_WARNING_EMITTED = false;
|
|
6
7
|
export function assertUuid(str, which) {
|
|
7
8
|
// Use relaxed regex validation instead of strict uuid.validate()
|
|
@@ -58,3 +59,114 @@ export function warnIfNotUuidV7(uuidStr, _idType) {
|
|
|
58
59
|
`Future versions will require UUID v7.`);
|
|
59
60
|
}
|
|
60
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Convert a UUID string to its 16-byte representation.
|
|
64
|
+
* @param uuidStr - The UUID string (with or without dashes)
|
|
65
|
+
* @returns A Uint8Array containing the 16 bytes of the UUID
|
|
66
|
+
*/
|
|
67
|
+
function uuidToBytes(uuidStr) {
|
|
68
|
+
const hex = uuidStr.replace(/-/g, "");
|
|
69
|
+
const bytes = new Uint8Array(16);
|
|
70
|
+
for (let i = 0; i < 16; i++) {
|
|
71
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
72
|
+
}
|
|
73
|
+
return bytes;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Convert 16 bytes to a UUID string.
|
|
77
|
+
* @param bytes - A Uint8Array containing 16 bytes
|
|
78
|
+
* @returns A UUID string in standard format
|
|
79
|
+
*/
|
|
80
|
+
function bytesToUuid(bytes) {
|
|
81
|
+
const hex = Array.from(bytes)
|
|
82
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
83
|
+
.join("");
|
|
84
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
85
|
+
}
|
|
86
|
+
// Reuse TextEncoder instance for performance
|
|
87
|
+
const _textEncoder = new TextEncoder();
|
|
88
|
+
/**
|
|
89
|
+
* Generates a 16-byte fingerprint for deterministic UUID generation using XXH3-128.
|
|
90
|
+
*
|
|
91
|
+
* XXH3 is an extremely fast, non-cryptographic hash function that provides excellent
|
|
92
|
+
* collision resistance. It's widely used in production systems and compatible with
|
|
93
|
+
* xxHash implementations in other languages.
|
|
94
|
+
*
|
|
95
|
+
* See: https://github.com/Cyan4973/xxHash
|
|
96
|
+
*
|
|
97
|
+
* @param str - The input string to hash
|
|
98
|
+
* @returns A Uint8Array containing 16 bytes of hash output
|
|
99
|
+
*/
|
|
100
|
+
function _fastHash128(str) {
|
|
101
|
+
const data = _textEncoder.encode(str);
|
|
102
|
+
// Compute XXH3-128 hash and convert to bytes
|
|
103
|
+
const hash128 = XXH3_128(data);
|
|
104
|
+
return xxh128ToBytes(hash128);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Generate a deterministic UUID v7 derived from an original UUID and a key.
|
|
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
|
|
114
|
+
*
|
|
115
|
+
* This is used for creating replica IDs that maintain time-ordering properties
|
|
116
|
+
* while being deterministic across distributed systems.
|
|
117
|
+
*
|
|
118
|
+
* @param originalId - The source UUID string (ideally UUID v7 to preserve timestamp)
|
|
119
|
+
* @param key - A string key used for deterministic derivation (e.g., project name)
|
|
120
|
+
* @returns A new UUID v7 string with preserved timestamp (if original is v7) and
|
|
121
|
+
* deterministic random bits
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* const original = uuidv7();
|
|
126
|
+
* const replicaId = nonCryptographicUuid7Deterministic(original, "replica-project");
|
|
127
|
+
* // Same inputs always produce same output
|
|
128
|
+
* assert(nonCryptographicUuid7Deterministic(original, "replica-project") === replicaId);
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export function nonCryptographicUuid7Deterministic(originalId, key) {
|
|
132
|
+
// Generate deterministic bytes from hash of original + key
|
|
133
|
+
const hashInput = `${originalId}:${key}`;
|
|
134
|
+
const h = _fastHash128(hashInput);
|
|
135
|
+
// Build new UUID7:
|
|
136
|
+
// UUID7 structure (RFC 9562):
|
|
137
|
+
// [0-5] 48 bits: unix_ts_ms (timestamp in milliseconds)
|
|
138
|
+
// [6] 4 bits: version (0111 = 7) + 4 bits rand_a
|
|
139
|
+
// [7] 8 bits: rand_a (continued)
|
|
140
|
+
// [8] 2 bits: variant (10) + 6 bits rand_b
|
|
141
|
+
// [9-15] 56 bits: rand_b (continued)
|
|
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
|
+
const version = getUuidVersion(originalId);
|
|
146
|
+
if (version === 7) {
|
|
147
|
+
// Preserve timestamp from original UUID7 (bytes 0-5)
|
|
148
|
+
const originalBytes = uuidToBytes(originalId);
|
|
149
|
+
b.set(originalBytes.slice(0, 6), 0);
|
|
150
|
+
}
|
|
151
|
+
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;
|
|
162
|
+
}
|
|
163
|
+
// Set version 7 (0111) in high nibble + 4 bits from hash
|
|
164
|
+
b[6] = 0x70 | (h[0] & 0x0f);
|
|
165
|
+
// rand_a continued (8 bits from hash)
|
|
166
|
+
b[7] = h[1];
|
|
167
|
+
// Set variant (10) in high 2 bits + 6 bits from hash
|
|
168
|
+
b[8] = 0x80 | (h[2] & 0x3f);
|
|
169
|
+
// rand_b (56 bits = 7 bytes from hash)
|
|
170
|
+
b.set(h.slice(3, 10), 9);
|
|
171
|
+
return bytesToUuid(b);
|
|
172
|
+
}
|