stitchkit 0.55.0 → 0.56.0
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/browser/socket-io.d.ts +14 -6
- package/dist/browser/socket-io.d.ts.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/contract/errors.d.ts +7 -0
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/index.js +1 -1
- package/dist/files/boundary.d.ts +8 -5
- package/dist/files/boundary.d.ts.map +1 -1
- package/dist/files.js +1 -1
- package/dist/{index-rgd18hx3.js → index-28cqssm3.js} +7 -0
- package/dist/index-557by2db.js +34 -0
- package/dist/index-5r13htq1.js +283 -0
- package/dist/index-6y759j86.js +1031 -0
- package/dist/{index-1k16zv57.js → index-bfcpjw20.js} +125 -31
- package/dist/{index-0gv7k0ra.js → index-escqg10p.js} +73 -666
- package/dist/{index-v4bbq3p4.js → index-j3dem06f.js} +1 -1
- package/dist/{index-qx84bqzk.js → index-jcc611vh.js} +1 -1
- package/dist/{index-npye5kt7.js → index-n4nfa7gh.js} +3 -3
- package/dist/{index-pzc32v75.js → index-sm2tjx06.js} +41 -4
- package/dist/{index-js7yexng.js → index-xy8fmh6w.js} +83 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -6
- package/dist/node.js +5 -4
- package/dist/observability/index.js +3 -3
- package/dist/remote.js +1 -1
- package/dist/server/context-contribution.d.ts +8 -0
- package/dist/server/context-contribution.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +8 -5
- package/dist/server/middleware/auth.d.ts +29 -2
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/testing/surface-conformance.d.ts +143 -10
- package/dist/testing/surface-conformance.d.ts.map +1 -1
- package/dist/testing/surface-manifest.d.ts +132 -33
- package/dist/testing/surface-manifest.d.ts.map +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +470 -122
- package/dist/tools/async-operation.d.ts +210 -18
- package/dist/tools/async-operation.d.ts.map +1 -1
- package/dist/tools/define-download-tool.d.ts.map +1 -1
- package/dist/tools/define-upload-tool.d.ts.map +1 -1
- package/dist/tools/internal/surface-projector.d.ts +101 -0
- package/dist/tools/internal/surface-projector.d.ts.map +1 -0
- package/dist/tools/managed-file-error.d.ts +6 -0
- package/dist/tools/managed-file-error.d.ts.map +1 -0
- package/dist/tools/mcp-prepare.d.ts +3 -21
- package/dist/tools/mcp-prepare.d.ts.map +1 -1
- package/dist/tools/mcp-round-policy.d.ts +9 -0
- package/dist/tools/mcp-round-policy.d.ts.map +1 -0
- package/dist/tools/mcp-round.d.ts +0 -4
- package/dist/tools/mcp-round.d.ts.map +1 -1
- package/dist/tools/mount-download.d.ts.map +1 -1
- package/dist/tools/mount-upload.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +5 -9
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/surface.d.ts +7 -0
- package/dist/tools/surface.d.ts.map +1 -1
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +348 -383
- package/llms-full.txt +361 -58
- package/package.json +1 -1
- package/dist/index-h60df7rj.js +0 -110
- package/dist/index-k4dftwf7.js +0 -66
|
@@ -9,11 +9,16 @@ import {
|
|
|
9
9
|
// src/files/boundary.ts
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
11
|
import { constants } from "node:fs";
|
|
12
|
-
import { link, open, realpath, rename, stat, unlink } from "node:fs/promises";
|
|
12
|
+
import { link, mkdir, open, realpath, rename, stat, unlink } from "node:fs/promises";
|
|
13
13
|
import { basename, dirname, resolve } from "node:path";
|
|
14
14
|
var DEFAULT_MAX_BYTES = 100 * 1024 * 1024;
|
|
15
15
|
var DEFAULT_INSPECTION_BYTES = 64 * 1024;
|
|
16
|
+
var DEFAULT_INSPECTION_TIMEOUT_MS = 15000;
|
|
16
17
|
var NOFOLLOW_FLAG = constants.O_NOFOLLOW ?? 0;
|
|
18
|
+
var ManagedFileInspectionSchema = ManagedFileRefSchema.pick({
|
|
19
|
+
mediaType: true,
|
|
20
|
+
name: true
|
|
21
|
+
});
|
|
17
22
|
|
|
18
23
|
class ManagedFileError extends Error {
|
|
19
24
|
code;
|
|
@@ -47,20 +52,118 @@ function ioError(message, error) {
|
|
|
47
52
|
return error;
|
|
48
53
|
return new ManagedFileError("FILE_IO_ERROR", message, { cause: error });
|
|
49
54
|
}
|
|
55
|
+
async function realpathOrNull(path, message) {
|
|
56
|
+
try {
|
|
57
|
+
return await realpath(path);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (errorCode(error) === "ENOENT")
|
|
60
|
+
return null;
|
|
61
|
+
throw ioError(message, error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function bindRoot(config) {
|
|
65
|
+
const requested = resolve(config.root);
|
|
66
|
+
let canonical = await realpathOrNull(requested, "failed to resolve managed-file root");
|
|
67
|
+
if (canonical === null && config.createRoot) {
|
|
68
|
+
const parent = await realpathOrNull(dirname(requested), "failed to resolve managed-file root parent");
|
|
69
|
+
if (parent === null) {
|
|
70
|
+
throw new ManagedFileError("FILE_NOT_FOUND", "managed-file root parent must exist");
|
|
71
|
+
}
|
|
72
|
+
const parentInfo = await stat(parent).catch((error) => {
|
|
73
|
+
throw ioError("failed to inspect managed-file root parent", error);
|
|
74
|
+
});
|
|
75
|
+
if (!parentInfo.isDirectory()) {
|
|
76
|
+
throw new ManagedFileError("FILE_NOT_REGULAR", "managed-file root parent must be a directory");
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
await mkdir(requested, { mode: 448 });
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (errorCode(error) !== "EEXIST") {
|
|
82
|
+
if (errorCode(error) === "ENOENT") {
|
|
83
|
+
throw new ManagedFileError("FILE_NOT_FOUND", "managed-file root parent must exist");
|
|
84
|
+
}
|
|
85
|
+
throw ioError("failed to create managed-file root", error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
canonical = await realpathOrNull(requested, "failed to resolve managed-file root");
|
|
89
|
+
}
|
|
90
|
+
if (canonical === null) {
|
|
91
|
+
throw new ManagedFileError("FILE_NOT_FOUND", "managed-file root must exist");
|
|
92
|
+
}
|
|
93
|
+
const info = await stat(canonical).catch((error) => {
|
|
94
|
+
throw ioError("failed to inspect managed-file root", error);
|
|
95
|
+
});
|
|
96
|
+
if (!info.isDirectory()) {
|
|
97
|
+
throw new ManagedFileError("FILE_NOT_REGULAR", "managed-file root must be a directory");
|
|
98
|
+
}
|
|
99
|
+
return canonical;
|
|
100
|
+
}
|
|
50
101
|
async function existingParent(root, target) {
|
|
51
|
-
const parent = await
|
|
102
|
+
const parent = await realpathOrNull(dirname(target), "failed to resolve managed-file parent directory");
|
|
52
103
|
if (parent === null) {
|
|
53
104
|
throw new ManagedFileError("FILE_NOT_FOUND", "managed-file parent directory not found");
|
|
54
105
|
}
|
|
55
106
|
if (!isWithinDir(root, parent)) {
|
|
56
107
|
throw new ManagedFileError("FILE_OUTSIDE_ROOT", "managed-file path escapes its boundary");
|
|
57
108
|
}
|
|
58
|
-
const info = await stat(parent).catch(() =>
|
|
59
|
-
|
|
60
|
-
|
|
109
|
+
const info = await stat(parent).catch((error) => {
|
|
110
|
+
throw ioError("failed to inspect managed-file parent directory", error);
|
|
111
|
+
});
|
|
112
|
+
if (!info.isDirectory()) {
|
|
113
|
+
throw new ManagedFileError("FILE_NOT_REGULAR", "managed-file parent is not a directory");
|
|
61
114
|
}
|
|
62
115
|
return parent;
|
|
63
116
|
}
|
|
117
|
+
function abortReason(signal) {
|
|
118
|
+
return signal.reason ?? new DOMException("The operation was aborted", "AbortError");
|
|
119
|
+
}
|
|
120
|
+
function raceWithSignal(operation, signal) {
|
|
121
|
+
signal.throwIfAborted();
|
|
122
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
123
|
+
let settled = false;
|
|
124
|
+
const settle = (complete) => {
|
|
125
|
+
if (settled)
|
|
126
|
+
return;
|
|
127
|
+
settled = true;
|
|
128
|
+
signal.removeEventListener("abort", onAbort);
|
|
129
|
+
complete();
|
|
130
|
+
};
|
|
131
|
+
const onAbort = () => settle(() => rejectPromise(abortReason(signal)));
|
|
132
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
133
|
+
if (signal.aborted)
|
|
134
|
+
onAbort();
|
|
135
|
+
operation.then((value) => settle(() => resolvePromise(value)), (error) => settle(() => rejectPromise(error)));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async function inspectFile(inspector, input, timeoutMs, outerSignal) {
|
|
139
|
+
if (!inspector)
|
|
140
|
+
return {};
|
|
141
|
+
outerSignal?.throwIfAborted();
|
|
142
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
143
|
+
const signal = outerSignal ? AbortSignal.any([outerSignal, timeoutSignal]) : timeoutSignal;
|
|
144
|
+
try {
|
|
145
|
+
const inspected = await raceWithSignal(Promise.resolve().then(() => inspector({ ...input, signal })), signal);
|
|
146
|
+
return ManagedFileInspectionSchema.parse(inspected);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (outerSignal?.aborted)
|
|
149
|
+
throw abortReason(outerSignal);
|
|
150
|
+
throw new ManagedFileError("FILE_INSPECTION_REJECTED", "managed file rejected by inspection", { cause: error });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function inspectedRef(path, size, inspection, fallback = {}) {
|
|
154
|
+
const mediaType = inspection.mediaType ?? fallback.mediaType;
|
|
155
|
+
const name = inspection.name ?? fallback.name;
|
|
156
|
+
const parsed = ManagedFileRefSchema.safeParse({
|
|
157
|
+
path,
|
|
158
|
+
size,
|
|
159
|
+
...mediaType ? { mediaType } : {},
|
|
160
|
+
...name ? { name } : {}
|
|
161
|
+
});
|
|
162
|
+
if (!parsed.success) {
|
|
163
|
+
throw new ManagedFileError("FILE_INSPECTION_REJECTED", "managed file rejected by inspection", { cause: parsed.error });
|
|
164
|
+
}
|
|
165
|
+
return parsed.data;
|
|
166
|
+
}
|
|
64
167
|
async function readHandle(handle, maxBytes, signal) {
|
|
65
168
|
const chunks = [];
|
|
66
169
|
let total = 0;
|
|
@@ -143,13 +246,11 @@ async function writeSource(handle, source, maxBytes, inspectionBytes, signal) {
|
|
|
143
246
|
return { size, prefix };
|
|
144
247
|
}
|
|
145
248
|
async function createManagedFileBoundary(config) {
|
|
146
|
-
const root = await
|
|
147
|
-
if (root === null || !(await stat(root).catch(() => null))?.isDirectory()) {
|
|
148
|
-
throw new ManagedFileError("FILE_NOT_FOUND", "managed-file root must exist");
|
|
149
|
-
}
|
|
249
|
+
const root = await bindRoot(config);
|
|
150
250
|
const maxReadBytes = positiveLimit(config.maxReadBytes, DEFAULT_MAX_BYTES, "maxReadBytes");
|
|
151
251
|
const maxWriteBytes = positiveLimit(config.maxWriteBytes, DEFAULT_MAX_BYTES, "maxWriteBytes");
|
|
152
252
|
const inspectionBytes = positiveLimit(config.inspectionBytes, DEFAULT_INSPECTION_BYTES, "inspectionBytes");
|
|
253
|
+
const inspectionTimeoutMs = positiveLimit(config.inspectionTimeoutMs, DEFAULT_INSPECTION_TIMEOUT_MS, "inspectionTimeoutMs");
|
|
153
254
|
const targetFor = (path) => {
|
|
154
255
|
const relative = filePath(path);
|
|
155
256
|
const target = resolve(root, ...relative.split("/"));
|
|
@@ -161,7 +262,8 @@ async function createManagedFileBoundary(config) {
|
|
|
161
262
|
return {
|
|
162
263
|
async read(path, options = {}) {
|
|
163
264
|
const resolved = targetFor(path);
|
|
164
|
-
|
|
265
|
+
options.signal?.throwIfAborted();
|
|
266
|
+
const realTarget = await realpathOrNull(resolved.target, "failed to resolve managed file");
|
|
165
267
|
if (realTarget === null) {
|
|
166
268
|
throw new ManagedFileError("FILE_NOT_FOUND", "managed file not found");
|
|
167
269
|
}
|
|
@@ -177,8 +279,12 @@ async function createManagedFileBoundary(config) {
|
|
|
177
279
|
throw new ManagedFileError("FILE_NOT_REGULAR", "managed path is not a regular file");
|
|
178
280
|
}
|
|
179
281
|
const bytes = await readHandle(handle, maxBytes, options.signal);
|
|
282
|
+
const inspection = await inspectFile(config.inspect, {
|
|
283
|
+
prefix: bytes.slice(0, inspectionBytes),
|
|
284
|
+
name: basename(resolved.path)
|
|
285
|
+
}, inspectionTimeoutMs, options.signal);
|
|
180
286
|
return {
|
|
181
|
-
ref:
|
|
287
|
+
ref: inspectedRef(resolved.path, bytes.byteLength, inspection),
|
|
182
288
|
bytes
|
|
183
289
|
};
|
|
184
290
|
} catch (error) {
|
|
@@ -211,18 +317,11 @@ async function createManagedFileBoundary(config) {
|
|
|
211
317
|
await handle.sync();
|
|
212
318
|
await handle.close();
|
|
213
319
|
handle = undefined;
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
declaredMediaType: options.mediaType,
|
|
220
|
-
name: options.name ?? basename(resolved.path)
|
|
221
|
-
});
|
|
222
|
-
} catch (error) {
|
|
223
|
-
throw new ManagedFileError("FILE_INSPECTION_REJECTED", "managed file rejected by inspection", { cause: error });
|
|
224
|
-
}
|
|
225
|
-
}
|
|
320
|
+
const inspection = await inspectFile(config.inspect, {
|
|
321
|
+
prefix: written.prefix,
|
|
322
|
+
...options.mediaType ? { declaredMediaType: options.mediaType } : {},
|
|
323
|
+
name: options.name ?? basename(resolved.path)
|
|
324
|
+
}, inspectionTimeoutMs, options.signal);
|
|
226
325
|
options.signal?.throwIfAborted();
|
|
227
326
|
if (options.replace) {
|
|
228
327
|
await rename(temporary, resolved.target);
|
|
@@ -238,15 +337,10 @@ async function createManagedFileBoundary(config) {
|
|
|
238
337
|
throw error;
|
|
239
338
|
}
|
|
240
339
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
path: resolved.path,
|
|
245
|
-
size,
|
|
246
|
-
...mediaType ? { mediaType } : {},
|
|
247
|
-
...name ? { name } : {}
|
|
340
|
+
return inspectedRef(resolved.path, size, inspection, {
|
|
341
|
+
mediaType: options.mediaType,
|
|
342
|
+
name: options.name
|
|
248
343
|
});
|
|
249
|
-
return ref;
|
|
250
344
|
} catch (error) {
|
|
251
345
|
if (options.signal?.aborted && error === options.signal.reason)
|
|
252
346
|
throw error;
|