trajectories-sh 1.23.1 → 1.24.1
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/{auth-4UEZSE4C.js → auth-NNBHKNIF.js} +1 -1
- package/dist/{chunk-35B24E3O.js → chunk-72L6XPK6.js} +1 -0
- package/dist/{chunk-Q2ZEN43L.js → chunk-ERO5HP43.js} +1 -1
- package/dist/{chunk-2ELJH3IV.js → chunk-HHUWMQ5S.js} +1 -1
- package/dist/chunk-LOJZPDGI.js +242 -0
- package/dist/cli.js +429 -43
- package/dist/{comments-PK7DDP36.js → comments-M3EX3VOU.js} +3 -3
- package/dist/{config-4XJRECIW.js → config-QS2IT5DI.js} +1 -1
- package/dist/contracts-preview-3J2PLT3U.js +140 -0
- package/dist/download-BWJDAVMM.js +28 -0
- package/dist/download-project-XN4OPF3M.js +70 -0
- package/dist/{projects-ESYLKFUR.js → projects-END2CFCS.js} +3 -3
- package/dist/{push-task-OZTAROKW.js → push-task-FN4XODRC.js} +12 -6
- package/dist/{upload-CIW2CHKX.js → upload-6HQ5A2H7.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-IUSBK45Q.js +0 -148
- package/dist/download-M6UVD5S5.js +0 -12
|
@@ -29,7 +29,7 @@ function getAccessToken() {
|
|
|
29
29
|
return loadConfig().access_token;
|
|
30
30
|
}
|
|
31
31
|
async function getAuthHeader() {
|
|
32
|
-
const envKey = process.env.TRAJECTORIES_API_KEY;
|
|
32
|
+
const envKey = process.env.TRAJECTORIES_API_KEY || process.env.TRAJECTORIES_TOKEN;
|
|
33
33
|
if (envKey && envKey.trim()) return `Bearer ${envKey.trim()}`;
|
|
34
34
|
const config = loadConfig();
|
|
35
35
|
if (config.api_key) return `Bearer ${config.api_key}`;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHUNK_DIR,
|
|
3
|
+
mimeFor,
|
|
4
|
+
unpackScreenshotChunks
|
|
5
|
+
} from "./chunk-72L6XPK6.js";
|
|
6
|
+
import {
|
|
7
|
+
getApiUrl,
|
|
8
|
+
getAuthHeader
|
|
9
|
+
} from "./chunk-ERO5HP43.js";
|
|
10
|
+
|
|
11
|
+
// src/download.ts
|
|
12
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
13
|
+
import { join, dirname } from "path";
|
|
14
|
+
var UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
|
|
15
|
+
var BATCH_SIZE = 100;
|
|
16
|
+
var CONCURRENCY = 25;
|
|
17
|
+
var DOWNLOAD_TIMEOUT_MS = 18e4;
|
|
18
|
+
var EXIT = {
|
|
19
|
+
OK: 0,
|
|
20
|
+
ERROR: 1,
|
|
21
|
+
USAGE: 2,
|
|
22
|
+
AUTH_REQUIRED: 3,
|
|
23
|
+
NOT_FOUND: 4
|
|
24
|
+
};
|
|
25
|
+
var CliError = class extends Error {
|
|
26
|
+
constructor(message, exitCode = EXIT.ERROR) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.exitCode = exitCode;
|
|
29
|
+
}
|
|
30
|
+
exitCode;
|
|
31
|
+
};
|
|
32
|
+
async function httpError(resp, what) {
|
|
33
|
+
let detail = "";
|
|
34
|
+
try {
|
|
35
|
+
const body = await resp.json();
|
|
36
|
+
detail = typeof body.detail === "string" ? body.detail : "";
|
|
37
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
38
|
+
return new CliError(
|
|
39
|
+
`${what}: authentication required (${resp.status}). ${body.how_to_authenticate?.cli ?? "Set TRAJECTORIES_API_KEY in the environment."}`,
|
|
40
|
+
EXIT.AUTH_REQUIRED
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (resp.status === 404) {
|
|
44
|
+
return new CliError(
|
|
45
|
+
`${what}: not found (404)${detail ? `: ${detail}` : ""}. If it is private, set TRAJECTORIES_API_KEY for an org member's key.`,
|
|
46
|
+
EXIT.NOT_FOUND
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
return new CliError(`${what}: HTTP ${resp.status}${detail ? ` ${detail}` : ""}`, EXIT.ERROR);
|
|
52
|
+
}
|
|
53
|
+
function parseJobId(input) {
|
|
54
|
+
const trimmed = input.trim();
|
|
55
|
+
if (UUID_RE.test(trimmed) && !trimmed.includes("/")) {
|
|
56
|
+
return trimmed.match(UUID_RE)[0];
|
|
57
|
+
}
|
|
58
|
+
const shortMatch = trimmed.match(/\/t\/([0-9a-f-]{36})/i);
|
|
59
|
+
if (shortMatch) return shortMatch[1];
|
|
60
|
+
const trajMatch = trimmed.match(/\/trajectories\/([0-9a-f-]{36})/i);
|
|
61
|
+
if (trajMatch) return trajMatch[1];
|
|
62
|
+
const trajViewerMatch = trimmed.match(/\/traj\/([0-9a-f-]{36})\/([^/]+)/i);
|
|
63
|
+
if (trajViewerMatch) {
|
|
64
|
+
return `org:${trajViewerMatch[1]}:${trajViewerMatch[2]}`;
|
|
65
|
+
}
|
|
66
|
+
const anyUuid = trimmed.match(UUID_RE);
|
|
67
|
+
if (anyUuid) return anyUuid[0];
|
|
68
|
+
throw new CliError(`Could not find a trajectory job ID in: ${input}`, EXIT.USAGE);
|
|
69
|
+
}
|
|
70
|
+
function parseProjectId(input) {
|
|
71
|
+
const trimmed = input.trim();
|
|
72
|
+
const m = trimmed.match(/\/projects\/([0-9a-f-]{36})/i) ?? (trimmed.includes("/") ? null : trimmed.match(UUID_RE));
|
|
73
|
+
if (!m) throw new CliError(`Could not find a project ID in: ${input}`, EXIT.USAGE);
|
|
74
|
+
return m[1] ?? m[0];
|
|
75
|
+
}
|
|
76
|
+
function isScreenshot(path) {
|
|
77
|
+
if (path.includes(`/${CHUNK_DIR}/`)) return true;
|
|
78
|
+
const parts = path.split("/");
|
|
79
|
+
return parts.length >= 3 && parts[1] === "agent" && mimeFor(parts[parts.length - 1]) != null;
|
|
80
|
+
}
|
|
81
|
+
async function planJob(input, options) {
|
|
82
|
+
const apiUrl = getApiUrl();
|
|
83
|
+
const auth = await getAuthHeader();
|
|
84
|
+
const headers = {};
|
|
85
|
+
if (auth) headers["Authorization"] = auth;
|
|
86
|
+
let jobId = parseJobId(input);
|
|
87
|
+
if (jobId.startsWith("org:")) {
|
|
88
|
+
const [, orgId, slug2] = jobId.split(":");
|
|
89
|
+
const configResp = await fetch(`${apiUrl}/api/viewer/${orgId}/${slug2}/api/jobs`, {
|
|
90
|
+
headers,
|
|
91
|
+
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
|
|
92
|
+
});
|
|
93
|
+
if (!configResp.ok) throw await httpError(configResp, `Resolve job ${orgId}/${slug2}`);
|
|
94
|
+
const items = (await configResp.json()).items || [];
|
|
95
|
+
if (!items[0]?.id) throw new CliError(`Could not resolve job ID for ${orgId}/${slug2}`, EXIT.NOT_FOUND);
|
|
96
|
+
jobId = items[0].id;
|
|
97
|
+
}
|
|
98
|
+
const listResp = await fetch(`${apiUrl}/api/cli/download/${jobId}/files`, {
|
|
99
|
+
headers,
|
|
100
|
+
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
|
|
101
|
+
});
|
|
102
|
+
if (!listResp.ok) throw await httpError(listResp, `List files for ${jobId}`);
|
|
103
|
+
const { slug, files: all } = await listResp.json();
|
|
104
|
+
const files = options.includeScreenshots ? all : all.filter((f) => !isScreenshot(f.path));
|
|
105
|
+
const trials = new Set(files.map((f) => f.path.split("/")[0]).filter((p) => p && !p.endsWith(".json") && !p.endsWith(".log")));
|
|
106
|
+
return {
|
|
107
|
+
jobId,
|
|
108
|
+
slug,
|
|
109
|
+
files,
|
|
110
|
+
bytes: files.reduce((s, f) => s + f.size, 0),
|
|
111
|
+
trials: trials.size,
|
|
112
|
+
trajectoryFiles: files.filter((f) => f.path.endsWith("/agent/trajectory.json")).length,
|
|
113
|
+
screenshotsSkipped: all.length - files.length
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async function downloadPlannedJob(plan, outputDir, options) {
|
|
117
|
+
const apiUrl = getApiUrl();
|
|
118
|
+
const auth = await getAuthHeader();
|
|
119
|
+
const headers = {};
|
|
120
|
+
if (auth) headers["Authorization"] = auth;
|
|
121
|
+
const { jobId, files } = plan;
|
|
122
|
+
const log = options.quiet ? () => {
|
|
123
|
+
} : (m) => console.log(m);
|
|
124
|
+
const packedTrials = new Set(files.filter((f) => f.path.includes(`/${CHUNK_DIR}/`)).map((f) => f.path.split("/")[0]));
|
|
125
|
+
log(` Job: ${plan.slug} (${jobId})`);
|
|
126
|
+
log(
|
|
127
|
+
` ${files.length} files, ${(plan.bytes / 1024 / 1024).toFixed(1)} MB` + (packedTrials.size > 0 ? ` (screenshots of ${packedTrials.size} trial${packedTrials.size === 1 ? "" : "s"} packed)` : "") + (plan.screenshotsSkipped > 0 ? ` (${plan.screenshotsSkipped} screenshot files skipped; --include-screenshots to fetch)` : "")
|
|
128
|
+
);
|
|
129
|
+
mkdirSync(outputDir, { recursive: true });
|
|
130
|
+
const bar = options.quiet ? { tick: (_n = 0) => {
|
|
131
|
+
}, done: () => {
|
|
132
|
+
} } : new (await import("./progress-H7WNKHDR.js")).ProgressBar({ total: files.length, totalBytes: plan.bytes, label: "\u2193" });
|
|
133
|
+
let downloaded = 0;
|
|
134
|
+
let downloadedBytes = 0;
|
|
135
|
+
const failed = [];
|
|
136
|
+
for (let batchStart = 0; batchStart < files.length; batchStart += BATCH_SIZE) {
|
|
137
|
+
const batch = files.slice(batchStart, batchStart + BATCH_SIZE);
|
|
138
|
+
const urlResp = await fetch(`${apiUrl}/api/cli/download/${jobId}/signed-urls`, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
141
|
+
body: JSON.stringify({ paths: batch.map((f) => f.path) }),
|
|
142
|
+
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS)
|
|
143
|
+
});
|
|
144
|
+
const urlMap = /* @__PURE__ */ new Map();
|
|
145
|
+
if (urlResp.ok) {
|
|
146
|
+
const { urls } = await urlResp.json();
|
|
147
|
+
for (const u of urls) if (u.signed_url) urlMap.set(u.path, u.signed_url);
|
|
148
|
+
}
|
|
149
|
+
let next = 0;
|
|
150
|
+
const worker = async () => {
|
|
151
|
+
while (next < batch.length) {
|
|
152
|
+
const file = batch[next++];
|
|
153
|
+
const signedUrl = urlMap.get(file.path);
|
|
154
|
+
const downloadUrl = signedUrl ?? `${apiUrl}/api/cli/download/${jobId}/file?path=${encodeURIComponent(file.path)}`;
|
|
155
|
+
const downloadHeaders = signedUrl ? {} : headers;
|
|
156
|
+
let ok = false;
|
|
157
|
+
for (let attempt = 0; attempt < 3 && !ok; attempt++) {
|
|
158
|
+
try {
|
|
159
|
+
const resp = await fetch(downloadUrl, { headers: downloadHeaders, signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
|
|
160
|
+
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
161
|
+
const data = Buffer.from(await resp.arrayBuffer());
|
|
162
|
+
const outPath = join(outputDir, file.path);
|
|
163
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
164
|
+
writeFileSync(outPath, data);
|
|
165
|
+
downloaded++;
|
|
166
|
+
downloadedBytes += data.length;
|
|
167
|
+
bar.tick(data.length);
|
|
168
|
+
ok = true;
|
|
169
|
+
} catch {
|
|
170
|
+
if (attempt < 2) await new Promise((r) => setTimeout(r, 1e3 * (attempt + 1)));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!ok) {
|
|
174
|
+
failed.push(file.path);
|
|
175
|
+
bar.tick(0);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, batch.length) }, worker));
|
|
180
|
+
}
|
|
181
|
+
bar.done();
|
|
182
|
+
let screenshotsUnpacked = 0;
|
|
183
|
+
if (packedTrials.size > 0) {
|
|
184
|
+
const unpacked = unpackScreenshotChunks(outputDir);
|
|
185
|
+
screenshotsUnpacked = unpacked.imagesWritten;
|
|
186
|
+
if (unpacked.imagesWritten > 0) {
|
|
187
|
+
log(` \u2713 Unpacked ${unpacked.imagesWritten} screenshots across ${unpacked.trialsUnpacked} trial${unpacked.trialsUnpacked === 1 ? "" : "s"}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return { ...plan, outputDir, downloaded, downloadedBytes, failed, screenshotsUnpacked };
|
|
191
|
+
}
|
|
192
|
+
async function downloadJob(input, outputDir, options = { includeScreenshots: true, quiet: false }) {
|
|
193
|
+
const plan = await planJob(input, options);
|
|
194
|
+
return downloadPlannedJob(plan, outputDir, options);
|
|
195
|
+
}
|
|
196
|
+
function writeJsonl(rootDir, jobDirs, outFile) {
|
|
197
|
+
const lines = [];
|
|
198
|
+
for (const job of jobDirs) {
|
|
199
|
+
if (!existsSync(job.dir)) continue;
|
|
200
|
+
for (const trial of readdirSync(job.dir, { withFileTypes: true })) {
|
|
201
|
+
if (!trial.isDirectory()) continue;
|
|
202
|
+
const trajPath = join(job.dir, trial.name, "agent", "trajectory.json");
|
|
203
|
+
if (!existsSync(trajPath)) continue;
|
|
204
|
+
let result = null;
|
|
205
|
+
const resultPath = join(job.dir, trial.name, "result.json");
|
|
206
|
+
if (existsSync(resultPath)) {
|
|
207
|
+
try {
|
|
208
|
+
result = JSON.parse(readFileSync(resultPath, "utf-8"));
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const trajectory = JSON.parse(readFileSync(trajPath, "utf-8"));
|
|
213
|
+
lines.push(JSON.stringify({ job_id: job.jobId, job_slug: job.slug, trial_name: trial.name, result, trajectory }));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
mkdirSync(dirname(outFile), { recursive: true });
|
|
217
|
+
writeFileSync(outFile, lines.map((l) => l + "\n").join(""));
|
|
218
|
+
return lines.length;
|
|
219
|
+
}
|
|
220
|
+
function dirBytes(dir) {
|
|
221
|
+
if (!existsSync(dir)) return 0;
|
|
222
|
+
let total = 0;
|
|
223
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
224
|
+
const p = join(dir, e.name);
|
|
225
|
+
total += e.isDirectory() ? dirBytes(p) : statSync(p).size;
|
|
226
|
+
}
|
|
227
|
+
return total;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export {
|
|
231
|
+
DOWNLOAD_TIMEOUT_MS,
|
|
232
|
+
EXIT,
|
|
233
|
+
CliError,
|
|
234
|
+
httpError,
|
|
235
|
+
parseJobId,
|
|
236
|
+
parseProjectId,
|
|
237
|
+
planJob,
|
|
238
|
+
downloadPlannedJob,
|
|
239
|
+
downloadJob,
|
|
240
|
+
writeJsonl,
|
|
241
|
+
dirBytes
|
|
242
|
+
};
|