@replayci/replay 0.1.4 → 0.1.5
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 +89 -28
- package/dist/index.d.cts +17 -2
- package/dist/index.d.ts +17 -2
- package/dist/index.js +79 -28
- package/package.json +2 -4
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -127,7 +137,7 @@ var CaptureBuffer = class {
|
|
|
127
137
|
}
|
|
128
138
|
flush() {
|
|
129
139
|
if (this.closed || this.remoteDisabled) {
|
|
130
|
-
return Promise.resolve();
|
|
140
|
+
return Promise.resolve({ captured: 0, sent: 0, active: true, errors: [] });
|
|
131
141
|
}
|
|
132
142
|
if (this.flushPromise) {
|
|
133
143
|
return this.flushPromise;
|
|
@@ -137,6 +147,7 @@ var CaptureBuffer = class {
|
|
|
137
147
|
type: "flush_error",
|
|
138
148
|
error: err instanceof Error ? err.message : String(err)
|
|
139
149
|
});
|
|
150
|
+
return { captured: 0, sent: 0, active: true, errors: [err instanceof Error ? err.message : String(err)] };
|
|
140
151
|
}).finally(() => {
|
|
141
152
|
if (this.flushPromise === flushPromise) {
|
|
142
153
|
this.flushPromise = void 0;
|
|
@@ -165,11 +176,11 @@ var CaptureBuffer = class {
|
|
|
165
176
|
}
|
|
166
177
|
async flushOnce() {
|
|
167
178
|
if (this.closed || this.remoteDisabled || this.queue.length === 0) {
|
|
168
|
-
return;
|
|
179
|
+
return { captured: 0, sent: 0, active: true, errors: [] };
|
|
169
180
|
}
|
|
170
181
|
const now = this.now();
|
|
171
182
|
if (this.circuitOpenUntil > now) {
|
|
172
|
-
return;
|
|
183
|
+
return { captured: 0, sent: 0, active: true, errors: [] };
|
|
173
184
|
}
|
|
174
185
|
if (this.circuitOpenUntil !== 0) {
|
|
175
186
|
this.circuitOpenUntil = 0;
|
|
@@ -177,8 +188,9 @@ var CaptureBuffer = class {
|
|
|
177
188
|
}
|
|
178
189
|
const batch = this.queue.splice(0, Math.min(this.queue.length, MAX_BATCH_SIZE));
|
|
179
190
|
if (batch.length === 0) {
|
|
180
|
-
return;
|
|
191
|
+
return { captured: 0, sent: 0, active: true, errors: [] };
|
|
181
192
|
}
|
|
193
|
+
const captured = batch.length;
|
|
182
194
|
this.lastFlushAttemptMs = this.now();
|
|
183
195
|
emitStateChange(this.onStateChange, { type: "flush_attempt" });
|
|
184
196
|
let payload = "";
|
|
@@ -186,7 +198,7 @@ var CaptureBuffer = class {
|
|
|
186
198
|
payload = JSON.stringify({ captures: batch });
|
|
187
199
|
} catch {
|
|
188
200
|
this.handleFailure("JSON serialization failed");
|
|
189
|
-
return;
|
|
201
|
+
return { captured, sent: 0, active: true, errors: ["JSON serialization failed"] };
|
|
190
202
|
}
|
|
191
203
|
const controller = new AbortController();
|
|
192
204
|
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
@@ -209,19 +221,23 @@ var CaptureBuffer = class {
|
|
|
209
221
|
this.circuitOpenUntil = Number.MAX_SAFE_INTEGER;
|
|
210
222
|
emitDiagnostics(this.diagnostics, { type: "remote_disabled" });
|
|
211
223
|
emitStateChange(this.onStateChange, { type: "remote_disabled" });
|
|
212
|
-
return;
|
|
224
|
+
return { captured, sent: 0, active: true, errors: ["remote_disabled"] };
|
|
213
225
|
}
|
|
214
226
|
if (!response.ok) {
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
const msg = `HTTP ${response.status}`;
|
|
228
|
+
this.handleFailure(msg);
|
|
229
|
+
return { captured, sent: 0, active: true, errors: [msg] };
|
|
217
230
|
}
|
|
218
231
|
this.failureCount = 0;
|
|
219
232
|
this.circuitOpenUntil = 0;
|
|
220
233
|
this.lastFlushSuccessMs = this.now();
|
|
221
234
|
this.lastFlushErrorMsg = null;
|
|
222
235
|
emitStateChange(this.onStateChange, { type: "flush_success", batch_size: batch.length });
|
|
236
|
+
return { captured, sent: captured, active: true, errors: [] };
|
|
223
237
|
} catch (err) {
|
|
224
|
-
|
|
238
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
239
|
+
this.handleFailure(msg);
|
|
240
|
+
return { captured, sent: 0, active: true, errors: [msg] };
|
|
225
241
|
} finally {
|
|
226
242
|
clearTimeout(timeout);
|
|
227
243
|
}
|
|
@@ -1436,6 +1452,32 @@ function ensureDir(dir) {
|
|
|
1436
1452
|
var REPLAY_WRAPPED = /* @__PURE__ */ Symbol.for("replayci.wrapped");
|
|
1437
1453
|
var DEFAULT_AGENT = "default";
|
|
1438
1454
|
var IDLE_HEARTBEAT_MS = 3e4;
|
|
1455
|
+
function defaultDiagnosticsHandler(event) {
|
|
1456
|
+
switch (event.type) {
|
|
1457
|
+
case "observe_inactive":
|
|
1458
|
+
if (event.reason_code === "missing_api_key") {
|
|
1459
|
+
console.warn(
|
|
1460
|
+
"[replayci] No API key provided. observe() is inactive \u2014 calls will not be captured. Set REPLAYCI_API_KEY or pass apiKey to observe()."
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
break;
|
|
1464
|
+
case "activation_warning":
|
|
1465
|
+
console.warn(`[replayci] ${event.message}`);
|
|
1466
|
+
break;
|
|
1467
|
+
case "flush_error":
|
|
1468
|
+
console.warn(`[replayci] flush failed: ${event.error}`);
|
|
1469
|
+
break;
|
|
1470
|
+
case "flush_empty":
|
|
1471
|
+
console.warn(
|
|
1472
|
+
"[replayci] flush(): No calls were captured. Ensure your LLM calls use the client returned by observe()."
|
|
1473
|
+
);
|
|
1474
|
+
break;
|
|
1475
|
+
case "circuit_open":
|
|
1476
|
+
break;
|
|
1477
|
+
default:
|
|
1478
|
+
break;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1439
1481
|
function observe(client, opts = {}) {
|
|
1440
1482
|
assertSupportedNodeRuntime();
|
|
1441
1483
|
const sessionId = generateSessionId();
|
|
@@ -1445,38 +1487,45 @@ function observe(client, opts = {}) {
|
|
|
1445
1487
|
if (isDisabled(opts)) {
|
|
1446
1488
|
return createInactiveHandle(client, sessionId, agent, "disabled", void 0, now, opts.diagnostics, opts.stateDir);
|
|
1447
1489
|
}
|
|
1490
|
+
const diagnosticsHandler = opts.diagnostics ?? defaultDiagnosticsHandler;
|
|
1448
1491
|
const apiKey = resolveApiKey(opts);
|
|
1492
|
+
if (apiKey && !/^rci_(live|test)_/.test(apiKey)) {
|
|
1493
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1494
|
+
type: "activation_warning",
|
|
1495
|
+
message: "API key format looks wrong (expected 'rci_live_...' or 'rci_test_...'). Verify your key at app.replayci.com/settings."
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1449
1498
|
if (!apiKey) {
|
|
1450
|
-
return createInactiveHandle(client, sessionId, agent, "missing_api_key", void 0, now,
|
|
1499
|
+
return createInactiveHandle(client, sessionId, agent, "missing_api_key", void 0, now, diagnosticsHandler, opts.stateDir);
|
|
1451
1500
|
}
|
|
1452
|
-
const provider = detectProviderSafely(client,
|
|
1501
|
+
const provider = detectProviderSafely(client, diagnosticsHandler);
|
|
1453
1502
|
if (!provider) {
|
|
1454
|
-
return createInactiveHandle(client, sessionId, agent, "unsupported_client", "Could not detect provider.", now,
|
|
1503
|
+
return createInactiveHandle(client, sessionId, agent, "unsupported_client", "Could not detect provider.", now, diagnosticsHandler, opts.stateDir);
|
|
1455
1504
|
}
|
|
1456
1505
|
const patchTarget = resolvePatchTarget(client, provider);
|
|
1457
1506
|
if (!patchTarget) {
|
|
1458
|
-
emitDiagnostic(
|
|
1507
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1459
1508
|
type: "unsupported_client",
|
|
1460
1509
|
mode: "observe",
|
|
1461
1510
|
detail: `Unsupported ${provider} client shape.`
|
|
1462
1511
|
});
|
|
1463
|
-
return createInactiveHandle(client, sessionId, agent, "unsupported_client", `Unsupported ${provider} client shape.`, now,
|
|
1512
|
+
return createInactiveHandle(client, sessionId, agent, "unsupported_client", `Unsupported ${provider} client shape.`, now, diagnosticsHandler, opts.stateDir);
|
|
1464
1513
|
}
|
|
1465
1514
|
if (isWrapped(client, patchTarget.target)) {
|
|
1466
|
-
emitDiagnostic(
|
|
1515
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1467
1516
|
type: "double_wrap",
|
|
1468
1517
|
mode: "observe"
|
|
1469
1518
|
});
|
|
1470
|
-
return createInactiveHandle(client, sessionId, agent, "double_wrap", void 0, now,
|
|
1519
|
+
return createInactiveHandle(client, sessionId, agent, "double_wrap", void 0, now, diagnosticsHandler, opts.stateDir);
|
|
1471
1520
|
}
|
|
1472
1521
|
const patchabilityError = getPatchabilityError(patchTarget.target, patchTarget.methodName);
|
|
1473
1522
|
if (patchabilityError) {
|
|
1474
|
-
emitDiagnostic(
|
|
1523
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1475
1524
|
type: "unsupported_client",
|
|
1476
1525
|
mode: "observe",
|
|
1477
1526
|
detail: patchabilityError
|
|
1478
1527
|
});
|
|
1479
|
-
return createInactiveHandle(client, sessionId, agent, "patch_target_unwritable", patchabilityError, now,
|
|
1528
|
+
return createInactiveHandle(client, sessionId, agent, "patch_target_unwritable", patchabilityError, now, diagnosticsHandler, opts.stateDir);
|
|
1480
1529
|
}
|
|
1481
1530
|
const captureLevel = normalizeCaptureLevel(opts.captureLevel);
|
|
1482
1531
|
const patchTargetName = `${provider}.${provider === "openai" ? "chat.completions.create" : "messages.create"}`;
|
|
@@ -1534,7 +1583,7 @@ function observe(client, opts = {}) {
|
|
|
1534
1583
|
const detail = err instanceof Error ? err.message : "Failed to write health snapshot";
|
|
1535
1584
|
lastHealthStoreErrorAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1536
1585
|
lastHealthStoreError = detail;
|
|
1537
|
-
emitDiagnostic(
|
|
1586
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1538
1587
|
type: "health_store_error",
|
|
1539
1588
|
detail
|
|
1540
1589
|
});
|
|
@@ -1574,7 +1623,7 @@ function observe(client, opts = {}) {
|
|
|
1574
1623
|
runtimeState.consecutive_failures = 0;
|
|
1575
1624
|
runtimeState.circuit_open_until = null;
|
|
1576
1625
|
runtimeState.queue_size = buffer.size;
|
|
1577
|
-
emitDiagnostic(
|
|
1626
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1578
1627
|
type: "flush_succeeded",
|
|
1579
1628
|
batch_size: event.batch_size
|
|
1580
1629
|
});
|
|
@@ -1610,7 +1659,7 @@ function observe(client, opts = {}) {
|
|
|
1610
1659
|
maxBuffer: opts.maxBuffer,
|
|
1611
1660
|
flushMs: opts.flushMs,
|
|
1612
1661
|
timeoutMs: opts.timeoutMs,
|
|
1613
|
-
diagnostics:
|
|
1662
|
+
diagnostics: diagnosticsHandler,
|
|
1614
1663
|
onStateChange: onBufferStateChange
|
|
1615
1664
|
});
|
|
1616
1665
|
registerBeforeExit(buffer);
|
|
@@ -1634,7 +1683,7 @@ function observe(client, opts = {}) {
|
|
|
1634
1683
|
persistHealthEvent();
|
|
1635
1684
|
safeRunJanitor(sessionsDir, sessionId);
|
|
1636
1685
|
startHeartbeat();
|
|
1637
|
-
emitDiagnostic(
|
|
1686
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1638
1687
|
type: "observe_activated",
|
|
1639
1688
|
session_id: sessionId,
|
|
1640
1689
|
provider,
|
|
@@ -1657,7 +1706,7 @@ function observe(client, opts = {}) {
|
|
|
1657
1706
|
sessionId,
|
|
1658
1707
|
runtimeState,
|
|
1659
1708
|
persistHealthEvent,
|
|
1660
|
-
diagnostics:
|
|
1709
|
+
diagnostics: diagnosticsHandler
|
|
1661
1710
|
});
|
|
1662
1711
|
return response;
|
|
1663
1712
|
});
|
|
@@ -1667,8 +1716,12 @@ function observe(client, opts = {}) {
|
|
|
1667
1716
|
let restored = false;
|
|
1668
1717
|
return {
|
|
1669
1718
|
client,
|
|
1670
|
-
flush() {
|
|
1671
|
-
|
|
1719
|
+
async flush() {
|
|
1720
|
+
const result = await buffer.flush();
|
|
1721
|
+
if (result.captured === 0 && result.errors.length === 0) {
|
|
1722
|
+
emitDiagnostic(diagnosticsHandler, { type: "flush_empty" });
|
|
1723
|
+
}
|
|
1724
|
+
return result;
|
|
1672
1725
|
},
|
|
1673
1726
|
restore() {
|
|
1674
1727
|
if (restored) {
|
|
@@ -1707,7 +1760,7 @@ function observe(client, opts = {}) {
|
|
|
1707
1760
|
};
|
|
1708
1761
|
} catch (err) {
|
|
1709
1762
|
const detail = err instanceof Error ? err.message : "Unknown internal error";
|
|
1710
|
-
return createInactiveHandle(client, sessionId, agent, "internal_error", detail, now, opts.diagnostics, opts.stateDir);
|
|
1763
|
+
return createInactiveHandle(client, sessionId, agent, "internal_error", detail, now, opts.diagnostics ?? defaultDiagnosticsHandler, opts.stateDir);
|
|
1711
1764
|
}
|
|
1712
1765
|
}
|
|
1713
1766
|
function createInactiveHandle(client, sessionId, agent, reasonCode, detail, activatedAt, diagnostics, stateDir) {
|
|
@@ -1734,7 +1787,7 @@ function createInactiveHandle(client, sessionId, agent, reasonCode, detail, acti
|
|
|
1734
1787
|
return {
|
|
1735
1788
|
client,
|
|
1736
1789
|
flush() {
|
|
1737
|
-
return Promise.resolve();
|
|
1790
|
+
return Promise.resolve({ captured: 0, sent: 0, active: false, errors: [] });
|
|
1738
1791
|
},
|
|
1739
1792
|
restore() {
|
|
1740
1793
|
},
|
|
@@ -2126,6 +2179,14 @@ var import_contracts_core3 = require("@replayci/contracts-core");
|
|
|
2126
2179
|
var import_contracts_core2 = require("@replayci/contracts-core");
|
|
2127
2180
|
var import_node_fs2 = require("fs");
|
|
2128
2181
|
var import_node_path2 = require("path");
|
|
2182
|
+
|
|
2183
|
+
// src/safeRegex.ts
|
|
2184
|
+
var import_re2 = __toESM(require("re2"), 1);
|
|
2185
|
+
function safeRegex(pattern) {
|
|
2186
|
+
return new import_re2.default(pattern);
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
// src/contracts.ts
|
|
2129
2190
|
var CONTRACT_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
2130
2191
|
var MAX_REGEX_BYTES = 1024;
|
|
2131
2192
|
var NESTED_QUANTIFIER_RE = /\((?:[^()\\]|\\.)*[+*{](?:[^()\\]|\\.)*\)(?:[+*]|\{\d+(?:,\d*)?\})/;
|
|
@@ -2319,7 +2380,7 @@ function validateSafeRegexes(contract) {
|
|
|
2319
2380
|
);
|
|
2320
2381
|
}
|
|
2321
2382
|
try {
|
|
2322
|
-
void
|
|
2383
|
+
void safeRegex(invariant.regex);
|
|
2323
2384
|
} catch (error) {
|
|
2324
2385
|
throw new ReplayConfigurationError(
|
|
2325
2386
|
`Invalid regex in ${contractLabel} (${group.label}, ${invariant.path}): ${formatErrorMessage(error)}`
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { Contract } from '@replayci/contracts-core';
|
|
2
2
|
|
|
3
|
+
interface FlushResult {
|
|
4
|
+
/** Number of captures in the buffer when flush was called */
|
|
5
|
+
captured: number;
|
|
6
|
+
/** Number of captures successfully sent to the server */
|
|
7
|
+
sent: number;
|
|
8
|
+
/** Whether the handle is active (false = no apiKey or disabled) */
|
|
9
|
+
active: boolean;
|
|
10
|
+
/** Error messages from send attempts */
|
|
11
|
+
errors: string[];
|
|
12
|
+
}
|
|
3
13
|
type ContractFailure = {
|
|
4
14
|
path: string;
|
|
5
15
|
operator: string;
|
|
@@ -93,6 +103,11 @@ type ObserveDiagnosticEvent = {
|
|
|
93
103
|
type: "unsupported_client";
|
|
94
104
|
mode: "observe";
|
|
95
105
|
detail: string;
|
|
106
|
+
} | {
|
|
107
|
+
type: "activation_warning";
|
|
108
|
+
message: string;
|
|
109
|
+
} | {
|
|
110
|
+
type: "flush_empty";
|
|
96
111
|
};
|
|
97
112
|
type ObserveOptions = {
|
|
98
113
|
agent?: string;
|
|
@@ -109,7 +124,7 @@ type ObserveOptions = {
|
|
|
109
124
|
};
|
|
110
125
|
type ObserveHandle<T> = {
|
|
111
126
|
client: T;
|
|
112
|
-
flush: () => Promise<
|
|
127
|
+
flush: () => Promise<FlushResult>;
|
|
113
128
|
restore: () => void;
|
|
114
129
|
getHealth: () => ObserveHealthSnapshot;
|
|
115
130
|
};
|
|
@@ -123,4 +138,4 @@ type ValidateOptions = {
|
|
|
123
138
|
declare function prepareContracts(input: string | string[] | Contract | Contract[]): Contract[];
|
|
124
139
|
declare function validate(response: unknown, opts?: ValidateOptions): ValidationResult;
|
|
125
140
|
|
|
126
|
-
export { type ContractFailure, type ObserveActivationReasonCode, type ObserveDiagnosticEvent, type ObserveHandle, type ObserveHealthSnapshot, type ObserveOptions, type ObserveSessionState, type ValidationResult, observe, prepareContracts, validate };
|
|
141
|
+
export { type ContractFailure, type FlushResult, type ObserveActivationReasonCode, type ObserveDiagnosticEvent, type ObserveHandle, type ObserveHealthSnapshot, type ObserveOptions, type ObserveSessionState, type ValidationResult, observe, prepareContracts, validate };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { Contract } from '@replayci/contracts-core';
|
|
2
2
|
|
|
3
|
+
interface FlushResult {
|
|
4
|
+
/** Number of captures in the buffer when flush was called */
|
|
5
|
+
captured: number;
|
|
6
|
+
/** Number of captures successfully sent to the server */
|
|
7
|
+
sent: number;
|
|
8
|
+
/** Whether the handle is active (false = no apiKey or disabled) */
|
|
9
|
+
active: boolean;
|
|
10
|
+
/** Error messages from send attempts */
|
|
11
|
+
errors: string[];
|
|
12
|
+
}
|
|
3
13
|
type ContractFailure = {
|
|
4
14
|
path: string;
|
|
5
15
|
operator: string;
|
|
@@ -93,6 +103,11 @@ type ObserveDiagnosticEvent = {
|
|
|
93
103
|
type: "unsupported_client";
|
|
94
104
|
mode: "observe";
|
|
95
105
|
detail: string;
|
|
106
|
+
} | {
|
|
107
|
+
type: "activation_warning";
|
|
108
|
+
message: string;
|
|
109
|
+
} | {
|
|
110
|
+
type: "flush_empty";
|
|
96
111
|
};
|
|
97
112
|
type ObserveOptions = {
|
|
98
113
|
agent?: string;
|
|
@@ -109,7 +124,7 @@ type ObserveOptions = {
|
|
|
109
124
|
};
|
|
110
125
|
type ObserveHandle<T> = {
|
|
111
126
|
client: T;
|
|
112
|
-
flush: () => Promise<
|
|
127
|
+
flush: () => Promise<FlushResult>;
|
|
113
128
|
restore: () => void;
|
|
114
129
|
getHealth: () => ObserveHealthSnapshot;
|
|
115
130
|
};
|
|
@@ -123,4 +138,4 @@ type ValidateOptions = {
|
|
|
123
138
|
declare function prepareContracts(input: string | string[] | Contract | Contract[]): Contract[];
|
|
124
139
|
declare function validate(response: unknown, opts?: ValidateOptions): ValidationResult;
|
|
125
140
|
|
|
126
|
-
export { type ContractFailure, type ObserveActivationReasonCode, type ObserveDiagnosticEvent, type ObserveHandle, type ObserveHealthSnapshot, type ObserveOptions, type ObserveSessionState, type ValidationResult, observe, prepareContracts, validate };
|
|
141
|
+
export { type ContractFailure, type FlushResult, type ObserveActivationReasonCode, type ObserveDiagnosticEvent, type ObserveHandle, type ObserveHealthSnapshot, type ObserveOptions, type ObserveSessionState, type ValidationResult, observe, prepareContracts, validate };
|
package/dist/index.js
CHANGED
|
@@ -99,7 +99,7 @@ var CaptureBuffer = class {
|
|
|
99
99
|
}
|
|
100
100
|
flush() {
|
|
101
101
|
if (this.closed || this.remoteDisabled) {
|
|
102
|
-
return Promise.resolve();
|
|
102
|
+
return Promise.resolve({ captured: 0, sent: 0, active: true, errors: [] });
|
|
103
103
|
}
|
|
104
104
|
if (this.flushPromise) {
|
|
105
105
|
return this.flushPromise;
|
|
@@ -109,6 +109,7 @@ var CaptureBuffer = class {
|
|
|
109
109
|
type: "flush_error",
|
|
110
110
|
error: err instanceof Error ? err.message : String(err)
|
|
111
111
|
});
|
|
112
|
+
return { captured: 0, sent: 0, active: true, errors: [err instanceof Error ? err.message : String(err)] };
|
|
112
113
|
}).finally(() => {
|
|
113
114
|
if (this.flushPromise === flushPromise) {
|
|
114
115
|
this.flushPromise = void 0;
|
|
@@ -137,11 +138,11 @@ var CaptureBuffer = class {
|
|
|
137
138
|
}
|
|
138
139
|
async flushOnce() {
|
|
139
140
|
if (this.closed || this.remoteDisabled || this.queue.length === 0) {
|
|
140
|
-
return;
|
|
141
|
+
return { captured: 0, sent: 0, active: true, errors: [] };
|
|
141
142
|
}
|
|
142
143
|
const now = this.now();
|
|
143
144
|
if (this.circuitOpenUntil > now) {
|
|
144
|
-
return;
|
|
145
|
+
return { captured: 0, sent: 0, active: true, errors: [] };
|
|
145
146
|
}
|
|
146
147
|
if (this.circuitOpenUntil !== 0) {
|
|
147
148
|
this.circuitOpenUntil = 0;
|
|
@@ -149,8 +150,9 @@ var CaptureBuffer = class {
|
|
|
149
150
|
}
|
|
150
151
|
const batch = this.queue.splice(0, Math.min(this.queue.length, MAX_BATCH_SIZE));
|
|
151
152
|
if (batch.length === 0) {
|
|
152
|
-
return;
|
|
153
|
+
return { captured: 0, sent: 0, active: true, errors: [] };
|
|
153
154
|
}
|
|
155
|
+
const captured = batch.length;
|
|
154
156
|
this.lastFlushAttemptMs = this.now();
|
|
155
157
|
emitStateChange(this.onStateChange, { type: "flush_attempt" });
|
|
156
158
|
let payload = "";
|
|
@@ -158,7 +160,7 @@ var CaptureBuffer = class {
|
|
|
158
160
|
payload = JSON.stringify({ captures: batch });
|
|
159
161
|
} catch {
|
|
160
162
|
this.handleFailure("JSON serialization failed");
|
|
161
|
-
return;
|
|
163
|
+
return { captured, sent: 0, active: true, errors: ["JSON serialization failed"] };
|
|
162
164
|
}
|
|
163
165
|
const controller = new AbortController();
|
|
164
166
|
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
@@ -181,19 +183,23 @@ var CaptureBuffer = class {
|
|
|
181
183
|
this.circuitOpenUntil = Number.MAX_SAFE_INTEGER;
|
|
182
184
|
emitDiagnostics(this.diagnostics, { type: "remote_disabled" });
|
|
183
185
|
emitStateChange(this.onStateChange, { type: "remote_disabled" });
|
|
184
|
-
return;
|
|
186
|
+
return { captured, sent: 0, active: true, errors: ["remote_disabled"] };
|
|
185
187
|
}
|
|
186
188
|
if (!response.ok) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
const msg = `HTTP ${response.status}`;
|
|
190
|
+
this.handleFailure(msg);
|
|
191
|
+
return { captured, sent: 0, active: true, errors: [msg] };
|
|
189
192
|
}
|
|
190
193
|
this.failureCount = 0;
|
|
191
194
|
this.circuitOpenUntil = 0;
|
|
192
195
|
this.lastFlushSuccessMs = this.now();
|
|
193
196
|
this.lastFlushErrorMsg = null;
|
|
194
197
|
emitStateChange(this.onStateChange, { type: "flush_success", batch_size: batch.length });
|
|
198
|
+
return { captured, sent: captured, active: true, errors: [] };
|
|
195
199
|
} catch (err) {
|
|
196
|
-
|
|
200
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
201
|
+
this.handleFailure(msg);
|
|
202
|
+
return { captured, sent: 0, active: true, errors: [msg] };
|
|
197
203
|
} finally {
|
|
198
204
|
clearTimeout(timeout);
|
|
199
205
|
}
|
|
@@ -1418,6 +1424,32 @@ function ensureDir(dir) {
|
|
|
1418
1424
|
var REPLAY_WRAPPED = /* @__PURE__ */ Symbol.for("replayci.wrapped");
|
|
1419
1425
|
var DEFAULT_AGENT = "default";
|
|
1420
1426
|
var IDLE_HEARTBEAT_MS = 3e4;
|
|
1427
|
+
function defaultDiagnosticsHandler(event) {
|
|
1428
|
+
switch (event.type) {
|
|
1429
|
+
case "observe_inactive":
|
|
1430
|
+
if (event.reason_code === "missing_api_key") {
|
|
1431
|
+
console.warn(
|
|
1432
|
+
"[replayci] No API key provided. observe() is inactive \u2014 calls will not be captured. Set REPLAYCI_API_KEY or pass apiKey to observe()."
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
break;
|
|
1436
|
+
case "activation_warning":
|
|
1437
|
+
console.warn(`[replayci] ${event.message}`);
|
|
1438
|
+
break;
|
|
1439
|
+
case "flush_error":
|
|
1440
|
+
console.warn(`[replayci] flush failed: ${event.error}`);
|
|
1441
|
+
break;
|
|
1442
|
+
case "flush_empty":
|
|
1443
|
+
console.warn(
|
|
1444
|
+
"[replayci] flush(): No calls were captured. Ensure your LLM calls use the client returned by observe()."
|
|
1445
|
+
);
|
|
1446
|
+
break;
|
|
1447
|
+
case "circuit_open":
|
|
1448
|
+
break;
|
|
1449
|
+
default:
|
|
1450
|
+
break;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1421
1453
|
function observe(client, opts = {}) {
|
|
1422
1454
|
assertSupportedNodeRuntime();
|
|
1423
1455
|
const sessionId = generateSessionId();
|
|
@@ -1427,38 +1459,45 @@ function observe(client, opts = {}) {
|
|
|
1427
1459
|
if (isDisabled(opts)) {
|
|
1428
1460
|
return createInactiveHandle(client, sessionId, agent, "disabled", void 0, now, opts.diagnostics, opts.stateDir);
|
|
1429
1461
|
}
|
|
1462
|
+
const diagnosticsHandler = opts.diagnostics ?? defaultDiagnosticsHandler;
|
|
1430
1463
|
const apiKey = resolveApiKey(opts);
|
|
1464
|
+
if (apiKey && !/^rci_(live|test)_/.test(apiKey)) {
|
|
1465
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1466
|
+
type: "activation_warning",
|
|
1467
|
+
message: "API key format looks wrong (expected 'rci_live_...' or 'rci_test_...'). Verify your key at app.replayci.com/settings."
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1431
1470
|
if (!apiKey) {
|
|
1432
|
-
return createInactiveHandle(client, sessionId, agent, "missing_api_key", void 0, now,
|
|
1471
|
+
return createInactiveHandle(client, sessionId, agent, "missing_api_key", void 0, now, diagnosticsHandler, opts.stateDir);
|
|
1433
1472
|
}
|
|
1434
|
-
const provider = detectProviderSafely(client,
|
|
1473
|
+
const provider = detectProviderSafely(client, diagnosticsHandler);
|
|
1435
1474
|
if (!provider) {
|
|
1436
|
-
return createInactiveHandle(client, sessionId, agent, "unsupported_client", "Could not detect provider.", now,
|
|
1475
|
+
return createInactiveHandle(client, sessionId, agent, "unsupported_client", "Could not detect provider.", now, diagnosticsHandler, opts.stateDir);
|
|
1437
1476
|
}
|
|
1438
1477
|
const patchTarget = resolvePatchTarget(client, provider);
|
|
1439
1478
|
if (!patchTarget) {
|
|
1440
|
-
emitDiagnostic(
|
|
1479
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1441
1480
|
type: "unsupported_client",
|
|
1442
1481
|
mode: "observe",
|
|
1443
1482
|
detail: `Unsupported ${provider} client shape.`
|
|
1444
1483
|
});
|
|
1445
|
-
return createInactiveHandle(client, sessionId, agent, "unsupported_client", `Unsupported ${provider} client shape.`, now,
|
|
1484
|
+
return createInactiveHandle(client, sessionId, agent, "unsupported_client", `Unsupported ${provider} client shape.`, now, diagnosticsHandler, opts.stateDir);
|
|
1446
1485
|
}
|
|
1447
1486
|
if (isWrapped(client, patchTarget.target)) {
|
|
1448
|
-
emitDiagnostic(
|
|
1487
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1449
1488
|
type: "double_wrap",
|
|
1450
1489
|
mode: "observe"
|
|
1451
1490
|
});
|
|
1452
|
-
return createInactiveHandle(client, sessionId, agent, "double_wrap", void 0, now,
|
|
1491
|
+
return createInactiveHandle(client, sessionId, agent, "double_wrap", void 0, now, diagnosticsHandler, opts.stateDir);
|
|
1453
1492
|
}
|
|
1454
1493
|
const patchabilityError = getPatchabilityError(patchTarget.target, patchTarget.methodName);
|
|
1455
1494
|
if (patchabilityError) {
|
|
1456
|
-
emitDiagnostic(
|
|
1495
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1457
1496
|
type: "unsupported_client",
|
|
1458
1497
|
mode: "observe",
|
|
1459
1498
|
detail: patchabilityError
|
|
1460
1499
|
});
|
|
1461
|
-
return createInactiveHandle(client, sessionId, agent, "patch_target_unwritable", patchabilityError, now,
|
|
1500
|
+
return createInactiveHandle(client, sessionId, agent, "patch_target_unwritable", patchabilityError, now, diagnosticsHandler, opts.stateDir);
|
|
1462
1501
|
}
|
|
1463
1502
|
const captureLevel = normalizeCaptureLevel(opts.captureLevel);
|
|
1464
1503
|
const patchTargetName = `${provider}.${provider === "openai" ? "chat.completions.create" : "messages.create"}`;
|
|
@@ -1516,7 +1555,7 @@ function observe(client, opts = {}) {
|
|
|
1516
1555
|
const detail = err instanceof Error ? err.message : "Failed to write health snapshot";
|
|
1517
1556
|
lastHealthStoreErrorAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1518
1557
|
lastHealthStoreError = detail;
|
|
1519
|
-
emitDiagnostic(
|
|
1558
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1520
1559
|
type: "health_store_error",
|
|
1521
1560
|
detail
|
|
1522
1561
|
});
|
|
@@ -1556,7 +1595,7 @@ function observe(client, opts = {}) {
|
|
|
1556
1595
|
runtimeState.consecutive_failures = 0;
|
|
1557
1596
|
runtimeState.circuit_open_until = null;
|
|
1558
1597
|
runtimeState.queue_size = buffer.size;
|
|
1559
|
-
emitDiagnostic(
|
|
1598
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1560
1599
|
type: "flush_succeeded",
|
|
1561
1600
|
batch_size: event.batch_size
|
|
1562
1601
|
});
|
|
@@ -1592,7 +1631,7 @@ function observe(client, opts = {}) {
|
|
|
1592
1631
|
maxBuffer: opts.maxBuffer,
|
|
1593
1632
|
flushMs: opts.flushMs,
|
|
1594
1633
|
timeoutMs: opts.timeoutMs,
|
|
1595
|
-
diagnostics:
|
|
1634
|
+
diagnostics: diagnosticsHandler,
|
|
1596
1635
|
onStateChange: onBufferStateChange
|
|
1597
1636
|
});
|
|
1598
1637
|
registerBeforeExit(buffer);
|
|
@@ -1616,7 +1655,7 @@ function observe(client, opts = {}) {
|
|
|
1616
1655
|
persistHealthEvent();
|
|
1617
1656
|
safeRunJanitor(sessionsDir, sessionId);
|
|
1618
1657
|
startHeartbeat();
|
|
1619
|
-
emitDiagnostic(
|
|
1658
|
+
emitDiagnostic(diagnosticsHandler, {
|
|
1620
1659
|
type: "observe_activated",
|
|
1621
1660
|
session_id: sessionId,
|
|
1622
1661
|
provider,
|
|
@@ -1639,7 +1678,7 @@ function observe(client, opts = {}) {
|
|
|
1639
1678
|
sessionId,
|
|
1640
1679
|
runtimeState,
|
|
1641
1680
|
persistHealthEvent,
|
|
1642
|
-
diagnostics:
|
|
1681
|
+
diagnostics: diagnosticsHandler
|
|
1643
1682
|
});
|
|
1644
1683
|
return response;
|
|
1645
1684
|
});
|
|
@@ -1649,8 +1688,12 @@ function observe(client, opts = {}) {
|
|
|
1649
1688
|
let restored = false;
|
|
1650
1689
|
return {
|
|
1651
1690
|
client,
|
|
1652
|
-
flush() {
|
|
1653
|
-
|
|
1691
|
+
async flush() {
|
|
1692
|
+
const result = await buffer.flush();
|
|
1693
|
+
if (result.captured === 0 && result.errors.length === 0) {
|
|
1694
|
+
emitDiagnostic(diagnosticsHandler, { type: "flush_empty" });
|
|
1695
|
+
}
|
|
1696
|
+
return result;
|
|
1654
1697
|
},
|
|
1655
1698
|
restore() {
|
|
1656
1699
|
if (restored) {
|
|
@@ -1689,7 +1732,7 @@ function observe(client, opts = {}) {
|
|
|
1689
1732
|
};
|
|
1690
1733
|
} catch (err) {
|
|
1691
1734
|
const detail = err instanceof Error ? err.message : "Unknown internal error";
|
|
1692
|
-
return createInactiveHandle(client, sessionId, agent, "internal_error", detail, now, opts.diagnostics, opts.stateDir);
|
|
1735
|
+
return createInactiveHandle(client, sessionId, agent, "internal_error", detail, now, opts.diagnostics ?? defaultDiagnosticsHandler, opts.stateDir);
|
|
1693
1736
|
}
|
|
1694
1737
|
}
|
|
1695
1738
|
function createInactiveHandle(client, sessionId, agent, reasonCode, detail, activatedAt, diagnostics, stateDir) {
|
|
@@ -1716,7 +1759,7 @@ function createInactiveHandle(client, sessionId, agent, reasonCode, detail, acti
|
|
|
1716
1759
|
return {
|
|
1717
1760
|
client,
|
|
1718
1761
|
flush() {
|
|
1719
|
-
return Promise.resolve();
|
|
1762
|
+
return Promise.resolve({ captured: 0, sent: 0, active: false, errors: [] });
|
|
1720
1763
|
},
|
|
1721
1764
|
restore() {
|
|
1722
1765
|
},
|
|
@@ -2124,6 +2167,14 @@ import {
|
|
|
2124
2167
|
relative,
|
|
2125
2168
|
resolve
|
|
2126
2169
|
} from "path";
|
|
2170
|
+
|
|
2171
|
+
// src/safeRegex.ts
|
|
2172
|
+
import RE2 from "re2";
|
|
2173
|
+
function safeRegex(pattern) {
|
|
2174
|
+
return new RE2(pattern);
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// src/contracts.ts
|
|
2127
2178
|
var CONTRACT_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
2128
2179
|
var MAX_REGEX_BYTES = 1024;
|
|
2129
2180
|
var NESTED_QUANTIFIER_RE = /\((?:[^()\\]|\\.)*[+*{](?:[^()\\]|\\.)*\)(?:[+*]|\{\d+(?:,\d*)?\})/;
|
|
@@ -2317,7 +2368,7 @@ function validateSafeRegexes(contract) {
|
|
|
2317
2368
|
);
|
|
2318
2369
|
}
|
|
2319
2370
|
try {
|
|
2320
|
-
void
|
|
2371
|
+
void safeRegex(invariant.regex);
|
|
2321
2372
|
} catch (error) {
|
|
2322
2373
|
throw new ReplayConfigurationError(
|
|
2323
2374
|
`Invalid regex in ${contractLabel} (${group.label}, ${invariant.path}): ${formatErrorMessage(error)}`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replayci/replay",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "ReplayCI SDK for deterministic tool-call validation and observation.",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "ReplayCI",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@replayci/contracts-core": "^0.1.0",
|
|
54
|
+
"re2": "^1.20.0",
|
|
54
55
|
"yaml": "^2.0.0"
|
|
55
56
|
},
|
|
56
57
|
"peerDependencies": {
|
|
@@ -65,9 +66,6 @@
|
|
|
65
66
|
"optional": true
|
|
66
67
|
}
|
|
67
68
|
},
|
|
68
|
-
"optionalDependencies": {
|
|
69
|
-
"re2": "^1.20.0"
|
|
70
|
-
},
|
|
71
69
|
"devDependencies": {
|
|
72
70
|
"tsup": "^8.5.0",
|
|
73
71
|
"typescript": "^5.9.3",
|