gitnexus 1.6.8-rc.13 → 1.6.8-rc.15

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.
Files changed (47) hide show
  1. package/dist/server/analyze-launch.d.ts +29 -0
  2. package/dist/server/analyze-launch.js +137 -0
  3. package/dist/server/analyze-upload.d.ts +33 -0
  4. package/dist/server/analyze-upload.js +123 -0
  5. package/dist/server/analyze-worker.d.ts +17 -1
  6. package/dist/server/api.js +46 -140
  7. package/dist/server/git-clone.d.ts +1 -0
  8. package/dist/server/git-clone.js +1 -1
  9. package/dist/server/middleware.d.ts +11 -0
  10. package/dist/server/middleware.js +27 -0
  11. package/dist/server/upload-ingest.d.ts +56 -0
  12. package/dist/server/upload-ingest.js +276 -0
  13. package/dist/server/upload-paths.d.ts +31 -0
  14. package/dist/server/upload-paths.js +51 -0
  15. package/dist/server/upload-sweep.d.ts +20 -0
  16. package/dist/server/upload-sweep.js +57 -0
  17. package/package.json +3 -1
  18. package/web/assets/{agent-CKPMqImC.js → agent-B0-_Ihgi.js} +1 -1
  19. package/web/assets/{architectureDiagram-UL44E2DR-D1EXI0zA.js → architectureDiagram-UL44E2DR-PdDPNXTO.js} +1 -1
  20. package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-u4-0IIru.js} +1 -1
  21. package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV--R3bV0KH.js} +1 -1
  22. package/web/assets/{classDiagram-KGZ6W3CR-Bv93af_b.js → classDiagram-KGZ6W3CR-CLDcI2Tc.js} +1 -1
  23. package/web/assets/{classDiagram-v2-72OJOZXJ-CLfEqHUa.js → classDiagram-v2-72OJOZXJ-dwefssU_.js} +1 -1
  24. package/web/assets/{diagram-3NCE3AQN-BqAtKUpW.js → diagram-3NCE3AQN-DMYhnaiQ.js} +1 -1
  25. package/web/assets/{diagram-GF46GFSD-CaBG6n6o.js → diagram-GF46GFSD-CgWD3Go0.js} +1 -1
  26. package/web/assets/{diagram-QXG6HAR7-CZ-O3rcV.js → diagram-QXG6HAR7-Bwu9Wi_f.js} +1 -1
  27. package/web/assets/{diagram-WEQXMOUZ-P4lSL4GH.js → diagram-WEQXMOUZ-CaxeST-3.js} +1 -1
  28. package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-WlblWt40.js} +1 -1
  29. package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-CRrYkD90.js} +1 -1
  30. package/web/assets/index-COMMmbxW.css +2 -0
  31. package/web/assets/index-D_6rUFGW.js +626 -0
  32. package/web/assets/{infoDiagram-3YFTVSEB-DmLICZx1.js → infoDiagram-3YFTVSEB-pvT6ArHz.js} +1 -1
  33. package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-D_0fBKRi.js} +1 -1
  34. package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-DGABptDC.js} +1 -1
  35. package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-r-bFdhPw.js} +1 -1
  36. package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-CS3o1Ug4.js} +1 -1
  37. package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BMYGXwjH.js} +1 -1
  38. package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-BJnTiLwO.js} +1 -1
  39. package/web/assets/{stateDiagram-7D4R322I-CB2nABwH.js → stateDiagram-7D4R322I-G35bS8CQ.js} +1 -1
  40. package/web/assets/{stateDiagram-v2-36443NZ5-COBGd2RL.js → stateDiagram-v2-36443NZ5-BUaZpLs6.js} +1 -1
  41. package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-B9o1j771.js} +1 -1
  42. package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-Vz5Sin3H.js} +1 -1
  43. package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-D42Nu4nI.js} +1 -1
  44. package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-CSImZ1y9.js} +1 -1
  45. package/web/index.html +2 -2
  46. package/web/assets/index-BKWA-m7o.css +0 -2
  47. package/web/assets/index-CG6q8eTs.js +0 -626
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Secure ingestion of a browser folder upload (multipart/form-data).
3
+ *
4
+ * Replaces the path-injection-prone GET /api/fs/list directory listing. The
5
+ * client streams the selected files plus a JSON `manifest` of their
6
+ * webkitRelativePaths; we write each into an mkdtemp staging dir under
7
+ * UPLOAD_ROOT with PROVABLE containment (resolve-then-contain), hard resource
8
+ * caps, and guaranteed cleanup on every failure/abort path. No client value
9
+ * ever reaches a filesystem READ — the server only writes into a sandbox it
10
+ * created, then hands that sandbox to the analysis pipeline.
11
+ *
12
+ * Security references: CodeQL js/path-injection (resolve + startsWith(root+sep)),
13
+ * OWASP File Upload / Path Traversal.
14
+ */
15
+ import type { IncomingMessage } from 'http';
16
+ export interface IngestLimits {
17
+ /** Aggregate bytes across all files (busboy has no aggregate limit). */
18
+ maxTotalBytes: number;
19
+ /** Per-file byte cap. */
20
+ maxFileBytes: number;
21
+ /** Maximum number of files. */
22
+ maxFiles: number;
23
+ /** Maximum multipart parts (files + fields). */
24
+ maxParts: number;
25
+ /** Maximum directories created (inode-exhaustion guard). */
26
+ maxDirs: number;
27
+ /** Maximum size of the manifest field. */
28
+ maxFieldBytes: number;
29
+ }
30
+ export declare const DEFAULT_INGEST_LIMITS: IngestLimits;
31
+ export interface IngestResult {
32
+ /** Absolute path to the populated staging directory (realpath-canonical). */
33
+ stageRoot: string;
34
+ fileCount: number;
35
+ totalBytes: number;
36
+ /** First path segment shared by the uploaded tree (the picked folder). */
37
+ topLevelName: string;
38
+ }
39
+ /**
40
+ * Resolve a client-provided relative path to an absolute destination PROVABLY
41
+ * contained within `stageRoot`. Throws BadRequestError on any unsafe input.
42
+ * This is the load-bearing path-traversal-on-write control; keep it pure and
43
+ * unit-tested.
44
+ */
45
+ export declare function resolveContainedDest(stageRoot: string, rel: unknown): string;
46
+ export interface IngestOptions {
47
+ /** Override the staging parent dir (defaults to UPLOAD_ROOT; for tests). */
48
+ root?: string;
49
+ }
50
+ /**
51
+ * Parse and securely write a multipart folder upload into a fresh staging
52
+ * directory under UPLOAD_ROOT. Resolves with the populated staging dir, or
53
+ * rejects with a BadRequestError (status 400/413) after removing the staging
54
+ * dir. The caller owns promotion + cleanup of the returned `stageRoot`.
55
+ */
56
+ export declare function ingestUpload(req: IncomingMessage, limitsOverride?: Partial<IngestLimits>, opts?: IngestOptions): Promise<IngestResult>;
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Secure ingestion of a browser folder upload (multipart/form-data).
3
+ *
4
+ * Replaces the path-injection-prone GET /api/fs/list directory listing. The
5
+ * client streams the selected files plus a JSON `manifest` of their
6
+ * webkitRelativePaths; we write each into an mkdtemp staging dir under
7
+ * UPLOAD_ROOT with PROVABLE containment (resolve-then-contain), hard resource
8
+ * caps, and guaranteed cleanup on every failure/abort path. No client value
9
+ * ever reaches a filesystem READ — the server only writes into a sandbox it
10
+ * created, then hands that sandbox to the analysis pipeline.
11
+ *
12
+ * Security references: CodeQL js/path-injection (resolve + startsWith(root+sep)),
13
+ * OWASP File Upload / Path Traversal.
14
+ */
15
+ import path from 'path';
16
+ import fs from 'fs';
17
+ import fsp from 'fs/promises';
18
+ import busboy from 'busboy';
19
+ import { UPLOAD_ROOT, STAGING_PREFIX } from './upload-paths.js';
20
+ import { BadRequestError } from './validation.js';
21
+ export const DEFAULT_INGEST_LIMITS = {
22
+ maxTotalBytes: 250 * 1024 * 1024,
23
+ maxFileBytes: 25 * 1024 * 1024,
24
+ maxFiles: 20000,
25
+ maxParts: 20100,
26
+ maxDirs: 50000,
27
+ maxFieldBytes: 2 * 1024 * 1024,
28
+ };
29
+ const MAX_PATH_DEPTH = 64;
30
+ const MAX_PATH_LENGTH = 4096;
31
+ /**
32
+ * Resolve a client-provided relative path to an absolute destination PROVABLY
33
+ * contained within `stageRoot`. Throws BadRequestError on any unsafe input.
34
+ * This is the load-bearing path-traversal-on-write control; keep it pure and
35
+ * unit-tested.
36
+ */
37
+ export function resolveContainedDest(stageRoot, rel) {
38
+ if (typeof rel !== 'string' || rel.length === 0) {
39
+ throw new BadRequestError('Invalid upload path');
40
+ }
41
+ if (rel.length > MAX_PATH_LENGTH) {
42
+ throw new BadRequestError('Upload path too long');
43
+ }
44
+ // webkitRelativePath is always relative; a leading slash is absolute/hostile.
45
+ if (rel.startsWith('/')) {
46
+ throw new BadRequestError('Invalid upload path');
47
+ }
48
+ // Browsers emit forward slashes only; a NUL byte or backslash is hostile.
49
+ if (rel.includes('\u0000') || rel.includes('\\')) {
50
+ throw new BadRequestError('Invalid upload path');
51
+ }
52
+ const rawSegments = rel.split('/').filter((s) => s.length > 0);
53
+ if (rawSegments.length === 0 || rawSegments.length > MAX_PATH_DEPTH) {
54
+ throw new BadRequestError('Invalid upload path');
55
+ }
56
+ const segments = [];
57
+ for (const seg of rawSegments) {
58
+ // Normalize so NFC/NFD variants don't collide silently on case/unicode
59
+ // -folding filesystems (macOS/Windows).
60
+ const s = seg.normalize('NFC');
61
+ if (s === '.' || s === '..') {
62
+ throw new BadRequestError('Upload path must not contain traversal segments');
63
+ }
64
+ segments.push(s);
65
+ }
66
+ const dest = path.resolve(stageRoot, segments.join(path.sep));
67
+ // Suffix path.sep so a sibling prefix (/sandbox-evil vs /sandbox) can't pass.
68
+ if (dest !== stageRoot && !dest.startsWith(stageRoot + path.sep)) {
69
+ throw new BadRequestError('Upload path escapes the sandbox');
70
+ }
71
+ return dest;
72
+ }
73
+ /**
74
+ * Create the parent directories of `destFile` one segment at a time, asserting
75
+ * after each `mkdir` that the segment is a real directory (not a symlink
76
+ * swapped in mid-stream) still inside `stageRoot`. Counts created dirs against
77
+ * the inode-exhaustion cap.
78
+ */
79
+ function mkdirContained(stageRoot, destFile, state) {
80
+ const parent = path.dirname(destFile);
81
+ const relParent = path.relative(stageRoot, parent);
82
+ if (relParent === '' || relParent === '.')
83
+ return;
84
+ const segs = relParent.split(path.sep).filter(Boolean);
85
+ let cur = stageRoot;
86
+ for (const seg of segs) {
87
+ cur = path.join(cur, seg);
88
+ let made = false;
89
+ try {
90
+ fs.mkdirSync(cur);
91
+ made = true;
92
+ }
93
+ catch (err) {
94
+ if (err.code !== 'EEXIST')
95
+ throw err;
96
+ }
97
+ const st = fs.lstatSync(cur);
98
+ if (st.isSymbolicLink() || !st.isDirectory()) {
99
+ throw new BadRequestError('Upload path escapes the sandbox');
100
+ }
101
+ if (made) {
102
+ state.dirCount++;
103
+ if (state.dirCount > state.limits.maxDirs) {
104
+ throw new BadRequestError('Too many directories in upload', 413);
105
+ }
106
+ }
107
+ }
108
+ }
109
+ /**
110
+ * Parse and securely write a multipart folder upload into a fresh staging
111
+ * directory under UPLOAD_ROOT. Resolves with the populated staging dir, or
112
+ * rejects with a BadRequestError (status 400/413) after removing the staging
113
+ * dir. The caller owns promotion + cleanup of the returned `stageRoot`.
114
+ */
115
+ export async function ingestUpload(req, limitsOverride, opts = {}) {
116
+ const limits = { ...DEFAULT_INGEST_LIMITS, ...limitsOverride };
117
+ const uploadRoot = opts.root ?? UPLOAD_ROOT;
118
+ await fsp.mkdir(uploadRoot, { recursive: true });
119
+ // mkdtemp creates the dir mode 0o700 (owner-only); realpath canonicalizes
120
+ // the root so the containment prefix check is exact.
121
+ const stageRoot = await fsp.realpath(await fsp.mkdtemp(path.join(uploadRoot, STAGING_PREFIX)));
122
+ let cleaned = false;
123
+ const cleanup = async () => {
124
+ if (cleaned)
125
+ return;
126
+ cleaned = true;
127
+ await fsp.rm(stageRoot, { recursive: true, force: true }).catch(() => { });
128
+ };
129
+ return new Promise((resolve, reject) => {
130
+ let settled = false;
131
+ let manifest = null;
132
+ let fileIndex = 0;
133
+ let fileCount = 0;
134
+ let totalBytes = 0;
135
+ let topLevelName = '';
136
+ const dirState = { dirCount: 0, limits };
137
+ const writePromises = [];
138
+ const bb = busboy({
139
+ headers: req.headers,
140
+ limits: {
141
+ fileSize: limits.maxFileBytes,
142
+ files: limits.maxFiles,
143
+ parts: limits.maxParts,
144
+ fields: 10,
145
+ fieldNameSize: 200,
146
+ fieldSize: limits.maxFieldBytes,
147
+ headerPairs: 2000,
148
+ },
149
+ });
150
+ const fail = (err) => {
151
+ if (settled)
152
+ return;
153
+ settled = true;
154
+ try {
155
+ req.unpipe(bb);
156
+ }
157
+ catch {
158
+ /* ignore */
159
+ }
160
+ try {
161
+ req.resume(); // drain remaining body so the socket isn't left hanging
162
+ }
163
+ catch {
164
+ /* ignore */
165
+ }
166
+ void cleanup().finally(() => reject(err));
167
+ };
168
+ bb.on('field', (name, val) => {
169
+ if (name !== 'manifest')
170
+ return;
171
+ try {
172
+ const parsed = JSON.parse(val);
173
+ if (!Array.isArray(parsed) || !parsed.every((p) => typeof p === 'string')) {
174
+ return fail(new BadRequestError('Invalid manifest'));
175
+ }
176
+ manifest = parsed;
177
+ }
178
+ catch {
179
+ fail(new BadRequestError('Invalid manifest'));
180
+ }
181
+ });
182
+ bb.on('file', (_name, stream, _info) => {
183
+ if (settled) {
184
+ stream.resume();
185
+ return;
186
+ }
187
+ if (manifest === null) {
188
+ // The manifest field MUST arrive before any file part.
189
+ stream.resume();
190
+ return fail(new BadRequestError('Manifest must precede file parts'));
191
+ }
192
+ const idx = fileIndex++;
193
+ const rel = manifest[idx];
194
+ let dest;
195
+ try {
196
+ dest = resolveContainedDest(stageRoot, rel);
197
+ // A folder upload is exactly one top-level directory: every entry must
198
+ // have ≥2 segments and share the same first segment. This rejects a
199
+ // bare file at the root (which would make the promote target a file)
200
+ // and a multi-top manifest (which would silently drop all but the
201
+ // first folder). Validated here, before any job is created.
202
+ const segs = String(rel)
203
+ .split('/')
204
+ .filter((s) => s.length > 0);
205
+ const firstSeg = (segs[0] ?? '').normalize('NFC');
206
+ if (!topLevelName) {
207
+ topLevelName = firstSeg;
208
+ }
209
+ if (segs.length < 2 || firstSeg !== topLevelName) {
210
+ throw new BadRequestError('Upload must be a single folder of files');
211
+ }
212
+ mkdirContained(stageRoot, dest, dirState);
213
+ }
214
+ catch (err) {
215
+ stream.resume();
216
+ return fail(err);
217
+ }
218
+ fileCount++;
219
+ const ws = fs.createWriteStream(dest, { flags: 'wx' });
220
+ const p = new Promise((res, rej) => {
221
+ stream.on('data', (chunk) => {
222
+ totalBytes += chunk.length;
223
+ if (totalBytes > limits.maxTotalBytes) {
224
+ stream.unpipe(ws);
225
+ ws.destroy();
226
+ rej(new BadRequestError('Upload exceeds total size limit', 413));
227
+ }
228
+ });
229
+ stream.on('limit', () => {
230
+ ws.destroy();
231
+ rej(new BadRequestError('File exceeds size limit', 413));
232
+ });
233
+ stream.on('error', rej);
234
+ ws.on('error', rej);
235
+ ws.on('finish', () => res());
236
+ stream.pipe(ws);
237
+ });
238
+ writePromises.push(p);
239
+ p.catch(fail);
240
+ });
241
+ bb.on('filesLimit', () => fail(new BadRequestError('Too many files in upload', 413)));
242
+ bb.on('partsLimit', () => fail(new BadRequestError('Too many parts in upload', 413)));
243
+ bb.on('fieldsLimit', () => fail(new BadRequestError('Too many fields in upload')));
244
+ bb.on('error', (err) => fail(err instanceof Error ? err : new BadRequestError('Upload parse error')));
245
+ bb.on('close', () => {
246
+ if (settled)
247
+ return;
248
+ Promise.all(writePromises)
249
+ .then(() => {
250
+ if (settled)
251
+ return;
252
+ if (manifest === null)
253
+ return fail(new BadRequestError('Missing manifest'));
254
+ if (fileCount === 0)
255
+ return fail(new BadRequestError('Empty upload'));
256
+ if (fileCount !== manifest.length) {
257
+ return fail(new BadRequestError('Manifest/file count mismatch'));
258
+ }
259
+ if (!topLevelName) {
260
+ return fail(new BadRequestError('Could not determine upload folder name'));
261
+ }
262
+ settled = true;
263
+ resolve({ stageRoot, fileCount, totalBytes, topLevelName });
264
+ })
265
+ .catch(() => {
266
+ /* a write rejected → fail() already invoked via p.catch */
267
+ });
268
+ });
269
+ req.on('aborted', () => {
270
+ if (!settled)
271
+ fail(new BadRequestError('Upload aborted'));
272
+ });
273
+ req.on('error', (err) => fail(err instanceof Error ? err : new BadRequestError('Request error')));
274
+ req.pipe(bb);
275
+ });
276
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Upload working-directory paths.
3
+ *
4
+ * Browser folder uploads are written into ~/.gitnexus/uploads/{name}/ — a
5
+ * sibling of the clone root (git-clone.ts CLONE_ROOT) — so an uploaded repo
6
+ * persists and behaves like a cloned one (the graph UI's /api/file reads its
7
+ * files after analysis, and DELETE /api/repo removes it). Staging happens in
8
+ * an mkdtemp dir *under* UPLOAD_ROOT so the promote rename stays on one
9
+ * filesystem and remains atomic (a rename from os.tmpdir() could trip EXDEV —
10
+ * the exact Docker case this feature targets; see bridge-db.ts for the same
11
+ * anchored-staging pattern).
12
+ */
13
+ /** Root directory for all uploaded repositories. Targets must resolve inside this. */
14
+ export declare const UPLOAD_ROOT: string;
15
+ /** Prefix for per-upload staging directories created under UPLOAD_ROOT. */
16
+ export declare const STAGING_PREFIX = ".staging-";
17
+ /**
18
+ * Get the upload target directory for a repo name.
19
+ *
20
+ * Re-validates at the boundary (callers may derive the name from an untrusted
21
+ * manifest). Rejects `.`, `..`, the `'unknown'` sentinel that sanitizeRepoName
22
+ * emits for un-nameable inputs, names beginning with `.` (which would collide
23
+ * with the `.staging-` prefix), and anything outside the safe charset.
24
+ */
25
+ export declare function getUploadDir(repoName: string): string;
26
+ /**
27
+ * Derive a filesystem-safe upload directory name from the manifest's
28
+ * top-level folder. Returns null when the name is un-nameable (so the caller
29
+ * rejects with 400 rather than colliding everyone on `UPLOAD_ROOT/unknown`).
30
+ */
31
+ export declare function deriveUploadName(topLevelName: string): string | null;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Upload working-directory paths.
3
+ *
4
+ * Browser folder uploads are written into ~/.gitnexus/uploads/{name}/ — a
5
+ * sibling of the clone root (git-clone.ts CLONE_ROOT) — so an uploaded repo
6
+ * persists and behaves like a cloned one (the graph UI's /api/file reads its
7
+ * files after analysis, and DELETE /api/repo removes it). Staging happens in
8
+ * an mkdtemp dir *under* UPLOAD_ROOT so the promote rename stays on one
9
+ * filesystem and remains atomic (a rename from os.tmpdir() could trip EXDEV —
10
+ * the exact Docker case this feature targets; see bridge-db.ts for the same
11
+ * anchored-staging pattern).
12
+ */
13
+ import path from 'path';
14
+ import os from 'os';
15
+ import { sanitizeRepoName } from '../storage/git.js';
16
+ import { REPO_NAME_PATTERN } from './git-clone.js';
17
+ /** Root directory for all uploaded repositories. Targets must resolve inside this. */
18
+ export const UPLOAD_ROOT = path.resolve(path.join(os.homedir(), '.gitnexus', 'uploads'));
19
+ /** Prefix for per-upload staging directories created under UPLOAD_ROOT. */
20
+ export const STAGING_PREFIX = '.staging-';
21
+ /**
22
+ * Get the upload target directory for a repo name.
23
+ *
24
+ * Re-validates at the boundary (callers may derive the name from an untrusted
25
+ * manifest). Rejects `.`, `..`, the `'unknown'` sentinel that sanitizeRepoName
26
+ * emits for un-nameable inputs, names beginning with `.` (which would collide
27
+ * with the `.staging-` prefix), and anything outside the safe charset.
28
+ */
29
+ export function getUploadDir(repoName) {
30
+ if (!repoName ||
31
+ repoName === '.' ||
32
+ repoName === '..' ||
33
+ repoName === 'unknown' ||
34
+ repoName.startsWith('.') ||
35
+ !REPO_NAME_PATTERN.test(repoName)) {
36
+ throw new Error('Invalid repository name');
37
+ }
38
+ return path.join(UPLOAD_ROOT, repoName);
39
+ }
40
+ /**
41
+ * Derive a filesystem-safe upload directory name from the manifest's
42
+ * top-level folder. Returns null when the name is un-nameable (so the caller
43
+ * rejects with 400 rather than colliding everyone on `UPLOAD_ROOT/unknown`).
44
+ */
45
+ export function deriveUploadName(topLevelName) {
46
+ const safe = sanitizeRepoName(topLevelName);
47
+ if (safe === 'unknown' || safe === '.' || safe === '..' || safe.startsWith('.')) {
48
+ return null;
49
+ }
50
+ return safe;
51
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Backstop cleanup for abandoned upload staging directories.
3
+ *
4
+ * A crashed/killed process can leave a `.staging-*` directory under
5
+ * UPLOAD_ROOT (the normal path removes it on success/failure/abort). This
6
+ * sweep, run once at server startup, removes staging dirs older than a
7
+ * threshold. Promoted upload dirs are persistent registered repos (like
8
+ * clones) and are NOT touched here — they are removed via DELETE /api/repo.
9
+ */
10
+ export interface SweepOptions {
11
+ /** Remove staging dirs older than this (default 6h). */
12
+ maxAgeMs?: number;
13
+ /** Override the root to sweep (defaults to UPLOAD_ROOT; for tests). */
14
+ root?: string;
15
+ /** Clock injection for tests. */
16
+ now?: number;
17
+ }
18
+ export declare function sweepStaleUploads(opts?: SweepOptions): Promise<{
19
+ removed: string[];
20
+ }>;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Backstop cleanup for abandoned upload staging directories.
3
+ *
4
+ * A crashed/killed process can leave a `.staging-*` directory under
5
+ * UPLOAD_ROOT (the normal path removes it on success/failure/abort). This
6
+ * sweep, run once at server startup, removes staging dirs older than a
7
+ * threshold. Promoted upload dirs are persistent registered repos (like
8
+ * clones) and are NOT touched here — they are removed via DELETE /api/repo.
9
+ */
10
+ import path from 'path';
11
+ import fsp from 'fs/promises';
12
+ import { UPLOAD_ROOT, STAGING_PREFIX } from './upload-paths.js';
13
+ export async function sweepStaleUploads(opts = {}) {
14
+ const maxAgeMs = opts.maxAgeMs ?? 6 * 60 * 60 * 1000;
15
+ const root = opts.root ?? UPLOAD_ROOT;
16
+ const now = opts.now ?? Date.now();
17
+ const removed = [];
18
+ let entries;
19
+ try {
20
+ entries = await fsp.readdir(root, { withFileTypes: true });
21
+ }
22
+ catch {
23
+ return { removed }; // root does not exist yet — nothing to sweep
24
+ }
25
+ for (const entry of entries) {
26
+ if (!entry.isDirectory())
27
+ continue;
28
+ const full = path.join(root, entry.name);
29
+ try {
30
+ const st = await fsp.stat(full);
31
+ if (now - st.mtimeMs <= maxAgeMs)
32
+ continue; // recent — keep
33
+ if (entry.name.startsWith(STAGING_PREFIX)) {
34
+ // Transient staging dir orphaned by a crash — always removable.
35
+ await fsp.rm(full, { recursive: true, force: true }).catch(() => { });
36
+ removed.push(full);
37
+ }
38
+ else {
39
+ // Promoted upload dir. A successfully-analyzed (registered) repo always
40
+ // has a `.gitnexus` index inside it; a stale promoted dir WITHOUT one is
41
+ // an orphan from an analysis that failed before registering — remove it.
42
+ const hasIndex = await fsp
43
+ .access(path.join(full, '.gitnexus'))
44
+ .then(() => true)
45
+ .catch(() => false);
46
+ if (!hasIndex) {
47
+ await fsp.rm(full, { recursive: true, force: true }).catch(() => { });
48
+ removed.push(full);
49
+ }
50
+ }
51
+ }
52
+ catch {
53
+ /* stat race — skip */
54
+ }
55
+ }
56
+ return { removed };
57
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.8-rc.13",
3
+ "version": "1.6.8-rc.15",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -59,6 +59,7 @@
59
59
  "@ladybugdb/core": "^0.17.0",
60
60
  "@modelcontextprotocol/sdk": "^1.0.0",
61
61
  "@scarf/scarf": "^1.4.0",
62
+ "busboy": "^1.6.0",
62
63
  "cli-progress": "^3.12.0",
63
64
  "commander": "^14.0.3",
64
65
  "cors": "^2.8.5",
@@ -93,6 +94,7 @@
93
94
  "uuid": "^14.0.0"
94
95
  },
95
96
  "devDependencies": {
97
+ "@types/busboy": "^1.5.4",
96
98
  "@types/cli-progress": "^3.11.6",
97
99
  "@types/cors": "^2.8.17",
98
100
  "@types/express": "^5.0.6",
@@ -1,4 +1,4 @@
1
- import{r as e,t}from"./chunk-CilyBKbf.js";import{o as n,s as r}from"./index-CG6q8eTs.js";import{buildDynamicSystemPrompt as i}from"./context-builder-_HS0v2ma.js";var a=Object.defineProperty,o=(e,t)=>{let n={};for(var r in e)a(n,r,{get:e[r],enumerable:!0});return t||a(n,Symbol.toStringTag,{value:`Module`}),n};function s(e){let t=Symbol.for(e);return{brand(n,r){let i=r?Symbol.for(`${e}.${r}`):t;class a extends n{[i]=!0;constructor(...e){super(...e)}static isInstance(e){return typeof e==`object`&&!!e&&i in e&&e[i]===!0}}return Object.defineProperty(a,`name`,{value:n.name}),a},sub(t){return s(`${e}.${t}`)},isInstance(e){return typeof e==`object`&&!!e&&t in e&&e[t]===!0}}}var c=s(`langchain`),l=o({ContextOverflowError:()=>m,LangChainError:()=>f,ModelAbortError:()=>p,addLangChainErrorFields:()=>u,ns:()=>d});function u(e,t){return e.lc_error_code=t,e.message=`${e.message}\n\nTroubleshooting URL: https://docs.langchain.com/oss/javascript/langchain/errors/${t}/\n`,e}var d=c.sub(`error`),f=class extends d.brand(Error){name=`LangChainError`;constructor(e){super(e),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}},p=class extends d.brand(f,`model-abort`){name=`ModelAbortError`;partialOutput;constructor(e,t){super(e),this.partialOutput=t}},m=class e extends d.brand(f,`context-overflow`){name=`ContextOverflowError`;cause;constructor(e){super(e??`Input exceeded the model's context window.`)}static fromError(t){let n=new e(t.message);return n.cause=t,n}};function h(e){return!!(e&&typeof e==`object`&&`type`in e&&e.type===`tool_call`)}function g(e){return!!(e&&typeof e==`object`&&`toolCall`in e&&e.toolCall!=null&&typeof e.toolCall==`object`&&`id`in e.toolCall&&typeof e.toolCall.id==`string`)}var _=class extends Error{output;constructor(e,t){super(e),this.output=t}};function v(e,t=b){e=e.trim();let n=e.indexOf("```");if(n===-1)return t(e);let r=e.substring(n+3);r.startsWith(`json
1
+ import{r as e,t}from"./chunk-CilyBKbf.js";import{o as n,s as r}from"./index-D_6rUFGW.js";import{buildDynamicSystemPrompt as i}from"./context-builder-_HS0v2ma.js";var a=Object.defineProperty,o=(e,t)=>{let n={};for(var r in e)a(n,r,{get:e[r],enumerable:!0});return t||a(n,Symbol.toStringTag,{value:`Module`}),n};function s(e){let t=Symbol.for(e);return{brand(n,r){let i=r?Symbol.for(`${e}.${r}`):t;class a extends n{[i]=!0;constructor(...e){super(...e)}static isInstance(e){return typeof e==`object`&&!!e&&i in e&&e[i]===!0}}return Object.defineProperty(a,`name`,{value:n.name}),a},sub(t){return s(`${e}.${t}`)},isInstance(e){return typeof e==`object`&&!!e&&t in e&&e[t]===!0}}}var c=s(`langchain`),l=o({ContextOverflowError:()=>m,LangChainError:()=>f,ModelAbortError:()=>p,addLangChainErrorFields:()=>u,ns:()=>d});function u(e,t){return e.lc_error_code=t,e.message=`${e.message}\n\nTroubleshooting URL: https://docs.langchain.com/oss/javascript/langchain/errors/${t}/\n`,e}var d=c.sub(`error`),f=class extends d.brand(Error){name=`LangChainError`;constructor(e){super(e),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}},p=class extends d.brand(f,`model-abort`){name=`ModelAbortError`;partialOutput;constructor(e,t){super(e),this.partialOutput=t}},m=class e extends d.brand(f,`context-overflow`){name=`ContextOverflowError`;cause;constructor(e){super(e??`Input exceeded the model's context window.`)}static fromError(t){let n=new e(t.message);return n.cause=t,n}};function h(e){return!!(e&&typeof e==`object`&&`type`in e&&e.type===`tool_call`)}function g(e){return!!(e&&typeof e==`object`&&`toolCall`in e&&e.toolCall!=null&&typeof e.toolCall==`object`&&`id`in e.toolCall&&typeof e.toolCall.id==`string`)}var _=class extends Error{output;constructor(e,t){super(e),this.output=t}};function v(e,t=b){e=e.trim();let n=e.indexOf("```");if(n===-1)return t(e);let r=e.substring(n+3);r.startsWith(`json
2
2
  `)?r=r.substring(5):r.startsWith(`json`)?r=r.substring(4):r.startsWith(`
3
3
  `)&&(r=r.substring(1));let i=r.indexOf("```"),a=r;return i!==-1&&(a=r.substring(0,i)),t(a.trim())}function y(e){try{return JSON.parse(e)}catch{}let t=e.trim();if(t.length===0)throw Error(`Unexpected end of JSON input`);let n=0;function r(){for(;n<t.length&&/\s/.test(t[n]);)n+=1}function i(){if(t[n]!==`"`)throw Error(`Expected '"' at position ${n}, got '${t[n]}'`);n+=1;let e=``,r=!1;for(;n<t.length;){let i=t[n];if(r){if(i===`n`)e+=`
4
4
  `;else if(i===`t`)e+=` `;else if(i===`r`)e+=`\r`;else if(i===`\\`)e+=`\\`;else if(i===`"`)e+=`"`;else if(i===`b`)e+=`\b`;else if(i===`f`)e+=`\f`;else if(i===`/`)e+=`/`;else if(i===`u`){let r=t.substring(n+1,n+5);if(/^[0-9A-Fa-f]{0,4}$/.test(r))r.length===4?e+=String.fromCharCode(Number.parseInt(r,16)):e+=`u${r}`,n+=r.length;else throw Error(`Invalid unicode escape sequence '\\u${r}' at position ${n}`)}else throw Error(`Invalid escape sequence '\\${i}' at position ${n}`);r=!1}else if(i===`\\`)r=!0;else if(i===`"`)return n+=1,e;else e+=i;n+=1}return r&&(e+=`\\`),e}function a(){let e=n,r=``;if(t[n]===`-`&&(r+=`-`,n+=1),n<t.length&&t[n]===`0`&&(r+=`0`,n+=1,t[n]>=`0`&&t[n]<=`9`))throw Error(`Invalid number at position ${e}`);if(n<t.length&&t[n]>=`1`&&t[n]<=`9`)for(;n<t.length&&t[n]>=`0`&&t[n]<=`9`;)r+=t[n],n+=1;if(n<t.length&&t[n]===`.`)for(r+=`.`,n+=1;n<t.length&&t[n]>=`0`&&t[n]<=`9`;)r+=t[n],n+=1;if(n<t.length&&(t[n]===`e`||t[n]===`E`))for(r+=t[n],n+=1,n<t.length&&(t[n]===`+`||t[n]===`-`)&&(r+=t[n],n+=1);n<t.length&&t[n]>=`0`&&t[n]<=`9`;)r+=t[n],n+=1;if(r===`-`)return-0;let i=Number.parseFloat(r);if(Number.isNaN(i))throw n=e,Error(`Invalid number '${r}' at position ${e}`);return i}function o(){if(r(),n>=t.length)throw Error(`Unexpected end of input at position ${n}`);let e=t[n];if(e===`{`)return c();if(e===`[`)return s();if(e===`"`)return i();if(`null`.startsWith(t.substring(n,n+4)))return n+=Math.min(4,t.length-n),null;if(`true`.startsWith(t.substring(n,n+4)))return n+=Math.min(4,t.length-n),!0;if(`false`.startsWith(t.substring(n,n+5)))return n+=Math.min(5,t.length-n),!1;if(e===`-`||e>=`0`&&e<=`9`)return a();throw Error(`Unexpected character '${e}' at position ${n}`)}function s(){if(t[n]!==`[`)throw Error(`Expected '[' at position ${n}, got '${t[n]}'`);let e=[];if(n+=1,r(),n>=t.length)return e;if(t[n]===`]`)return n+=1,e;for(;n<t.length;){if(r(),n>=t.length||(e.push(o()),r(),n>=t.length))return e;if(t[n]===`]`)return n+=1,e;if(t[n]===`,`){n+=1;continue}throw Error(`Expected ',' or ']' at position ${n}, got '${t[n]}'`)}return e}function c(){if(t[n]!==`{`)throw Error(`Expected '{' at position ${n}, got '${t[n]}'`);let e={};if(n+=1,r(),n>=t.length)return e;if(t[n]===`}`)return n+=1,e;for(;n<t.length;){if(r(),n>=t.length)return e;let a=i();if(r(),n>=t.length)return e;if(t[n]!==`:`)throw Error(`Expected ':' at position ${n}, got '${t[n]}'`);if(n+=1,r(),n>=t.length||(e[a]=o(),r(),n>=t.length))return e;if(t[n]===`}`)return n+=1,e;if(t[n]===`,`){n+=1;continue}throw Error(`Expected ',' or '}' at position ${n}, got '${t[n]}'`)}return e}let l=o();if(r(),n<t.length)throw Error(`Unexpected character '${t[n]}' at position ${n}`);return l}function b(e){try{return e===void 0?null:y(e)}catch{return null}}var ee=/([A-Z]+)([A-Z][a-z0-9]+)/g,te=/([a-z0-9])([A-Z])/g,ne=/[-_\s]+/g;function x(e){return e.replace(ee,`$1_$2`).replace(te,`$1_$2`).replace(ne,`_`).toLowerCase()}function re(e){let t=e.trim();return/[-_\s]/.test(t)?t.replace(ne,`_`).toLowerCase().replace(/_+([a-z0-9])/g,(e,t)=>t.toUpperCase()):t}function ie(e,t){return t?.[e]||x(e)}function ae(e,t){return t?.[e]||re(e)}function oe(e,t,n){let r={};for(let i in e)Object.hasOwn(e,i)&&(r[t(i,n)]=e[i]);return r}var se=`__lc_escaped__`;function ce(e){return`lc`in e||Object.keys(e).length===1&&`__lc_escaped__`in e}function le(e){return{[se]:e}}function S(e){return Object.keys(e).length===1&&`__lc_escaped__`in e}function ue(e){return typeof e==`object`&&!!e&&`lc_serializable`in e&&typeof e.toJSON==`function`}function de(e){let t;return t=typeof e==`object`&&e?`lc_id`in e&&Array.isArray(e.lc_id)?e.lc_id:[e.constructor?.name??`Object`]:[typeof e],{lc:1,type:`not_implemented`,id:t}}function fe(e,t=new WeakSet){if(typeof e==`object`&&e&&!Array.isArray(e)){if(t.has(e))return de(e);if(ue(e))return e;t.add(e);let n=e;if(ce(n))return t.delete(e),le(n);let r={};for(let[e,i]of Object.entries(n))r[e]=fe(i,t);return t.delete(e),r}return Array.isArray(e)?e.map(e=>fe(e,t)):e}function pe(e){if(typeof e==`object`&&e&&!Array.isArray(e)){let t=e;if(S(t))return t[se];let n={};for(let[e,r]of Object.entries(t))n[e]=pe(r);return n}return Array.isArray(e)?e.map(e=>pe(e)):e}var me=o({Serializable:()=>ve,get_lc_unique_name:()=>_e});function he(e){return Array.isArray(e)?[...e]:{...e}}function ge(e,t){let n=he(e);for(let[e,r]of Object.entries(t)){let[t,...i]=e.split(`.`).reverse(),a=n;for(let e of i.reverse()){if(a[e]===void 0)break;a[e]=he(a[e]),a=a[e]}a[t]!==void 0&&(a[t]={lc:1,type:`secret`,id:[r]})}return n}function _e(e){let t=Object.getPrototypeOf(e);return typeof e.lc_name==`function`&&(typeof t.lc_name!=`function`||e.lc_name()!==t.lc_name())?e.lc_name():e.name}var ve=class e{lc_serializable=!1;lc_kwargs;static lc_name(){return this.name}get lc_id(){return[...this.lc_namespace,_e(this.constructor)]}get lc_secrets(){}get lc_attributes(){}get lc_aliases(){}get lc_serializable_keys(){}constructor(e,...t){this.lc_serializable_keys===void 0?this.lc_kwargs=e??{}:this.lc_kwargs=Object.fromEntries(Object.entries(e||{}).filter(([e])=>this.lc_serializable_keys?.includes(e)))}toJSON(){if(!this.lc_serializable||this.lc_kwargs instanceof e||typeof this.lc_kwargs!=`object`||Array.isArray(this.lc_kwargs))return this.toJSONNotImplemented();let t={},n={},r=Object.keys(this.lc_kwargs).reduce((e,t)=>(e[t]=t in this?this[t]:this.lc_kwargs[t],e),{});for(let e=Object.getPrototypeOf(this);e;e=Object.getPrototypeOf(e))Object.assign(t,Reflect.get(e,`lc_aliases`,this)),Object.assign(n,Reflect.get(e,`lc_secrets`,this)),Object.assign(r,Reflect.get(e,`lc_attributes`,this));Object.keys(n).forEach(e=>{let t=this,n=r,[i,...a]=e.split(`.`).reverse();for(let e of a.reverse()){if(!(e in t)||t[e]===void 0)return;(!(e in n)||n[e]===void 0)&&(typeof t[e]==`object`&&t[e]!=null?n[e]={}:Array.isArray(t[e])&&(n[e]=[])),t=t[e],n=n[e]}i in t&&t[i]!==void 0&&(n[i]=n[i]||t[i])});let i={},a=new WeakSet;a.add(this);for(let[e,t]of Object.entries(r))i[e]=fe(t,a);let o=oe(Object.keys(n).length?ge(i,n):i,ie,t);return{lc:1,type:`constructor`,id:this.lc_id,kwargs:o}}toJSONNotImplemented(){return{lc:1,type:`not_implemented`,id:this.lc_id}}};function ye(e){return typeof e==`object`&&!!e&&`type`in e&&typeof e.type==`string`&&`source_type`in e&&(e.source_type===`url`||e.source_type===`base64`||e.source_type===`text`||e.source_type===`id`)}function be(e){return ye(e)&&e.source_type===`url`&&`url`in e&&typeof e.url==`string`}function xe(e){return ye(e)&&e.source_type===`base64`&&`data`in e&&typeof e.data==`string`}function Se(e){return ye(e)&&e.source_type===`text`&&`text`in e&&typeof e.text==`string`}function Ce(e){return ye(e)&&e.source_type===`id`&&`id`in e&&typeof e.id==`string`}function we(e){if(ye(e)){if(e.source_type===`url`)return{type:`image_url`,image_url:{url:e.url}};if(e.source_type===`base64`){if(!e.mime_type)throw Error(`mime_type key is required for base64 data.`);return{type:`image_url`,image_url:{url:`data:${e.mime_type};base64,${e.data}`}}}}throw Error(`Unsupported source type. Only 'url' and 'base64' are supported.`)}function Te(e){let t=e.split(`;`)[0].split(`/`);if(t.length!==2)throw Error(`Invalid mime type: "${e}" - does not match type/subtype format.`);let n=t[0].trim(),r=t[1].trim();if(n===``||r===``)throw Error(`Invalid mime type: "${e}" - type or subtype is empty.`);let i={};for(let t of e.split(`;`).slice(1)){let n=t.split(`=`);if(n.length!==2)throw Error(`Invalid parameter syntax in mime type: "${e}".`);let r=n[0].trim(),a=n[1].trim();if(r===``)throw Error(`Invalid parameter syntax in mime type: "${e}".`);i[r]=a}return{type:n,subtype:r,parameters:i}}function Ee({dataUrl:e,asTypedArray:t=!1}){let n=e.match(/^data:(\w+\/\w+);base64,([A-Za-z0-9+/]+=*)$/),r;if(n){r=n[1].toLowerCase();let e=t?Uint8Array.from(atob(n[2]),e=>e.charCodeAt(0)):n[2];return{mime_type:r,data:e}}}function De(e,t){if(e.type===`text`){if(!t.fromStandardTextBlock)throw Error(`Converter for ${t.providerName} does not implement \`fromStandardTextBlock\` method.`);return t.fromStandardTextBlock(e)}if(e.type===`image`){if(!t.fromStandardImageBlock)throw Error(`Converter for ${t.providerName} does not implement \`fromStandardImageBlock\` method.`);return t.fromStandardImageBlock(e)}if(e.type===`audio`){if(!t.fromStandardAudioBlock)throw Error(`Converter for ${t.providerName} does not implement \`fromStandardAudioBlock\` method.`);return t.fromStandardAudioBlock(e)}if(e.type===`file`){if(!t.fromStandardFileBlock)throw Error(`Converter for ${t.providerName} does not implement \`fromStandardFileBlock\` method.`);return t.fromStandardFileBlock(e)}throw Error(`Unable to convert content block type '${e.type}' to provider-specific format: not recognized.`)}function C(e,t){return w(e)&&e.type===t}function w(e){return typeof e==`object`&&!!e}function Oe(e){return Array.isArray(e)}function T(e){return typeof e==`string`}function ke(e){return typeof e==`number`}function E(e){return e instanceof Uint8Array}function Ae(e){try{return JSON.parse(e)}catch{return}}var je=e=>e();function Me(e){if(e.type===`char_location`&&T(e.document_title)&&ke(e.start_char_index)&&ke(e.end_char_index)&&T(e.cited_text)){let{document_title:t,start_char_index:n,end_char_index:r,cited_text:i,...a}=e;return{...a,type:`citation`,source:`char`,title:t??void 0,startIndex:n,endIndex:r,citedText:i}}if(e.type===`page_location`&&T(e.document_title)&&ke(e.start_page_number)&&ke(e.end_page_number)&&T(e.cited_text)){let{document_title:t,start_page_number:n,end_page_number:r,cited_text:i,...a}=e;return{...a,type:`citation`,source:`page`,title:t??void 0,startIndex:n,endIndex:r,citedText:i}}if(e.type===`content_block_location`&&T(e.document_title)&&ke(e.start_block_index)&&ke(e.end_block_index)&&T(e.cited_text)){let{document_title:t,start_block_index:n,end_block_index:r,cited_text:i,...a}=e;return{...a,type:`citation`,source:`block`,title:t??void 0,startIndex:n,endIndex:r,citedText:i}}if(e.type===`web_search_result_location`&&T(e.url)&&T(e.title)&&T(e.encrypted_index)&&T(e.cited_text)){let{url:t,title:n,encrypted_index:r,cited_text:i,...a}=e;return{...a,type:`citation`,source:`url`,url:t,title:n,startIndex:Number(r),endIndex:Number(r),citedText:i}}if(e.type===`search_result_location`&&T(e.source)&&T(e.title)&&ke(e.start_block_index)&&ke(e.end_block_index)&&T(e.cited_text)){let{source:t,title:n,start_block_index:r,end_block_index:i,cited_text:a,...o}=e;return{...o,type:`citation`,source:`search`,url:t,title:n??void 0,startIndex:r,endIndex:i,citedText:a}}}function Ne(e){if(C(e,`document`)&&w(e.source)&&`type`in e.source){if(e.source.type===`base64`&&T(e.source.media_type)&&T(e.source.data))return{type:`file`,mimeType:e.source.media_type,data:e.source.data};if(e.source.type===`url`&&T(e.source.url))return{type:`file`,url:e.source.url};if(e.source.type===`file`&&T(e.source.file_id))return{type:`file`,fileId:e.source.file_id};if(e.source.type===`text`&&T(e.source.data))return{type:`file`,mimeType:String(e.source.media_type??`text/plain`),data:e.source.data}}else if(C(e,`image`)&&w(e.source)&&`type`in e.source){if(e.source.type===`base64`&&T(e.source.media_type)&&T(e.source.data))return{type:`image`,mimeType:e.source.media_type,data:e.source.data};if(e.source.type===`url`&&T(e.source.url))return{type:`image`,url:e.source.url};if(e.source.type===`file`&&T(e.source.file_id))return{type:`image`,fileId:e.source.file_id}}}function Pe(e){function*t(){for(let t of e){let e=Ne(t);e?yield e:yield t}}return Array.from(t())}function Fe(e){function*t(){let t=typeof e.content==`string`?[{type:`text`,text:e.content}]:e.content;for(let n of t){if(C(n,`text`)&&T(n.text)){let{text:e,citations:t,...r}=n;if(Oe(t)&&t.length){let n=t.reduce((e,t)=>{let n=Me(t);return n?[...e,n]:e},[]);yield{...r,type:`text`,text:e,annotations:n};continue}else{yield{...r,type:`text`,text:e};continue}}else if(C(n,`thinking`)&&T(n.thinking)){let{thinking:e,signature:t,...r}=n;yield{...r,type:`reasoning`,reasoning:e,signature:t};continue}else if(C(n,`redacted_thinking`)){yield{type:`non_standard`,value:n};continue}else if(C(n,`tool_use`)&&T(n.name)&&T(n.id)){yield{type:`tool_call`,id:n.id,name:n.name,args:n.input};continue}else if(C(n,`input_json_delta`)){if(Le(e)&&e.tool_call_chunks?.length){let t=e.tool_call_chunks[0];yield{type:`tool_call_chunk`,id:t.id,name:t.name,args:t.args,index:t.index};continue}}else if(C(n,`server_tool_use`)&&T(n.name)&&T(n.id)){let{name:e,id:t}=n;if(e===`web_search`){yield{id:t,type:`server_tool_call`,name:`web_search`,args:{query:je(()=>{if(typeof n.input==`string`)return n.input;if(w(n.input)&&T(n.input.query))return n.input.query;if(T(n.partial_json)){let e=Ae(n.partial_json);if(e?.query)return e.query}return``})}};continue}else if(n.name===`code_execution`){yield{id:t,type:`server_tool_call`,name:`code_execution`,args:{code:je(()=>{if(typeof n.input==`string`)return n.input;if(w(n.input)&&T(n.input.code))return n.input.code;if(T(n.partial_json)){let e=Ae(n.partial_json);if(e?.code)return e.code}return``})}};continue}}else if(C(n,`web_search_tool_result`)&&T(n.tool_use_id)&&Oe(n.content)){let{content:e,tool_use_id:t}=n;yield{type:`server_tool_call_result`,name:`web_search`,toolCallId:t,status:`success`,output:{urls:e.reduce((e,t)=>C(t,`web_search_result`)?[...e,t.url]:e,[])}};continue}else if(C(n,`code_execution_tool_result`)&&T(n.tool_use_id)&&w(n.content)){yield{type:`server_tool_call_result`,name:`code_execution`,toolCallId:n.tool_use_id,status:`success`,output:n.content};continue}else if(C(n,`mcp_tool_use`)){yield{id:n.id,type:`server_tool_call`,name:`mcp_tool_use`,args:n.input};continue}else if(C(n,`mcp_tool_result`)&&T(n.tool_use_id)&&w(n.content)){yield{type:`server_tool_call_result`,name:`mcp_tool_use`,toolCallId:n.tool_use_id,status:`success`,output:n.content};continue}else if(C(n,`container_upload`)){yield{type:`server_tool_call`,name:`container_upload`,args:n.input};continue}else if(C(n,`search_result`)){yield{id:n.id,type:`non_standard`,value:n};continue}else if(C(n,`tool_result`)){yield{id:n.id,type:`non_standard`,value:n};continue}else{let e=Ne(n);if(e){yield e;continue}}yield{type:`non_standard`,value:n}}}return Array.from(t())}var Ie={translateContent:Fe,translateContentChunk:Fe};function Le(e){return typeof e?._getType==`function`&&typeof e.concat==`function`&&e._getType()===`ai`}function Re(e){return be(e)?{type:e.type,mimeType:e.mime_type,url:e.url,metadata:e.metadata}:xe(e)?{type:e.type,mimeType:e.mime_type??`application/octet-stream`,data:e.data,metadata:e.metadata}:Ce(e)?{type:e.type,mimeType:e.mime_type,fileId:e.id,metadata:e.metadata}:e}function ze(e){return e.map(Re)}function Be(e){return!!(C(e,`image_url`)&&w(e.image_url)||C(e,`input_audio`)&&w(e.input_audio)||C(e,`file`)&&w(e.file))}function Ve(e){if(C(e,`image_url`)&&w(e.image_url)&&T(e.image_url.url)){let t=Ee({dataUrl:e.image_url.url});return t?{type:`image`,mimeType:t.mime_type,data:t.data}:{type:`image`,url:e.image_url.url}}else if(C(e,`input_audio`)&&w(e.input_audio)&&T(e.input_audio.data)&&T(e.input_audio.format))return{type:`audio`,data:e.input_audio.data,mimeType:`audio/${e.input_audio.format}`};else if(C(e,`file`)&&w(e.file)&&T(e.file.data)){let t=Ee({dataUrl:e.file.data});if(t)return{type:`file`,data:t.data,mimeType:t.mime_type};if(T(e.file.file_id))return{type:`file`,fileId:e.file.file_id}}return e}function He(e){let t=[];typeof e.content==`string`?e.content.length>0&&t.push({type:`text`,text:e.content}):t.push(...We(e.content));for(let n of e.tool_calls??[])t.push({type:`tool_call`,id:n.id,name:n.name,args:n.args});return t}function Ue(e){let t=[];typeof e.content==`string`?e.content.length>0&&t.push({type:`text`,text:e.content}):t.push(...We(e.content));for(let n of e.tool_calls??[])t.push({type:`tool_call`,id:n.id,name:n.name,args:n.args});return t}function We(e){let t=[];for(let n of e)Be(n)?t.push(Ve(n)):t.push(n);return t}function Ge(e){if(e.type===`url_citation`){let{url:t,title:n,start_index:r,end_index:i}=e;return{type:`citation`,url:t,title:n,startIndex:r,endIndex:i}}if(e.type===`file_citation`){let{file_id:t,filename:n,index:r}=e;return{type:`citation`,title:n,startIndex:r,endIndex:r,fileId:t}}return e}function Ke(e){function*t(){w(e.additional_kwargs?.reasoning)&&Oe(e.additional_kwargs.reasoning.summary)&&(yield{type:`reasoning`,reasoning:e.additional_kwargs.reasoning.summary.reduce((e,t)=>w(t)&&T(t.text)?`${e}${t.text}`:e,``)});let t=typeof e.content==`string`?[{type:`text`,text:e.content}]:e.content;for(let e of t)if(C(e,`text`)){let{text:t,annotations:n,phase:r,extras:i,...a}=e,o=w(i)?{...i}:{};T(r)&&(o.phase=r);let s=Object.keys(o).length>0?{extras:o}:{};Array.isArray(n)?yield{...a,...s,type:`text`,text:String(t),annotations:n.map(Ge)}:yield{...a,...s,type:`text`,text:String(t)}}for(let t of e.tool_calls??[])yield{type:`tool_call`,id:t.id,name:t.name,args:t.args};if(w(e.additional_kwargs)&&Oe(e.additional_kwargs.tool_outputs))for(let t of e.additional_kwargs.tool_outputs){if(C(t,`web_search_call`)){let e={};if(w(t.action)&&T(t.action.query)&&(e.query=t.action.query),yield{id:t.id,type:`server_tool_call`,name:`web_search`,args:e},t.status===`completed`||t.status===`failed`){let e={};w(t.action)&&(e.action=t.action),yield{type:`server_tool_call_result`,toolCallId:T(t.id)?t.id:``,status:t.status===`completed`?`success`:`error`,output:e}}continue}else if(C(t,`file_search_call`)){yield{id:t.id,type:`server_tool_call`,name:`file_search`,args:{queries:Oe(t.queries)?t.queries:[]}},(t.status===`completed`||t.status===`failed`)&&(yield{type:`server_tool_call_result`,toolCallId:T(t.id)?t.id:``,status:t.status===`completed`?`success`:`error`,output:Oe(t.results)?{results:t.results}:{}});continue}else if(C(t,`computer_call`)){yield{type:`non_standard`,value:t};continue}else if(C(t,`code_interpreter_call`)){if(T(t.code)&&(yield{id:t.id,type:`server_tool_call`,name:`code_interpreter`,args:{code:t.code}}),Oe(t.outputs)){let e=je(()=>{if(t.status!==`in_progress`){if(t.status===`completed`)return 0;if(t.status===`incomplete`)return 127;if(t.status!==`interpreting`&&t.status===`failed`)return 1}});for(let n of t.outputs)if(C(n,`logs`)){yield{type:`server_tool_call_result`,toolCallId:t.id??``,status:`success`,output:{type:`code_interpreter_output`,returnCode:e??0,stderr:[0,void 0].includes(e)?void 0:String(n.logs),stdout:[0,void 0].includes(e)?String(n.logs):void 0}};continue}}continue}else if(C(t,`mcp_call`)){yield{id:t.id,type:`server_tool_call`,name:`mcp_call`,args:t.input};continue}else if(C(t,`mcp_list_tools`)){yield{id:t.id,type:`server_tool_call`,name:`mcp_list_tools`,args:t.input};continue}else if(C(t,`mcp_approval_request`)){yield{type:`non_standard`,value:t};continue}else if(C(t,`tool_search_call`)){let e={};w(t.arguments)&&Object.assign(e,t.arguments);let n={};T(t.execution)&&(n.execution=t.execution),T(t.status)&&(n.status=t.status),T(t.call_id)&&(n.call_id=t.call_id),yield{id:T(t.id)?t.id:``,type:`server_tool_call`,name:`tool_search`,args:e,...Object.keys(n).length>0?{extras:n}:{}};continue}else if(C(t,`tool_search_output`)){let e={name:`tool_search`};T(t.execution)&&(e.execution=t.execution),yield{type:`server_tool_call_result`,toolCallId:T(t.id)?t.id:``,status:t.status===`completed`?`success`:t.status===`failed`?`error`:`success`,output:{tools:Oe(t.tools)?t.tools:[]},extras:e};continue}else if(C(t,`image_generation_call`)){T(t.result)&&(yield{type:`image`,mimeType:`image/png`,data:t.result,id:T(t.id)?t.id:void 0,metadata:{status:T(t.status)?t.status:void 0}}),yield{type:`non_standard`,value:t};continue}w(t)&&(yield{type:`non_standard`,value:t})}}return Array.from(t())}function qe(e){function*t(){yield*Ke(e);for(let t of e.tool_call_chunks??[])yield{type:`tool_call_chunk`,id:t.id,name:t.name,args:t.args}}return Array.from(t())}var Je={translateContent:e=>typeof e.content==`string`?He(e):Ke(e),translateContentChunk:e=>typeof e.content==`string`?Ue(e):qe(e)};function Ye(e){return typeof e==`object`&&!!e&&`type`in e&&`content`in e&&(typeof e.content==`string`||Array.isArray(e.content))}function Xe(e,t=`pretty`){return t===`pretty`?Ze(e):JSON.stringify(e)}function Ze(e){let t=[],n=` ${e.type.charAt(0).toUpperCase()+e.type.slice(1)} Message `,r=Math.floor((80-n.length)/2),i=`=`.repeat(r),a=n.length%2==0?i:`${i}=`;if(t.push(`${i}${n}${a}`),e.type===`ai`){let n=e;if(n.tool_calls&&n.tool_calls.length>0){t.push(`Tool Calls:`);for(let e of n.tool_calls){t.push(` ${e.name} (${e.id})`),t.push(` Call ID: ${e.id}`),t.push(` Args:`);for(let[n,r]of Object.entries(e.args))t.push(` ${n}: ${typeof r==`object`?JSON.stringify(r):r}`)}}}if(e.type===`tool`){let n=e;n.name&&t.push(`Name: ${n.name}`)}return typeof e.content==`string`&&e.content.trim()&&(t.length>1&&t.push(``),t.push(e.content)),t.join(`