langsmith 0.7.2 → 0.7.4
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 +32 -6
- package/dist/client.d.ts +13 -0
- package/dist/client.js +32 -6
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/sandbox/client.cjs +270 -5
- package/dist/sandbox/client.d.ts +16 -1
- package/dist/sandbox/client.js +270 -5
- package/dist/sandbox/command_handle.cjs +1 -1
- package/dist/sandbox/command_handle.js +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/types.d.ts +31 -0
- package/dist/schemas.d.ts +3 -1
- package/package.json +8 -7
package/dist/client.cjs
CHANGED
|
@@ -4013,6 +4013,35 @@ class Client {
|
|
|
4013
4013
|
const run = await response.json();
|
|
4014
4014
|
return _normalizeRunTimestamps(run);
|
|
4015
4015
|
}
|
|
4016
|
+
/**
|
|
4017
|
+
* List the runs in an annotation queue.
|
|
4018
|
+
* @param queueId - The ID of the annotation queue
|
|
4019
|
+
* @param options - The options for listing runs in the annotation queue
|
|
4020
|
+
* @param options.status - Filter runs by review status. If omitted, returns
|
|
4021
|
+
* runs across all review states.
|
|
4022
|
+
* @param options.limit - The maximum number of runs to return
|
|
4023
|
+
* @returns An iterator of RunWithAnnotationQueueInfo objects
|
|
4024
|
+
*/
|
|
4025
|
+
async *listRunsFromAnnotationQueue(queueId, options = {}) {
|
|
4026
|
+
const { status, limit: userLimit } = options;
|
|
4027
|
+
const params = new URLSearchParams();
|
|
4028
|
+
const limit = userLimit !== undefined && Number.isFinite(userLimit)
|
|
4029
|
+
? Math.min(userLimit, 100)
|
|
4030
|
+
: 100;
|
|
4031
|
+
if (status)
|
|
4032
|
+
params.append("status", status);
|
|
4033
|
+
params.append("limit", limit.toString());
|
|
4034
|
+
let count = 0;
|
|
4035
|
+
const path = `/annotation-queues/${(0, _uuid_js_1.assertUuid)(queueId, "queueId")}/runs`;
|
|
4036
|
+
for await (const runs of this._getPaginated(path, params)) {
|
|
4037
|
+
for (const run of runs) {
|
|
4038
|
+
yield _normalizeRunTimestamps(run);
|
|
4039
|
+
count++;
|
|
4040
|
+
if (count >= limit)
|
|
4041
|
+
return;
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4016
4045
|
/**
|
|
4017
4046
|
* Delete a run from an an annotation queue.
|
|
4018
4047
|
* @param queueId - The ID of the annotation queue to delete the run from
|
|
@@ -4898,12 +4927,9 @@ class Client {
|
|
|
4898
4927
|
});
|
|
4899
4928
|
const data = (await response.json());
|
|
4900
4929
|
const commitHash = data.commit.commit_hash;
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
ownerForUrl = settings.tenant_handle || owner;
|
|
4905
|
-
}
|
|
4906
|
-
return `${this.getHostUrl()}/hub/${ownerForUrl}/${name}:${commitHash.slice(0, 8)}`;
|
|
4930
|
+
const settings = await this._getSettings();
|
|
4931
|
+
const query = new URLSearchParams({ organizationId: settings.id });
|
|
4932
|
+
return `${this.getHostUrl()}/context/${name}/${commitHash.slice(0, 8)}?${query.toString()}`;
|
|
4907
4933
|
}
|
|
4908
4934
|
async _deleteDirectory(identifier) {
|
|
4909
4935
|
const [owner, name] = (0, prompts_js_1.parseHubIdentifier)(identifier);
|
package/dist/client.d.ts
CHANGED
|
@@ -1100,6 +1100,19 @@ export declare class Client implements LangSmithTracingClientInterface {
|
|
|
1100
1100
|
* @throws {Error} If the run is not found at the given index or for other API-related errors
|
|
1101
1101
|
*/
|
|
1102
1102
|
getRunFromAnnotationQueue(queueId: string, index: number): Promise<RunWithAnnotationQueueInfo>;
|
|
1103
|
+
/**
|
|
1104
|
+
* List the runs in an annotation queue.
|
|
1105
|
+
* @param queueId - The ID of the annotation queue
|
|
1106
|
+
* @param options - The options for listing runs in the annotation queue
|
|
1107
|
+
* @param options.status - Filter runs by review status. If omitted, returns
|
|
1108
|
+
* runs across all review states.
|
|
1109
|
+
* @param options.limit - The maximum number of runs to return
|
|
1110
|
+
* @returns An iterator of RunWithAnnotationQueueInfo objects
|
|
1111
|
+
*/
|
|
1112
|
+
listRunsFromAnnotationQueue(queueId: string, options?: {
|
|
1113
|
+
status?: "needs_my_review" | "needs_others_review" | "completed";
|
|
1114
|
+
limit?: number;
|
|
1115
|
+
}): AsyncIterableIterator<RunWithAnnotationQueueInfo>;
|
|
1103
1116
|
/**
|
|
1104
1117
|
* Delete a run from an an annotation queue.
|
|
1105
1118
|
* @param queueId - The ID of the annotation queue to delete the run from
|
package/dist/client.js
CHANGED
|
@@ -3975,6 +3975,35 @@ export class Client {
|
|
|
3975
3975
|
const run = await response.json();
|
|
3976
3976
|
return _normalizeRunTimestamps(run);
|
|
3977
3977
|
}
|
|
3978
|
+
/**
|
|
3979
|
+
* List the runs in an annotation queue.
|
|
3980
|
+
* @param queueId - The ID of the annotation queue
|
|
3981
|
+
* @param options - The options for listing runs in the annotation queue
|
|
3982
|
+
* @param options.status - Filter runs by review status. If omitted, returns
|
|
3983
|
+
* runs across all review states.
|
|
3984
|
+
* @param options.limit - The maximum number of runs to return
|
|
3985
|
+
* @returns An iterator of RunWithAnnotationQueueInfo objects
|
|
3986
|
+
*/
|
|
3987
|
+
async *listRunsFromAnnotationQueue(queueId, options = {}) {
|
|
3988
|
+
const { status, limit: userLimit } = options;
|
|
3989
|
+
const params = new URLSearchParams();
|
|
3990
|
+
const limit = userLimit !== undefined && Number.isFinite(userLimit)
|
|
3991
|
+
? Math.min(userLimit, 100)
|
|
3992
|
+
: 100;
|
|
3993
|
+
if (status)
|
|
3994
|
+
params.append("status", status);
|
|
3995
|
+
params.append("limit", limit.toString());
|
|
3996
|
+
let count = 0;
|
|
3997
|
+
const path = `/annotation-queues/${assertUuid(queueId, "queueId")}/runs`;
|
|
3998
|
+
for await (const runs of this._getPaginated(path, params)) {
|
|
3999
|
+
for (const run of runs) {
|
|
4000
|
+
yield _normalizeRunTimestamps(run);
|
|
4001
|
+
count++;
|
|
4002
|
+
if (count >= limit)
|
|
4003
|
+
return;
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
3978
4007
|
/**
|
|
3979
4008
|
* Delete a run from an an annotation queue.
|
|
3980
4009
|
* @param queueId - The ID of the annotation queue to delete the run from
|
|
@@ -4860,12 +4889,9 @@ export class Client {
|
|
|
4860
4889
|
});
|
|
4861
4890
|
const data = (await response.json());
|
|
4862
4891
|
const commitHash = data.commit.commit_hash;
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
ownerForUrl = settings.tenant_handle || owner;
|
|
4867
|
-
}
|
|
4868
|
-
return `${this.getHostUrl()}/hub/${ownerForUrl}/${name}:${commitHash.slice(0, 8)}`;
|
|
4892
|
+
const settings = await this._getSettings();
|
|
4893
|
+
const query = new URLSearchParams({ organizationId: settings.id });
|
|
4894
|
+
return `${this.getHostUrl()}/context/${name}/${commitHash.slice(0, 8)}?${query.toString()}`;
|
|
4869
4895
|
}
|
|
4870
4896
|
async _deleteDirectory(identifier) {
|
|
4871
4897
|
const [owner, name] = parseHubIdentifier(identifier);
|
package/dist/index.cjs
CHANGED
|
@@ -18,4 +18,4 @@ 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.4";
|
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, 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.4";
|
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, 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.4";
|
package/dist/sandbox/client.cjs
CHANGED
|
@@ -10,6 +10,7 @@ const async_caller_js_1 = require("../utils/async_caller.cjs");
|
|
|
10
10
|
const sandbox_js_1 = require("./sandbox.cjs");
|
|
11
11
|
const errors_js_1 = require("./errors.cjs");
|
|
12
12
|
const helpers_js_1 = require("./helpers.cjs");
|
|
13
|
+
const index_js_1 = require("../utils/uuid/src/index.cjs");
|
|
13
14
|
/**
|
|
14
15
|
* Sleep that can be interrupted by an AbortSignal.
|
|
15
16
|
* Resolves after `ms` milliseconds or rejects immediately if the signal fires.
|
|
@@ -18,17 +19,18 @@ function sleepWithSignal(ms, signal) {
|
|
|
18
19
|
if (!signal) {
|
|
19
20
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
21
|
}
|
|
21
|
-
signal
|
|
22
|
+
const abortSignal = signal;
|
|
23
|
+
abortSignal.throwIfAborted();
|
|
22
24
|
return new Promise((resolve, reject) => {
|
|
23
25
|
const timer = setTimeout(() => {
|
|
24
|
-
|
|
26
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
25
27
|
resolve();
|
|
26
28
|
}, ms);
|
|
27
29
|
function onAbort() {
|
|
28
30
|
clearTimeout(timer);
|
|
29
|
-
reject(
|
|
31
|
+
reject(abortSignal.reason);
|
|
30
32
|
}
|
|
31
|
-
|
|
33
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
32
34
|
});
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
@@ -47,6 +49,192 @@ function getDefaultApiEndpoint() {
|
|
|
47
49
|
function getDefaultApiKey() {
|
|
48
50
|
return (0, env_js_1.getLangSmithEnvironmentVariable)("API_KEY");
|
|
49
51
|
}
|
|
52
|
+
function shellQuote(value) {
|
|
53
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
54
|
+
}
|
|
55
|
+
function writeString(header, value, offset, length) {
|
|
56
|
+
header.write(value.slice(0, length), offset, length, "utf8");
|
|
57
|
+
}
|
|
58
|
+
function writeOctal(header, value, offset, length) {
|
|
59
|
+
const octal = value.toString(8).padStart(length - 1, "0");
|
|
60
|
+
header.write(octal.slice(-length + 1) + "\0", offset, length, "ascii");
|
|
61
|
+
}
|
|
62
|
+
function splitTarPath(name) {
|
|
63
|
+
if (Buffer.byteLength(name) <= 100) {
|
|
64
|
+
return { name, prefix: "" };
|
|
65
|
+
}
|
|
66
|
+
const parts = name.split("/");
|
|
67
|
+
for (let i = 1; i < parts.length; i += 1) {
|
|
68
|
+
const prefix = parts.slice(0, i).join("/");
|
|
69
|
+
const basename = parts.slice(i).join("/");
|
|
70
|
+
if (Buffer.byteLength(prefix) <= 155 &&
|
|
71
|
+
Buffer.byteLength(basename) <= 100) {
|
|
72
|
+
return { name: basename, prefix };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Docker build context path is too long for tar: ${name}`);
|
|
76
|
+
}
|
|
77
|
+
function makeTarHeader(args) {
|
|
78
|
+
const header = Buffer.alloc(512, 0);
|
|
79
|
+
const split = splitTarPath(args.name);
|
|
80
|
+
writeString(header, split.name, 0, 100);
|
|
81
|
+
writeOctal(header, args.mode, 100, 8);
|
|
82
|
+
writeOctal(header, 0, 108, 8);
|
|
83
|
+
writeOctal(header, 0, 116, 8);
|
|
84
|
+
writeOctal(header, args.size, 124, 12);
|
|
85
|
+
writeOctal(header, Math.floor(args.mtimeMs / 1000), 136, 12);
|
|
86
|
+
header.fill(" ", 148, 156);
|
|
87
|
+
writeString(header, args.type === "directory" ? "5" : args.type === "symlink" ? "2" : "0", 156, 1);
|
|
88
|
+
if (args.linkName) {
|
|
89
|
+
writeString(header, args.linkName, 157, 100);
|
|
90
|
+
}
|
|
91
|
+
writeString(header, "ustar", 257, 6);
|
|
92
|
+
writeString(header, "00", 263, 2);
|
|
93
|
+
if (split.prefix) {
|
|
94
|
+
writeString(header, split.prefix, 345, 155);
|
|
95
|
+
}
|
|
96
|
+
let checksum = 0;
|
|
97
|
+
for (const byte of header) {
|
|
98
|
+
checksum += byte;
|
|
99
|
+
}
|
|
100
|
+
header.write(checksum.toString(8).padStart(6, "0") + "\0 ", 148, 8, "ascii");
|
|
101
|
+
return header;
|
|
102
|
+
}
|
|
103
|
+
async function makeDockerContextTar(contextPath) {
|
|
104
|
+
const fs = await import("node:fs/promises");
|
|
105
|
+
const path = await import("node:path");
|
|
106
|
+
const contextRoot = path.resolve(contextPath);
|
|
107
|
+
const chunks = [];
|
|
108
|
+
async function addEntry(absPath) {
|
|
109
|
+
const rel = path.relative(contextRoot, absPath);
|
|
110
|
+
if (!rel || rel.split(path.sep).includes(".git")) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const tarPath = rel.split(path.sep).join("/");
|
|
114
|
+
const stat = await fs.lstat(absPath);
|
|
115
|
+
if (stat.isDirectory()) {
|
|
116
|
+
chunks.push(makeTarHeader({
|
|
117
|
+
name: tarPath.endsWith("/") ? tarPath : `${tarPath}/`,
|
|
118
|
+
mode: stat.mode & 0o777,
|
|
119
|
+
size: 0,
|
|
120
|
+
type: "directory",
|
|
121
|
+
mtimeMs: stat.mtimeMs,
|
|
122
|
+
}));
|
|
123
|
+
const entries = await fs.readdir(absPath);
|
|
124
|
+
for (const entry of entries.sort()) {
|
|
125
|
+
await addEntry(path.join(absPath, entry));
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (stat.isSymbolicLink()) {
|
|
130
|
+
chunks.push(makeTarHeader({
|
|
131
|
+
name: tarPath,
|
|
132
|
+
mode: stat.mode & 0o777,
|
|
133
|
+
size: 0,
|
|
134
|
+
type: "symlink",
|
|
135
|
+
linkName: await fs.readlink(absPath),
|
|
136
|
+
mtimeMs: stat.mtimeMs,
|
|
137
|
+
}));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (!stat.isFile()) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const content = await fs.readFile(absPath);
|
|
144
|
+
chunks.push(makeTarHeader({
|
|
145
|
+
name: tarPath,
|
|
146
|
+
mode: stat.mode & 0o777,
|
|
147
|
+
size: content.byteLength,
|
|
148
|
+
type: "file",
|
|
149
|
+
mtimeMs: stat.mtimeMs,
|
|
150
|
+
}), content);
|
|
151
|
+
const padding = (512 - (content.byteLength % 512)) % 512;
|
|
152
|
+
if (padding) {
|
|
153
|
+
chunks.push(Buffer.alloc(padding, 0));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const rootEntries = await fs.readdir(contextRoot);
|
|
157
|
+
for (const entry of rootEntries.sort()) {
|
|
158
|
+
await addEntry(path.join(contextRoot, entry));
|
|
159
|
+
}
|
|
160
|
+
chunks.push(Buffer.alloc(1024, 0));
|
|
161
|
+
return new Uint8Array(Buffer.concat(chunks));
|
|
162
|
+
}
|
|
163
|
+
async function resolveDockerfileContext(dockerfile, context) {
|
|
164
|
+
const fs = await import("node:fs/promises");
|
|
165
|
+
const path = await import("node:path");
|
|
166
|
+
const contextPath = path.resolve(context);
|
|
167
|
+
const dockerfilePath = path.resolve(contextPath, dockerfile);
|
|
168
|
+
const contextStat = await fs.stat(contextPath);
|
|
169
|
+
if (!contextStat.isDirectory()) {
|
|
170
|
+
throw new Error(`context must be a directory: ${contextPath}`);
|
|
171
|
+
}
|
|
172
|
+
const dockerfileStat = await fs.stat(dockerfilePath);
|
|
173
|
+
if (!dockerfileStat.isFile()) {
|
|
174
|
+
throw new Error(`dockerfile must be a file: ${dockerfilePath}`);
|
|
175
|
+
}
|
|
176
|
+
const dockerfileRel = path.relative(contextPath, dockerfilePath);
|
|
177
|
+
if (dockerfileRel === "" ||
|
|
178
|
+
dockerfileRel.startsWith("..") ||
|
|
179
|
+
path.isAbsolute(dockerfileRel)) {
|
|
180
|
+
throw new Error("dockerfile must be inside context");
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
contextPath,
|
|
184
|
+
dockerfileRel: dockerfileRel.split(path.sep).join("/"),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function makeDockerfileBuildCommand(args) {
|
|
188
|
+
const dockerfileRemote = `${args.remoteContext}/${args.dockerfileRel}`;
|
|
189
|
+
const dockerfileDir = dockerfileRemote.split("/").slice(0, -1).join("/");
|
|
190
|
+
const dockerfileName = dockerfileRemote.split("/").at(-1) ?? "Dockerfile";
|
|
191
|
+
const socketPath = `${args.buildkitRun}/buildkitd.sock`;
|
|
192
|
+
const buildctl = [
|
|
193
|
+
"buildctl",
|
|
194
|
+
"--addr",
|
|
195
|
+
`unix://${socketPath}`,
|
|
196
|
+
"build",
|
|
197
|
+
"--progress=plain",
|
|
198
|
+
"--frontend",
|
|
199
|
+
"dockerfile.v0",
|
|
200
|
+
"--local",
|
|
201
|
+
`context=${args.remoteContext}`,
|
|
202
|
+
"--local",
|
|
203
|
+
`dockerfile=${dockerfileDir}`,
|
|
204
|
+
"--opt",
|
|
205
|
+
`filename=${dockerfileName}`,
|
|
206
|
+
"--output",
|
|
207
|
+
`type=docker,name=${args.imageRef}`,
|
|
208
|
+
];
|
|
209
|
+
if (args.target !== undefined) {
|
|
210
|
+
buildctl.push("--opt", `target=${args.target}`);
|
|
211
|
+
}
|
|
212
|
+
for (const [key, value] of Object.entries(args.buildArgs ?? {}).sort()) {
|
|
213
|
+
buildctl.push("--opt", `build-arg:${key}=${value}`);
|
|
214
|
+
}
|
|
215
|
+
return [
|
|
216
|
+
"set -euo pipefail",
|
|
217
|
+
`mkdir -p ${shellQuote(args.buildkitRoot)} ${shellQuote(args.buildkitRun)}`,
|
|
218
|
+
`buildkitd --addr ${shellQuote(`unix://${socketPath}`)} --root ${shellQuote(args.buildkitRoot)} --oci-worker=true --containerd-worker=false --oci-worker-snapshotter=native --oci-worker-binary buildkit-runc > ${shellQuote(`${args.buildkitRun}/buildkitd.log`)} 2>&1 &`,
|
|
219
|
+
"buildkitd_pid=$!",
|
|
220
|
+
'cleanup() { kill "$buildkitd_pid" >/dev/null 2>&1 || true; }',
|
|
221
|
+
"trap cleanup EXIT",
|
|
222
|
+
"for i in $(seq 1 300); do",
|
|
223
|
+
` if buildctl --addr ${shellQuote(`unix://${socketPath}`)} debug workers >/dev/null 2>&1; then break; fi`,
|
|
224
|
+
` if ! kill -0 "$buildkitd_pid" >/dev/null 2>&1; then cat ${shellQuote(`${args.buildkitRun}/buildkitd.log`)}; exit 1; fi`,
|
|
225
|
+
` if [ "$i" = 300 ]; then cat ${shellQuote(`${args.buildkitRun}/buildkitd.log`)}; exit 1; fi`,
|
|
226
|
+
" sleep 0.1",
|
|
227
|
+
"done",
|
|
228
|
+
"for i in $(seq 1 300); do",
|
|
229
|
+
" if docker info >/dev/null 2>&1; then break; fi",
|
|
230
|
+
' if [ "$i" = 300 ]; then docker info; exit 1; fi',
|
|
231
|
+
" sleep 0.1",
|
|
232
|
+
"done",
|
|
233
|
+
`${buildctl.map(shellQuote).join(" ")} | docker load`,
|
|
234
|
+
`rm -rf ${shellQuote(args.buildkitRoot)} || true`,
|
|
235
|
+
"",
|
|
236
|
+
].join("\n");
|
|
237
|
+
}
|
|
50
238
|
/**
|
|
51
239
|
* Client for interacting with the Sandbox Server API.
|
|
52
240
|
*
|
|
@@ -453,6 +641,77 @@ class SandboxClient {
|
|
|
453
641
|
const snapshot = (await response.json());
|
|
454
642
|
return this.waitForSnapshot(snapshot.id, { timeout, signal });
|
|
455
643
|
}
|
|
644
|
+
/**
|
|
645
|
+
* Build a snapshot from a local Dockerfile context.
|
|
646
|
+
*
|
|
647
|
+
* Creates a temporary builder sandbox, uploads the Docker build context,
|
|
648
|
+
* runs BuildKit inside the sandbox, and captures the built image as a
|
|
649
|
+
* LangSmith snapshot.
|
|
650
|
+
*
|
|
651
|
+
* @param name - Snapshot name.
|
|
652
|
+
* @param dockerfile - Local Dockerfile path, relative to context by default.
|
|
653
|
+
* @param fsCapacityBytes - Filesystem capacity in bytes.
|
|
654
|
+
* @param options - Build context, args, target, build log callback, builder
|
|
655
|
+
* vCPUs/memory, timeout.
|
|
656
|
+
* @returns Snapshot in "ready" status.
|
|
657
|
+
*/
|
|
658
|
+
async createSnapshotFromDockerfile(name, dockerfile, fsCapacityBytes, options = {}) {
|
|
659
|
+
const { context = ".", buildArgs, target, onBuildLog, vCpus, memBytes, timeout = 60, } = options;
|
|
660
|
+
const { contextPath, dockerfileRel } = await resolveDockerfileContext(dockerfile, context);
|
|
661
|
+
const builderName = `snapshot-builder-${(0, index_js_1.v4)().replace(/-/g, "").slice(0, 12)}`;
|
|
662
|
+
// Stage the build on the capacity-backed root filesystem, not /tmp.
|
|
663
|
+
// Inside the sandbox /tmp is a RAM-backed tmpfs that fsCapacityBytes does
|
|
664
|
+
// not size, and BuildKit's native snapshotter writes a full copy of every
|
|
665
|
+
// layer under its root, so a /tmp build exhausts guest RAM and fails with
|
|
666
|
+
// "No space left on device".
|
|
667
|
+
const buildRoot = `/var/lib/langsmith-build/${(0, index_js_1.v4)()
|
|
668
|
+
.replace(/-/g, "")
|
|
669
|
+
.slice(0, 12)}`;
|
|
670
|
+
const remoteContext = `${buildRoot}/context`;
|
|
671
|
+
const remoteTar = `${buildRoot}/context.tar`;
|
|
672
|
+
const imageRef = `langsmith-snapshot-build:${(0, index_js_1.v4)().replace(/-/g, "")}`;
|
|
673
|
+
const buildkitRoot = `${buildRoot}/buildkit-root`;
|
|
674
|
+
const buildkitRun = `${buildRoot}/buildkit-run`;
|
|
675
|
+
const builder = await this.createSandbox({
|
|
676
|
+
name: builderName,
|
|
677
|
+
timeout,
|
|
678
|
+
vCpus,
|
|
679
|
+
memBytes,
|
|
680
|
+
fsCapacityBytes,
|
|
681
|
+
});
|
|
682
|
+
try {
|
|
683
|
+
await builder.write(remoteTar, await makeDockerContextTar(contextPath), timeout);
|
|
684
|
+
await builder.run([
|
|
685
|
+
`rm -rf ${shellQuote(remoteContext)}`,
|
|
686
|
+
`mkdir -p ${shellQuote(remoteContext)}`,
|
|
687
|
+
`tar -xf ${shellQuote(remoteTar)} -C ${shellQuote(remoteContext)}`,
|
|
688
|
+
].join(" && "), { timeout });
|
|
689
|
+
const result = await builder.run(makeDockerfileBuildCommand({
|
|
690
|
+
remoteContext,
|
|
691
|
+
dockerfileRel,
|
|
692
|
+
imageRef,
|
|
693
|
+
buildkitRoot,
|
|
694
|
+
buildkitRun,
|
|
695
|
+
buildArgs,
|
|
696
|
+
target,
|
|
697
|
+
}), {
|
|
698
|
+
timeout,
|
|
699
|
+
onStdout: onBuildLog,
|
|
700
|
+
onStderr: onBuildLog,
|
|
701
|
+
});
|
|
702
|
+
if (result.exit_code !== 0) {
|
|
703
|
+
throw new errors_js_1.LangSmithResourceCreationError("Dockerfile snapshot build failed", "snapshot");
|
|
704
|
+
}
|
|
705
|
+
return await this.captureSnapshot(builder.name, name, {
|
|
706
|
+
dockerImage: imageRef,
|
|
707
|
+
fsCapacityBytes,
|
|
708
|
+
timeout,
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
finally {
|
|
712
|
+
await builder.delete();
|
|
713
|
+
}
|
|
714
|
+
}
|
|
456
715
|
/**
|
|
457
716
|
* Capture a snapshot from a running sandbox.
|
|
458
717
|
*
|
|
@@ -464,9 +723,15 @@ class SandboxClient {
|
|
|
464
723
|
* @returns Snapshot in "ready" status.
|
|
465
724
|
*/
|
|
466
725
|
async captureSnapshot(sandboxName, name, options = {}) {
|
|
467
|
-
const { timeout = 60, signal } = options;
|
|
726
|
+
const { dockerImage, fsCapacityBytes, timeout = 60, signal } = options;
|
|
468
727
|
const url = `${this._baseUrl}/boxes/${encodeURIComponent(sandboxName)}/snapshot`;
|
|
469
728
|
const payload = { name };
|
|
729
|
+
if (dockerImage !== undefined) {
|
|
730
|
+
payload.docker_image = dockerImage;
|
|
731
|
+
}
|
|
732
|
+
if (fsCapacityBytes !== undefined) {
|
|
733
|
+
payload.fs_capacity_bytes = fsCapacityBytes;
|
|
734
|
+
}
|
|
470
735
|
const response = await this._postJson(url, payload, { signal });
|
|
471
736
|
const snapshot = (await response.json());
|
|
472
737
|
return this.waitForSnapshot(snapshot.id, { timeout, signal });
|
package/dist/sandbox/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Main SandboxClient class for interacting with the sandbox server API.
|
|
3
3
|
*/
|
|
4
|
-
import type { CaptureSnapshotOptions, CreateSandboxOptions, CreateSnapshotOptions, ListSnapshotsOptions, ResourceStatus, SandboxClientConfig, Snapshot, StartSandboxOptions, UpdateSandboxOptions, WaitForSandboxOptions, WaitForSnapshotOptions } from "./types.js";
|
|
4
|
+
import type { CaptureSnapshotOptions, CreateDockerfileSnapshotOptions, CreateSandboxOptions, CreateSnapshotOptions, ListSnapshotsOptions, ResourceStatus, SandboxClientConfig, Snapshot, StartSandboxOptions, UpdateSandboxOptions, WaitForSandboxOptions, WaitForSnapshotOptions } from "./types.js";
|
|
5
5
|
import { Sandbox } from "./sandbox.js";
|
|
6
6
|
/**
|
|
7
7
|
* Client for interacting with the Sandbox Server API.
|
|
@@ -173,6 +173,21 @@ export declare class SandboxClient {
|
|
|
173
173
|
* @returns Snapshot in "ready" status.
|
|
174
174
|
*/
|
|
175
175
|
createSnapshot(name: string, dockerImage: string, fsCapacityBytes: number, options?: CreateSnapshotOptions): Promise<Snapshot>;
|
|
176
|
+
/**
|
|
177
|
+
* Build a snapshot from a local Dockerfile context.
|
|
178
|
+
*
|
|
179
|
+
* Creates a temporary builder sandbox, uploads the Docker build context,
|
|
180
|
+
* runs BuildKit inside the sandbox, and captures the built image as a
|
|
181
|
+
* LangSmith snapshot.
|
|
182
|
+
*
|
|
183
|
+
* @param name - Snapshot name.
|
|
184
|
+
* @param dockerfile - Local Dockerfile path, relative to context by default.
|
|
185
|
+
* @param fsCapacityBytes - Filesystem capacity in bytes.
|
|
186
|
+
* @param options - Build context, args, target, build log callback, builder
|
|
187
|
+
* vCPUs/memory, timeout.
|
|
188
|
+
* @returns Snapshot in "ready" status.
|
|
189
|
+
*/
|
|
190
|
+
createSnapshotFromDockerfile(name: string, dockerfile: string, fsCapacityBytes: number, options?: CreateDockerfileSnapshotOptions): Promise<Snapshot>;
|
|
176
191
|
/**
|
|
177
192
|
* Capture a snapshot from a running sandbox.
|
|
178
193
|
*
|
package/dist/sandbox/client.js
CHANGED
|
@@ -7,6 +7,7 @@ import { AsyncCaller } from "../utils/async_caller.js";
|
|
|
7
7
|
import { Sandbox } from "./sandbox.js";
|
|
8
8
|
import { LangSmithResourceCreationError, LangSmithResourceNameConflictError, LangSmithResourceNotFoundError, LangSmithResourceTimeoutError, LangSmithSandboxAPIError, LangSmithValidationError, } from "./errors.js";
|
|
9
9
|
import { handleClientHttpError, handleSandboxCreationError, validateTtl, } from "./helpers.js";
|
|
10
|
+
import { v4 as uuidv4 } from "../utils/uuid/src/index.js";
|
|
10
11
|
/**
|
|
11
12
|
* Sleep that can be interrupted by an AbortSignal.
|
|
12
13
|
* Resolves after `ms` milliseconds or rejects immediately if the signal fires.
|
|
@@ -15,17 +16,18 @@ function sleepWithSignal(ms, signal) {
|
|
|
15
16
|
if (!signal) {
|
|
16
17
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
18
|
}
|
|
18
|
-
signal
|
|
19
|
+
const abortSignal = signal;
|
|
20
|
+
abortSignal.throwIfAborted();
|
|
19
21
|
return new Promise((resolve, reject) => {
|
|
20
22
|
const timer = setTimeout(() => {
|
|
21
|
-
|
|
23
|
+
abortSignal.removeEventListener("abort", onAbort);
|
|
22
24
|
resolve();
|
|
23
25
|
}, ms);
|
|
24
26
|
function onAbort() {
|
|
25
27
|
clearTimeout(timer);
|
|
26
|
-
reject(
|
|
28
|
+
reject(abortSignal.reason);
|
|
27
29
|
}
|
|
28
|
-
|
|
30
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
29
31
|
});
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
@@ -44,6 +46,192 @@ function getDefaultApiEndpoint() {
|
|
|
44
46
|
function getDefaultApiKey() {
|
|
45
47
|
return getLangSmithEnvironmentVariable("API_KEY");
|
|
46
48
|
}
|
|
49
|
+
function shellQuote(value) {
|
|
50
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
51
|
+
}
|
|
52
|
+
function writeString(header, value, offset, length) {
|
|
53
|
+
header.write(value.slice(0, length), offset, length, "utf8");
|
|
54
|
+
}
|
|
55
|
+
function writeOctal(header, value, offset, length) {
|
|
56
|
+
const octal = value.toString(8).padStart(length - 1, "0");
|
|
57
|
+
header.write(octal.slice(-length + 1) + "\0", offset, length, "ascii");
|
|
58
|
+
}
|
|
59
|
+
function splitTarPath(name) {
|
|
60
|
+
if (Buffer.byteLength(name) <= 100) {
|
|
61
|
+
return { name, prefix: "" };
|
|
62
|
+
}
|
|
63
|
+
const parts = name.split("/");
|
|
64
|
+
for (let i = 1; i < parts.length; i += 1) {
|
|
65
|
+
const prefix = parts.slice(0, i).join("/");
|
|
66
|
+
const basename = parts.slice(i).join("/");
|
|
67
|
+
if (Buffer.byteLength(prefix) <= 155 &&
|
|
68
|
+
Buffer.byteLength(basename) <= 100) {
|
|
69
|
+
return { name: basename, prefix };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Docker build context path is too long for tar: ${name}`);
|
|
73
|
+
}
|
|
74
|
+
function makeTarHeader(args) {
|
|
75
|
+
const header = Buffer.alloc(512, 0);
|
|
76
|
+
const split = splitTarPath(args.name);
|
|
77
|
+
writeString(header, split.name, 0, 100);
|
|
78
|
+
writeOctal(header, args.mode, 100, 8);
|
|
79
|
+
writeOctal(header, 0, 108, 8);
|
|
80
|
+
writeOctal(header, 0, 116, 8);
|
|
81
|
+
writeOctal(header, args.size, 124, 12);
|
|
82
|
+
writeOctal(header, Math.floor(args.mtimeMs / 1000), 136, 12);
|
|
83
|
+
header.fill(" ", 148, 156);
|
|
84
|
+
writeString(header, args.type === "directory" ? "5" : args.type === "symlink" ? "2" : "0", 156, 1);
|
|
85
|
+
if (args.linkName) {
|
|
86
|
+
writeString(header, args.linkName, 157, 100);
|
|
87
|
+
}
|
|
88
|
+
writeString(header, "ustar", 257, 6);
|
|
89
|
+
writeString(header, "00", 263, 2);
|
|
90
|
+
if (split.prefix) {
|
|
91
|
+
writeString(header, split.prefix, 345, 155);
|
|
92
|
+
}
|
|
93
|
+
let checksum = 0;
|
|
94
|
+
for (const byte of header) {
|
|
95
|
+
checksum += byte;
|
|
96
|
+
}
|
|
97
|
+
header.write(checksum.toString(8).padStart(6, "0") + "\0 ", 148, 8, "ascii");
|
|
98
|
+
return header;
|
|
99
|
+
}
|
|
100
|
+
async function makeDockerContextTar(contextPath) {
|
|
101
|
+
const fs = await import("node:fs/promises");
|
|
102
|
+
const path = await import("node:path");
|
|
103
|
+
const contextRoot = path.resolve(contextPath);
|
|
104
|
+
const chunks = [];
|
|
105
|
+
async function addEntry(absPath) {
|
|
106
|
+
const rel = path.relative(contextRoot, absPath);
|
|
107
|
+
if (!rel || rel.split(path.sep).includes(".git")) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const tarPath = rel.split(path.sep).join("/");
|
|
111
|
+
const stat = await fs.lstat(absPath);
|
|
112
|
+
if (stat.isDirectory()) {
|
|
113
|
+
chunks.push(makeTarHeader({
|
|
114
|
+
name: tarPath.endsWith("/") ? tarPath : `${tarPath}/`,
|
|
115
|
+
mode: stat.mode & 0o777,
|
|
116
|
+
size: 0,
|
|
117
|
+
type: "directory",
|
|
118
|
+
mtimeMs: stat.mtimeMs,
|
|
119
|
+
}));
|
|
120
|
+
const entries = await fs.readdir(absPath);
|
|
121
|
+
for (const entry of entries.sort()) {
|
|
122
|
+
await addEntry(path.join(absPath, entry));
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (stat.isSymbolicLink()) {
|
|
127
|
+
chunks.push(makeTarHeader({
|
|
128
|
+
name: tarPath,
|
|
129
|
+
mode: stat.mode & 0o777,
|
|
130
|
+
size: 0,
|
|
131
|
+
type: "symlink",
|
|
132
|
+
linkName: await fs.readlink(absPath),
|
|
133
|
+
mtimeMs: stat.mtimeMs,
|
|
134
|
+
}));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (!stat.isFile()) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const content = await fs.readFile(absPath);
|
|
141
|
+
chunks.push(makeTarHeader({
|
|
142
|
+
name: tarPath,
|
|
143
|
+
mode: stat.mode & 0o777,
|
|
144
|
+
size: content.byteLength,
|
|
145
|
+
type: "file",
|
|
146
|
+
mtimeMs: stat.mtimeMs,
|
|
147
|
+
}), content);
|
|
148
|
+
const padding = (512 - (content.byteLength % 512)) % 512;
|
|
149
|
+
if (padding) {
|
|
150
|
+
chunks.push(Buffer.alloc(padding, 0));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const rootEntries = await fs.readdir(contextRoot);
|
|
154
|
+
for (const entry of rootEntries.sort()) {
|
|
155
|
+
await addEntry(path.join(contextRoot, entry));
|
|
156
|
+
}
|
|
157
|
+
chunks.push(Buffer.alloc(1024, 0));
|
|
158
|
+
return new Uint8Array(Buffer.concat(chunks));
|
|
159
|
+
}
|
|
160
|
+
async function resolveDockerfileContext(dockerfile, context) {
|
|
161
|
+
const fs = await import("node:fs/promises");
|
|
162
|
+
const path = await import("node:path");
|
|
163
|
+
const contextPath = path.resolve(context);
|
|
164
|
+
const dockerfilePath = path.resolve(contextPath, dockerfile);
|
|
165
|
+
const contextStat = await fs.stat(contextPath);
|
|
166
|
+
if (!contextStat.isDirectory()) {
|
|
167
|
+
throw new Error(`context must be a directory: ${contextPath}`);
|
|
168
|
+
}
|
|
169
|
+
const dockerfileStat = await fs.stat(dockerfilePath);
|
|
170
|
+
if (!dockerfileStat.isFile()) {
|
|
171
|
+
throw new Error(`dockerfile must be a file: ${dockerfilePath}`);
|
|
172
|
+
}
|
|
173
|
+
const dockerfileRel = path.relative(contextPath, dockerfilePath);
|
|
174
|
+
if (dockerfileRel === "" ||
|
|
175
|
+
dockerfileRel.startsWith("..") ||
|
|
176
|
+
path.isAbsolute(dockerfileRel)) {
|
|
177
|
+
throw new Error("dockerfile must be inside context");
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
contextPath,
|
|
181
|
+
dockerfileRel: dockerfileRel.split(path.sep).join("/"),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function makeDockerfileBuildCommand(args) {
|
|
185
|
+
const dockerfileRemote = `${args.remoteContext}/${args.dockerfileRel}`;
|
|
186
|
+
const dockerfileDir = dockerfileRemote.split("/").slice(0, -1).join("/");
|
|
187
|
+
const dockerfileName = dockerfileRemote.split("/").at(-1) ?? "Dockerfile";
|
|
188
|
+
const socketPath = `${args.buildkitRun}/buildkitd.sock`;
|
|
189
|
+
const buildctl = [
|
|
190
|
+
"buildctl",
|
|
191
|
+
"--addr",
|
|
192
|
+
`unix://${socketPath}`,
|
|
193
|
+
"build",
|
|
194
|
+
"--progress=plain",
|
|
195
|
+
"--frontend",
|
|
196
|
+
"dockerfile.v0",
|
|
197
|
+
"--local",
|
|
198
|
+
`context=${args.remoteContext}`,
|
|
199
|
+
"--local",
|
|
200
|
+
`dockerfile=${dockerfileDir}`,
|
|
201
|
+
"--opt",
|
|
202
|
+
`filename=${dockerfileName}`,
|
|
203
|
+
"--output",
|
|
204
|
+
`type=docker,name=${args.imageRef}`,
|
|
205
|
+
];
|
|
206
|
+
if (args.target !== undefined) {
|
|
207
|
+
buildctl.push("--opt", `target=${args.target}`);
|
|
208
|
+
}
|
|
209
|
+
for (const [key, value] of Object.entries(args.buildArgs ?? {}).sort()) {
|
|
210
|
+
buildctl.push("--opt", `build-arg:${key}=${value}`);
|
|
211
|
+
}
|
|
212
|
+
return [
|
|
213
|
+
"set -euo pipefail",
|
|
214
|
+
`mkdir -p ${shellQuote(args.buildkitRoot)} ${shellQuote(args.buildkitRun)}`,
|
|
215
|
+
`buildkitd --addr ${shellQuote(`unix://${socketPath}`)} --root ${shellQuote(args.buildkitRoot)} --oci-worker=true --containerd-worker=false --oci-worker-snapshotter=native --oci-worker-binary buildkit-runc > ${shellQuote(`${args.buildkitRun}/buildkitd.log`)} 2>&1 &`,
|
|
216
|
+
"buildkitd_pid=$!",
|
|
217
|
+
'cleanup() { kill "$buildkitd_pid" >/dev/null 2>&1 || true; }',
|
|
218
|
+
"trap cleanup EXIT",
|
|
219
|
+
"for i in $(seq 1 300); do",
|
|
220
|
+
` if buildctl --addr ${shellQuote(`unix://${socketPath}`)} debug workers >/dev/null 2>&1; then break; fi`,
|
|
221
|
+
` if ! kill -0 "$buildkitd_pid" >/dev/null 2>&1; then cat ${shellQuote(`${args.buildkitRun}/buildkitd.log`)}; exit 1; fi`,
|
|
222
|
+
` if [ "$i" = 300 ]; then cat ${shellQuote(`${args.buildkitRun}/buildkitd.log`)}; exit 1; fi`,
|
|
223
|
+
" sleep 0.1",
|
|
224
|
+
"done",
|
|
225
|
+
"for i in $(seq 1 300); do",
|
|
226
|
+
" if docker info >/dev/null 2>&1; then break; fi",
|
|
227
|
+
' if [ "$i" = 300 ]; then docker info; exit 1; fi',
|
|
228
|
+
" sleep 0.1",
|
|
229
|
+
"done",
|
|
230
|
+
`${buildctl.map(shellQuote).join(" ")} | docker load`,
|
|
231
|
+
`rm -rf ${shellQuote(args.buildkitRoot)} || true`,
|
|
232
|
+
"",
|
|
233
|
+
].join("\n");
|
|
234
|
+
}
|
|
47
235
|
/**
|
|
48
236
|
* Client for interacting with the Sandbox Server API.
|
|
49
237
|
*
|
|
@@ -450,6 +638,77 @@ export class SandboxClient {
|
|
|
450
638
|
const snapshot = (await response.json());
|
|
451
639
|
return this.waitForSnapshot(snapshot.id, { timeout, signal });
|
|
452
640
|
}
|
|
641
|
+
/**
|
|
642
|
+
* Build a snapshot from a local Dockerfile context.
|
|
643
|
+
*
|
|
644
|
+
* Creates a temporary builder sandbox, uploads the Docker build context,
|
|
645
|
+
* runs BuildKit inside the sandbox, and captures the built image as a
|
|
646
|
+
* LangSmith snapshot.
|
|
647
|
+
*
|
|
648
|
+
* @param name - Snapshot name.
|
|
649
|
+
* @param dockerfile - Local Dockerfile path, relative to context by default.
|
|
650
|
+
* @param fsCapacityBytes - Filesystem capacity in bytes.
|
|
651
|
+
* @param options - Build context, args, target, build log callback, builder
|
|
652
|
+
* vCPUs/memory, timeout.
|
|
653
|
+
* @returns Snapshot in "ready" status.
|
|
654
|
+
*/
|
|
655
|
+
async createSnapshotFromDockerfile(name, dockerfile, fsCapacityBytes, options = {}) {
|
|
656
|
+
const { context = ".", buildArgs, target, onBuildLog, vCpus, memBytes, timeout = 60, } = options;
|
|
657
|
+
const { contextPath, dockerfileRel } = await resolveDockerfileContext(dockerfile, context);
|
|
658
|
+
const builderName = `snapshot-builder-${uuidv4().replace(/-/g, "").slice(0, 12)}`;
|
|
659
|
+
// Stage the build on the capacity-backed root filesystem, not /tmp.
|
|
660
|
+
// Inside the sandbox /tmp is a RAM-backed tmpfs that fsCapacityBytes does
|
|
661
|
+
// not size, and BuildKit's native snapshotter writes a full copy of every
|
|
662
|
+
// layer under its root, so a /tmp build exhausts guest RAM and fails with
|
|
663
|
+
// "No space left on device".
|
|
664
|
+
const buildRoot = `/var/lib/langsmith-build/${uuidv4()
|
|
665
|
+
.replace(/-/g, "")
|
|
666
|
+
.slice(0, 12)}`;
|
|
667
|
+
const remoteContext = `${buildRoot}/context`;
|
|
668
|
+
const remoteTar = `${buildRoot}/context.tar`;
|
|
669
|
+
const imageRef = `langsmith-snapshot-build:${uuidv4().replace(/-/g, "")}`;
|
|
670
|
+
const buildkitRoot = `${buildRoot}/buildkit-root`;
|
|
671
|
+
const buildkitRun = `${buildRoot}/buildkit-run`;
|
|
672
|
+
const builder = await this.createSandbox({
|
|
673
|
+
name: builderName,
|
|
674
|
+
timeout,
|
|
675
|
+
vCpus,
|
|
676
|
+
memBytes,
|
|
677
|
+
fsCapacityBytes,
|
|
678
|
+
});
|
|
679
|
+
try {
|
|
680
|
+
await builder.write(remoteTar, await makeDockerContextTar(contextPath), timeout);
|
|
681
|
+
await builder.run([
|
|
682
|
+
`rm -rf ${shellQuote(remoteContext)}`,
|
|
683
|
+
`mkdir -p ${shellQuote(remoteContext)}`,
|
|
684
|
+
`tar -xf ${shellQuote(remoteTar)} -C ${shellQuote(remoteContext)}`,
|
|
685
|
+
].join(" && "), { timeout });
|
|
686
|
+
const result = await builder.run(makeDockerfileBuildCommand({
|
|
687
|
+
remoteContext,
|
|
688
|
+
dockerfileRel,
|
|
689
|
+
imageRef,
|
|
690
|
+
buildkitRoot,
|
|
691
|
+
buildkitRun,
|
|
692
|
+
buildArgs,
|
|
693
|
+
target,
|
|
694
|
+
}), {
|
|
695
|
+
timeout,
|
|
696
|
+
onStdout: onBuildLog,
|
|
697
|
+
onStderr: onBuildLog,
|
|
698
|
+
});
|
|
699
|
+
if (result.exit_code !== 0) {
|
|
700
|
+
throw new LangSmithResourceCreationError("Dockerfile snapshot build failed", "snapshot");
|
|
701
|
+
}
|
|
702
|
+
return await this.captureSnapshot(builder.name, name, {
|
|
703
|
+
dockerImage: imageRef,
|
|
704
|
+
fsCapacityBytes,
|
|
705
|
+
timeout,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
finally {
|
|
709
|
+
await builder.delete();
|
|
710
|
+
}
|
|
711
|
+
}
|
|
453
712
|
/**
|
|
454
713
|
* Capture a snapshot from a running sandbox.
|
|
455
714
|
*
|
|
@@ -461,9 +720,15 @@ export class SandboxClient {
|
|
|
461
720
|
* @returns Snapshot in "ready" status.
|
|
462
721
|
*/
|
|
463
722
|
async captureSnapshot(sandboxName, name, options = {}) {
|
|
464
|
-
const { timeout = 60, signal } = options;
|
|
723
|
+
const { dockerImage, fsCapacityBytes, timeout = 60, signal } = options;
|
|
465
724
|
const url = `${this._baseUrl}/boxes/${encodeURIComponent(sandboxName)}/snapshot`;
|
|
466
725
|
const payload = { name };
|
|
726
|
+
if (dockerImage !== undefined) {
|
|
727
|
+
payload.docker_image = dockerImage;
|
|
728
|
+
}
|
|
729
|
+
if (fsCapacityBytes !== undefined) {
|
|
730
|
+
payload.fs_capacity_bytes = fsCapacityBytes;
|
|
731
|
+
}
|
|
467
732
|
const response = await this._postJson(url, payload, { signal });
|
|
468
733
|
const snapshot = (await response.json());
|
|
469
734
|
return this.waitForSnapshot(snapshot.id, { timeout, signal });
|
|
@@ -202,7 +202,7 @@ class CommandHandle {
|
|
|
202
202
|
return;
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
|
-
|
|
205
|
+
throw new errors_js_1.LangSmithSandboxConnectionError("Command stream ended without exit message");
|
|
206
206
|
}
|
|
207
207
|
/**
|
|
208
208
|
* Async iterate over output chunks with auto-reconnect on transient errors.
|
|
@@ -199,7 +199,7 @@ export class CommandHandle {
|
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
|
-
|
|
202
|
+
throw new LangSmithSandboxConnectionError("Command stream ended without exit message");
|
|
203
203
|
}
|
|
204
204
|
/**
|
|
205
205
|
* Async iterate over output chunks with auto-reconnect on transient errors.
|
package/dist/sandbox/index.d.ts
CHANGED
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
export { SandboxClient } from "./client.js";
|
|
31
31
|
export { Sandbox } from "./sandbox.js";
|
|
32
32
|
export { CommandHandle } from "./command_handle.js";
|
|
33
|
-
export type { ExecutionResult, OutputChunk, WsMessage, WsRunOptions, ResourceStatus, Snapshot, SandboxData, SandboxClientConfig, RunOptions, CreateSandboxOptions, SandboxAccessControl, SandboxProxyConfig, CreateSnapshotOptions, CaptureSnapshotOptions, ListSnapshotsOptions, WaitForSnapshotOptions, StartSandboxOptions, UpdateSandboxOptions, WaitForSandboxOptions, } from "./types.js";
|
|
33
|
+
export type { ExecutionResult, OutputChunk, WsMessage, WsRunOptions, ResourceStatus, Snapshot, SandboxData, SandboxClientConfig, RunOptions, CreateSandboxOptions, SandboxAccessControl, SandboxProxyConfig, CreateSnapshotOptions, CreateDockerfileSnapshotOptions, CaptureSnapshotOptions, ListSnapshotsOptions, WaitForSnapshotOptions, StartSandboxOptions, UpdateSandboxOptions, WaitForSandboxOptions, } from "./types.js";
|
|
34
34
|
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/types.d.ts
CHANGED
|
@@ -333,10 +333,41 @@ export interface CreateSnapshotOptions {
|
|
|
333
333
|
/** AbortSignal for cancellation. */
|
|
334
334
|
signal?: AbortSignal;
|
|
335
335
|
}
|
|
336
|
+
/**
|
|
337
|
+
* Options for creating a snapshot from a local Dockerfile context.
|
|
338
|
+
*/
|
|
339
|
+
export interface CreateDockerfileSnapshotOptions {
|
|
340
|
+
/** Local Docker build context directory. Default: current working directory. */
|
|
341
|
+
context?: string;
|
|
342
|
+
/** Docker build args passed as BuildKit build-arg opts. */
|
|
343
|
+
buildArgs?: Record<string, string>;
|
|
344
|
+
/** Optional Dockerfile target stage. */
|
|
345
|
+
target?: string;
|
|
346
|
+
/** Callback for Docker build stdout/stderr chunks. */
|
|
347
|
+
onBuildLog?: (data: string) => void;
|
|
348
|
+
/**
|
|
349
|
+
* Number of vCPUs for the temporary builder sandbox. The build runs
|
|
350
|
+
* BuildKit plus the native snapshotter's layer copies inside it, which
|
|
351
|
+
* contend for a single core by default, so an extra vCPU can cut a cold
|
|
352
|
+
* build's wall time substantially.
|
|
353
|
+
*/
|
|
354
|
+
vCpus?: number;
|
|
355
|
+
/** Memory in bytes for the temporary builder sandbox. */
|
|
356
|
+
memBytes?: number;
|
|
357
|
+
/** Timeout in seconds for builder sandbox operations. Default: 60. */
|
|
358
|
+
timeout?: number;
|
|
359
|
+
}
|
|
336
360
|
/**
|
|
337
361
|
* Options for capturing a snapshot from a running sandbox.
|
|
338
362
|
*/
|
|
339
363
|
export interface CaptureSnapshotOptions {
|
|
364
|
+
/**
|
|
365
|
+
* Docker image tag inside the sandbox to export into the snapshot instead
|
|
366
|
+
* of capturing the live root filesystem.
|
|
367
|
+
*/
|
|
368
|
+
dockerImage?: string;
|
|
369
|
+
/** Filesystem capacity in bytes for Docker image export. */
|
|
370
|
+
fsCapacityBytes?: number;
|
|
340
371
|
/** Timeout in seconds when waiting for ready. Default: 60. */
|
|
341
372
|
timeout?: number;
|
|
342
373
|
/** AbortSignal for cancellation. */
|
package/dist/schemas.d.ts
CHANGED
|
@@ -543,7 +543,9 @@ export interface FeedbackConfigSchema {
|
|
|
543
543
|
/** Whether a lower score is considered better for this feedback key. */
|
|
544
544
|
is_lower_score_better?: boolean | null;
|
|
545
545
|
}
|
|
546
|
-
export interface RunWithAnnotationQueueInfo extends BaseRun {
|
|
546
|
+
export interface RunWithAnnotationQueueInfo extends Exclude<BaseRun, "id"> {
|
|
547
|
+
/** An unique identifier for the run. */
|
|
548
|
+
id: string;
|
|
547
549
|
/** The last time this run was reviewed. */
|
|
548
550
|
last_reviewed_time?: string;
|
|
549
551
|
/** The time this run was added to the queue. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "langsmith",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Client library to connect to the LangSmith Observability and Evaluation Platform.",
|
|
5
5
|
"packageManager": "pnpm@10.33.0",
|
|
6
6
|
"files": [
|
|
@@ -159,8 +159,8 @@
|
|
|
159
159
|
"@ai-sdk/openai": "4.0.0-canary.59",
|
|
160
160
|
"@ai-sdk/provider": "4.0.0-canary.16",
|
|
161
161
|
"@ai-sdk/anthropic": "4.0.0-canary.55",
|
|
162
|
-
"@anthropic-ai/claude-agent-sdk": "^0.
|
|
163
|
-
"@anthropic-ai/sdk": "^0.
|
|
162
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.150",
|
|
163
|
+
"@anthropic-ai/sdk": "^0.98.0",
|
|
164
164
|
"@babel/preset-env": "^7.22.4",
|
|
165
165
|
"@faker-js/faker": "^8.4.1",
|
|
166
166
|
"@google/genai": "^2.0.1",
|
|
@@ -169,15 +169,16 @@
|
|
|
169
169
|
"@langchain/core": "^0.3.72",
|
|
170
170
|
"@langchain/langgraph": "^0.3.6",
|
|
171
171
|
"@langchain/openai": "^0.6.17",
|
|
172
|
-
"@openai/agents": "^0.
|
|
172
|
+
"@openai/agents": "^0.11.5",
|
|
173
173
|
"@opentelemetry/api": "^1.9.0",
|
|
174
|
-
"@opentelemetry/auto-instrumentations-node": "^0.
|
|
174
|
+
"@opentelemetry/auto-instrumentations-node": "^0.76.0",
|
|
175
175
|
"@opentelemetry/context-async-hooks": "^2.6.1",
|
|
176
|
-
"@opentelemetry/sdk-node": "^0.
|
|
176
|
+
"@opentelemetry/sdk-node": "^0.218.0",
|
|
177
177
|
"@opentelemetry/sdk-trace-base": "^2.0.0",
|
|
178
178
|
"@opentelemetry/sdk-trace-node": "^2.0.0",
|
|
179
179
|
"@tsconfig/recommended": "^1.0.2",
|
|
180
180
|
"@types/jest": "^29.5.1",
|
|
181
|
+
"@types/node": "^25.9.1",
|
|
181
182
|
"@types/node-fetch": "^2.6.12",
|
|
182
183
|
"@types/semver": "^7.7.1",
|
|
183
184
|
"@types/ws": "^8.18.1",
|
|
@@ -200,7 +201,7 @@
|
|
|
200
201
|
"ts-node": "^10.9.1",
|
|
201
202
|
"typedoc": "^0.28.16",
|
|
202
203
|
"typedoc-plugin-expand-object-like-types": "^0.1.2",
|
|
203
|
-
"typescript": "^
|
|
204
|
+
"typescript": "^6.0.3",
|
|
204
205
|
"vitest": "^3.1.3",
|
|
205
206
|
"ws": "^8.19.0",
|
|
206
207
|
"zod": "^4.3.6"
|