langsmith 0.7.5 → 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 +13 -4
- package/dist/client.d.ts +4 -1
- package/dist/client.js +13 -4
- package/dist/evaluation/_runner.cjs +77 -0
- package/dist/evaluation/_runner.d.ts +4 -0
- package/dist/evaluation/_runner.js +77 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -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
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
|
+
};
|
package/dist/utils/profiles.cjs
CHANGED
|
@@ -38,6 +38,7 @@ exports.hasValue = hasValue;
|
|
|
38
38
|
exports.loadProfileClientConfig = loadProfileClientConfig;
|
|
39
39
|
const env_js_1 = require("./env.cjs");
|
|
40
40
|
const fsUtils = __importStar(require("./fs.cjs"));
|
|
41
|
+
const profile_lock_js_1 = require("./profile-lock.cjs");
|
|
41
42
|
exports.DEFAULT_API_URL = "https://api.smith.langchain.com";
|
|
42
43
|
const OAUTH_CLIENT_ID = "langsmith-cli";
|
|
43
44
|
const TOKEN_REFRESH_LEEWAY_MS = 60_000;
|
|
@@ -227,17 +228,39 @@ class ProfileAuth {
|
|
|
227
228
|
isProfileAuthorizationHeader(value) {
|
|
228
229
|
return value === this.managedAuthorizationValue;
|
|
229
230
|
}
|
|
231
|
+
reloadProfile() {
|
|
232
|
+
try {
|
|
233
|
+
const config = JSON.parse(fsUtils.readFileSync(this.state.configPath));
|
|
234
|
+
const profile = config.profiles?.[this.state.profileName];
|
|
235
|
+
if (!profile) {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
this.state.config = config;
|
|
239
|
+
this.state.profile = profile;
|
|
240
|
+
return profile;
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
230
246
|
async refreshOAuthToken(fetchImplementation) {
|
|
231
247
|
const refreshToken = this.state.profile.oauth?.refresh_token;
|
|
232
248
|
if (!refreshToken) {
|
|
233
249
|
return;
|
|
234
250
|
}
|
|
235
251
|
const refreshApiUrl = trimConfigValue(this.state.profile.api_url) ?? exports.DEFAULT_API_URL;
|
|
252
|
+
const deadline = Date.now() + TOKEN_REFRESH_TIMEOUT_MS;
|
|
253
|
+
let lock;
|
|
236
254
|
try {
|
|
255
|
+
lock = await (0, profile_lock_js_1.acquireOAuthRefreshLock)(this.state.configPath, deadline);
|
|
256
|
+
const fresh = this.reloadProfile();
|
|
257
|
+
if (fresh && !shouldRefreshProfileToken(this.state.profile)) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
237
260
|
const body = new URLSearchParams({
|
|
238
261
|
grant_type: "refresh_token",
|
|
239
262
|
client_id: OAUTH_CLIENT_ID,
|
|
240
|
-
refresh_token: refreshToken,
|
|
263
|
+
refresh_token: this.state.profile.oauth?.refresh_token ?? refreshToken,
|
|
241
264
|
});
|
|
242
265
|
const response = await fetchImplementation(`${normalizeConfigUrl(refreshApiUrl)}/oauth/token`, {
|
|
243
266
|
method: "POST",
|
|
@@ -245,7 +268,7 @@ class ProfileAuth {
|
|
|
245
268
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
246
269
|
},
|
|
247
270
|
body: body.toString(),
|
|
248
|
-
signal: AbortSignal.timeout(
|
|
271
|
+
signal: AbortSignal.timeout(Math.max(0, deadline - Date.now())),
|
|
249
272
|
});
|
|
250
273
|
if (!response.ok) {
|
|
251
274
|
return;
|
|
@@ -262,6 +285,9 @@ class ProfileAuth {
|
|
|
262
285
|
catch {
|
|
263
286
|
return;
|
|
264
287
|
}
|
|
288
|
+
finally {
|
|
289
|
+
await lock?.release();
|
|
290
|
+
}
|
|
265
291
|
}
|
|
266
292
|
rememberProfileAuthHeader(header) {
|
|
267
293
|
this.managedAuthorizationValue =
|
package/dist/utils/profiles.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export declare class ProfileAuth {
|
|
|
42
42
|
currentAuthHeader(): ProfileAuthHeader | undefined;
|
|
43
43
|
getAuthHeader(fetchImplementation: typeof fetch, signal?: AbortSignal | null): Promise<ProfileAuthHeader | undefined>;
|
|
44
44
|
isProfileAuthorizationHeader(value: string): boolean;
|
|
45
|
+
private reloadProfile;
|
|
45
46
|
private refreshOAuthToken;
|
|
46
47
|
private rememberProfileAuthHeader;
|
|
47
48
|
}
|
package/dist/utils/profiles.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getEnv, getEnvironmentVariable } from "./env.js";
|
|
2
2
|
import * as fsUtils from "./fs.js";
|
|
3
|
+
import { acquireOAuthRefreshLock } from "./profile-lock.js";
|
|
3
4
|
export const DEFAULT_API_URL = "https://api.smith.langchain.com";
|
|
4
5
|
const OAUTH_CLIENT_ID = "langsmith-cli";
|
|
5
6
|
const TOKEN_REFRESH_LEEWAY_MS = 60_000;
|
|
@@ -189,17 +190,39 @@ export class ProfileAuth {
|
|
|
189
190
|
isProfileAuthorizationHeader(value) {
|
|
190
191
|
return value === this.managedAuthorizationValue;
|
|
191
192
|
}
|
|
193
|
+
reloadProfile() {
|
|
194
|
+
try {
|
|
195
|
+
const config = JSON.parse(fsUtils.readFileSync(this.state.configPath));
|
|
196
|
+
const profile = config.profiles?.[this.state.profileName];
|
|
197
|
+
if (!profile) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
this.state.config = config;
|
|
201
|
+
this.state.profile = profile;
|
|
202
|
+
return profile;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
192
208
|
async refreshOAuthToken(fetchImplementation) {
|
|
193
209
|
const refreshToken = this.state.profile.oauth?.refresh_token;
|
|
194
210
|
if (!refreshToken) {
|
|
195
211
|
return;
|
|
196
212
|
}
|
|
197
213
|
const refreshApiUrl = trimConfigValue(this.state.profile.api_url) ?? DEFAULT_API_URL;
|
|
214
|
+
const deadline = Date.now() + TOKEN_REFRESH_TIMEOUT_MS;
|
|
215
|
+
let lock;
|
|
198
216
|
try {
|
|
217
|
+
lock = await acquireOAuthRefreshLock(this.state.configPath, deadline);
|
|
218
|
+
const fresh = this.reloadProfile();
|
|
219
|
+
if (fresh && !shouldRefreshProfileToken(this.state.profile)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
199
222
|
const body = new URLSearchParams({
|
|
200
223
|
grant_type: "refresh_token",
|
|
201
224
|
client_id: OAUTH_CLIENT_ID,
|
|
202
|
-
refresh_token: refreshToken,
|
|
225
|
+
refresh_token: this.state.profile.oauth?.refresh_token ?? refreshToken,
|
|
203
226
|
});
|
|
204
227
|
const response = await fetchImplementation(`${normalizeConfigUrl(refreshApiUrl)}/oauth/token`, {
|
|
205
228
|
method: "POST",
|
|
@@ -207,7 +230,7 @@ export class ProfileAuth {
|
|
|
207
230
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
208
231
|
},
|
|
209
232
|
body: body.toString(),
|
|
210
|
-
signal: AbortSignal.timeout(
|
|
233
|
+
signal: AbortSignal.timeout(Math.max(0, deadline - Date.now())),
|
|
211
234
|
});
|
|
212
235
|
if (!response.ok) {
|
|
213
236
|
return;
|
|
@@ -224,6 +247,9 @@ export class ProfileAuth {
|
|
|
224
247
|
catch {
|
|
225
248
|
return;
|
|
226
249
|
}
|
|
250
|
+
finally {
|
|
251
|
+
await lock?.release();
|
|
252
|
+
}
|
|
227
253
|
}
|
|
228
254
|
rememberProfileAuthHeader(header) {
|
|
229
255
|
this.managedAuthorizationValue =
|
package/dist/wrappers/gemini.cjs
CHANGED
|
@@ -2,44 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.wrapGemini = wrapGemini;
|
|
4
4
|
const traceable_js_1 = require("../traceable.cjs");
|
|
5
|
-
const
|
|
6
|
-
const usageMetadata = {
|
|
7
|
-
input_tokens: usage.promptTokenCount || 0,
|
|
8
|
-
output_tokens: (() => {
|
|
9
|
-
if ("responseTokenCount" in usage) {
|
|
10
|
-
return usage.responseTokenCount || 0;
|
|
11
|
-
}
|
|
12
|
-
if ("candidatesTokenCount" in usage) {
|
|
13
|
-
return usage.candidatesTokenCount || 0;
|
|
14
|
-
}
|
|
15
|
-
return 0;
|
|
16
|
-
})(),
|
|
17
|
-
total_tokens: usage.totalTokenCount || 0,
|
|
18
|
-
};
|
|
19
|
-
// Add input token details if available
|
|
20
|
-
usageMetadata.input_token_details = {
|
|
21
|
-
...(usage.cachedContentTokenCount && {
|
|
22
|
-
cache_read_over_200k: Math.max(0, usage.cachedContentTokenCount - 200000),
|
|
23
|
-
}),
|
|
24
|
-
...(usage.promptTokenCount && {
|
|
25
|
-
over_200k: Math.max(0, usage.promptTokenCount - 200000),
|
|
26
|
-
}),
|
|
27
|
-
...(usage.cachedContentTokenCount && {
|
|
28
|
-
cache_read: usage.cachedContentTokenCount,
|
|
29
|
-
}),
|
|
30
|
-
};
|
|
31
|
-
// Add output token details if available
|
|
32
|
-
usageMetadata.output_token_details = {
|
|
33
|
-
...("candidatesTokenCount" in usage &&
|
|
34
|
-
usage.candidatesTokenCount != null && {
|
|
35
|
-
over_200k: Math.max(0, usage.candidatesTokenCount - 200000),
|
|
36
|
-
}),
|
|
37
|
-
...(usage.thoughtsTokenCount && {
|
|
38
|
-
reasoning: usage.thoughtsTokenCount,
|
|
39
|
-
}),
|
|
40
|
-
};
|
|
41
|
-
return usageMetadata;
|
|
42
|
-
};
|
|
5
|
+
const gemini_utils_js_1 = require("./gemini.utils.cjs");
|
|
43
6
|
const chatAggregator = (input) => {
|
|
44
7
|
const chunks = Array.isArray(input) &&
|
|
45
8
|
input.every((item) => typeof item === "object" && item !== null)
|
|
@@ -161,7 +124,7 @@ const chatAggregator = (input) => {
|
|
|
161
124
|
result.safety_ratings = safetyRatings;
|
|
162
125
|
}
|
|
163
126
|
if (usageMetadata) {
|
|
164
|
-
result.usage_metadata =
|
|
127
|
+
result.usage_metadata = (0, gemini_utils_js_1.createGeminiUsageMetadata)(usageMetadata);
|
|
165
128
|
}
|
|
166
129
|
return result;
|
|
167
130
|
};
|
|
@@ -354,7 +317,7 @@ function processGeminiOutputs(outputs) {
|
|
|
354
317
|
result.safety_ratings = safetyRatings;
|
|
355
318
|
}
|
|
356
319
|
if ("usageMetadata" in response && response.usageMetadata) {
|
|
357
|
-
result.usage_metadata =
|
|
320
|
+
result.usage_metadata = (0, gemini_utils_js_1.createGeminiUsageMetadata)(response.usageMetadata);
|
|
358
321
|
}
|
|
359
322
|
return result;
|
|
360
323
|
}
|
package/dist/wrappers/gemini.js
CHANGED
|
@@ -1,42 +1,5 @@
|
|
|
1
1
|
import { isTraceableFunction, traceable, } from "../traceable.js";
|
|
2
|
-
|
|
3
|
-
const usageMetadata = {
|
|
4
|
-
input_tokens: usage.promptTokenCount || 0,
|
|
5
|
-
output_tokens: (() => {
|
|
6
|
-
if ("responseTokenCount" in usage) {
|
|
7
|
-
return usage.responseTokenCount || 0;
|
|
8
|
-
}
|
|
9
|
-
if ("candidatesTokenCount" in usage) {
|
|
10
|
-
return usage.candidatesTokenCount || 0;
|
|
11
|
-
}
|
|
12
|
-
return 0;
|
|
13
|
-
})(),
|
|
14
|
-
total_tokens: usage.totalTokenCount || 0,
|
|
15
|
-
};
|
|
16
|
-
// Add input token details if available
|
|
17
|
-
usageMetadata.input_token_details = {
|
|
18
|
-
...(usage.cachedContentTokenCount && {
|
|
19
|
-
cache_read_over_200k: Math.max(0, usage.cachedContentTokenCount - 200000),
|
|
20
|
-
}),
|
|
21
|
-
...(usage.promptTokenCount && {
|
|
22
|
-
over_200k: Math.max(0, usage.promptTokenCount - 200000),
|
|
23
|
-
}),
|
|
24
|
-
...(usage.cachedContentTokenCount && {
|
|
25
|
-
cache_read: usage.cachedContentTokenCount,
|
|
26
|
-
}),
|
|
27
|
-
};
|
|
28
|
-
// Add output token details if available
|
|
29
|
-
usageMetadata.output_token_details = {
|
|
30
|
-
...("candidatesTokenCount" in usage &&
|
|
31
|
-
usage.candidatesTokenCount != null && {
|
|
32
|
-
over_200k: Math.max(0, usage.candidatesTokenCount - 200000),
|
|
33
|
-
}),
|
|
34
|
-
...(usage.thoughtsTokenCount && {
|
|
35
|
-
reasoning: usage.thoughtsTokenCount,
|
|
36
|
-
}),
|
|
37
|
-
};
|
|
38
|
-
return usageMetadata;
|
|
39
|
-
};
|
|
2
|
+
import { createGeminiUsageMetadata } from "./gemini.utils.js";
|
|
40
3
|
const chatAggregator = (input) => {
|
|
41
4
|
const chunks = Array.isArray(input) &&
|
|
42
5
|
input.every((item) => typeof item === "object" && item !== null)
|
|
@@ -158,7 +121,7 @@ const chatAggregator = (input) => {
|
|
|
158
121
|
result.safety_ratings = safetyRatings;
|
|
159
122
|
}
|
|
160
123
|
if (usageMetadata) {
|
|
161
|
-
result.usage_metadata =
|
|
124
|
+
result.usage_metadata = createGeminiUsageMetadata(usageMetadata);
|
|
162
125
|
}
|
|
163
126
|
return result;
|
|
164
127
|
};
|
|
@@ -351,7 +314,7 @@ function processGeminiOutputs(outputs) {
|
|
|
351
314
|
result.safety_ratings = safetyRatings;
|
|
352
315
|
}
|
|
353
316
|
if ("usageMetadata" in response && response.usageMetadata) {
|
|
354
|
-
result.usage_metadata =
|
|
317
|
+
result.usage_metadata = createGeminiUsageMetadata(response.usageMetadata);
|
|
355
318
|
}
|
|
356
319
|
return result;
|
|
357
320
|
}
|