@replayci/replay 0.1.1 → 0.1.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/dist/index.cjs +645 -45
- package/dist/index.d.cts +72 -8
- package/dist/index.d.ts +72 -8
- package/dist/index.js +651 -41
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,7 @@ var CaptureBuffer = class {
|
|
|
50
50
|
apiKey;
|
|
51
51
|
endpoint;
|
|
52
52
|
diagnostics;
|
|
53
|
+
onStateChange;
|
|
53
54
|
fetchImpl;
|
|
54
55
|
now;
|
|
55
56
|
queue = [];
|
|
@@ -59,6 +60,11 @@ var CaptureBuffer = class {
|
|
|
59
60
|
circuitOpenUntil = 0;
|
|
60
61
|
remoteDisabled = false;
|
|
61
62
|
closed = false;
|
|
63
|
+
droppedOverflowTotal = 0;
|
|
64
|
+
lastFlushAttemptMs = 0;
|
|
65
|
+
lastFlushSuccessMs = 0;
|
|
66
|
+
lastFlushErrorMs = 0;
|
|
67
|
+
lastFlushErrorMsg = null;
|
|
62
68
|
constructor(opts) {
|
|
63
69
|
this.apiKey = opts.apiKey;
|
|
64
70
|
this.endpoint = normalizeEndpoint(opts.endpoint);
|
|
@@ -69,6 +75,7 @@ var CaptureBuffer = class {
|
|
|
69
75
|
MAX_SEND_TIMEOUT
|
|
70
76
|
);
|
|
71
77
|
this.diagnostics = opts.diagnostics;
|
|
78
|
+
this.onStateChange = opts.onStateChange;
|
|
72
79
|
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
73
80
|
this.now = opts.now ?? Date.now;
|
|
74
81
|
this.scheduleNextDrain();
|
|
@@ -85,6 +92,21 @@ var CaptureBuffer = class {
|
|
|
85
92
|
get isRemoteDisabled() {
|
|
86
93
|
return this.remoteDisabled;
|
|
87
94
|
}
|
|
95
|
+
get droppedOverflow() {
|
|
96
|
+
return this.droppedOverflowTotal;
|
|
97
|
+
}
|
|
98
|
+
get lastFlushAttemptAt() {
|
|
99
|
+
return this.lastFlushAttemptMs;
|
|
100
|
+
}
|
|
101
|
+
get lastFlushSuccessAt() {
|
|
102
|
+
return this.lastFlushSuccessMs;
|
|
103
|
+
}
|
|
104
|
+
get lastFlushErrorAt() {
|
|
105
|
+
return this.lastFlushErrorMs;
|
|
106
|
+
}
|
|
107
|
+
get lastFlushError() {
|
|
108
|
+
return this.lastFlushErrorMsg;
|
|
109
|
+
}
|
|
88
110
|
push(item) {
|
|
89
111
|
if (this.closed || this.remoteDisabled) {
|
|
90
112
|
return;
|
|
@@ -94,10 +116,12 @@ var CaptureBuffer = class {
|
|
|
94
116
|
}
|
|
95
117
|
if (this.queue.length >= this.maxBuffer) {
|
|
96
118
|
this.queue.shift();
|
|
119
|
+
this.droppedOverflowTotal += 1;
|
|
97
120
|
emitDiagnostics(this.diagnostics, {
|
|
98
121
|
type: "buffer_overflow",
|
|
99
122
|
dropped: 1
|
|
100
123
|
});
|
|
124
|
+
emitStateChange(this.onStateChange, { type: "buffer_overflow", dropped: 1 });
|
|
101
125
|
}
|
|
102
126
|
this.queue.push(item);
|
|
103
127
|
}
|
|
@@ -155,11 +179,13 @@ var CaptureBuffer = class {
|
|
|
155
179
|
if (batch.length === 0) {
|
|
156
180
|
return;
|
|
157
181
|
}
|
|
182
|
+
this.lastFlushAttemptMs = this.now();
|
|
183
|
+
emitStateChange(this.onStateChange, { type: "flush_attempt" });
|
|
158
184
|
let payload = "";
|
|
159
185
|
try {
|
|
160
186
|
payload = JSON.stringify({ captures: batch });
|
|
161
187
|
} catch {
|
|
162
|
-
this.handleFailure();
|
|
188
|
+
this.handleFailure("JSON serialization failed");
|
|
163
189
|
return;
|
|
164
190
|
}
|
|
165
191
|
const controller = new AbortController();
|
|
@@ -181,22 +207,35 @@ var CaptureBuffer = class {
|
|
|
181
207
|
this.clearTimer();
|
|
182
208
|
this.failureCount = 0;
|
|
183
209
|
this.circuitOpenUntil = Number.MAX_SAFE_INTEGER;
|
|
210
|
+
emitDiagnostics(this.diagnostics, { type: "remote_disabled" });
|
|
211
|
+
emitStateChange(this.onStateChange, { type: "remote_disabled" });
|
|
184
212
|
return;
|
|
185
213
|
}
|
|
186
214
|
if (!response.ok) {
|
|
187
|
-
this.handleFailure();
|
|
215
|
+
this.handleFailure(`HTTP ${response.status}`);
|
|
188
216
|
return;
|
|
189
217
|
}
|
|
190
218
|
this.failureCount = 0;
|
|
191
219
|
this.circuitOpenUntil = 0;
|
|
192
|
-
|
|
193
|
-
this.
|
|
220
|
+
this.lastFlushSuccessMs = this.now();
|
|
221
|
+
this.lastFlushErrorMsg = null;
|
|
222
|
+
emitStateChange(this.onStateChange, { type: "flush_success", batch_size: batch.length });
|
|
223
|
+
} catch (err) {
|
|
224
|
+
this.handleFailure(err instanceof Error ? err.message : String(err));
|
|
194
225
|
} finally {
|
|
195
226
|
clearTimeout(timeout);
|
|
196
227
|
}
|
|
197
228
|
}
|
|
198
|
-
handleFailure() {
|
|
229
|
+
handleFailure(errorMsg) {
|
|
199
230
|
this.failureCount += 1;
|
|
231
|
+
const errorStr = errorMsg ?? "unknown error";
|
|
232
|
+
this.lastFlushErrorMs = this.now();
|
|
233
|
+
this.lastFlushErrorMsg = errorStr;
|
|
234
|
+
emitDiagnostics(this.diagnostics, {
|
|
235
|
+
type: "flush_error",
|
|
236
|
+
error: errorStr
|
|
237
|
+
});
|
|
238
|
+
emitStateChange(this.onStateChange, { type: "flush_error", error: errorStr });
|
|
200
239
|
if (this.failureCount >= CIRCUIT_BREAKER_FAILURE_LIMIT) {
|
|
201
240
|
this.circuitOpenUntil = this.now() + CIRCUIT_BREAKER_MS;
|
|
202
241
|
emitDiagnostics(this.diagnostics, {
|
|
@@ -204,6 +243,12 @@ var CaptureBuffer = class {
|
|
|
204
243
|
failures: this.failureCount,
|
|
205
244
|
backoffMs: CIRCUIT_BREAKER_MS
|
|
206
245
|
});
|
|
246
|
+
emitStateChange(this.onStateChange, {
|
|
247
|
+
type: "circuit_open",
|
|
248
|
+
failures: this.failureCount,
|
|
249
|
+
backoffMs: CIRCUIT_BREAKER_MS,
|
|
250
|
+
openUntil: this.circuitOpenUntil
|
|
251
|
+
});
|
|
207
252
|
try {
|
|
208
253
|
console.warn(
|
|
209
254
|
`[replayci] Capture buffer circuit breaker open after ${this.failureCount} consecutive failures. Captures will be dropped for ${CIRCUIT_BREAKER_MS / 6e4} minutes.`
|
|
@@ -283,6 +328,12 @@ function emitDiagnostics(diagnostics, event) {
|
|
|
283
328
|
} catch {
|
|
284
329
|
}
|
|
285
330
|
}
|
|
331
|
+
function emitStateChange(listener, event) {
|
|
332
|
+
try {
|
|
333
|
+
listener?.(event);
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
}
|
|
286
337
|
function isRemoteDisable(response) {
|
|
287
338
|
return response.headers.get("x-replayci-disable")?.toLowerCase() === "true";
|
|
288
339
|
}
|
|
@@ -933,7 +984,8 @@ function parseNodeMajorVersion(nodeVersion) {
|
|
|
933
984
|
|
|
934
985
|
// src/captureSchema.ts
|
|
935
986
|
var CAPTURE_SCHEMA_VERSION_LEGACY = "2026-03-04";
|
|
936
|
-
var
|
|
987
|
+
var CAPTURE_SCHEMA_VERSION_V2 = "2026-03-06";
|
|
988
|
+
var CAPTURE_SCHEMA_VERSION_CURRENT = "2026-03-09";
|
|
937
989
|
function isRecord(value) {
|
|
938
990
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
939
991
|
}
|
|
@@ -1097,9 +1149,19 @@ function validateUsage(value, path) {
|
|
|
1097
1149
|
total_tokens: requireNonNegativeInt(usage.total_tokens, `${path}.total_tokens`)
|
|
1098
1150
|
};
|
|
1099
1151
|
}
|
|
1152
|
+
function optionalString(value, path) {
|
|
1153
|
+
if (value === void 0 || value === null) {
|
|
1154
|
+
return void 0;
|
|
1155
|
+
}
|
|
1156
|
+
if (typeof value !== "string") {
|
|
1157
|
+
throw new Error(`${path} must be a string when provided`);
|
|
1158
|
+
}
|
|
1159
|
+
return value;
|
|
1160
|
+
}
|
|
1100
1161
|
function parseCommonCapture(capture, index, modelId, schemaVersion) {
|
|
1101
1162
|
const toolNames = requireStringArray(capture.tool_names, `captures[${index}].tool_names`);
|
|
1102
1163
|
const primaryToolName = capture.primary_tool_name === void 0 ? toolNames[0] ?? null : nullableString(capture.primary_tool_name, `captures[${index}].primary_tool_name`);
|
|
1164
|
+
const sdkSessionId = optionalString(capture.sdk_session_id, `captures[${index}].sdk_session_id`);
|
|
1103
1165
|
return {
|
|
1104
1166
|
schema_version: schemaVersion,
|
|
1105
1167
|
agent: requireString(capture.agent, `captures[${index}].agent`),
|
|
@@ -1112,7 +1174,8 @@ function parseCommonCapture(capture, index, modelId, schemaVersion) {
|
|
|
1112
1174
|
response: validateResponse(capture.response, `captures[${index}].response`),
|
|
1113
1175
|
...capture.validation !== void 0 ? { validation: validateValidation(capture.validation, `captures[${index}].validation`) } : {},
|
|
1114
1176
|
...capture.usage !== void 0 ? { usage: validateUsage(capture.usage, `captures[${index}].usage`) } : {},
|
|
1115
|
-
latency_ms: requireNonNegativeInt(capture.latency_ms, `captures[${index}].latency_ms`)
|
|
1177
|
+
latency_ms: requireNonNegativeInt(capture.latency_ms, `captures[${index}].latency_ms`),
|
|
1178
|
+
...sdkSessionId !== void 0 ? { sdk_session_id: sdkSessionId } : {}
|
|
1116
1179
|
};
|
|
1117
1180
|
}
|
|
1118
1181
|
function parseLegacyCapturedCall(capture, index) {
|
|
@@ -1123,6 +1186,14 @@ function parseLegacyCapturedCall(capture, index) {
|
|
|
1123
1186
|
CAPTURE_SCHEMA_VERSION_LEGACY
|
|
1124
1187
|
);
|
|
1125
1188
|
}
|
|
1189
|
+
function parseV2CapturedCall(capture, index) {
|
|
1190
|
+
return parseCommonCapture(
|
|
1191
|
+
capture,
|
|
1192
|
+
index,
|
|
1193
|
+
requireString(capture.model_id, `captures[${index}].model_id`),
|
|
1194
|
+
CAPTURE_SCHEMA_VERSION_V2
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1126
1197
|
function parseCurrentCapturedCall(capture, index) {
|
|
1127
1198
|
return parseCommonCapture(
|
|
1128
1199
|
capture,
|
|
@@ -1133,25 +1204,254 @@ function parseCurrentCapturedCall(capture, index) {
|
|
|
1133
1204
|
}
|
|
1134
1205
|
var CAPTURE_SCHEMA_PARSERS = {
|
|
1135
1206
|
[CAPTURE_SCHEMA_VERSION_LEGACY]: parseLegacyCapturedCall,
|
|
1207
|
+
[CAPTURE_SCHEMA_VERSION_V2]: parseV2CapturedCall,
|
|
1136
1208
|
[CAPTURE_SCHEMA_VERSION_CURRENT]: parseCurrentCapturedCall
|
|
1137
1209
|
};
|
|
1138
1210
|
|
|
1211
|
+
// src/healthStore.ts
|
|
1212
|
+
var import_node_crypto = require("crypto");
|
|
1213
|
+
var import_node_fs = require("fs");
|
|
1214
|
+
var import_promises = require("fs/promises");
|
|
1215
|
+
var import_node_path = require("path");
|
|
1216
|
+
var import_meta = {};
|
|
1217
|
+
var SESSIONS_DIR = "observe-sessions";
|
|
1218
|
+
var SCHEMA_VERSION = "1.0";
|
|
1219
|
+
var JANITOR_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1220
|
+
function generateSessionId() {
|
|
1221
|
+
return `obs_${(0, import_node_crypto.randomBytes)(12).toString("hex")}`;
|
|
1222
|
+
}
|
|
1223
|
+
function resolveStateDir(opts) {
|
|
1224
|
+
let explicit;
|
|
1225
|
+
let workDir;
|
|
1226
|
+
if (typeof opts === "string") {
|
|
1227
|
+
workDir = opts;
|
|
1228
|
+
} else if (opts != null) {
|
|
1229
|
+
explicit = opts.stateDir;
|
|
1230
|
+
workDir = opts.cwd;
|
|
1231
|
+
}
|
|
1232
|
+
if (typeof explicit === "string" && explicit.length > 0) {
|
|
1233
|
+
return explicit;
|
|
1234
|
+
}
|
|
1235
|
+
const envValue = typeof process !== "undefined" ? process.env.REPLAYCI_STATE_DIR : void 0;
|
|
1236
|
+
if (typeof envValue === "string" && envValue.length > 0) {
|
|
1237
|
+
return envValue;
|
|
1238
|
+
}
|
|
1239
|
+
const base = workDir ?? process.cwd();
|
|
1240
|
+
return (0, import_node_path.join)(base, ".replayci", "runtime");
|
|
1241
|
+
}
|
|
1242
|
+
function resolveSessionsDir(stateDir) {
|
|
1243
|
+
return (0, import_node_path.join)(stateDir, SESSIONS_DIR);
|
|
1244
|
+
}
|
|
1245
|
+
function sessionFilePath(sessionsDir, sessionId) {
|
|
1246
|
+
return (0, import_node_path.join)(sessionsDir, `${sessionId}.json`);
|
|
1247
|
+
}
|
|
1248
|
+
function writeHealthSnapshot(sessionsDir, sessionId, snapshot) {
|
|
1249
|
+
ensureDir(sessionsDir);
|
|
1250
|
+
const destPath = sessionFilePath(sessionsDir, sessionId);
|
|
1251
|
+
const tmpPath = (0, import_node_path.join)(sessionsDir, `.tmp_${sessionId}_${(0, import_node_crypto.randomBytes)(4).toString("hex")}.json`);
|
|
1252
|
+
const content = JSON.stringify(snapshot, null, 2);
|
|
1253
|
+
const fd = (0, import_node_fs.openSync)(tmpPath, "w", 384);
|
|
1254
|
+
try {
|
|
1255
|
+
(0, import_node_fs.writeFileSync)(fd, content);
|
|
1256
|
+
(0, import_node_fs.fsyncSync)(fd);
|
|
1257
|
+
} finally {
|
|
1258
|
+
(0, import_node_fs.closeSync)(fd);
|
|
1259
|
+
}
|
|
1260
|
+
(0, import_node_fs.renameSync)(tmpPath, destPath);
|
|
1261
|
+
}
|
|
1262
|
+
function parseHealthFile(raw) {
|
|
1263
|
+
let parsed;
|
|
1264
|
+
try {
|
|
1265
|
+
parsed = JSON.parse(raw);
|
|
1266
|
+
} catch {
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
if (!isValidHealthSnapshot(parsed)) {
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
return parsed;
|
|
1273
|
+
}
|
|
1274
|
+
function isValidHealthSnapshot(value) {
|
|
1275
|
+
if (value === null || typeof value !== "object") {
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
const obj = value;
|
|
1279
|
+
if (obj.schema_version !== SCHEMA_VERSION) {
|
|
1280
|
+
return false;
|
|
1281
|
+
}
|
|
1282
|
+
if (typeof obj.session_id !== "string" || obj.session_id.length === 0) {
|
|
1283
|
+
return false;
|
|
1284
|
+
}
|
|
1285
|
+
if (typeof obj.agent !== "string") {
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
const validProviders = ["openai", "anthropic", null];
|
|
1289
|
+
if (!validProviders.includes(obj.provider)) {
|
|
1290
|
+
return false;
|
|
1291
|
+
}
|
|
1292
|
+
const validStates = ["active", "inactive", "stopped", "stale"];
|
|
1293
|
+
if (typeof obj.state !== "string" || !validStates.includes(obj.state)) {
|
|
1294
|
+
return false;
|
|
1295
|
+
}
|
|
1296
|
+
if (!isValidActivation(obj.activation)) {
|
|
1297
|
+
return false;
|
|
1298
|
+
}
|
|
1299
|
+
if (!isValidProcess(obj.process)) {
|
|
1300
|
+
return false;
|
|
1301
|
+
}
|
|
1302
|
+
if (!isValidRuntime(obj.runtime)) {
|
|
1303
|
+
return false;
|
|
1304
|
+
}
|
|
1305
|
+
if (typeof obj.last_heartbeat_at !== "string") {
|
|
1306
|
+
return false;
|
|
1307
|
+
}
|
|
1308
|
+
return true;
|
|
1309
|
+
}
|
|
1310
|
+
function isValidActivation(value) {
|
|
1311
|
+
if (value === null || typeof value !== "object") {
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
const act = value;
|
|
1315
|
+
if (typeof act.active !== "boolean") {
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
const validReasons = [
|
|
1319
|
+
"active",
|
|
1320
|
+
"disabled",
|
|
1321
|
+
"missing_api_key",
|
|
1322
|
+
"unsupported_client",
|
|
1323
|
+
"double_wrap",
|
|
1324
|
+
"patch_target_unwritable",
|
|
1325
|
+
"internal_error"
|
|
1326
|
+
];
|
|
1327
|
+
if (typeof act.reason_code !== "string" || !validReasons.includes(act.reason_code)) {
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
if (typeof act.activated_at !== "string") {
|
|
1331
|
+
return false;
|
|
1332
|
+
}
|
|
1333
|
+
return true;
|
|
1334
|
+
}
|
|
1335
|
+
function isValidProcess(value) {
|
|
1336
|
+
if (value === null || typeof value !== "object") {
|
|
1337
|
+
return false;
|
|
1338
|
+
}
|
|
1339
|
+
const proc = value;
|
|
1340
|
+
if (typeof proc.cwd !== "string") {
|
|
1341
|
+
return false;
|
|
1342
|
+
}
|
|
1343
|
+
if (typeof proc.node_version !== "string") {
|
|
1344
|
+
return false;
|
|
1345
|
+
}
|
|
1346
|
+
if (typeof proc.sdk_version !== "string") {
|
|
1347
|
+
return false;
|
|
1348
|
+
}
|
|
1349
|
+
return true;
|
|
1350
|
+
}
|
|
1351
|
+
function isValidRuntime(value) {
|
|
1352
|
+
if (value === null || typeof value !== "object") {
|
|
1353
|
+
return false;
|
|
1354
|
+
}
|
|
1355
|
+
const rt = value;
|
|
1356
|
+
if (typeof rt.captures_seen !== "number") {
|
|
1357
|
+
return false;
|
|
1358
|
+
}
|
|
1359
|
+
if (typeof rt.dropped_overflow !== "number") {
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
if (typeof rt.queue_size !== "number") {
|
|
1363
|
+
return false;
|
|
1364
|
+
}
|
|
1365
|
+
if (typeof rt.consecutive_failures !== "number") {
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1368
|
+
if (typeof rt.remote_disabled !== "boolean") {
|
|
1369
|
+
return false;
|
|
1370
|
+
}
|
|
1371
|
+
return true;
|
|
1372
|
+
}
|
|
1373
|
+
function runJanitor(sessionsDir, currentSessionId, now) {
|
|
1374
|
+
try {
|
|
1375
|
+
if (!(0, import_node_fs.existsSync)(sessionsDir)) {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const nowMs = now ?? Date.now();
|
|
1379
|
+
const entries = (0, import_node_fs.readdirSync)(sessionsDir);
|
|
1380
|
+
for (const entry of entries) {
|
|
1381
|
+
if (!entry.endsWith(".json") || entry.startsWith(".tmp_")) {
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
const sessionId = entry.replace(/\.json$/, "");
|
|
1385
|
+
if (sessionId === currentSessionId) {
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
const filePath = (0, import_node_path.join)(sessionsDir, entry);
|
|
1389
|
+
let snapshot = null;
|
|
1390
|
+
try {
|
|
1391
|
+
const raw = (0, import_node_fs.readFileSync)(filePath, "utf-8");
|
|
1392
|
+
snapshot = parseHealthFile(raw);
|
|
1393
|
+
} catch {
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
if (!snapshot) {
|
|
1397
|
+
continue;
|
|
1398
|
+
}
|
|
1399
|
+
const isTerminal = snapshot.state === "stopped" || snapshot.state === "stale";
|
|
1400
|
+
if (!isTerminal) {
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
const heartbeatAge = nowMs - new Date(snapshot.last_heartbeat_at).getTime();
|
|
1404
|
+
if (heartbeatAge < JANITOR_TTL_MS) {
|
|
1405
|
+
continue;
|
|
1406
|
+
}
|
|
1407
|
+
try {
|
|
1408
|
+
(0, import_node_fs.unlinkSync)(filePath);
|
|
1409
|
+
} catch {
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
} catch {
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
function getSdkVersion() {
|
|
1416
|
+
try {
|
|
1417
|
+
const url = new URL("../package.json", import_meta.url);
|
|
1418
|
+
const raw = (0, import_node_fs.readFileSync)(url, "utf-8");
|
|
1419
|
+
const pkg = JSON.parse(raw);
|
|
1420
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
1421
|
+
} catch {
|
|
1422
|
+
return "0.0.0";
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
function ensureDir(dir) {
|
|
1426
|
+
try {
|
|
1427
|
+
(0, import_node_fs.mkdirSync)(dir, { recursive: true });
|
|
1428
|
+
} catch (err) {
|
|
1429
|
+
if (err.code !== "EEXIST") {
|
|
1430
|
+
throw err;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1139
1435
|
// src/observe.ts
|
|
1140
1436
|
var REPLAY_WRAPPED = /* @__PURE__ */ Symbol.for("replayci.wrapped");
|
|
1141
1437
|
var DEFAULT_AGENT = "default";
|
|
1438
|
+
var IDLE_HEARTBEAT_MS = 3e4;
|
|
1142
1439
|
function observe(client, opts = {}) {
|
|
1143
1440
|
assertSupportedNodeRuntime();
|
|
1441
|
+
const sessionId = generateSessionId();
|
|
1442
|
+
const agent = typeof opts.agent === "string" && opts.agent.length > 0 ? opts.agent : DEFAULT_AGENT;
|
|
1443
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1144
1444
|
try {
|
|
1145
1445
|
if (isDisabled(opts)) {
|
|
1146
|
-
return
|
|
1446
|
+
return createInactiveHandle(client, sessionId, agent, "disabled", void 0, now, opts.diagnostics, opts.stateDir);
|
|
1147
1447
|
}
|
|
1148
1448
|
const apiKey = resolveApiKey(opts);
|
|
1149
1449
|
if (!apiKey) {
|
|
1150
|
-
return
|
|
1450
|
+
return createInactiveHandle(client, sessionId, agent, "missing_api_key", void 0, now, opts.diagnostics, opts.stateDir);
|
|
1151
1451
|
}
|
|
1152
1452
|
const provider = detectProviderSafely(client, opts.diagnostics);
|
|
1153
1453
|
if (!provider) {
|
|
1154
|
-
return
|
|
1454
|
+
return createInactiveHandle(client, sessionId, agent, "unsupported_client", "Could not detect provider.", now, opts.diagnostics, opts.stateDir);
|
|
1155
1455
|
}
|
|
1156
1456
|
const patchTarget = resolvePatchTarget(client, provider);
|
|
1157
1457
|
if (!patchTarget) {
|
|
@@ -1160,14 +1460,14 @@ function observe(client, opts = {}) {
|
|
|
1160
1460
|
mode: "observe",
|
|
1161
1461
|
detail: `Unsupported ${provider} client shape.`
|
|
1162
1462
|
});
|
|
1163
|
-
return
|
|
1463
|
+
return createInactiveHandle(client, sessionId, agent, "unsupported_client", `Unsupported ${provider} client shape.`, now, opts.diagnostics, opts.stateDir);
|
|
1164
1464
|
}
|
|
1165
1465
|
if (isWrapped(client, patchTarget.target)) {
|
|
1166
1466
|
emitDiagnostic(opts.diagnostics, {
|
|
1167
1467
|
type: "double_wrap",
|
|
1168
1468
|
mode: "observe"
|
|
1169
1469
|
});
|
|
1170
|
-
return
|
|
1470
|
+
return createInactiveHandle(client, sessionId, agent, "double_wrap", void 0, now, opts.diagnostics, opts.stateDir);
|
|
1171
1471
|
}
|
|
1172
1472
|
const patchabilityError = getPatchabilityError(patchTarget.target, patchTarget.methodName);
|
|
1173
1473
|
if (patchabilityError) {
|
|
@@ -1176,19 +1476,171 @@ function observe(client, opts = {}) {
|
|
|
1176
1476
|
mode: "observe",
|
|
1177
1477
|
detail: patchabilityError
|
|
1178
1478
|
});
|
|
1179
|
-
return
|
|
1479
|
+
return createInactiveHandle(client, sessionId, agent, "patch_target_unwritable", patchabilityError, now, opts.diagnostics, opts.stateDir);
|
|
1180
1480
|
}
|
|
1181
1481
|
const captureLevel = normalizeCaptureLevel(opts.captureLevel);
|
|
1182
|
-
const
|
|
1482
|
+
const patchTargetName = `${provider}.${provider === "openai" ? "chat.completions.create" : "messages.create"}`;
|
|
1483
|
+
const runtimeState = {
|
|
1484
|
+
captures_seen: 0,
|
|
1485
|
+
dropped_overflow: 0,
|
|
1486
|
+
last_capture_at: null,
|
|
1487
|
+
last_flush_attempt_at: null,
|
|
1488
|
+
last_flush_success_at: null,
|
|
1489
|
+
last_flush_error_at: null,
|
|
1490
|
+
last_flush_error: null,
|
|
1491
|
+
queue_size: 0,
|
|
1492
|
+
consecutive_failures: 0,
|
|
1493
|
+
circuit_open_until: null,
|
|
1494
|
+
remote_disabled: false
|
|
1495
|
+
};
|
|
1496
|
+
let lastHealthStoreErrorAt = null;
|
|
1497
|
+
let lastHealthStoreError = null;
|
|
1498
|
+
const sessionsDir = resolveSessionsDir(
|
|
1499
|
+
resolveStateDir(opts.stateDir ? { stateDir: opts.stateDir } : void 0)
|
|
1500
|
+
);
|
|
1501
|
+
const buildCurrentSnapshot = () => ({
|
|
1502
|
+
schema_version: "1.0",
|
|
1503
|
+
session_id: sessionId,
|
|
1504
|
+
agent,
|
|
1505
|
+
provider,
|
|
1506
|
+
patch_target: patchTargetName,
|
|
1507
|
+
state: "active",
|
|
1508
|
+
activation: {
|
|
1509
|
+
active: true,
|
|
1510
|
+
reason_code: "active",
|
|
1511
|
+
activated_at: now
|
|
1512
|
+
},
|
|
1513
|
+
process: {
|
|
1514
|
+
pid: typeof process !== "undefined" ? process.pid : null,
|
|
1515
|
+
cwd: typeof process !== "undefined" ? process.cwd() : "",
|
|
1516
|
+
node_version: typeof process !== "undefined" ? process.versions?.node ?? "" : "",
|
|
1517
|
+
sdk_version: getSdkVersion()
|
|
1518
|
+
},
|
|
1519
|
+
runtime: { ...runtimeState },
|
|
1520
|
+
stopped_at: null,
|
|
1521
|
+
last_heartbeat_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1522
|
+
last_health_store_error_at: lastHealthStoreErrorAt,
|
|
1523
|
+
last_health_store_error: lastHealthStoreError
|
|
1524
|
+
});
|
|
1525
|
+
const persistHealth = (snapshot) => {
|
|
1526
|
+
const s = snapshot ?? buildCurrentSnapshot();
|
|
1527
|
+
try {
|
|
1528
|
+
writeHealthSnapshot(sessionsDir, sessionId, s);
|
|
1529
|
+
if (lastHealthStoreErrorAt !== null) {
|
|
1530
|
+
lastHealthStoreErrorAt = null;
|
|
1531
|
+
lastHealthStoreError = null;
|
|
1532
|
+
}
|
|
1533
|
+
} catch (err) {
|
|
1534
|
+
const detail = err instanceof Error ? err.message : "Failed to write health snapshot";
|
|
1535
|
+
lastHealthStoreErrorAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1536
|
+
lastHealthStoreError = detail;
|
|
1537
|
+
emitDiagnostic(opts.diagnostics, {
|
|
1538
|
+
type: "health_store_error",
|
|
1539
|
+
detail
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
let lastEventWriteAt = Date.now();
|
|
1544
|
+
let heartbeatTimer;
|
|
1545
|
+
const persistHealthEvent = () => {
|
|
1546
|
+
lastEventWriteAt = Date.now();
|
|
1547
|
+
persistHealth();
|
|
1548
|
+
};
|
|
1549
|
+
const startHeartbeat = () => {
|
|
1550
|
+
heartbeatTimer = setInterval(() => {
|
|
1551
|
+
const sinceLast = Date.now() - lastEventWriteAt;
|
|
1552
|
+
if (sinceLast >= IDLE_HEARTBEAT_MS) {
|
|
1553
|
+
persistHealth();
|
|
1554
|
+
}
|
|
1555
|
+
}, IDLE_HEARTBEAT_MS);
|
|
1556
|
+
unrefTimerHandle(heartbeatTimer);
|
|
1557
|
+
};
|
|
1558
|
+
const stopHeartbeat = () => {
|
|
1559
|
+
if (heartbeatTimer !== void 0) {
|
|
1560
|
+
clearInterval(heartbeatTimer);
|
|
1561
|
+
heartbeatTimer = void 0;
|
|
1562
|
+
}
|
|
1563
|
+
};
|
|
1564
|
+
const onBufferStateChange = (event) => {
|
|
1565
|
+
if (restored) return;
|
|
1566
|
+
switch (event.type) {
|
|
1567
|
+
case "flush_attempt":
|
|
1568
|
+
runtimeState.last_flush_attempt_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1569
|
+
persistHealthEvent();
|
|
1570
|
+
break;
|
|
1571
|
+
case "flush_success":
|
|
1572
|
+
runtimeState.last_flush_success_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1573
|
+
runtimeState.last_flush_error = null;
|
|
1574
|
+
runtimeState.consecutive_failures = 0;
|
|
1575
|
+
runtimeState.circuit_open_until = null;
|
|
1576
|
+
runtimeState.queue_size = buffer.size;
|
|
1577
|
+
emitDiagnostic(opts.diagnostics, {
|
|
1578
|
+
type: "flush_succeeded",
|
|
1579
|
+
batch_size: event.batch_size
|
|
1580
|
+
});
|
|
1581
|
+
persistHealthEvent();
|
|
1582
|
+
break;
|
|
1583
|
+
case "flush_error":
|
|
1584
|
+
runtimeState.last_flush_error_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1585
|
+
runtimeState.last_flush_error = event.error;
|
|
1586
|
+
runtimeState.consecutive_failures = buffer.consecutiveFailures;
|
|
1587
|
+
runtimeState.queue_size = buffer.size;
|
|
1588
|
+
persistHealthEvent();
|
|
1589
|
+
break;
|
|
1590
|
+
case "buffer_overflow":
|
|
1591
|
+
runtimeState.dropped_overflow = buffer.droppedOverflow;
|
|
1592
|
+
runtimeState.queue_size = buffer.size;
|
|
1593
|
+
persistHealthEvent();
|
|
1594
|
+
break;
|
|
1595
|
+
case "circuit_open":
|
|
1596
|
+
runtimeState.circuit_open_until = new Date(event.openUntil).toISOString();
|
|
1597
|
+
runtimeState.consecutive_failures = event.failures;
|
|
1598
|
+
persistHealthEvent();
|
|
1599
|
+
break;
|
|
1600
|
+
case "remote_disabled":
|
|
1601
|
+
runtimeState.remote_disabled = true;
|
|
1602
|
+
runtimeState.queue_size = 0;
|
|
1603
|
+
persistHealthEvent();
|
|
1604
|
+
break;
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1183
1607
|
const buffer = new CaptureBuffer({
|
|
1184
1608
|
apiKey,
|
|
1185
1609
|
endpoint: opts.endpoint,
|
|
1186
1610
|
maxBuffer: opts.maxBuffer,
|
|
1187
1611
|
flushMs: opts.flushMs,
|
|
1188
1612
|
timeoutMs: opts.timeoutMs,
|
|
1189
|
-
diagnostics: opts.diagnostics
|
|
1613
|
+
diagnostics: opts.diagnostics,
|
|
1614
|
+
onStateChange: onBufferStateChange
|
|
1190
1615
|
});
|
|
1191
1616
|
registerBeforeExit(buffer);
|
|
1617
|
+
let exitSnapshotWritten = false;
|
|
1618
|
+
const beforeExitHandler = () => {
|
|
1619
|
+
if (exitSnapshotWritten || restored) return;
|
|
1620
|
+
exitSnapshotWritten = true;
|
|
1621
|
+
try {
|
|
1622
|
+
stopHeartbeat();
|
|
1623
|
+
const stoppedSnapshot = buildCurrentSnapshot();
|
|
1624
|
+
stoppedSnapshot.state = "stopped";
|
|
1625
|
+
stoppedSnapshot.stopped_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1626
|
+
stoppedSnapshot.last_heartbeat_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1627
|
+
persistHealth(stoppedSnapshot);
|
|
1628
|
+
} catch {
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1631
|
+
if (typeof process !== "undefined" && typeof process.on === "function") {
|
|
1632
|
+
process.on("beforeExit", beforeExitHandler);
|
|
1633
|
+
}
|
|
1634
|
+
persistHealthEvent();
|
|
1635
|
+
safeRunJanitor(sessionsDir, sessionId);
|
|
1636
|
+
startHeartbeat();
|
|
1637
|
+
emitDiagnostic(opts.diagnostics, {
|
|
1638
|
+
type: "observe_activated",
|
|
1639
|
+
session_id: sessionId,
|
|
1640
|
+
provider,
|
|
1641
|
+
agent,
|
|
1642
|
+
patch_target: patchTargetName
|
|
1643
|
+
});
|
|
1192
1644
|
const wrappedCreate = function observeWrappedCreate(...args) {
|
|
1193
1645
|
const requestSnapshot = snapshotRequest(args[0], captureLevel);
|
|
1194
1646
|
const startedAt = Date.now();
|
|
@@ -1201,7 +1653,11 @@ function observe(client, opts = {}) {
|
|
|
1201
1653
|
provider,
|
|
1202
1654
|
requestSnapshot,
|
|
1203
1655
|
response,
|
|
1204
|
-
startedAt
|
|
1656
|
+
startedAt,
|
|
1657
|
+
sessionId,
|
|
1658
|
+
runtimeState,
|
|
1659
|
+
persistHealthEvent,
|
|
1660
|
+
diagnostics: opts.diagnostics
|
|
1205
1661
|
});
|
|
1206
1662
|
return response;
|
|
1207
1663
|
});
|
|
@@ -1211,11 +1667,18 @@ function observe(client, opts = {}) {
|
|
|
1211
1667
|
let restored = false;
|
|
1212
1668
|
return {
|
|
1213
1669
|
client,
|
|
1670
|
+
flush() {
|
|
1671
|
+
return buffer.flush();
|
|
1672
|
+
},
|
|
1214
1673
|
restore() {
|
|
1215
1674
|
if (restored) {
|
|
1216
1675
|
return;
|
|
1217
1676
|
}
|
|
1218
1677
|
restored = true;
|
|
1678
|
+
stopHeartbeat();
|
|
1679
|
+
if (typeof process !== "undefined" && typeof process.removeListener === "function") {
|
|
1680
|
+
process.removeListener("beforeExit", beforeExitHandler);
|
|
1681
|
+
}
|
|
1219
1682
|
if (patchTarget.target[patchTarget.methodName] === wrappedCreate) {
|
|
1220
1683
|
if (patchTarget.hadOwnMethod) {
|
|
1221
1684
|
patchTarget.target[patchTarget.methodName] = patchTarget.originalCreate;
|
|
@@ -1224,11 +1687,124 @@ function observe(client, opts = {}) {
|
|
|
1224
1687
|
}
|
|
1225
1688
|
}
|
|
1226
1689
|
clearWrapped(client, patchTarget.target);
|
|
1690
|
+
const stoppedSnapshot = buildCurrentSnapshot();
|
|
1691
|
+
stoppedSnapshot.state = "stopped";
|
|
1692
|
+
stoppedSnapshot.stopped_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1693
|
+
stoppedSnapshot.last_heartbeat_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1694
|
+
persistHealth(stoppedSnapshot);
|
|
1227
1695
|
buffer.close();
|
|
1696
|
+
},
|
|
1697
|
+
getHealth() {
|
|
1698
|
+
if (restored) {
|
|
1699
|
+
const snapshot = buildCurrentSnapshot();
|
|
1700
|
+
snapshot.state = "stopped";
|
|
1701
|
+
snapshot.stopped_at = snapshot.stopped_at ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1702
|
+
return snapshot;
|
|
1703
|
+
}
|
|
1704
|
+
runtimeState.queue_size = buffer.size;
|
|
1705
|
+
return buildCurrentSnapshot();
|
|
1228
1706
|
}
|
|
1229
1707
|
};
|
|
1708
|
+
} catch (err) {
|
|
1709
|
+
const detail = err instanceof Error ? err.message : "Unknown internal error";
|
|
1710
|
+
return createInactiveHandle(client, sessionId, agent, "internal_error", detail, now, opts.diagnostics, opts.stateDir);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
function createInactiveHandle(client, sessionId, agent, reasonCode, detail, activatedAt, diagnostics, stateDir) {
|
|
1714
|
+
const healthSnapshot = buildHealthSnapshot({
|
|
1715
|
+
sessionId,
|
|
1716
|
+
agent,
|
|
1717
|
+
provider: null,
|
|
1718
|
+
patchTarget: null,
|
|
1719
|
+
state: "inactive",
|
|
1720
|
+
reasonCode,
|
|
1721
|
+
active: false,
|
|
1722
|
+
activatedAt,
|
|
1723
|
+
detail
|
|
1724
|
+
});
|
|
1725
|
+
const sessionsDir = resolveSessionsDir(
|
|
1726
|
+
resolveStateDir(stateDir ? { stateDir } : void 0)
|
|
1727
|
+
);
|
|
1728
|
+
safeWriteHealth(sessionsDir, sessionId, healthSnapshot, diagnostics);
|
|
1729
|
+
emitDiagnostic(diagnostics, {
|
|
1730
|
+
type: "observe_inactive",
|
|
1731
|
+
reason_code: reasonCode,
|
|
1732
|
+
...detail ? { detail } : {}
|
|
1733
|
+
});
|
|
1734
|
+
return {
|
|
1735
|
+
client,
|
|
1736
|
+
flush() {
|
|
1737
|
+
return Promise.resolve();
|
|
1738
|
+
},
|
|
1739
|
+
restore() {
|
|
1740
|
+
},
|
|
1741
|
+
getHealth() {
|
|
1742
|
+
return { ...healthSnapshot };
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
function unrefTimerHandle(timer) {
|
|
1747
|
+
if (typeof timer === "object" && timer !== null && "unref" in timer) {
|
|
1748
|
+
try {
|
|
1749
|
+
timer.unref?.call(timer);
|
|
1750
|
+
} catch {
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
function buildHealthSnapshot(input) {
|
|
1755
|
+
const snapshot = {
|
|
1756
|
+
schema_version: "1.0",
|
|
1757
|
+
session_id: input.sessionId,
|
|
1758
|
+
agent: input.agent,
|
|
1759
|
+
provider: input.provider,
|
|
1760
|
+
patch_target: input.patchTarget,
|
|
1761
|
+
state: input.state,
|
|
1762
|
+
activation: {
|
|
1763
|
+
active: input.active,
|
|
1764
|
+
reason_code: input.reasonCode,
|
|
1765
|
+
activated_at: input.activatedAt,
|
|
1766
|
+
...input.detail ? { detail: input.detail } : {}
|
|
1767
|
+
},
|
|
1768
|
+
process: {
|
|
1769
|
+
pid: typeof process !== "undefined" ? process.pid : null,
|
|
1770
|
+
cwd: typeof process !== "undefined" ? process.cwd() : "",
|
|
1771
|
+
node_version: typeof process !== "undefined" ? process.versions?.node ?? "" : "",
|
|
1772
|
+
sdk_version: getSdkVersion()
|
|
1773
|
+
},
|
|
1774
|
+
runtime: {
|
|
1775
|
+
captures_seen: 0,
|
|
1776
|
+
dropped_overflow: 0,
|
|
1777
|
+
last_capture_at: null,
|
|
1778
|
+
last_flush_attempt_at: null,
|
|
1779
|
+
last_flush_success_at: null,
|
|
1780
|
+
last_flush_error_at: null,
|
|
1781
|
+
last_flush_error: null,
|
|
1782
|
+
queue_size: 0,
|
|
1783
|
+
consecutive_failures: 0,
|
|
1784
|
+
circuit_open_until: null,
|
|
1785
|
+
remote_disabled: false
|
|
1786
|
+
},
|
|
1787
|
+
stopped_at: null,
|
|
1788
|
+
last_heartbeat_at: input.activatedAt,
|
|
1789
|
+
last_health_store_error_at: null,
|
|
1790
|
+
last_health_store_error: null
|
|
1791
|
+
};
|
|
1792
|
+
return snapshot;
|
|
1793
|
+
}
|
|
1794
|
+
function safeWriteHealth(sessionsDir, sessionId, snapshot, diagnostics) {
|
|
1795
|
+
try {
|
|
1796
|
+
writeHealthSnapshot(sessionsDir, sessionId, snapshot);
|
|
1797
|
+
} catch (err) {
|
|
1798
|
+
emitDiagnostic(diagnostics, {
|
|
1799
|
+
type: "health_store_error",
|
|
1800
|
+
detail: err instanceof Error ? err.message : "Failed to write health snapshot"
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
function safeRunJanitor(sessionsDir, currentSessionId) {
|
|
1805
|
+
try {
|
|
1806
|
+
runJanitor(sessionsDir, currentSessionId);
|
|
1230
1807
|
} catch {
|
|
1231
|
-
return createNoopHandle(client);
|
|
1232
1808
|
}
|
|
1233
1809
|
}
|
|
1234
1810
|
function safelyCaptureResponse(input) {
|
|
@@ -1258,10 +1834,19 @@ function safelyCaptureResponse(input) {
|
|
|
1258
1834
|
usage: extractUsage(input.response, input.provider)
|
|
1259
1835
|
},
|
|
1260
1836
|
endedAt: Date.now(),
|
|
1261
|
-
startedAt: input.startedAt
|
|
1837
|
+
startedAt: input.startedAt,
|
|
1838
|
+
sessionId: input.sessionId
|
|
1262
1839
|
});
|
|
1263
1840
|
if (capture) {
|
|
1264
1841
|
input.buffer.push(capture);
|
|
1842
|
+
input.runtimeState.captures_seen += 1;
|
|
1843
|
+
input.runtimeState.last_capture_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1844
|
+
input.runtimeState.queue_size = input.buffer.size;
|
|
1845
|
+
emitDiagnostic(input.diagnostics, {
|
|
1846
|
+
type: "capture_seen",
|
|
1847
|
+
session_id: input.sessionId
|
|
1848
|
+
});
|
|
1849
|
+
input.persistHealthEvent();
|
|
1265
1850
|
}
|
|
1266
1851
|
} catch {
|
|
1267
1852
|
}
|
|
@@ -1278,10 +1863,19 @@ function safelyPushStreamCapture(input) {
|
|
|
1278
1863
|
requestSnapshot: input.requestSnapshot,
|
|
1279
1864
|
responseData: input.summary,
|
|
1280
1865
|
startedAt: input.startedAt,
|
|
1281
|
-
endedAt: Date.now()
|
|
1866
|
+
endedAt: Date.now(),
|
|
1867
|
+
sessionId: input.sessionId
|
|
1282
1868
|
});
|
|
1283
1869
|
if (capture) {
|
|
1284
1870
|
input.buffer.push(capture);
|
|
1871
|
+
input.runtimeState.captures_seen += 1;
|
|
1872
|
+
input.runtimeState.last_capture_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1873
|
+
input.runtimeState.queue_size = input.buffer.size;
|
|
1874
|
+
emitDiagnostic(input.diagnostics, {
|
|
1875
|
+
type: "capture_seen",
|
|
1876
|
+
session_id: input.sessionId
|
|
1877
|
+
});
|
|
1878
|
+
input.persistHealthEvent();
|
|
1285
1879
|
}
|
|
1286
1880
|
} catch {
|
|
1287
1881
|
}
|
|
@@ -1307,7 +1901,8 @@ function buildCapturedCall(input) {
|
|
|
1307
1901
|
...input.captureLevel === "full" && input.responseData.textBlocks && input.responseData.textBlocks.length > 0 ? { text_blocks: input.responseData.textBlocks } : {}
|
|
1308
1902
|
},
|
|
1309
1903
|
...input.responseData.usage ? { usage: input.responseData.usage } : {},
|
|
1310
|
-
latency_ms: Math.max(0, input.endedAt - input.startedAt)
|
|
1904
|
+
latency_ms: Math.max(0, input.endedAt - input.startedAt),
|
|
1905
|
+
...input.sessionId ? { sdk_session_id: input.sessionId } : {}
|
|
1311
1906
|
};
|
|
1312
1907
|
} catch {
|
|
1313
1908
|
return null;
|
|
@@ -1486,13 +2081,6 @@ function clearWrapped(client, target) {
|
|
|
1486
2081
|
} catch {
|
|
1487
2082
|
}
|
|
1488
2083
|
}
|
|
1489
|
-
function createNoopHandle(client) {
|
|
1490
|
-
return {
|
|
1491
|
-
client,
|
|
1492
|
-
restore() {
|
|
1493
|
-
}
|
|
1494
|
-
};
|
|
1495
|
-
}
|
|
1496
2084
|
function resolveApiKey(opts) {
|
|
1497
2085
|
return typeof opts.apiKey === "string" && opts.apiKey.length > 0 ? opts.apiKey : toNonEmptyString(process.env.REPLAYCI_API_KEY);
|
|
1498
2086
|
}
|
|
@@ -1536,8 +2124,8 @@ var import_contracts_core3 = require("@replayci/contracts-core");
|
|
|
1536
2124
|
|
|
1537
2125
|
// src/contracts.ts
|
|
1538
2126
|
var import_contracts_core2 = require("@replayci/contracts-core");
|
|
1539
|
-
var
|
|
1540
|
-
var
|
|
2127
|
+
var import_node_fs2 = require("fs");
|
|
2128
|
+
var import_node_path2 = require("path");
|
|
1541
2129
|
var CONTRACT_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
1542
2130
|
var MAX_REGEX_BYTES = 1024;
|
|
1543
2131
|
var NESTED_QUANTIFIER_RE = /\((?:[^()\\]|\\.)*[+*{](?:[^()\\]|\\.)*\)(?:[+*]|\{\d+(?:,\d*)?\})/;
|
|
@@ -1586,39 +2174,51 @@ function loadContractsFromPaths(inputs) {
|
|
|
1586
2174
|
const repoRoot = process.cwd();
|
|
1587
2175
|
const contractFiles = inputs.flatMap((input) => {
|
|
1588
2176
|
try {
|
|
1589
|
-
return collectContractFiles((0,
|
|
2177
|
+
return collectContractFiles((0, import_node_path2.resolve)(repoRoot, input));
|
|
1590
2178
|
} catch (error) {
|
|
1591
2179
|
throw new ReplayConfigurationError(
|
|
1592
2180
|
`Failed to load contracts from "${input}": ${formatErrorMessage(error)}`
|
|
1593
2181
|
);
|
|
1594
2182
|
}
|
|
1595
2183
|
});
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
2184
|
+
const loaded = [];
|
|
2185
|
+
for (const contractFile of contractFiles) {
|
|
2186
|
+
const contractPath = (0, import_node_path2.relative)(repoRoot, contractFile);
|
|
2187
|
+
try {
|
|
2188
|
+
const contract = (0, import_contracts_core2.loadContractSync)({
|
|
2189
|
+
repoRoot,
|
|
2190
|
+
contractPath
|
|
2191
|
+
});
|
|
2192
|
+
loaded.push(normalizeInlineContract({
|
|
2193
|
+
...contract,
|
|
2194
|
+
contract_file: contractFile
|
|
2195
|
+
}));
|
|
2196
|
+
} catch (error) {
|
|
2197
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2198
|
+
if (msg.startsWith("ContractMissingTool:")) {
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
throw new ReplayConfigurationError(
|
|
2202
|
+
`Failed to parse contract "${contractPath}": ${msg}`
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
return loaded;
|
|
1607
2207
|
}
|
|
1608
2208
|
function collectContractFiles(inputPath) {
|
|
1609
|
-
const stat = (0,
|
|
2209
|
+
const stat = (0, import_node_fs2.statSync)(inputPath);
|
|
1610
2210
|
if (stat.isFile()) {
|
|
1611
2211
|
return [inputPath];
|
|
1612
2212
|
}
|
|
1613
2213
|
if (!stat.isDirectory()) {
|
|
1614
2214
|
return [];
|
|
1615
2215
|
}
|
|
1616
|
-
return (0,
|
|
1617
|
-
const fullPath = (0,
|
|
2216
|
+
return (0, import_node_fs2.readdirSync)(inputPath, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name)).flatMap((entry) => {
|
|
2217
|
+
const fullPath = (0, import_node_path2.join)(inputPath, entry.name);
|
|
1618
2218
|
if (entry.isDirectory()) {
|
|
1619
2219
|
return collectContractFiles(fullPath);
|
|
1620
2220
|
}
|
|
1621
|
-
if (entry.isFile() && CONTRACT_EXTENSIONS.has((0,
|
|
2221
|
+
if (entry.isFile() && CONTRACT_EXTENSIONS.has((0, import_node_path2.extname)(entry.name).toLowerCase())) {
|
|
1622
2222
|
return [fullPath];
|
|
1623
2223
|
}
|
|
1624
2224
|
return [];
|