langsmith 0.7.4 → 0.7.6
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/README.md +41 -0
- package/dist/client.cjs +42 -8
- package/dist/client.d.ts +11 -3
- package/dist/client.js +42 -8
- package/dist/evaluation/_runner.cjs +77 -0
- package/dist/evaluation/_runner.d.ts +4 -0
- package/dist/evaluation/_runner.js +77 -0
- package/dist/evaluation/evaluate_comparative.cjs +1 -1
- package/dist/evaluation/evaluate_comparative.d.ts +7 -1
- package/dist/evaluation/evaluate_comparative.js +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/sandbox/index.cjs +5 -1
- package/dist/sandbox/index.d.ts +2 -1
- package/dist/sandbox/index.js +1 -0
- package/dist/sandbox/proxy_config.cjs +47 -0
- package/dist/sandbox/proxy_config.d.ts +12 -0
- package/dist/sandbox/proxy_config.js +42 -0
- package/dist/sandbox/types.d.ts +23 -1
- package/dist/utils/fs.browser.cjs +11 -0
- package/dist/utils/fs.browser.d.ts +3 -0
- package/dist/utils/fs.browser.js +8 -0
- package/dist/utils/fs.cjs +22 -0
- package/dist/utils/fs.d.ts +3 -0
- package/dist/utils/fs.js +19 -0
- package/dist/utils/profile-lock.cjs +140 -0
- package/dist/utils/profile-lock.d.ts +20 -0
- package/dist/utils/profile-lock.js +103 -0
- package/dist/utils/profiles.cjs +28 -2
- package/dist/utils/profiles.d.ts +1 -0
- package/dist/utils/profiles.js +28 -2
- package/dist/wrappers/gemini.cjs +3 -40
- package/dist/wrappers/gemini.js +3 -40
- package/dist/wrappers/gemini.utils.cjs +41 -0
- package/dist/wrappers/gemini.utils.d.ts +3 -0
- package/dist/wrappers/gemini.utils.js +37 -0
- package/package.json +2 -2
|
@@ -224,5 +224,5 @@ async function evaluateComparative(experiments, options) {
|
|
|
224
224
|
});
|
|
225
225
|
const results = await Promise.all(promises);
|
|
226
226
|
await client.awaitPendingTraceBatches();
|
|
227
|
-
return { experimentName, results };
|
|
227
|
+
return { experimentName, results, url: viewUrl, comparativeExperiment };
|
|
228
228
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Client } from "../index.js";
|
|
2
|
-
import { ComparisonEvaluationResult as ComparisonEvaluationResultRow, Example, Run } from "../schemas.js";
|
|
2
|
+
import { ComparativeExperiment, ComparisonEvaluationResult as ComparisonEvaluationResultRow, Example, Run } from "../schemas.js";
|
|
3
3
|
import { evaluate } from "./index.js";
|
|
4
4
|
type ExperimentResults = Awaited<ReturnType<typeof evaluate>>;
|
|
5
5
|
/** @deprecated Use ComparativeEvaluatorNew instead: (args: { runs, example, inputs, outputs, referenceOutputs }) => ... */
|
|
@@ -54,8 +54,14 @@ export interface EvaluateComparativeOptions {
|
|
|
54
54
|
maxConcurrency?: number;
|
|
55
55
|
}
|
|
56
56
|
export interface ComparisonEvaluationResults {
|
|
57
|
+
/** The name of the comparative experiment. */
|
|
57
58
|
experimentName: string;
|
|
59
|
+
/** The per-example comparison results. */
|
|
58
60
|
results: ComparisonEvaluationResultRow[];
|
|
61
|
+
/** URL of the pairwise comparison view in the LangSmith UI, if available. */
|
|
62
|
+
url: string | null;
|
|
63
|
+
/** The comparative experiment, exposing its id, dataset, and metadata. */
|
|
64
|
+
comparativeExperiment: ComparativeExperiment;
|
|
59
65
|
}
|
|
60
66
|
/** @deprecated Use `evaluate` and pass two experiments as targets. */
|
|
61
67
|
export declare function evaluateComparative(experiments: Array<string> | Array<Promise<ExperimentResults> | ExperimentResults>, options: EvaluateComparativeOptions): Promise<ComparisonEvaluationResults>;
|
|
@@ -218,5 +218,5 @@ export async function evaluateComparative(experiments, options) {
|
|
|
218
218
|
});
|
|
219
219
|
const results = await Promise.all(promises);
|
|
220
220
|
await client.awaitPendingTraceBatches();
|
|
221
|
-
return { experimentName, results };
|
|
221
|
+
return { experimentName, results, url: viewUrl, comparativeExperiment };
|
|
222
222
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.__version__ = exports.promptCacheSingleton = exports.configureGlobalPromptCache = exports.PromptCache = exports.Cache = exports.uuid7FromTime = exports.uuid7 = exports.getDefaultProjectName = exports.overrideFetchImplementation = exports.RunTree = exports.Client = void 0;
|
|
3
|
+
exports.LS_MESSAGE_VIEW_EXCLUDE = exports.__version__ = exports.promptCacheSingleton = exports.configureGlobalPromptCache = exports.PromptCache = exports.Cache = exports.uuid7FromTime = exports.uuid7 = exports.getDefaultProjectName = exports.overrideFetchImplementation = exports.RunTree = exports.Client = void 0;
|
|
4
4
|
var client_js_1 = require("./client.cjs");
|
|
5
5
|
Object.defineProperty(exports, "Client", { enumerable: true, get: function () { return client_js_1.Client; } });
|
|
6
6
|
var run_trees_js_1 = require("./run_trees.cjs");
|
|
@@ -18,4 +18,6 @@ Object.defineProperty(exports, "PromptCache", { enumerable: true, get: function
|
|
|
18
18
|
Object.defineProperty(exports, "configureGlobalPromptCache", { enumerable: true, get: function () { return index_js_1.configureGlobalPromptCache; } });
|
|
19
19
|
Object.defineProperty(exports, "promptCacheSingleton", { enumerable: true, get: function () { return index_js_1.promptCacheSingleton; } });
|
|
20
20
|
// Update using pnpm bump-version
|
|
21
|
-
exports.__version__ = "0.7.
|
|
21
|
+
exports.__version__ = "0.7.6";
|
|
22
|
+
// Metadata key to hide a traced run from LangSmith's Messages View.
|
|
23
|
+
exports.LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ 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, PromptCache, type CacheConfig, type CacheMetrics, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
|
|
8
|
-
export declare const __version__ = "0.7.
|
|
8
|
+
export declare const __version__ = "0.7.6";
|
|
9
|
+
export declare const LS_MESSAGE_VIEW_EXCLUDE: "ls_message_view_exclude";
|
package/dist/index.js
CHANGED
|
@@ -5,4 +5,6 @@ export { getDefaultProjectName } from "./utils/project.js";
|
|
|
5
5
|
export { uuid7, uuid7FromTime } from "./uuid.js";
|
|
6
6
|
export { Cache, PromptCache, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
|
|
7
7
|
// Update using pnpm bump-version
|
|
8
|
-
export const __version__ = "0.7.
|
|
8
|
+
export const __version__ = "0.7.6";
|
|
9
|
+
// Metadata key to hide a traced run from LangSmith's Messages View.
|
|
10
|
+
export const LS_MESSAGE_VIEW_EXCLUDE = "ls_message_view_exclude";
|
package/dist/sandbox/index.cjs
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* @packageDocumentation
|
|
30
30
|
*/
|
|
31
31
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
-
exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxServerReloadError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = exports.CommandHandle = exports.Sandbox = exports.SandboxClient = void 0;
|
|
32
|
+
exports.LangSmithDataplaneNotConfiguredError = exports.LangSmithCommandTimeoutError = exports.LangSmithSandboxOperationError = exports.LangSmithSandboxNotReadyError = exports.LangSmithSandboxCreationError = exports.LangSmithResourceCreationError = exports.LangSmithQuotaExceededError = exports.LangSmithValidationError = exports.LangSmithResourceNameConflictError = exports.LangSmithResourceAlreadyExistsError = exports.LangSmithResourceInUseError = exports.LangSmithResourceTimeoutError = exports.LangSmithResourceNotFoundError = exports.LangSmithSandboxServerReloadError = exports.LangSmithSandboxConnectionError = exports.LangSmithSandboxAuthenticationError = exports.LangSmithSandboxAPIError = exports.LangSmithSandboxError = exports.workspaceSecret = exports.opaqueSecret = exports.awsAuthProxyConfig = exports.CommandHandle = exports.Sandbox = exports.SandboxClient = void 0;
|
|
33
33
|
// Main classes
|
|
34
34
|
var client_js_1 = require("./client.cjs");
|
|
35
35
|
Object.defineProperty(exports, "SandboxClient", { enumerable: true, get: function () { return client_js_1.SandboxClient; } });
|
|
@@ -37,6 +37,10 @@ var sandbox_js_1 = require("./sandbox.cjs");
|
|
|
37
37
|
Object.defineProperty(exports, "Sandbox", { enumerable: true, get: function () { return sandbox_js_1.Sandbox; } });
|
|
38
38
|
var command_handle_js_1 = require("./command_handle.cjs");
|
|
39
39
|
Object.defineProperty(exports, "CommandHandle", { enumerable: true, get: function () { return command_handle_js_1.CommandHandle; } });
|
|
40
|
+
var proxy_config_js_1 = require("./proxy_config.cjs");
|
|
41
|
+
Object.defineProperty(exports, "awsAuthProxyConfig", { enumerable: true, get: function () { return proxy_config_js_1.awsAuthProxyConfig; } });
|
|
42
|
+
Object.defineProperty(exports, "opaqueSecret", { enumerable: true, get: function () { return proxy_config_js_1.opaqueSecret; } });
|
|
43
|
+
Object.defineProperty(exports, "workspaceSecret", { enumerable: true, get: function () { return proxy_config_js_1.workspaceSecret; } });
|
|
40
44
|
// Errors
|
|
41
45
|
var errors_js_1 = require("./errors.cjs");
|
|
42
46
|
// Base and connection errors
|
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -30,5 +30,6 @@
|
|
|
30
30
|
export { SandboxClient } from "./client.js";
|
|
31
31
|
export { Sandbox } from "./sandbox.js";
|
|
32
32
|
export { CommandHandle } from "./command_handle.js";
|
|
33
|
-
export
|
|
33
|
+
export { awsAuthProxyConfig, opaqueSecret, workspaceSecret, } from "./proxy_config.js";
|
|
34
|
+
export type { ExecutionResult, OutputChunk, WsMessage, WsRunOptions, ResourceStatus, Snapshot, SandboxData, SandboxClientConfig, RunOptions, CreateSandboxOptions, SandboxAccessControl, SandboxAwsAuthRule, SandboxProxyConfig, SandboxProxySecret, CreateSnapshotOptions, CreateDockerfileSnapshotOptions, CaptureSnapshotOptions, ListSnapshotsOptions, WaitForSnapshotOptions, StartSandboxOptions, UpdateSandboxOptions, WaitForSandboxOptions, } from "./types.js";
|
|
34
35
|
export { LangSmithSandboxError, LangSmithSandboxAPIError, LangSmithSandboxAuthenticationError, LangSmithSandboxConnectionError, LangSmithSandboxServerReloadError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithResourceInUseError, LangSmithResourceAlreadyExistsError, LangSmithResourceNameConflictError, LangSmithValidationError, LangSmithQuotaExceededError, LangSmithResourceCreationError, LangSmithSandboxCreationError, LangSmithSandboxNotReadyError, LangSmithSandboxOperationError, LangSmithCommandTimeoutError, LangSmithDataplaneNotConfiguredError, } from "./errors.js";
|
package/dist/sandbox/index.js
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
export { SandboxClient } from "./client.js";
|
|
32
32
|
export { Sandbox } from "./sandbox.js";
|
|
33
33
|
export { CommandHandle } from "./command_handle.js";
|
|
34
|
+
export { awsAuthProxyConfig, opaqueSecret, workspaceSecret, } from "./proxy_config.js";
|
|
34
35
|
// Errors
|
|
35
36
|
export {
|
|
36
37
|
// Base and connection errors
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.workspaceSecret = workspaceSecret;
|
|
4
|
+
exports.opaqueSecret = opaqueSecret;
|
|
5
|
+
exports.awsAuthProxyConfig = awsAuthProxyConfig;
|
|
6
|
+
function requireNonEmptyString(value, field) {
|
|
7
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
8
|
+
throw new Error(`${field} must be a non-empty string`);
|
|
9
|
+
}
|
|
10
|
+
return value.trim();
|
|
11
|
+
}
|
|
12
|
+
/** Reference a LangSmith workspace secret in a sandbox proxy configuration. */
|
|
13
|
+
function workspaceSecret(name) {
|
|
14
|
+
const normalized = requireNonEmptyString(name, "name");
|
|
15
|
+
const startsWithBrace = normalized.startsWith("{");
|
|
16
|
+
const endsWithBrace = normalized.endsWith("}");
|
|
17
|
+
if (startsWithBrace !== endsWithBrace) {
|
|
18
|
+
throw new Error("workspace secret must be a name or a {NAME} reference");
|
|
19
|
+
}
|
|
20
|
+
if (startsWithBrace && normalized.slice(1, -1).trim() === "") {
|
|
21
|
+
throw new Error("workspace secret reference must contain a name");
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
type: "workspace_secret",
|
|
25
|
+
value: startsWithBrace ? normalized : `{${normalized}}`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Provide a write-only secret value for a sandbox proxy configuration. */
|
|
29
|
+
function opaqueSecret(value) {
|
|
30
|
+
return {
|
|
31
|
+
type: "opaque",
|
|
32
|
+
value: requireNonEmptyString(value, "value"),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Build a sandbox proxy config that signs AWS HTTPS requests with SigV4. */
|
|
36
|
+
function awsAuthProxyConfig({ accessKeyId, secretAccessKey, name = "aws", enabled = true, }) {
|
|
37
|
+
const rule = {
|
|
38
|
+
name: requireNonEmptyString(name, "name"),
|
|
39
|
+
type: "aws",
|
|
40
|
+
enabled,
|
|
41
|
+
aws: {
|
|
42
|
+
access_key_id: accessKeyId,
|
|
43
|
+
secret_access_key: secretAccessKey,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
return { rules: [rule] };
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SandboxProxyConfig, SandboxProxySecret } from "./types.js";
|
|
2
|
+
/** Reference a LangSmith workspace secret in a sandbox proxy configuration. */
|
|
3
|
+
export declare function workspaceSecret(name: string): SandboxProxySecret;
|
|
4
|
+
/** Provide a write-only secret value for a sandbox proxy configuration. */
|
|
5
|
+
export declare function opaqueSecret(value: string): SandboxProxySecret;
|
|
6
|
+
/** Build a sandbox proxy config that signs AWS HTTPS requests with SigV4. */
|
|
7
|
+
export declare function awsAuthProxyConfig({ accessKeyId, secretAccessKey, name, enabled, }: {
|
|
8
|
+
accessKeyId: SandboxProxySecret;
|
|
9
|
+
secretAccessKey: SandboxProxySecret;
|
|
10
|
+
name?: string;
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
}): SandboxProxyConfig;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
function requireNonEmptyString(value, field) {
|
|
2
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
3
|
+
throw new Error(`${field} must be a non-empty string`);
|
|
4
|
+
}
|
|
5
|
+
return value.trim();
|
|
6
|
+
}
|
|
7
|
+
/** Reference a LangSmith workspace secret in a sandbox proxy configuration. */
|
|
8
|
+
export function workspaceSecret(name) {
|
|
9
|
+
const normalized = requireNonEmptyString(name, "name");
|
|
10
|
+
const startsWithBrace = normalized.startsWith("{");
|
|
11
|
+
const endsWithBrace = normalized.endsWith("}");
|
|
12
|
+
if (startsWithBrace !== endsWithBrace) {
|
|
13
|
+
throw new Error("workspace secret must be a name or a {NAME} reference");
|
|
14
|
+
}
|
|
15
|
+
if (startsWithBrace && normalized.slice(1, -1).trim() === "") {
|
|
16
|
+
throw new Error("workspace secret reference must contain a name");
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
type: "workspace_secret",
|
|
20
|
+
value: startsWithBrace ? normalized : `{${normalized}}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Provide a write-only secret value for a sandbox proxy configuration. */
|
|
24
|
+
export function opaqueSecret(value) {
|
|
25
|
+
return {
|
|
26
|
+
type: "opaque",
|
|
27
|
+
value: requireNonEmptyString(value, "value"),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Build a sandbox proxy config that signs AWS HTTPS requests with SigV4. */
|
|
31
|
+
export function awsAuthProxyConfig({ accessKeyId, secretAccessKey, name = "aws", enabled = true, }) {
|
|
32
|
+
const rule = {
|
|
33
|
+
name: requireNonEmptyString(name, "name"),
|
|
34
|
+
type: "aws",
|
|
35
|
+
enabled,
|
|
36
|
+
aws: {
|
|
37
|
+
access_key_id: accessKeyId,
|
|
38
|
+
secret_access_key: secretAccessKey,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
return { rules: [rule] };
|
|
42
|
+
}
|
package/dist/sandbox/types.d.ts
CHANGED
|
@@ -249,6 +249,27 @@ export interface SandboxAccessControl {
|
|
|
249
249
|
/** Hosts the sandbox is blocked from reaching. */
|
|
250
250
|
deny_list?: string[];
|
|
251
251
|
}
|
|
252
|
+
/** Secret value reference for sandbox proxy rules. */
|
|
253
|
+
export interface SandboxProxySecret {
|
|
254
|
+
/** `workspace_secret` references a workspace secret; `opaque` is write-only. */
|
|
255
|
+
type: "workspace_secret" | "opaque";
|
|
256
|
+
/** Workspace secret reference or opaque secret value. */
|
|
257
|
+
value: string;
|
|
258
|
+
}
|
|
259
|
+
/** AWS auth rule for sandbox proxy SigV4 signing. */
|
|
260
|
+
export interface SandboxAwsAuthRule {
|
|
261
|
+
/** Rule name. */
|
|
262
|
+
name: string;
|
|
263
|
+
/** AWS auth rules are matched by the sandbox proxy's AWS endpoint matcher. */
|
|
264
|
+
type: "aws";
|
|
265
|
+
/** Whether the rule is enabled. */
|
|
266
|
+
enabled?: boolean;
|
|
267
|
+
/** AWS credentials used by the proxy signer. */
|
|
268
|
+
aws: {
|
|
269
|
+
access_key_id: SandboxProxySecret;
|
|
270
|
+
secret_access_key: SandboxProxySecret;
|
|
271
|
+
};
|
|
272
|
+
}
|
|
252
273
|
/**
|
|
253
274
|
* Full proxy configuration forwarded to the sandbox server as-is (snake_case
|
|
254
275
|
* so it's wire-compatible with the backend). Mirrors the server's
|
|
@@ -312,7 +333,8 @@ export interface CreateSandboxOptions {
|
|
|
312
333
|
* Per-sandbox proxy configuration. Use
|
|
313
334
|
* `{ access_control: { allow_list: ["github.com", "*.example.com"] } }`
|
|
314
335
|
* to restrict outbound HTTPS to a set of host patterns. Forwarded to the
|
|
315
|
-
* server as-is on the wire.
|
|
336
|
+
* server as-is on the wire. Use `awsAuthProxyConfig` to let the proxy sign
|
|
337
|
+
* supported AWS HTTPS requests on the sandbox's behalf.
|
|
316
338
|
*/
|
|
317
339
|
proxyConfig?: SandboxProxyConfig;
|
|
318
340
|
}
|
|
@@ -20,6 +20,9 @@ exports.writeFileSync = writeFileSync;
|
|
|
20
20
|
exports.renameSync = renameSync;
|
|
21
21
|
exports.unlinkSync = unlinkSync;
|
|
22
22
|
exports.readFileSync = readFileSync;
|
|
23
|
+
exports.mkdirExclusive = mkdirExclusive;
|
|
24
|
+
exports.statMtimeMs = statMtimeMs;
|
|
25
|
+
exports.rmRecursive = rmRecursive;
|
|
23
26
|
exports.path = {
|
|
24
27
|
join: (...parts) => parts.join("/"),
|
|
25
28
|
dirname: (p) => p.split("/").slice(0, -1).join("/"),
|
|
@@ -49,3 +52,11 @@ function unlinkSync(_filePath) { }
|
|
|
49
52
|
function readFileSync(_filePath) {
|
|
50
53
|
return "";
|
|
51
54
|
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Lock primitives – no-op / safe defaults in browser
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
async function mkdirExclusive(_dir) { }
|
|
59
|
+
function statMtimeMs(_filePath) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
async function rmRecursive(_filePath) { }
|
|
@@ -23,3 +23,6 @@ export declare function writeFileSync(_filePath: string, _content: string): void
|
|
|
23
23
|
export declare function renameSync(_oldPath: string, _newPath: string): void;
|
|
24
24
|
export declare function unlinkSync(_filePath: string): void;
|
|
25
25
|
export declare function readFileSync(_filePath: string): string;
|
|
26
|
+
export declare function mkdirExclusive(_dir: string): Promise<void>;
|
|
27
|
+
export declare function statMtimeMs(_filePath: string): number | undefined;
|
|
28
|
+
export declare function rmRecursive(_filePath: string): Promise<void>;
|
package/dist/utils/fs.browser.js
CHANGED
|
@@ -35,3 +35,11 @@ export function unlinkSync(_filePath) { }
|
|
|
35
35
|
export function readFileSync(_filePath) {
|
|
36
36
|
return "";
|
|
37
37
|
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Lock primitives – no-op / safe defaults in browser
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
export async function mkdirExclusive(_dir) { }
|
|
42
|
+
export function statMtimeMs(_filePath) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
export async function rmRecursive(_filePath) { }
|
package/dist/utils/fs.cjs
CHANGED
|
@@ -51,6 +51,9 @@ exports.writeFileSync = writeFileSync;
|
|
|
51
51
|
exports.renameSync = renameSync;
|
|
52
52
|
exports.unlinkSync = unlinkSync;
|
|
53
53
|
exports.readFileSync = readFileSync;
|
|
54
|
+
exports.mkdirExclusive = mkdirExclusive;
|
|
55
|
+
exports.statMtimeMs = statMtimeMs;
|
|
56
|
+
exports.rmRecursive = rmRecursive;
|
|
54
57
|
const nodeFs = __importStar(require("node:fs"));
|
|
55
58
|
const nodeFsPromises = __importStar(require("node:fs/promises"));
|
|
56
59
|
const nodePath = __importStar(require("node:path"));
|
|
@@ -99,3 +102,22 @@ function unlinkSync(filePath) {
|
|
|
99
102
|
function readFileSync(filePath) {
|
|
100
103
|
return nodeFs.readFileSync(filePath, "utf-8");
|
|
101
104
|
}
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Lock primitives (used by the OAuth refresh directory lock)
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
async function mkdirExclusive(dir) {
|
|
109
|
+
// Non-recursive mkdir throws EEXIST if the directory already exists, which
|
|
110
|
+
// is the atomic test-and-set the cross-process lock relies on.
|
|
111
|
+
await nodeFsPromises.mkdir(dir, { mode: 0o700 });
|
|
112
|
+
}
|
|
113
|
+
function statMtimeMs(filePath) {
|
|
114
|
+
try {
|
|
115
|
+
return nodeFs.statSync(filePath).mtimeMs;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function rmRecursive(filePath) {
|
|
122
|
+
await nodeFsPromises.rm(filePath, { recursive: true, force: true });
|
|
123
|
+
}
|
package/dist/utils/fs.d.ts
CHANGED
|
@@ -19,3 +19,6 @@ export declare function writeFileSync(filePath: string, content: string): void;
|
|
|
19
19
|
export declare function renameSync(oldPath: string, newPath: string): void;
|
|
20
20
|
export declare function unlinkSync(filePath: string): void;
|
|
21
21
|
export declare function readFileSync(filePath: string): string;
|
|
22
|
+
export declare function mkdirExclusive(dir: string): Promise<void>;
|
|
23
|
+
export declare function statMtimeMs(filePath: string): number | undefined;
|
|
24
|
+
export declare function rmRecursive(filePath: string): Promise<void>;
|
package/dist/utils/fs.js
CHANGED
|
@@ -52,3 +52,22 @@ export function unlinkSync(filePath) {
|
|
|
52
52
|
export function readFileSync(filePath) {
|
|
53
53
|
return nodeFs.readFileSync(filePath, "utf-8");
|
|
54
54
|
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Lock primitives (used by the OAuth refresh directory lock)
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
export async function mkdirExclusive(dir) {
|
|
59
|
+
// Non-recursive mkdir throws EEXIST if the directory already exists, which
|
|
60
|
+
// is the atomic test-and-set the cross-process lock relies on.
|
|
61
|
+
await nodeFsPromises.mkdir(dir, { mode: 0o700 });
|
|
62
|
+
}
|
|
63
|
+
export function statMtimeMs(filePath) {
|
|
64
|
+
try {
|
|
65
|
+
return nodeFs.statSync(filePath).mtimeMs;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function rmRecursive(filePath) {
|
|
72
|
+
await nodeFsPromises.rm(filePath, { recursive: true, force: true });
|
|
73
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports._internal = void 0;
|
|
37
|
+
exports.acquireOAuthRefreshLock = acquireOAuthRefreshLock;
|
|
38
|
+
const fsUtils = __importStar(require("./fs.cjs"));
|
|
39
|
+
const LOCK_POLL_INTERVAL_MS = 10;
|
|
40
|
+
const LOCK_STALE_AFTER_MS = 10_000;
|
|
41
|
+
const LOCK_METADATA_FILE = "created_at";
|
|
42
|
+
function sleep(ms) {
|
|
43
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
44
|
+
}
|
|
45
|
+
function isEEXIST(err) {
|
|
46
|
+
return (typeof err === "object" &&
|
|
47
|
+
err !== null &&
|
|
48
|
+
err.code === "EEXIST");
|
|
49
|
+
}
|
|
50
|
+
function lockMetadataLines(lockDir) {
|
|
51
|
+
try {
|
|
52
|
+
return fsUtils
|
|
53
|
+
.readFileSync(fsUtils.path.join(lockDir, LOCK_METADATA_FILE))
|
|
54
|
+
.split("\n");
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function lockCreatedAtMs(lockDir) {
|
|
61
|
+
const lines = lockMetadataLines(lockDir);
|
|
62
|
+
if (lines && lines[0] && lines[0].trim()) {
|
|
63
|
+
const parsed = Date.parse(lines[0].trim());
|
|
64
|
+
if (!Number.isNaN(parsed)) {
|
|
65
|
+
return parsed;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return fsUtils.statMtimeMs(lockDir);
|
|
69
|
+
}
|
|
70
|
+
function lockOwner(lockDir) {
|
|
71
|
+
const lines = lockMetadataLines(lockDir);
|
|
72
|
+
if (lines && lines.length >= 2 && lines[1].trim()) {
|
|
73
|
+
return lines[1].trim();
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
async function removeStaleLock(lockDir) {
|
|
78
|
+
const createdAt = lockCreatedAtMs(lockDir);
|
|
79
|
+
if (createdAt === undefined ||
|
|
80
|
+
Date.now() - createdAt <= LOCK_STALE_AFTER_MS) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
await fsUtils.rmRecursive(lockDir);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Acquire an exclusive cross-process lock for refreshing OAuth tokens.
|
|
88
|
+
*
|
|
89
|
+
* Uses an atomic-`mkdir` directory lock at `<configPath>.oauth.lock.lock` with a
|
|
90
|
+
* stale-break heuristic and owner-checked release, mirroring langsmith-go's
|
|
91
|
+
* non-POSIX path. `deadline` is a `Date.now()`-based timestamp; acquisition
|
|
92
|
+
* rejects once it passes. Callers treat any rejection as "skip refresh, use the
|
|
93
|
+
* current token".
|
|
94
|
+
*/
|
|
95
|
+
async function acquireOAuthRefreshLock(configPath, deadline) {
|
|
96
|
+
const lockDir = `${configPath}.oauth.lock.lock`;
|
|
97
|
+
const parent = fsUtils.path.dirname(lockDir);
|
|
98
|
+
if (parent) {
|
|
99
|
+
await fsUtils.mkdir(parent);
|
|
100
|
+
}
|
|
101
|
+
const owner = globalThis.crypto.randomUUID();
|
|
102
|
+
for (;;) {
|
|
103
|
+
try {
|
|
104
|
+
await fsUtils.mkdirExclusive(lockDir);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
if (!isEEXIST(err)) {
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
if (!(await removeStaleLock(lockDir))) {
|
|
111
|
+
if (Date.now() >= deadline) {
|
|
112
|
+
throw new Error("timed out acquiring OAuth refresh lock");
|
|
113
|
+
}
|
|
114
|
+
await sleep(Math.min(LOCK_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
|
|
115
|
+
}
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
await fsUtils.writeFileAtomic(fsUtils.path.join(lockDir, LOCK_METADATA_FILE), `${new Date().toISOString()}\n${owner}\n`);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
await fsUtils.rmRecursive(lockDir);
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
async release() {
|
|
129
|
+
if (lockOwner(lockDir) === owner) {
|
|
130
|
+
await fsUtils.rmRecursive(lockDir);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// Exposed for tests only.
|
|
136
|
+
exports._internal = {
|
|
137
|
+
LOCK_METADATA_FILE,
|
|
138
|
+
LOCK_STALE_AFTER_MS,
|
|
139
|
+
lockOwner,
|
|
140
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface OAuthRefreshLock {
|
|
2
|
+
release(): Promise<void>;
|
|
3
|
+
}
|
|
4
|
+
declare function lockOwner(lockDir: string): string | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Acquire an exclusive cross-process lock for refreshing OAuth tokens.
|
|
7
|
+
*
|
|
8
|
+
* Uses an atomic-`mkdir` directory lock at `<configPath>.oauth.lock.lock` with a
|
|
9
|
+
* stale-break heuristic and owner-checked release, mirroring langsmith-go's
|
|
10
|
+
* non-POSIX path. `deadline` is a `Date.now()`-based timestamp; acquisition
|
|
11
|
+
* rejects once it passes. Callers treat any rejection as "skip refresh, use the
|
|
12
|
+
* current token".
|
|
13
|
+
*/
|
|
14
|
+
export declare function acquireOAuthRefreshLock(configPath: string, deadline: number): Promise<OAuthRefreshLock>;
|
|
15
|
+
export declare const _internal: {
|
|
16
|
+
LOCK_METADATA_FILE: string;
|
|
17
|
+
LOCK_STALE_AFTER_MS: number;
|
|
18
|
+
lockOwner: typeof lockOwner;
|
|
19
|
+
};
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import * as fsUtils from "./fs.js";
|
|
2
|
+
const LOCK_POLL_INTERVAL_MS = 10;
|
|
3
|
+
const LOCK_STALE_AFTER_MS = 10_000;
|
|
4
|
+
const LOCK_METADATA_FILE = "created_at";
|
|
5
|
+
function sleep(ms) {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
function isEEXIST(err) {
|
|
9
|
+
return (typeof err === "object" &&
|
|
10
|
+
err !== null &&
|
|
11
|
+
err.code === "EEXIST");
|
|
12
|
+
}
|
|
13
|
+
function lockMetadataLines(lockDir) {
|
|
14
|
+
try {
|
|
15
|
+
return fsUtils
|
|
16
|
+
.readFileSync(fsUtils.path.join(lockDir, LOCK_METADATA_FILE))
|
|
17
|
+
.split("\n");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function lockCreatedAtMs(lockDir) {
|
|
24
|
+
const lines = lockMetadataLines(lockDir);
|
|
25
|
+
if (lines && lines[0] && lines[0].trim()) {
|
|
26
|
+
const parsed = Date.parse(lines[0].trim());
|
|
27
|
+
if (!Number.isNaN(parsed)) {
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return fsUtils.statMtimeMs(lockDir);
|
|
32
|
+
}
|
|
33
|
+
function lockOwner(lockDir) {
|
|
34
|
+
const lines = lockMetadataLines(lockDir);
|
|
35
|
+
if (lines && lines.length >= 2 && lines[1].trim()) {
|
|
36
|
+
return lines[1].trim();
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
async function removeStaleLock(lockDir) {
|
|
41
|
+
const createdAt = lockCreatedAtMs(lockDir);
|
|
42
|
+
if (createdAt === undefined ||
|
|
43
|
+
Date.now() - createdAt <= LOCK_STALE_AFTER_MS) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
await fsUtils.rmRecursive(lockDir);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Acquire an exclusive cross-process lock for refreshing OAuth tokens.
|
|
51
|
+
*
|
|
52
|
+
* Uses an atomic-`mkdir` directory lock at `<configPath>.oauth.lock.lock` with a
|
|
53
|
+
* stale-break heuristic and owner-checked release, mirroring langsmith-go's
|
|
54
|
+
* non-POSIX path. `deadline` is a `Date.now()`-based timestamp; acquisition
|
|
55
|
+
* rejects once it passes. Callers treat any rejection as "skip refresh, use the
|
|
56
|
+
* current token".
|
|
57
|
+
*/
|
|
58
|
+
export async function acquireOAuthRefreshLock(configPath, deadline) {
|
|
59
|
+
const lockDir = `${configPath}.oauth.lock.lock`;
|
|
60
|
+
const parent = fsUtils.path.dirname(lockDir);
|
|
61
|
+
if (parent) {
|
|
62
|
+
await fsUtils.mkdir(parent);
|
|
63
|
+
}
|
|
64
|
+
const owner = globalThis.crypto.randomUUID();
|
|
65
|
+
for (;;) {
|
|
66
|
+
try {
|
|
67
|
+
await fsUtils.mkdirExclusive(lockDir);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
if (!isEEXIST(err)) {
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
if (!(await removeStaleLock(lockDir))) {
|
|
74
|
+
if (Date.now() >= deadline) {
|
|
75
|
+
throw new Error("timed out acquiring OAuth refresh lock");
|
|
76
|
+
}
|
|
77
|
+
await sleep(Math.min(LOCK_POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
await fsUtils.writeFileAtomic(fsUtils.path.join(lockDir, LOCK_METADATA_FILE), `${new Date().toISOString()}\n${owner}\n`);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
await fsUtils.rmRecursive(lockDir);
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
async release() {
|
|
92
|
+
if (lockOwner(lockDir) === owner) {
|
|
93
|
+
await fsUtils.rmRecursive(lockDir);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Exposed for tests only.
|
|
99
|
+
export const _internal = {
|
|
100
|
+
LOCK_METADATA_FILE,
|
|
101
|
+
LOCK_STALE_AFTER_MS,
|
|
102
|
+
lockOwner,
|
|
103
|
+
};
|