modelrig 0.0.1 → 0.1.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/LICENSE +201 -0
- package/README.md +115 -8
- package/dist/adapters/anthropic.d.ts +46 -0
- package/dist/adapters/anthropic.d.ts.map +1 -0
- package/dist/adapters/anthropic.js +202 -0
- package/dist/adapters/deepseek.d.ts +41 -0
- package/dist/adapters/deepseek.d.ts.map +1 -0
- package/dist/adapters/deepseek.js +163 -0
- package/dist/adapters/gemini.d.ts +44 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +222 -0
- package/dist/adapters/grok.d.ts +44 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +168 -0
- package/dist/adapters/openai-compat.d.ts +74 -0
- package/dist/adapters/openai-compat.d.ts.map +1 -0
- package/dist/adapters/openai-compat.js +187 -0
- package/dist/adapters/openai.d.ts +49 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +222 -0
- package/dist/bakeoff/engine.d.ts +52 -0
- package/dist/bakeoff/engine.d.ts.map +1 -0
- package/dist/bakeoff/engine.js +163 -0
- package/dist/bakeoff/replay.d.ts +52 -0
- package/dist/bakeoff/replay.d.ts.map +1 -0
- package/dist/bakeoff/replay.js +94 -0
- package/dist/bakeoff/samples.d.ts +62 -0
- package/dist/bakeoff/samples.d.ts.map +1 -0
- package/dist/bakeoff/samples.js +128 -0
- package/dist/bakeoff/stats.d.ts +13 -0
- package/dist/bakeoff/stats.d.ts.map +1 -0
- package/dist/bakeoff/stats.js +48 -0
- package/dist/bakeoff/types.d.ts +37 -0
- package/dist/bakeoff/types.d.ts.map +1 -0
- package/dist/bakeoff/types.js +10 -0
- package/dist/cli.d.ts +27 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +572 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +148 -0
- package/dist/feedback/coverage.d.ts +32 -0
- package/dist/feedback/coverage.d.ts.map +1 -0
- package/dist/feedback/coverage.js +95 -0
- package/dist/feedback/feedback.d.ts +35 -0
- package/dist/feedback/feedback.d.ts.map +1 -0
- package/dist/feedback/feedback.js +39 -0
- package/dist/index.d.ts +155 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +448 -0
- package/dist/init/finder.d.ts +79 -0
- package/dist/init/finder.d.ts.map +1 -0
- package/dist/init/finder.js +297 -0
- package/dist/init/run.d.ts +99 -0
- package/dist/init/run.d.ts.map +1 -0
- package/dist/init/run.js +281 -0
- package/dist/init/signatures.d.ts +39 -0
- package/dist/init/signatures.d.ts.map +1 -0
- package/dist/init/signatures.js +116 -0
- package/dist/matrix/aggregate.d.ts +89 -0
- package/dist/matrix/aggregate.d.ts.map +1 -0
- package/dist/matrix/aggregate.js +186 -0
- package/dist/query/grammar.d.ts +35 -0
- package/dist/query/grammar.d.ts.map +1 -0
- package/dist/query/grammar.js +279 -0
- package/dist/query/run.d.ts +8 -0
- package/dist/query/run.d.ts.map +1 -0
- package/dist/query/run.js +21 -0
- package/dist/registry/build.d.ts +126 -0
- package/dist/registry/build.d.ts.map +1 -0
- package/dist/registry/build.js +192 -0
- package/dist/registry/call-notes.d.ts +11 -0
- package/dist/registry/call-notes.d.ts.map +1 -0
- package/dist/registry/call-notes.js +72 -0
- package/dist/registry/claims.d.ts +63 -0
- package/dist/registry/claims.d.ts.map +1 -0
- package/dist/registry/claims.js +107 -0
- package/dist/registry/facts.d.ts +32 -0
- package/dist/registry/facts.d.ts.map +1 -0
- package/dist/registry/facts.js +120 -0
- package/dist/registry/fingerprint.d.ts +20 -0
- package/dist/registry/fingerprint.d.ts.map +1 -0
- package/dist/registry/fingerprint.js +150 -0
- package/dist/registry/parity.d.ts +26 -0
- package/dist/registry/parity.d.ts.map +1 -0
- package/dist/registry/parity.js +41 -0
- package/dist/registry/pricing.d.ts +19 -0
- package/dist/registry/pricing.d.ts.map +1 -0
- package/dist/registry/pricing.js +69 -0
- package/dist/registry/rows.d.ts +16 -0
- package/dist/registry/rows.d.ts.map +1 -0
- package/dist/registry/rows.js +57 -0
- package/dist/registry/seed.d.ts +43 -0
- package/dist/registry/seed.d.ts.map +1 -0
- package/dist/registry/seed.js +97 -0
- package/dist/registry/select.d.ts +68 -0
- package/dist/registry/select.d.ts.map +1 -0
- package/dist/registry/select.js +141 -0
- package/dist/registry/staleness.d.ts +18 -0
- package/dist/registry/staleness.d.ts.map +1 -0
- package/dist/registry/staleness.js +39 -0
- package/dist/routes/draft.d.ts +83 -0
- package/dist/routes/draft.d.ts.map +1 -0
- package/dist/routes/draft.js +135 -0
- package/dist/routes/loader.d.ts +26 -0
- package/dist/routes/loader.d.ts.map +1 -0
- package/dist/routes/loader.js +436 -0
- package/dist/routes/manifest.d.ts +24 -0
- package/dist/routes/manifest.d.ts.map +1 -0
- package/dist/routes/manifest.js +118 -0
- package/dist/routes/serveability.d.ts +52 -0
- package/dist/routes/serveability.d.ts.map +1 -0
- package/dist/routes/serveability.js +73 -0
- package/dist/routes/template.d.ts +18 -0
- package/dist/routes/template.d.ts.map +1 -0
- package/dist/routes/template.js +94 -0
- package/dist/run/cost.d.ts +26 -0
- package/dist/run/cost.d.ts.map +1 -0
- package/dist/run/cost.js +41 -0
- package/dist/run/envelope.d.ts +21 -0
- package/dist/run/envelope.d.ts.map +1 -0
- package/dist/run/envelope.js +45 -0
- package/dist/run/grounding.d.ts +56 -0
- package/dist/run/grounding.d.ts.map +1 -0
- package/dist/run/grounding.js +89 -0
- package/dist/run/repair.d.ts +36 -0
- package/dist/run/repair.d.ts.map +1 -0
- package/dist/run/repair.js +61 -0
- package/dist/run/resolve.d.ts +26 -0
- package/dist/run/resolve.d.ts.map +1 -0
- package/dist/run/resolve.js +81 -0
- package/dist/run/retry.d.ts +19 -0
- package/dist/run/retry.d.ts.map +1 -0
- package/dist/run/retry.js +50 -0
- package/dist/run/run.d.ts +41 -0
- package/dist/run/run.d.ts.map +1 -0
- package/dist/run/run.js +0 -0
- package/dist/run/validate.d.ts +15 -0
- package/dist/run/validate.d.ts.map +1 -0
- package/dist/run/validate.js +56 -0
- package/dist/savings/emit.d.ts +25 -0
- package/dist/savings/emit.d.ts.map +1 -0
- package/dist/savings/emit.js +75 -0
- package/dist/savings/odometer.d.ts +32 -0
- package/dist/savings/odometer.d.ts.map +1 -0
- package/dist/savings/odometer.js +70 -0
- package/dist/savings/swap-execute.d.ts +44 -0
- package/dist/savings/swap-execute.d.ts.map +1 -0
- package/dist/savings/swap-execute.js +87 -0
- package/dist/savings/swaps-client.d.ts +41 -0
- package/dist/savings/swaps-client.d.ts.map +1 -0
- package/dist/savings/swaps-client.js +81 -0
- package/dist/telemetry/capture.d.ts +28 -0
- package/dist/telemetry/capture.d.ts.map +1 -0
- package/dist/telemetry/capture.js +74 -0
- package/dist/telemetry/exporter.d.ts +62 -0
- package/dist/telemetry/exporter.d.ts.map +1 -0
- package/dist/telemetry/exporter.js +233 -0
- package/dist/telemetry/ingest-protocol.d.ts +161 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -0
- package/dist/telemetry/ingest-protocol.js +163 -0
- package/dist/telemetry/sinks.d.ts +54 -0
- package/dist/telemetry/sinks.d.ts.map +1 -0
- package/dist/telemetry/sinks.js +89 -0
- package/dist/telemetry/store.d.ts +38 -0
- package/dist/telemetry/store.d.ts.map +1 -0
- package/dist/telemetry/store.js +332 -0
- package/dist/telemetry/supabase-exporter.d.ts +64 -0
- package/dist/telemetry/supabase-exporter.d.ts.map +1 -0
- package/dist/telemetry/supabase-exporter.js +211 -0
- package/dist/tenancy/api-key-format.d.ts +67 -0
- package/dist/tenancy/api-key-format.d.ts.map +1 -0
- package/dist/tenancy/api-key-format.js +91 -0
- package/dist/tenancy/constants.d.ts +23 -0
- package/dist/tenancy/constants.d.ts.map +1 -0
- package/dist/tenancy/constants.js +25 -0
- package/dist/tenancy/scrub.d.ts +47 -0
- package/dist/tenancy/scrub.d.ts.map +1 -0
- package/dist/tenancy/scrub.js +85 -0
- package/dist/types/failures-teaching.d.ts +56 -0
- package/dist/types/failures-teaching.d.ts.map +1 -0
- package/dist/types/failures-teaching.js +177 -0
- package/dist/types/failures.d.ts +27 -0
- package/dist/types/failures.d.ts.map +1 -0
- package/dist/types/failures.js +41 -0
- package/dist/types/provider.d.ts +79 -0
- package/dist/types/provider.d.ts.map +1 -0
- package/dist/types/provider.js +20 -0
- package/dist/types/route.d.ts +127 -0
- package/dist/types/route.d.ts.map +1 -0
- package/dist/types/route.js +6 -0
- package/dist/types/telemetry.d.ts +89 -0
- package/dist/types/telemetry.d.ts.map +1 -0
- package/dist/types/telemetry.js +6 -0
- package/dist/watch/budget.d.ts +19 -0
- package/dist/watch/budget.d.ts.map +1 -0
- package/dist/watch/budget.js +35 -0
- package/dist/watch/evaluate.d.ts +66 -0
- package/dist/watch/evaluate.d.ts.map +1 -0
- package/dist/watch/evaluate.js +94 -0
- package/dist/watch/proposals.d.ts +73 -0
- package/dist/watch/proposals.d.ts.map +1 -0
- package/dist/watch/proposals.js +169 -0
- package/dist/watch/run-record.d.ts +64 -0
- package/dist/watch/run-record.d.ts.map +1 -0
- package/dist/watch/run-record.js +86 -0
- package/dist/watch/run-reported.d.ts +20 -0
- package/dist/watch/run-reported.d.ts.map +1 -0
- package/dist/watch/run-reported.js +53 -0
- package/dist/watch/run.d.ts +40 -0
- package/dist/watch/run.d.ts.map +1 -0
- package/dist/watch/run.js +44 -0
- package/dist/watch/state-store.d.ts +48 -0
- package/dist/watch/state-store.d.ts.map +1 -0
- package/dist/watch/state-store.js +86 -0
- package/dist/watch/state.d.ts +21 -0
- package/dist/watch/state.d.ts.map +1 -0
- package/dist/watch/state.js +93 -0
- package/package.json +50 -14
- package/index.js +0 -3
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* swap_events control-plane client — Phase 4 WS4. One row per approved,
|
|
3
|
+
* EXECUTED swap; the baseline snapshot is the odometer's fixed comparison
|
|
4
|
+
* point. Same PostgREST transport as proposals/exporter.
|
|
5
|
+
*/
|
|
6
|
+
export interface SwapBaseline {
|
|
7
|
+
/** Incumbent's effective $ per 1K conformant from the evidence bake-off. */
|
|
8
|
+
readonly effective_usd_per_1k_conformant: number;
|
|
9
|
+
readonly conform_rate: number;
|
|
10
|
+
readonly samples: number;
|
|
11
|
+
readonly bakeoff_id: string;
|
|
12
|
+
/** Pre-swap candidate order — lets a revert be emitted later. */
|
|
13
|
+
readonly incumbent_candidates?: ReadonlyArray<{
|
|
14
|
+
provider: string;
|
|
15
|
+
model: string;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
export interface SwapEventRecord {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly proposalId: string | null;
|
|
21
|
+
readonly route: string;
|
|
22
|
+
readonly fromVariant: string;
|
|
23
|
+
readonly toVariant: string;
|
|
24
|
+
readonly baseline: SwapBaseline;
|
|
25
|
+
readonly executedAt: string;
|
|
26
|
+
readonly approvedBy: string;
|
|
27
|
+
readonly revertedAt: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface SwapsClientConfig {
|
|
30
|
+
readonly supabaseUrl: string;
|
|
31
|
+
readonly serviceRoleKey: string;
|
|
32
|
+
readonly fetchFn?: typeof fetch;
|
|
33
|
+
}
|
|
34
|
+
export interface SwapsClient {
|
|
35
|
+
record(event: SwapEventRecord): Promise<void>;
|
|
36
|
+
list(): Promise<SwapEventRecord[]>;
|
|
37
|
+
get(id: string): Promise<SwapEventRecord | null>;
|
|
38
|
+
markReverted(id: string, revertedAt: string): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export declare function createSwapsClient(config: SwapsClientConfig): SwapsClient;
|
|
41
|
+
//# sourceMappingURL=swaps-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swaps-client.d.ts","sourceRoot":"","sources":["../../src/savings/swaps-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,+BAA+B,EAAE,MAAM,CAAC;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpF;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AA4BD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IACnC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACjD,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,WAAW,CAkExE"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* swap_events control-plane client — Phase 4 WS4. One row per approved,
|
|
4
|
+
* EXECUTED swap; the baseline snapshot is the odometer's fixed comparison
|
|
5
|
+
* point. Same PostgREST transport as proposals/exporter.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.createSwapsClient = createSwapsClient;
|
|
9
|
+
function toRecord(r) {
|
|
10
|
+
return {
|
|
11
|
+
id: r.id,
|
|
12
|
+
proposalId: r.proposal_id,
|
|
13
|
+
route: r.route,
|
|
14
|
+
fromVariant: r.from_variant,
|
|
15
|
+
toVariant: r.to_variant,
|
|
16
|
+
baseline: r.baseline,
|
|
17
|
+
executedAt: r.executed_at,
|
|
18
|
+
approvedBy: r.approved_by,
|
|
19
|
+
revertedAt: r.reverted_at,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function createSwapsClient(config) {
|
|
23
|
+
const fetchFn = config.fetchFn ?? fetch;
|
|
24
|
+
const base = config.supabaseUrl.replace(/\/$/, "");
|
|
25
|
+
async function request(path, init) {
|
|
26
|
+
const response = await fetchFn(`${base}/rest/v1/${path}`, {
|
|
27
|
+
...init,
|
|
28
|
+
headers: {
|
|
29
|
+
"content-type": "application/json",
|
|
30
|
+
apikey: config.serviceRoleKey,
|
|
31
|
+
authorization: `Bearer ${config.serviceRoleKey}`,
|
|
32
|
+
...(init.headers ?? {}),
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
throw new Error(`swap_events ${init.method ?? "GET"} ${path} → ${response.status} ` +
|
|
37
|
+
`${await response.text().catch(() => "")}`);
|
|
38
|
+
}
|
|
39
|
+
return response;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
async record(event) {
|
|
43
|
+
await request("swap_events", {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { prefer: "resolution=ignore-duplicates,return=minimal" },
|
|
46
|
+
body: JSON.stringify([
|
|
47
|
+
{
|
|
48
|
+
id: event.id,
|
|
49
|
+
proposal_id: event.proposalId,
|
|
50
|
+
route: event.route,
|
|
51
|
+
from_variant: event.fromVariant,
|
|
52
|
+
to_variant: event.toVariant,
|
|
53
|
+
baseline: event.baseline,
|
|
54
|
+
executed_at: event.executedAt,
|
|
55
|
+
approved_by: event.approvedBy,
|
|
56
|
+
reverted_at: event.revertedAt,
|
|
57
|
+
},
|
|
58
|
+
]),
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
async list() {
|
|
62
|
+
const response = await request("swap_events?select=*&order=executed_at.desc", {
|
|
63
|
+
method: "GET",
|
|
64
|
+
});
|
|
65
|
+
return (await response.json()).map(toRecord);
|
|
66
|
+
},
|
|
67
|
+
async get(id) {
|
|
68
|
+
const response = await request(`swap_events?select=*&id=eq.${encodeURIComponent(id)}`, {
|
|
69
|
+
method: "GET",
|
|
70
|
+
});
|
|
71
|
+
const rows = (await response.json());
|
|
72
|
+
return rows.length > 0 ? toRecord(rows[0]) : null;
|
|
73
|
+
},
|
|
74
|
+
async markReverted(id, revertedAt) {
|
|
75
|
+
await request(`swap_events?id=eq.${encodeURIComponent(id)}`, {
|
|
76
|
+
method: "PATCH",
|
|
77
|
+
body: JSON.stringify({ reverted_at: revertedAt }),
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-only replay capture — Phase 3 spec WS4. Opt-in per route
|
|
3
|
+
* (bundle `capture: true`). PRIVACY INVARIANT: captures NEVER leave the
|
|
4
|
+
* machine — this store is a SEPARATE handle the exporter cannot reach
|
|
5
|
+
* (ExportableTelemetryStore has no capture accessors; export-isolation is a
|
|
6
|
+
* C3 acceptance criterion with structural + runtime tests).
|
|
7
|
+
*/
|
|
8
|
+
export interface CaptureRow {
|
|
9
|
+
readonly inferenceId: string;
|
|
10
|
+
readonly ts: string;
|
|
11
|
+
readonly route: string;
|
|
12
|
+
readonly routeVersion: number;
|
|
13
|
+
readonly variablesJson: string;
|
|
14
|
+
readonly outputText: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CaptureStore {
|
|
17
|
+
record(row: CaptureRow): void;
|
|
18
|
+
/** Most recent N captures for a route (replay's read path). */
|
|
19
|
+
readLast(route: string, n: number): CaptureRow[];
|
|
20
|
+
close(): void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Separate better-sqlite3 handle over the same telemetry file. Deliberately
|
|
24
|
+
* NOT part of ExportableTelemetryStore — never add capture accessors there;
|
|
25
|
+
* see telemetry/export-isolation.test.ts.
|
|
26
|
+
*/
|
|
27
|
+
export declare function openCaptureStore(dbPath: string): CaptureStore;
|
|
28
|
+
//# sourceMappingURL=capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/telemetry/capture.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,+DAA+D;IAC/D,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE,CAAC;IACjD,KAAK,IAAI,IAAI,CAAC;CACf;AAyBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CA2C7D"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Local-only replay capture — Phase 3 spec WS4. Opt-in per route
|
|
4
|
+
* (bundle `capture: true`). PRIVACY INVARIANT: captures NEVER leave the
|
|
5
|
+
* machine — this store is a SEPARATE handle the exporter cannot reach
|
|
6
|
+
* (ExportableTelemetryStore has no capture accessors; export-isolation is a
|
|
7
|
+
* C3 acceptance criterion with structural + runtime tests).
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.openCaptureStore = openCaptureStore;
|
|
14
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
15
|
+
const node_fs_1 = require("node:fs");
|
|
16
|
+
const node_path_1 = require("node:path");
|
|
17
|
+
/** Idempotent — matches SCHEMA_SQL in store.ts (a fresh capture handle may
|
|
18
|
+
* open before the telemetry store has run the v3 migration). */
|
|
19
|
+
const CAPTURES_SQL = `
|
|
20
|
+
CREATE TABLE IF NOT EXISTS captures (
|
|
21
|
+
inference_id TEXT PRIMARY KEY,
|
|
22
|
+
ts TEXT NOT NULL,
|
|
23
|
+
route TEXT NOT NULL,
|
|
24
|
+
route_version INTEGER NOT NULL,
|
|
25
|
+
variables_json TEXT NOT NULL,
|
|
26
|
+
output_text TEXT NOT NULL
|
|
27
|
+
);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_captures_route_ts ON captures (route, ts);
|
|
29
|
+
`;
|
|
30
|
+
/**
|
|
31
|
+
* Separate better-sqlite3 handle over the same telemetry file. Deliberately
|
|
32
|
+
* NOT part of ExportableTelemetryStore — never add capture accessors there;
|
|
33
|
+
* see telemetry/export-isolation.test.ts.
|
|
34
|
+
*/
|
|
35
|
+
function openCaptureStore(dbPath) {
|
|
36
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(dbPath), { recursive: true });
|
|
37
|
+
const db = new better_sqlite3_1.default(dbPath);
|
|
38
|
+
db.pragma("journal_mode = WAL"); // matches store.ts; safe on an already-WAL file
|
|
39
|
+
db.exec(CAPTURES_SQL);
|
|
40
|
+
const insert = db.prepare(`
|
|
41
|
+
INSERT OR REPLACE INTO captures (
|
|
42
|
+
inference_id, ts, route, route_version, variables_json, output_text
|
|
43
|
+
) VALUES (@inference_id, @ts, @route, @route_version, @variables_json, @output_text)
|
|
44
|
+
`);
|
|
45
|
+
const selectLast = db.prepare(`
|
|
46
|
+
SELECT * FROM captures WHERE route = ? ORDER BY ts DESC, rowid DESC LIMIT ?
|
|
47
|
+
`);
|
|
48
|
+
return {
|
|
49
|
+
record(row) {
|
|
50
|
+
insert.run({
|
|
51
|
+
inference_id: row.inferenceId,
|
|
52
|
+
ts: row.ts,
|
|
53
|
+
route: row.route,
|
|
54
|
+
route_version: row.routeVersion,
|
|
55
|
+
variables_json: row.variablesJson,
|
|
56
|
+
output_text: row.outputText,
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
readLast(route, n) {
|
|
60
|
+
const rows = selectLast.all(route, n);
|
|
61
|
+
return rows.map((r) => ({
|
|
62
|
+
inferenceId: r.inference_id,
|
|
63
|
+
ts: r.ts,
|
|
64
|
+
route: r.route,
|
|
65
|
+
routeVersion: r.route_version,
|
|
66
|
+
variablesJson: r.variables_json,
|
|
67
|
+
outputText: r.output_text,
|
|
68
|
+
}));
|
|
69
|
+
},
|
|
70
|
+
close() {
|
|
71
|
+
db.close();
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async batch exporter: SQLite telemetry → wherever this rig's telemetry goes.
|
|
3
|
+
* Spec §5 (cloud sink) + §6 criteria 8–9. Fire-and-forget from the run loop:
|
|
4
|
+
* the inference path NEVER blocks on, or fails because of, this sink.
|
|
5
|
+
* At-least-once delivery with idempotent ids; unexported rows queue locally in
|
|
6
|
+
* SQLite (the inferences table itself, behind a rowid watermark) and flush on
|
|
7
|
+
* recovery; watermark bookkeeping per source instance.
|
|
8
|
+
*
|
|
9
|
+
* ── ONE LOOP, TWO TRANSPORTS (Phase 6 WS2) ───────────────────────────────
|
|
10
|
+
* Everything below is transport-agnostic. Batching, watermarks, backoff,
|
|
11
|
+
* once-per-process mirrors — all of it runs identically whether the batch
|
|
12
|
+
* leaves as a service-role PostgREST write (self-host) or as a `rig_sk_`-
|
|
13
|
+
* authenticated POST to api.modelrig.ai (hosted). That is not a tidiness
|
|
14
|
+
* preference: telemetry-ingestion-boundary.md §4 makes it a requirement, so
|
|
15
|
+
* that moving between self-host and hosted is a config change rather than a
|
|
16
|
+
* data-model change. `sinks.ts` holds the two transports; `ingest-protocol.ts`
|
|
17
|
+
* holds the one mapper both of them write rows through.
|
|
18
|
+
*/
|
|
19
|
+
import type { RigManifest } from "../routes/manifest";
|
|
20
|
+
import type { ServeabilityWarning } from "../routes/serveability";
|
|
21
|
+
import type { TelemetrySink } from "./sinks";
|
|
22
|
+
import type { ExportableTelemetryStore } from "./store";
|
|
23
|
+
export interface ExportedRouteMirror {
|
|
24
|
+
readonly route: string;
|
|
25
|
+
readonly version: number;
|
|
26
|
+
readonly candidates: ReadonlyArray<{
|
|
27
|
+
readonly provider: string;
|
|
28
|
+
readonly model: string;
|
|
29
|
+
}>;
|
|
30
|
+
/** Capture opt-in FLAG (Phase 3 WS4) — status only; capture content never
|
|
31
|
+
* has a cloud surface. */
|
|
32
|
+
readonly capture?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface ExporterConfig {
|
|
35
|
+
/** Where batches go. `sinks.ts` builds one of these from config. */
|
|
36
|
+
readonly sink: TelemetrySink;
|
|
37
|
+
readonly sourceId: string;
|
|
38
|
+
readonly batchSize?: number;
|
|
39
|
+
readonly intervalMs?: number;
|
|
40
|
+
/** Loaded route bundles, mirrored to the `routes` table for the console. */
|
|
41
|
+
readonly routes?: readonly ExportedRouteMirror[];
|
|
42
|
+
/** Rig manifest, mirrored to the `rig_manifests` table for the console
|
|
43
|
+
* (dimension NAMES only — tag values never leave the telemetry rows). */
|
|
44
|
+
readonly manifest?: RigManifest | null;
|
|
45
|
+
/** Inert-candidate warnings for THIS deployment (F1) — mirrored onto the
|
|
46
|
+
* manifest row so console /setup can say "2 candidates inert here, set
|
|
47
|
+
* XAI_API_KEY" instead of the fact living only in a boot log. Names only:
|
|
48
|
+
* route, provider, model, env var. */
|
|
49
|
+
readonly serveability?: readonly ServeabilityWarning[];
|
|
50
|
+
}
|
|
51
|
+
export interface Exporter {
|
|
52
|
+
/** Which lane this rig's telemetry is travelling in. Read by the boot
|
|
53
|
+
* banner — WS1's lesson was that a mode nobody can observe gets asked
|
|
54
|
+
* about instead of answered. */
|
|
55
|
+
readonly mode: "direct" | "api";
|
|
56
|
+
start(): void;
|
|
57
|
+
/** Flush pending rows now (used by tests and process-exit hooks). */
|
|
58
|
+
flush(): Promise<void>;
|
|
59
|
+
stop(): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
export declare function createTelemetryExporter(store: ExportableTelemetryStore, config: ExporterConfig): Exporter;
|
|
62
|
+
//# sourceMappingURL=exporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exporter.d.ts","sourceRoot":"","sources":["../../src/telemetry/exporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAIlE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAExD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1F;8BAC0B;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD;6EACyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACvC;;;0CAGsC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;CACxD;AAED,MAAM,WAAW,QAAQ;IACvB;;oCAEgC;IAChC,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC;IAChC,KAAK,IAAI,IAAI,CAAC;IACd,qEAAqE;IACrE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAoDD,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,wBAAwB,EAC/B,MAAM,EAAE,cAAc,GACrB,QAAQ,CAuKV"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Async batch exporter: SQLite telemetry → wherever this rig's telemetry goes.
|
|
4
|
+
* Spec §5 (cloud sink) + §6 criteria 8–9. Fire-and-forget from the run loop:
|
|
5
|
+
* the inference path NEVER blocks on, or fails because of, this sink.
|
|
6
|
+
* At-least-once delivery with idempotent ids; unexported rows queue locally in
|
|
7
|
+
* SQLite (the inferences table itself, behind a rowid watermark) and flush on
|
|
8
|
+
* recovery; watermark bookkeeping per source instance.
|
|
9
|
+
*
|
|
10
|
+
* ── ONE LOOP, TWO TRANSPORTS (Phase 6 WS2) ───────────────────────────────
|
|
11
|
+
* Everything below is transport-agnostic. Batching, watermarks, backoff,
|
|
12
|
+
* once-per-process mirrors — all of it runs identically whether the batch
|
|
13
|
+
* leaves as a service-role PostgREST write (self-host) or as a `rig_sk_`-
|
|
14
|
+
* authenticated POST to api.modelrig.ai (hosted). That is not a tidiness
|
|
15
|
+
* preference: telemetry-ingestion-boundary.md §4 makes it a requirement, so
|
|
16
|
+
* that moving between self-host and hosted is a config change rather than a
|
|
17
|
+
* data-model change. `sinks.ts` holds the two transports; `ingest-protocol.ts`
|
|
18
|
+
* holds the one mapper both of them write rows through.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.createTelemetryExporter = createTelemetryExporter;
|
|
22
|
+
const ingest_protocol_1 = require("./ingest-protocol");
|
|
23
|
+
const DEFAULT_BATCH_SIZE = 200;
|
|
24
|
+
const DEFAULT_INTERVAL_MS = 10000;
|
|
25
|
+
/** After a sink failure, skip this many intervals, doubling per consecutive
|
|
26
|
+
* failure (simple exponential backoff, capped). */
|
|
27
|
+
const BACKOFF_MAX_SKIPS = 32;
|
|
28
|
+
function toWireInference(row) {
|
|
29
|
+
return {
|
|
30
|
+
id: row.id,
|
|
31
|
+
ts: row.ts,
|
|
32
|
+
route: row.route,
|
|
33
|
+
route_version: row.routeVersion,
|
|
34
|
+
provider: row.provider,
|
|
35
|
+
model: row.model,
|
|
36
|
+
requested_tier: row.requestedTier,
|
|
37
|
+
served_tier: row.servedTier,
|
|
38
|
+
tokens_in: row.tokensIn,
|
|
39
|
+
tokens_out: row.tokensOut,
|
|
40
|
+
tokens_cached: row.tokensCached,
|
|
41
|
+
cost_estimate_usd: row.costEstimateUsd,
|
|
42
|
+
latency_ms: row.latencyMs,
|
|
43
|
+
ttfb_ms: row.ttfbMs,
|
|
44
|
+
failure_class: row.failureClass,
|
|
45
|
+
attempts: JSON.parse(row.attemptsJson),
|
|
46
|
+
tags: JSON.parse(row.tagsJson),
|
|
47
|
+
fingerprint_cell: row.fingerprintCell,
|
|
48
|
+
served_variant: row.servedVariant,
|
|
49
|
+
repaired_by: row.repairedBy,
|
|
50
|
+
pricing_missing: row.pricingMissing,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function toWireFeedback(row) {
|
|
54
|
+
return {
|
|
55
|
+
id: row.id,
|
|
56
|
+
ts: row.ts,
|
|
57
|
+
inference_id: row.inferenceId,
|
|
58
|
+
episode_key: row.episodeKey,
|
|
59
|
+
verdict: row.verdict,
|
|
60
|
+
score: row.score,
|
|
61
|
+
note: row.note,
|
|
62
|
+
source: row.source,
|
|
63
|
+
created_by: row.createdBy,
|
|
64
|
+
// Present only when the verdict names its own org. Omitted (not null) so
|
|
65
|
+
// that `stampBatch` can fall back to the exporter's tenant without having
|
|
66
|
+
// to distinguish "no org" from "explicitly no org".
|
|
67
|
+
...(row.orgId !== null ? { org_id: row.orgId } : {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function createTelemetryExporter(store, config) {
|
|
71
|
+
const batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
72
|
+
const intervalMs = config.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
73
|
+
const { sink, sourceId } = config;
|
|
74
|
+
let timer = null;
|
|
75
|
+
let flushing = false;
|
|
76
|
+
let skipsRemaining = 0;
|
|
77
|
+
let consecutiveFailures = 0;
|
|
78
|
+
let routesMirrored = false;
|
|
79
|
+
let manifestMirrored = false;
|
|
80
|
+
/**
|
|
81
|
+
* The once-per-process mirrors, folded into whichever batch goes out first.
|
|
82
|
+
*
|
|
83
|
+
* They ride along rather than travelling alone because in api mode a batch
|
|
84
|
+
* is an HTTP request, and three requests where one would do is three times
|
|
85
|
+
* the chance of a partial flush.
|
|
86
|
+
*/
|
|
87
|
+
function pendingHeader() {
|
|
88
|
+
const header = {};
|
|
89
|
+
if (!routesMirrored && config.routes !== undefined && config.routes.length > 0) {
|
|
90
|
+
header.routes = config.routes.map((r) => ({
|
|
91
|
+
route: r.route,
|
|
92
|
+
version: r.version,
|
|
93
|
+
candidates: r.candidates,
|
|
94
|
+
capture: r.capture ?? false,
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
if (!manifestMirrored && config.manifest !== undefined && config.manifest !== null) {
|
|
98
|
+
header.manifest = {
|
|
99
|
+
name: config.manifest.name,
|
|
100
|
+
dimensions: config.manifest.dimensions,
|
|
101
|
+
contribution: config.manifest.contribution,
|
|
102
|
+
// F1: names only — route, provider/model, and the env var to set.
|
|
103
|
+
serveability: (config.serveability ?? []).map((w) => ({
|
|
104
|
+
route: w.route,
|
|
105
|
+
inert: w.inert.map((c) => ({ provider: c.provider, model: c.model })),
|
|
106
|
+
serveable_count: w.serveableCount,
|
|
107
|
+
fix_hint: w.fixHint,
|
|
108
|
+
})),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return header;
|
|
112
|
+
}
|
|
113
|
+
/** Mark the mirrors delivered — called ONLY after a confirmed send, so a
|
|
114
|
+
* failed first batch re-sends them rather than silently losing them. */
|
|
115
|
+
function markHeaderDelivered(header) {
|
|
116
|
+
if (header.routes !== undefined)
|
|
117
|
+
routesMirrored = true;
|
|
118
|
+
if (header.manifest !== undefined)
|
|
119
|
+
manifestMirrored = true;
|
|
120
|
+
}
|
|
121
|
+
async function flushOnce() {
|
|
122
|
+
let header = pendingHeader();
|
|
123
|
+
// Inferences past the watermark, in batches. The watermark advances ONLY
|
|
124
|
+
// after a confirmed send (at-least-once; the sink's upsert makes the
|
|
125
|
+
// duplicate harmless).
|
|
126
|
+
for (;;) {
|
|
127
|
+
const watermark = store.getExportWatermark(sourceId);
|
|
128
|
+
const pending = store.readInferencesAfter(watermark, batchSize);
|
|
129
|
+
if (pending.length === 0)
|
|
130
|
+
break;
|
|
131
|
+
await sink.send({
|
|
132
|
+
v: ingest_protocol_1.INGEST_PROTOCOL_VERSION,
|
|
133
|
+
source: sourceId,
|
|
134
|
+
...header,
|
|
135
|
+
inferences: pending.map(({ row }) => toWireInference(row)),
|
|
136
|
+
watermarkAt: new Date().toISOString(),
|
|
137
|
+
});
|
|
138
|
+
markHeaderDelivered(header);
|
|
139
|
+
header = {};
|
|
140
|
+
store.setExportWatermark(sourceId, pending[pending.length - 1].rowid);
|
|
141
|
+
if (pending.length < batchSize)
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
// Feedback: same at-least-once watermark dance, behind its own export_state
|
|
145
|
+
// key so the two streams never block each other.
|
|
146
|
+
const feedbackSource = `${sourceId}:feedback`;
|
|
147
|
+
for (;;) {
|
|
148
|
+
const watermark = store.getExportWatermark(feedbackSource);
|
|
149
|
+
const pending = store.readFeedbackAfter(watermark, batchSize);
|
|
150
|
+
if (pending.length === 0)
|
|
151
|
+
break;
|
|
152
|
+
await sink.send({
|
|
153
|
+
v: ingest_protocol_1.INGEST_PROTOCOL_VERSION,
|
|
154
|
+
source: sourceId,
|
|
155
|
+
...header,
|
|
156
|
+
feedback: pending.map(({ row }) => toWireFeedback(row)),
|
|
157
|
+
});
|
|
158
|
+
markHeaderDelivered(header);
|
|
159
|
+
header = {};
|
|
160
|
+
store.setExportWatermark(feedbackSource, pending[pending.length - 1].rowid);
|
|
161
|
+
if (pending.length < batchSize)
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
// Envelopes: small table, full upsert each flush. This send also carries
|
|
165
|
+
// any header the two loops above never got the chance to — a rig that has
|
|
166
|
+
// routes but has not run yet must still appear in the console.
|
|
167
|
+
const envelopes = store.listEnvelopes();
|
|
168
|
+
if (envelopes.length > 0 || Object.keys(header).length > 0) {
|
|
169
|
+
await sink.send({
|
|
170
|
+
v: ingest_protocol_1.INGEST_PROTOCOL_VERSION,
|
|
171
|
+
source: sourceId,
|
|
172
|
+
...header,
|
|
173
|
+
...(envelopes.length > 0
|
|
174
|
+
? {
|
|
175
|
+
envelopes: envelopes.map((e) => ({
|
|
176
|
+
envelope_key: e.envelopeKey,
|
|
177
|
+
budget_usd: e.budgetUsd,
|
|
178
|
+
spent_usd: e.spentUsd,
|
|
179
|
+
status: e.status,
|
|
180
|
+
})),
|
|
181
|
+
}
|
|
182
|
+
: {}),
|
|
183
|
+
});
|
|
184
|
+
markHeaderDelivered(header);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function guardedFlush() {
|
|
188
|
+
if (flushing)
|
|
189
|
+
return;
|
|
190
|
+
flushing = true;
|
|
191
|
+
try {
|
|
192
|
+
await flushOnce();
|
|
193
|
+
consecutiveFailures = 0;
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
// Swallow-and-log, ALWAYS — sink failures must never reach the run loop.
|
|
197
|
+
consecutiveFailures += 1;
|
|
198
|
+
skipsRemaining = Math.min(2 ** consecutiveFailures, BACKOFF_MAX_SKIPS);
|
|
199
|
+
console.warn(`[modelrig exporter] ${sink.mode} sink flush failed (attempt ${consecutiveFailures}, ` +
|
|
200
|
+
`backing off ${skipsRemaining} intervals): ` +
|
|
201
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
flushing = false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
mode: sink.mode,
|
|
209
|
+
start() {
|
|
210
|
+
if (timer !== null)
|
|
211
|
+
return;
|
|
212
|
+
timer = setInterval(() => {
|
|
213
|
+
if (skipsRemaining > 0) {
|
|
214
|
+
skipsRemaining -= 1;
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
void guardedFlush();
|
|
218
|
+
}, intervalMs);
|
|
219
|
+
// Never keep the process alive for telemetry export.
|
|
220
|
+
timer.unref?.();
|
|
221
|
+
},
|
|
222
|
+
async flush() {
|
|
223
|
+
await guardedFlush();
|
|
224
|
+
},
|
|
225
|
+
async stop() {
|
|
226
|
+
if (timer !== null) {
|
|
227
|
+
clearInterval(timer);
|
|
228
|
+
timer = null;
|
|
229
|
+
}
|
|
230
|
+
await guardedFlush();
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|