@cookielab.io/oopst 0.6.2 → 0.6.4
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.js +2490 -965
- package/dist/index.js.map +1 -1
- package/package.json +10 -8
package/dist/index.js
CHANGED
|
@@ -4,23 +4,23 @@ const require = createRequire(import.meta.url);
|
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
import { realpathSync } from "fs";
|
|
7
|
-
import
|
|
7
|
+
import process2 from "process";
|
|
8
8
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
9
9
|
import { NodeRuntime, NodeServices as NodeServices2 } from "@effect/platform-node";
|
|
10
|
-
import { Effect as
|
|
10
|
+
import { Effect as Effect27, Layer as Layer12, Option as Option4, Runtime } from "effect";
|
|
11
11
|
import { CliOutput, Command, Flag } from "effect/unstable/cli";
|
|
12
12
|
|
|
13
13
|
// src/config.ts
|
|
14
14
|
import { readFileSync } from "fs";
|
|
15
15
|
import { homedir as readHomedir, hostname as readHostname } from "os";
|
|
16
16
|
import { isAbsolute, join, normalize, resolve } from "path";
|
|
17
|
-
import
|
|
17
|
+
import { env as processEnv } from "process";
|
|
18
18
|
import { Config, ConfigProvider, Effect, FileSystem, Option, Schema } from "effect";
|
|
19
19
|
import { parse as parseYaml } from "yaml";
|
|
20
20
|
var DEFAULT_SERVER_URL = "http://localhost:15433";
|
|
21
21
|
var DEFAULT_LOOKBACK_DAYS = 2;
|
|
22
22
|
var USER_CONFIG_FILE = ".oopst.yml";
|
|
23
|
-
var runtimeEnv =
|
|
23
|
+
var runtimeEnv = processEnv;
|
|
24
24
|
var NonEmptyString = Schema.String.check(Schema.isNonEmpty());
|
|
25
25
|
var NonNegativeInteger = Schema.Number.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0));
|
|
26
26
|
var CliConfigSourceSchema = Schema.Struct({
|
|
@@ -261,31 +261,42 @@ function resolveSyncConfig(flags, env) {
|
|
|
261
261
|
|
|
262
262
|
// src/services/command-platform.ts
|
|
263
263
|
import { homedir as readHomedir2, hostname as readHostname2 } from "os";
|
|
264
|
-
import
|
|
264
|
+
import process from "process";
|
|
265
265
|
import { fileURLToPath } from "url";
|
|
266
266
|
import { NodeFileSystem, NodeServices } from "@effect/platform-node";
|
|
267
|
-
import {
|
|
267
|
+
import { Effect as Effect2, FileSystem as FileSystem2, Layer, Stream } from "effect";
|
|
268
268
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
];
|
|
269
|
+
|
|
270
|
+
// src/services/command-console-services.ts
|
|
271
|
+
import { Context } from "effect";
|
|
273
272
|
var CliConsole = class extends Context.Service()("@oopst/cli/CliConsole") {
|
|
274
273
|
};
|
|
275
274
|
var CliProcess = class extends Context.Service()("@oopst/cli/CliProcess") {
|
|
276
275
|
};
|
|
277
|
-
|
|
276
|
+
|
|
277
|
+
// src/services/command-host-services.ts
|
|
278
|
+
import { Context as Context2 } from "effect";
|
|
279
|
+
var CliHost = class extends Context2.Service()("@oopst/cli/CliHost") {
|
|
278
280
|
};
|
|
279
|
-
var CliCommandRunner = class extends
|
|
281
|
+
var CliCommandRunner = class extends Context2.Service()(
|
|
280
282
|
"@oopst/cli/CliCommandRunner"
|
|
281
283
|
) {
|
|
282
284
|
};
|
|
283
|
-
|
|
285
|
+
|
|
286
|
+
// src/services/command-metadata-services.ts
|
|
287
|
+
import { Context as Context3 } from "effect";
|
|
288
|
+
var CliPackageMetadata = class extends Context3.Service()(
|
|
284
289
|
"@oopst/cli/CliPackageMetadata"
|
|
285
290
|
) {
|
|
286
291
|
};
|
|
287
|
-
var CliConfigReader = class extends
|
|
292
|
+
var CliConfigReader = class extends Context3.Service()("@oopst/cli/CliConfigReader") {
|
|
288
293
|
};
|
|
294
|
+
|
|
295
|
+
// src/services/command-platform.ts
|
|
296
|
+
var PACKAGE_JSON_PATHS = [
|
|
297
|
+
fileURLToPath(new URL("../package.json", import.meta.url)),
|
|
298
|
+
fileURLToPath(new URL("../../package.json", import.meta.url))
|
|
299
|
+
];
|
|
289
300
|
function writeLine(output, message) {
|
|
290
301
|
output.write(`${message}
|
|
291
302
|
`);
|
|
@@ -340,9 +351,9 @@ var CliConsoleLive = Layer.effect(
|
|
|
340
351
|
Effect2.sync(
|
|
341
352
|
() => CliConsole.of(
|
|
342
353
|
makeCliConsole({
|
|
343
|
-
stdin:
|
|
344
|
-
stdout:
|
|
345
|
-
stderr:
|
|
354
|
+
stdin: process.stdin,
|
|
355
|
+
stdout: process.stdout,
|
|
356
|
+
stderr: process.stderr
|
|
346
357
|
})
|
|
347
358
|
)
|
|
348
359
|
)
|
|
@@ -353,7 +364,7 @@ var CliProcessLive = Layer.effect(
|
|
|
353
364
|
() => CliProcess.of({
|
|
354
365
|
execPath: Effect2.fn("CliProcess.execPath")(
|
|
355
366
|
() => Effect2.try({
|
|
356
|
-
try: () =>
|
|
367
|
+
try: () => process.execPath,
|
|
357
368
|
catch: (err) => err
|
|
358
369
|
})
|
|
359
370
|
)()
|
|
@@ -443,8 +454,8 @@ var CliCommandPlatformLive = Layer.mergeAll(
|
|
|
443
454
|
import { homedir } from "os";
|
|
444
455
|
import { join as join2 } from "path";
|
|
445
456
|
import { NodeFileSystem as NodeFileSystem2 } from "@effect/platform-node";
|
|
446
|
-
import { Context as
|
|
447
|
-
var ParserSources = class extends
|
|
457
|
+
import { Context as Context4, Effect as Effect3, FileSystem as FileSystem3, Layer as Layer2, Schema as Schema2 } from "effect";
|
|
458
|
+
var ParserSources = class extends Context4.Service()("@oopst/cli/ParserSources") {
|
|
448
459
|
};
|
|
449
460
|
var ParserSourceError = class extends Schema2.TaggedErrorClass()("ParserSourceError", {
|
|
450
461
|
cause: Schema2.optionalKey(Schema2.Defect()),
|
|
@@ -521,223 +532,106 @@ var ParserSourcesLive = Layer2.effect(
|
|
|
521
532
|
).pipe(Layer2.provide(NodeFileSystem2.layer));
|
|
522
533
|
|
|
523
534
|
// src/services/sync-program.ts
|
|
524
|
-
import { Context as
|
|
535
|
+
import { Context as Context8, Effect as Effect10, Layer as Layer5 } from "effect";
|
|
525
536
|
|
|
526
537
|
// src/sync/run.ts
|
|
527
|
-
import {
|
|
538
|
+
import { createHash } from "crypto";
|
|
528
539
|
|
|
529
|
-
// src/
|
|
530
|
-
import {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
message: Schema3.String,
|
|
534
|
-
cause: Schema3.optionalKey(Schema3.Defect())
|
|
540
|
+
// ../../packages/api/src/errors/auth-errors.ts
|
|
541
|
+
import { Schema as Schema3 } from "effect";
|
|
542
|
+
var ErrorFields = {
|
|
543
|
+
message: Schema3.String
|
|
535
544
|
};
|
|
536
|
-
var
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
) {
|
|
545
|
+
var UnauthorizedError = class extends Schema3.TaggedErrorClass()("UnauthorizedError", ErrorFields) {
|
|
546
|
+
};
|
|
547
|
+
var ForbiddenError = class extends Schema3.TaggedErrorClass()("ForbiddenError", ErrorFields) {
|
|
540
548
|
};
|
|
541
|
-
function toIso(hourUtc) {
|
|
542
|
-
return hourUtc instanceof Date ? hourUtc.toISOString() : hourUtc;
|
|
543
|
-
}
|
|
544
|
-
function key(b) {
|
|
545
|
-
return `${b.harness} ${b.model} ${toIso(b.hourUtc)}`;
|
|
546
|
-
}
|
|
547
|
-
function finalize(acc) {
|
|
548
|
-
return {
|
|
549
|
-
harness: acc.harness,
|
|
550
|
-
model: acc.model,
|
|
551
|
-
hourUtc: acc.hourUtc,
|
|
552
|
-
inputTokens: acc.inputTokens,
|
|
553
|
-
outputTokens: acc.outputTokens,
|
|
554
|
-
cacheReadTokens: acc.cacheReadTokens,
|
|
555
|
-
cacheCreateTokens: acc.cacheCreateTokens,
|
|
556
|
-
sessionCount: acc.sessionIds.size
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
var SESSION_KEY_SALT_PATTERN = /^[a-f0-9]{64}$/u;
|
|
560
|
-
function createSessionKey(harness, localSessionId, sessionKeySalt) {
|
|
561
|
-
if (!SESSION_KEY_SALT_PATTERN.test(sessionKeySalt)) {
|
|
562
|
-
throw new Error("invalid session key salt");
|
|
563
|
-
}
|
|
564
|
-
return createHmac("sha256", Buffer.from(sessionKeySalt, "hex")).update("oopst/session-key/v1").update("\0").update(harness).update("\0").update(localSessionId).digest("hex");
|
|
565
|
-
}
|
|
566
|
-
function sessionMapKey(harness, sessionKey) {
|
|
567
|
-
return `${harness} ${sessionKey}`;
|
|
568
|
-
}
|
|
569
|
-
function finalizeSession(acc) {
|
|
570
|
-
return {
|
|
571
|
-
harness: acc.harness,
|
|
572
|
-
sessionKey: acc.sessionKey,
|
|
573
|
-
startedAt: acc.startedAt.toISOString(),
|
|
574
|
-
lastSeenAt: acc.lastSeenAt.toISOString()
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
function addSessionAccumulator(sessionAccumulators, bucket, localSessionId, sessionKeySalt) {
|
|
578
|
-
const sessionKey = createSessionKey(bucket.harness, localSessionId, sessionKeySalt);
|
|
579
|
-
const sessionAccumulatorKey = sessionMapKey(bucket.harness, sessionKey);
|
|
580
|
-
const existingSession = sessionAccumulators.get(sessionAccumulatorKey);
|
|
581
|
-
if (existingSession) {
|
|
582
|
-
if (bucket.sessionStartedAt.getTime() < existingSession.startedAt.getTime()) {
|
|
583
|
-
existingSession.startedAt = bucket.sessionStartedAt;
|
|
584
|
-
}
|
|
585
|
-
if (bucket.sessionLastSeenAt.getTime() > existingSession.lastSeenAt.getTime()) {
|
|
586
|
-
existingSession.lastSeenAt = bucket.sessionLastSeenAt;
|
|
587
|
-
}
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
sessionAccumulators.set(sessionAccumulatorKey, {
|
|
591
|
-
harness: bucket.harness,
|
|
592
|
-
sessionKey,
|
|
593
|
-
startedAt: bucket.sessionStartedAt,
|
|
594
|
-
lastSeenAt: bucket.sessionLastSeenAt
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
function createUsageAggregationState() {
|
|
598
|
-
return {
|
|
599
|
-
accumulators: /* @__PURE__ */ new Map(),
|
|
600
|
-
sessionAccumulators: /* @__PURE__ */ new Map()
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
function addBucketToAggregationState(state, bucket, sessionKeySalt) {
|
|
604
|
-
const k = key(bucket);
|
|
605
|
-
let acc = state.accumulators.get(k);
|
|
606
|
-
if (!acc) {
|
|
607
|
-
acc = {
|
|
608
|
-
harness: bucket.harness,
|
|
609
|
-
model: bucket.model,
|
|
610
|
-
hourUtc: toIso(bucket.hourUtc),
|
|
611
|
-
inputTokens: 0,
|
|
612
|
-
outputTokens: 0,
|
|
613
|
-
cacheReadTokens: 0,
|
|
614
|
-
cacheCreateTokens: 0,
|
|
615
|
-
sessionIds: /* @__PURE__ */ new Set()
|
|
616
|
-
};
|
|
617
|
-
state.accumulators.set(k, acc);
|
|
618
|
-
}
|
|
619
|
-
acc.inputTokens += bucket.inputTokens;
|
|
620
|
-
acc.outputTokens += bucket.outputTokens;
|
|
621
|
-
acc.cacheReadTokens += bucket.cacheReadTokens;
|
|
622
|
-
acc.cacheCreateTokens += bucket.cacheCreateTokens;
|
|
623
|
-
for (const id of bucket.sessionIds) {
|
|
624
|
-
acc.sessionIds.add(id);
|
|
625
|
-
addSessionAccumulator(state.sessionAccumulators, bucket, id, sessionKeySalt);
|
|
626
|
-
}
|
|
627
|
-
return state;
|
|
628
|
-
}
|
|
629
|
-
function addBucketToAggregationStateEffect(state, bucket, sessionKeySalt) {
|
|
630
|
-
return Effect4.try({
|
|
631
|
-
try: () => addBucketToAggregationState(state, bucket, sessionKeySalt),
|
|
632
|
-
catch: toUsageAggregationError
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
function finalizeUsageBatches(state, batchSize) {
|
|
636
|
-
const finalized = [];
|
|
637
|
-
for (const acc of state.accumulators.values()) {
|
|
638
|
-
finalized.push(finalize(acc));
|
|
639
|
-
}
|
|
640
|
-
const finalizedSessions = [];
|
|
641
|
-
for (const acc of state.sessionAccumulators.values()) {
|
|
642
|
-
finalizedSessions.push(finalizeSession(acc));
|
|
643
|
-
}
|
|
644
|
-
const batches = [];
|
|
645
|
-
const maxLength = Math.max(finalized.length, finalizedSessions.length);
|
|
646
|
-
for (let i = 0; i < maxLength; i += batchSize) {
|
|
647
|
-
batches.push({
|
|
648
|
-
buckets: finalized.slice(i, i + batchSize),
|
|
649
|
-
sessions: finalizedSessions.slice(i, i + batchSize)
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
return batches;
|
|
653
|
-
}
|
|
654
|
-
function toUsageAggregationError(cause) {
|
|
655
|
-
return new UsageAggregationError({
|
|
656
|
-
message: `usage aggregation failed: ${errorMessage(cause)}`,
|
|
657
|
-
cause
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
function errorMessage(cause) {
|
|
661
|
-
return cause instanceof Error ? cause.message : "unknown aggregation error";
|
|
662
|
-
}
|
|
663
|
-
function aggregateUsageStream(source, options) {
|
|
664
|
-
return Stream2.fromIterableEffect(
|
|
665
|
-
source.pipe(
|
|
666
|
-
Stream2.runFoldEffect(
|
|
667
|
-
() => createUsageAggregationState(),
|
|
668
|
-
(state, bucket) => addBucketToAggregationStateEffect(state, bucket, options.sessionKeySalt)
|
|
669
|
-
),
|
|
670
|
-
Effect4.map((state) => finalizeUsageBatches(state, options.batchSize))
|
|
671
|
-
)
|
|
672
|
-
);
|
|
673
|
-
}
|
|
674
549
|
|
|
675
|
-
// ../../packages/api/src/errors.ts
|
|
550
|
+
// ../../packages/api/src/errors/config-errors.ts
|
|
676
551
|
import { Schema as Schema4 } from "effect";
|
|
677
|
-
var ErrorFields = {
|
|
678
|
-
message: Schema4.String
|
|
679
|
-
};
|
|
680
552
|
var InfrastructureErrorFields = {
|
|
681
|
-
|
|
553
|
+
message: Schema4.String,
|
|
682
554
|
cause: Schema4.optionalKey(Schema4.Defect())
|
|
683
555
|
};
|
|
684
|
-
var
|
|
556
|
+
var AppConfigError = class extends Schema4.TaggedErrorClass()("AppConfigError", InfrastructureErrorFields) {
|
|
557
|
+
};
|
|
558
|
+
var PricingRefreshError = class extends Schema4.TaggedErrorClass()(
|
|
559
|
+
"PricingRefreshError",
|
|
560
|
+
InfrastructureErrorFields
|
|
561
|
+
) {
|
|
685
562
|
};
|
|
686
|
-
|
|
563
|
+
|
|
564
|
+
// ../../packages/api/src/errors/infrastructure-errors.ts
|
|
565
|
+
import { Schema as Schema5 } from "effect";
|
|
566
|
+
var InfrastructureErrorFields2 = {
|
|
567
|
+
message: Schema5.String,
|
|
568
|
+
cause: Schema5.optionalKey(Schema5.Defect())
|
|
569
|
+
};
|
|
570
|
+
var ExternalHttpError = class extends Schema5.TaggedErrorClass()(
|
|
571
|
+
"ExternalHttpError",
|
|
572
|
+
InfrastructureErrorFields2
|
|
573
|
+
) {
|
|
687
574
|
};
|
|
688
|
-
var
|
|
575
|
+
var DatabaseError = class extends Schema5.TaggedErrorClass()("DatabaseError", InfrastructureErrorFields2) {
|
|
689
576
|
};
|
|
690
|
-
|
|
577
|
+
|
|
578
|
+
// ../../packages/api/src/errors/request-errors.ts
|
|
579
|
+
import { Schema as Schema6 } from "effect";
|
|
580
|
+
var ErrorFields2 = {
|
|
581
|
+
message: Schema6.String
|
|
691
582
|
};
|
|
692
|
-
var ValidationError = class extends
|
|
583
|
+
var ValidationError = class extends Schema6.TaggedErrorClass()("ValidationError", ErrorFields2) {
|
|
693
584
|
};
|
|
694
|
-
var RateLimitedError = class extends
|
|
585
|
+
var RateLimitedError = class extends Schema6.TaggedErrorClass()("RateLimitedError", ErrorFields2) {
|
|
695
586
|
};
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
587
|
+
|
|
588
|
+
// ../../packages/api/src/errors/resource-errors.ts
|
|
589
|
+
import { Schema as Schema7 } from "effect";
|
|
590
|
+
var ErrorFields3 = {
|
|
591
|
+
message: Schema7.String
|
|
700
592
|
};
|
|
701
|
-
var
|
|
702
|
-
"DatabaseError",
|
|
703
|
-
InfrastructureErrorFields
|
|
704
|
-
) {
|
|
593
|
+
var NotFoundError = class extends Schema7.TaggedErrorClass()("NotFoundError", ErrorFields3) {
|
|
705
594
|
};
|
|
706
|
-
var
|
|
707
|
-
"AppConfigError",
|
|
708
|
-
InfrastructureErrorFields
|
|
709
|
-
) {
|
|
595
|
+
var ConflictError = class extends Schema7.TaggedErrorClass()("ConflictError", ErrorFields3) {
|
|
710
596
|
};
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
597
|
+
|
|
598
|
+
// ../../packages/api/src/errors/source-errors.ts
|
|
599
|
+
import { Schema as Schema8 } from "effect";
|
|
600
|
+
var ErrorFields4 = {
|
|
601
|
+
message: Schema8.String
|
|
715
602
|
};
|
|
716
|
-
var
|
|
603
|
+
var InfrastructureErrorFields3 = {
|
|
604
|
+
...ErrorFields4,
|
|
605
|
+
cause: Schema8.optionalKey(Schema8.Defect())
|
|
606
|
+
};
|
|
607
|
+
var ParserSourceError2 = class extends Schema8.TaggedErrorClass()(
|
|
717
608
|
"ParserSourceError",
|
|
718
|
-
|
|
609
|
+
InfrastructureErrorFields3
|
|
719
610
|
) {
|
|
720
611
|
};
|
|
721
|
-
var SyncQuotaExceededError = class extends
|
|
612
|
+
var SyncQuotaExceededError = class extends Schema8.TaggedErrorClass()(
|
|
722
613
|
"SyncQuotaExceededError",
|
|
723
|
-
|
|
614
|
+
ErrorFields4
|
|
724
615
|
) {
|
|
725
616
|
};
|
|
726
|
-
|
|
617
|
+
|
|
618
|
+
// ../../packages/api/src/errors/sync-errors.ts
|
|
619
|
+
import { Schema as Schema9 } from "effect";
|
|
620
|
+
var SyncTimestampRejectedError = class extends Schema9.TaggedErrorClass()(
|
|
727
621
|
"SyncTimestampRejectedError",
|
|
728
|
-
|
|
622
|
+
{ message: Schema9.String }
|
|
729
623
|
) {
|
|
730
624
|
};
|
|
731
625
|
|
|
732
626
|
// ../../packages/api/src/rpc/auth.ts
|
|
733
|
-
import { Context as
|
|
627
|
+
import { Context as Context5, Schema as Schema12 } from "effect";
|
|
734
628
|
import { Rpc, RpcGroup } from "effect/unstable/rpc";
|
|
735
629
|
|
|
736
630
|
// ../../packages/api/src/schemas/auth.ts
|
|
737
|
-
import { Schema as
|
|
631
|
+
import { Schema as Schema10, SchemaGetter } from "effect";
|
|
738
632
|
var MAX_DISPLAY_NAME_LENGTH = 80;
|
|
739
|
-
var displayNameInput =
|
|
740
|
-
|
|
633
|
+
var displayNameInput = Schema10.String.check(Schema10.isMaxLength(MAX_DISPLAY_NAME_LENGTH)).pipe(
|
|
634
|
+
Schema10.decodeTo(Schema10.NullOr(Schema10.String), {
|
|
741
635
|
decode: SchemaGetter.transform((value) => {
|
|
742
636
|
const trimmed = value.trim();
|
|
743
637
|
return trimmed.length === 0 ? null : trimmed;
|
|
@@ -745,12 +639,12 @@ var displayNameInput = Schema5.String.check(Schema5.isMaxLength(MAX_DISPLAY_NAME
|
|
|
745
639
|
encode: SchemaGetter.transform((value) => value ?? "")
|
|
746
640
|
})
|
|
747
641
|
);
|
|
748
|
-
var updateProfileInput =
|
|
642
|
+
var updateProfileInput = Schema10.Struct({
|
|
749
643
|
displayName: displayNameInput
|
|
750
644
|
});
|
|
751
645
|
|
|
752
646
|
// ../../packages/api/src/schemas/common.ts
|
|
753
|
-
import { Schema as
|
|
647
|
+
import { Schema as Schema11 } from "effect";
|
|
754
648
|
var ISO_UTC_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
|
|
755
649
|
var CIVIL_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
|
|
756
650
|
var HEX64_PATTERN = /^[0-9a-f]{64}$/u;
|
|
@@ -795,49 +689,49 @@ var isApiUrl = (value) => {
|
|
|
795
689
|
return false;
|
|
796
690
|
}
|
|
797
691
|
};
|
|
798
|
-
var nonEmptyTrimmedString =
|
|
799
|
-
|
|
800
|
-
|
|
692
|
+
var nonEmptyTrimmedString = Schema11.String.check(
|
|
693
|
+
Schema11.isTrimmed(),
|
|
694
|
+
Schema11.isNonEmpty({ message: "Expected a non-empty string" })
|
|
801
695
|
);
|
|
802
|
-
var safeInteger =
|
|
803
|
-
|
|
804
|
-
|
|
696
|
+
var safeInteger = Schema11.Number.check(
|
|
697
|
+
Schema11.isInt(),
|
|
698
|
+
Schema11.isBetween({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER })
|
|
805
699
|
);
|
|
806
|
-
var nonNegativeSafeInteger = safeInteger.check(
|
|
807
|
-
var isoUtcString =
|
|
808
|
-
|
|
700
|
+
var nonNegativeSafeInteger = safeInteger.check(Schema11.isGreaterThanOrEqualTo(0));
|
|
701
|
+
var isoUtcString = Schema11.String.check(
|
|
702
|
+
Schema11.makeFilter(isExactUtcInstant, { expected: "an ISO-8601 UTC timestamp with millisecond precision" })
|
|
809
703
|
);
|
|
810
|
-
var civilDate =
|
|
811
|
-
|
|
704
|
+
var civilDate = Schema11.String.check(
|
|
705
|
+
Schema11.makeFilter(isCivilDate, { expected: "a valid calendar date formatted as YYYY-MM-DD" })
|
|
812
706
|
);
|
|
813
|
-
var ianaTimezone =
|
|
814
|
-
|
|
707
|
+
var ianaTimezone = Schema11.String.check(
|
|
708
|
+
Schema11.makeFilter(isIanaTimezone, { expected: "an IANA timezone identifier" })
|
|
815
709
|
);
|
|
816
|
-
var uuidV7 =
|
|
817
|
-
var generalUuid =
|
|
818
|
-
var hex64 =
|
|
819
|
-
|
|
710
|
+
var uuidV7 = Schema11.String.check(Schema11.isUUID(UUID_V7_VERSION)).pipe(Schema11.brand("UuidV7"));
|
|
711
|
+
var generalUuid = Schema11.String.check(Schema11.isUUID()).pipe(Schema11.brand("GeneralUuid"));
|
|
712
|
+
var hex64 = Schema11.String.check(
|
|
713
|
+
Schema11.isPattern(HEX64_PATTERN, { expected: "a 64-character lowercase hex string" })
|
|
820
714
|
);
|
|
821
|
-
var apiUrl =
|
|
715
|
+
var apiUrl = Schema11.String.check(Schema11.makeFilter(isApiUrl, { expected: "an HTTP(S) URL" }));
|
|
822
716
|
|
|
823
717
|
// ../../packages/api/src/rpc/auth.ts
|
|
824
|
-
var AuthUserResponse =
|
|
825
|
-
user:
|
|
826
|
-
id:
|
|
827
|
-
email:
|
|
828
|
-
displayName:
|
|
718
|
+
var AuthUserResponse = Schema12.Struct({
|
|
719
|
+
user: Schema12.Struct({
|
|
720
|
+
id: Schema12.String,
|
|
721
|
+
email: Schema12.String,
|
|
722
|
+
displayName: Schema12.NullOr(Schema12.String),
|
|
829
723
|
createdAt: isoUtcString
|
|
830
724
|
})
|
|
831
725
|
});
|
|
832
|
-
var LogoutResponse =
|
|
833
|
-
ok:
|
|
726
|
+
var LogoutResponse = Schema12.Struct({
|
|
727
|
+
ok: Schema12.Boolean
|
|
834
728
|
});
|
|
835
|
-
var WebRpcContext = class extends
|
|
729
|
+
var WebRpcContext = class extends Context5.Service()("@oopst/api/WebRpcContext") {
|
|
836
730
|
};
|
|
837
731
|
var AuthRpcs = RpcGroup.make(
|
|
838
732
|
Rpc.make("auth.logout", {
|
|
839
733
|
success: LogoutResponse,
|
|
840
|
-
error:
|
|
734
|
+
error: Schema12.Union([DatabaseError, UnauthorizedError])
|
|
841
735
|
}),
|
|
842
736
|
Rpc.make("auth.me", {
|
|
843
737
|
success: AuthUserResponse,
|
|
@@ -846,42 +740,42 @@ var AuthRpcs = RpcGroup.make(
|
|
|
846
740
|
Rpc.make("auth.updateProfile", {
|
|
847
741
|
payload: updateProfileInput,
|
|
848
742
|
success: AuthUserResponse,
|
|
849
|
-
error:
|
|
743
|
+
error: Schema12.Union([DatabaseError, UnauthorizedError])
|
|
850
744
|
})
|
|
851
745
|
);
|
|
852
746
|
|
|
853
747
|
// ../../packages/api/src/rpc/devices.ts
|
|
854
|
-
import { Schema as
|
|
748
|
+
import { Schema as Schema14 } from "effect";
|
|
855
749
|
import { Rpc as Rpc2, RpcGroup as RpcGroup2 } from "effect/unstable/rpc";
|
|
856
750
|
|
|
857
751
|
// ../../packages/api/src/schemas/devices.ts
|
|
858
|
-
import { Schema as
|
|
752
|
+
import { Schema as Schema13 } from "effect";
|
|
859
753
|
var MIN_DEVICE_NAME_LENGTH = 1;
|
|
860
754
|
var MAX_DEVICE_NAME_LENGTH = 80;
|
|
861
755
|
var UUID_V7_VERSION2 = 7;
|
|
862
|
-
var deviceNameSchema =
|
|
863
|
-
|
|
864
|
-
|
|
756
|
+
var deviceNameSchema = Schema13.String.check(
|
|
757
|
+
Schema13.isMinLength(MIN_DEVICE_NAME_LENGTH),
|
|
758
|
+
Schema13.isMaxLength(MAX_DEVICE_NAME_LENGTH)
|
|
865
759
|
);
|
|
866
|
-
var editableDeviceNameSchema =
|
|
867
|
-
|
|
868
|
-
|
|
760
|
+
var editableDeviceNameSchema = Schema13.Trim.check(
|
|
761
|
+
Schema13.isMinLength(MIN_DEVICE_NAME_LENGTH),
|
|
762
|
+
Schema13.isMaxLength(MAX_DEVICE_NAME_LENGTH)
|
|
869
763
|
);
|
|
870
|
-
var deviceIdSchema =
|
|
871
|
-
var renameDeviceInput =
|
|
764
|
+
var deviceIdSchema = Schema13.String.check(Schema13.isUUID(UUID_V7_VERSION2));
|
|
765
|
+
var renameDeviceInput = Schema13.Struct({
|
|
872
766
|
id: deviceIdSchema,
|
|
873
767
|
name: editableDeviceNameSchema
|
|
874
768
|
});
|
|
875
|
-
var deleteDeviceInput =
|
|
769
|
+
var deleteDeviceInput = Schema13.Struct({
|
|
876
770
|
id: deviceIdSchema
|
|
877
771
|
});
|
|
878
|
-
var renameDeviceOutput =
|
|
772
|
+
var renameDeviceOutput = Schema13.Struct({
|
|
879
773
|
id: deviceIdSchema,
|
|
880
|
-
name:
|
|
774
|
+
name: Schema13.String,
|
|
881
775
|
lastSeen: isoUtcString
|
|
882
776
|
});
|
|
883
|
-
var deleteDeviceOutput =
|
|
884
|
-
ok:
|
|
777
|
+
var deleteDeviceOutput = Schema13.Struct({
|
|
778
|
+
ok: Schema13.Boolean
|
|
885
779
|
});
|
|
886
780
|
|
|
887
781
|
// ../../packages/api/src/rpc/devices.ts
|
|
@@ -889,27 +783,27 @@ var DevicesRpcs = RpcGroup2.make(
|
|
|
889
783
|
Rpc2.make("devices.rename", {
|
|
890
784
|
payload: renameDeviceInput,
|
|
891
785
|
success: renameDeviceOutput,
|
|
892
|
-
error:
|
|
786
|
+
error: Schema14.Union([ConflictError, DatabaseError, NotFoundError, UnauthorizedError])
|
|
893
787
|
}),
|
|
894
788
|
Rpc2.make("devices.delete", {
|
|
895
789
|
payload: deleteDeviceInput,
|
|
896
790
|
success: deleteDeviceOutput,
|
|
897
|
-
error:
|
|
791
|
+
error: Schema14.Union([DatabaseError, NotFoundError, UnauthorizedError])
|
|
898
792
|
})
|
|
899
793
|
);
|
|
900
794
|
|
|
901
795
|
// ../../packages/api/src/rpc/stats.ts
|
|
902
|
-
import { Schema as
|
|
796
|
+
import { Schema as Schema16 } from "effect";
|
|
903
797
|
import { Rpc as Rpc3, RpcGroup as RpcGroup3 } from "effect/unstable/rpc";
|
|
904
798
|
|
|
905
799
|
// ../../packages/api/src/schemas/stats.ts
|
|
906
|
-
import { Effect as
|
|
907
|
-
var scopeEnum =
|
|
908
|
-
var costStackByValueEnum =
|
|
909
|
-
var costStackByEnum =
|
|
910
|
-
var usageDetailStackByEnum =
|
|
911
|
-
var stackByValueEnum =
|
|
912
|
-
var stackByEnum =
|
|
800
|
+
import { Effect as Effect4, Schema as Schema15 } from "effect";
|
|
801
|
+
var scopeEnum = Schema15.Literals(["all", "me"]);
|
|
802
|
+
var costStackByValueEnum = Schema15.Literals(["device", "harness", "model"]);
|
|
803
|
+
var costStackByEnum = Schema15.optional(costStackByValueEnum);
|
|
804
|
+
var usageDetailStackByEnum = Schema15.Literals(["device", "harness", "model"]);
|
|
805
|
+
var stackByValueEnum = Schema15.Literals(["device", "harness"]);
|
|
806
|
+
var stackByEnum = Schema15.optional(stackByValueEnum);
|
|
913
807
|
var MAX_TIME_ZONE_LENGTH = 64;
|
|
914
808
|
var DEFAULT_DAYS = 90;
|
|
915
809
|
var MAX_DAYS = 365;
|
|
@@ -927,16 +821,16 @@ var MAX_LEADERBOARD_LIMIT = 50;
|
|
|
927
821
|
var DEFAULT_LEADERBOARD_LIMIT = 10;
|
|
928
822
|
var UUID_V7_VERSION3 = 7;
|
|
929
823
|
var CIVIL_DATE_PATTERN2 = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/u;
|
|
930
|
-
var timeZoneSchema = ianaTimezone.check(
|
|
824
|
+
var timeZoneSchema = ianaTimezone.check(Schema15.isMaxLength(MAX_TIME_ZONE_LENGTH));
|
|
931
825
|
var civilDateSchema = civilDate;
|
|
932
|
-
var daysSchema = safeInteger.check(
|
|
933
|
-
var leaderboardLimitSchema = safeInteger.check(
|
|
934
|
-
var defaultTimeZoneSchema = timeZoneSchema.pipe(
|
|
935
|
-
var uuidSchema =
|
|
826
|
+
var daysSchema = safeInteger.check(Schema15.isBetween({ minimum: MIN_LEADERBOARD_LIMIT, maximum: MAX_DAYS })).pipe(Schema15.withDecodingDefaultTypeKey(Effect4.succeed(DEFAULT_DAYS)));
|
|
827
|
+
var leaderboardLimitSchema = safeInteger.check(Schema15.isBetween({ minimum: MIN_LEADERBOARD_LIMIT, maximum: MAX_LEADERBOARD_LIMIT })).pipe(Schema15.withDecodingDefaultTypeKey(Effect4.succeed(DEFAULT_LEADERBOARD_LIMIT)));
|
|
828
|
+
var defaultTimeZoneSchema = timeZoneSchema.pipe(Schema15.withDecodingDefaultTypeKey(Effect4.succeed("UTC")));
|
|
829
|
+
var uuidSchema = Schema15.String.check(Schema15.isUUID(UUID_V7_VERSION3));
|
|
936
830
|
var rangeFields = {
|
|
937
831
|
days: daysSchema,
|
|
938
|
-
endDate:
|
|
939
|
-
startDate:
|
|
832
|
+
endDate: Schema15.optional(civilDateSchema),
|
|
833
|
+
startDate: Schema15.optional(civilDateSchema)
|
|
940
834
|
};
|
|
941
835
|
function validateDateRange(input) {
|
|
942
836
|
if (!(input.startDate || input.endDate)) {
|
|
@@ -976,216 +870,216 @@ function parseCivilDateUtcMs(date) {
|
|
|
976
870
|
}
|
|
977
871
|
return parsed.getTime();
|
|
978
872
|
}
|
|
979
|
-
var dailyCostInput =
|
|
873
|
+
var dailyCostInput = Schema15.Struct({
|
|
980
874
|
scope: scopeEnum,
|
|
981
875
|
stackBy: costStackByEnum,
|
|
982
876
|
...rangeFields,
|
|
983
877
|
timeZone: defaultTimeZoneSchema
|
|
984
|
-
}).check(
|
|
985
|
-
var dailyPointSchema =
|
|
878
|
+
}).check(Schema15.makeFilter(validateDateRange));
|
|
879
|
+
var dailyPointSchema = Schema15.Struct({
|
|
986
880
|
/** Local civil date in YYYY-MM-DD for the requested stats timezone. */
|
|
987
881
|
date: civilDateSchema,
|
|
988
882
|
/** null when stackBy is undefined. */
|
|
989
|
-
seriesName:
|
|
883
|
+
seriesName: Schema15.NullOr(Schema15.String),
|
|
990
884
|
/** USD; 0 or positive. */
|
|
991
|
-
usd:
|
|
885
|
+
usd: Schema15.Number
|
|
992
886
|
});
|
|
993
|
-
var dailyCostOutput =
|
|
994
|
-
var dailySessionsInput =
|
|
887
|
+
var dailyCostOutput = Schema15.Array(dailyPointSchema);
|
|
888
|
+
var dailySessionsInput = Schema15.Struct({
|
|
995
889
|
scope: scopeEnum,
|
|
996
890
|
stackBy: stackByEnum,
|
|
997
891
|
...rangeFields,
|
|
998
892
|
timeZone: defaultTimeZoneSchema
|
|
999
|
-
}).check(
|
|
1000
|
-
var dailySessionsPointSchema =
|
|
893
|
+
}).check(Schema15.makeFilter(validateDateRange));
|
|
894
|
+
var dailySessionsPointSchema = Schema15.Struct({
|
|
1001
895
|
date: civilDateSchema,
|
|
1002
|
-
seriesName:
|
|
896
|
+
seriesName: Schema15.NullOr(Schema15.String),
|
|
1003
897
|
sessions: nonNegativeSafeInteger
|
|
1004
898
|
});
|
|
1005
|
-
var dailySessionsOutput =
|
|
899
|
+
var dailySessionsOutput = Schema15.Array(dailySessionsPointSchema);
|
|
1006
900
|
var weekdaySessionsInput = dailySessionsInput;
|
|
1007
|
-
var weekdaySessionsPointSchema =
|
|
901
|
+
var weekdaySessionsPointSchema = Schema15.Struct({
|
|
1008
902
|
/** Local ISO weekday for the requested stats timezone: 1=Monday, 7=Sunday. */
|
|
1009
|
-
weekday: safeInteger.check(
|
|
1010
|
-
seriesName:
|
|
903
|
+
weekday: safeInteger.check(Schema15.isBetween({ minimum: MIN_ISO_WEEKDAY, maximum: MAX_ISO_WEEKDAY })),
|
|
904
|
+
seriesName: Schema15.NullOr(Schema15.String),
|
|
1011
905
|
sessions: nonNegativeSafeInteger
|
|
1012
906
|
});
|
|
1013
|
-
var weekdaySessionsOutput =
|
|
907
|
+
var weekdaySessionsOutput = Schema15.Array(weekdaySessionsPointSchema);
|
|
1014
908
|
var hourlySessionsInput = dailySessionsInput;
|
|
1015
|
-
var hourlySessionsPointSchema =
|
|
909
|
+
var hourlySessionsPointSchema = Schema15.Struct({
|
|
1016
910
|
/** Local hour of day for the requested stats timezone: 0-23. */
|
|
1017
|
-
hour: safeInteger.check(
|
|
1018
|
-
seriesName:
|
|
911
|
+
hour: safeInteger.check(Schema15.isBetween({ minimum: MIN_HOUR_OF_DAY, maximum: MAX_HOUR_OF_DAY })),
|
|
912
|
+
seriesName: Schema15.NullOr(Schema15.String),
|
|
1019
913
|
sessions: nonNegativeSafeInteger
|
|
1020
914
|
});
|
|
1021
|
-
var hourlySessionsOutput =
|
|
1022
|
-
var summaryInput =
|
|
915
|
+
var hourlySessionsOutput = Schema15.Array(hourlySessionsPointSchema);
|
|
916
|
+
var summaryInput = Schema15.Struct({
|
|
1023
917
|
...rangeFields,
|
|
1024
918
|
scope: scopeEnum,
|
|
1025
919
|
timeZone: defaultTimeZoneSchema
|
|
1026
|
-
}).check(
|
|
1027
|
-
var summaryOutput =
|
|
1028
|
-
rangeUsd:
|
|
920
|
+
}).check(Schema15.makeFilter(validateDateRange));
|
|
921
|
+
var summaryOutput = Schema15.Struct({
|
|
922
|
+
rangeUsd: Schema15.Number.check(Schema15.isGreaterThanOrEqualTo(0)),
|
|
1029
923
|
rangeSessions: nonNegativeSafeInteger,
|
|
1030
924
|
rangeSubjectCount: nonNegativeSafeInteger,
|
|
1031
|
-
totalUsd:
|
|
925
|
+
totalUsd: Schema15.Number.check(Schema15.isGreaterThanOrEqualTo(0)),
|
|
1032
926
|
totalSessions: nonNegativeSafeInteger
|
|
1033
927
|
});
|
|
1034
|
-
var deviceSummarySchema =
|
|
928
|
+
var deviceSummarySchema = Schema15.Struct({
|
|
1035
929
|
id: uuidSchema,
|
|
1036
|
-
name:
|
|
930
|
+
name: Schema15.String,
|
|
1037
931
|
/** ISO-8601 UTC. */
|
|
1038
932
|
lastSeen: isoUtcString
|
|
1039
933
|
});
|
|
1040
|
-
var devicesOutput =
|
|
1041
|
-
var participantSyncSchema =
|
|
934
|
+
var devicesOutput = Schema15.Array(deviceSummarySchema);
|
|
935
|
+
var participantSyncSchema = Schema15.Struct({
|
|
1042
936
|
userId: uuidSchema,
|
|
1043
|
-
userLabel:
|
|
937
|
+
userLabel: Schema15.String,
|
|
1044
938
|
/** ISO-8601 UTC, null when the participant has no synced devices yet. */
|
|
1045
|
-
latestSyncAt:
|
|
939
|
+
latestSyncAt: Schema15.NullOr(isoUtcString)
|
|
1046
940
|
});
|
|
1047
|
-
var participantsOutput =
|
|
1048
|
-
var leaderboardInput =
|
|
941
|
+
var participantsOutput = Schema15.Array(participantSyncSchema);
|
|
942
|
+
var leaderboardInput = Schema15.Struct({
|
|
1049
943
|
...rangeFields,
|
|
1050
944
|
limit: leaderboardLimitSchema,
|
|
1051
945
|
timeZone: defaultTimeZoneSchema
|
|
1052
|
-
}).check(
|
|
1053
|
-
var leaderboardRowSchema =
|
|
1054
|
-
rank: safeInteger.check(
|
|
946
|
+
}).check(Schema15.makeFilter(validateDateRange));
|
|
947
|
+
var leaderboardRowSchema = Schema15.Struct({
|
|
948
|
+
rank: safeInteger.check(Schema15.isGreaterThanOrEqualTo(1)),
|
|
1055
949
|
userId: uuidSchema,
|
|
1056
|
-
userLabel:
|
|
1057
|
-
usd:
|
|
950
|
+
userLabel: Schema15.String,
|
|
951
|
+
usd: Schema15.Number.check(Schema15.isGreaterThanOrEqualTo(0)),
|
|
1058
952
|
tokens: nonNegativeSafeInteger,
|
|
1059
953
|
sessions: nonNegativeSafeInteger
|
|
1060
954
|
});
|
|
1061
|
-
var leaderboardOutput =
|
|
955
|
+
var leaderboardOutput = Schema15.Array(leaderboardRowSchema);
|
|
1062
956
|
var topDimensionsInput = leaderboardInput;
|
|
1063
|
-
var topDimensionRowSchema =
|
|
1064
|
-
rank: safeInteger.check(
|
|
1065
|
-
name:
|
|
1066
|
-
usd:
|
|
957
|
+
var topDimensionRowSchema = Schema15.Struct({
|
|
958
|
+
rank: safeInteger.check(Schema15.isGreaterThanOrEqualTo(1)),
|
|
959
|
+
name: Schema15.String,
|
|
960
|
+
usd: Schema15.Number.check(Schema15.isGreaterThanOrEqualTo(0)),
|
|
1067
961
|
tokens: nonNegativeSafeInteger,
|
|
1068
|
-
sessions:
|
|
962
|
+
sessions: Schema15.NullOr(nonNegativeSafeInteger)
|
|
1069
963
|
});
|
|
1070
|
-
var topDimensionsOutput =
|
|
1071
|
-
harnesses:
|
|
1072
|
-
models:
|
|
964
|
+
var topDimensionsOutput = Schema15.Struct({
|
|
965
|
+
harnesses: Schema15.Array(topDimensionRowSchema),
|
|
966
|
+
models: Schema15.Array(topDimensionRowSchema)
|
|
1073
967
|
});
|
|
1074
|
-
var usageDetailsInput =
|
|
968
|
+
var usageDetailsInput = Schema15.Struct({
|
|
1075
969
|
...rangeFields,
|
|
1076
970
|
stackBy: usageDetailStackByEnum,
|
|
1077
971
|
timeZone: defaultTimeZoneSchema
|
|
1078
|
-
}).check(
|
|
1079
|
-
var usageDetailsRowSchema =
|
|
1080
|
-
id:
|
|
1081
|
-
name:
|
|
972
|
+
}).check(Schema15.makeFilter(validateDateRange));
|
|
973
|
+
var usageDetailsRowSchema = Schema15.Struct({
|
|
974
|
+
id: Schema15.String,
|
|
975
|
+
name: Schema15.String,
|
|
1082
976
|
tokens: nonNegativeSafeInteger,
|
|
1083
|
-
sessions:
|
|
1084
|
-
usd:
|
|
1085
|
-
totalUsd:
|
|
977
|
+
sessions: Schema15.NullOr(nonNegativeSafeInteger),
|
|
978
|
+
usd: Schema15.Number.check(Schema15.isGreaterThanOrEqualTo(0)),
|
|
979
|
+
totalUsd: Schema15.Number.check(Schema15.isGreaterThanOrEqualTo(0))
|
|
1086
980
|
});
|
|
1087
|
-
var usageDetailsOutput =
|
|
981
|
+
var usageDetailsOutput = Schema15.Array(usageDetailsRowSchema);
|
|
1088
982
|
|
|
1089
983
|
// ../../packages/api/src/rpc/stats.ts
|
|
1090
984
|
var StatsRpcs = RpcGroup3.make(
|
|
1091
985
|
Rpc3.make("stats.devices", {
|
|
1092
986
|
success: devicesOutput,
|
|
1093
|
-
error:
|
|
987
|
+
error: Schema16.Union([DatabaseError, UnauthorizedError])
|
|
1094
988
|
}),
|
|
1095
989
|
Rpc3.make("stats.participants", {
|
|
1096
990
|
success: participantsOutput,
|
|
1097
|
-
error:
|
|
991
|
+
error: Schema16.Union([DatabaseError, UnauthorizedError])
|
|
1098
992
|
}),
|
|
1099
993
|
Rpc3.make("stats.leaderboard", {
|
|
1100
994
|
payload: leaderboardInput,
|
|
1101
995
|
success: leaderboardOutput,
|
|
1102
|
-
error:
|
|
996
|
+
error: Schema16.Union([DatabaseError, UnauthorizedError])
|
|
1103
997
|
}),
|
|
1104
998
|
Rpc3.make("stats.topDimensions", {
|
|
1105
999
|
payload: topDimensionsInput,
|
|
1106
1000
|
success: topDimensionsOutput,
|
|
1107
|
-
error:
|
|
1001
|
+
error: Schema16.Union([DatabaseError, UnauthorizedError])
|
|
1108
1002
|
}),
|
|
1109
1003
|
Rpc3.make("stats.summary", {
|
|
1110
1004
|
payload: summaryInput,
|
|
1111
1005
|
success: summaryOutput,
|
|
1112
|
-
error:
|
|
1006
|
+
error: Schema16.Union([DatabaseError, UnauthorizedError])
|
|
1113
1007
|
}),
|
|
1114
1008
|
Rpc3.make("stats.dailyCost", {
|
|
1115
1009
|
payload: dailyCostInput,
|
|
1116
1010
|
success: dailyCostOutput,
|
|
1117
|
-
error:
|
|
1011
|
+
error: Schema16.Union([DatabaseError, UnauthorizedError])
|
|
1118
1012
|
}),
|
|
1119
1013
|
Rpc3.make("stats.dailySessions", {
|
|
1120
1014
|
payload: dailySessionsInput,
|
|
1121
1015
|
success: dailySessionsOutput,
|
|
1122
|
-
error:
|
|
1016
|
+
error: Schema16.Union([DatabaseError, UnauthorizedError])
|
|
1123
1017
|
}),
|
|
1124
1018
|
Rpc3.make("stats.weekdaySessions", {
|
|
1125
1019
|
payload: weekdaySessionsInput,
|
|
1126
1020
|
success: weekdaySessionsOutput,
|
|
1127
|
-
error:
|
|
1021
|
+
error: Schema16.Union([DatabaseError, UnauthorizedError])
|
|
1128
1022
|
}),
|
|
1129
1023
|
Rpc3.make("stats.hourlySessions", {
|
|
1130
1024
|
payload: hourlySessionsInput,
|
|
1131
1025
|
success: hourlySessionsOutput,
|
|
1132
|
-
error:
|
|
1026
|
+
error: Schema16.Union([DatabaseError, UnauthorizedError])
|
|
1133
1027
|
}),
|
|
1134
1028
|
Rpc3.make("stats.usageDetails", {
|
|
1135
1029
|
payload: usageDetailsInput,
|
|
1136
1030
|
success: usageDetailsOutput,
|
|
1137
|
-
error:
|
|
1031
|
+
error: Schema16.Union([DatabaseError, UnauthorizedError])
|
|
1138
1032
|
})
|
|
1139
1033
|
);
|
|
1140
1034
|
|
|
1141
1035
|
// ../../packages/api/src/rpc/tokens.ts
|
|
1142
|
-
import { Schema as
|
|
1036
|
+
import { Schema as Schema18 } from "effect";
|
|
1143
1037
|
import { Rpc as Rpc4, RpcGroup as RpcGroup4 } from "effect/unstable/rpc";
|
|
1144
1038
|
|
|
1145
1039
|
// ../../packages/api/src/schemas/tokens.ts
|
|
1146
|
-
import { Schema as
|
|
1040
|
+
import { Schema as Schema17 } from "effect";
|
|
1147
1041
|
var MIN_TOKEN_NAME_LENGTH = 1;
|
|
1148
1042
|
var MAX_TOKEN_NAME_LENGTH = 80;
|
|
1149
|
-
var createTokenInput =
|
|
1150
|
-
name:
|
|
1043
|
+
var createTokenInput = Schema17.Struct({
|
|
1044
|
+
name: Schema17.String.check(Schema17.isMinLength(MIN_TOKEN_NAME_LENGTH), Schema17.isMaxLength(MAX_TOKEN_NAME_LENGTH))
|
|
1151
1045
|
});
|
|
1152
|
-
var revokeTokenInput =
|
|
1046
|
+
var revokeTokenInput = Schema17.Struct({
|
|
1153
1047
|
id: uuidV7
|
|
1154
1048
|
});
|
|
1155
|
-
var tokenMetadata =
|
|
1049
|
+
var tokenMetadata = Schema17.Struct({
|
|
1156
1050
|
id: uuidV7,
|
|
1157
|
-
name:
|
|
1158
|
-
prefix:
|
|
1051
|
+
name: Schema17.String,
|
|
1052
|
+
prefix: Schema17.String,
|
|
1159
1053
|
createdAt: isoUtcString,
|
|
1160
|
-
lastUsedAt:
|
|
1161
|
-
revokedAt:
|
|
1054
|
+
lastUsedAt: Schema17.NullOr(isoUtcString),
|
|
1055
|
+
revokedAt: Schema17.NullOr(isoUtcString)
|
|
1162
1056
|
});
|
|
1163
|
-
var listTokensOutput =
|
|
1164
|
-
tokens:
|
|
1057
|
+
var listTokensOutput = Schema17.Struct({
|
|
1058
|
+
tokens: Schema17.Array(tokenMetadata)
|
|
1165
1059
|
});
|
|
1166
|
-
var createTokenOutput =
|
|
1167
|
-
token:
|
|
1060
|
+
var createTokenOutput = Schema17.Struct({
|
|
1061
|
+
token: Schema17.String,
|
|
1168
1062
|
metadata: tokenMetadata
|
|
1169
1063
|
});
|
|
1170
|
-
var revokeTokenOutput =
|
|
1171
|
-
ok:
|
|
1064
|
+
var revokeTokenOutput = Schema17.Struct({
|
|
1065
|
+
ok: Schema17.Boolean
|
|
1172
1066
|
});
|
|
1173
1067
|
|
|
1174
1068
|
// ../../packages/api/src/rpc/tokens.ts
|
|
1175
1069
|
var TokensRpcs = RpcGroup4.make(
|
|
1176
1070
|
Rpc4.make("tokens.list", {
|
|
1177
1071
|
success: listTokensOutput,
|
|
1178
|
-
error:
|
|
1072
|
+
error: Schema18.Union([DatabaseError, UnauthorizedError])
|
|
1179
1073
|
}),
|
|
1180
1074
|
Rpc4.make("tokens.create", {
|
|
1181
1075
|
payload: createTokenInput,
|
|
1182
1076
|
success: createTokenOutput,
|
|
1183
|
-
error:
|
|
1077
|
+
error: Schema18.Union([DatabaseError, UnauthorizedError])
|
|
1184
1078
|
}),
|
|
1185
1079
|
Rpc4.make("tokens.revoke", {
|
|
1186
1080
|
payload: revokeTokenInput,
|
|
1187
1081
|
success: revokeTokenOutput,
|
|
1188
|
-
error:
|
|
1082
|
+
error: Schema18.Union([DatabaseError, NotFoundError, UnauthorizedError])
|
|
1189
1083
|
})
|
|
1190
1084
|
);
|
|
1191
1085
|
|
|
@@ -1193,13 +1087,15 @@ var TokensRpcs = RpcGroup4.make(
|
|
|
1193
1087
|
var WebRpcs = AuthRpcs.merge(StatsRpcs).merge(TokensRpcs).merge(DevicesRpcs);
|
|
1194
1088
|
|
|
1195
1089
|
// ../../packages/api/src/rpc/sync.ts
|
|
1196
|
-
import { Schema as
|
|
1090
|
+
import { Schema as Schema20 } from "effect";
|
|
1197
1091
|
import { Rpc as Rpc5, RpcGroup as RpcGroup5 } from "effect/unstable/rpc";
|
|
1198
1092
|
|
|
1199
1093
|
// ../../packages/api/src/schemas/sync.ts
|
|
1200
|
-
import { Effect as
|
|
1094
|
+
import { Effect as Effect5, Schema as Schema19 } from "effect";
|
|
1201
1095
|
var MIN_DEVICE_NAME_LENGTH2 = 1;
|
|
1202
1096
|
var MAX_DEVICE_NAME_LENGTH2 = 80;
|
|
1097
|
+
var MIN_CLI_VERSION_LENGTH = 1;
|
|
1098
|
+
var MAX_CLI_VERSION_LENGTH = 100;
|
|
1203
1099
|
var MIN_MODEL_LENGTH = 1;
|
|
1204
1100
|
var MAX_MODEL_LENGTH = 200;
|
|
1205
1101
|
var SESSION_KEY_LENGTH = 64;
|
|
@@ -1208,28 +1104,38 @@ var MAX_BUCKETS_PER_SYNC = 5e3;
|
|
|
1208
1104
|
var MAX_SESSIONS_PER_SYNC = 5e3;
|
|
1209
1105
|
var MAX_BUCKET_SESSION_COUNT = 1e3;
|
|
1210
1106
|
var MAX_BUCKET_TOKEN_COUNT = null;
|
|
1107
|
+
var MAX_REPLACEMENT_BATCHES = 1e4;
|
|
1108
|
+
var MAX_REPLACEMENT_FACTS = 1e5;
|
|
1211
1109
|
var HARNESSES = ["codex", "claude_code", "pi", "hermes", "opencode"];
|
|
1212
|
-
var
|
|
1213
|
-
var
|
|
1214
|
-
|
|
1215
|
-
|
|
1110
|
+
var MAX_REPLACEMENT_HARNESSES = HARNESSES.length;
|
|
1111
|
+
var harnessEnum = Schema19.Literals(HARNESSES);
|
|
1112
|
+
var deviceNameSchema2 = Schema19.String.check(
|
|
1113
|
+
Schema19.isMinLength(MIN_DEVICE_NAME_LENGTH2),
|
|
1114
|
+
Schema19.isMaxLength(MAX_DEVICE_NAME_LENGTH2)
|
|
1115
|
+
);
|
|
1116
|
+
var cliVersionSchema = Schema19.String.check(
|
|
1117
|
+
Schema19.isMinLength(MIN_CLI_VERSION_LENGTH),
|
|
1118
|
+
Schema19.isMaxLength(MAX_CLI_VERSION_LENGTH)
|
|
1119
|
+
);
|
|
1120
|
+
var legacyCompatibleCliVersionSchema = Schema19.NullOr(cliVersionSchema).pipe(
|
|
1121
|
+
Schema19.withDecodingDefaultTypeKey(Effect5.succeed(null))
|
|
1216
1122
|
);
|
|
1217
|
-
var getWatermarkInput =
|
|
1123
|
+
var getWatermarkInput = Schema19.Struct({
|
|
1218
1124
|
device: deviceNameSchema2
|
|
1219
1125
|
});
|
|
1220
|
-
var getWatermarkOutput =
|
|
1221
|
-
watermarkHourUtc:
|
|
1222
|
-
sessionKeySalt: hex64.check(
|
|
1126
|
+
var getWatermarkOutput = Schema19.Struct({
|
|
1127
|
+
watermarkHourUtc: Schema19.NullOr(isoUtcString),
|
|
1128
|
+
sessionKeySalt: hex64.check(Schema19.isLengthBetween(SESSION_KEY_SALT_LENGTH, SESSION_KEY_SALT_LENGTH))
|
|
1223
1129
|
});
|
|
1224
1130
|
function boundedInteger(max) {
|
|
1225
1131
|
if (max === null) {
|
|
1226
1132
|
return nonNegativeSafeInteger;
|
|
1227
1133
|
}
|
|
1228
|
-
return nonNegativeSafeInteger.check(
|
|
1134
|
+
return nonNegativeSafeInteger.check(Schema19.isLessThanOrEqualTo(max));
|
|
1229
1135
|
}
|
|
1230
|
-
var bucketInput =
|
|
1136
|
+
var bucketInput = Schema19.Struct({
|
|
1231
1137
|
harness: harnessEnum,
|
|
1232
|
-
model:
|
|
1138
|
+
model: Schema19.String.check(Schema19.isMinLength(MIN_MODEL_LENGTH), Schema19.isMaxLength(MAX_MODEL_LENGTH)),
|
|
1233
1139
|
hourUtc: isoUtcString,
|
|
1234
1140
|
inputTokens: boundedInteger(MAX_BUCKET_TOKEN_COUNT),
|
|
1235
1141
|
outputTokens: boundedInteger(MAX_BUCKET_TOKEN_COUNT),
|
|
@@ -1237,110 +1143,421 @@ var bucketInput = Schema14.Struct({
|
|
|
1237
1143
|
cacheCreateTokens: boundedInteger(MAX_BUCKET_TOKEN_COUNT),
|
|
1238
1144
|
sessionCount: boundedInteger(MAX_BUCKET_SESSION_COUNT)
|
|
1239
1145
|
});
|
|
1240
|
-
var sessionInput =
|
|
1146
|
+
var sessionInput = Schema19.Struct({
|
|
1241
1147
|
harness: harnessEnum,
|
|
1242
|
-
sessionKey: hex64.check(
|
|
1148
|
+
sessionKey: hex64.check(Schema19.isLengthBetween(SESSION_KEY_LENGTH, SESSION_KEY_LENGTH)),
|
|
1243
1149
|
startedAt: isoUtcString,
|
|
1244
1150
|
lastSeenAt: isoUtcString
|
|
1245
1151
|
});
|
|
1246
|
-
var upsertBucketsInput =
|
|
1152
|
+
var upsertBucketsInput = Schema19.Struct({
|
|
1247
1153
|
device: deviceNameSchema2,
|
|
1248
|
-
|
|
1249
|
-
|
|
1154
|
+
cliVersion: legacyCompatibleCliVersionSchema,
|
|
1155
|
+
buckets: Schema19.Array(bucketInput).check(Schema19.isMaxLength(MAX_BUCKETS_PER_SYNC)),
|
|
1156
|
+
sessions: Schema19.Array(sessionInput).check(Schema19.isMaxLength(MAX_SESSIONS_PER_SYNC)).pipe(Schema19.withDecodingDefaultTypeKey(Effect5.succeed([])))
|
|
1250
1157
|
});
|
|
1251
|
-
var upsertBucketsOutput =
|
|
1158
|
+
var upsertBucketsOutput = Schema19.Struct({
|
|
1252
1159
|
upserted: boundedInteger(null),
|
|
1253
1160
|
upsertedSessions: boundedInteger(null)
|
|
1254
1161
|
});
|
|
1162
|
+
var replacementId = uuidV7;
|
|
1163
|
+
var createUsageReplacementInput = Schema19.Struct({
|
|
1164
|
+
device: deviceNameSchema2,
|
|
1165
|
+
cliVersion: legacyCompatibleCliVersionSchema,
|
|
1166
|
+
startHourUtc: isoUtcString,
|
|
1167
|
+
endHourUtc: isoUtcString,
|
|
1168
|
+
harnesses: Schema19.Array(harnessEnum).check(Schema19.isMinLength(1), Schema19.isMaxLength(MAX_REPLACEMENT_HARNESSES))
|
|
1169
|
+
});
|
|
1170
|
+
var createUsageReplacementOutput = Schema19.Struct({
|
|
1171
|
+
replacementId,
|
|
1172
|
+
startHourUtc: isoUtcString,
|
|
1173
|
+
endHourUtc: isoUtcString,
|
|
1174
|
+
harnesses: Schema19.Array(harnessEnum),
|
|
1175
|
+
expiresAt: isoUtcString
|
|
1176
|
+
});
|
|
1177
|
+
var stageUsageReplacementBatchInput = Schema19.Struct({
|
|
1178
|
+
device: deviceNameSchema2,
|
|
1179
|
+
replacementId,
|
|
1180
|
+
batchIndex: boundedInteger(MAX_REPLACEMENT_BATCHES),
|
|
1181
|
+
batchDigest: hex64,
|
|
1182
|
+
buckets: Schema19.Array(bucketInput).check(Schema19.isMaxLength(MAX_BUCKETS_PER_SYNC)),
|
|
1183
|
+
sessions: Schema19.Array(sessionInput).check(Schema19.isMaxLength(MAX_SESSIONS_PER_SYNC))
|
|
1184
|
+
});
|
|
1185
|
+
var stageUsageReplacementBatchOutput = Schema19.Struct({
|
|
1186
|
+
stagedBuckets: boundedInteger(MAX_BUCKETS_PER_SYNC),
|
|
1187
|
+
stagedSessions: boundedInteger(MAX_SESSIONS_PER_SYNC),
|
|
1188
|
+
alreadyStaged: Schema19.Boolean
|
|
1189
|
+
});
|
|
1190
|
+
var finalizeUsageReplacementInput = Schema19.Struct({
|
|
1191
|
+
device: deviceNameSchema2,
|
|
1192
|
+
replacementId,
|
|
1193
|
+
expectedBatchCount: boundedInteger(MAX_REPLACEMENT_BATCHES),
|
|
1194
|
+
expectedBucketCount: boundedInteger(MAX_REPLACEMENT_FACTS),
|
|
1195
|
+
expectedSessionCount: boundedInteger(MAX_REPLACEMENT_FACTS),
|
|
1196
|
+
digest: hex64
|
|
1197
|
+
});
|
|
1198
|
+
var finalizeUsageReplacementOutput = Schema19.Struct({
|
|
1199
|
+
replacementId,
|
|
1200
|
+
committedAt: isoUtcString,
|
|
1201
|
+
replacedBuckets: boundedInteger(MAX_REPLACEMENT_FACTS),
|
|
1202
|
+
replacedSessions: boundedInteger(MAX_REPLACEMENT_FACTS),
|
|
1203
|
+
digest: hex64
|
|
1204
|
+
});
|
|
1255
1205
|
|
|
1256
1206
|
// ../../packages/api/src/rpc/sync.ts
|
|
1207
|
+
var replacementErrors = Schema20.Union([
|
|
1208
|
+
ConflictError,
|
|
1209
|
+
DatabaseError,
|
|
1210
|
+
NotFoundError,
|
|
1211
|
+
SyncQuotaExceededError,
|
|
1212
|
+
SyncTimestampRejectedError,
|
|
1213
|
+
UnauthorizedError,
|
|
1214
|
+
ValidationError
|
|
1215
|
+
]);
|
|
1257
1216
|
var SyncRpcs = RpcGroup5.make(
|
|
1258
1217
|
Rpc5.make("sync.getWatermark", {
|
|
1259
1218
|
payload: getWatermarkInput,
|
|
1260
1219
|
success: getWatermarkOutput,
|
|
1261
|
-
error:
|
|
1220
|
+
error: Schema20.Union([DatabaseError, UnauthorizedError])
|
|
1262
1221
|
}),
|
|
1263
1222
|
Rpc5.make("sync.upsertBuckets", {
|
|
1264
1223
|
payload: upsertBucketsInput,
|
|
1265
1224
|
success: upsertBucketsOutput,
|
|
1266
|
-
error:
|
|
1225
|
+
error: Schema20.Union([DatabaseError, SyncQuotaExceededError, SyncTimestampRejectedError, UnauthorizedError])
|
|
1226
|
+
}),
|
|
1227
|
+
Rpc5.make("sync.createUsageReplacement", {
|
|
1228
|
+
payload: createUsageReplacementInput,
|
|
1229
|
+
success: createUsageReplacementOutput,
|
|
1230
|
+
error: replacementErrors
|
|
1231
|
+
}),
|
|
1232
|
+
Rpc5.make("sync.stageUsageReplacementBatch", {
|
|
1233
|
+
payload: stageUsageReplacementBatchInput,
|
|
1234
|
+
success: stageUsageReplacementBatchOutput,
|
|
1235
|
+
error: replacementErrors
|
|
1236
|
+
}),
|
|
1237
|
+
Rpc5.make("sync.finalizeUsageReplacement", {
|
|
1238
|
+
payload: finalizeUsageReplacementInput,
|
|
1239
|
+
success: finalizeUsageReplacementOutput,
|
|
1240
|
+
error: replacementErrors
|
|
1267
1241
|
})
|
|
1268
1242
|
);
|
|
1269
1243
|
|
|
1270
|
-
// src/sync
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
const client = yield* RpcClient.make(SyncRpcs);
|
|
1298
|
-
const getWatermark = Effect7.fn("SyncRpcClient.getWatermark")(
|
|
1299
|
-
(input) => client["sync.getWatermark"](input)
|
|
1300
|
-
);
|
|
1301
|
-
const upsertBuckets = Effect7.fn("SyncRpcClient.upsertBuckets")(
|
|
1302
|
-
(input) => client["sync.upsertBuckets"](input)
|
|
1303
|
-
);
|
|
1304
|
-
return SyncRpcClient.of({ getWatermark, upsertBuckets });
|
|
1305
|
-
})
|
|
1306
|
-
).pipe(Layer3.provide(makeClientLive(options)));
|
|
1307
|
-
}
|
|
1308
|
-
function makeClientLive(options) {
|
|
1309
|
-
let clientLive = RpcClient.layerProtocolHttp({
|
|
1310
|
-
url: stripTrailingSlash(options.serverUrl),
|
|
1311
|
-
transformClient: HttpClient.mapRequest(
|
|
1312
|
-
(request) => request.pipe(
|
|
1313
|
-
HttpClientRequest.appendUrl(SYNC_RPC_PATH),
|
|
1314
|
-
HttpClientRequest.setHeader(AUTHORIZATION_HEADER, `Bearer ${options.token}`)
|
|
1315
|
-
)
|
|
1244
|
+
// ../../packages/api/src/sync-replacement-canonical.ts
|
|
1245
|
+
function normalizedIso(value) {
|
|
1246
|
+
return new Date(value).toISOString();
|
|
1247
|
+
}
|
|
1248
|
+
function canonicalReplacementLines(facts) {
|
|
1249
|
+
const lines = [
|
|
1250
|
+
...facts.buckets.map(
|
|
1251
|
+
(bucket) => JSON.stringify([
|
|
1252
|
+
"bucket",
|
|
1253
|
+
bucket.harness,
|
|
1254
|
+
bucket.model,
|
|
1255
|
+
normalizedIso(bucket.hourUtc),
|
|
1256
|
+
bucket.inputTokens,
|
|
1257
|
+
bucket.outputTokens,
|
|
1258
|
+
bucket.cacheReadTokens,
|
|
1259
|
+
bucket.cacheCreateTokens,
|
|
1260
|
+
bucket.sessionCount
|
|
1261
|
+
])
|
|
1262
|
+
),
|
|
1263
|
+
...facts.sessions.map(
|
|
1264
|
+
(session) => JSON.stringify([
|
|
1265
|
+
"session",
|
|
1266
|
+
session.harness,
|
|
1267
|
+
session.sessionKey,
|
|
1268
|
+
normalizedIso(session.startedAt),
|
|
1269
|
+
normalizedIso(session.lastSeenAt)
|
|
1270
|
+
])
|
|
1316
1271
|
)
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
clientLive = clientLive.pipe(Layer3.provide(Layer3.succeed(FetchHttpClient.Fetch, options.fetch)));
|
|
1320
|
-
}
|
|
1321
|
-
return clientLive;
|
|
1272
|
+
];
|
|
1273
|
+
return lines.sort();
|
|
1322
1274
|
}
|
|
1323
|
-
function
|
|
1324
|
-
return
|
|
1275
|
+
function canonicalReplacementPayload(facts) {
|
|
1276
|
+
return canonicalReplacementLines(facts).join("\n");
|
|
1325
1277
|
}
|
|
1326
1278
|
|
|
1327
|
-
// src/sync/
|
|
1328
|
-
import {
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
var
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1279
|
+
// src/sync/run.ts
|
|
1280
|
+
import { Effect as Effect9, Stream as Stream4 } from "effect";
|
|
1281
|
+
|
|
1282
|
+
// src/sync/aggregate.ts
|
|
1283
|
+
import { createHmac } from "crypto";
|
|
1284
|
+
import { Effect as Effect6, Schema as Schema21, Stream as Stream2 } from "effect";
|
|
1285
|
+
var UsageAggregationErrorFields = {
|
|
1286
|
+
message: Schema21.String,
|
|
1287
|
+
cause: Schema21.optionalKey(Schema21.Defect())
|
|
1288
|
+
};
|
|
1289
|
+
var UsageAggregationError = class extends Schema21.TaggedErrorClass()(
|
|
1290
|
+
"UsageAggregationError",
|
|
1291
|
+
UsageAggregationErrorFields
|
|
1292
|
+
) {
|
|
1293
|
+
};
|
|
1294
|
+
function toIso(hourUtc) {
|
|
1295
|
+
return hourUtc instanceof Date ? hourUtc.toISOString() : hourUtc;
|
|
1296
|
+
}
|
|
1297
|
+
function key(b) {
|
|
1298
|
+
return `${b.harness} ${b.model} ${toIso(b.hourUtc)}`;
|
|
1299
|
+
}
|
|
1300
|
+
function finalize(acc) {
|
|
1301
|
+
return {
|
|
1302
|
+
harness: acc.harness,
|
|
1303
|
+
model: acc.model,
|
|
1304
|
+
hourUtc: acc.hourUtc,
|
|
1305
|
+
inputTokens: acc.inputTokens,
|
|
1306
|
+
outputTokens: acc.outputTokens,
|
|
1307
|
+
cacheReadTokens: acc.cacheReadTokens,
|
|
1308
|
+
cacheCreateTokens: acc.cacheCreateTokens,
|
|
1309
|
+
sessionCount: acc.sessionIds.size
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
var SESSION_KEY_SALT_PATTERN = /^[a-f0-9]{64}$/u;
|
|
1313
|
+
function createSessionKey(harness, localSessionId, sessionKeySalt) {
|
|
1314
|
+
if (!SESSION_KEY_SALT_PATTERN.test(sessionKeySalt)) {
|
|
1315
|
+
throw new Error("invalid session key salt");
|
|
1316
|
+
}
|
|
1317
|
+
return createHmac("sha256", Buffer.from(sessionKeySalt, "hex")).update("oopst/session-key/v1").update("\0").update(harness).update("\0").update(localSessionId).digest("hex");
|
|
1318
|
+
}
|
|
1319
|
+
function sessionMapKey(harness, sessionKey) {
|
|
1320
|
+
return `${harness} ${sessionKey}`;
|
|
1321
|
+
}
|
|
1322
|
+
function finalizeSession(acc) {
|
|
1323
|
+
return {
|
|
1324
|
+
harness: acc.harness,
|
|
1325
|
+
sessionKey: acc.sessionKey,
|
|
1326
|
+
startedAt: acc.startedAt.toISOString(),
|
|
1327
|
+
lastSeenAt: acc.lastSeenAt.toISOString()
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function addSessionAccumulator(sessionAccumulators, bucket, localSessionId, sessionKeySalt) {
|
|
1331
|
+
const sessionKey = createSessionKey(bucket.harness, localSessionId, sessionKeySalt);
|
|
1332
|
+
const sessionAccumulatorKey = sessionMapKey(bucket.harness, sessionKey);
|
|
1333
|
+
const existingSession = sessionAccumulators.get(sessionAccumulatorKey);
|
|
1334
|
+
if (existingSession) {
|
|
1335
|
+
if (bucket.sessionStartedAt.getTime() < existingSession.startedAt.getTime()) {
|
|
1336
|
+
existingSession.startedAt = bucket.sessionStartedAt;
|
|
1337
|
+
}
|
|
1338
|
+
if (bucket.sessionLastSeenAt.getTime() > existingSession.lastSeenAt.getTime()) {
|
|
1339
|
+
existingSession.lastSeenAt = bucket.sessionLastSeenAt;
|
|
1340
|
+
}
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
sessionAccumulators.set(sessionAccumulatorKey, {
|
|
1344
|
+
harness: bucket.harness,
|
|
1345
|
+
sessionKey,
|
|
1346
|
+
startedAt: bucket.sessionStartedAt,
|
|
1347
|
+
lastSeenAt: bucket.sessionLastSeenAt
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
function createUsageAggregationState() {
|
|
1351
|
+
return {
|
|
1352
|
+
accumulators: /* @__PURE__ */ new Map(),
|
|
1353
|
+
sessionAccumulators: /* @__PURE__ */ new Map()
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
function addBucketToAggregationState(state, bucket, sessionKeySalt, replacementRange) {
|
|
1357
|
+
const includeBucket = isReplacementBucketEligible(bucket, replacementRange);
|
|
1358
|
+
if (includeBucket) {
|
|
1359
|
+
const k = key(bucket);
|
|
1360
|
+
let acc = state.accumulators.get(k);
|
|
1361
|
+
if (!acc) {
|
|
1362
|
+
acc = {
|
|
1363
|
+
harness: bucket.harness,
|
|
1364
|
+
model: bucket.model,
|
|
1365
|
+
hourUtc: toIso(bucket.hourUtc),
|
|
1366
|
+
inputTokens: 0,
|
|
1367
|
+
outputTokens: 0,
|
|
1368
|
+
cacheReadTokens: 0,
|
|
1369
|
+
cacheCreateTokens: 0,
|
|
1370
|
+
sessionIds: /* @__PURE__ */ new Set()
|
|
1371
|
+
};
|
|
1372
|
+
state.accumulators.set(k, acc);
|
|
1373
|
+
}
|
|
1374
|
+
acc.inputTokens += bucket.inputTokens;
|
|
1375
|
+
acc.outputTokens += bucket.outputTokens;
|
|
1376
|
+
acc.cacheReadTokens += bucket.cacheReadTokens;
|
|
1377
|
+
acc.cacheCreateTokens += bucket.cacheCreateTokens;
|
|
1378
|
+
for (const id of bucket.sessionIds) {
|
|
1379
|
+
acc.sessionIds.add(id);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
if (isReplacementSessionEligible(bucket, replacementRange)) {
|
|
1383
|
+
for (const id of bucket.sessionIds) {
|
|
1384
|
+
addSessionAccumulator(state.sessionAccumulators, bucket, id, sessionKeySalt);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
return state;
|
|
1388
|
+
}
|
|
1389
|
+
function isReplacementBucketEligible(bucket, range) {
|
|
1390
|
+
if (!range) {
|
|
1391
|
+
return true;
|
|
1392
|
+
}
|
|
1393
|
+
const hourMs = bucket.hourUtc.getTime();
|
|
1394
|
+
return range.harnesses.has(bucket.harness) && hourMs >= range.startHourUtc.getTime() && hourMs < range.endHourUtc.getTime();
|
|
1395
|
+
}
|
|
1396
|
+
function isReplacementSessionEligible(bucket, range) {
|
|
1397
|
+
if (!range) {
|
|
1398
|
+
return true;
|
|
1399
|
+
}
|
|
1400
|
+
return range.harnesses.has(bucket.harness) && bucket.sessionStartedAt.getTime() < range.endHourUtc.getTime() && bucket.sessionLastSeenAt.getTime() >= range.startHourUtc.getTime();
|
|
1401
|
+
}
|
|
1402
|
+
function addBucketToAggregationStateEffect(state, bucket, sessionKeySalt, replacementRange) {
|
|
1403
|
+
return Effect6.try({
|
|
1404
|
+
try: () => addBucketToAggregationState(state, bucket, sessionKeySalt, replacementRange),
|
|
1405
|
+
catch: toUsageAggregationError
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
function finalizeUsageBatches(state, batchSize) {
|
|
1409
|
+
const finalized = [];
|
|
1410
|
+
for (const acc of state.accumulators.values()) {
|
|
1411
|
+
finalized.push(finalize(acc));
|
|
1412
|
+
}
|
|
1413
|
+
const finalizedSessions = [];
|
|
1414
|
+
for (const acc of state.sessionAccumulators.values()) {
|
|
1415
|
+
finalizedSessions.push(finalizeSession(acc));
|
|
1416
|
+
}
|
|
1417
|
+
const batches = [];
|
|
1418
|
+
const maxLength = Math.max(finalized.length, finalizedSessions.length);
|
|
1419
|
+
for (let i = 0; i < maxLength; i += batchSize) {
|
|
1420
|
+
batches.push({
|
|
1421
|
+
buckets: finalized.slice(i, i + batchSize),
|
|
1422
|
+
sessions: finalizedSessions.slice(i, i + batchSize)
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
return batches;
|
|
1426
|
+
}
|
|
1427
|
+
function toUsageAggregationError(cause) {
|
|
1428
|
+
return new UsageAggregationError({
|
|
1429
|
+
message: `usage aggregation failed: ${errorMessage(cause)}`,
|
|
1430
|
+
cause
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
function errorMessage(cause) {
|
|
1434
|
+
return cause instanceof Error ? cause.message : "unknown aggregation error";
|
|
1435
|
+
}
|
|
1436
|
+
function aggregateUsageStream(source, options) {
|
|
1437
|
+
return Stream2.fromIterableEffect(
|
|
1438
|
+
source.pipe(
|
|
1439
|
+
Stream2.runFoldEffect(
|
|
1440
|
+
() => createUsageAggregationState(),
|
|
1441
|
+
(state, bucket) => addBucketToAggregationStateEffect(state, bucket, options.sessionKeySalt, options.replacementRange)
|
|
1442
|
+
),
|
|
1443
|
+
Effect6.map((state) => finalizeUsageBatches(state, options.batchSize))
|
|
1444
|
+
)
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
// src/sync/client.ts
|
|
1449
|
+
import { Context as Context6, Effect as Effect7, Layer as Layer3 } from "effect";
|
|
1450
|
+
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
1451
|
+
import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
|
|
1452
|
+
var AUTHORIZATION_HEADER = "authorization";
|
|
1453
|
+
var SYNC_RPC_PATH = "/rpc/sync";
|
|
1454
|
+
var SyncRpcClient = class extends Context6.Service()("@oopst/cli/SyncRpcClient") {
|
|
1455
|
+
};
|
|
1456
|
+
function syncRpcClientFromPromiseClient(client) {
|
|
1457
|
+
const getWatermark = Effect7.fn("SyncRpcClient.fromPromise.getWatermark")(
|
|
1458
|
+
(input) => Effect7.tryPromise({
|
|
1459
|
+
try: () => client.sync.getWatermark.mutate(input),
|
|
1460
|
+
catch: (err) => err
|
|
1461
|
+
})
|
|
1462
|
+
);
|
|
1463
|
+
const upsertBuckets = Effect7.fn("SyncRpcClient.fromPromise.upsertBuckets")(
|
|
1464
|
+
(input) => Effect7.tryPromise({
|
|
1465
|
+
try: () => client.sync.upsertBuckets.mutate(input),
|
|
1466
|
+
catch: (err) => err
|
|
1467
|
+
})
|
|
1468
|
+
);
|
|
1469
|
+
const createUsageReplacement = Effect7.fn("SyncRpcClient.fromPromise.createUsageReplacement")(
|
|
1470
|
+
(input) => Effect7.tryPromise({
|
|
1471
|
+
try: () => client.sync.createUsageReplacement.mutate(input),
|
|
1472
|
+
catch: (err) => err
|
|
1473
|
+
})
|
|
1474
|
+
);
|
|
1475
|
+
const stageUsageReplacementBatch = Effect7.fn("SyncRpcClient.fromPromise.stageUsageReplacementBatch")(
|
|
1476
|
+
(input) => Effect7.tryPromise({
|
|
1477
|
+
try: () => client.sync.stageUsageReplacementBatch.mutate(input),
|
|
1478
|
+
catch: (err) => err
|
|
1479
|
+
})
|
|
1480
|
+
);
|
|
1481
|
+
const finalizeUsageReplacement = Effect7.fn("SyncRpcClient.fromPromise.finalizeUsageReplacement")(
|
|
1482
|
+
(input) => Effect7.tryPromise({
|
|
1483
|
+
try: () => client.sync.finalizeUsageReplacement.mutate(input),
|
|
1484
|
+
catch: (err) => err
|
|
1485
|
+
})
|
|
1486
|
+
);
|
|
1487
|
+
return SyncRpcClient.of({
|
|
1488
|
+
createUsageReplacement,
|
|
1489
|
+
finalizeUsageReplacement,
|
|
1490
|
+
getWatermark,
|
|
1491
|
+
stageUsageReplacementBatch,
|
|
1492
|
+
upsertBuckets
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
function makeSyncRpcClientLayer(options) {
|
|
1496
|
+
return Layer3.effect(
|
|
1497
|
+
SyncRpcClient,
|
|
1498
|
+
Effect7.gen(function* () {
|
|
1499
|
+
const client = yield* RpcClient.make(SyncRpcs);
|
|
1500
|
+
const getWatermark = Effect7.fn("SyncRpcClient.getWatermark")(
|
|
1501
|
+
(input) => client["sync.getWatermark"](input)
|
|
1502
|
+
);
|
|
1503
|
+
const upsertBuckets = Effect7.fn("SyncRpcClient.upsertBuckets")(
|
|
1504
|
+
(input) => client["sync.upsertBuckets"](input)
|
|
1505
|
+
);
|
|
1506
|
+
const createUsageReplacement = Effect7.fn("SyncRpcClient.createUsageReplacement")(
|
|
1507
|
+
(input) => client["sync.createUsageReplacement"](input)
|
|
1508
|
+
);
|
|
1509
|
+
const stageUsageReplacementBatch = Effect7.fn("SyncRpcClient.stageUsageReplacementBatch")(
|
|
1510
|
+
(input) => client["sync.stageUsageReplacementBatch"](input)
|
|
1511
|
+
);
|
|
1512
|
+
const finalizeUsageReplacement = Effect7.fn("SyncRpcClient.finalizeUsageReplacement")(
|
|
1513
|
+
(input) => client["sync.finalizeUsageReplacement"](input)
|
|
1514
|
+
);
|
|
1515
|
+
return SyncRpcClient.of({
|
|
1516
|
+
createUsageReplacement,
|
|
1517
|
+
finalizeUsageReplacement,
|
|
1518
|
+
getWatermark,
|
|
1519
|
+
stageUsageReplacementBatch,
|
|
1520
|
+
upsertBuckets
|
|
1521
|
+
});
|
|
1522
|
+
})
|
|
1523
|
+
).pipe(Layer3.provide(makeClientLive(options)));
|
|
1524
|
+
}
|
|
1525
|
+
function makeClientLive(options) {
|
|
1526
|
+
let clientLive = RpcClient.layerProtocolHttp({
|
|
1527
|
+
url: stripTrailingSlash(options.serverUrl),
|
|
1528
|
+
transformClient: HttpClient.mapRequest(
|
|
1529
|
+
(request) => request.pipe(
|
|
1530
|
+
HttpClientRequest.appendUrl(SYNC_RPC_PATH),
|
|
1531
|
+
HttpClientRequest.setHeader(AUTHORIZATION_HEADER, `Bearer ${options.token}`)
|
|
1532
|
+
)
|
|
1533
|
+
)
|
|
1534
|
+
}).pipe(Layer3.provide(RpcSerialization.layerJson), Layer3.provide(FetchHttpClient.layer));
|
|
1535
|
+
if (options.fetch) {
|
|
1536
|
+
clientLive = clientLive.pipe(Layer3.provide(Layer3.succeed(FetchHttpClient.Fetch, options.fetch)));
|
|
1537
|
+
}
|
|
1538
|
+
return clientLive;
|
|
1539
|
+
}
|
|
1540
|
+
function stripTrailingSlash(url) {
|
|
1541
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// src/sync/errors.ts
|
|
1545
|
+
import { Schema as Schema22 } from "effect";
|
|
1546
|
+
var SUCCESS_EXIT_CODE = 0;
|
|
1547
|
+
var ZERO_BUCKETS_EXIT_CODE = 1;
|
|
1548
|
+
var UNAUTHORIZED_EXIT_CODE = 2;
|
|
1549
|
+
var NETWORK_ERROR_EXIT_CODE = 3;
|
|
1550
|
+
var INVALID_ARGUMENTS_EXIT_CODE = 4;
|
|
1551
|
+
var GENERAL_FAILURE_EXIT_CODE = 5;
|
|
1552
|
+
var EXIT_CODES = [
|
|
1553
|
+
SUCCESS_EXIT_CODE,
|
|
1554
|
+
ZERO_BUCKETS_EXIT_CODE,
|
|
1555
|
+
UNAUTHORIZED_EXIT_CODE,
|
|
1556
|
+
NETWORK_ERROR_EXIT_CODE,
|
|
1557
|
+
INVALID_ARGUMENTS_EXIT_CODE,
|
|
1558
|
+
GENERAL_FAILURE_EXIT_CODE
|
|
1559
|
+
];
|
|
1560
|
+
var EXIT = {
|
|
1344
1561
|
success: SUCCESS_EXIT_CODE,
|
|
1345
1562
|
zeroBucketsButSourcesExist: ZERO_BUCKETS_EXIT_CODE,
|
|
1346
1563
|
unauthorized: UNAUTHORIZED_EXIT_CODE,
|
|
@@ -1348,10 +1565,10 @@ var EXIT = {
|
|
|
1348
1565
|
invalidArguments: INVALID_ARGUMENTS_EXIT_CODE,
|
|
1349
1566
|
generalFailure: GENERAL_FAILURE_EXIT_CODE
|
|
1350
1567
|
};
|
|
1351
|
-
var SyncError = class extends
|
|
1352
|
-
message:
|
|
1353
|
-
exitCode:
|
|
1354
|
-
cause:
|
|
1568
|
+
var SyncError = class extends Schema22.TaggedErrorClass()("SyncError", {
|
|
1569
|
+
message: Schema22.String,
|
|
1570
|
+
exitCode: Schema22.Literals(EXIT_CODES),
|
|
1571
|
+
cause: Schema22.optionalKey(Schema22.Defect())
|
|
1355
1572
|
}) {
|
|
1356
1573
|
constructor(exitCode, message, options = {}) {
|
|
1357
1574
|
super(
|
|
@@ -1384,24 +1601,25 @@ function isNetworkError(err) {
|
|
|
1384
1601
|
if (code && NETWORK_ERROR_CODES.includes(code)) {
|
|
1385
1602
|
return true;
|
|
1386
1603
|
}
|
|
1387
|
-
const
|
|
1604
|
+
const candidateMessage = err.message;
|
|
1605
|
+
const message = typeof candidateMessage === "string" ? candidateMessage : "";
|
|
1388
1606
|
return NETWORK_ERROR_PATTERN.test(message);
|
|
1389
1607
|
}
|
|
1390
1608
|
|
|
1391
1609
|
// src/sync/parser-collection.ts
|
|
1392
|
-
import { Context as
|
|
1610
|
+
import { Context as Context7, Effect as Effect8, Layer as Layer4, Schema as Schema23, Stream as Stream3 } from "effect";
|
|
1393
1611
|
var SCAN_PROGRESS_SESSION_STEP = 500;
|
|
1394
1612
|
var ParserCollectionErrorFields = {
|
|
1395
|
-
message:
|
|
1396
|
-
source:
|
|
1397
|
-
cause:
|
|
1613
|
+
message: Schema23.String,
|
|
1614
|
+
source: Schema23.String,
|
|
1615
|
+
cause: Schema23.optionalKey(Schema23.Defect())
|
|
1398
1616
|
};
|
|
1399
|
-
var ParserCollectionError = class extends
|
|
1617
|
+
var ParserCollectionError = class extends Schema23.TaggedErrorClass()(
|
|
1400
1618
|
"ParserCollectionError",
|
|
1401
1619
|
ParserCollectionErrorFields
|
|
1402
1620
|
) {
|
|
1403
1621
|
};
|
|
1404
|
-
var ParserCollection = class extends
|
|
1622
|
+
var ParserCollection = class extends Context7.Service()(
|
|
1405
1623
|
"@oopst/cli/ParserCollection"
|
|
1406
1624
|
) {
|
|
1407
1625
|
};
|
|
@@ -1524,6 +1742,7 @@ function runSyncEffect(options) {
|
|
|
1524
1742
|
function runSyncProgram(options) {
|
|
1525
1743
|
return Effect9.gen(function* () {
|
|
1526
1744
|
yield* validateRunSyncOptions(options);
|
|
1745
|
+
const replacement = yield* validateUsageReplacement(options.replacement, options.harness);
|
|
1527
1746
|
const client = yield* SyncRpcClient;
|
|
1528
1747
|
const parserCollection = yield* ParserCollection;
|
|
1529
1748
|
const color = options.color === true;
|
|
@@ -1532,46 +1751,100 @@ function runSyncProgram(options) {
|
|
|
1532
1751
|
return watermarkResult.result;
|
|
1533
1752
|
}
|
|
1534
1753
|
const { sessionKeySalt, watermark } = watermarkResult.value;
|
|
1535
|
-
const cutoff = new Date((watermark?.getTime() ?? 0) - options.lookbackDays * MS_PER_DAY2);
|
|
1754
|
+
const cutoff = replacement ? /* @__PURE__ */ new Date(0) : new Date((watermark?.getTime() ?? 0) - options.lookbackDays * MS_PER_DAY2);
|
|
1536
1755
|
if (options.verbose) {
|
|
1537
1756
|
yield* logInfo2(options.logger, `watermark=${watermark?.toISOString() ?? "null"} cutoff=${cutoff.toISOString()}`);
|
|
1538
1757
|
}
|
|
1539
1758
|
const parsers = yield* Effect9.sync(() => options.parserFactory({ cutoff, device: options.device }));
|
|
1540
|
-
const sources = selectSources(options.harness, parsers);
|
|
1759
|
+
const sources = selectSources(options.harness, parsers, replacement?.harnesses);
|
|
1541
1760
|
const parserStream = yield* parserCollection.stream({
|
|
1542
1761
|
sources,
|
|
1543
1762
|
logger: options.logger,
|
|
1544
1763
|
color
|
|
1545
1764
|
});
|
|
1546
|
-
const usageBatches = usageBatchStream(parserStream, sessionKeySalt);
|
|
1765
|
+
const usageBatches = usageBatchStream(parserStream, sessionKeySalt, replacement?.aggregationRange);
|
|
1766
|
+
if (replacement) {
|
|
1767
|
+
return yield* runReplacementUsage({
|
|
1768
|
+
options,
|
|
1769
|
+
client,
|
|
1770
|
+
color,
|
|
1771
|
+
replacement,
|
|
1772
|
+
usageBatches
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
return yield* runOrdinaryUsage(options, client, color, usageBatches);
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
function runReplacementUsage(input) {
|
|
1779
|
+
return Effect9.gen(function* () {
|
|
1780
|
+
const { client, color, options, replacement, usageBatches } = input;
|
|
1781
|
+
const batchesResult = yield* recoverSyncFailure(usageBatches.pipe(Stream4.runCollect), options.logger);
|
|
1782
|
+
if (batchesResult._tag === "SyncFailure") {
|
|
1783
|
+
return batchesResult.result;
|
|
1784
|
+
}
|
|
1785
|
+
const batches = batchesResult.value;
|
|
1786
|
+
const plan = summarizeUsageBatchArray(batches);
|
|
1547
1787
|
if (options.dryRun) {
|
|
1548
|
-
const planResult = yield* recoverSyncFailure(summarizeUsageBatchStream(usageBatches), options.logger);
|
|
1549
|
-
if (planResult._tag === "SyncFailure") {
|
|
1550
|
-
return planResult.result;
|
|
1551
|
-
}
|
|
1552
|
-
const plan2 = planResult.value;
|
|
1553
|
-
const dryRunResult = yield* finishDryRun(options, plan2, color);
|
|
1554
|
-
if (dryRunResult) {
|
|
1555
|
-
return dryRunResult;
|
|
1556
|
-
}
|
|
1557
1788
|
yield* logInfo2(
|
|
1558
1789
|
options.logger,
|
|
1559
|
-
|
|
1790
|
+
`${dryRunPrefix(color)} atomic replacement would stage ${usageRecordText(plan.bucketCount)} and ${localSessionText2(plan.sessionCount)} in ${uploadRequestText(plan.batchCount)}.`
|
|
1560
1791
|
);
|
|
1561
|
-
const dryRunSummary = `${dryRunPrefix(color)} ${usageRecordText(plan2.bucketCount)} from ${localSessionText2(plan2.sessionCount)} would be uploaded in ${uploadRequestText(plan2.batchCount)}.`;
|
|
1562
|
-
yield* logInfo2(options.logger, dryRunSummary);
|
|
1563
1792
|
yield* logInfo2(options.logger, muted("No usage data was uploaded.", color));
|
|
1564
|
-
return
|
|
1565
|
-
exitCode: EXIT.success,
|
|
1566
|
-
batchesUploaded: plan2.batchCount,
|
|
1567
|
-
bucketsUploaded: plan2.bucketCount,
|
|
1568
|
-
sessionsUploaded: plan2.sessionCount
|
|
1569
|
-
};
|
|
1793
|
+
return resultFromPlan(plan);
|
|
1570
1794
|
}
|
|
1795
|
+
const uploadResult = yield* recoverSyncFailure(
|
|
1796
|
+
uploadUsageReplacement({
|
|
1797
|
+
batches,
|
|
1798
|
+
client,
|
|
1799
|
+
cliVersion: options.cliVersion,
|
|
1800
|
+
color,
|
|
1801
|
+
device: options.device,
|
|
1802
|
+
logger: options.logger,
|
|
1803
|
+
replacement
|
|
1804
|
+
}),
|
|
1805
|
+
options.logger
|
|
1806
|
+
);
|
|
1807
|
+
if (uploadResult._tag === "SyncFailure") {
|
|
1808
|
+
return uploadResult.result;
|
|
1809
|
+
}
|
|
1810
|
+
yield* logInfo2(
|
|
1811
|
+
options.logger,
|
|
1812
|
+
`${successPrefix(color)} atomically replaced ${usageRecordText(plan.bucketCount)} and ${localSessionText2(plan.sessionCount)}.`
|
|
1813
|
+
);
|
|
1814
|
+
return uploadResult.value;
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
function runOrdinaryUsage(options, client, color, usageBatches) {
|
|
1818
|
+
return options.dryRun ? runOrdinaryDryRun(options, color, usageBatches) : uploadOrdinaryUsage(options, client, color, usageBatches);
|
|
1819
|
+
}
|
|
1820
|
+
function runOrdinaryDryRun(options, color, usageBatches) {
|
|
1821
|
+
return Effect9.gen(function* () {
|
|
1822
|
+
const planResult = yield* recoverSyncFailure(summarizeUsageBatchStream(usageBatches), options.logger);
|
|
1823
|
+
if (planResult._tag === "SyncFailure") {
|
|
1824
|
+
return planResult.result;
|
|
1825
|
+
}
|
|
1826
|
+
const plan = planResult.value;
|
|
1827
|
+
const earlyResult = yield* finishDryRun(options, plan, color);
|
|
1828
|
+
if (earlyResult) {
|
|
1829
|
+
return earlyResult;
|
|
1830
|
+
}
|
|
1831
|
+
yield* logInfo2(
|
|
1832
|
+
options.logger,
|
|
1833
|
+
phase2(`Aggregated usage by hour and model: ${usageRecordText(plan.bucketCount)} ready.`, color)
|
|
1834
|
+
);
|
|
1835
|
+
const summary = `${dryRunPrefix(color)} ${usageRecordText(plan.bucketCount)} from ${localSessionText2(plan.sessionCount)} would be uploaded in ${uploadRequestText(plan.batchCount)}.`;
|
|
1836
|
+
yield* logInfo2(options.logger, summary);
|
|
1837
|
+
yield* logInfo2(options.logger, muted("No usage data was uploaded.", color));
|
|
1838
|
+
return resultFromPlan(plan);
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
function uploadOrdinaryUsage(options, client, color, usageBatches) {
|
|
1842
|
+
return Effect9.gen(function* () {
|
|
1571
1843
|
const uploadResult = yield* recoverSyncFailure(
|
|
1572
1844
|
uploadUsageBatchStream({
|
|
1573
1845
|
batches: usageBatches,
|
|
1574
1846
|
client,
|
|
1847
|
+
cliVersion: options.cliVersion,
|
|
1575
1848
|
color,
|
|
1576
1849
|
device: options.device,
|
|
1577
1850
|
logger: options.logger
|
|
@@ -1597,8 +1870,10 @@ function runSyncProgram(options) {
|
|
|
1597
1870
|
return emptySuccessResult();
|
|
1598
1871
|
}
|
|
1599
1872
|
const verboseUploadSuffix = options.verbose ? ` in ${uploadRequestText(totals.batchesUploaded)}` : "";
|
|
1600
|
-
|
|
1601
|
-
|
|
1873
|
+
yield* logInfo2(
|
|
1874
|
+
options.logger,
|
|
1875
|
+
`${successPrefix(color)} uploaded ${usageRecordText(totals.bucketsUploaded)} from ${localSessionText2(totals.sessionsUploaded)}${verboseUploadSuffix}.`
|
|
1876
|
+
);
|
|
1602
1877
|
return {
|
|
1603
1878
|
exitCode: EXIT.success,
|
|
1604
1879
|
batchesUploaded: totals.batchesUploaded,
|
|
@@ -1607,6 +1882,14 @@ function runSyncProgram(options) {
|
|
|
1607
1882
|
};
|
|
1608
1883
|
});
|
|
1609
1884
|
}
|
|
1885
|
+
function resultFromPlan(plan) {
|
|
1886
|
+
return {
|
|
1887
|
+
exitCode: EXIT.success,
|
|
1888
|
+
batchesUploaded: plan.batchCount,
|
|
1889
|
+
bucketsUploaded: plan.bucketCount,
|
|
1890
|
+
sessionsUploaded: plan.sessionCount
|
|
1891
|
+
};
|
|
1892
|
+
}
|
|
1610
1893
|
function validateRunSyncOptions(options) {
|
|
1611
1894
|
return Effect9.gen(function* () {
|
|
1612
1895
|
if (!options.token) {
|
|
@@ -1617,6 +1900,45 @@ function validateRunSyncOptions(options) {
|
|
|
1617
1900
|
}
|
|
1618
1901
|
});
|
|
1619
1902
|
}
|
|
1903
|
+
function validateUsageReplacement(replacement, harness) {
|
|
1904
|
+
if (!replacement) {
|
|
1905
|
+
return Effect9.succeed(void 0);
|
|
1906
|
+
}
|
|
1907
|
+
return Effect9.try({
|
|
1908
|
+
try: () => {
|
|
1909
|
+
if (harness !== void 0) {
|
|
1910
|
+
throw new SyncError(EXIT.invalidArguments, "--harness cannot be combined with replacement harnesses");
|
|
1911
|
+
}
|
|
1912
|
+
const start = parseUtcHour("replacement start", replacement.startHourUtc);
|
|
1913
|
+
const end = parseUtcHour("replacement end", replacement.endHourUtc);
|
|
1914
|
+
if (end.getTime() <= start.getTime()) {
|
|
1915
|
+
throw new SyncError(EXIT.invalidArguments, "replacement end must be later than replacement start");
|
|
1916
|
+
}
|
|
1917
|
+
const harnesses = [...new Set(replacement.harnesses)].sort();
|
|
1918
|
+
if (harnesses.length === 0 || harnesses.length !== replacement.harnesses.length) {
|
|
1919
|
+
throw new SyncError(EXIT.invalidArguments, "replacement harnesses must be a non-empty set without duplicates");
|
|
1920
|
+
}
|
|
1921
|
+
return {
|
|
1922
|
+
startHourUtc: start.toISOString(),
|
|
1923
|
+
endHourUtc: end.toISOString(),
|
|
1924
|
+
harnesses,
|
|
1925
|
+
aggregationRange: {
|
|
1926
|
+
startHourUtc: start,
|
|
1927
|
+
endHourUtc: end,
|
|
1928
|
+
harnesses: new Set(harnesses)
|
|
1929
|
+
}
|
|
1930
|
+
};
|
|
1931
|
+
},
|
|
1932
|
+
catch: (error) => error instanceof SyncError ? error : new SyncError(EXIT.invalidArguments, "replacement bounds must be exact UTC hours")
|
|
1933
|
+
});
|
|
1934
|
+
}
|
|
1935
|
+
function parseUtcHour(field, value) {
|
|
1936
|
+
const parsed = new Date(value);
|
|
1937
|
+
if (Number.isNaN(parsed.getTime()) || parsed.toISOString() !== value || parsed.getUTCMinutes() !== 0 || parsed.getUTCSeconds() !== 0 || parsed.getUTCMilliseconds() !== 0) {
|
|
1938
|
+
throw new SyncError(EXIT.invalidArguments, `${field} must be an ISO UTC timestamp aligned to an hour`);
|
|
1939
|
+
}
|
|
1940
|
+
return parsed;
|
|
1941
|
+
}
|
|
1620
1942
|
function fetchWatermark(options, client, color) {
|
|
1621
1943
|
return Effect9.gen(function* () {
|
|
1622
1944
|
yield* logInfo2(options.logger, phase2("Checking server sync point...", color));
|
|
@@ -1627,29 +1949,24 @@ function fetchWatermark(options, client, color) {
|
|
|
1627
1949
|
};
|
|
1628
1950
|
});
|
|
1629
1951
|
}
|
|
1630
|
-
function selectSources(harness, parsers) {
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
sources.push({ label: "Hermes", source: parsers.hermes });
|
|
1643
|
-
}
|
|
1644
|
-
if (harness === void 0 || harness === "opencode") {
|
|
1645
|
-
sources.push({ label: "OpenCode", source: parsers.opencode });
|
|
1646
|
-
}
|
|
1647
|
-
return sources;
|
|
1952
|
+
function selectSources(harness, parsers, replacementHarnesses) {
|
|
1953
|
+
const replacementSet = replacementHarnesses ? new Set(replacementHarnesses) : void 0;
|
|
1954
|
+
const candidates = [
|
|
1955
|
+
{ harness: "codex", label: "Codex", source: parsers.codex },
|
|
1956
|
+
{ harness: "claude_code", label: "Claude Code", source: parsers.claudeCode },
|
|
1957
|
+
{ harness: "pi", label: "Pi", source: parsers.pi },
|
|
1958
|
+
{ harness: "hermes", label: "Hermes", source: parsers.hermes },
|
|
1959
|
+
{ harness: "opencode", label: "OpenCode", source: parsers.opencode }
|
|
1960
|
+
];
|
|
1961
|
+
return candidates.filter(
|
|
1962
|
+
(candidate) => replacementSet ? replacementSet.has(candidate.harness) : harness === void 0 || harness === candidate.harness
|
|
1963
|
+
).map(({ label, source }) => ({ label, source }));
|
|
1648
1964
|
}
|
|
1649
|
-
function usageBatchStream(source, sessionKeySalt) {
|
|
1965
|
+
function usageBatchStream(source, sessionKeySalt, replacementRange) {
|
|
1650
1966
|
return aggregateUsageStream(source, {
|
|
1651
1967
|
batchSize: SYNC_BATCH_SIZE,
|
|
1652
|
-
sessionKeySalt
|
|
1968
|
+
sessionKeySalt,
|
|
1969
|
+
replacementRange
|
|
1653
1970
|
}).pipe(Stream4.filter((batch) => batch.buckets.length > 0 || batch.sessions.length > 0));
|
|
1654
1971
|
}
|
|
1655
1972
|
var summarizeUsageBatchStream = Effect9.fn("RunSync.summarizeUsageBatchStream")(
|
|
@@ -1723,6 +2040,7 @@ var uploadUsageBatchStream = Effect9.fn("RunSync.uploadUsageBatchStream")(
|
|
|
1723
2040
|
);
|
|
1724
2041
|
const result = yield* options.client.upsertBuckets({
|
|
1725
2042
|
device: options.device,
|
|
2043
|
+
cliVersion: options.cliVersion,
|
|
1726
2044
|
buckets: batch.buckets,
|
|
1727
2045
|
sessions: batch.sessions
|
|
1728
2046
|
});
|
|
@@ -1738,6 +2056,52 @@ var uploadUsageBatchStream = Effect9.fn("RunSync.uploadUsageBatchStream")(
|
|
|
1738
2056
|
return { plan, totals };
|
|
1739
2057
|
})
|
|
1740
2058
|
);
|
|
2059
|
+
var uploadUsageReplacement = Effect9.fn("RunSync.uploadUsageReplacement")(
|
|
2060
|
+
(options) => Effect9.gen(function* () {
|
|
2061
|
+
yield* logInfo2(options.logger, phase2("Creating atomic replacement scope...", options.color));
|
|
2062
|
+
const created = yield* options.client.createUsageReplacement({
|
|
2063
|
+
device: options.device,
|
|
2064
|
+
cliVersion: options.cliVersion,
|
|
2065
|
+
startHourUtc: options.replacement.startHourUtc,
|
|
2066
|
+
endHourUtc: options.replacement.endHourUtc,
|
|
2067
|
+
harnesses: options.replacement.harnesses
|
|
2068
|
+
});
|
|
2069
|
+
const buckets = options.batches.flatMap((batch) => batch.buckets);
|
|
2070
|
+
const sessions = options.batches.flatMap((batch) => batch.sessions);
|
|
2071
|
+
for (const [batchIndex, batch] of options.batches.entries()) {
|
|
2072
|
+
yield* logInfo2(
|
|
2073
|
+
options.logger,
|
|
2074
|
+
phase2(`Staging corrected usage... ${batchIndex + 1}/${options.batches.length}`, options.color)
|
|
2075
|
+
);
|
|
2076
|
+
yield* options.client.stageUsageReplacementBatch({
|
|
2077
|
+
device: options.device,
|
|
2078
|
+
replacementId: created.replacementId,
|
|
2079
|
+
batchIndex,
|
|
2080
|
+
batchDigest: usageReplacementDigest(batch),
|
|
2081
|
+
buckets: batch.buckets,
|
|
2082
|
+
sessions: batch.sessions
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
yield* logInfo2(options.logger, phase2("Finalizing corrected usage atomically...", options.color));
|
|
2086
|
+
yield* options.client.finalizeUsageReplacement({
|
|
2087
|
+
device: options.device,
|
|
2088
|
+
replacementId: created.replacementId,
|
|
2089
|
+
expectedBatchCount: options.batches.length,
|
|
2090
|
+
expectedBucketCount: buckets.length,
|
|
2091
|
+
expectedSessionCount: sessions.length,
|
|
2092
|
+
digest: usageReplacementDigest({ buckets, sessions })
|
|
2093
|
+
});
|
|
2094
|
+
return {
|
|
2095
|
+
exitCode: EXIT.success,
|
|
2096
|
+
batchesUploaded: options.batches.length,
|
|
2097
|
+
bucketsUploaded: buckets.length,
|
|
2098
|
+
sessionsUploaded: sessions.length
|
|
2099
|
+
};
|
|
2100
|
+
})
|
|
2101
|
+
);
|
|
2102
|
+
function usageReplacementDigest(facts) {
|
|
2103
|
+
return createHash("sha256").update(canonicalReplacementPayload(facts)).digest("hex");
|
|
2104
|
+
}
|
|
1741
2105
|
function recoverSyncFailure(effect, logger) {
|
|
1742
2106
|
return Effect9.gen(function* () {
|
|
1743
2107
|
return syncStepSuccess(yield* effect);
|
|
@@ -1812,7 +2176,7 @@ function uploadRequestText(value) {
|
|
|
1812
2176
|
}
|
|
1813
2177
|
|
|
1814
2178
|
// src/services/sync-program.ts
|
|
1815
|
-
var SyncProgram = class extends
|
|
2179
|
+
var SyncProgram = class extends Context8.Service()("@oopst/cli/SyncProgram") {
|
|
1816
2180
|
};
|
|
1817
2181
|
var SyncProgramLive = Layer5.effect(
|
|
1818
2182
|
SyncProgram,
|
|
@@ -1833,49 +2197,60 @@ function runSyncProgram2(options) {
|
|
|
1833
2197
|
import { dirname, join as join3 } from "path";
|
|
1834
2198
|
import { createInterface } from "readline/promises";
|
|
1835
2199
|
import { NodeCrypto, NodeFileSystem as NodeFileSystem3 } from "@effect/platform-node";
|
|
1836
|
-
import {
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
var
|
|
1841
|
-
|
|
1842
|
-
var LINE_SPLIT_PATTERN = /\r?\n/u;
|
|
1843
|
-
var SetupError = class extends Schema18.TaggedErrorClass()("SetupError", {
|
|
1844
|
-
message: Schema18.String,
|
|
1845
|
-
exitCode: Schema18.Number,
|
|
1846
|
-
cause: Schema18.optionalKey(Schema18.Defect())
|
|
1847
|
-
}) {
|
|
1848
|
-
constructor(message, options = {}) {
|
|
1849
|
-
const exitCode = options.exitCode ?? EXIT.generalFailure;
|
|
1850
|
-
super(
|
|
1851
|
-
options.cause === void 0 ? { message, exitCode } : { message, exitCode, cause: options.cause }
|
|
1852
|
-
);
|
|
1853
|
-
}
|
|
1854
|
-
};
|
|
1855
|
-
var SetupPromptService = class extends Context7.Service()(
|
|
1856
|
-
"@oopst/cli/SetupPromptService"
|
|
2200
|
+
import { Crypto, Effect as Effect11, FileSystem as FileSystem4, Layer as Layer6, Schema as Schema24 } from "effect";
|
|
2201
|
+
|
|
2202
|
+
// src/services/setup-cron-services.ts
|
|
2203
|
+
import { Context as Context9 } from "effect";
|
|
2204
|
+
var SetupCronCommandService = class extends Context9.Service()(
|
|
2205
|
+
"@oopst/cli/SetupCronCommandService"
|
|
1857
2206
|
) {
|
|
1858
2207
|
};
|
|
1859
|
-
var
|
|
1860
|
-
"@oopst/cli/
|
|
2208
|
+
var SetupRandomService = class extends Context9.Service()(
|
|
2209
|
+
"@oopst/cli/SetupRandomService"
|
|
1861
2210
|
) {
|
|
1862
2211
|
};
|
|
1863
|
-
|
|
1864
|
-
|
|
2212
|
+
|
|
2213
|
+
// src/services/setup-prompt-services.ts
|
|
2214
|
+
import { Context as Context10 } from "effect";
|
|
2215
|
+
var SetupPromptService = class extends Context10.Service()(
|
|
2216
|
+
"@oopst/cli/SetupPromptService"
|
|
1865
2217
|
) {
|
|
1866
2218
|
};
|
|
1867
|
-
var
|
|
1868
|
-
"@oopst/cli/
|
|
2219
|
+
var SetupConfigFileService = class extends Context10.Service()(
|
|
2220
|
+
"@oopst/cli/SetupConfigFileService"
|
|
1869
2221
|
) {
|
|
1870
2222
|
};
|
|
1871
|
-
|
|
2223
|
+
|
|
2224
|
+
// src/services/setup-runtime-services.ts
|
|
2225
|
+
import { Context as Context11 } from "effect";
|
|
2226
|
+
var SetupOutputService = class extends Context11.Service()(
|
|
1872
2227
|
"@oopst/cli/SetupOutputService"
|
|
1873
2228
|
) {
|
|
1874
2229
|
};
|
|
1875
|
-
var SetupEnvironmentService = class extends
|
|
2230
|
+
var SetupEnvironmentService = class extends Context11.Service()(
|
|
1876
2231
|
"@oopst/cli/SetupEnvironmentService"
|
|
1877
2232
|
) {
|
|
1878
2233
|
};
|
|
2234
|
+
|
|
2235
|
+
// src/setup.ts
|
|
2236
|
+
var CRON_START_MARKER = "# OopsT sync start";
|
|
2237
|
+
var CRON_END_MARKER = "# OopsT sync end";
|
|
2238
|
+
var DEFAULT_CRON_PATHS = ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"];
|
|
2239
|
+
var DEFAULT_PACKAGE_NAME = "@cookielab.io/oopst";
|
|
2240
|
+
var CONFIG_FILE_MODE = 384;
|
|
2241
|
+
var LINE_SPLIT_PATTERN = /\r?\n/u;
|
|
2242
|
+
var SetupError = class extends Schema24.TaggedErrorClass()("SetupError", {
|
|
2243
|
+
message: Schema24.String,
|
|
2244
|
+
exitCode: Schema24.Number,
|
|
2245
|
+
cause: Schema24.optionalKey(Schema24.Defect())
|
|
2246
|
+
}) {
|
|
2247
|
+
constructor(message, options = {}) {
|
|
2248
|
+
const exitCode = options.exitCode ?? EXIT.generalFailure;
|
|
2249
|
+
super(
|
|
2250
|
+
options.cause === void 0 ? { message, exitCode } : { message, exitCode, cause: options.cause }
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2253
|
+
};
|
|
1879
2254
|
function errorMessage3(cause) {
|
|
1880
2255
|
return cause instanceof Error ? cause.message : String(cause);
|
|
1881
2256
|
}
|
|
@@ -2065,7 +2440,11 @@ function commandFailureOptions(result) {
|
|
|
2065
2440
|
return result.error === void 0 ? {} : { cause: result.error };
|
|
2066
2441
|
}
|
|
2067
2442
|
function commandFailureReason(result, fallback) {
|
|
2068
|
-
|
|
2443
|
+
if (result.error !== void 0) {
|
|
2444
|
+
return result.error.message;
|
|
2445
|
+
}
|
|
2446
|
+
const stderr2 = result.stderr.trim();
|
|
2447
|
+
return stderr2.length > 0 ? stderr2 : fallback;
|
|
2069
2448
|
}
|
|
2070
2449
|
function existingCrontabFromResult(result) {
|
|
2071
2450
|
if (result.status === 0) {
|
|
@@ -2270,29 +2649,29 @@ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join8, matchesG
|
|
|
2270
2649
|
|
|
2271
2650
|
// ../../packages/parsers/src/claude/index.ts
|
|
2272
2651
|
import { basename, dirname as dirname2 } from "path";
|
|
2273
|
-
import { Effect as
|
|
2652
|
+
import { Effect as Effect18, Stream as Stream11 } from "effect";
|
|
2274
2653
|
|
|
2275
2654
|
// ../../packages/parsers/src/config.ts
|
|
2276
2655
|
import { homedir as homedir2 } from "os";
|
|
2277
2656
|
import { isAbsolute as isAbsolute2, join as join4 } from "path";
|
|
2278
|
-
import
|
|
2279
|
-
import { Config as Config2, ConfigProvider as ConfigProvider2, Effect as Effect12, Schema as
|
|
2280
|
-
var runtimeEnv2 =
|
|
2281
|
-
var RawParserEnvSchema =
|
|
2657
|
+
import { env as processEnv2 } from "process";
|
|
2658
|
+
import { Config as Config2, ConfigProvider as ConfigProvider2, Effect as Effect12, Schema as Schema25 } from "effect";
|
|
2659
|
+
var runtimeEnv2 = processEnv2;
|
|
2660
|
+
var RawParserEnvSchema = Schema25.Struct({
|
|
2282
2661
|
// biome-ignore lint/style/useNamingConvention: ENV vars are SCREAMING_SNAKE_CASE by convention
|
|
2283
|
-
CLAUDE_CONFIG_DIR:
|
|
2662
|
+
CLAUDE_CONFIG_DIR: Schema25.optional(Schema25.Trim),
|
|
2284
2663
|
// biome-ignore lint/style/useNamingConvention: ENV vars are SCREAMING_SNAKE_CASE by convention
|
|
2285
|
-
HERMES_CODING_AGENT_DIR:
|
|
2664
|
+
HERMES_CODING_AGENT_DIR: Schema25.optional(Schema25.Trim),
|
|
2286
2665
|
// biome-ignore lint/style/useNamingConvention: ENV vars are SCREAMING_SNAKE_CASE by convention
|
|
2287
|
-
HERMES_CODING_AGENT_SESSION_DIR:
|
|
2666
|
+
HERMES_CODING_AGENT_SESSION_DIR: Schema25.optional(Schema25.Trim),
|
|
2288
2667
|
// biome-ignore lint/style/useNamingConvention: ENV vars are SCREAMING_SNAKE_CASE by convention
|
|
2289
|
-
OPENCODE_DATA_DIR:
|
|
2668
|
+
OPENCODE_DATA_DIR: Schema25.optional(Schema25.Trim),
|
|
2290
2669
|
// biome-ignore lint/style/useNamingConvention: ENV vars are SCREAMING_SNAKE_CASE by convention
|
|
2291
|
-
OPENCODE_DB:
|
|
2670
|
+
OPENCODE_DB: Schema25.optional(Schema25.Trim),
|
|
2292
2671
|
// biome-ignore lint/style/useNamingConvention: ENV vars are SCREAMING_SNAKE_CASE by convention
|
|
2293
|
-
PI_CODING_AGENT_DIR:
|
|
2672
|
+
PI_CODING_AGENT_DIR: Schema25.optional(Schema25.Trim),
|
|
2294
2673
|
// biome-ignore lint/style/useNamingConvention: ENV vars are SCREAMING_SNAKE_CASE by convention
|
|
2295
|
-
PI_CODING_AGENT_SESSION_DIR:
|
|
2674
|
+
PI_CODING_AGENT_SESSION_DIR: Schema25.optional(Schema25.Trim)
|
|
2296
2675
|
});
|
|
2297
2676
|
var parserEnvConfig = Config2.schema(RawParserEnvSchema);
|
|
2298
2677
|
var emptyToUndefined = (value) => value === void 0 || value.length === 0 ? void 0 : value;
|
|
@@ -2346,18 +2725,18 @@ function resolveOpenCodeRoot(config = loadParserConfig(), homeDir = readParserHo
|
|
|
2346
2725
|
// ../../packages/parsers/src/effect/file-walker.ts
|
|
2347
2726
|
import { join as join5 } from "path";
|
|
2348
2727
|
import { NodeFileSystem as NodeFileSystem4 } from "@effect/platform-node";
|
|
2349
|
-
import { Context as
|
|
2350
|
-
var FileWalkerFilesystemError = class extends
|
|
2728
|
+
import { Context as Context12, Effect as Effect13, FileSystem as FileSystem5, Layer as Layer7, Option as Option2, Schema as Schema26, Stream as Stream5 } from "effect";
|
|
2729
|
+
var FileWalkerFilesystemError = class extends Schema26.TaggedErrorClass()(
|
|
2351
2730
|
"FileWalkerFilesystemError",
|
|
2352
2731
|
{
|
|
2353
|
-
message:
|
|
2354
|
-
operation:
|
|
2355
|
-
path:
|
|
2356
|
-
cause:
|
|
2732
|
+
message: Schema26.String,
|
|
2733
|
+
operation: Schema26.Literals(["readdir", "stat"]),
|
|
2734
|
+
path: Schema26.String,
|
|
2735
|
+
cause: Schema26.optionalKey(Schema26.Defect())
|
|
2357
2736
|
}
|
|
2358
2737
|
) {
|
|
2359
2738
|
};
|
|
2360
|
-
var FileWalker = class extends
|
|
2739
|
+
var FileWalker = class extends Context12.Service()("@oopst/parsers/FileWalker") {
|
|
2361
2740
|
};
|
|
2362
2741
|
var toFileWalkerFilesystemError = (operation, path) => (error) => makeFileWalkerFilesystemError(operation, path, error);
|
|
2363
2742
|
var makeFileWalkerFilesystemError = (operation, path, error) => new FileWalkerFilesystemError({
|
|
@@ -2490,27 +2869,199 @@ var stderrLogger = createWarningLogger(stderr);
|
|
|
2490
2869
|
|
|
2491
2870
|
// ../../packages/parsers/src/claude/bucketize.ts
|
|
2492
2871
|
import { createReadStream as createReadStream2 } from "fs";
|
|
2872
|
+
import { normalize as normalize3 } from "path";
|
|
2493
2873
|
import { createInterface as createInterface3 } from "readline";
|
|
2494
|
-
import { Cause as Cause2, Effect as
|
|
2874
|
+
import { Cause as Cause2, Effect as Effect17, Queue as Queue2, Stream as Stream10 } from "effect";
|
|
2495
2875
|
|
|
2496
2876
|
// ../../packages/parsers/src/codex/bucketize.ts
|
|
2497
|
-
import {
|
|
2498
|
-
import { createInterface as createInterface2 } from "readline";
|
|
2499
|
-
import { Cause, Effect as Effect15, Queue, Stream as Stream8 } from "effect";
|
|
2877
|
+
import { Effect as Effect16, Stream as Stream9 } from "effect";
|
|
2500
2878
|
|
|
2501
|
-
// ../../packages/parsers/src/
|
|
2502
|
-
|
|
2503
|
-
var
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
}) {
|
|
2879
|
+
// ../../packages/parsers/src/codex/counters.ts
|
|
2880
|
+
var SEEN_RAW_TOTALS_LIMIT = 64;
|
|
2881
|
+
var ZERO_CODEX_USAGE = {
|
|
2882
|
+
inputTokens: 0,
|
|
2883
|
+
outputTokens: 0,
|
|
2884
|
+
cacheReadTokens: 0
|
|
2508
2885
|
};
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2886
|
+
function normalizeCodexUsage(usage) {
|
|
2887
|
+
return {
|
|
2888
|
+
inputTokens: Math.max(0, usage.input_tokens ?? 0),
|
|
2889
|
+
outputTokens: Math.max(0, usage.output_tokens ?? 0),
|
|
2890
|
+
cacheReadTokens: Math.max(0, usage.cached_input_tokens ?? usage.cache_read_input_tokens ?? 0)
|
|
2891
|
+
};
|
|
2892
|
+
}
|
|
2893
|
+
function sameCodexUsage(a, b) {
|
|
2894
|
+
return a.inputTokens === b.inputTokens && a.outputTokens === b.outputTokens && a.cacheReadTokens === b.cacheReadTokens;
|
|
2895
|
+
}
|
|
2896
|
+
function positiveCodexUsage(usage) {
|
|
2897
|
+
return usage.inputTokens > 0 || usage.outputTokens > 0 || usage.cacheReadTokens > 0;
|
|
2898
|
+
}
|
|
2899
|
+
function addCodexUsage(a, b) {
|
|
2900
|
+
return {
|
|
2901
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
2902
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
2903
|
+
cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
function minCodexUsage(a, b) {
|
|
2907
|
+
return {
|
|
2908
|
+
inputTokens: Math.min(a.inputTokens, b.inputTokens),
|
|
2909
|
+
outputTokens: Math.min(a.outputTokens, b.outputTokens),
|
|
2910
|
+
cacheReadTokens: Math.min(a.cacheReadTokens, b.cacheReadTokens)
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
function maxCodexUsage(a, b) {
|
|
2914
|
+
if (a === void 0) {
|
|
2915
|
+
return b;
|
|
2916
|
+
}
|
|
2917
|
+
return {
|
|
2918
|
+
inputTokens: Math.max(a.inputTokens, b.inputTokens),
|
|
2919
|
+
outputTokens: Math.max(a.outputTokens, b.outputTokens),
|
|
2920
|
+
cacheReadTokens: Math.max(a.cacheReadTokens, b.cacheReadTokens)
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
function subtractCodexUsageFloorZero(current, baseline) {
|
|
2924
|
+
return {
|
|
2925
|
+
inputTokens: Math.max(0, current.inputTokens - baseline.inputTokens),
|
|
2926
|
+
outputTokens: Math.max(0, current.outputTokens - baseline.outputTokens),
|
|
2927
|
+
cacheReadTokens: Math.max(0, current.cacheReadTokens - baseline.cacheReadTokens)
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
function codexUsageContains(current, baseline) {
|
|
2931
|
+
return current.inputTokens >= baseline.inputTokens && current.outputTokens >= baseline.outputTokens && current.cacheReadTokens >= baseline.cacheReadTokens;
|
|
2932
|
+
}
|
|
2933
|
+
function codexUsageAtMost(current, ceiling) {
|
|
2934
|
+
return current.inputTokens <= ceiling.inputTokens && current.outputTokens <= ceiling.outputTokens && current.cacheReadTokens <= ceiling.cacheReadTokens;
|
|
2935
|
+
}
|
|
2936
|
+
function makeCodexContainmentState() {
|
|
2937
|
+
return {
|
|
2938
|
+
countedUsage: void 0,
|
|
2939
|
+
rawBaseline: void 0,
|
|
2940
|
+
watermark: void 0,
|
|
2941
|
+
seenRawTotals: [],
|
|
2942
|
+
sawDivergentTotals: false,
|
|
2943
|
+
sawInterleavedTotals: false,
|
|
2944
|
+
sawUnresolvedTotal: false
|
|
2945
|
+
};
|
|
2946
|
+
}
|
|
2947
|
+
function containedCodexDelta(watermark, counted, current) {
|
|
2948
|
+
const water = watermark ?? ZERO_CODEX_USAGE;
|
|
2949
|
+
const accepted = counted ?? ZERO_CODEX_USAGE;
|
|
2950
|
+
const component = (highWatermark, countedUsage, currentUsage) => currentUsage >= highWatermark ? Math.max(0, currentUsage - Math.max(highWatermark, countedUsage)) : Math.max(0, currentUsage - countedUsage);
|
|
2951
|
+
return {
|
|
2952
|
+
inputTokens: component(water.inputTokens, accepted.inputTokens, current.inputTokens),
|
|
2953
|
+
outputTokens: component(water.outputTokens, accepted.outputTokens, current.outputTokens),
|
|
2954
|
+
cacheReadTokens: component(water.cacheReadTokens, accepted.cacheReadTokens, current.cacheReadTokens)
|
|
2955
|
+
};
|
|
2956
|
+
}
|
|
2957
|
+
function divergentCodexDelta(rawBaseline, countedBaseline, current) {
|
|
2958
|
+
const raw = rawBaseline ?? ZERO_CODEX_USAGE;
|
|
2959
|
+
const counted = countedBaseline ?? ZERO_CODEX_USAGE;
|
|
2960
|
+
const component = (rawUsage, countedUsage, currentUsage) => currentUsage >= rawUsage ? Math.max(0, currentUsage - rawUsage) : Math.max(0, currentUsage - countedUsage);
|
|
2961
|
+
return {
|
|
2962
|
+
inputTokens: component(raw.inputTokens, counted.inputTokens, current.inputTokens),
|
|
2963
|
+
outputTokens: component(raw.outputTokens, counted.outputTokens, current.outputTokens),
|
|
2964
|
+
cacheReadTokens: component(raw.cacheReadTokens, counted.cacheReadTokens, current.cacheReadTokens)
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2967
|
+
function rememberCodexTotal(state, total) {
|
|
2968
|
+
state.watermark = maxCodexUsage(state.watermark, total);
|
|
2969
|
+
if (state.seenRawTotals.some((seen) => sameCodexUsage(seen, total))) {
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
state.seenRawTotals.push(total);
|
|
2973
|
+
if (state.seenRawTotals.length > SEEN_RAW_TOTALS_LIMIT) {
|
|
2974
|
+
state.seenRawTotals.splice(0, state.seenRawTotals.length - SEEN_RAW_TOTALS_LIMIT);
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
function applyCodexSnapshotWithLast(state, last, total, watermarkBaseline) {
|
|
2978
|
+
const base = state.countedUsage ?? ZERO_CODEX_USAGE;
|
|
2979
|
+
if (total === void 0) {
|
|
2980
|
+
state.countedUsage = addCodexUsage(base, last);
|
|
2981
|
+
state.rawBaseline = state.countedUsage;
|
|
2982
|
+
state.watermark = maxCodexUsage(state.watermark, state.countedUsage);
|
|
2983
|
+
return last;
|
|
2984
|
+
}
|
|
2985
|
+
let delta = last;
|
|
2986
|
+
if (state.sawInterleavedTotals) {
|
|
2987
|
+
delta = minCodexUsage(last, containedCodexDelta(watermarkBaseline, state.countedUsage, total));
|
|
2988
|
+
} else {
|
|
2989
|
+
const totalDelta = subtractCodexUsageFloorZero(total, watermarkBaseline ?? ZERO_CODEX_USAGE);
|
|
2990
|
+
const useTotalDelta = !state.sawDivergentTotals && watermarkBaseline !== void 0 && codexUsageContains(total, watermarkBaseline) && codexUsageAtMost(totalDelta, last);
|
|
2991
|
+
if (useTotalDelta) {
|
|
2992
|
+
delta = totalDelta;
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
state.countedUsage = addCodexUsage(base, delta);
|
|
2996
|
+
state.rawBaseline = total;
|
|
2997
|
+
state.sawDivergentTotals ||= !sameCodexUsage(total, state.countedUsage);
|
|
2998
|
+
return delta;
|
|
2999
|
+
}
|
|
3000
|
+
function applyCodexTotalOnlySnapshot(state, total, watermarkBaseline) {
|
|
3001
|
+
let delta;
|
|
3002
|
+
if (state.sawInterleavedTotals) {
|
|
3003
|
+
delta = containedCodexDelta(watermarkBaseline, state.countedUsage, total);
|
|
3004
|
+
} else if (state.sawDivergentTotals) {
|
|
3005
|
+
delta = divergentCodexDelta(watermarkBaseline, state.countedUsage, total);
|
|
3006
|
+
} else {
|
|
3007
|
+
delta = subtractCodexUsageFloorZero(total, watermarkBaseline ?? ZERO_CODEX_USAGE);
|
|
3008
|
+
}
|
|
3009
|
+
state.countedUsage = addCodexUsage(state.countedUsage ?? ZERO_CODEX_USAGE, delta);
|
|
3010
|
+
state.rawBaseline = total;
|
|
3011
|
+
state.sawDivergentTotals ||= !sameCodexUsage(total, state.countedUsage);
|
|
3012
|
+
return delta;
|
|
3013
|
+
}
|
|
3014
|
+
function applyCodexContainedSnapshot(state, last, total, options = {}) {
|
|
3015
|
+
if (total !== void 0 && state.seenRawTotals.some((seen) => sameCodexUsage(seen, total))) {
|
|
3016
|
+
return void 0;
|
|
3017
|
+
}
|
|
3018
|
+
if (total !== void 0 && state.watermark !== void 0 && !codexUsageContains(total, state.watermark)) {
|
|
3019
|
+
state.sawInterleavedTotals = true;
|
|
3020
|
+
}
|
|
3021
|
+
const watermarkBaseline = state.watermark ?? state.rawBaseline;
|
|
3022
|
+
const effectiveLast = total !== void 0 && options.totalOnlyBeforeInterleaving === true && !state.sawInterleavedTotals ? void 0 : last;
|
|
3023
|
+
let delta;
|
|
3024
|
+
if (effectiveLast !== void 0) {
|
|
3025
|
+
delta = applyCodexSnapshotWithLast(state, effectiveLast, total, watermarkBaseline);
|
|
3026
|
+
} else if (total !== void 0) {
|
|
3027
|
+
delta = applyCodexTotalOnlySnapshot(state, total, watermarkBaseline);
|
|
3028
|
+
}
|
|
3029
|
+
if (total !== void 0) {
|
|
3030
|
+
rememberCodexTotal(state, total);
|
|
3031
|
+
}
|
|
3032
|
+
return delta !== void 0 && positiveCodexUsage(delta) ? delta : void 0;
|
|
3033
|
+
}
|
|
3034
|
+
function applyCodexUnresolvedSnapshot(state, last, total) {
|
|
3035
|
+
if (total === void 0) {
|
|
3036
|
+
return applyCodexContainedSnapshot(state, last, void 0);
|
|
3037
|
+
}
|
|
3038
|
+
if (state.seenRawTotals.some((seen) => sameCodexUsage(seen, total))) {
|
|
3039
|
+
return void 0;
|
|
3040
|
+
}
|
|
3041
|
+
if (state.watermark !== void 0 && !codexUsageContains(total, state.watermark)) {
|
|
3042
|
+
state.sawInterleavedTotals = true;
|
|
3043
|
+
}
|
|
3044
|
+
const watermarkBaseline = state.watermark ?? state.rawBaseline;
|
|
3045
|
+
let delta;
|
|
3046
|
+
if (state.sawUnresolvedTotal && last !== void 0) {
|
|
3047
|
+
delta = minCodexUsage(last, subtractCodexUsageFloorZero(total, watermarkBaseline ?? ZERO_CODEX_USAGE));
|
|
3048
|
+
state.countedUsage = addCodexUsage(state.countedUsage ?? ZERO_CODEX_USAGE, delta);
|
|
3049
|
+
state.rawBaseline = state.countedUsage;
|
|
3050
|
+
}
|
|
3051
|
+
state.sawUnresolvedTotal = true;
|
|
3052
|
+
rememberCodexTotal(state, total);
|
|
3053
|
+
return delta !== void 0 && positiveCodexUsage(delta) ? delta : void 0;
|
|
3054
|
+
}
|
|
3055
|
+
function cumulativeCodexDelta(current, previous) {
|
|
3056
|
+
if (previous === void 0) {
|
|
3057
|
+
return current;
|
|
3058
|
+
}
|
|
3059
|
+
return {
|
|
3060
|
+
inputTokens: current.inputTokens < previous.inputTokens ? current.inputTokens : current.inputTokens - previous.inputTokens,
|
|
3061
|
+
outputTokens: current.outputTokens < previous.outputTokens ? current.outputTokens : current.outputTokens - previous.outputTokens,
|
|
3062
|
+
cacheReadTokens: current.cacheReadTokens < previous.cacheReadTokens ? current.cacheReadTokens : current.cacheReadTokens - previous.cacheReadTokens
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
2514
3065
|
|
|
2515
3066
|
// ../../packages/parsers/src/codex/events.ts
|
|
2516
3067
|
function isObject(value) {
|
|
@@ -2519,6 +3070,12 @@ function isObject(value) {
|
|
|
2519
3070
|
function isFiniteNumber(value) {
|
|
2520
3071
|
return typeof value === "number" && Number.isFinite(value);
|
|
2521
3072
|
}
|
|
3073
|
+
var MAX_CODEX_SOURCE_ID_LENGTH = 512;
|
|
3074
|
+
var MAX_CODEX_TIMESTAMP_LENGTH = 128;
|
|
3075
|
+
var MAX_CODEX_LOCAL_PATH_LENGTH = 4096;
|
|
3076
|
+
function isOptionalBoundedString(value, maxLength) {
|
|
3077
|
+
return value === void 0 || typeof value === "string" && value.length <= maxLength;
|
|
3078
|
+
}
|
|
2522
3079
|
function isCodexUsageCounters(value) {
|
|
2523
3080
|
if (!isObject(value)) {
|
|
2524
3081
|
return false;
|
|
@@ -2549,6 +3106,11 @@ function isCodexTokenCountEvent(value) {
|
|
|
2549
3106
|
if (payload["type"] !== "token_count") {
|
|
2550
3107
|
return false;
|
|
2551
3108
|
}
|
|
3109
|
+
for (const key6 of ["id", "turn_id", "turnId"]) {
|
|
3110
|
+
if (!isOptionalBoundedString(payload[key6], MAX_CODEX_SOURCE_ID_LENGTH)) {
|
|
3111
|
+
return false;
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
2552
3114
|
const info = payload["info"];
|
|
2553
3115
|
if (info !== void 0) {
|
|
2554
3116
|
if (!isObject(info)) {
|
|
@@ -2557,6 +3119,11 @@ function isCodexTokenCountEvent(value) {
|
|
|
2557
3119
|
if (info["model"] !== void 0 && typeof info["model"] !== "string") {
|
|
2558
3120
|
return false;
|
|
2559
3121
|
}
|
|
3122
|
+
for (const key6 of ["id", "turn_id", "turnId"]) {
|
|
3123
|
+
if (!isOptionalBoundedString(info[key6], MAX_CODEX_SOURCE_ID_LENGTH)) {
|
|
3124
|
+
return false;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
2560
3127
|
if (info["last_token_usage"] !== void 0 && !isCodexUsageCounters(info["last_token_usage"])) {
|
|
2561
3128
|
return false;
|
|
2562
3129
|
}
|
|
@@ -2572,6 +3139,28 @@ function isCodexTokenCountEvent(value) {
|
|
|
2572
3139
|
}
|
|
2573
3140
|
return true;
|
|
2574
3141
|
}
|
|
3142
|
+
function isCodexTaskStartedEvent(value) {
|
|
3143
|
+
if (!isObject(value) || value["type"] !== "event_msg" || typeof value["timestamp"] !== "string") {
|
|
3144
|
+
return false;
|
|
3145
|
+
}
|
|
3146
|
+
const payload = value["payload"];
|
|
3147
|
+
if (!isObject(payload) || payload["type"] !== "task_started") {
|
|
3148
|
+
return false;
|
|
3149
|
+
}
|
|
3150
|
+
for (const key6 of ["id", "turn_id", "turnId"]) {
|
|
3151
|
+
if (!isOptionalBoundedString(payload[key6], MAX_CODEX_SOURCE_ID_LENGTH)) {
|
|
3152
|
+
return false;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
const info = payload["info"];
|
|
3156
|
+
if (info === void 0) {
|
|
3157
|
+
return true;
|
|
3158
|
+
}
|
|
3159
|
+
if (!isObject(info)) {
|
|
3160
|
+
return false;
|
|
3161
|
+
}
|
|
3162
|
+
return ["id", "turn_id", "turnId"].every((key6) => isOptionalBoundedString(info[key6], MAX_CODEX_SOURCE_ID_LENGTH));
|
|
3163
|
+
}
|
|
2575
3164
|
function isCodexTurnContextEvent(value) {
|
|
2576
3165
|
if (!isObject(value)) {
|
|
2577
3166
|
return false;
|
|
@@ -2610,18 +3199,66 @@ function isCodexSessionMetaEvent(value) {
|
|
|
2610
3199
|
if (value["type"] !== "session_meta") {
|
|
2611
3200
|
return false;
|
|
2612
3201
|
}
|
|
2613
|
-
if (value["timestamp"]
|
|
3202
|
+
if (!isOptionalBoundedString(value["timestamp"], MAX_CODEX_TIMESTAMP_LENGTH)) {
|
|
2614
3203
|
return false;
|
|
2615
3204
|
}
|
|
3205
|
+
for (const key6 of ["id", "session_id", "sessionId"]) {
|
|
3206
|
+
if (!isOptionalBoundedString(value[key6], MAX_CODEX_SOURCE_ID_LENGTH)) {
|
|
3207
|
+
return false;
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
2616
3210
|
const payload = value["payload"];
|
|
2617
3211
|
if (!isObject(payload)) {
|
|
2618
3212
|
return false;
|
|
2619
3213
|
}
|
|
2620
|
-
if (payload["cwd"]
|
|
3214
|
+
if (!isOptionalBoundedString(payload["cwd"], MAX_CODEX_LOCAL_PATH_LENGTH)) {
|
|
3215
|
+
return false;
|
|
3216
|
+
}
|
|
3217
|
+
for (const key6 of [
|
|
3218
|
+
"id",
|
|
3219
|
+
"session_id",
|
|
3220
|
+
"sessionId",
|
|
3221
|
+
"forked_from_id",
|
|
3222
|
+
"forkedFromId",
|
|
3223
|
+
"parent_thread_id",
|
|
3224
|
+
"parent_session_id",
|
|
3225
|
+
"parentSessionId"
|
|
3226
|
+
]) {
|
|
3227
|
+
if (!isOptionalBoundedString(payload[key6], MAX_CODEX_SOURCE_ID_LENGTH)) {
|
|
3228
|
+
return false;
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
if (!isOptionalBoundedString(payload["timestamp"], MAX_CODEX_TIMESTAMP_LENGTH)) {
|
|
2621
3232
|
return false;
|
|
2622
3233
|
}
|
|
2623
3234
|
return true;
|
|
2624
3235
|
}
|
|
3236
|
+
function nonEmpty(value) {
|
|
3237
|
+
const trimmed = value?.trim();
|
|
3238
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
3239
|
+
}
|
|
3240
|
+
function extractCodexSourceMetadata(event) {
|
|
3241
|
+
const sourceSessionId = nonEmpty(
|
|
3242
|
+
event.payload.id ?? event.id ?? event.payload.session_id ?? event.payload.sessionId ?? event.session_id ?? event.sessionId
|
|
3243
|
+
);
|
|
3244
|
+
const canonicalSessionId = nonEmpty(
|
|
3245
|
+
event.payload.session_id ?? event.payload.sessionId ?? event.session_id ?? event.sessionId
|
|
3246
|
+
);
|
|
3247
|
+
return {
|
|
3248
|
+
sourceSessionId,
|
|
3249
|
+
canonicalSessionId,
|
|
3250
|
+
lineageSessionId: canonicalSessionId ?? sourceSessionId,
|
|
3251
|
+
parentSourceSessionId: nonEmpty(
|
|
3252
|
+
event.payload.forked_from_id ?? event.payload.forkedFromId ?? event.payload.parent_session_id ?? event.payload.parentSessionId
|
|
3253
|
+
),
|
|
3254
|
+
// Native agent files also carry parent_thread_id for structural thread
|
|
3255
|
+
// ownership. It is not evidence that token counters share a cumulative
|
|
3256
|
+
// scale, so retain it as bounded metadata without using it as a baseline.
|
|
3257
|
+
parentThreadId: nonEmpty(event.payload.parent_thread_id),
|
|
3258
|
+
forkedAt: nonEmpty(event.payload.timestamp ?? event.timestamp),
|
|
3259
|
+
cwd: event.payload.cwd
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
2625
3262
|
function extractCodexUsageSnapshot(event) {
|
|
2626
3263
|
return {
|
|
2627
3264
|
last: event.payload.info?.last_token_usage,
|
|
@@ -2631,30 +3268,36 @@ function extractCodexUsageSnapshot(event) {
|
|
|
2631
3268
|
function pickCodexEventModel(event) {
|
|
2632
3269
|
return event.payload.info?.model ?? event.payload.model ?? event.model;
|
|
2633
3270
|
}
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
}
|
|
2639
|
-
function key2(hourUtc, model) {
|
|
2640
|
-
return `${hourUtc.toISOString()}|${model}`;
|
|
2641
|
-
}
|
|
2642
|
-
function shouldExcludePath(path, excludePath) {
|
|
2643
|
-
return path !== void 0 && excludePath?.(path) === true;
|
|
2644
|
-
}
|
|
2645
|
-
function makeCodexFileAccumulator() {
|
|
2646
|
-
return {
|
|
2647
|
-
buckets: /* @__PURE__ */ new Map(),
|
|
2648
|
-
currentModel: void 0,
|
|
2649
|
-
excluded: false,
|
|
2650
|
-
lineNo: 0,
|
|
2651
|
-
sawTurnContext: false,
|
|
2652
|
-
previousTotalUsage: void 0,
|
|
2653
|
-
pendingUsage: [],
|
|
2654
|
-
sessionLastSeenAt: void 0,
|
|
2655
|
-
sessionStartedAt: void 0
|
|
2656
|
-
};
|
|
3271
|
+
function pickCodexTurnId(event) {
|
|
3272
|
+
return nonEmpty(
|
|
3273
|
+
event.payload.turn_id ?? event.payload.turnId ?? event.payload.id ?? event.payload.info?.turn_id ?? event.payload.info?.turnId ?? event.payload.info?.id
|
|
3274
|
+
);
|
|
2657
3275
|
}
|
|
3276
|
+
|
|
3277
|
+
// ../../packages/parsers/src/codex/lineage.ts
|
|
3278
|
+
import { createReadStream } from "fs";
|
|
3279
|
+
import { open, stat } from "fs/promises";
|
|
3280
|
+
import { normalize as normalize2 } from "path";
|
|
3281
|
+
import { createInterface as createInterface2 } from "readline";
|
|
3282
|
+
import { Cause, Effect as Effect15, Queue, Stream as Stream8 } from "effect";
|
|
3283
|
+
|
|
3284
|
+
// ../../packages/parsers/src/types.ts
|
|
3285
|
+
import { Schema as Schema27, Stream as Stream7 } from "effect";
|
|
3286
|
+
var ParserSourceReadError = class extends Schema27.TaggedErrorClass()("ParserSourceReadError", {
|
|
3287
|
+
message: Schema27.String,
|
|
3288
|
+
path: Schema27.String,
|
|
3289
|
+
cause: Schema27.optionalKey(Schema27.Defect())
|
|
3290
|
+
}) {
|
|
3291
|
+
};
|
|
3292
|
+
var makeParserSourceReadError = (path, error) => new ParserSourceReadError({
|
|
3293
|
+
message: `Parser source read failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3294
|
+
path,
|
|
3295
|
+
cause: error
|
|
3296
|
+
});
|
|
3297
|
+
|
|
3298
|
+
// ../../packages/parsers/src/codex/lineage.ts
|
|
3299
|
+
var CANDIDATE_TAIL_BYTES = 262144;
|
|
3300
|
+
var CANDIDATE_INSPECTION_CONCURRENCY = 8;
|
|
2658
3301
|
function lineStreamFromFile(path) {
|
|
2659
3302
|
return Stream8.callback(
|
|
2660
3303
|
(queue) => Effect15.acquireRelease(
|
|
@@ -2699,44 +3342,442 @@ function lineStreamFromFile(path) {
|
|
|
2699
3342
|
)
|
|
2700
3343
|
);
|
|
2701
3344
|
}
|
|
2702
|
-
function
|
|
3345
|
+
async function inspectSourceMetadata(path) {
|
|
3346
|
+
const stream = createReadStream(path, { encoding: "utf8" });
|
|
3347
|
+
const lines = createInterface2({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
3348
|
+
let first;
|
|
3349
|
+
const sourceSessionIds = /* @__PURE__ */ new Set();
|
|
3350
|
+
try {
|
|
3351
|
+
for await (const line of lines) {
|
|
3352
|
+
if (!line.includes("session_meta")) {
|
|
3353
|
+
continue;
|
|
3354
|
+
}
|
|
3355
|
+
let parsed;
|
|
3356
|
+
try {
|
|
3357
|
+
parsed = JSON.parse(line);
|
|
3358
|
+
} catch {
|
|
3359
|
+
continue;
|
|
3360
|
+
}
|
|
3361
|
+
if (isCodexSessionMetaEvent(parsed)) {
|
|
3362
|
+
const metadata = extractCodexSourceMetadata(parsed);
|
|
3363
|
+
first ??= metadata;
|
|
3364
|
+
if (metadata.sourceSessionId !== void 0) {
|
|
3365
|
+
sourceSessionIds.add(metadata.sourceSessionId);
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
return { first, containsMultipleSessionIds: sourceSessionIds.size > 1 };
|
|
3370
|
+
} finally {
|
|
3371
|
+
lines.close();
|
|
3372
|
+
stream.destroy();
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
async function hasCompleteTail(path, size) {
|
|
3376
|
+
if (size <= 0) {
|
|
3377
|
+
return false;
|
|
3378
|
+
}
|
|
3379
|
+
const handle = await open(path, "r");
|
|
3380
|
+
try {
|
|
3381
|
+
const bytesToRead = Math.min(size, CANDIDATE_TAIL_BYTES);
|
|
3382
|
+
const tail = Buffer.allocUnsafe(bytesToRead);
|
|
3383
|
+
await handle.read(tail, 0, bytesToRead, size - bytesToRead);
|
|
3384
|
+
const lastLine = tail.toString("utf8").split("\n").findLast((line) => line.trim().length > 0)?.trim();
|
|
3385
|
+
if (!lastLine) {
|
|
3386
|
+
return false;
|
|
3387
|
+
}
|
|
3388
|
+
try {
|
|
3389
|
+
JSON.parse(lastLine);
|
|
3390
|
+
return true;
|
|
3391
|
+
} catch {
|
|
3392
|
+
return false;
|
|
3393
|
+
}
|
|
3394
|
+
} finally {
|
|
3395
|
+
await handle.close();
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
function inspectCodexSourceCandidate(path) {
|
|
3399
|
+
return Effect15.tryPromise({
|
|
3400
|
+
try: async () => {
|
|
3401
|
+
const info = await stat(path);
|
|
3402
|
+
const inspectedMetadata = await inspectSourceMetadata(path);
|
|
3403
|
+
return {
|
|
3404
|
+
path,
|
|
3405
|
+
normalizedPath: normalize2(path),
|
|
3406
|
+
metadata: inspectedMetadata.first,
|
|
3407
|
+
containsMultipleSessionIds: inspectedMetadata.containsMultipleSessionIds,
|
|
3408
|
+
complete: await hasCompleteTail(path, info.size),
|
|
3409
|
+
mtimeMs: info.mtimeMs,
|
|
3410
|
+
size: info.size
|
|
3411
|
+
};
|
|
3412
|
+
},
|
|
3413
|
+
catch: (error) => makeParserSourceReadError(path, error)
|
|
3414
|
+
});
|
|
3415
|
+
}
|
|
3416
|
+
function candidateComesFirst(a, b) {
|
|
3417
|
+
if (a.complete !== b.complete) {
|
|
3418
|
+
return a.complete ? -1 : 1;
|
|
3419
|
+
}
|
|
3420
|
+
if (a.size !== b.size) {
|
|
3421
|
+
return b.size - a.size;
|
|
3422
|
+
}
|
|
3423
|
+
if (a.mtimeMs !== b.mtimeMs) {
|
|
3424
|
+
return b.mtimeMs - a.mtimeMs;
|
|
3425
|
+
}
|
|
3426
|
+
return a.normalizedPath.localeCompare(b.normalizedPath);
|
|
3427
|
+
}
|
|
3428
|
+
function candidateConflictKind(candidates) {
|
|
3429
|
+
const parents = new Set(candidates.map((candidate) => candidate.metadata?.parentSourceSessionId ?? ""));
|
|
3430
|
+
if (parents.size > 1) {
|
|
3431
|
+
return "parent";
|
|
3432
|
+
}
|
|
3433
|
+
const forkBoundaries = new Set(candidates.map((candidate) => candidate.metadata?.forkedAt ?? ""));
|
|
3434
|
+
return forkBoundaries.size > 1 ? "fork_boundary" : void 0;
|
|
3435
|
+
}
|
|
3436
|
+
function cycleSessionIds(sources) {
|
|
3437
|
+
const parents = /* @__PURE__ */ new Map();
|
|
3438
|
+
const knownIds = new Set(
|
|
3439
|
+
sources.flatMap((source) => source.sourceSessionId === void 0 ? [] : [source.sourceSessionId])
|
|
3440
|
+
);
|
|
3441
|
+
for (const source of sources) {
|
|
3442
|
+
if (source.sourceSessionId !== void 0 && source.parentSourceSessionId !== void 0 && knownIds.has(source.parentSourceSessionId)) {
|
|
3443
|
+
parents.set(source.sourceSessionId, source.parentSourceSessionId);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
const cycles = /* @__PURE__ */ new Set();
|
|
3447
|
+
for (const sourceId of [...knownIds].sort()) {
|
|
3448
|
+
const positions = /* @__PURE__ */ new Map();
|
|
3449
|
+
const chain = [];
|
|
3450
|
+
let current = sourceId;
|
|
3451
|
+
while (current !== void 0) {
|
|
3452
|
+
const cycleStart = positions.get(current);
|
|
3453
|
+
if (cycleStart !== void 0) {
|
|
3454
|
+
for (const member of chain.slice(cycleStart)) {
|
|
3455
|
+
cycles.add(member);
|
|
3456
|
+
}
|
|
3457
|
+
break;
|
|
3458
|
+
}
|
|
3459
|
+
positions.set(current, chain.length);
|
|
3460
|
+
chain.push(current);
|
|
3461
|
+
current = parents.get(current);
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
return cycles;
|
|
3465
|
+
}
|
|
3466
|
+
function groupCodexSourceCandidates(candidates) {
|
|
3467
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3468
|
+
for (const candidate of candidates) {
|
|
3469
|
+
const sourceId = candidate.metadata?.sourceSessionId;
|
|
3470
|
+
const groupKey = sourceId === void 0 ? `path:${candidate.normalizedPath}` : `session:${sourceId}`;
|
|
3471
|
+
const group = groups.get(groupKey) ?? [];
|
|
3472
|
+
group.push(candidate);
|
|
3473
|
+
groups.set(groupKey, group);
|
|
3474
|
+
}
|
|
3475
|
+
return groups;
|
|
3476
|
+
}
|
|
3477
|
+
function indexedSourceFromCandidates(group, cutoffMs, logger) {
|
|
3478
|
+
group.sort(candidateComesFirst);
|
|
3479
|
+
const [canonical] = group;
|
|
3480
|
+
if (canonical === void 0) {
|
|
3481
|
+
return void 0;
|
|
3482
|
+
}
|
|
3483
|
+
const conflict = candidateConflictKind(group);
|
|
3484
|
+
if (conflict !== void 0) {
|
|
3485
|
+
logger.warn("conflicting codex source candidates", {
|
|
3486
|
+
candidateCount: group.length,
|
|
3487
|
+
conflict
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
const sourceSessionId = canonical.metadata?.sourceSessionId;
|
|
2703
3491
|
return {
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
3492
|
+
path: canonical.path,
|
|
3493
|
+
sourceIdentity: sourceSessionId ?? canonical.normalizedPath,
|
|
3494
|
+
sourceSessionId,
|
|
3495
|
+
lineageSessionId: canonical.metadata?.lineageSessionId,
|
|
3496
|
+
parentSourceSessionId: canonical.metadata?.parentSourceSessionId,
|
|
3497
|
+
forkedAt: canonical.metadata?.forkedAt,
|
|
3498
|
+
cwd: canonical.metadata?.cwd,
|
|
3499
|
+
containsMultipleSessionIds: canonical.containsMultipleSessionIds || canonical.metadata?.canonicalSessionId !== void 0,
|
|
3500
|
+
eligible: group.some((candidate) => candidate.mtimeMs >= cutoffMs)
|
|
2707
3501
|
};
|
|
2708
3502
|
}
|
|
2709
|
-
function
|
|
2710
|
-
|
|
3503
|
+
function indexCodexLineageSessions(sources) {
|
|
3504
|
+
const bySessionId = /* @__PURE__ */ new Map();
|
|
3505
|
+
for (const source of sources) {
|
|
3506
|
+
const { lineageSessionId } = source;
|
|
3507
|
+
if (lineageSessionId === void 0) {
|
|
3508
|
+
continue;
|
|
3509
|
+
}
|
|
3510
|
+
const existing = bySessionId.get(lineageSessionId);
|
|
3511
|
+
if (existing === void 0 || source.sourceSessionId === lineageSessionId && existing.sourceSessionId !== lineageSessionId) {
|
|
3512
|
+
bySessionId.set(lineageSessionId, source);
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
return bySessionId;
|
|
2711
3516
|
}
|
|
2712
|
-
function
|
|
2713
|
-
|
|
3517
|
+
var buildCodexSourceIndex = Effect15.fn("CodexLineage.buildSourceIndex")(function* (paths, cutoff, logger) {
|
|
3518
|
+
const uniquePaths = [...new Set(paths.map(normalize2))].sort();
|
|
3519
|
+
const candidates = yield* Effect15.forEach(uniquePaths, inspectCodexSourceCandidate, {
|
|
3520
|
+
concurrency: CANDIDATE_INSPECTION_CONCURRENCY
|
|
3521
|
+
});
|
|
3522
|
+
const groups = groupCodexSourceCandidates(candidates);
|
|
3523
|
+
const sources = [];
|
|
3524
|
+
for (const groupKey of [...groups.keys()].sort()) {
|
|
3525
|
+
const group = groups.get(groupKey) ?? [];
|
|
3526
|
+
const source = indexedSourceFromCandidates(group, cutoff.getTime(), logger);
|
|
3527
|
+
if (source !== void 0) {
|
|
3528
|
+
sources.push(source);
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
sources.sort((a, b) => a.sourceIdentity.localeCompare(b.sourceIdentity));
|
|
3532
|
+
return {
|
|
3533
|
+
sources,
|
|
3534
|
+
bySessionId: indexCodexLineageSessions(sources),
|
|
3535
|
+
cycleSessionIds: cycleSessionIds(sources)
|
|
3536
|
+
};
|
|
3537
|
+
});
|
|
3538
|
+
function parseTimestamp(timestamp) {
|
|
3539
|
+
const parsed = Date.parse(timestamp);
|
|
3540
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
2714
3541
|
}
|
|
2715
|
-
function
|
|
2716
|
-
if (
|
|
2717
|
-
return
|
|
3542
|
+
function parseSnapshotIncrement(state, last, total) {
|
|
3543
|
+
if (last !== void 0) {
|
|
3544
|
+
return normalizeCodexUsage(last);
|
|
3545
|
+
}
|
|
3546
|
+
return total === void 0 ? void 0 : cumulativeCodexDelta(total, state.previousTotal);
|
|
3547
|
+
}
|
|
3548
|
+
function accumulateCodexSnapshot(state, raw) {
|
|
3549
|
+
let parsed;
|
|
3550
|
+
try {
|
|
3551
|
+
parsed = JSON.parse(raw);
|
|
3552
|
+
} catch {
|
|
3553
|
+
return state;
|
|
3554
|
+
}
|
|
3555
|
+
if (!isCodexTokenCountEvent(parsed)) {
|
|
3556
|
+
return state;
|
|
3557
|
+
}
|
|
3558
|
+
const snapshot = extractCodexUsageSnapshot(parsed);
|
|
3559
|
+
const total = snapshot.total === void 0 ? void 0 : normalizeCodexUsage(snapshot.total);
|
|
3560
|
+
if (state.containment !== void 0) {
|
|
3561
|
+
const last = snapshot.last === void 0 ? void 0 : normalizeCodexUsage(snapshot.last);
|
|
3562
|
+
const increment2 = applyCodexContainedSnapshot(state.containment, last, total);
|
|
3563
|
+
if (increment2 === void 0) {
|
|
3564
|
+
return state;
|
|
3565
|
+
}
|
|
3566
|
+
state.counted = addCodexUsage(state.counted, increment2);
|
|
3567
|
+
state.snapshots.push({
|
|
3568
|
+
timestamp: parsed.timestamp,
|
|
3569
|
+
timestampMs: parseTimestamp(parsed.timestamp),
|
|
3570
|
+
usage: state.counted
|
|
3571
|
+
});
|
|
3572
|
+
return state;
|
|
3573
|
+
}
|
|
3574
|
+
if (total !== void 0 && state.previousTotal !== void 0 && sameCodexUsage(total, state.previousTotal)) {
|
|
3575
|
+
return state;
|
|
3576
|
+
}
|
|
3577
|
+
const increment = parseSnapshotIncrement(state, snapshot.last, total);
|
|
3578
|
+
if (total !== void 0) {
|
|
3579
|
+
state.previousTotal = total;
|
|
3580
|
+
}
|
|
3581
|
+
if (increment === void 0 || !positiveCodexUsage(increment)) {
|
|
3582
|
+
return state;
|
|
3583
|
+
}
|
|
3584
|
+
state.counted = addCodexUsage(state.counted, increment);
|
|
3585
|
+
state.snapshots.push({
|
|
3586
|
+
timestamp: parsed.timestamp,
|
|
3587
|
+
timestampMs: parseTimestamp(parsed.timestamp),
|
|
3588
|
+
usage: state.counted
|
|
3589
|
+
});
|
|
3590
|
+
return state;
|
|
3591
|
+
}
|
|
3592
|
+
function collectCodexSnapshots(path, containsMultipleSessionIds) {
|
|
3593
|
+
return lineStreamFromFile(path).pipe(
|
|
3594
|
+
Stream8.runFold(
|
|
3595
|
+
() => ({
|
|
3596
|
+
snapshots: [],
|
|
3597
|
+
containment: containsMultipleSessionIds ? makeCodexContainmentState() : void 0,
|
|
3598
|
+
counted: ZERO_CODEX_USAGE,
|
|
3599
|
+
previousTotal: void 0
|
|
3600
|
+
}),
|
|
3601
|
+
accumulateCodexSnapshot
|
|
3602
|
+
),
|
|
3603
|
+
Effect15.map((state) => state.snapshots)
|
|
3604
|
+
);
|
|
3605
|
+
}
|
|
3606
|
+
function selectInheritedUsage(snapshots, forkedAt, forkedAtMs) {
|
|
3607
|
+
let inherited = ZERO_CODEX_USAGE;
|
|
3608
|
+
for (const snapshot of snapshots) {
|
|
3609
|
+
const atOrBefore = snapshot.timestampMs === void 0 ? snapshot.timestamp <= forkedAt : snapshot.timestampMs <= forkedAtMs;
|
|
3610
|
+
if (atOrBefore) {
|
|
3611
|
+
inherited = snapshot.usage;
|
|
3612
|
+
}
|
|
2718
3613
|
}
|
|
3614
|
+
return inherited;
|
|
3615
|
+
}
|
|
3616
|
+
function makeCodexLineageResolver(index, logger) {
|
|
3617
|
+
const snapshotsBySessionId = /* @__PURE__ */ new Map();
|
|
3618
|
+
const warned = /* @__PURE__ */ new Set();
|
|
3619
|
+
const unresolved = (source, reason) => {
|
|
3620
|
+
const warningKey = `${source.sourceIdentity}:${reason}`;
|
|
3621
|
+
if (!warned.has(warningKey)) {
|
|
3622
|
+
warned.add(warningKey);
|
|
3623
|
+
logger.warn("codex fork baseline unresolved", { reason });
|
|
3624
|
+
}
|
|
3625
|
+
return { _tag: "Unresolved", reason };
|
|
3626
|
+
};
|
|
3627
|
+
const requestFor = (source) => {
|
|
3628
|
+
const parentId = source.parentSourceSessionId;
|
|
3629
|
+
if (parentId === void 0) {
|
|
3630
|
+
return { _tag: "Root" };
|
|
3631
|
+
}
|
|
3632
|
+
if (source.sourceSessionId !== void 0 && index.cycleSessionIds.has(source.sourceSessionId)) {
|
|
3633
|
+
return unresolved(source, "cycle");
|
|
3634
|
+
}
|
|
3635
|
+
const parent = index.bySessionId.get(parentId);
|
|
3636
|
+
if (parent === void 0) {
|
|
3637
|
+
return unresolved(source, "missing_parent");
|
|
3638
|
+
}
|
|
3639
|
+
const { forkedAt } = source;
|
|
3640
|
+
const forkedAtMs = forkedAt === void 0 ? void 0 : parseTimestamp(forkedAt);
|
|
3641
|
+
if (forkedAt === void 0 || forkedAtMs === void 0) {
|
|
3642
|
+
return unresolved(source, "invalid_fork_boundary");
|
|
3643
|
+
}
|
|
3644
|
+
return { _tag: "Ready", forkedAt, forkedAtMs, parent, parentId };
|
|
3645
|
+
};
|
|
2719
3646
|
return {
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
3647
|
+
baselineFor: Effect15.fn("CodexLineage.baselineFor")(function* (source) {
|
|
3648
|
+
const request = requestFor(source);
|
|
3649
|
+
if (request._tag !== "Ready") {
|
|
3650
|
+
return request;
|
|
3651
|
+
}
|
|
3652
|
+
let snapshots = snapshotsBySessionId.get(request.parentId);
|
|
3653
|
+
if (snapshots === void 0) {
|
|
3654
|
+
snapshots = yield* collectCodexSnapshots(request.parent.path, request.parent.containsMultipleSessionIds);
|
|
3655
|
+
snapshotsBySessionId.set(request.parentId, snapshots);
|
|
3656
|
+
}
|
|
3657
|
+
return {
|
|
3658
|
+
_tag: "Resolved",
|
|
3659
|
+
usage: selectInheritedUsage(snapshots, request.forkedAt, request.forkedAtMs)
|
|
3660
|
+
};
|
|
3661
|
+
})
|
|
2723
3662
|
};
|
|
2724
3663
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
3664
|
+
|
|
3665
|
+
// ../../packages/parsers/src/codex/bucketize.ts
|
|
3666
|
+
function startOfUtcHour(d) {
|
|
3667
|
+
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), 0, 0, 0));
|
|
3668
|
+
}
|
|
3669
|
+
function key2(hourUtc, model) {
|
|
3670
|
+
return `${hourUtc.toISOString()}|${model}`;
|
|
3671
|
+
}
|
|
3672
|
+
function shouldExcludePath(path, excludePath) {
|
|
3673
|
+
return path !== void 0 && excludePath?.(path) === true;
|
|
3674
|
+
}
|
|
3675
|
+
function makeCodexFileAccumulator(options) {
|
|
3676
|
+
const lineageBaseline = options.lineageBaseline ?? { _tag: "Root" };
|
|
3677
|
+
return {
|
|
3678
|
+
buckets: /* @__PURE__ */ new Map(),
|
|
3679
|
+
candidates: [],
|
|
3680
|
+
currentModel: void 0,
|
|
3681
|
+
currentTurnId: void 0,
|
|
3682
|
+
containment: options.containInterleavedTotals === true ? makeCodexContainmentState() : void 0,
|
|
3683
|
+
excluded: false,
|
|
3684
|
+
lineNo: 0,
|
|
3685
|
+
nextEventIndex: 0,
|
|
3686
|
+
lineageKind: lineageBaseline._tag,
|
|
3687
|
+
inheritedBaselineUsage: lineageBaseline._tag === "Resolved" ? lineageBaseline.usage : void 0,
|
|
3688
|
+
sawTurnContext: false,
|
|
3689
|
+
previousTotalUsage: lineageBaseline._tag === "Resolved" ? lineageBaseline.usage : void 0,
|
|
3690
|
+
remainingInheritedUsage: lineageBaseline._tag === "Resolved" ? lineageBaseline.usage : void 0,
|
|
3691
|
+
pendingUsage: [],
|
|
3692
|
+
sessionLastSeenAt: void 0,
|
|
3693
|
+
sessionStartedAt: void 0
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
function subtractRemainingInheritedUsage(acc, rawIncrement) {
|
|
3697
|
+
const remaining = acc.remainingInheritedUsage;
|
|
3698
|
+
if (remaining === void 0) {
|
|
3699
|
+
return rawIncrement;
|
|
3700
|
+
}
|
|
3701
|
+
acc.remainingInheritedUsage = subtractCodexUsageFloorZero(remaining, rawIncrement);
|
|
3702
|
+
return subtractCodexUsageFloorZero(rawIncrement, remaining);
|
|
3703
|
+
}
|
|
3704
|
+
function positiveIncrement(increment) {
|
|
3705
|
+
return positiveCodexUsage(increment) ? increment : void 0;
|
|
3706
|
+
}
|
|
3707
|
+
function unresolvedUsageIncrement(acc, total) {
|
|
3708
|
+
if (total === void 0) {
|
|
3709
|
+
return void 0;
|
|
3710
|
+
}
|
|
3711
|
+
const previous = acc.previousTotalUsage;
|
|
3712
|
+
acc.previousTotalUsage = total;
|
|
3713
|
+
if (previous === void 0 || sameCodexUsage(previous, total)) {
|
|
3714
|
+
return void 0;
|
|
3715
|
+
}
|
|
3716
|
+
return positiveIncrement(cumulativeCodexDelta(total, previous));
|
|
3717
|
+
}
|
|
3718
|
+
function resolvedUsageIncrement(acc, last, total) {
|
|
3719
|
+
if (total !== void 0) {
|
|
3720
|
+
const previous = acc.previousTotalUsage;
|
|
3721
|
+
if (previous !== void 0 && sameCodexUsage(previous, total)) {
|
|
2729
3722
|
return void 0;
|
|
2730
3723
|
}
|
|
2731
|
-
|
|
2732
|
-
acc.
|
|
2733
|
-
return
|
|
3724
|
+
acc.previousTotalUsage = total;
|
|
3725
|
+
acc.remainingInheritedUsage = void 0;
|
|
3726
|
+
return positiveIncrement(cumulativeCodexDelta(total, previous));
|
|
2734
3727
|
}
|
|
2735
3728
|
if (last === void 0) {
|
|
2736
3729
|
return void 0;
|
|
2737
3730
|
}
|
|
2738
|
-
const
|
|
2739
|
-
return
|
|
3731
|
+
const rawIncrement = normalizeCodexUsage(last);
|
|
3732
|
+
return positiveIncrement(subtractRemainingInheritedUsage(acc, rawIncrement));
|
|
3733
|
+
}
|
|
3734
|
+
function containedUsageIncrement(acc, last, total) {
|
|
3735
|
+
const { containment } = acc;
|
|
3736
|
+
if (containment === void 0) {
|
|
3737
|
+
return void 0;
|
|
3738
|
+
}
|
|
3739
|
+
const normalizedTotal = total === void 0 ? void 0 : normalizeCodexUsage(total);
|
|
3740
|
+
const normalizedLast = last === void 0 ? void 0 : normalizeCodexUsage(last);
|
|
3741
|
+
if (acc.lineageKind === "Unresolved") {
|
|
3742
|
+
return applyCodexUnresolvedSnapshot(containment, normalizedLast, normalizedTotal);
|
|
3743
|
+
}
|
|
3744
|
+
if (acc.lineageKind === "Resolved") {
|
|
3745
|
+
const adjustedTotal = normalizedTotal === void 0 || acc.inheritedBaselineUsage === void 0 ? normalizedTotal : subtractCodexUsageFloorZero(normalizedTotal, acc.inheritedBaselineUsage);
|
|
3746
|
+
const adjustedLast = normalizedTotal === void 0 && normalizedLast !== void 0 ? subtractRemainingInheritedUsage(acc, normalizedLast) : normalizedLast;
|
|
3747
|
+
const increment = applyCodexContainedSnapshot(containment, adjustedLast, adjustedTotal, {
|
|
3748
|
+
totalOnlyBeforeInterleaving: true
|
|
3749
|
+
});
|
|
3750
|
+
if (normalizedTotal !== void 0) {
|
|
3751
|
+
acc.remainingInheritedUsage = void 0;
|
|
3752
|
+
}
|
|
3753
|
+
return increment;
|
|
3754
|
+
}
|
|
3755
|
+
return applyCodexContainedSnapshot(containment, normalizedLast, normalizedTotal);
|
|
3756
|
+
}
|
|
3757
|
+
function rootUsageIncrement(acc, last, total) {
|
|
3758
|
+
if (total !== void 0) {
|
|
3759
|
+
const previous = acc.previousTotalUsage;
|
|
3760
|
+
if (previous !== void 0 && sameCodexUsage(previous, total)) {
|
|
3761
|
+
return void 0;
|
|
3762
|
+
}
|
|
3763
|
+
const increment = last === void 0 ? cumulativeCodexDelta(total, previous) : normalizeCodexUsage(last);
|
|
3764
|
+
acc.previousTotalUsage = total;
|
|
3765
|
+
return positiveIncrement(increment);
|
|
3766
|
+
}
|
|
3767
|
+
return last === void 0 ? void 0 : positiveIncrement(normalizeCodexUsage(last));
|
|
3768
|
+
}
|
|
3769
|
+
function usageIncrement(acc, last, total) {
|
|
3770
|
+
if (acc.containment !== void 0) {
|
|
3771
|
+
return containedUsageIncrement(acc, last, total);
|
|
3772
|
+
}
|
|
3773
|
+
const normalizedTotal = total === void 0 ? void 0 : normalizeCodexUsage(total);
|
|
3774
|
+
if (acc.lineageKind === "Resolved") {
|
|
3775
|
+
return resolvedUsageIncrement(acc, last, normalizedTotal);
|
|
3776
|
+
}
|
|
3777
|
+
if (acc.lineageKind === "Unresolved") {
|
|
3778
|
+
return unresolvedUsageIncrement(acc, normalizedTotal);
|
|
3779
|
+
}
|
|
3780
|
+
return rootUsageIncrement(acc, last, normalizedTotal);
|
|
2740
3781
|
}
|
|
2741
3782
|
function addToBucket(acc, hourUtc, model, delta) {
|
|
2742
3783
|
const { inputTokens, outputTokens, cacheReadTokens } = delta;
|
|
@@ -2761,11 +3802,16 @@ function addToBucket(acc, hourUtc, model, delta) {
|
|
|
2761
3802
|
existing.cacheReadTokens += cacheReadTokens;
|
|
2762
3803
|
return true;
|
|
2763
3804
|
}
|
|
3805
|
+
function recordUsageCandidate(acc, usage, model) {
|
|
3806
|
+
if (!addToBucket(acc, startOfUtcHour(usage.eventAt), model, usage.usage)) {
|
|
3807
|
+
return;
|
|
3808
|
+
}
|
|
3809
|
+
acc.candidates.push({ ...usage, model });
|
|
3810
|
+
markSessionSeen(acc, usage.eventAt);
|
|
3811
|
+
}
|
|
2764
3812
|
function flushPendingUsage(acc, model) {
|
|
2765
3813
|
for (const pending of acc.pendingUsage) {
|
|
2766
|
-
|
|
2767
|
-
markSessionSeen(acc, pending.eventAt);
|
|
2768
|
-
}
|
|
3814
|
+
recordUsageCandidate(acc, pending, model);
|
|
2769
3815
|
}
|
|
2770
3816
|
acc.pendingUsage = [];
|
|
2771
3817
|
}
|
|
@@ -2798,6 +3844,7 @@ function accumulateCodexLine(context, acc, raw) {
|
|
|
2798
3844
|
if (shouldExcludePath(parsed.payload.cwd, options.excludePath)) {
|
|
2799
3845
|
acc.excluded = true;
|
|
2800
3846
|
acc.buckets.clear();
|
|
3847
|
+
acc.candidates = [];
|
|
2801
3848
|
acc.pendingUsage = [];
|
|
2802
3849
|
}
|
|
2803
3850
|
return acc;
|
|
@@ -2806,6 +3853,7 @@ function accumulateCodexLine(context, acc, raw) {
|
|
|
2806
3853
|
if (shouldExcludePath(parsed.payload.cwd, options.excludePath)) {
|
|
2807
3854
|
acc.excluded = true;
|
|
2808
3855
|
acc.buckets.clear();
|
|
3856
|
+
acc.candidates = [];
|
|
2809
3857
|
acc.pendingUsage = [];
|
|
2810
3858
|
return acc;
|
|
2811
3859
|
}
|
|
@@ -2817,6 +3865,10 @@ function accumulateCodexLine(context, acc, raw) {
|
|
|
2817
3865
|
}
|
|
2818
3866
|
return acc;
|
|
2819
3867
|
}
|
|
3868
|
+
if (isCodexTaskStartedEvent(parsed)) {
|
|
3869
|
+
acc.currentTurnId = pickCodexTurnId(parsed);
|
|
3870
|
+
return acc;
|
|
3871
|
+
}
|
|
2820
3872
|
if (!isCodexTokenCountEvent(parsed)) {
|
|
2821
3873
|
return acc;
|
|
2822
3874
|
}
|
|
@@ -2830,24 +3882,25 @@ function accumulateCodexLine(context, acc, raw) {
|
|
|
2830
3882
|
return acc;
|
|
2831
3883
|
}
|
|
2832
3884
|
const eventAt = new Date(tsMs);
|
|
3885
|
+
const eventIndex = acc.nextEventIndex;
|
|
3886
|
+
acc.nextEventIndex += 1;
|
|
3887
|
+
const turnId = pickCodexTurnId(parsed) ?? acc.currentTurnId;
|
|
3888
|
+
const candidate = { eventAt, eventIndex, turnId, usage: delta };
|
|
2833
3889
|
const eventModel = pickCodexEventModel(parsed);
|
|
2834
3890
|
const model = acc.sawTurnContext ? acc.currentModel ?? eventModel : eventModel ?? acc.currentModel;
|
|
2835
3891
|
if (model === void 0) {
|
|
2836
|
-
acc.pendingUsage.push(
|
|
3892
|
+
acc.pendingUsage.push(candidate);
|
|
2837
3893
|
return acc;
|
|
2838
3894
|
}
|
|
2839
|
-
|
|
2840
|
-
markSessionSeen(acc, eventAt);
|
|
2841
|
-
}
|
|
3895
|
+
recordUsageCandidate(acc, candidate, model);
|
|
2842
3896
|
return acc;
|
|
2843
3897
|
}
|
|
2844
|
-
function bucketsFromAccumulator(path, acc) {
|
|
3898
|
+
function bucketsFromAccumulator(path, options, acc) {
|
|
2845
3899
|
if (acc.excluded || acc.sessionStartedAt === void 0 || acc.sessionLastSeenAt === void 0) {
|
|
2846
3900
|
return [];
|
|
2847
3901
|
}
|
|
2848
|
-
const sessionStartedAt = acc
|
|
2849
|
-
const
|
|
2850
|
-
const sessionIds = /* @__PURE__ */ new Set([path]);
|
|
3902
|
+
const { sessionLastSeenAt, sessionStartedAt } = acc;
|
|
3903
|
+
const sessionIds = /* @__PURE__ */ new Set([options.sessionIdentity ?? path]);
|
|
2851
3904
|
return Array.from(acc.buckets.values(), (bucket) => ({
|
|
2852
3905
|
harness: "codex",
|
|
2853
3906
|
model: bucket.model,
|
|
@@ -2861,13 +3914,37 @@ function bucketsFromAccumulator(path, acc) {
|
|
|
2861
3914
|
sessionLastSeenAt
|
|
2862
3915
|
}));
|
|
2863
3916
|
}
|
|
2864
|
-
function
|
|
3917
|
+
function candidatesFromAccumulator(path, options, acc) {
|
|
3918
|
+
if (acc.excluded || acc.sessionStartedAt === void 0 || acc.sessionLastSeenAt === void 0) {
|
|
3919
|
+
return [];
|
|
3920
|
+
}
|
|
3921
|
+
const sessionIdentity = options.sessionIdentity ?? path;
|
|
3922
|
+
const canonicalSessionIdentity = options.canonicalSessionIdentity ?? sessionIdentity;
|
|
3923
|
+
return acc.candidates.map((candidate) => ({
|
|
3924
|
+
cacheReadTokens: candidate.usage.cacheReadTokens,
|
|
3925
|
+
canonicalSessionIdentity,
|
|
3926
|
+
eventIndex: candidate.eventIndex,
|
|
3927
|
+
hourUtc: startOfUtcHour(candidate.eventAt),
|
|
3928
|
+
inputTokens: candidate.usage.inputTokens,
|
|
3929
|
+
model: candidate.model,
|
|
3930
|
+
outputTokens: candidate.usage.outputTokens,
|
|
3931
|
+
sessionIdentity,
|
|
3932
|
+
sessionLastSeenAt: acc.sessionLastSeenAt,
|
|
3933
|
+
sessionStartedAt: acc.sessionStartedAt,
|
|
3934
|
+
turnId: candidate.turnId
|
|
3935
|
+
}));
|
|
3936
|
+
}
|
|
3937
|
+
function codexFileUsageEffect(path, logger, options = {}) {
|
|
2865
3938
|
const context = { logger, options, path };
|
|
2866
|
-
return
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
)
|
|
3939
|
+
return lineStreamFromFile(path).pipe(
|
|
3940
|
+
Stream9.runFold(
|
|
3941
|
+
() => makeCodexFileAccumulator(options),
|
|
3942
|
+
(acc, raw) => accumulateCodexLine(context, acc, raw)
|
|
3943
|
+
),
|
|
3944
|
+
Effect16.map((acc) => ({
|
|
3945
|
+
buckets: bucketsFromAccumulator(path, options, acc),
|
|
3946
|
+
candidates: candidatesFromAccumulator(path, options, acc)
|
|
3947
|
+
}))
|
|
2871
3948
|
);
|
|
2872
3949
|
}
|
|
2873
3950
|
|
|
@@ -2878,6 +3955,12 @@ function isObject2(value) {
|
|
|
2878
3955
|
function isFiniteNumber2(value) {
|
|
2879
3956
|
return typeof value === "number" && Number.isFinite(value);
|
|
2880
3957
|
}
|
|
3958
|
+
var MAX_CLAUDE_SOURCE_ID_LENGTH = 512;
|
|
3959
|
+
var MAX_CLAUDE_TIMESTAMP_LENGTH = 128;
|
|
3960
|
+
var MAX_CLAUDE_LOCAL_PATH_LENGTH = 4096;
|
|
3961
|
+
function isOptionalBoundedString2(value, maxLength) {
|
|
3962
|
+
return value === void 0 || typeof value === "string" && value.length <= maxLength;
|
|
3963
|
+
}
|
|
2881
3964
|
function isClaudeUsage(value) {
|
|
2882
3965
|
if (!isObject2(value)) {
|
|
2883
3966
|
return false;
|
|
@@ -2898,20 +3981,23 @@ function isClaudeAssistantEvent(value) {
|
|
|
2898
3981
|
if (value["type"] !== "assistant") {
|
|
2899
3982
|
return false;
|
|
2900
3983
|
}
|
|
2901
|
-
if (typeof value["timestamp"] !== "string") {
|
|
3984
|
+
if (typeof value["timestamp"] !== "string" || value["timestamp"].length > MAX_CLAUDE_TIMESTAMP_LENGTH) {
|
|
2902
3985
|
return false;
|
|
2903
3986
|
}
|
|
2904
|
-
if (value["cwd"]
|
|
3987
|
+
if (!isOptionalBoundedString2(value["cwd"], MAX_CLAUDE_LOCAL_PATH_LENGTH)) {
|
|
2905
3988
|
return false;
|
|
2906
3989
|
}
|
|
2907
|
-
if (value["requestId"]
|
|
3990
|
+
if (!isOptionalBoundedString2(value["requestId"], MAX_CLAUDE_SOURCE_ID_LENGTH)) {
|
|
3991
|
+
return false;
|
|
3992
|
+
}
|
|
3993
|
+
if (value["isSidechain"] !== void 0 && typeof value["isSidechain"] !== "boolean") {
|
|
2908
3994
|
return false;
|
|
2909
3995
|
}
|
|
2910
3996
|
const message = value["message"];
|
|
2911
3997
|
if (!isObject2(message)) {
|
|
2912
3998
|
return false;
|
|
2913
3999
|
}
|
|
2914
|
-
if (message["id"]
|
|
4000
|
+
if (!isOptionalBoundedString2(message["id"], MAX_CLAUDE_SOURCE_ID_LENGTH)) {
|
|
2915
4001
|
return false;
|
|
2916
4002
|
}
|
|
2917
4003
|
if (typeof message["model"] !== "string") {
|
|
@@ -2930,23 +4016,19 @@ function key3(hourUtc, model) {
|
|
|
2930
4016
|
function shouldExcludePath2(path, excludePath) {
|
|
2931
4017
|
return path !== void 0 && excludePath?.(path) === true;
|
|
2932
4018
|
}
|
|
2933
|
-
function
|
|
2934
|
-
return {
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
seenTuples: options.seenTuples ?? /* @__PURE__ */ new Set(),
|
|
2939
|
-
sessionLastSeenAt: void 0,
|
|
2940
|
-
sessionStartedAt: void 0
|
|
2941
|
-
};
|
|
4019
|
+
function makeClaudeFileCandidateAccumulator() {
|
|
4020
|
+
return { excluded: false, identified: /* @__PURE__ */ new Map(), lineNo: 0, unidentified: [] };
|
|
4021
|
+
}
|
|
4022
|
+
function makeClaudeSessionAccumulator() {
|
|
4023
|
+
return { buckets: /* @__PURE__ */ new Map(), sessionLastSeenAt: void 0, sessionStartedAt: void 0 };
|
|
2942
4024
|
}
|
|
2943
4025
|
function lineStreamFromFile2(path) {
|
|
2944
|
-
return
|
|
2945
|
-
(queue) =>
|
|
2946
|
-
|
|
4026
|
+
return Stream10.callback(
|
|
4027
|
+
(queue) => Effect17.acquireRelease(
|
|
4028
|
+
Effect17.sync(() => {
|
|
2947
4029
|
let done = false;
|
|
2948
4030
|
const stream = createReadStream2(path, { encoding: "utf8" });
|
|
2949
|
-
const
|
|
4031
|
+
const lines = createInterface3({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
2950
4032
|
const offer = (line) => {
|
|
2951
4033
|
if (!done) {
|
|
2952
4034
|
Queue2.offerUnsafe(queue, line);
|
|
@@ -2965,61 +4047,32 @@ function lineStreamFromFile2(path) {
|
|
|
2965
4047
|
}
|
|
2966
4048
|
};
|
|
2967
4049
|
stream.once("error", fail);
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
4050
|
+
lines.once("error", fail);
|
|
4051
|
+
lines.once("close", end);
|
|
4052
|
+
lines.on("line", offer);
|
|
2971
4053
|
return {
|
|
2972
4054
|
close: () => {
|
|
2973
4055
|
done = true;
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
4056
|
+
lines.off("close", end);
|
|
4057
|
+
lines.off("error", fail);
|
|
4058
|
+
lines.off("line", offer);
|
|
2977
4059
|
stream.off("error", fail);
|
|
2978
|
-
|
|
2979
|
-
stream.destroy();
|
|
2980
|
-
}
|
|
2981
|
-
};
|
|
2982
|
-
}),
|
|
2983
|
-
(resource) =>
|
|
2984
|
-
)
|
|
2985
|
-
);
|
|
2986
|
-
}
|
|
2987
|
-
function addToBucket2(acc, hourUtc, model, usage) {
|
|
2988
|
-
const inputTokens = Math.max(0, usage.input_tokens ?? 0);
|
|
2989
|
-
const outputTokens = Math.max(0, usage.output_tokens ?? 0);
|
|
2990
|
-
const cacheReadTokens = Math.max(0, usage.cache_read_input_tokens ?? 0);
|
|
2991
|
-
const cacheCreateTokens = Math.max(0, usage.cache_creation_input_tokens ?? 0);
|
|
2992
|
-
if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheCreateTokens === 0) {
|
|
2993
|
-
return false;
|
|
2994
|
-
}
|
|
2995
|
-
const k = key3(hourUtc, model);
|
|
2996
|
-
const existing = acc.buckets.get(k);
|
|
2997
|
-
if (existing === void 0) {
|
|
2998
|
-
acc.buckets.set(k, {
|
|
2999
|
-
hourUtc,
|
|
3000
|
-
model,
|
|
3001
|
-
inputTokens,
|
|
3002
|
-
outputTokens,
|
|
3003
|
-
cacheReadTokens,
|
|
3004
|
-
cacheCreateTokens
|
|
3005
|
-
});
|
|
3006
|
-
return true;
|
|
3007
|
-
}
|
|
3008
|
-
existing.inputTokens += inputTokens;
|
|
3009
|
-
existing.outputTokens += outputTokens;
|
|
3010
|
-
existing.cacheReadTokens += cacheReadTokens;
|
|
3011
|
-
existing.cacheCreateTokens += cacheCreateTokens;
|
|
3012
|
-
return true;
|
|
4060
|
+
lines.close();
|
|
4061
|
+
stream.destroy();
|
|
4062
|
+
}
|
|
4063
|
+
};
|
|
4064
|
+
}),
|
|
4065
|
+
(resource) => Effect17.sync(() => resource.close())
|
|
4066
|
+
)
|
|
4067
|
+
);
|
|
3013
4068
|
}
|
|
3014
|
-
function
|
|
3015
|
-
if (
|
|
3016
|
-
|
|
3017
|
-
}
|
|
3018
|
-
if (acc.sessionLastSeenAt === void 0 || eventAt.getTime() > acc.sessionLastSeenAt.getTime()) {
|
|
3019
|
-
acc.sessionLastSeenAt = eventAt;
|
|
4069
|
+
function eventIsEligible(timestampMs3, options) {
|
|
4070
|
+
if (options.cutoff !== void 0 && timestampMs3 < options.cutoff.getTime()) {
|
|
4071
|
+
return false;
|
|
3020
4072
|
}
|
|
4073
|
+
return options.endExclusive === void 0 || timestampMs3 < options.endExclusive.getTime();
|
|
3021
4074
|
}
|
|
3022
|
-
function
|
|
4075
|
+
function accumulateClaudeCandidate(context, acc, raw) {
|
|
3023
4076
|
const { logger, options, path } = context;
|
|
3024
4077
|
acc.lineNo += 1;
|
|
3025
4078
|
if (acc.excluded) {
|
|
@@ -3041,37 +4094,129 @@ function accumulateClaudeLine(context, acc, raw) {
|
|
|
3041
4094
|
}
|
|
3042
4095
|
if (shouldExcludePath2(parsed.cwd, options.excludePath)) {
|
|
3043
4096
|
acc.excluded = true;
|
|
3044
|
-
acc.
|
|
4097
|
+
acc.identified.clear();
|
|
4098
|
+
acc.unidentified = [];
|
|
3045
4099
|
return acc;
|
|
3046
4100
|
}
|
|
3047
|
-
const
|
|
3048
|
-
if (!Number.isFinite(
|
|
4101
|
+
const timestampMs3 = Date.parse(parsed.timestamp);
|
|
4102
|
+
if (!(Number.isFinite(timestampMs3) && eventIsEligible(timestampMs3, options))) {
|
|
3049
4103
|
return acc;
|
|
3050
4104
|
}
|
|
3051
|
-
|
|
3052
|
-
|
|
4105
|
+
const tupleKey = parsed.message.id === void 0 || parsed.requestId === void 0 ? void 0 : JSON.stringify([parsed.message.id, parsed.requestId]);
|
|
4106
|
+
const candidate = {
|
|
4107
|
+
eventAt: new Date(timestampMs3),
|
|
4108
|
+
eventOrder: acc.lineNo,
|
|
4109
|
+
isSidechain: parsed.isSidechain === true,
|
|
4110
|
+
model: parsed.message.model,
|
|
4111
|
+
pathRole: options.pathRole ?? "parent",
|
|
4112
|
+
sessionId: options.sessionId ?? path,
|
|
4113
|
+
sourceOrder: normalize3(path),
|
|
4114
|
+
tupleKey,
|
|
4115
|
+
usage: parsed.message.usage
|
|
4116
|
+
};
|
|
4117
|
+
if (tupleKey === void 0) {
|
|
4118
|
+
acc.unidentified.push(candidate);
|
|
4119
|
+
} else {
|
|
4120
|
+
acc.identified.set(tupleKey, candidate);
|
|
3053
4121
|
}
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
4122
|
+
return acc;
|
|
4123
|
+
}
|
|
4124
|
+
function candidatesFromAccumulator2(acc) {
|
|
4125
|
+
return acc.excluded ? [] : [...acc.identified.values(), ...acc.unidentified];
|
|
4126
|
+
}
|
|
4127
|
+
function collectClaudeFileCandidates(path, logger, options = {}) {
|
|
4128
|
+
const context = { logger, options, path };
|
|
4129
|
+
return lineStreamFromFile2(path).pipe(
|
|
4130
|
+
Stream10.runFold(makeClaudeFileCandidateAccumulator, (acc, raw) => accumulateClaudeCandidate(context, acc, raw)),
|
|
4131
|
+
Effect17.map(candidatesFromAccumulator2)
|
|
4132
|
+
);
|
|
4133
|
+
}
|
|
4134
|
+
function normalizedUsage(usage) {
|
|
4135
|
+
return {
|
|
4136
|
+
inputTokens: Math.max(0, usage.input_tokens ?? 0),
|
|
4137
|
+
outputTokens: Math.max(0, usage.output_tokens ?? 0),
|
|
4138
|
+
cacheReadTokens: Math.max(0, usage.cache_read_input_tokens ?? 0),
|
|
4139
|
+
cacheCreateTokens: Math.max(0, usage.cache_creation_input_tokens ?? 0)
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
function sameUsage(left, right) {
|
|
4143
|
+
const a = normalizedUsage(left);
|
|
4144
|
+
const b = normalizedUsage(right);
|
|
4145
|
+
return a.inputTokens === b.inputTokens && a.outputTokens === b.outputTokens && a.cacheReadTokens === b.cacheReadTokens && a.cacheCreateTokens === b.cacheCreateTokens;
|
|
4146
|
+
}
|
|
4147
|
+
function candidateWins(next, current) {
|
|
4148
|
+
if (next.model === current.model && sameUsage(next.usage, current.usage)) {
|
|
4149
|
+
if (next.isSidechain !== current.isSidechain) {
|
|
4150
|
+
return !next.isSidechain;
|
|
4151
|
+
}
|
|
4152
|
+
if (next.pathRole !== current.pathRole) {
|
|
4153
|
+
return next.pathRole === "parent";
|
|
3059
4154
|
}
|
|
3060
|
-
acc.seenTuples.add(tuple);
|
|
3061
4155
|
}
|
|
3062
|
-
const
|
|
3063
|
-
if (
|
|
3064
|
-
|
|
4156
|
+
const timestampOrder = next.eventAt.getTime() - current.eventAt.getTime();
|
|
4157
|
+
if (timestampOrder !== 0) {
|
|
4158
|
+
return timestampOrder > 0;
|
|
3065
4159
|
}
|
|
3066
|
-
|
|
4160
|
+
if (next.eventOrder !== current.eventOrder) {
|
|
4161
|
+
return next.eventOrder > current.eventOrder;
|
|
4162
|
+
}
|
|
4163
|
+
return next.sourceOrder.localeCompare(current.sourceOrder) < 0;
|
|
3067
4164
|
}
|
|
3068
|
-
function
|
|
3069
|
-
|
|
4165
|
+
function reconcileClaudeCandidates(candidates) {
|
|
4166
|
+
const identified = /* @__PURE__ */ new Map();
|
|
4167
|
+
const unidentified = [];
|
|
4168
|
+
for (const candidate of candidates) {
|
|
4169
|
+
if (candidate.tupleKey === void 0) {
|
|
4170
|
+
unidentified.push(candidate);
|
|
4171
|
+
continue;
|
|
4172
|
+
}
|
|
4173
|
+
const current = identified.get(candidate.tupleKey);
|
|
4174
|
+
if (current === void 0 || candidateWins(candidate, current)) {
|
|
4175
|
+
identified.set(candidate.tupleKey, candidate);
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
return [...identified.values(), ...unidentified];
|
|
4179
|
+
}
|
|
4180
|
+
function addToBucket2(acc, candidate) {
|
|
4181
|
+
const usage = normalizedUsage(candidate.usage);
|
|
4182
|
+
const { cacheCreateTokens, cacheReadTokens, inputTokens, outputTokens } = usage;
|
|
4183
|
+
if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheCreateTokens === 0) {
|
|
4184
|
+
return false;
|
|
4185
|
+
}
|
|
4186
|
+
const hourUtc = startOfUtcHour(candidate.eventAt);
|
|
4187
|
+
const bucketKey = key3(hourUtc, candidate.model);
|
|
4188
|
+
const existing = acc.buckets.get(bucketKey);
|
|
4189
|
+
if (existing === void 0) {
|
|
4190
|
+
acc.buckets.set(bucketKey, {
|
|
4191
|
+
hourUtc,
|
|
4192
|
+
model: candidate.model,
|
|
4193
|
+
inputTokens,
|
|
4194
|
+
outputTokens,
|
|
4195
|
+
cacheReadTokens,
|
|
4196
|
+
cacheCreateTokens
|
|
4197
|
+
});
|
|
4198
|
+
return true;
|
|
4199
|
+
}
|
|
4200
|
+
existing.inputTokens += inputTokens;
|
|
4201
|
+
existing.outputTokens += outputTokens;
|
|
4202
|
+
existing.cacheReadTokens += cacheReadTokens;
|
|
4203
|
+
existing.cacheCreateTokens += cacheCreateTokens;
|
|
4204
|
+
return true;
|
|
4205
|
+
}
|
|
4206
|
+
function markSessionSeen2(acc, eventAt) {
|
|
4207
|
+
if (acc.sessionStartedAt === void 0 || eventAt.getTime() < acc.sessionStartedAt.getTime()) {
|
|
4208
|
+
acc.sessionStartedAt = eventAt;
|
|
4209
|
+
}
|
|
4210
|
+
if (acc.sessionLastSeenAt === void 0 || eventAt.getTime() > acc.sessionLastSeenAt.getTime()) {
|
|
4211
|
+
acc.sessionLastSeenAt = eventAt;
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
function bucketsForSession(sessionId, acc) {
|
|
4215
|
+
const { sessionLastSeenAt, sessionStartedAt } = acc;
|
|
4216
|
+
if (sessionStartedAt === void 0 || sessionLastSeenAt === void 0) {
|
|
3070
4217
|
return [];
|
|
3071
4218
|
}
|
|
3072
|
-
const
|
|
3073
|
-
const sessionLastSeenAt = acc.sessionLastSeenAt;
|
|
3074
|
-
const sessionIds = /* @__PURE__ */ new Set([options.sessionId ?? path]);
|
|
4219
|
+
const sessionIds = /* @__PURE__ */ new Set([sessionId]);
|
|
3075
4220
|
return Array.from(acc.buckets.values(), (bucket) => ({
|
|
3076
4221
|
harness: "claude_code",
|
|
3077
4222
|
model: bucket.model,
|
|
@@ -3085,70 +4230,80 @@ function bucketsFromAccumulator2(path, options, acc) {
|
|
|
3085
4230
|
sessionLastSeenAt
|
|
3086
4231
|
}));
|
|
3087
4232
|
}
|
|
4233
|
+
function bucketizeClaudeCandidates(candidates) {
|
|
4234
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
4235
|
+
for (const candidate of reconcileClaudeCandidates(candidates)) {
|
|
4236
|
+
const acc = sessions.get(candidate.sessionId) ?? makeClaudeSessionAccumulator();
|
|
4237
|
+
if (addToBucket2(acc, candidate)) {
|
|
4238
|
+
markSessionSeen2(acc, candidate.eventAt);
|
|
4239
|
+
}
|
|
4240
|
+
sessions.set(candidate.sessionId, acc);
|
|
4241
|
+
}
|
|
4242
|
+
return [...sessions.entries()].flatMap(([sessionId, acc]) => bucketsForSession(sessionId, acc));
|
|
4243
|
+
}
|
|
3088
4244
|
function bucketizeClaudeFileStream(path, logger, options = {}) {
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
Stream9.runFold(
|
|
3093
|
-
() => makeClaudeFileAccumulator(options),
|
|
3094
|
-
(acc, raw) => accumulateClaudeLine(context, acc, raw)
|
|
3095
|
-
),
|
|
3096
|
-
Effect16.map((acc) => Stream9.fromIterable(bucketsFromAccumulator2(path, options, acc)))
|
|
4245
|
+
return Stream10.unwrap(
|
|
4246
|
+
collectClaudeFileCandidates(path, logger, options).pipe(
|
|
4247
|
+
Effect17.map((candidates) => Stream10.fromIterable(bucketizeClaudeCandidates(candidates)))
|
|
3097
4248
|
)
|
|
3098
4249
|
);
|
|
3099
4250
|
}
|
|
3100
4251
|
|
|
3101
4252
|
// ../../packages/parsers/src/claude/index.ts
|
|
3102
|
-
function
|
|
4253
|
+
function sourceContext(path) {
|
|
3103
4254
|
const fileDir = dirname2(path);
|
|
3104
4255
|
if (basename(fileDir) !== "subagents") {
|
|
3105
|
-
return path;
|
|
4256
|
+
return { pathRole: "parent", sessionId: path };
|
|
3106
4257
|
}
|
|
3107
4258
|
const sessionDir = dirname2(fileDir);
|
|
3108
|
-
return `${sessionDir}.jsonl
|
|
4259
|
+
return { pathRole: "subagent", sessionId: `${sessionDir}.jsonl` };
|
|
3109
4260
|
}
|
|
3110
4261
|
function bucketizeClaudeSourcesStream(files, logger, options) {
|
|
3111
|
-
return
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
4262
|
+
return Stream11.unwrap(
|
|
4263
|
+
files.pipe(
|
|
4264
|
+
Stream11.runCollect,
|
|
4265
|
+
Effect18.map((paths) => [...paths].sort()),
|
|
4266
|
+
Effect18.flatMap(
|
|
4267
|
+
(paths) => Effect18.forEach(paths, (path) => {
|
|
4268
|
+
const source = sourceContext(path);
|
|
4269
|
+
return collectClaudeFileCandidates(path, logger, {
|
|
4270
|
+
...options,
|
|
4271
|
+
pathRole: source.pathRole,
|
|
4272
|
+
sessionId: source.sessionId
|
|
3122
4273
|
});
|
|
3123
|
-
}
|
|
3124
|
-
|
|
3125
|
-
)
|
|
3126
|
-
)
|
|
3127
|
-
|
|
4274
|
+
})
|
|
4275
|
+
),
|
|
4276
|
+
Effect18.map((filesCandidates) => Stream11.fromIterable(bucketizeClaudeCandidates(filesCandidates.flat())))
|
|
4277
|
+
)
|
|
4278
|
+
);
|
|
3128
4279
|
}
|
|
3129
|
-
var parseClaudeEffect =
|
|
3130
|
-
(opts) =>
|
|
4280
|
+
var parseClaudeEffect = Effect18.fn("ClaudeParser.parseClaudeEffect")(
|
|
4281
|
+
(opts) => Stream11.runCollect(parseClaudeStream(opts))
|
|
3131
4282
|
);
|
|
3132
|
-
var parseClaudeStream = (opts) =>
|
|
3133
|
-
|
|
4283
|
+
var parseClaudeStream = (opts) => Stream11.unwrap(
|
|
4284
|
+
Effect18.gen(function* () {
|
|
3134
4285
|
const logger = opts.logger ?? silentLogger;
|
|
3135
4286
|
const root = opts.root ?? resolveClaudeRoot();
|
|
3136
4287
|
if ((yield* pathKindEffectStrict(root)) === "file") {
|
|
4288
|
+
const source = sourceContext(root);
|
|
3137
4289
|
return bucketizeClaudeFileStream(root, logger, {
|
|
3138
4290
|
cutoff: opts.cutoff,
|
|
4291
|
+
endExclusive: opts.endExclusive,
|
|
3139
4292
|
excludePath: opts.excludePath,
|
|
3140
|
-
|
|
4293
|
+
pathRole: source.pathRole,
|
|
4294
|
+
sessionId: source.sessionId
|
|
3141
4295
|
});
|
|
3142
4296
|
}
|
|
3143
4297
|
return bucketizeClaudeSourcesStream(walkStreamStrict(root, opts.cutoff), logger, {
|
|
3144
4298
|
cutoff: opts.cutoff,
|
|
4299
|
+
endExclusive: opts.endExclusive,
|
|
3145
4300
|
excludePath: opts.excludePath
|
|
3146
4301
|
});
|
|
3147
4302
|
})
|
|
3148
4303
|
);
|
|
3149
4304
|
|
|
3150
4305
|
// ../../packages/parsers/src/codex/index.ts
|
|
3151
|
-
import { Effect as
|
|
4306
|
+
import { Effect as Effect19, Stream as Stream12 } from "effect";
|
|
3152
4307
|
var codexRoots = (opts, root) => {
|
|
3153
4308
|
const roots = [root];
|
|
3154
4309
|
if (opts.root === void 0 && opts.archivedRoot === void 0) {
|
|
@@ -3158,53 +4313,135 @@ var codexRoots = (opts, root) => {
|
|
|
3158
4313
|
}
|
|
3159
4314
|
return roots;
|
|
3160
4315
|
};
|
|
3161
|
-
|
|
3162
|
-
|
|
4316
|
+
function candidateIdentity(candidate) {
|
|
4317
|
+
return [
|
|
4318
|
+
candidate.canonicalSessionIdentity,
|
|
4319
|
+
candidate.turnId ?? "",
|
|
4320
|
+
String(candidate.eventIndex),
|
|
4321
|
+
candidate.hourUtc.toISOString().slice(0, 10),
|
|
4322
|
+
candidate.model,
|
|
4323
|
+
String(candidate.inputTokens),
|
|
4324
|
+
String(candidate.cacheReadTokens),
|
|
4325
|
+
String(candidate.outputTokens)
|
|
4326
|
+
].join("");
|
|
4327
|
+
}
|
|
4328
|
+
function reconcileCodexCandidates(candidates) {
|
|
4329
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4330
|
+
const winners = [];
|
|
4331
|
+
for (const candidate of candidates) {
|
|
4332
|
+
const identity = candidateIdentity(candidate);
|
|
4333
|
+
if (seen.has(identity)) {
|
|
4334
|
+
continue;
|
|
4335
|
+
}
|
|
4336
|
+
seen.add(identity);
|
|
4337
|
+
winners.push(candidate);
|
|
4338
|
+
}
|
|
4339
|
+
return winners;
|
|
4340
|
+
}
|
|
4341
|
+
function bucketsFromCodexCandidates(candidates) {
|
|
4342
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
4343
|
+
for (const candidate of candidates) {
|
|
4344
|
+
const identity = `${candidate.sessionIdentity}${candidate.hourUtc.toISOString()}${candidate.model}`;
|
|
4345
|
+
const existing = buckets.get(identity);
|
|
4346
|
+
if (existing === void 0) {
|
|
4347
|
+
buckets.set(identity, {
|
|
4348
|
+
cacheReadTokens: candidate.cacheReadTokens,
|
|
4349
|
+
hourUtc: candidate.hourUtc,
|
|
4350
|
+
inputTokens: candidate.inputTokens,
|
|
4351
|
+
model: candidate.model,
|
|
4352
|
+
outputTokens: candidate.outputTokens,
|
|
4353
|
+
sessionIdentity: candidate.sessionIdentity,
|
|
4354
|
+
sessionLastSeenAt: candidate.sessionLastSeenAt,
|
|
4355
|
+
sessionStartedAt: candidate.sessionStartedAt
|
|
4356
|
+
});
|
|
4357
|
+
continue;
|
|
4358
|
+
}
|
|
4359
|
+
existing.inputTokens += candidate.inputTokens;
|
|
4360
|
+
existing.outputTokens += candidate.outputTokens;
|
|
4361
|
+
existing.cacheReadTokens += candidate.cacheReadTokens;
|
|
4362
|
+
}
|
|
4363
|
+
return [...buckets.values()].map((bucket) => ({
|
|
4364
|
+
harness: "codex",
|
|
4365
|
+
model: bucket.model,
|
|
4366
|
+
hourUtc: bucket.hourUtc,
|
|
4367
|
+
inputTokens: bucket.inputTokens,
|
|
4368
|
+
outputTokens: bucket.outputTokens,
|
|
4369
|
+
cacheReadTokens: bucket.cacheReadTokens,
|
|
4370
|
+
cacheCreateTokens: 0,
|
|
4371
|
+
sessionIds: /* @__PURE__ */ new Set([bucket.sessionIdentity]),
|
|
4372
|
+
sessionStartedAt: bucket.sessionStartedAt,
|
|
4373
|
+
sessionLastSeenAt: bucket.sessionLastSeenAt
|
|
4374
|
+
}));
|
|
4375
|
+
}
|
|
4376
|
+
var parseCodexEffect = Effect19.fn("CodexParser.parseCodexEffect")(
|
|
4377
|
+
(opts) => Stream12.runCollect(parseCodexStream(opts))
|
|
3163
4378
|
);
|
|
3164
|
-
var parseCodexStream = (opts) =>
|
|
3165
|
-
|
|
4379
|
+
var parseCodexStream = (opts) => Stream12.unwrap(
|
|
4380
|
+
Effect19.gen(function* () {
|
|
3166
4381
|
const logger = opts.logger ?? silentLogger;
|
|
3167
4382
|
const root = opts.root ?? resolveCodexSessionsRoot();
|
|
3168
4383
|
if ((yield* pathKindEffectStrict(root)) === "file") {
|
|
3169
|
-
return
|
|
4384
|
+
return bucketizeIndexedCodexFilesStream(Stream12.succeed(root), opts.cutoff, logger, {
|
|
4385
|
+
excludePath: opts.excludePath
|
|
4386
|
+
});
|
|
3170
4387
|
}
|
|
3171
|
-
return
|
|
3172
|
-
|
|
3173
|
-
|
|
4388
|
+
return bucketizeIndexedCodexFilesStream(
|
|
4389
|
+
walkManyStreamStrict(codexRoots(opts, root), /* @__PURE__ */ new Date(0)),
|
|
4390
|
+
opts.cutoff,
|
|
4391
|
+
logger,
|
|
4392
|
+
{ excludePath: opts.excludePath }
|
|
4393
|
+
);
|
|
3174
4394
|
})
|
|
3175
4395
|
);
|
|
3176
|
-
function
|
|
3177
|
-
return
|
|
4396
|
+
function bucketizeIndexedCodexFilesStream(files, cutoff, logger, options) {
|
|
4397
|
+
return Stream12.unwrap(
|
|
4398
|
+
files.pipe(
|
|
4399
|
+
Stream12.runCollect,
|
|
4400
|
+
Effect19.map((paths) => [...paths]),
|
|
4401
|
+
Effect19.flatMap((paths) => buildCodexSourceIndex(paths, cutoff, logger)),
|
|
4402
|
+
Effect19.flatMap(
|
|
4403
|
+
(index) => Effect19.gen(function* () {
|
|
4404
|
+
const resolver = makeCodexLineageResolver(index, logger);
|
|
4405
|
+
const usages = yield* Effect19.forEach(
|
|
4406
|
+
index.sources.filter((source) => source.eligible),
|
|
4407
|
+
(source) => resolver.baselineFor(source).pipe(
|
|
4408
|
+
Effect19.flatMap(
|
|
4409
|
+
(lineageBaseline) => codexFileUsageEffect(source.path, logger, {
|
|
4410
|
+
...options,
|
|
4411
|
+
canonicalSessionIdentity: source.lineageSessionId ?? source.sourceIdentity,
|
|
4412
|
+
containInterleavedTotals: source.containsMultipleSessionIds,
|
|
4413
|
+
lineageBaseline,
|
|
4414
|
+
sessionIdentity: source.sourceIdentity
|
|
4415
|
+
})
|
|
4416
|
+
)
|
|
4417
|
+
)
|
|
4418
|
+
);
|
|
4419
|
+
const candidates = usages.flatMap((usage) => usage.candidates);
|
|
4420
|
+
return Stream12.fromIterable(bucketsFromCodexCandidates(reconcileCodexCandidates(candidates)));
|
|
4421
|
+
})
|
|
4422
|
+
)
|
|
4423
|
+
)
|
|
4424
|
+
);
|
|
3178
4425
|
}
|
|
3179
4426
|
|
|
3180
4427
|
// ../../packages/parsers/src/effect/hermes-sqlite.ts
|
|
3181
|
-
import { Context as
|
|
4428
|
+
import { Context as Context13, Effect as Effect20, Layer as Layer8, Schema as Schema28 } from "effect";
|
|
3182
4429
|
var NODE_SQLITE_SPECIFIER = ["node", "sqlite"].join(":");
|
|
3183
4430
|
var MILLISECONDS_PER_SECOND = 1e3;
|
|
3184
|
-
var
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
started_at as "startedAt",
|
|
3190
|
-
ended_at as "endedAt",
|
|
3191
|
-
input_tokens as "inputTokens",
|
|
3192
|
-
output_tokens as "outputTokens",
|
|
3193
|
-
cache_read_tokens as "cacheReadTokens",
|
|
3194
|
-
cache_write_tokens as "cacheWriteTokens",
|
|
3195
|
-
cwd
|
|
3196
|
-
from sessions
|
|
3197
|
-
where coalesce(ended_at, started_at) >= ?
|
|
3198
|
-
order by started_at asc, id asc
|
|
3199
|
-
`;
|
|
3200
|
-
var HermesSqliteError = class extends Schema22.TaggedErrorClass()("HermesSqliteError", {
|
|
3201
|
-
message: Schema22.String,
|
|
3202
|
-
operation: Schema22.Literals(["load", "open", "query", "close"]),
|
|
3203
|
-
path: Schema22.optionalKey(Schema22.String),
|
|
3204
|
-
cause: Schema22.optionalKey(Schema22.Defect())
|
|
4431
|
+
var HermesSqliteError = class extends Schema28.TaggedErrorClass()("HermesSqliteError", {
|
|
4432
|
+
message: Schema28.String,
|
|
4433
|
+
operation: Schema28.Literals(["close", "load", "open", "query", "schema"]),
|
|
4434
|
+
path: Schema28.optionalKey(Schema28.String),
|
|
4435
|
+
cause: Schema28.optionalKey(Schema28.Defect())
|
|
3205
4436
|
}) {
|
|
3206
4437
|
};
|
|
3207
|
-
|
|
4438
|
+
function unsupportedSchema(message) {
|
|
4439
|
+
return { _tag: "UnsupportedHermesSchema", message };
|
|
4440
|
+
}
|
|
4441
|
+
function isUnsupportedSchema(value) {
|
|
4442
|
+
return typeof value === "object" && value !== null && "_tag" in value && value._tag === "UnsupportedHermesSchema";
|
|
4443
|
+
}
|
|
4444
|
+
var HermesSqlite = class extends Context13.Service()("@oopst/parsers/HermesSqlite") {
|
|
3208
4445
|
};
|
|
3209
4446
|
var toHermesSqliteError = (operation, path) => (error) => new HermesSqliteError({
|
|
3210
4447
|
message: `HermesSqlite.${operation} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -3212,34 +4449,124 @@ var toHermesSqliteError = (operation, path) => (error) => new HermesSqliteError(
|
|
|
3212
4449
|
path,
|
|
3213
4450
|
cause: error
|
|
3214
4451
|
});
|
|
3215
|
-
var loadDatabaseSync =
|
|
3216
|
-
const sqliteModule = yield*
|
|
4452
|
+
var loadDatabaseSync = Effect20.fn("HermesSqlite.loadDatabaseSync")(function* () {
|
|
4453
|
+
const sqliteModule = yield* Effect20.tryPromise({
|
|
3217
4454
|
try: () => import(NODE_SQLITE_SPECIFIER),
|
|
3218
4455
|
catch: toHermesSqliteError("load")
|
|
3219
4456
|
});
|
|
3220
4457
|
return sqliteModule.DatabaseSync;
|
|
3221
4458
|
});
|
|
3222
|
-
var openDatabase = (path, DatabaseSync) =>
|
|
4459
|
+
var openDatabase = (path, DatabaseSync) => Effect20.try({
|
|
3223
4460
|
try: () => new DatabaseSync(path, { readOnly: true, timeout: 1e3 }),
|
|
3224
4461
|
catch: toHermesSqliteError("open", path)
|
|
3225
4462
|
});
|
|
3226
|
-
var closeDatabase = (path, db) =>
|
|
4463
|
+
var closeDatabase = (path, db) => Effect20.try({
|
|
3227
4464
|
try: () => {
|
|
3228
4465
|
db.close();
|
|
3229
4466
|
},
|
|
3230
4467
|
catch: toHermesSqliteError("close", path)
|
|
3231
4468
|
});
|
|
3232
|
-
|
|
4469
|
+
function tableNames(db) {
|
|
4470
|
+
const rows = db.prepare("select name from sqlite_master where type = 'table'").all();
|
|
4471
|
+
return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : []));
|
|
4472
|
+
}
|
|
4473
|
+
function tableColumns(db, table) {
|
|
4474
|
+
const rows = db.prepare(`pragma table_info(${table})`).all();
|
|
4475
|
+
return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : []));
|
|
4476
|
+
}
|
|
4477
|
+
function requireColumns(table, columns, required) {
|
|
4478
|
+
const missing = required.filter((column) => !columns.has(column));
|
|
4479
|
+
if (missing.length > 0) {
|
|
4480
|
+
throw unsupportedSchema(`${table} is missing required accounting columns`);
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
function selectedColumn(columns, column, alias, qualifier) {
|
|
4484
|
+
const source = qualifier === void 0 ? column : `${qualifier}.${column}`;
|
|
4485
|
+
return columns.has(column) ? `${source} as "${alias}"` : `null as "${alias}"`;
|
|
4486
|
+
}
|
|
4487
|
+
function sessionRows(db, cutoffSeconds) {
|
|
4488
|
+
const columns = tableColumns(db, "sessions");
|
|
4489
|
+
requireColumns("sessions", columns, ["id", "model", "started_at", "ended_at", "input_tokens", "output_tokens"]);
|
|
4490
|
+
const query = `
|
|
4491
|
+
select
|
|
4492
|
+
id,
|
|
4493
|
+
model,
|
|
4494
|
+
${selectedColumn(columns, "billing_provider", "billingProvider")},
|
|
4495
|
+
started_at as "startedAt",
|
|
4496
|
+
ended_at as "endedAt",
|
|
4497
|
+
input_tokens as "inputTokens",
|
|
4498
|
+
output_tokens as "outputTokens",
|
|
4499
|
+
${selectedColumn(columns, "cache_read_tokens", "cacheReadTokens")},
|
|
4500
|
+
${selectedColumn(columns, "cache_write_tokens", "cacheWriteTokens")},
|
|
4501
|
+
${selectedColumn(columns, "reasoning_tokens", "reasoningTokens")},
|
|
4502
|
+
${selectedColumn(columns, "cwd", "cwd")}
|
|
4503
|
+
from sessions
|
|
4504
|
+
where ended_at is null or ended_at >= ?
|
|
4505
|
+
order by started_at asc, id asc
|
|
4506
|
+
`;
|
|
4507
|
+
return db.prepare(query).all(cutoffSeconds);
|
|
4508
|
+
}
|
|
4509
|
+
function modelUsageRows(db, cutoffSeconds) {
|
|
4510
|
+
const columns = tableColumns(db, "session_model_usage");
|
|
4511
|
+
requireColumns("session_model_usage", columns, ["session_id", "model", "input_tokens", "output_tokens"]);
|
|
4512
|
+
const query = `
|
|
4513
|
+
select
|
|
4514
|
+
usage.session_id as "sessionId",
|
|
4515
|
+
usage.model,
|
|
4516
|
+
${selectedColumn(columns, "billing_provider", "billingProvider", "usage")},
|
|
4517
|
+
${selectedColumn(columns, "first_seen", "firstSeen", "usage")},
|
|
4518
|
+
${selectedColumn(columns, "last_seen", "lastSeen", "usage")},
|
|
4519
|
+
usage.input_tokens as "inputTokens",
|
|
4520
|
+
usage.output_tokens as "outputTokens",
|
|
4521
|
+
${selectedColumn(columns, "cache_read_tokens", "cacheReadTokens", "usage")},
|
|
4522
|
+
${selectedColumn(columns, "cache_write_tokens", "cacheWriteTokens", "usage")},
|
|
4523
|
+
${selectedColumn(columns, "reasoning_tokens", "reasoningTokens", "usage")}
|
|
4524
|
+
from session_model_usage as usage
|
|
4525
|
+
inner join sessions on sessions.id = usage.session_id
|
|
4526
|
+
where sessions.ended_at is null or sessions.ended_at >= ?
|
|
4527
|
+
order by usage.session_id asc, usage.model asc
|
|
4528
|
+
`;
|
|
4529
|
+
return db.prepare(query).all(cutoffSeconds);
|
|
4530
|
+
}
|
|
4531
|
+
function attachModelUsageRows(sessions, usageRows) {
|
|
4532
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
4533
|
+
for (const row of usageRows) {
|
|
4534
|
+
if (typeof row.sessionId !== "string") {
|
|
4535
|
+
continue;
|
|
4536
|
+
}
|
|
4537
|
+
const rows = bySession.get(row.sessionId) ?? [];
|
|
4538
|
+
rows.push(row);
|
|
4539
|
+
bySession.set(row.sessionId, rows);
|
|
4540
|
+
}
|
|
4541
|
+
return sessions.map((row) => ({
|
|
4542
|
+
...row,
|
|
4543
|
+
modelUsageRows: typeof row.id === "string" ? bySession.get(row.id) ?? [] : []
|
|
4544
|
+
}));
|
|
4545
|
+
}
|
|
4546
|
+
var querySessionRows = (path, db, cutoff) => Effect20.try({
|
|
3233
4547
|
try: () => {
|
|
3234
|
-
const
|
|
3235
|
-
|
|
4548
|
+
const tables = tableNames(db);
|
|
4549
|
+
if (!tables.has("sessions")) {
|
|
4550
|
+
throw unsupportedSchema("sessions table is unavailable");
|
|
4551
|
+
}
|
|
4552
|
+
const cutoffSeconds = cutoff.getTime() / MILLISECONDS_PER_SECOND;
|
|
4553
|
+
const sessions = sessionRows(db, cutoffSeconds);
|
|
4554
|
+
if (!tables.has("session_model_usage")) {
|
|
4555
|
+
return sessions;
|
|
4556
|
+
}
|
|
4557
|
+
return attachModelUsageRows(sessions, modelUsageRows(db, cutoffSeconds));
|
|
3236
4558
|
},
|
|
3237
|
-
catch:
|
|
4559
|
+
catch: (error) => isUnsupportedSchema(error) ? new HermesSqliteError({
|
|
4560
|
+
message: "Hermes SQLite schema does not expose supported accounting aggregates",
|
|
4561
|
+
operation: "schema",
|
|
4562
|
+
path,
|
|
4563
|
+
cause: error
|
|
4564
|
+
}) : toHermesSqliteError("query", path)(error)
|
|
3238
4565
|
});
|
|
3239
4566
|
var makeHermesSqlite = () => ({
|
|
3240
|
-
readSessionRows:
|
|
4567
|
+
readSessionRows: Effect20.fn("HermesSqlite.readSessionRows")(function* (path, cutoff) {
|
|
3241
4568
|
const DatabaseSync = yield* loadDatabaseSync();
|
|
3242
|
-
return yield*
|
|
4569
|
+
return yield* Effect20.acquireUseRelease(
|
|
3243
4570
|
openDatabase(path, DatabaseSync),
|
|
3244
4571
|
(db) => querySessionRows(path, db, cutoff),
|
|
3245
4572
|
(db) => closeDatabase(path, db)
|
|
@@ -3248,15 +4575,21 @@ var makeHermesSqlite = () => ({
|
|
|
3248
4575
|
});
|
|
3249
4576
|
var HermesSqliteLive = Layer8.effect(
|
|
3250
4577
|
HermesSqlite,
|
|
3251
|
-
|
|
4578
|
+
Effect20.sync(() => HermesSqlite.of(makeHermesSqlite()))
|
|
3252
4579
|
);
|
|
3253
4580
|
|
|
3254
4581
|
// ../../packages/parsers/src/effect/opencode-io.ts
|
|
3255
4582
|
import { basename as basename2, join as join6 } from "path";
|
|
3256
4583
|
import { NodeFileSystem as NodeFileSystem5 } from "@effect/platform-node";
|
|
3257
|
-
import { Context as
|
|
4584
|
+
import { Context as Context14, Effect as Effect21, FileSystem as FileSystem6, Layer as Layer9, Option as Option3, Schema as Schema29, Stream as Stream13 } from "effect";
|
|
3258
4585
|
var NODE_SQLITE_SPECIFIER2 = ["node", "sqlite"].join(":");
|
|
3259
4586
|
var OPENCODE_DB_FILE_PATTERN2 = /^opencode(?:-[^/]+)?\.db$/u;
|
|
4587
|
+
var MESSAGE_QUERY = `
|
|
4588
|
+
select session_id as "sessionId", time_created as "timeCreated", data
|
|
4589
|
+
from message
|
|
4590
|
+
where time_created >= ?
|
|
4591
|
+
order by time_created asc, id asc
|
|
4592
|
+
`;
|
|
3260
4593
|
var SESSION_MESSAGE_QUERY = `
|
|
3261
4594
|
select session_id as "sessionId", time_created as "timeCreated", data
|
|
3262
4595
|
from session_message
|
|
@@ -3271,14 +4604,14 @@ var SESSION_PATH_QUERIES = [
|
|
|
3271
4604
|
join project p on p.id = s.project_id
|
|
3272
4605
|
where p.worktree is not null`
|
|
3273
4606
|
];
|
|
3274
|
-
var OpenCodeIoError = class extends
|
|
3275
|
-
message:
|
|
3276
|
-
operation:
|
|
3277
|
-
path:
|
|
3278
|
-
cause:
|
|
4607
|
+
var OpenCodeIoError = class extends Schema29.TaggedErrorClass()("OpenCodeIoError", {
|
|
4608
|
+
message: Schema29.String,
|
|
4609
|
+
operation: Schema29.Literals(["stat", "readdir", "readFile", "parseJson", "load", "open", "query", "close"]),
|
|
4610
|
+
path: Schema29.optionalKey(Schema29.String),
|
|
4611
|
+
cause: Schema29.optionalKey(Schema29.Defect())
|
|
3279
4612
|
}) {
|
|
3280
4613
|
};
|
|
3281
|
-
var OpenCodeIo = class extends
|
|
4614
|
+
var OpenCodeIo = class extends Context14.Service()("@oopst/parsers/OpenCodeIo") {
|
|
3282
4615
|
};
|
|
3283
4616
|
var toOpenCodeIoError = (operation, path) => (error) => new OpenCodeIoError({
|
|
3284
4617
|
message: `OpenCodeIo.${operation} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -3286,13 +4619,13 @@ var toOpenCodeIoError = (operation, path) => (error) => new OpenCodeIoError({
|
|
|
3286
4619
|
path,
|
|
3287
4620
|
cause: error
|
|
3288
4621
|
});
|
|
3289
|
-
var readDir2 = (fs, path) => fs.readDirectory(path).pipe(
|
|
3290
|
-
var readDirBestEffort = (fs, path) => readDir2(fs, path).pipe(
|
|
3291
|
-
var readStat2 = (fs, path) => fs.stat(path).pipe(
|
|
3292
|
-
var readStatBestEffort = (fs, path) => readStat2(fs, path).pipe(
|
|
3293
|
-
var pathKind =
|
|
4622
|
+
var readDir2 = (fs, path) => fs.readDirectory(path).pipe(Effect21.mapError(toOpenCodeIoError("readdir", path)));
|
|
4623
|
+
var readDirBestEffort = (fs, path) => readDir2(fs, path).pipe(Effect21.catch(() => Effect21.succeed([])));
|
|
4624
|
+
var readStat2 = (fs, path) => fs.stat(path).pipe(Effect21.mapError(toOpenCodeIoError("stat", path)));
|
|
4625
|
+
var readStatBestEffort = (fs, path) => readStat2(fs, path).pipe(Effect21.catch(() => Effect21.succeed(void 0)));
|
|
4626
|
+
var pathKind = Effect21.fn("OpenCodeIo.pathKind")(function* (fs, path) {
|
|
3294
4627
|
return yield* readStat2(fs, path).pipe(
|
|
3295
|
-
|
|
4628
|
+
Effect21.map((info) => {
|
|
3296
4629
|
if (info.type === "File") {
|
|
3297
4630
|
return "file";
|
|
3298
4631
|
}
|
|
@@ -3301,10 +4634,10 @@ var pathKind = Effect20.fn("OpenCodeIo.pathKind")(function* (fs, path) {
|
|
|
3301
4634
|
}
|
|
3302
4635
|
return "missing";
|
|
3303
4636
|
}),
|
|
3304
|
-
|
|
4637
|
+
Effect21.catch(() => Effect21.succeed("missing"))
|
|
3305
4638
|
);
|
|
3306
4639
|
});
|
|
3307
|
-
var listOpenCodeDatabases =
|
|
4640
|
+
var listOpenCodeDatabases = Effect21.fn("OpenCodeIo.listOpenCodeDatabases")(function* (fs, root) {
|
|
3308
4641
|
const entries = yield* readDirBestEffort(fs, root);
|
|
3309
4642
|
const databasePaths = [];
|
|
3310
4643
|
for (const entry of entries) {
|
|
@@ -3318,7 +4651,7 @@ var listOpenCodeDatabases = Effect20.fn("OpenCodeIo.listOpenCodeDatabases")(func
|
|
|
3318
4651
|
}
|
|
3319
4652
|
return databasePaths;
|
|
3320
4653
|
});
|
|
3321
|
-
var listProjectLegacyRoots =
|
|
4654
|
+
var listProjectLegacyRoots = Effect21.fn("OpenCodeIo.listProjectLegacyRoots")(function* (fs, root) {
|
|
3322
4655
|
const projectRoot = join6(root, "project");
|
|
3323
4656
|
const entries = yield* readDirBestEffort(fs, projectRoot);
|
|
3324
4657
|
const roots = [];
|
|
@@ -3330,7 +4663,7 @@ var listProjectLegacyRoots = Effect20.fn("OpenCodeIo.listProjectLegacyRoots")(fu
|
|
|
3330
4663
|
}
|
|
3331
4664
|
return roots;
|
|
3332
4665
|
});
|
|
3333
|
-
var legacyRoots =
|
|
4666
|
+
var legacyRoots = Effect21.fn("OpenCodeIo.legacyRoots")(function* (fs, root) {
|
|
3334
4667
|
return [
|
|
3335
4668
|
join6(root, "storage", "message"),
|
|
3336
4669
|
join6(root, "storage", "session", "message"),
|
|
@@ -3341,26 +4674,26 @@ var isRecent = (info, cutoff) => Option3.match(info.mtime, {
|
|
|
3341
4674
|
onNone: () => false,
|
|
3342
4675
|
onSome: (mtime) => mtime.getTime() >= cutoff.getTime()
|
|
3343
4676
|
});
|
|
3344
|
-
var streamJsonFiles = (fs, root, cutoff) =>
|
|
3345
|
-
() =>
|
|
3346
|
-
|
|
4677
|
+
var streamJsonFiles = (fs, root, cutoff) => Stream13.suspend(
|
|
4678
|
+
() => Stream13.fromIterableEffect(readDirBestEffort(fs, root)).pipe(
|
|
4679
|
+
Stream13.flatMap((entry) => {
|
|
3347
4680
|
const full = join6(root, entry);
|
|
3348
|
-
return
|
|
3349
|
-
|
|
4681
|
+
return Stream13.fromEffect(readStatBestEffort(fs, full)).pipe(
|
|
4682
|
+
Stream13.flatMap((info) => {
|
|
3350
4683
|
if (info?.type === "Directory") {
|
|
3351
4684
|
return streamJsonFiles(fs, full, cutoff);
|
|
3352
4685
|
}
|
|
3353
4686
|
if (info?.type === "File" && entry.endsWith(".json") && isRecent(info, cutoff)) {
|
|
3354
|
-
return
|
|
4687
|
+
return Stream13.succeed(full);
|
|
3355
4688
|
}
|
|
3356
|
-
return
|
|
4689
|
+
return Stream13.empty;
|
|
3357
4690
|
})
|
|
3358
4691
|
);
|
|
3359
4692
|
})
|
|
3360
4693
|
)
|
|
3361
4694
|
);
|
|
3362
|
-
var streamManyJsonFiles = (fs, roots, cutoff) =>
|
|
3363
|
-
var discoverSources =
|
|
4695
|
+
var streamManyJsonFiles = (fs, roots, cutoff) => Stream13.fromIterable(roots).pipe(Stream13.flatMap((root) => streamJsonFiles(fs, root, cutoff)));
|
|
4696
|
+
var discoverSources = Effect21.fn("OpenCodeIo.discoverSources")(function* (fs, root, cutoff) {
|
|
3364
4697
|
const kind = yield* pathKind(fs, root);
|
|
3365
4698
|
if (kind === "missing") {
|
|
3366
4699
|
return { _tag: "Missing" };
|
|
@@ -3379,26 +4712,26 @@ var discoverSources = Effect20.fn("OpenCodeIo.discoverSources")(function* (fs, r
|
|
|
3379
4712
|
return { _tag: "LegacyJsonFiles", paths: streamManyJsonFiles(fs, roots, cutoff) };
|
|
3380
4713
|
});
|
|
3381
4714
|
var readJsonFile = (fs, path) => fs.readFileString(path, "utf8").pipe(
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
(text) =>
|
|
4715
|
+
Effect21.mapError(toOpenCodeIoError("readFile", path)),
|
|
4716
|
+
Effect21.flatMap(
|
|
4717
|
+
(text) => Effect21.try({
|
|
3385
4718
|
try: () => JSON.parse(text),
|
|
3386
4719
|
catch: toOpenCodeIoError("parseJson", path)
|
|
3387
4720
|
})
|
|
3388
4721
|
)
|
|
3389
4722
|
);
|
|
3390
|
-
var loadDatabaseSync2 =
|
|
3391
|
-
const sqliteModule = yield*
|
|
4723
|
+
var loadDatabaseSync2 = Effect21.fn("OpenCodeIo.loadDatabaseSync")(function* () {
|
|
4724
|
+
const sqliteModule = yield* Effect21.tryPromise({
|
|
3392
4725
|
try: () => import(NODE_SQLITE_SPECIFIER2),
|
|
3393
4726
|
catch: toOpenCodeIoError("load")
|
|
3394
4727
|
});
|
|
3395
4728
|
return sqliteModule.DatabaseSync;
|
|
3396
4729
|
});
|
|
3397
|
-
var openDatabase2 = (path, DatabaseSync) =>
|
|
4730
|
+
var openDatabase2 = (path, DatabaseSync) => Effect21.try({
|
|
3398
4731
|
try: () => new DatabaseSync(path, { readOnly: true, timeout: 1e3 }),
|
|
3399
4732
|
catch: toOpenCodeIoError("open", path)
|
|
3400
4733
|
});
|
|
3401
|
-
var closeDatabase2 = (path, db) =>
|
|
4734
|
+
var closeDatabase2 = (path, db) => Effect21.try({
|
|
3402
4735
|
try: () => {
|
|
3403
4736
|
db.close();
|
|
3404
4737
|
},
|
|
@@ -3423,19 +4756,43 @@ function readSessionPaths(db) {
|
|
|
3423
4756
|
}
|
|
3424
4757
|
return paths;
|
|
3425
4758
|
}
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
4759
|
+
function queryMessageRows(db, sql, cutoff) {
|
|
4760
|
+
return db.prepare(sql).all(cutoff.getTime());
|
|
4761
|
+
}
|
|
4762
|
+
function isMissingTableError(error, table) {
|
|
4763
|
+
return error instanceof Error && error.message.includes(`no such table: ${table}`);
|
|
4764
|
+
}
|
|
4765
|
+
function readOpenCodeMessages(db, cutoff) {
|
|
4766
|
+
let messages;
|
|
4767
|
+
try {
|
|
4768
|
+
messages = queryMessageRows(db, MESSAGE_QUERY, cutoff);
|
|
4769
|
+
if (messages.length > 0) {
|
|
4770
|
+
return messages;
|
|
4771
|
+
}
|
|
4772
|
+
} catch (error) {
|
|
4773
|
+
if (!isMissingTableError(error, "message")) {
|
|
4774
|
+
throw error;
|
|
4775
|
+
}
|
|
4776
|
+
}
|
|
4777
|
+
try {
|
|
4778
|
+
return queryMessageRows(db, SESSION_MESSAGE_QUERY, cutoff);
|
|
4779
|
+
} catch (error) {
|
|
4780
|
+
if (messages !== void 0 && isMissingTableError(error, "session_message")) {
|
|
4781
|
+
return messages;
|
|
4782
|
+
}
|
|
4783
|
+
throw error;
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
var querySqliteRows = (path, db, cutoff) => Effect21.try({
|
|
4787
|
+
try: () => ({
|
|
4788
|
+
sessionMessages: readOpenCodeMessages(db, cutoff),
|
|
4789
|
+
sessionPaths: readSessionPaths(db)
|
|
4790
|
+
}),
|
|
3434
4791
|
catch: toOpenCodeIoError("query", path)
|
|
3435
4792
|
});
|
|
3436
|
-
var readSqlite =
|
|
4793
|
+
var readSqlite = Effect21.fn("OpenCodeIo.readSqlite")(function* (path, cutoff) {
|
|
3437
4794
|
const DatabaseSync = yield* loadDatabaseSync2();
|
|
3438
|
-
return yield*
|
|
4795
|
+
return yield* Effect21.acquireUseRelease(
|
|
3439
4796
|
openDatabase2(path, DatabaseSync),
|
|
3440
4797
|
(db) => querySqliteRows(path, db, cutoff),
|
|
3441
4798
|
(db) => closeDatabase2(path, db)
|
|
@@ -3448,17 +4805,17 @@ var makeOpenCodeIo = (fs) => ({
|
|
|
3448
4805
|
});
|
|
3449
4806
|
var OpenCodeIoLive = Layer9.effect(
|
|
3450
4807
|
OpenCodeIo,
|
|
3451
|
-
|
|
4808
|
+
Effect21.gen(function* () {
|
|
3452
4809
|
const fs = yield* FileSystem6.FileSystem;
|
|
3453
4810
|
return OpenCodeIo.of(makeOpenCodeIo(fs));
|
|
3454
4811
|
})
|
|
3455
4812
|
).pipe(Layer9.provide(NodeFileSystem5.layer));
|
|
3456
4813
|
|
|
3457
4814
|
// ../../packages/parsers/src/opencode/index.ts
|
|
3458
|
-
import { Effect as
|
|
4815
|
+
import { Effect as Effect23, Stream as Stream14 } from "effect";
|
|
3459
4816
|
|
|
3460
4817
|
// ../../packages/parsers/src/opencode/bucketize.ts
|
|
3461
|
-
import { Effect as
|
|
4818
|
+
import { Effect as Effect22 } from "effect";
|
|
3462
4819
|
|
|
3463
4820
|
// ../../packages/parsers/src/opencode/events.ts
|
|
3464
4821
|
function isObject3(value) {
|
|
@@ -3679,7 +5036,7 @@ function* finalizedBuckets(accumulators) {
|
|
|
3679
5036
|
function parseJson(value) {
|
|
3680
5037
|
return typeof value === "string" ? JSON.parse(value) : value;
|
|
3681
5038
|
}
|
|
3682
|
-
var bucketizeOpenCodeDatabaseEffect =
|
|
5039
|
+
var bucketizeOpenCodeDatabaseEffect = Effect22.fn("bucketizeOpenCodeDatabaseEffect")(
|
|
3683
5040
|
function* (path, cutoff, logger, excludePath) {
|
|
3684
5041
|
const io = yield* OpenCodeIo;
|
|
3685
5042
|
const rows = yield* io.readSqlite(path, cutoff);
|
|
@@ -3702,7 +5059,7 @@ var bucketizeOpenCodeDatabaseEffect = Effect21.fn("bucketizeOpenCodeDatabaseEffe
|
|
|
3702
5059
|
return [...finalizedBuckets(accumulators)];
|
|
3703
5060
|
}
|
|
3704
5061
|
);
|
|
3705
|
-
var bucketizeOpenCodeJsonFileEffect =
|
|
5062
|
+
var bucketizeOpenCodeJsonFileEffect = Effect22.fn("bucketizeOpenCodeJsonFileEffect")(
|
|
3706
5063
|
function* (path) {
|
|
3707
5064
|
const io = yield* OpenCodeIo;
|
|
3708
5065
|
const parsed = yield* io.readJsonFile(path);
|
|
@@ -3717,23 +5074,23 @@ var bucketizeOpenCodeJsonFileEffect = Effect21.fn("bucketizeOpenCodeJsonFileEffe
|
|
|
3717
5074
|
);
|
|
3718
5075
|
|
|
3719
5076
|
// ../../packages/parsers/src/opencode/index.ts
|
|
3720
|
-
var discoverOpenCodeSourcesEffect =
|
|
5077
|
+
var discoverOpenCodeSourcesEffect = Effect23.fn("discoverOpenCodeSourcesEffect")(function* (root, cutoff) {
|
|
3721
5078
|
const io = yield* OpenCodeIo;
|
|
3722
5079
|
return yield* io.discoverSources(root, cutoff);
|
|
3723
5080
|
});
|
|
3724
|
-
var parseOpenCodeEffect =
|
|
5081
|
+
var parseOpenCodeEffect = Effect23.fn(
|
|
3725
5082
|
"OpenCodeParser.parseOpenCodeEffect"
|
|
3726
|
-
)((opts) =>
|
|
3727
|
-
var parseOpenCodeStream = (opts) =>
|
|
3728
|
-
|
|
5083
|
+
)((opts) => Stream14.runCollect(parseOpenCodeStream(opts)));
|
|
5084
|
+
var parseOpenCodeStream = (opts) => Stream14.unwrap(
|
|
5085
|
+
Effect23.gen(function* () {
|
|
3729
5086
|
const logger = opts.logger ?? silentLogger;
|
|
3730
5087
|
const root = opts.root ?? resolveOpenCodeRoot();
|
|
3731
5088
|
const sources = yield* discoverOpenCodeSourcesEffect(root, opts.cutoff);
|
|
3732
5089
|
if (sources._tag === "Missing") {
|
|
3733
|
-
return
|
|
5090
|
+
return Stream14.empty;
|
|
3734
5091
|
}
|
|
3735
5092
|
if (sources._tag === "JsonFile") {
|
|
3736
|
-
return openCodeJsonFileStreams(
|
|
5093
|
+
return openCodeJsonFileStreams(Stream14.succeed(sources.path));
|
|
3737
5094
|
}
|
|
3738
5095
|
if (sources._tag === "DatabaseFiles") {
|
|
3739
5096
|
return openCodeDatabaseStreams(sources.paths, opts, logger);
|
|
@@ -3742,28 +5099,28 @@ var parseOpenCodeStream = (opts) => Stream13.unwrap(
|
|
|
3742
5099
|
})
|
|
3743
5100
|
);
|
|
3744
5101
|
function openCodeDatabaseStreams(paths, opts, logger) {
|
|
3745
|
-
let stream =
|
|
5102
|
+
let stream = Stream14.empty;
|
|
3746
5103
|
for (const dbPath of paths) {
|
|
3747
5104
|
stream = stream.pipe(
|
|
3748
|
-
|
|
3749
|
-
|
|
5105
|
+
Stream14.concat(
|
|
5106
|
+
Stream14.fromIterableEffect(bucketizeOpenCodeDatabaseEffect(dbPath, opts.cutoff, logger, opts.excludePath))
|
|
3750
5107
|
)
|
|
3751
5108
|
);
|
|
3752
5109
|
}
|
|
3753
5110
|
return stream;
|
|
3754
5111
|
}
|
|
3755
5112
|
function openCodeJsonFileStreams(paths) {
|
|
3756
|
-
return paths.pipe(
|
|
5113
|
+
return paths.pipe(Stream14.flatMap((file) => Stream14.fromIterableEffect(bucketizeOpenCodeJsonFileEffect(file))));
|
|
3757
5114
|
}
|
|
3758
5115
|
|
|
3759
5116
|
// ../../packages/parsers/src/pi/index.ts
|
|
3760
5117
|
import { basename as basename3, join as join7 } from "path";
|
|
3761
|
-
import { Effect as
|
|
5118
|
+
import { Effect as Effect25, Layer as Layer10, Stream as Stream16 } from "effect";
|
|
3762
5119
|
|
|
3763
5120
|
// ../../packages/parsers/src/pi/bucketize.ts
|
|
3764
5121
|
import { createReadStream as createReadStream3 } from "fs";
|
|
3765
5122
|
import { createInterface as createInterface4 } from "readline";
|
|
3766
|
-
import { Cause as Cause3, Effect as
|
|
5123
|
+
import { Cause as Cause3, Effect as Effect24, Queue as Queue3, Stream as Stream15 } from "effect";
|
|
3767
5124
|
|
|
3768
5125
|
// ../../packages/parsers/src/pi/events.ts
|
|
3769
5126
|
function isObject4(value) {
|
|
@@ -3842,9 +5199,9 @@ function key5(hourUtc, model) {
|
|
|
3842
5199
|
return `${hourUtc.toISOString()}|${model}`;
|
|
3843
5200
|
}
|
|
3844
5201
|
function lineStreamFromFile3(path) {
|
|
3845
|
-
return
|
|
3846
|
-
(queue) =>
|
|
3847
|
-
|
|
5202
|
+
return Stream15.callback(
|
|
5203
|
+
(queue) => Effect24.acquireRelease(
|
|
5204
|
+
Effect24.sync(() => {
|
|
3848
5205
|
let done = false;
|
|
3849
5206
|
const stream = createReadStream3(path, { encoding: "utf8" });
|
|
3850
5207
|
const rl = createInterface4({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
@@ -3881,7 +5238,7 @@ function lineStreamFromFile3(path) {
|
|
|
3881
5238
|
}
|
|
3882
5239
|
};
|
|
3883
5240
|
}),
|
|
3884
|
-
(resource) =>
|
|
5241
|
+
(resource) => Effect24.sync(() => resource.close())
|
|
3885
5242
|
)
|
|
3886
5243
|
);
|
|
3887
5244
|
}
|
|
@@ -3899,6 +5256,33 @@ function modelId2(provider, model) {
|
|
|
3899
5256
|
}
|
|
3900
5257
|
return `${trimmedProvider}/${trimmedModel}`;
|
|
3901
5258
|
}
|
|
5259
|
+
function hermesModelId(provider, model, fallbackProvider) {
|
|
5260
|
+
if (typeof model !== "string") {
|
|
5261
|
+
return void 0;
|
|
5262
|
+
}
|
|
5263
|
+
const trimmedModel = model.trim();
|
|
5264
|
+
if (trimmedModel.length === 0) {
|
|
5265
|
+
return void 0;
|
|
5266
|
+
}
|
|
5267
|
+
const directProvider = typeof provider === "string" ? provider.trim() : "";
|
|
5268
|
+
const fallback = typeof fallbackProvider === "string" ? fallbackProvider.trim() : "";
|
|
5269
|
+
const sourceProvider = directProvider.length > 0 ? directProvider : fallback;
|
|
5270
|
+
if (sourceProvider.length > 0) {
|
|
5271
|
+
const normalizedProvider = normalizeProvider(sourceProvider);
|
|
5272
|
+
if (trimmedModel.startsWith(`${sourceProvider}/`)) {
|
|
5273
|
+
return `${normalizedProvider}/${trimmedModel.slice(sourceProvider.length + 1)}`;
|
|
5274
|
+
}
|
|
5275
|
+
if (trimmedModel.startsWith(`${normalizedProvider}/`)) {
|
|
5276
|
+
return trimmedModel;
|
|
5277
|
+
}
|
|
5278
|
+
return `${normalizedProvider}/${trimmedModel}`;
|
|
5279
|
+
}
|
|
5280
|
+
const separator = trimmedModel.indexOf("/");
|
|
5281
|
+
if (separator <= 0) {
|
|
5282
|
+
return void 0;
|
|
5283
|
+
}
|
|
5284
|
+
return `${normalizeProvider(trimmedModel.slice(0, separator))}/${trimmedModel.slice(separator + 1)}`;
|
|
5285
|
+
}
|
|
3902
5286
|
function normalizeProvider(provider) {
|
|
3903
5287
|
return provider === "openai-codex" ? "openai" : provider;
|
|
3904
5288
|
}
|
|
@@ -3915,53 +5299,134 @@ function timestampMs2(value) {
|
|
|
3915
5299
|
}
|
|
3916
5300
|
return Math.abs(n) < UNIX_MILLISECONDS_THRESHOLD ? n * MILLISECONDS_PER_SECOND2 : n;
|
|
3917
5301
|
}
|
|
3918
|
-
function
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
5302
|
+
function normalizeHermesUsage(row) {
|
|
5303
|
+
return {
|
|
5304
|
+
inputTokens: positive2(row.inputTokens),
|
|
5305
|
+
outputTokens: positive2(row.outputTokens),
|
|
5306
|
+
cacheReadTokens: positive2(row.cacheReadTokens),
|
|
5307
|
+
cacheCreateTokens: positive2(row.cacheWriteTokens)
|
|
5308
|
+
};
|
|
5309
|
+
}
|
|
5310
|
+
function addHermesUsage(left, right) {
|
|
5311
|
+
return {
|
|
5312
|
+
inputTokens: left.inputTokens + right.inputTokens,
|
|
5313
|
+
outputTokens: left.outputTokens + right.outputTokens,
|
|
5314
|
+
cacheReadTokens: left.cacheReadTokens + right.cacheReadTokens,
|
|
5315
|
+
cacheCreateTokens: left.cacheCreateTokens + right.cacheCreateTokens
|
|
5316
|
+
};
|
|
5317
|
+
}
|
|
5318
|
+
function subtractHermesUsageFloorZero(current, baseline) {
|
|
5319
|
+
return {
|
|
5320
|
+
inputTokens: Math.max(0, current.inputTokens - baseline.inputTokens),
|
|
5321
|
+
outputTokens: Math.max(0, current.outputTokens - baseline.outputTokens),
|
|
5322
|
+
cacheReadTokens: Math.max(0, current.cacheReadTokens - baseline.cacheReadTokens),
|
|
5323
|
+
cacheCreateTokens: Math.max(0, current.cacheCreateTokens - baseline.cacheCreateTokens)
|
|
5324
|
+
};
|
|
5325
|
+
}
|
|
5326
|
+
function hasHermesUsage(usage) {
|
|
5327
|
+
return usage.inputTokens > 0 || usage.outputTokens > 0 || usage.cacheReadTokens > 0 || usage.cacheCreateTokens > 0;
|
|
5328
|
+
}
|
|
5329
|
+
function maxModelLastSeen(rows) {
|
|
5330
|
+
let latest;
|
|
5331
|
+
for (const row of rows) {
|
|
5332
|
+
const at = timestampMs2(row.lastSeen) ?? timestampMs2(row.firstSeen);
|
|
5333
|
+
if (at !== void 0 && (latest === void 0 || at > latest)) {
|
|
5334
|
+
latest = at;
|
|
5335
|
+
}
|
|
3933
5336
|
}
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheCreateTokens === 0) {
|
|
5337
|
+
return latest;
|
|
5338
|
+
}
|
|
5339
|
+
function makeHermesBucket(options) {
|
|
5340
|
+
if (!hasHermesUsage(options.usage)) {
|
|
3939
5341
|
return void 0;
|
|
3940
5342
|
}
|
|
3941
|
-
const startedAt = new Date(startedAtMs);
|
|
5343
|
+
const startedAt = new Date(options.startedAtMs);
|
|
3942
5344
|
return {
|
|
3943
5345
|
harness: "hermes",
|
|
3944
|
-
model,
|
|
3945
|
-
hourUtc: startOfUtcHour(
|
|
3946
|
-
inputTokens,
|
|
3947
|
-
outputTokens,
|
|
3948
|
-
cacheReadTokens,
|
|
3949
|
-
cacheCreateTokens,
|
|
3950
|
-
sessionIds: /* @__PURE__ */ new Set([`hermes:${sessionId}`]),
|
|
5346
|
+
model: options.model,
|
|
5347
|
+
hourUtc: startOfUtcHour(new Date(options.attributedAtMs)),
|
|
5348
|
+
inputTokens: options.usage.inputTokens,
|
|
5349
|
+
outputTokens: options.usage.outputTokens,
|
|
5350
|
+
cacheReadTokens: options.usage.cacheReadTokens,
|
|
5351
|
+
cacheCreateTokens: options.usage.cacheCreateTokens,
|
|
5352
|
+
sessionIds: /* @__PURE__ */ new Set([`hermes:${options.sessionId}`]),
|
|
3951
5353
|
sessionStartedAt: startedAt,
|
|
3952
|
-
sessionLastSeenAt: new Date(lastSeenAtMs)
|
|
5354
|
+
sessionLastSeenAt: new Date(options.lastSeenAtMs)
|
|
3953
5355
|
};
|
|
3954
5356
|
}
|
|
3955
|
-
function
|
|
5357
|
+
function bucketizeHermesModelUsage(options) {
|
|
3956
5358
|
const buckets = [];
|
|
3957
|
-
|
|
3958
|
-
|
|
5359
|
+
let attributedUsage = {
|
|
5360
|
+
cacheCreateTokens: 0,
|
|
5361
|
+
cacheReadTokens: 0,
|
|
5362
|
+
inputTokens: 0,
|
|
5363
|
+
outputTokens: 0
|
|
5364
|
+
};
|
|
5365
|
+
for (const usageRow of options.rows) {
|
|
5366
|
+
const model = hermesModelId(usageRow.billingProvider, usageRow.model, options.fallbackProvider);
|
|
5367
|
+
if (model === void 0) {
|
|
5368
|
+
continue;
|
|
5369
|
+
}
|
|
5370
|
+
const usage = normalizeHermesUsage(usageRow);
|
|
5371
|
+
const bucket = makeHermesBucket({
|
|
5372
|
+
attributedAtMs: timestampMs2(usageRow.firstSeen) ?? options.startedAtMs,
|
|
5373
|
+
lastSeenAtMs: options.lastSeenAtMs,
|
|
5374
|
+
model,
|
|
5375
|
+
sessionId: options.sessionId,
|
|
5376
|
+
startedAtMs: options.startedAtMs,
|
|
5377
|
+
usage
|
|
5378
|
+
});
|
|
3959
5379
|
if (bucket !== void 0) {
|
|
3960
5380
|
buckets.push(bucket);
|
|
5381
|
+
attributedUsage = addHermesUsage(attributedUsage, usage);
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
return { attributedUsage, buckets };
|
|
5385
|
+
}
|
|
5386
|
+
function hermesRowToBuckets(row, excludePath) {
|
|
5387
|
+
if (typeof row.cwd === "string" && excludePath?.(row.cwd) === true) {
|
|
5388
|
+
return [];
|
|
5389
|
+
}
|
|
5390
|
+
const sessionId = typeof row.id === "string" ? row.id : void 0;
|
|
5391
|
+
const startedAtMs = timestampMs2(row.startedAt);
|
|
5392
|
+
if (sessionId === void 0 || startedAtMs === void 0) {
|
|
5393
|
+
return [];
|
|
5394
|
+
}
|
|
5395
|
+
const modelUsageRows2 = row.modelUsageRows ?? [];
|
|
5396
|
+
const lastSeenAtMs = timestampMs2(row.endedAt) ?? maxModelLastSeen(modelUsageRows2) ?? startedAtMs;
|
|
5397
|
+
const modelUsage = bucketizeHermesModelUsage({
|
|
5398
|
+
fallbackProvider: row.billingProvider,
|
|
5399
|
+
lastSeenAtMs,
|
|
5400
|
+
rows: modelUsageRows2,
|
|
5401
|
+
sessionId,
|
|
5402
|
+
startedAtMs
|
|
5403
|
+
});
|
|
5404
|
+
const buckets = [...modelUsage.buckets];
|
|
5405
|
+
const aggregateUsage = normalizeHermesUsage(row);
|
|
5406
|
+
const residualUsage = subtractHermesUsageFloorZero(aggregateUsage, modelUsage.attributedUsage);
|
|
5407
|
+
const residualModel = hermesModelId(row.billingProvider, row.model);
|
|
5408
|
+
if (residualModel !== void 0) {
|
|
5409
|
+
const residual = makeHermesBucket({
|
|
5410
|
+
attributedAtMs: startedAtMs,
|
|
5411
|
+
lastSeenAtMs,
|
|
5412
|
+
model: residualModel,
|
|
5413
|
+
sessionId,
|
|
5414
|
+
startedAtMs,
|
|
5415
|
+
usage: residualUsage
|
|
5416
|
+
});
|
|
5417
|
+
if (residual !== void 0) {
|
|
5418
|
+
buckets.push(residual);
|
|
3961
5419
|
}
|
|
3962
5420
|
}
|
|
3963
5421
|
return buckets;
|
|
3964
5422
|
}
|
|
5423
|
+
function bucketizeHermesRows(rows, excludePath) {
|
|
5424
|
+
const buckets = [];
|
|
5425
|
+
for (const row of rows) {
|
|
5426
|
+
buckets.push(...hermesRowToBuckets(row, excludePath));
|
|
5427
|
+
}
|
|
5428
|
+
return buckets;
|
|
5429
|
+
}
|
|
3965
5430
|
function makePiStyleFileAccumulator(options) {
|
|
3966
5431
|
return {
|
|
3967
5432
|
buckets: /* @__PURE__ */ new Map(),
|
|
@@ -4054,12 +5519,11 @@ function accumulatePiStyleLine(context, acc, raw) {
|
|
|
4054
5519
|
existing.cacheCreateTokens += cacheCreateTokens;
|
|
4055
5520
|
return acc;
|
|
4056
5521
|
}
|
|
4057
|
-
function
|
|
5522
|
+
function bucketsFromAccumulator2(path, harness, acc) {
|
|
4058
5523
|
if (acc.excluded || acc.sessionStartedAt === void 0 || acc.sessionLastSeenAt === void 0) {
|
|
4059
5524
|
return [];
|
|
4060
5525
|
}
|
|
4061
|
-
const sessionStartedAt = acc
|
|
4062
|
-
const sessionLastSeenAt = acc.sessionLastSeenAt;
|
|
5526
|
+
const { sessionLastSeenAt, sessionStartedAt } = acc;
|
|
4063
5527
|
const sessionIds = /* @__PURE__ */ new Set([path]);
|
|
4064
5528
|
return Array.from(acc.buckets.values(), (bucket) => ({
|
|
4065
5529
|
harness,
|
|
@@ -4076,17 +5540,17 @@ function bucketsFromAccumulator3(path, harness, acc) {
|
|
|
4076
5540
|
}
|
|
4077
5541
|
function bucketizePiStyleFileStream(path, harness, logger, options = {}) {
|
|
4078
5542
|
const context = { logger, options, path };
|
|
4079
|
-
return
|
|
5543
|
+
return Stream15.unwrap(
|
|
4080
5544
|
lineStreamFromFile3(path).pipe(
|
|
4081
|
-
|
|
5545
|
+
Stream15.runFold(
|
|
4082
5546
|
() => makePiStyleFileAccumulator(options),
|
|
4083
5547
|
(acc, raw) => accumulatePiStyleLine(context, acc, raw)
|
|
4084
5548
|
),
|
|
4085
|
-
|
|
5549
|
+
Effect24.map((acc) => Stream15.fromIterable(bucketsFromAccumulator2(path, harness, acc)))
|
|
4086
5550
|
)
|
|
4087
5551
|
);
|
|
4088
5552
|
}
|
|
4089
|
-
var bucketizeHermesDatabaseEffect =
|
|
5553
|
+
var bucketizeHermesDatabaseEffect = Effect24.fn(
|
|
4090
5554
|
"bucketizeHermesDatabaseEffect"
|
|
4091
5555
|
)(function* (path, cutoff, excludePath) {
|
|
4092
5556
|
const sqlite = yield* HermesSqlite;
|
|
@@ -4095,14 +5559,14 @@ var bucketizeHermesDatabaseEffect = Effect23.fn(
|
|
|
4095
5559
|
});
|
|
4096
5560
|
|
|
4097
5561
|
// ../../packages/parsers/src/pi/index.ts
|
|
4098
|
-
var isFileBestEffortEffect = (path) => pathKindEffect(path).pipe(
|
|
5562
|
+
var isFileBestEffortEffect = (path) => pathKindEffect(path).pipe(Effect25.map((kind) => kind === "file"));
|
|
4099
5563
|
function bucketizePiStyleSourcesStream(files, harness, logger, options) {
|
|
4100
5564
|
return files.pipe(
|
|
4101
|
-
|
|
5565
|
+
Stream16.flatMap((file) => bucketizePiStyleFileStream(file, harness, logger, options), { concurrency: 1 })
|
|
4102
5566
|
);
|
|
4103
5567
|
}
|
|
4104
|
-
var parsePiStyleStream = (opts, harness, defaultRoot) =>
|
|
4105
|
-
|
|
5568
|
+
var parsePiStyleStream = (opts, harness, defaultRoot) => Stream16.unwrap(
|
|
5569
|
+
Effect25.gen(function* () {
|
|
4106
5570
|
const logger = opts.logger ?? silentLogger;
|
|
4107
5571
|
const root = opts.root ?? defaultRoot();
|
|
4108
5572
|
const kind = yield* pathKindEffectStrict(root);
|
|
@@ -4120,25 +5584,23 @@ var parsePiStyleStream = (opts, harness, defaultRoot) => Stream15.unwrap(
|
|
|
4120
5584
|
});
|
|
4121
5585
|
})
|
|
4122
5586
|
);
|
|
4123
|
-
var parsePiEffect =
|
|
5587
|
+
var parsePiEffect = Effect25.fn("PiParser.parsePiEffect")((opts) => Stream16.runCollect(parsePiStream(opts)));
|
|
4124
5588
|
var parsePiStream = (opts) => parsePiStyleStream(opts, "pi", resolvePiRoot);
|
|
4125
5589
|
var parserLive = Layer10.mergeAll(FileWalkerLive, HermesSqliteLive);
|
|
4126
|
-
var parseHermesEffect =
|
|
5590
|
+
var parseHermesEffect = Effect25.fn(
|
|
4127
5591
|
"HermesParser.parseHermesEffect"
|
|
4128
|
-
)((opts) =>
|
|
4129
|
-
var parseHermesStream = (opts) =>
|
|
4130
|
-
|
|
5592
|
+
)((opts) => Stream16.runCollect(parseHermesStream(opts)));
|
|
5593
|
+
var parseHermesStream = (opts) => Stream16.unwrap(
|
|
5594
|
+
Effect25.gen(function* () {
|
|
4131
5595
|
const logger = opts.logger ?? silentLogger;
|
|
4132
5596
|
const root = opts.root ?? resolveHermesRoot();
|
|
4133
5597
|
const kind = yield* pathKindEffectStrict(root);
|
|
4134
5598
|
if (kind === "missing") {
|
|
4135
|
-
return asHermesStream(
|
|
5599
|
+
return asHermesStream(Stream16.empty);
|
|
4136
5600
|
}
|
|
4137
5601
|
if (kind === "file") {
|
|
4138
5602
|
if (basename3(root).endsWith(".db")) {
|
|
4139
|
-
return asHermesStream(
|
|
4140
|
-
Stream15.fromIterableEffect(bucketizeHermesDatabaseEffect(root, opts.cutoff, opts.excludePath))
|
|
4141
|
-
);
|
|
5603
|
+
return asHermesStream(collectHermesDatabaseStream(root, logger, opts));
|
|
4142
5604
|
}
|
|
4143
5605
|
return asHermesStream(
|
|
4144
5606
|
bucketizePiStyleFileStream(root, "hermes", logger, { cutoff: opts.cutoff, excludePath: opts.excludePath })
|
|
@@ -4146,9 +5608,7 @@ var parseHermesStream = (opts) => Stream15.unwrap(
|
|
|
4146
5608
|
}
|
|
4147
5609
|
const stateDb = join7(root, "state.db");
|
|
4148
5610
|
if (yield* isFileBestEffortEffect(stateDb)) {
|
|
4149
|
-
return asHermesStream(
|
|
4150
|
-
Stream15.fromIterableEffect(bucketizeHermesDatabaseEffect(stateDb, opts.cutoff, opts.excludePath))
|
|
4151
|
-
);
|
|
5611
|
+
return asHermesStream(collectHermesDatabaseStream(stateDb, logger, opts));
|
|
4152
5612
|
}
|
|
4153
5613
|
const sessionsRoot2 = join7(root, "sessions");
|
|
4154
5614
|
if ((yield* pathKindEffect(sessionsRoot2)) === "directory") {
|
|
@@ -4164,8 +5624,20 @@ var parseHermesStream = (opts) => Stream15.unwrap(
|
|
|
4164
5624
|
function asHermesStream(stream) {
|
|
4165
5625
|
return stream;
|
|
4166
5626
|
}
|
|
4167
|
-
|
|
4168
|
-
|
|
5627
|
+
function collectHermesDatabaseStream(path, logger, opts) {
|
|
5628
|
+
return Stream16.fromIterableEffect(bucketizeHermesDatabaseEffect(path, opts.cutoff, opts.excludePath)).pipe(
|
|
5629
|
+
Stream16.catchTag("HermesSqliteError", (error) => {
|
|
5630
|
+
if (error.operation !== "schema") {
|
|
5631
|
+
return Stream16.fail(error);
|
|
5632
|
+
}
|
|
5633
|
+
return Stream16.fromEffectDrain(
|
|
5634
|
+
Effect25.sync(() => logger.warn("unsupported hermes sqlite schema", { reason: "unsupported_schema" }))
|
|
5635
|
+
);
|
|
5636
|
+
})
|
|
5637
|
+
);
|
|
5638
|
+
}
|
|
5639
|
+
var collectHermesPiStyleDirectoryStream = (root, logger, opts) => Stream16.unwrap(
|
|
5640
|
+
Effect25.sync(() => {
|
|
4169
5641
|
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
4170
5642
|
return bucketizePiStyleSourcesStream(walkStreamStrict(root, opts.cutoff), "hermes", logger, {
|
|
4171
5643
|
cutoff: opts.cutoff,
|
|
@@ -4176,7 +5648,7 @@ var collectHermesPiStyleDirectoryStream = (root, logger, opts) => Stream15.unwra
|
|
|
4176
5648
|
);
|
|
4177
5649
|
|
|
4178
5650
|
// src/sync/parser-factory.ts
|
|
4179
|
-
import { Effect as
|
|
5651
|
+
import { Effect as Effect26, Layer as Layer11, Stream as Stream17 } from "effect";
|
|
4180
5652
|
var GLOB_PATTERN = /[*?[\]{}]/u;
|
|
4181
5653
|
var HermesParserLive = Layer11.mergeAll(FileWalkerLive, HermesSqliteLive);
|
|
4182
5654
|
function sessionsRoot(agentDir, sessionDir, fallbackAgentDir) {
|
|
@@ -4241,7 +5713,7 @@ function createExcludeMatcher(options) {
|
|
|
4241
5713
|
};
|
|
4242
5714
|
}
|
|
4243
5715
|
function resolveParserSourcePlanEffect(options = {}) {
|
|
4244
|
-
return
|
|
5716
|
+
return Effect26.gen(function* () {
|
|
4245
5717
|
const sources = yield* ParserSources;
|
|
4246
5718
|
const home = yield* sources.homeDir;
|
|
4247
5719
|
const env = options.env ?? (yield* sources.cliEnv);
|
|
@@ -4280,49 +5752,43 @@ function resolveParserSourcePlanEffect(options = {}) {
|
|
|
4280
5752
|
});
|
|
4281
5753
|
}
|
|
4282
5754
|
function provideFileWalker(stream) {
|
|
4283
|
-
return stream.pipe(
|
|
4284
|
-
}
|
|
4285
|
-
function
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
5755
|
+
return stream.pipe(Stream17.provide(FileWalkerLive));
|
|
5756
|
+
}
|
|
5757
|
+
function codexStreamForPlan(plan, cutoff, excludePath) {
|
|
5758
|
+
if (plan.codexSessionsSourceExisted) {
|
|
5759
|
+
return provideFileWalker(
|
|
5760
|
+
parseCodexStream({
|
|
5761
|
+
archivedRoot: plan.codexArchivedSourceExisted ? plan.codexArchivedRoot : void 0,
|
|
5762
|
+
cutoff,
|
|
5763
|
+
excludePath,
|
|
5764
|
+
root: plan.codexSessionsRoot
|
|
5765
|
+
})
|
|
5766
|
+
);
|
|
4289
5767
|
}
|
|
4290
|
-
|
|
5768
|
+
if (plan.codexArchivedSourceExisted) {
|
|
5769
|
+
return provideFileWalker(
|
|
5770
|
+
parseCodexStream({ cutoff, excludePath, root: plan.codexArchivedRoot })
|
|
5771
|
+
);
|
|
5772
|
+
}
|
|
5773
|
+
return Stream17.empty;
|
|
4291
5774
|
}
|
|
4292
5775
|
function buildFactoryFromPlan(plan) {
|
|
4293
5776
|
const excludePath = createExcludeMatcher({ exclude: plan.exclude, home: plan.home });
|
|
4294
|
-
return (ctx) => {
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
}
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
parseCodexStream({ cutoff: ctx.cutoff, excludePath, root: plan.codexArchivedRoot })
|
|
4307
|
-
)
|
|
4308
|
-
);
|
|
4309
|
-
}
|
|
4310
|
-
return {
|
|
4311
|
-
anySourceExisted: plan.anySourceExisted,
|
|
4312
|
-
codex: concatParserStreams(codexStreams),
|
|
4313
|
-
claudeCode: plan.claudeSourceExisted ? provideFileWalker(parseClaudeStream({ cutoff: ctx.cutoff, excludePath, root: plan.claudeRoot })) : Stream16.empty,
|
|
4314
|
-
pi: plan.piSourceExisted ? provideFileWalker(parsePiStream({ cutoff: ctx.cutoff, excludePath, root: plan.piRoot })) : Stream16.empty,
|
|
4315
|
-
hermes: plan.hermesSourceExisted ? parseHermesStream({ cutoff: ctx.cutoff, excludePath, root: plan.hermesRoot }).pipe(
|
|
4316
|
-
Stream16.provide(HermesParserLive)
|
|
4317
|
-
) : Stream16.empty,
|
|
4318
|
-
opencode: plan.opencodeSourceExisted ? parseOpenCodeStream({ cutoff: ctx.cutoff, excludePath, root: plan.opencodeRoot }).pipe(
|
|
4319
|
-
Stream16.provide(OpenCodeIoLive)
|
|
4320
|
-
) : Stream16.empty
|
|
4321
|
-
};
|
|
4322
|
-
};
|
|
5777
|
+
return (ctx) => ({
|
|
5778
|
+
anySourceExisted: plan.anySourceExisted,
|
|
5779
|
+
codex: codexStreamForPlan(plan, ctx.cutoff, excludePath),
|
|
5780
|
+
claudeCode: plan.claudeSourceExisted ? provideFileWalker(parseClaudeStream({ cutoff: ctx.cutoff, excludePath, root: plan.claudeRoot })) : Stream17.empty,
|
|
5781
|
+
pi: plan.piSourceExisted ? provideFileWalker(parsePiStream({ cutoff: ctx.cutoff, excludePath, root: plan.piRoot })) : Stream17.empty,
|
|
5782
|
+
hermes: plan.hermesSourceExisted ? parseHermesStream({ cutoff: ctx.cutoff, excludePath, root: plan.hermesRoot }).pipe(
|
|
5783
|
+
Stream17.provide(HermesParserLive)
|
|
5784
|
+
) : Stream17.empty,
|
|
5785
|
+
opencode: plan.opencodeSourceExisted ? parseOpenCodeStream({ cutoff: ctx.cutoff, excludePath, root: plan.opencodeRoot }).pipe(
|
|
5786
|
+
Stream17.provide(OpenCodeIoLive)
|
|
5787
|
+
) : Stream17.empty
|
|
5788
|
+
});
|
|
4323
5789
|
}
|
|
4324
5790
|
function buildRealParserFactoryEffect(options = {}) {
|
|
4325
|
-
return
|
|
5791
|
+
return Effect26.map(resolveParserSourcePlanEffect(options), buildFactoryFromPlan);
|
|
4326
5792
|
}
|
|
4327
5793
|
|
|
4328
5794
|
// src/index.ts
|
|
@@ -4344,7 +5810,7 @@ function optionalValue(value) {
|
|
|
4344
5810
|
return Option4.getOrUndefined(value);
|
|
4345
5811
|
}
|
|
4346
5812
|
function isEntrypoint() {
|
|
4347
|
-
const [, argvPath] =
|
|
5813
|
+
const [, argvPath] = process2.argv;
|
|
4348
5814
|
return argvPath ? realpathSync(fileURLToPath2(import.meta.url)) === realpathSync(argvPath) : false;
|
|
4349
5815
|
}
|
|
4350
5816
|
function parseHarnessFilter(value) {
|
|
@@ -4356,8 +5822,40 @@ function parseHarnessFilter(value) {
|
|
|
4356
5822
|
}
|
|
4357
5823
|
throw new SyncError(EXIT.invalidArguments, `error: --harness must be one of ${SUPPORTED_HARNESSES.join("|")}`);
|
|
4358
5824
|
}
|
|
5825
|
+
function parseReplacementFlags(raw) {
|
|
5826
|
+
const startHourUtc = optionalValue(raw.replaceStart);
|
|
5827
|
+
const endHourUtc = optionalValue(raw.replaceEnd);
|
|
5828
|
+
const harnessList = optionalValue(raw.replaceHarnesses);
|
|
5829
|
+
if (startHourUtc === void 0 && endHourUtc === void 0 && harnessList === void 0) {
|
|
5830
|
+
if (raw.confirmReplacement) {
|
|
5831
|
+
throw new SyncError(EXIT.invalidArguments, "error: --confirm-replacement requires an atomic replacement scope");
|
|
5832
|
+
}
|
|
5833
|
+
return void 0;
|
|
5834
|
+
}
|
|
5835
|
+
if (!(startHourUtc && endHourUtc && harnessList)) {
|
|
5836
|
+
throw new SyncError(
|
|
5837
|
+
EXIT.invalidArguments,
|
|
5838
|
+
"error: --replace-start, --replace-end, and --replace-harnesses must be provided together"
|
|
5839
|
+
);
|
|
5840
|
+
}
|
|
5841
|
+
const harnesses = harnessList.split(",").map((value) => parseHarnessFilter(value.trim()));
|
|
5842
|
+
if (harnesses.some((harness) => harness === void 0)) {
|
|
5843
|
+
throw new SyncError(EXIT.invalidArguments, "error: replacement harness list cannot be empty");
|
|
5844
|
+
}
|
|
5845
|
+
if (!(raw.dryRun || raw.confirmReplacement)) {
|
|
5846
|
+
throw new SyncError(
|
|
5847
|
+
EXIT.invalidArguments,
|
|
5848
|
+
"error: atomic replacement uploads require --confirm-replacement (use --dry-run to inspect first)"
|
|
5849
|
+
);
|
|
5850
|
+
}
|
|
5851
|
+
return {
|
|
5852
|
+
startHourUtc,
|
|
5853
|
+
endHourUtc,
|
|
5854
|
+
harnesses
|
|
5855
|
+
};
|
|
5856
|
+
}
|
|
4359
5857
|
function syncCommandErrorExitCode(err) {
|
|
4360
|
-
return
|
|
5858
|
+
return Effect27.gen(function* () {
|
|
4361
5859
|
const console = yield* CliConsole;
|
|
4362
5860
|
if (err instanceof CliConfigError) {
|
|
4363
5861
|
yield* console.writeStderrLine(err.message);
|
|
@@ -4372,7 +5870,7 @@ function syncCommandErrorExitCode(err) {
|
|
|
4372
5870
|
});
|
|
4373
5871
|
}
|
|
4374
5872
|
function setupCommandErrorExitCode(err) {
|
|
4375
|
-
return
|
|
5873
|
+
return Effect27.gen(function* () {
|
|
4376
5874
|
const console = yield* CliConsole;
|
|
4377
5875
|
if (err instanceof CliConfigError || err instanceof SetupError) {
|
|
4378
5876
|
yield* console.writeStderrLine(err.message);
|
|
@@ -4384,26 +5882,27 @@ function setupCommandErrorExitCode(err) {
|
|
|
4384
5882
|
}
|
|
4385
5883
|
function recoverCommandExitCode(effect) {
|
|
4386
5884
|
return effect.pipe(
|
|
4387
|
-
|
|
4388
|
-
|
|
5885
|
+
Effect27.catch((err) => syncCommandErrorExitCode(err)),
|
|
5886
|
+
Effect27.catchDefect((err) => syncCommandErrorExitCode(err))
|
|
4389
5887
|
);
|
|
4390
5888
|
}
|
|
4391
5889
|
function recoverSetupExitCode(effect) {
|
|
4392
5890
|
return effect.pipe(
|
|
4393
|
-
|
|
4394
|
-
|
|
5891
|
+
Effect27.catch((err) => setupCommandErrorExitCode(err)),
|
|
5892
|
+
Effect27.catchDefect((err) => setupCommandErrorExitCode(err))
|
|
4395
5893
|
);
|
|
4396
5894
|
}
|
|
4397
5895
|
function exitCodeToCommandEffect(exitCode) {
|
|
4398
|
-
return exitCode === EXIT.success ?
|
|
5896
|
+
return exitCode === EXIT.success ? Effect27.void : Effect27.fail(new CliExit(exitCode));
|
|
4399
5897
|
}
|
|
4400
5898
|
function runSyncCommand(raw) {
|
|
4401
5899
|
return recoverCommandExitCode(
|
|
4402
|
-
|
|
5900
|
+
Effect27.gen(function* () {
|
|
4403
5901
|
const configReader = yield* CliConfigReader;
|
|
4404
5902
|
const console = yield* CliConsole;
|
|
5903
|
+
const metadata = yield* CliPackageMetadata;
|
|
4405
5904
|
const env = yield* configReader.read();
|
|
4406
|
-
const config = yield*
|
|
5905
|
+
const config = yield* Effect27.try({
|
|
4407
5906
|
try: () => resolveSyncConfig(
|
|
4408
5907
|
{
|
|
4409
5908
|
api: optionalValue(raw.api),
|
|
@@ -4417,10 +5916,14 @@ function runSyncCommand(raw) {
|
|
|
4417
5916
|
catch: (err) => err
|
|
4418
5917
|
});
|
|
4419
5918
|
const { device, token } = config;
|
|
4420
|
-
const harness = yield*
|
|
5919
|
+
const harness = yield* Effect27.try({
|
|
4421
5920
|
try: () => parseHarnessFilter(optionalValue(raw.harness)),
|
|
4422
5921
|
catch: (err) => err
|
|
4423
5922
|
});
|
|
5923
|
+
const replacement = yield* Effect27.try({
|
|
5924
|
+
try: () => parseReplacementFlags(raw),
|
|
5925
|
+
catch: (err) => err
|
|
5926
|
+
});
|
|
4424
5927
|
if (!(token && device)) {
|
|
4425
5928
|
yield* console.writeStderrLine(
|
|
4426
5929
|
"error: --token and --device are required (or set ~/.oopst.yml / OOPST_TOKEN / OOPST_DEVICE)"
|
|
@@ -4428,7 +5931,7 @@ function runSyncCommand(raw) {
|
|
|
4428
5931
|
return EXIT.invalidArguments;
|
|
4429
5932
|
}
|
|
4430
5933
|
const factory = yield* buildRealParserFactoryEffect({ env, exclude: config.exclude }).pipe(
|
|
4431
|
-
|
|
5934
|
+
Effect27.provide(ParserSourcesLive)
|
|
4432
5935
|
);
|
|
4433
5936
|
let sourcesExistedHint = false;
|
|
4434
5937
|
const parserFactory = (ctx) => {
|
|
@@ -4438,10 +5941,12 @@ function runSyncCommand(raw) {
|
|
|
4438
5941
|
};
|
|
4439
5942
|
const result = yield* runSyncProgram2({
|
|
4440
5943
|
token,
|
|
5944
|
+
cliVersion: metadata.version,
|
|
4441
5945
|
device,
|
|
4442
5946
|
serverUrl: config.serverUrl,
|
|
4443
5947
|
lookbackDays: config.lookbackDays,
|
|
4444
5948
|
harness,
|
|
5949
|
+
replacement,
|
|
4445
5950
|
dryRun: raw.dryRun,
|
|
4446
5951
|
verbose: raw.verbose,
|
|
4447
5952
|
color: yield* console.stdoutIsTTY,
|
|
@@ -4453,16 +5958,16 @@ function runSyncCommand(raw) {
|
|
|
4453
5958
|
});
|
|
4454
5959
|
return result.exitCode;
|
|
4455
5960
|
})
|
|
4456
|
-
).pipe(
|
|
5961
|
+
).pipe(Effect27.flatMap(exitCodeToCommandEffect));
|
|
4457
5962
|
}
|
|
4458
5963
|
function runSetupCommand(raw) {
|
|
4459
5964
|
return recoverSetupExitCode(
|
|
4460
|
-
|
|
5965
|
+
Effect27.gen(function* () {
|
|
4461
5966
|
const console = yield* CliConsole;
|
|
4462
5967
|
const metadata = yield* CliPackageMetadata;
|
|
4463
5968
|
const cliProcess = yield* CliProcess;
|
|
4464
5969
|
const nodePath = yield* cliProcess.execPath;
|
|
4465
|
-
const result = yield*
|
|
5970
|
+
const result = yield* Effect27.tryPromise({
|
|
4466
5971
|
try: () => runSetup({
|
|
4467
5972
|
api: optionalValue(raw.api),
|
|
4468
5973
|
server: optionalValue(raw.server),
|
|
@@ -4475,7 +5980,7 @@ function runSetupCommand(raw) {
|
|
|
4475
5980
|
});
|
|
4476
5981
|
return result.exitCode;
|
|
4477
5982
|
})
|
|
4478
|
-
).pipe(
|
|
5983
|
+
).pipe(Effect27.flatMap(exitCodeToCommandEffect));
|
|
4479
5984
|
}
|
|
4480
5985
|
var apiFlag = Flag.string("api").pipe(
|
|
4481
5986
|
Flag.withDescription("API base URL (or server.api / OOPST_SERVER)"),
|
|
@@ -4498,6 +6003,22 @@ var harnessFlag = Flag.string("harness").pipe(
|
|
|
4498
6003
|
Flag.withDescription("Restrict to codex|claude_code|pi|hermes|opencode"),
|
|
4499
6004
|
Flag.optional
|
|
4500
6005
|
);
|
|
6006
|
+
var replaceStartFlag = Flag.string("replace-start").pipe(
|
|
6007
|
+
Flag.withDescription("Atomic replacement start as an inclusive UTC hour"),
|
|
6008
|
+
Flag.optional
|
|
6009
|
+
);
|
|
6010
|
+
var replaceEndFlag = Flag.string("replace-end").pipe(
|
|
6011
|
+
Flag.withDescription("Atomic replacement end as an exclusive UTC hour"),
|
|
6012
|
+
Flag.optional
|
|
6013
|
+
);
|
|
6014
|
+
var replaceHarnessesFlag = Flag.string("replace-harnesses").pipe(
|
|
6015
|
+
Flag.withDescription("Comma-separated harness set for an atomic replacement"),
|
|
6016
|
+
Flag.optional
|
|
6017
|
+
);
|
|
6018
|
+
var confirmReplacementFlag = Flag.boolean("confirm-replacement").pipe(
|
|
6019
|
+
Flag.withDescription("Confirm that the declared replacement scope may overwrite live facts"),
|
|
6020
|
+
Flag.withDefault(false)
|
|
6021
|
+
);
|
|
4501
6022
|
var dryRunFlag = Flag.boolean("dry-run").pipe(
|
|
4502
6023
|
Flag.withDescription("Parse and print without uploading"),
|
|
4503
6024
|
Flag.withDefault(false)
|
|
@@ -4513,6 +6034,10 @@ function buildCli() {
|
|
|
4513
6034
|
server: serverFlag,
|
|
4514
6035
|
lookbackDays: lookbackDaysFlag,
|
|
4515
6036
|
harness: harnessFlag,
|
|
6037
|
+
replaceStart: replaceStartFlag,
|
|
6038
|
+
replaceEnd: replaceEndFlag,
|
|
6039
|
+
replaceHarnesses: replaceHarnessesFlag,
|
|
6040
|
+
confirmReplacement: confirmReplacementFlag,
|
|
4516
6041
|
dryRun: dryRunFlag,
|
|
4517
6042
|
verbose: verboseFlag
|
|
4518
6043
|
},
|
|
@@ -4532,10 +6057,10 @@ function buildCli() {
|
|
|
4532
6057
|
);
|
|
4533
6058
|
}
|
|
4534
6059
|
function runCli() {
|
|
4535
|
-
return
|
|
6060
|
+
return Effect27.gen(function* () {
|
|
4536
6061
|
const metadata = yield* CliPackageMetadata;
|
|
4537
6062
|
yield* buildCli().pipe(Command.run({ version: metadata.version }));
|
|
4538
|
-
}).pipe(
|
|
6063
|
+
}).pipe(Effect27.provide(CliRuntimeLayer));
|
|
4539
6064
|
}
|
|
4540
6065
|
if (isEntrypoint()) {
|
|
4541
6066
|
runCli().pipe(NodeRuntime.runMain);
|