@bitfab/sdk 0.33.7 → 0.34.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/README.md +19 -0
- package/dist/chunk-4J36FZ4K.js +2299 -0
- package/dist/chunk-4J36FZ4K.js.map +1 -0
- package/dist/{chunk-DZJ5K75J.js → chunk-ESKBRHVG.js} +87 -604
- package/dist/chunk-ESKBRHVG.js.map +1 -0
- package/dist/index.cjs +2297 -1365
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +374 -28
- package/dist/index.d.ts +374 -28
- package/dist/index.js +5 -5
- package/dist/node.cjs +2287 -1355
- package/dist/node.cjs.map +1 -1
- package/dist/node.js +5 -5
- package/dist/{replay-SRQI4QMY.js → replay-QFNYP7EY.js} +2 -2
- package/package.json +6 -1
- package/dist/chunk-DPV6PBWE.js +0 -916
- package/dist/chunk-DPV6PBWE.js.map +0 -1
- package/dist/chunk-DZJ5K75J.js.map +0 -1
- /package/dist/{replay-SRQI4QMY.js.map → replay-QFNYP7EY.js.map} +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BitfabError,
|
|
3
|
+
DEFAULT_SERVICE_URL,
|
|
4
|
+
HttpClient,
|
|
3
5
|
__privateAdd,
|
|
4
6
|
__privateGet,
|
|
5
7
|
__privateSet,
|
|
@@ -14,513 +16,7 @@ import {
|
|
|
14
16
|
toJsonSafe,
|
|
15
17
|
toJsonSafeReport,
|
|
16
18
|
warnOnce
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
|
|
19
|
-
// src/version.generated.ts
|
|
20
|
-
var __version__ = "0.33.7";
|
|
21
|
-
|
|
22
|
-
// src/constants.ts
|
|
23
|
-
var DEFAULT_SERVICE_URL = "https://bitfab.ai";
|
|
24
|
-
|
|
25
|
-
// src/unrefTimer.ts
|
|
26
|
-
function unrefTimer(timer) {
|
|
27
|
-
const handle = timer;
|
|
28
|
-
if (typeof handle.unref === "function") {
|
|
29
|
-
handle.unref();
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// src/http.ts
|
|
34
|
-
var REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS = 3e5;
|
|
35
|
-
function serializePayloadBody(payload) {
|
|
36
|
-
try {
|
|
37
|
-
return { body: JSON.stringify(payload), dropped: [] };
|
|
38
|
-
} catch {
|
|
39
|
-
const dropped = [];
|
|
40
|
-
const sanitize = (value, seen) => {
|
|
41
|
-
const t = typeof value;
|
|
42
|
-
if (value === null || t === "string" || t === "number" || t === "boolean") {
|
|
43
|
-
return value;
|
|
44
|
-
}
|
|
45
|
-
if (t === "bigint") {
|
|
46
|
-
dropped.push("BigInt");
|
|
47
|
-
return "<unserializable: BigInt>";
|
|
48
|
-
}
|
|
49
|
-
if (t === "function") {
|
|
50
|
-
const name = value.name || "Function";
|
|
51
|
-
dropped.push(name);
|
|
52
|
-
return `<unserializable: ${name}>`;
|
|
53
|
-
}
|
|
54
|
-
if (t === "symbol") {
|
|
55
|
-
dropped.push("Symbol");
|
|
56
|
-
return "<unserializable: Symbol>";
|
|
57
|
-
}
|
|
58
|
-
if (t !== "object") {
|
|
59
|
-
return void 0;
|
|
60
|
-
}
|
|
61
|
-
const obj = value;
|
|
62
|
-
const className = obj.constructor?.name || "object";
|
|
63
|
-
if (seen.has(obj)) {
|
|
64
|
-
dropped.push(className);
|
|
65
|
-
return `<cycle: ${className}>`;
|
|
66
|
-
}
|
|
67
|
-
seen.add(obj);
|
|
68
|
-
let result;
|
|
69
|
-
if (Array.isArray(obj)) {
|
|
70
|
-
result = obj.map((item) => sanitize(item, seen));
|
|
71
|
-
} else if (typeof obj.toJSON === "function") {
|
|
72
|
-
try {
|
|
73
|
-
result = sanitize(obj.toJSON(), seen);
|
|
74
|
-
} catch {
|
|
75
|
-
dropped.push(className);
|
|
76
|
-
result = `<unserializable: ${className}>`;
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
try {
|
|
80
|
-
const out = {};
|
|
81
|
-
for (const [k, v] of Object.entries(obj)) {
|
|
82
|
-
out[k] = sanitize(v, seen);
|
|
83
|
-
}
|
|
84
|
-
result = out;
|
|
85
|
-
} catch {
|
|
86
|
-
warnOnce(
|
|
87
|
-
"payload:field-getter-threw",
|
|
88
|
-
"a value with a throwing getter/proxy could not be serialized into a span payload; it was replaced with a placeholder. The span still ships with its other fields intact."
|
|
89
|
-
);
|
|
90
|
-
dropped.push(className);
|
|
91
|
-
result = `<unserializable: ${className}>`;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
seen.delete(obj);
|
|
95
|
-
return result;
|
|
96
|
-
};
|
|
97
|
-
let sanitized;
|
|
98
|
-
try {
|
|
99
|
-
sanitized = sanitize(payload, /* @__PURE__ */ new WeakSet());
|
|
100
|
-
} catch (error) {
|
|
101
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
102
|
-
return {
|
|
103
|
-
body: JSON.stringify({ error: `payload_serialize_failed: ${message}` }),
|
|
104
|
-
dropped
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
if (dropped.length > 0 && typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
|
|
108
|
-
const obj = sanitized;
|
|
109
|
-
const existing = Array.isArray(obj.errors) ? obj.errors : [];
|
|
110
|
-
obj.errors = [
|
|
111
|
-
...existing,
|
|
112
|
-
{
|
|
113
|
-
source: "sdk",
|
|
114
|
-
step: "json_serialize",
|
|
115
|
-
error: `stubbed non-serializable value(s): ${[
|
|
116
|
-
...new Set(dropped)
|
|
117
|
-
].join(", ")}`
|
|
118
|
-
}
|
|
119
|
-
];
|
|
120
|
-
}
|
|
121
|
-
return { body: JSON.stringify(sanitized), dropped };
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
var pendingTracePromises = /* @__PURE__ */ new Set();
|
|
125
|
-
function awaitOnExit(promise) {
|
|
126
|
-
pendingTracePromises.add(promise);
|
|
127
|
-
void promise.finally(() => {
|
|
128
|
-
pendingTracePromises.delete(promise);
|
|
129
|
-
}).catch(() => {
|
|
130
|
-
});
|
|
131
|
-
return promise;
|
|
132
|
-
}
|
|
133
|
-
async function flushTraces(timeoutMs = 5e3) {
|
|
134
|
-
if (pendingTracePromises.size === 0) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
let timer;
|
|
138
|
-
try {
|
|
139
|
-
await Promise.race([
|
|
140
|
-
Promise.allSettled(Array.from(pendingTracePromises)),
|
|
141
|
-
new Promise((resolve) => {
|
|
142
|
-
timer = setTimeout(resolve, timeoutMs);
|
|
143
|
-
unrefTimer(timer);
|
|
144
|
-
})
|
|
145
|
-
]);
|
|
146
|
-
} finally {
|
|
147
|
-
if (timer) {
|
|
148
|
-
clearTimeout(timer);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (typeof process !== "undefined" && process.versions != null && process.versions.node != null) {
|
|
153
|
-
let isFlushing = false;
|
|
154
|
-
process.on("beforeExit", () => {
|
|
155
|
-
if (pendingTracePromises.size > 0 && !isFlushing) {
|
|
156
|
-
isFlushing = true;
|
|
157
|
-
Promise.allSettled(
|
|
158
|
-
Array.from(pendingTracePromises).map(
|
|
159
|
-
(p) => p.catch(() => {
|
|
160
|
-
})
|
|
161
|
-
)
|
|
162
|
-
).then(() => {
|
|
163
|
-
isFlushing = false;
|
|
164
|
-
}).catch(() => {
|
|
165
|
-
isFlushing = false;
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
var HttpClient = class {
|
|
171
|
-
constructor(config) {
|
|
172
|
-
this.apiKey = config.apiKey;
|
|
173
|
-
this.serviceUrl = config.serviceUrl;
|
|
174
|
-
this.timeout = config.timeout ?? 12e4;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Resolve the API key at the moment it is needed (request time), invoking
|
|
178
|
-
* the function form if one was supplied. Never read at construction.
|
|
179
|
-
*/
|
|
180
|
-
resolveApiKey() {
|
|
181
|
-
return typeof this.apiKey === "function" ? this.apiKey() : this.apiKey;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Make an HTTP request to the Bitfab API. Defaults to POST; pass
|
|
185
|
-
* `options.method` to use a different verb (e.g. "PATCH").
|
|
186
|
-
*
|
|
187
|
-
* @param endpoint - The API endpoint (without base URL)
|
|
188
|
-
* @param payload - The request body
|
|
189
|
-
* @param options - Optional request options
|
|
190
|
-
* @returns The parsed JSON response
|
|
191
|
-
* @throws {BitfabError} If the request fails
|
|
192
|
-
*/
|
|
193
|
-
async request(endpoint, payload, options) {
|
|
194
|
-
const url = `${this.serviceUrl}${endpoint}`;
|
|
195
|
-
const timeout = options?.timeout ?? this.timeout;
|
|
196
|
-
const method = options?.method ?? "POST";
|
|
197
|
-
const controller = new AbortController();
|
|
198
|
-
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
199
|
-
const { body, dropped } = serializePayloadBody(payload);
|
|
200
|
-
if (dropped.length > 0) {
|
|
201
|
-
try {
|
|
202
|
-
console.warn(
|
|
203
|
-
`Bitfab: request body to ${endpoint} held ${dropped.length} non-serializable value(s) (${[...new Set(dropped)].join(", ")}); they were stubbed so the span still sends, but the trace may be incomplete or not replayable. Capture a JSON-safe projection of this input to make it replayable.`
|
|
204
|
-
);
|
|
205
|
-
} catch {
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
try {
|
|
209
|
-
const response = await fetch(url, {
|
|
210
|
-
method,
|
|
211
|
-
headers: {
|
|
212
|
-
"Content-Type": "application/json",
|
|
213
|
-
Authorization: `Bearer ${this.resolveApiKey() ?? ""}`
|
|
214
|
-
},
|
|
215
|
-
body,
|
|
216
|
-
signal: controller.signal
|
|
217
|
-
});
|
|
218
|
-
if (!response.ok) {
|
|
219
|
-
const errorText = await response.text();
|
|
220
|
-
throw new BitfabError(
|
|
221
|
-
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
const result = await response.json();
|
|
225
|
-
if (result.error) {
|
|
226
|
-
if (result.url) {
|
|
227
|
-
throw new BitfabError(
|
|
228
|
-
`${result.error} Configure it at: ${this.serviceUrl}${result.url}`,
|
|
229
|
-
result.url
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
throw new BitfabError(result.error);
|
|
233
|
-
}
|
|
234
|
-
return result;
|
|
235
|
-
} catch (error) {
|
|
236
|
-
if (error instanceof BitfabError) {
|
|
237
|
-
throw error;
|
|
238
|
-
}
|
|
239
|
-
if (error instanceof Error) {
|
|
240
|
-
if (error.name === "AbortError") {
|
|
241
|
-
throw new BitfabError(`Request timed out after ${timeout}ms`);
|
|
242
|
-
}
|
|
243
|
-
throw new BitfabError(error.message);
|
|
244
|
-
}
|
|
245
|
-
throw new BitfabError("Unknown error occurred");
|
|
246
|
-
} finally {
|
|
247
|
-
clearTimeout(timeoutId);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Look up a function by name.
|
|
252
|
-
* Blocks until complete - needed for function execution.
|
|
253
|
-
*/
|
|
254
|
-
async lookupFunction(name) {
|
|
255
|
-
return this.request("/api/sdk/functions/lookup", { name });
|
|
256
|
-
}
|
|
257
|
-
async getTraceSpan(traceId, lookup) {
|
|
258
|
-
const searchParams = new URLSearchParams();
|
|
259
|
-
if (lookup.id !== void 0) {
|
|
260
|
-
searchParams.set("id", lookup.id);
|
|
261
|
-
} else {
|
|
262
|
-
searchParams.set("name", lookup.name);
|
|
263
|
-
searchParams.set("occurrence", String(lookup.occurrence ?? "last"));
|
|
264
|
-
}
|
|
265
|
-
const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}/span?${searchParams.toString()}`;
|
|
266
|
-
const response = await this.get(endpoint);
|
|
267
|
-
return response.span;
|
|
268
|
-
}
|
|
269
|
-
async get(endpoint) {
|
|
270
|
-
const url = `${this.serviceUrl}${endpoint}`;
|
|
271
|
-
const controller = new AbortController();
|
|
272
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
273
|
-
try {
|
|
274
|
-
const response = await fetch(url, {
|
|
275
|
-
method: "GET",
|
|
276
|
-
headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
|
|
277
|
-
signal: controller.signal
|
|
278
|
-
});
|
|
279
|
-
if (!response.ok) {
|
|
280
|
-
const errorText = await response.text();
|
|
281
|
-
throw new BitfabError(
|
|
282
|
-
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
return await response.json();
|
|
286
|
-
} catch (error) {
|
|
287
|
-
if (error instanceof BitfabError) {
|
|
288
|
-
throw error;
|
|
289
|
-
}
|
|
290
|
-
if (error instanceof Error) {
|
|
291
|
-
if (error.name === "AbortError") {
|
|
292
|
-
throw new BitfabError(`Request timed out after ${this.timeout}ms`);
|
|
293
|
-
}
|
|
294
|
-
throw new BitfabError(error.message);
|
|
295
|
-
}
|
|
296
|
-
throw new BitfabError("Unknown error occurred");
|
|
297
|
-
} finally {
|
|
298
|
-
clearTimeout(timeoutId);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Send an internal trace (from BAML execution).
|
|
303
|
-
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
304
|
-
*/
|
|
305
|
-
sendInternalTrace(functionId, payload) {
|
|
306
|
-
void awaitOnExit(
|
|
307
|
-
this.request(`/api/sdk/functions/${functionId}/traces`, {
|
|
308
|
-
...payload,
|
|
309
|
-
sdkVersion: __version__
|
|
310
|
-
})
|
|
311
|
-
).catch((error) => {
|
|
312
|
-
try {
|
|
313
|
-
console.error("Bitfab: Failed to create trace:", error);
|
|
314
|
-
} catch {
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Send an external span (from withSpan wrapper or OpenAI tracing).
|
|
320
|
-
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
321
|
-
* Returns the tracked promise so callers can optionally await it.
|
|
322
|
-
*/
|
|
323
|
-
sendExternalSpan(payload) {
|
|
324
|
-
return awaitOnExit(
|
|
325
|
-
this.request("/api/sdk/externalSpans", {
|
|
326
|
-
...payload,
|
|
327
|
-
sdkVersion: __version__
|
|
328
|
-
})
|
|
329
|
-
).catch((error) => {
|
|
330
|
-
try {
|
|
331
|
-
console.error("Bitfab: Failed to create external span:", error);
|
|
332
|
-
} catch {
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Send an external trace (from OpenAI tracing).
|
|
338
|
-
* Fire-and-forget with awaitOnExit - doesn't block the caller.
|
|
339
|
-
* Returns the tracked promise so callers can optionally await it
|
|
340
|
-
* (the replay path does, so trace completions are persisted before
|
|
341
|
-
* `completeReplay` builds the trace-ID mapping).
|
|
342
|
-
*/
|
|
343
|
-
sendExternalTrace(payload) {
|
|
344
|
-
return awaitOnExit(
|
|
345
|
-
this.request("/api/sdk/externalTraces", {
|
|
346
|
-
...payload,
|
|
347
|
-
sdkVersion: __version__
|
|
348
|
-
})
|
|
349
|
-
).catch((error) => {
|
|
350
|
-
try {
|
|
351
|
-
console.error("Bitfab: Failed to create external trace:", error);
|
|
352
|
-
} catch {
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Partial update of an existing trace identified by its Bitfab trace ID.
|
|
358
|
-
* Used by the detached `client.getTrace(id)` handle. Fire-and-forget;
|
|
359
|
-
* returns a tracked promise that callers may optionally await.
|
|
360
|
-
*/
|
|
361
|
-
patchTrace(traceId, payload) {
|
|
362
|
-
const endpoint = `/api/sdk/traces/${encodeURIComponent(traceId)}`;
|
|
363
|
-
return awaitOnExit(
|
|
364
|
-
this.request(endpoint, payload, { method: "PATCH" })
|
|
365
|
-
).catch((error) => {
|
|
366
|
-
try {
|
|
367
|
-
console.error("Bitfab: Failed to patch trace:", error);
|
|
368
|
-
} catch {
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
/**
|
|
373
|
-
* Start a replay session by fetching historical traces.
|
|
374
|
-
* Blocking call - creates a test run and returns lightweight item references.
|
|
375
|
-
*/
|
|
376
|
-
async startReplay(traceFunctionKey, limit, traceIds, name, codeChangeDescription, codeChangeFiles, includeDbBranchLease, experimentGroupId, datasetId, graderIds, dbBranchSettings) {
|
|
377
|
-
const payload = { traceFunctionKey };
|
|
378
|
-
if (limit !== void 0) {
|
|
379
|
-
payload.limit = limit;
|
|
380
|
-
}
|
|
381
|
-
if (traceIds) {
|
|
382
|
-
payload.traceIds = traceIds;
|
|
383
|
-
}
|
|
384
|
-
if (name !== void 0) {
|
|
385
|
-
payload.name = name;
|
|
386
|
-
}
|
|
387
|
-
if (codeChangeDescription !== void 0) {
|
|
388
|
-
payload.codeChangeDescription = codeChangeDescription;
|
|
389
|
-
}
|
|
390
|
-
if (codeChangeFiles !== void 0) {
|
|
391
|
-
payload.codeChangeFiles = codeChangeFiles;
|
|
392
|
-
}
|
|
393
|
-
if (includeDbBranchLease) {
|
|
394
|
-
payload.includeDbBranchLease = true;
|
|
395
|
-
payload.lazyDbBranchLease = true;
|
|
396
|
-
}
|
|
397
|
-
if (experimentGroupId !== void 0) {
|
|
398
|
-
payload.experimentGroupId = experimentGroupId;
|
|
399
|
-
}
|
|
400
|
-
if (datasetId !== void 0) {
|
|
401
|
-
payload.datasetId = datasetId;
|
|
402
|
-
}
|
|
403
|
-
if (graderIds !== void 0) {
|
|
404
|
-
payload.graderIds = graderIds;
|
|
405
|
-
}
|
|
406
|
-
if (dbBranchSettings !== void 0) {
|
|
407
|
-
payload.dbBranchSettings = dbBranchSettings;
|
|
408
|
-
}
|
|
409
|
-
const timeout = includeDbBranchLease ? REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS : 3e4;
|
|
410
|
-
return this.request("/api/sdk/replay/start", payload, {
|
|
411
|
-
timeout
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
/**
|
|
415
|
-
* Fetch an external span by ID.
|
|
416
|
-
* Blocking GET request.
|
|
417
|
-
*/
|
|
418
|
-
async getExternalSpan(spanId) {
|
|
419
|
-
const url = `${this.serviceUrl}/api/sdk/externalSpans/${spanId}`;
|
|
420
|
-
const controller = new AbortController();
|
|
421
|
-
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
422
|
-
try {
|
|
423
|
-
const response = await fetch(url, {
|
|
424
|
-
method: "GET",
|
|
425
|
-
headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
|
|
426
|
-
signal: controller.signal
|
|
427
|
-
});
|
|
428
|
-
if (!response.ok) {
|
|
429
|
-
const errorText = await response.text();
|
|
430
|
-
throw new BitfabError(
|
|
431
|
-
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
432
|
-
);
|
|
433
|
-
}
|
|
434
|
-
return await response.json();
|
|
435
|
-
} catch (error) {
|
|
436
|
-
if (error instanceof BitfabError) {
|
|
437
|
-
throw error;
|
|
438
|
-
}
|
|
439
|
-
if (error instanceof Error) {
|
|
440
|
-
if (error.name === "AbortError") {
|
|
441
|
-
throw new BitfabError("Request timed out after 30000ms");
|
|
442
|
-
}
|
|
443
|
-
throw new BitfabError(error.message);
|
|
444
|
-
}
|
|
445
|
-
throw new BitfabError("Unknown error occurred");
|
|
446
|
-
} finally {
|
|
447
|
-
clearTimeout(timeoutId);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* Fetch the span tree for a root span.
|
|
452
|
-
* Blocking GET request.
|
|
453
|
-
*
|
|
454
|
-
* Pass `includeOutputs: false` for a payload-free tree (structure +
|
|
455
|
-
* `externalSpanId` only), so recorded outputs are fetched lazily per mocked
|
|
456
|
-
* span instead of all up front. Omit it (default eager) for `mock: "all"`.
|
|
457
|
-
*/
|
|
458
|
-
async getSpanTree(externalSpanId, options) {
|
|
459
|
-
const query = options?.includeOutputs === false ? "?includeOutputs=false" : "";
|
|
460
|
-
const url = `${this.serviceUrl}/api/sdk/replay/spanTree/${externalSpanId}${query}`;
|
|
461
|
-
const controller = new AbortController();
|
|
462
|
-
const timeoutId = setTimeout(() => controller.abort(), 3e4);
|
|
463
|
-
try {
|
|
464
|
-
const response = await fetch(url, {
|
|
465
|
-
method: "GET",
|
|
466
|
-
headers: { Authorization: `Bearer ${this.resolveApiKey() ?? ""}` },
|
|
467
|
-
signal: controller.signal
|
|
468
|
-
});
|
|
469
|
-
if (!response.ok) {
|
|
470
|
-
const errorText = await response.text();
|
|
471
|
-
throw new BitfabError(
|
|
472
|
-
`HTTP ${response.status}: ${errorText.slice(0, 500)}`
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
return await response.json();
|
|
476
|
-
} catch (error) {
|
|
477
|
-
if (error instanceof BitfabError) {
|
|
478
|
-
throw error;
|
|
479
|
-
}
|
|
480
|
-
if (error instanceof Error) {
|
|
481
|
-
if (error.name === "AbortError") {
|
|
482
|
-
throw new BitfabError("Request timed out after 30000ms");
|
|
483
|
-
}
|
|
484
|
-
throw new BitfabError(error.message);
|
|
485
|
-
}
|
|
486
|
-
throw new BitfabError("Unknown error occurred");
|
|
487
|
-
} finally {
|
|
488
|
-
clearTimeout(timeoutId);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* Mark a replay test run as completed.
|
|
493
|
-
* Blocking call.
|
|
494
|
-
*/
|
|
495
|
-
async completeReplay(testRunId) {
|
|
496
|
-
return this.request(
|
|
497
|
-
"/api/sdk/replay/complete",
|
|
498
|
-
{ testRunId },
|
|
499
|
-
{ timeout: 3e4 }
|
|
500
|
-
);
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* Ask the server to materialize a per-trace DB branch lease from a
|
|
504
|
-
* captured `dbSnapshotRef`. Blocking - the resolver creates a Neon
|
|
505
|
-
* snapshot + preview branch and polls operations to readiness, which
|
|
506
|
-
* can take seconds.
|
|
507
|
-
*/
|
|
508
|
-
async resolveDbBranchLease(testRunId, traceId, dbBranchSettings) {
|
|
509
|
-
return this.request(
|
|
510
|
-
"/api/sdk/replay/resolveDbBranchLease",
|
|
511
|
-
{ testRunId, traceId, dbBranchSettings },
|
|
512
|
-
{ timeout: REPLAY_DB_BRANCH_REQUEST_TIMEOUT_MS }
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
/** Release a previously-resolved DB branch by deleting its Neon branch. Idempotent server-side. */
|
|
516
|
-
async releaseDbBranchLease(neonBranchId) {
|
|
517
|
-
await this.request(
|
|
518
|
-
"/api/sdk/replay/releaseDbBranchLease",
|
|
519
|
-
{ neonBranchId },
|
|
520
|
-
{ timeout: 3e4 }
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
};
|
|
19
|
+
} from "./chunk-4J36FZ4K.js";
|
|
524
20
|
|
|
525
21
|
// src/processorPayload.ts
|
|
526
22
|
var SERIALIZATION_DEGRADED_STEP = "serialization_degraded";
|
|
@@ -641,7 +137,8 @@ var BitfabClaudeAgentHandler = class {
|
|
|
641
137
|
// its root. The prompt is not present anywhere in the message stream, so it
|
|
642
138
|
// must be handed in explicitly.
|
|
643
139
|
this.hasRootInput = false;
|
|
644
|
-
this.
|
|
140
|
+
this.ownsHttpClient = config._httpClient === void 0;
|
|
141
|
+
this.httpClient = config._httpClient ?? new HttpClient({
|
|
645
142
|
apiKey: config.apiKey,
|
|
646
143
|
serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
|
|
647
144
|
timeout: config.timeout ?? 1e4
|
|
@@ -654,6 +151,14 @@ var BitfabClaudeAgentHandler = class {
|
|
|
654
151
|
this.subagentStartHook = this.subagentStartHook.bind(this);
|
|
655
152
|
this.subagentStopHook = this.subagentStopHook.bind(this);
|
|
656
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Flush and release the span transport this handler started. A no-op when
|
|
156
|
+
* the handler borrowed a `Bitfab` client's HTTP client: that client's
|
|
157
|
+
* `close()` owns the worker's lifetime.
|
|
158
|
+
*/
|
|
159
|
+
async close(timeoutMs) {
|
|
160
|
+
return this.ownsHttpClient ? this.httpClient.close(timeoutMs) : true;
|
|
161
|
+
}
|
|
657
162
|
// ── trace lifecycle ──────────────────────────────────────────
|
|
658
163
|
ensureTrace() {
|
|
659
164
|
if (this.traceId !== null) {
|
|
@@ -1659,7 +1164,8 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
1659
1164
|
this.ignoreCustomEvent = true;
|
|
1660
1165
|
this.runToSpan = /* @__PURE__ */ new Map();
|
|
1661
1166
|
this.invocations = /* @__PURE__ */ new Map();
|
|
1662
|
-
this.
|
|
1167
|
+
this.ownsHttpClient = config._httpClient === void 0;
|
|
1168
|
+
this.httpClient = config._httpClient ?? new HttpClient({
|
|
1663
1169
|
apiKey: config.apiKey,
|
|
1664
1170
|
serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
|
|
1665
1171
|
timeout: config.timeout ?? 1e4
|
|
@@ -1667,6 +1173,14 @@ var BitfabLangGraphCallbackHandler = class {
|
|
|
1667
1173
|
this.traceFunctionKey = config.traceFunctionKey;
|
|
1668
1174
|
this.getActiveSpanContext = config.getActiveSpanContext ?? null;
|
|
1669
1175
|
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Flush and release the span transport this handler started. A no-op when
|
|
1178
|
+
* the handler borrowed a `Bitfab` client's HTTP client: that client's
|
|
1179
|
+
* `close()` owns the worker's lifetime.
|
|
1180
|
+
*/
|
|
1181
|
+
async close(timeoutMs) {
|
|
1182
|
+
return this.ownsHttpClient ? this.httpClient.close(timeoutMs) : true;
|
|
1183
|
+
}
|
|
1670
1184
|
// ── lifecycle helpers ──────────────────────────────────────────
|
|
1671
1185
|
startSpan(runId, parentRunId, name, spanType, inputData, metadata, tags) {
|
|
1672
1186
|
const parentSpan = parentRunId ? this.runToSpan.get(parentRunId) : void 0;
|
|
@@ -2138,7 +1652,8 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2138
1652
|
this.activeTraces = {};
|
|
2139
1653
|
this.activeSpanMappings = {};
|
|
2140
1654
|
this.canonicalTraceIds = {};
|
|
2141
|
-
this.
|
|
1655
|
+
this.ownsHttpClient = config._httpClient === void 0;
|
|
1656
|
+
this.httpClient = config._httpClient ?? new HttpClient({
|
|
2142
1657
|
apiKey: config.apiKey,
|
|
2143
1658
|
serviceUrl: config.serviceUrl ?? DEFAULT_SERVICE_URL,
|
|
2144
1659
|
timeout: config.timeout ?? 1e4
|
|
@@ -2154,6 +1669,14 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2154
1669
|
this.canonicalTraceIds[sourceTraceId] = created;
|
|
2155
1670
|
return created;
|
|
2156
1671
|
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Flush and release the span transport this processor started. A no-op when
|
|
1674
|
+
* the processor borrowed a `Bitfab` client's HTTP client: that client's
|
|
1675
|
+
* `close()` owns the worker's lifetime.
|
|
1676
|
+
*/
|
|
1677
|
+
async close(timeoutMs) {
|
|
1678
|
+
return this.ownsHttpClient ? this.httpClient.close(timeoutMs) : true;
|
|
1679
|
+
}
|
|
2157
1680
|
/**
|
|
2158
1681
|
* Called when a trace is started.
|
|
2159
1682
|
* If there's an active withSpan context, the trace ID is remapped to the
|
|
@@ -2208,14 +1731,16 @@ var BitfabOpenAITracingProcessor = class {
|
|
|
2208
1731
|
* Called when a trace is being flushed.
|
|
2209
1732
|
*/
|
|
2210
1733
|
async forceFlush() {
|
|
1734
|
+
await this.httpClient.waitForPendingRequests();
|
|
2211
1735
|
}
|
|
2212
1736
|
/**
|
|
2213
1737
|
* Called when the trace processor is shutting down.
|
|
2214
1738
|
*/
|
|
2215
|
-
async shutdown(
|
|
1739
|
+
async shutdown(timeout) {
|
|
2216
1740
|
this.activeTraces = {};
|
|
2217
1741
|
this.activeSpanMappings = {};
|
|
2218
1742
|
this.canonicalTraceIds = {};
|
|
1743
|
+
await this.close(timeout);
|
|
2219
1744
|
}
|
|
2220
1745
|
/**
|
|
2221
1746
|
* Send trace to Bitfab API (fire-and-forget).
|
|
@@ -2481,7 +2006,6 @@ var BitfabVercelAiHandler = class {
|
|
|
2481
2006
|
|
|
2482
2007
|
// src/client.ts
|
|
2483
2008
|
var activeTraceStates = /* @__PURE__ */ new Map();
|
|
2484
|
-
var pendingSpanPromises = /* @__PURE__ */ new Map();
|
|
2485
2009
|
var asyncLocalStorage = null;
|
|
2486
2010
|
var SPAN_CONTEXT_STORAGE_SYMBOL = /* @__PURE__ */ Symbol.for("bitfab.spanContextStorage");
|
|
2487
2011
|
var initializeAsyncContext = () => {
|
|
@@ -2837,6 +2361,23 @@ var Bitfab = class {
|
|
|
2837
2361
|
timeout: this.timeout
|
|
2838
2362
|
});
|
|
2839
2363
|
}
|
|
2364
|
+
/**
|
|
2365
|
+
* Flush and permanently close this client's tracing resources: its pending
|
|
2366
|
+
* requests and the single span-transport worker shared by its decorators and
|
|
2367
|
+
* framework handlers.
|
|
2368
|
+
*
|
|
2369
|
+
* Resolves `false` when delivery failed or the deadline expired. Long-lived
|
|
2370
|
+
* processes never need this (the transport batches in the background and the
|
|
2371
|
+
* exit hook drains it); scripts and tests that want a hard guarantee should
|
|
2372
|
+
* await it.
|
|
2373
|
+
*
|
|
2374
|
+
* Deliberately not a `Symbol.asyncDispose` method: the SDK targets runtimes
|
|
2375
|
+
* where that symbol may be absent, and a computed key on a missing symbol
|
|
2376
|
+
* throws at class-definition time, taking the whole SDK down on load.
|
|
2377
|
+
*/
|
|
2378
|
+
close(timeoutMs) {
|
|
2379
|
+
return this.httpClient.close(timeoutMs);
|
|
2380
|
+
}
|
|
2840
2381
|
/**
|
|
2841
2382
|
* Resolve the API key lazily, the first time a span actually needs it.
|
|
2842
2383
|
*
|
|
@@ -2986,7 +2527,8 @@ var Bitfab = class {
|
|
|
2986
2527
|
getActiveSpanContext: () => {
|
|
2987
2528
|
const stack = getSpanStack();
|
|
2988
2529
|
return stack[stack.length - 1] ?? null;
|
|
2989
|
-
}
|
|
2530
|
+
},
|
|
2531
|
+
_httpClient: this.httpClient
|
|
2990
2532
|
});
|
|
2991
2533
|
}
|
|
2992
2534
|
/**
|
|
@@ -3046,7 +2588,8 @@ var Bitfab = class {
|
|
|
3046
2588
|
getActiveSpanContext: () => {
|
|
3047
2589
|
const stack = getSpanStack();
|
|
3048
2590
|
return stack[stack.length - 1] ?? null;
|
|
3049
|
-
}
|
|
2591
|
+
},
|
|
2592
|
+
_httpClient: this.httpClient
|
|
3050
2593
|
});
|
|
3051
2594
|
}
|
|
3052
2595
|
/**
|
|
@@ -3098,7 +2641,8 @@ var Bitfab = class {
|
|
|
3098
2641
|
getActiveSpanContext: () => {
|
|
3099
2642
|
const stack = getSpanStack();
|
|
3100
2643
|
return stack[stack.length - 1] ?? null;
|
|
3101
|
-
}
|
|
2644
|
+
},
|
|
2645
|
+
_httpClient: this.httpClient
|
|
3102
2646
|
});
|
|
3103
2647
|
}
|
|
3104
2648
|
/**
|
|
@@ -3351,7 +2895,6 @@ var Bitfab = class {
|
|
|
3351
2895
|
},
|
|
3352
2896
|
dbSnapshotRef
|
|
3353
2897
|
});
|
|
3354
|
-
pendingSpanPromises.set(traceId, []);
|
|
3355
2898
|
registeredTraceId = traceId;
|
|
3356
2899
|
}
|
|
3357
2900
|
const functionName = fn.name !== "" ? fn.name : void 0;
|
|
@@ -3366,57 +2909,29 @@ var Bitfab = class {
|
|
|
3366
2909
|
startedAt,
|
|
3367
2910
|
spanType: options.type ?? "custom"
|
|
3368
2911
|
};
|
|
3369
|
-
const sendSpan = async (params
|
|
2912
|
+
const sendSpan = async (params) => {
|
|
3370
2913
|
const replayCtx = getReplayContext();
|
|
3371
|
-
const persistenceCollector = isRootSpan ? replayCtx?.pendingPersistence : void 0;
|
|
3372
|
-
let resolvePersistence;
|
|
3373
|
-
if (persistenceCollector && !spanOpts?.skipPersistenceRegistration) {
|
|
3374
|
-
persistenceCollector.push(
|
|
3375
|
-
new Promise((resolve) => {
|
|
3376
|
-
resolvePersistence = resolve;
|
|
3377
|
-
})
|
|
3378
|
-
);
|
|
3379
|
-
}
|
|
3380
2914
|
try {
|
|
3381
2915
|
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3382
2916
|
const traceDropped = activeTraceStates.get(traceId)?.dropped === true;
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
testRunId
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
inputSourceSpanId
|
|
3394
|
-
|
|
3395
|
-
});
|
|
3396
|
-
if (isRootSpan) {
|
|
3397
|
-
const pending = pendingSpanPromises.get(traceId) ?? [];
|
|
3398
|
-
pending.push(spanPromise);
|
|
3399
|
-
if (persistenceCollector) {
|
|
3400
|
-
await Promise.allSettled(pending);
|
|
3401
|
-
} else {
|
|
3402
|
-
let raceTimer;
|
|
3403
|
-
try {
|
|
3404
|
-
await Promise.race([
|
|
3405
|
-
Promise.allSettled(pending),
|
|
3406
|
-
new Promise((resolve) => {
|
|
3407
|
-
raceTimer = setTimeout(resolve, 5e3);
|
|
3408
|
-
unrefTimer(raceTimer);
|
|
3409
|
-
})
|
|
3410
|
-
]);
|
|
3411
|
-
} finally {
|
|
3412
|
-
if (raceTimer) {
|
|
3413
|
-
clearTimeout(raceTimer);
|
|
3414
|
-
}
|
|
2917
|
+
if (!traceDropped) {
|
|
2918
|
+
self.sendWrapperSpan({
|
|
2919
|
+
...baseSpanParams,
|
|
2920
|
+
...params,
|
|
2921
|
+
contexts: newContext.contexts,
|
|
2922
|
+
prompt: newContext.prompt,
|
|
2923
|
+
endedAt,
|
|
2924
|
+
...replayCtx?.testRunId && {
|
|
2925
|
+
testRunId: replayCtx.testRunId
|
|
2926
|
+
},
|
|
2927
|
+
...replayCtx?.inputSourceSpanId && {
|
|
2928
|
+
inputSourceSpanId: replayCtx.inputSourceSpanId
|
|
3415
2929
|
}
|
|
3416
|
-
}
|
|
3417
|
-
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
if (isRootSpan) {
|
|
3418
2933
|
const traceState = activeTraceStates.get(traceId);
|
|
3419
|
-
|
|
2934
|
+
self.sendTraceCompletion({
|
|
3420
2935
|
traceFunctionKey,
|
|
3421
2936
|
traceId,
|
|
3422
2937
|
startedAt: traceState?.startedAt ?? startedAt,
|
|
@@ -3442,20 +2957,8 @@ var Bitfab = class {
|
|
|
3442
2957
|
}
|
|
3443
2958
|
});
|
|
3444
2959
|
activeTraceStates.delete(traceId);
|
|
3445
|
-
if (persistenceCollector) {
|
|
3446
|
-
await completionPromise;
|
|
3447
|
-
}
|
|
3448
|
-
} else {
|
|
3449
|
-
const pending = pendingSpanPromises.get(traceId);
|
|
3450
|
-
if (pending) {
|
|
3451
|
-
pending.push(spanPromise);
|
|
3452
|
-
} else {
|
|
3453
|
-
pendingSpanPromises.set(traceId, [spanPromise]);
|
|
3454
|
-
}
|
|
3455
2960
|
}
|
|
3456
2961
|
} catch {
|
|
3457
|
-
} finally {
|
|
3458
|
-
resolvePersistence?.();
|
|
3459
2962
|
}
|
|
3460
2963
|
};
|
|
3461
2964
|
const replayCtxForMock = getReplayContext();
|
|
@@ -3539,30 +3042,14 @@ var Bitfab = class {
|
|
|
3539
3042
|
}
|
|
3540
3043
|
const recordSpan = (result) => {
|
|
3541
3044
|
if (options.finalize) {
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
if (persistenceCollector) {
|
|
3546
|
-
persistenceCollector.push(
|
|
3547
|
-
new Promise((resolve) => {
|
|
3548
|
-
resolvePersistence = resolve;
|
|
3549
|
-
})
|
|
3550
|
-
);
|
|
3551
|
-
}
|
|
3552
|
-
void Promise.resolve().then(() => options.finalize(result)).then(
|
|
3553
|
-
(output) => sendSpan(
|
|
3554
|
-
{ result: output },
|
|
3555
|
-
{ skipPersistenceRegistration: true }
|
|
3556
|
-
)
|
|
3557
|
-
).catch(
|
|
3558
|
-
(error) => sendSpan(
|
|
3559
|
-
{
|
|
3045
|
+
void self.httpClient.trackDeferred(
|
|
3046
|
+
Promise.resolve().then(() => options.finalize(result)).then((output) => sendSpan({ result: output })).catch(
|
|
3047
|
+
(error) => sendSpan({
|
|
3560
3048
|
result: void 0,
|
|
3561
3049
|
error: error instanceof Error ? `finalize failed: ${error.message}` : `finalize failed: ${String(error)}`
|
|
3562
|
-
}
|
|
3563
|
-
{ skipPersistenceRegistration: true }
|
|
3050
|
+
})
|
|
3564
3051
|
)
|
|
3565
|
-
)
|
|
3052
|
+
);
|
|
3566
3053
|
} else {
|
|
3567
3054
|
void sendSpan({ result });
|
|
3568
3055
|
}
|
|
@@ -3590,7 +3077,6 @@ var Bitfab = class {
|
|
|
3590
3077
|
} catch (setupError) {
|
|
3591
3078
|
if (registeredTraceId) {
|
|
3592
3079
|
activeTraceStates.delete(registeredTraceId);
|
|
3593
|
-
pendingSpanPromises.delete(registeredTraceId);
|
|
3594
3080
|
}
|
|
3595
3081
|
if (getReplayContext()) {
|
|
3596
3082
|
throw setupError;
|
|
@@ -3713,7 +3199,7 @@ var Bitfab = class {
|
|
|
3713
3199
|
/**
|
|
3714
3200
|
* Send trace completion when a root span ends.
|
|
3715
3201
|
* Internal method to record trace completion with end time.
|
|
3716
|
-
*
|
|
3202
|
+
* Queued on the client's span transport; delivery is the transport's job.
|
|
3717
3203
|
*/
|
|
3718
3204
|
sendTraceCompletion(params) {
|
|
3719
3205
|
const rawTrace = {
|
|
@@ -3749,7 +3235,7 @@ var Bitfab = class {
|
|
|
3749
3235
|
accessed: params.dbSnapshotUsage.accessed
|
|
3750
3236
|
};
|
|
3751
3237
|
}
|
|
3752
|
-
|
|
3238
|
+
this.httpClient.sendExternalTrace({
|
|
3753
3239
|
id: params.traceId,
|
|
3754
3240
|
type: "sdk-function",
|
|
3755
3241
|
source: "typescript-sdk-function",
|
|
@@ -3764,7 +3250,7 @@ var Bitfab = class {
|
|
|
3764
3250
|
/**
|
|
3765
3251
|
* Send a wrapper span from function execution.
|
|
3766
3252
|
* Internal method to record spans when using withSpan.
|
|
3767
|
-
*
|
|
3253
|
+
* Queued on the client's span transport; delivery is the transport's job.
|
|
3768
3254
|
*/
|
|
3769
3255
|
sendWrapperSpan(params) {
|
|
3770
3256
|
const serializedInputs = serializeValue(params.inputs);
|
|
@@ -3805,7 +3291,7 @@ var Bitfab = class {
|
|
|
3805
3291
|
if (params.inputSourceSpanId) {
|
|
3806
3292
|
externalSpan.input_source_span_id = params.inputSourceSpanId;
|
|
3807
3293
|
}
|
|
3808
|
-
|
|
3294
|
+
this.httpClient.sendExternalSpan({
|
|
3809
3295
|
id: params.spanId,
|
|
3810
3296
|
traceId: params.traceId,
|
|
3811
3297
|
type: "sdk-function",
|
|
@@ -3839,7 +3325,7 @@ var Bitfab = class {
|
|
|
3839
3325
|
`Function is wrapped with trace function key '${wrappedKey}' but replay was called with '${traceFunctionKey}'. Pass matching keys, or pass the unwrapped function to replay it under the explicit key.`
|
|
3840
3326
|
);
|
|
3841
3327
|
}
|
|
3842
|
-
const { replay: doReplay } = await import("./replay-
|
|
3328
|
+
const { replay: doReplay } = await import("./replay-QFNYP7EY.js");
|
|
3843
3329
|
return doReplay(
|
|
3844
3330
|
this.httpClient,
|
|
3845
3331
|
this.serviceUrl,
|
|
@@ -4043,9 +3529,6 @@ var finalizers = {
|
|
|
4043
3529
|
};
|
|
4044
3530
|
|
|
4045
3531
|
export {
|
|
4046
|
-
__version__,
|
|
4047
|
-
DEFAULT_SERVICE_URL,
|
|
4048
|
-
flushTraces,
|
|
4049
3532
|
BitfabClaudeAgentHandler,
|
|
4050
3533
|
SUPPORTED_PROVIDERS,
|
|
4051
3534
|
BitfabLangGraphCallbackHandler,
|
|
@@ -4059,4 +3542,4 @@ export {
|
|
|
4059
3542
|
BitfabFunction,
|
|
4060
3543
|
finalizers
|
|
4061
3544
|
};
|
|
4062
|
-
//# sourceMappingURL=chunk-
|
|
3545
|
+
//# sourceMappingURL=chunk-ESKBRHVG.js.map
|