@agent-inspect/studio 6.7.1 → 6.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -9
- package/dist/index.cjs +35 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +35 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -256,7 +256,7 @@ interface HttpIngestConfig {
|
|
|
256
256
|
tokenEnv: string;
|
|
257
257
|
maxBytes: number;
|
|
258
258
|
}
|
|
259
|
-
declare function resolveHttpIngestConfig(options: StudioServerOptions,
|
|
259
|
+
declare function resolveHttpIngestConfig(options: StudioServerOptions, registryHttp?: StudioRegistryHttpIngest): HttpIngestConfig;
|
|
260
260
|
declare function isHttpIngestRoute(pathname: string, config: HttpIngestConfig): boolean;
|
|
261
261
|
declare function handleHttpIngestRequest(req: IncomingMessage, res: ServerResponse, ctx: StudioContext, options: StudioServerOptions, pathname: string): Promise<boolean>;
|
|
262
262
|
|
package/dist/index.d.ts
CHANGED
|
@@ -256,7 +256,7 @@ interface HttpIngestConfig {
|
|
|
256
256
|
tokenEnv: string;
|
|
257
257
|
maxBytes: number;
|
|
258
258
|
}
|
|
259
|
-
declare function resolveHttpIngestConfig(options: StudioServerOptions,
|
|
259
|
+
declare function resolveHttpIngestConfig(options: StudioServerOptions, registryHttp?: StudioRegistryHttpIngest): HttpIngestConfig;
|
|
260
260
|
declare function isHttpIngestRoute(pathname: string, config: HttpIngestConfig): boolean;
|
|
261
261
|
declare function handleHttpIngestRequest(req: IncomingMessage, res: ServerResponse, ctx: StudioContext, options: StudioServerOptions, pathname: string): Promise<boolean>;
|
|
262
262
|
|
package/dist/index.mjs
CHANGED
|
@@ -855,9 +855,9 @@ var DEFAULT_HTTP_INGEST_BASE_PATH = "/api/ingest";
|
|
|
855
855
|
var HTTP_INGEST_BUNDLE_PATH = "/api/ingest/bundle";
|
|
856
856
|
var HTTP_INGEST_ARTIFACT_PATH = "/api/ingest/artifact";
|
|
857
857
|
var DEFAULT_MAX_INGEST_BYTES = 52428800;
|
|
858
|
-
function resolveHttpIngestConfig(options,
|
|
859
|
-
const http = options.context?.registry.ingest?.http;
|
|
860
|
-
const enabled = options.ingestHttp === true ||
|
|
858
|
+
function resolveHttpIngestConfig(options, registryHttp) {
|
|
859
|
+
const http = registryHttp ?? options.context?.registry.ingest?.http;
|
|
860
|
+
const enabled = options.ingestHttp === true || http?.enabled === true;
|
|
861
861
|
const basePath = (http?.path ?? DEFAULT_HTTP_INGEST_BASE_PATH).trim() || DEFAULT_HTTP_INGEST_BASE_PATH;
|
|
862
862
|
const tokenEnv = resolveIngestTokenEnv({
|
|
863
863
|
...options.ingestTokenEnv !== void 0 ? { tokenEnv: options.ingestTokenEnv } : {},
|
|
@@ -890,7 +890,7 @@ function isHttpIngestRoute(pathname, config) {
|
|
|
890
890
|
return pathname === HTTP_INGEST_BUNDLE_PATH || pathname === HTTP_INGEST_ARTIFACT_PATH || pathname === `${config.basePath}/bundle` || pathname === `${config.basePath}/artifact`;
|
|
891
891
|
}
|
|
892
892
|
async function handleHttpIngestRequest(req, res, ctx, options, pathname) {
|
|
893
|
-
const config = resolveHttpIngestConfig(options, ctx.registry.ingest?.http
|
|
893
|
+
const config = resolveHttpIngestConfig(options, ctx.registry.ingest?.http);
|
|
894
894
|
if (!isHttpIngestRoute(pathname, config)) return false;
|
|
895
895
|
if (!config.enabled) {
|
|
896
896
|
sendJson(res, 404, { error: "HTTP ingest is disabled" });
|
|
@@ -1337,8 +1337,6 @@ async function loadBundleExportView(ctx, params) {
|
|
|
1337
1337
|
note: "Studio does not mutate traces or upload bundles. Run the CLI locally to assemble a share-safe bundle."
|
|
1338
1338
|
};
|
|
1339
1339
|
}
|
|
1340
|
-
|
|
1341
|
-
// packages/studio/src/auth.ts
|
|
1342
1340
|
function resolveStudioAuthMode(options) {
|
|
1343
1341
|
return options.auth === "basic" ? "basic" : "none";
|
|
1344
1342
|
}
|
|
@@ -1358,13 +1356,19 @@ function parseBasicAuth(header) {
|
|
|
1358
1356
|
return void 0;
|
|
1359
1357
|
}
|
|
1360
1358
|
}
|
|
1359
|
+
function timingSafePasswordEqual(provided, expected) {
|
|
1360
|
+
const providedDigest = createHash("sha256").update(provided, "utf8").digest();
|
|
1361
|
+
const expectedDigest = createHash("sha256").update(expected, "utf8").digest();
|
|
1362
|
+
return timingSafeEqual(providedDigest, expectedDigest);
|
|
1363
|
+
}
|
|
1361
1364
|
function isStudioRequestAuthorized(req, options) {
|
|
1362
1365
|
const mode = resolveStudioAuthMode(options);
|
|
1363
1366
|
if (mode === "none") return true;
|
|
1364
1367
|
const expected = resolveStudioPassword(options);
|
|
1365
1368
|
if (!expected) return false;
|
|
1366
1369
|
const provided = parseBasicAuth(req.headers.authorization);
|
|
1367
|
-
|
|
1370
|
+
if (provided === void 0) return false;
|
|
1371
|
+
return timingSafePasswordEqual(provided, expected);
|
|
1368
1372
|
}
|
|
1369
1373
|
function studioAuthRequiredResponse() {
|
|
1370
1374
|
return {
|
|
@@ -1609,7 +1613,7 @@ function createStudioServer(options = {}) {
|
|
|
1609
1613
|
contextPromise = createStudioContext(options);
|
|
1610
1614
|
}
|
|
1611
1615
|
const ctx = await contextPromise;
|
|
1612
|
-
const httpConfig = resolveHttpIngestConfig(options, ctx.registry.ingest?.http
|
|
1616
|
+
const httpConfig = resolveHttpIngestConfig(options, ctx.registry.ingest?.http);
|
|
1613
1617
|
if (isHttpIngestRoute(pathname, httpConfig)) {
|
|
1614
1618
|
const handled2 = await handleHttpIngestRequest(req, res, ctx, options, pathname);
|
|
1615
1619
|
if (handled2) return;
|
|
@@ -1888,6 +1892,28 @@ async function validateBundleDirectory(bundleDir) {
|
|
|
1888
1892
|
}
|
|
1889
1893
|
return errors;
|
|
1890
1894
|
}
|
|
1895
|
+
async function hashBundleContents(bundleDir) {
|
|
1896
|
+
const hash = createHash("sha256");
|
|
1897
|
+
const walk = async (dir, relPrefix) => {
|
|
1898
|
+
const entries = (await readdir(dir, { withFileTypes: true })).sort(
|
|
1899
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
1900
|
+
);
|
|
1901
|
+
for (const entry of entries) {
|
|
1902
|
+
const abs = path8.join(dir, entry.name);
|
|
1903
|
+
const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
|
|
1904
|
+
if (entry.isDirectory()) {
|
|
1905
|
+
await walk(abs, rel);
|
|
1906
|
+
} else if (entry.isFile()) {
|
|
1907
|
+
hash.update(rel);
|
|
1908
|
+
hash.update("\0");
|
|
1909
|
+
hash.update(await readFile(abs));
|
|
1910
|
+
hash.update("\0");
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
};
|
|
1914
|
+
await walk(bundleDir, "");
|
|
1915
|
+
return hash.digest("hex");
|
|
1916
|
+
}
|
|
1891
1917
|
async function copyDirectoryRecursive(sourceDir, destDir) {
|
|
1892
1918
|
await mkdir(destDir, { recursive: true });
|
|
1893
1919
|
const entries = await readdir(sourceDir, { withFileTypes: true });
|
|
@@ -1942,8 +1968,7 @@ async function importBundleUpload(options) {
|
|
|
1942
1968
|
registryImportWarnings
|
|
1943
1969
|
};
|
|
1944
1970
|
}
|
|
1945
|
-
const
|
|
1946
|
-
const contentHash = createHash("sha256").update(metadataRaw).digest("hex");
|
|
1971
|
+
const contentHash = await hashBundleContents(bundlePath);
|
|
1947
1972
|
const sourceKey = `bundle:${bundlePath}`;
|
|
1948
1973
|
const existing = findIngestFileBySourceKey(options.db, sourceKey);
|
|
1949
1974
|
if (existing && existing.contentHash === contentHash) {
|