@cookielab.io/oopst 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2349 -872
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { realpathSync } from "fs";
|
|
|
7
7
|
import process4 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
|
|
@@ -524,220 +524,74 @@ var ParserSourcesLive = Layer2.effect(
|
|
|
524
524
|
import { Context as Context6, Effect as Effect10, Layer as Layer5 } from "effect";
|
|
525
525
|
|
|
526
526
|
// src/sync/run.ts
|
|
527
|
-
import {
|
|
528
|
-
|
|
529
|
-
// src/sync/aggregate.ts
|
|
530
|
-
import { createHmac } from "crypto";
|
|
531
|
-
import { Effect as Effect4, Schema as Schema3, Stream as Stream2 } from "effect";
|
|
532
|
-
var UsageAggregationErrorFields = {
|
|
533
|
-
message: Schema3.String,
|
|
534
|
-
cause: Schema3.optionalKey(Schema3.Defect())
|
|
535
|
-
};
|
|
536
|
-
var UsageAggregationError = class extends Schema3.TaggedErrorClass()(
|
|
537
|
-
"UsageAggregationError",
|
|
538
|
-
UsageAggregationErrorFields
|
|
539
|
-
) {
|
|
540
|
-
};
|
|
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
|
-
}
|
|
527
|
+
import { createHash } from "crypto";
|
|
674
528
|
|
|
675
529
|
// ../../packages/api/src/errors.ts
|
|
676
|
-
import { Schema as
|
|
530
|
+
import { Schema as Schema3 } from "effect";
|
|
677
531
|
var ErrorFields = {
|
|
678
|
-
message:
|
|
532
|
+
message: Schema3.String
|
|
679
533
|
};
|
|
680
534
|
var InfrastructureErrorFields = {
|
|
681
535
|
...ErrorFields,
|
|
682
|
-
cause:
|
|
536
|
+
cause: Schema3.optionalKey(Schema3.Defect())
|
|
683
537
|
};
|
|
684
|
-
var UnauthorizedError = class extends
|
|
538
|
+
var UnauthorizedError = class extends Schema3.TaggedErrorClass()("UnauthorizedError", ErrorFields) {
|
|
685
539
|
};
|
|
686
|
-
var ForbiddenError = class extends
|
|
540
|
+
var ForbiddenError = class extends Schema3.TaggedErrorClass()("ForbiddenError", ErrorFields) {
|
|
687
541
|
};
|
|
688
|
-
var NotFoundError = class extends
|
|
542
|
+
var NotFoundError = class extends Schema3.TaggedErrorClass()("NotFoundError", ErrorFields) {
|
|
689
543
|
};
|
|
690
|
-
var ConflictError = class extends
|
|
544
|
+
var ConflictError = class extends Schema3.TaggedErrorClass()("ConflictError", ErrorFields) {
|
|
691
545
|
};
|
|
692
|
-
var ValidationError = class extends
|
|
546
|
+
var ValidationError = class extends Schema3.TaggedErrorClass()("ValidationError", ErrorFields) {
|
|
693
547
|
};
|
|
694
|
-
var RateLimitedError = class extends
|
|
548
|
+
var RateLimitedError = class extends Schema3.TaggedErrorClass()("RateLimitedError", ErrorFields) {
|
|
695
549
|
};
|
|
696
|
-
var ExternalHttpError = class extends
|
|
550
|
+
var ExternalHttpError = class extends Schema3.TaggedErrorClass()(
|
|
697
551
|
"ExternalHttpError",
|
|
698
552
|
InfrastructureErrorFields
|
|
699
553
|
) {
|
|
700
554
|
};
|
|
701
|
-
var DatabaseError = class extends
|
|
555
|
+
var DatabaseError = class extends Schema3.TaggedErrorClass()(
|
|
702
556
|
"DatabaseError",
|
|
703
557
|
InfrastructureErrorFields
|
|
704
558
|
) {
|
|
705
559
|
};
|
|
706
|
-
var AppConfigError = class extends
|
|
560
|
+
var AppConfigError = class extends Schema3.TaggedErrorClass()(
|
|
707
561
|
"AppConfigError",
|
|
708
562
|
InfrastructureErrorFields
|
|
709
563
|
) {
|
|
710
564
|
};
|
|
711
|
-
var PricingRefreshError = class extends
|
|
565
|
+
var PricingRefreshError = class extends Schema3.TaggedErrorClass()(
|
|
712
566
|
"PricingRefreshError",
|
|
713
567
|
InfrastructureErrorFields
|
|
714
568
|
) {
|
|
715
569
|
};
|
|
716
|
-
var ParserSourceError2 = class extends
|
|
570
|
+
var ParserSourceError2 = class extends Schema3.TaggedErrorClass()(
|
|
717
571
|
"ParserSourceError",
|
|
718
572
|
InfrastructureErrorFields
|
|
719
573
|
) {
|
|
720
574
|
};
|
|
721
|
-
var SyncQuotaExceededError = class extends
|
|
575
|
+
var SyncQuotaExceededError = class extends Schema3.TaggedErrorClass()(
|
|
722
576
|
"SyncQuotaExceededError",
|
|
723
577
|
ErrorFields
|
|
724
578
|
) {
|
|
725
579
|
};
|
|
726
|
-
var SyncTimestampRejectedError = class extends
|
|
580
|
+
var SyncTimestampRejectedError = class extends Schema3.TaggedErrorClass()(
|
|
727
581
|
"SyncTimestampRejectedError",
|
|
728
582
|
ErrorFields
|
|
729
583
|
) {
|
|
730
584
|
};
|
|
731
585
|
|
|
732
586
|
// ../../packages/api/src/rpc/auth.ts
|
|
733
|
-
import { Context as Context3, Schema as
|
|
587
|
+
import { Context as Context3, Schema as Schema6 } from "effect";
|
|
734
588
|
import { Rpc, RpcGroup } from "effect/unstable/rpc";
|
|
735
589
|
|
|
736
590
|
// ../../packages/api/src/schemas/auth.ts
|
|
737
|
-
import { Schema as
|
|
591
|
+
import { Schema as Schema4, SchemaGetter } from "effect";
|
|
738
592
|
var MAX_DISPLAY_NAME_LENGTH = 80;
|
|
739
|
-
var displayNameInput =
|
|
740
|
-
|
|
593
|
+
var displayNameInput = Schema4.String.check(Schema4.isMaxLength(MAX_DISPLAY_NAME_LENGTH)).pipe(
|
|
594
|
+
Schema4.decodeTo(Schema4.NullOr(Schema4.String), {
|
|
741
595
|
decode: SchemaGetter.transform((value) => {
|
|
742
596
|
const trimmed = value.trim();
|
|
743
597
|
return trimmed.length === 0 ? null : trimmed;
|
|
@@ -745,12 +599,12 @@ var displayNameInput = Schema5.String.check(Schema5.isMaxLength(MAX_DISPLAY_NAME
|
|
|
745
599
|
encode: SchemaGetter.transform((value) => value ?? "")
|
|
746
600
|
})
|
|
747
601
|
);
|
|
748
|
-
var updateProfileInput =
|
|
602
|
+
var updateProfileInput = Schema4.Struct({
|
|
749
603
|
displayName: displayNameInput
|
|
750
604
|
});
|
|
751
605
|
|
|
752
606
|
// ../../packages/api/src/schemas/common.ts
|
|
753
|
-
import { Schema as
|
|
607
|
+
import { Schema as Schema5 } from "effect";
|
|
754
608
|
var ISO_UTC_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
|
|
755
609
|
var CIVIL_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
|
|
756
610
|
var HEX64_PATTERN = /^[0-9a-f]{64}$/u;
|
|
@@ -795,49 +649,49 @@ var isApiUrl = (value) => {
|
|
|
795
649
|
return false;
|
|
796
650
|
}
|
|
797
651
|
};
|
|
798
|
-
var nonEmptyTrimmedString =
|
|
799
|
-
|
|
800
|
-
|
|
652
|
+
var nonEmptyTrimmedString = Schema5.String.check(
|
|
653
|
+
Schema5.isTrimmed(),
|
|
654
|
+
Schema5.isNonEmpty({ message: "Expected a non-empty string" })
|
|
801
655
|
);
|
|
802
|
-
var safeInteger =
|
|
803
|
-
|
|
804
|
-
|
|
656
|
+
var safeInteger = Schema5.Number.check(
|
|
657
|
+
Schema5.isInt(),
|
|
658
|
+
Schema5.isBetween({ minimum: Number.MIN_SAFE_INTEGER, maximum: Number.MAX_SAFE_INTEGER })
|
|
805
659
|
);
|
|
806
|
-
var nonNegativeSafeInteger = safeInteger.check(
|
|
807
|
-
var isoUtcString =
|
|
808
|
-
|
|
660
|
+
var nonNegativeSafeInteger = safeInteger.check(Schema5.isGreaterThanOrEqualTo(0));
|
|
661
|
+
var isoUtcString = Schema5.String.check(
|
|
662
|
+
Schema5.makeFilter(isExactUtcInstant, { expected: "an ISO-8601 UTC timestamp with millisecond precision" })
|
|
809
663
|
);
|
|
810
|
-
var civilDate =
|
|
811
|
-
|
|
664
|
+
var civilDate = Schema5.String.check(
|
|
665
|
+
Schema5.makeFilter(isCivilDate, { expected: "a valid calendar date formatted as YYYY-MM-DD" })
|
|
812
666
|
);
|
|
813
|
-
var ianaTimezone =
|
|
814
|
-
|
|
667
|
+
var ianaTimezone = Schema5.String.check(
|
|
668
|
+
Schema5.makeFilter(isIanaTimezone, { expected: "an IANA timezone identifier" })
|
|
815
669
|
);
|
|
816
|
-
var uuidV7 =
|
|
817
|
-
var generalUuid =
|
|
818
|
-
var hex64 =
|
|
819
|
-
|
|
670
|
+
var uuidV7 = Schema5.String.check(Schema5.isUUID(UUID_V7_VERSION)).pipe(Schema5.brand("UuidV7"));
|
|
671
|
+
var generalUuid = Schema5.String.check(Schema5.isUUID()).pipe(Schema5.brand("GeneralUuid"));
|
|
672
|
+
var hex64 = Schema5.String.check(
|
|
673
|
+
Schema5.isPattern(HEX64_PATTERN, { expected: "a 64-character lowercase hex string" })
|
|
820
674
|
);
|
|
821
|
-
var apiUrl =
|
|
675
|
+
var apiUrl = Schema5.String.check(Schema5.makeFilter(isApiUrl, { expected: "an HTTP(S) URL" }));
|
|
822
676
|
|
|
823
677
|
// ../../packages/api/src/rpc/auth.ts
|
|
824
|
-
var AuthUserResponse =
|
|
825
|
-
user:
|
|
826
|
-
id:
|
|
827
|
-
email:
|
|
828
|
-
displayName:
|
|
678
|
+
var AuthUserResponse = Schema6.Struct({
|
|
679
|
+
user: Schema6.Struct({
|
|
680
|
+
id: Schema6.String,
|
|
681
|
+
email: Schema6.String,
|
|
682
|
+
displayName: Schema6.NullOr(Schema6.String),
|
|
829
683
|
createdAt: isoUtcString
|
|
830
684
|
})
|
|
831
685
|
});
|
|
832
|
-
var LogoutResponse =
|
|
833
|
-
ok:
|
|
686
|
+
var LogoutResponse = Schema6.Struct({
|
|
687
|
+
ok: Schema6.Boolean
|
|
834
688
|
});
|
|
835
689
|
var WebRpcContext = class extends Context3.Service()("@oopst/api/WebRpcContext") {
|
|
836
690
|
};
|
|
837
691
|
var AuthRpcs = RpcGroup.make(
|
|
838
692
|
Rpc.make("auth.logout", {
|
|
839
693
|
success: LogoutResponse,
|
|
840
|
-
error:
|
|
694
|
+
error: Schema6.Union([DatabaseError, UnauthorizedError])
|
|
841
695
|
}),
|
|
842
696
|
Rpc.make("auth.me", {
|
|
843
697
|
success: AuthUserResponse,
|
|
@@ -846,42 +700,42 @@ var AuthRpcs = RpcGroup.make(
|
|
|
846
700
|
Rpc.make("auth.updateProfile", {
|
|
847
701
|
payload: updateProfileInput,
|
|
848
702
|
success: AuthUserResponse,
|
|
849
|
-
error:
|
|
703
|
+
error: Schema6.Union([DatabaseError, UnauthorizedError])
|
|
850
704
|
})
|
|
851
705
|
);
|
|
852
706
|
|
|
853
707
|
// ../../packages/api/src/rpc/devices.ts
|
|
854
|
-
import { Schema as
|
|
708
|
+
import { Schema as Schema8 } from "effect";
|
|
855
709
|
import { Rpc as Rpc2, RpcGroup as RpcGroup2 } from "effect/unstable/rpc";
|
|
856
710
|
|
|
857
711
|
// ../../packages/api/src/schemas/devices.ts
|
|
858
|
-
import { Schema as
|
|
712
|
+
import { Schema as Schema7 } from "effect";
|
|
859
713
|
var MIN_DEVICE_NAME_LENGTH = 1;
|
|
860
714
|
var MAX_DEVICE_NAME_LENGTH = 80;
|
|
861
715
|
var UUID_V7_VERSION2 = 7;
|
|
862
|
-
var deviceNameSchema =
|
|
863
|
-
|
|
864
|
-
|
|
716
|
+
var deviceNameSchema = Schema7.String.check(
|
|
717
|
+
Schema7.isMinLength(MIN_DEVICE_NAME_LENGTH),
|
|
718
|
+
Schema7.isMaxLength(MAX_DEVICE_NAME_LENGTH)
|
|
865
719
|
);
|
|
866
|
-
var editableDeviceNameSchema =
|
|
867
|
-
|
|
868
|
-
|
|
720
|
+
var editableDeviceNameSchema = Schema7.Trim.check(
|
|
721
|
+
Schema7.isMinLength(MIN_DEVICE_NAME_LENGTH),
|
|
722
|
+
Schema7.isMaxLength(MAX_DEVICE_NAME_LENGTH)
|
|
869
723
|
);
|
|
870
|
-
var deviceIdSchema =
|
|
871
|
-
var renameDeviceInput =
|
|
724
|
+
var deviceIdSchema = Schema7.String.check(Schema7.isUUID(UUID_V7_VERSION2));
|
|
725
|
+
var renameDeviceInput = Schema7.Struct({
|
|
872
726
|
id: deviceIdSchema,
|
|
873
727
|
name: editableDeviceNameSchema
|
|
874
728
|
});
|
|
875
|
-
var deleteDeviceInput =
|
|
729
|
+
var deleteDeviceInput = Schema7.Struct({
|
|
876
730
|
id: deviceIdSchema
|
|
877
731
|
});
|
|
878
|
-
var renameDeviceOutput =
|
|
732
|
+
var renameDeviceOutput = Schema7.Struct({
|
|
879
733
|
id: deviceIdSchema,
|
|
880
|
-
name:
|
|
734
|
+
name: Schema7.String,
|
|
881
735
|
lastSeen: isoUtcString
|
|
882
736
|
});
|
|
883
|
-
var deleteDeviceOutput =
|
|
884
|
-
ok:
|
|
737
|
+
var deleteDeviceOutput = Schema7.Struct({
|
|
738
|
+
ok: Schema7.Boolean
|
|
885
739
|
});
|
|
886
740
|
|
|
887
741
|
// ../../packages/api/src/rpc/devices.ts
|
|
@@ -889,27 +743,27 @@ var DevicesRpcs = RpcGroup2.make(
|
|
|
889
743
|
Rpc2.make("devices.rename", {
|
|
890
744
|
payload: renameDeviceInput,
|
|
891
745
|
success: renameDeviceOutput,
|
|
892
|
-
error:
|
|
746
|
+
error: Schema8.Union([ConflictError, DatabaseError, NotFoundError, UnauthorizedError])
|
|
893
747
|
}),
|
|
894
748
|
Rpc2.make("devices.delete", {
|
|
895
749
|
payload: deleteDeviceInput,
|
|
896
750
|
success: deleteDeviceOutput,
|
|
897
|
-
error:
|
|
751
|
+
error: Schema8.Union([DatabaseError, NotFoundError, UnauthorizedError])
|
|
898
752
|
})
|
|
899
753
|
);
|
|
900
754
|
|
|
901
755
|
// ../../packages/api/src/rpc/stats.ts
|
|
902
|
-
import { Schema as
|
|
756
|
+
import { Schema as Schema10 } from "effect";
|
|
903
757
|
import { Rpc as Rpc3, RpcGroup as RpcGroup3 } from "effect/unstable/rpc";
|
|
904
758
|
|
|
905
759
|
// ../../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 =
|
|
760
|
+
import { Effect as Effect4, Schema as Schema9 } from "effect";
|
|
761
|
+
var scopeEnum = Schema9.Literals(["all", "me"]);
|
|
762
|
+
var costStackByValueEnum = Schema9.Literals(["device", "harness", "model"]);
|
|
763
|
+
var costStackByEnum = Schema9.optional(costStackByValueEnum);
|
|
764
|
+
var usageDetailStackByEnum = Schema9.Literals(["device", "harness", "model"]);
|
|
765
|
+
var stackByValueEnum = Schema9.Literals(["device", "harness"]);
|
|
766
|
+
var stackByEnum = Schema9.optional(stackByValueEnum);
|
|
913
767
|
var MAX_TIME_ZONE_LENGTH = 64;
|
|
914
768
|
var DEFAULT_DAYS = 90;
|
|
915
769
|
var MAX_DAYS = 365;
|
|
@@ -927,16 +781,16 @@ var MAX_LEADERBOARD_LIMIT = 50;
|
|
|
927
781
|
var DEFAULT_LEADERBOARD_LIMIT = 10;
|
|
928
782
|
var UUID_V7_VERSION3 = 7;
|
|
929
783
|
var CIVIL_DATE_PATTERN2 = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/u;
|
|
930
|
-
var timeZoneSchema = ianaTimezone.check(
|
|
784
|
+
var timeZoneSchema = ianaTimezone.check(Schema9.isMaxLength(MAX_TIME_ZONE_LENGTH));
|
|
931
785
|
var civilDateSchema = civilDate;
|
|
932
|
-
var daysSchema = safeInteger.check(
|
|
933
|
-
var leaderboardLimitSchema = safeInteger.check(
|
|
934
|
-
var defaultTimeZoneSchema = timeZoneSchema.pipe(
|
|
935
|
-
var uuidSchema =
|
|
786
|
+
var daysSchema = safeInteger.check(Schema9.isBetween({ minimum: MIN_LEADERBOARD_LIMIT, maximum: MAX_DAYS })).pipe(Schema9.withDecodingDefaultTypeKey(Effect4.succeed(DEFAULT_DAYS)));
|
|
787
|
+
var leaderboardLimitSchema = safeInteger.check(Schema9.isBetween({ minimum: MIN_LEADERBOARD_LIMIT, maximum: MAX_LEADERBOARD_LIMIT })).pipe(Schema9.withDecodingDefaultTypeKey(Effect4.succeed(DEFAULT_LEADERBOARD_LIMIT)));
|
|
788
|
+
var defaultTimeZoneSchema = timeZoneSchema.pipe(Schema9.withDecodingDefaultTypeKey(Effect4.succeed("UTC")));
|
|
789
|
+
var uuidSchema = Schema9.String.check(Schema9.isUUID(UUID_V7_VERSION3));
|
|
936
790
|
var rangeFields = {
|
|
937
791
|
days: daysSchema,
|
|
938
|
-
endDate:
|
|
939
|
-
startDate:
|
|
792
|
+
endDate: Schema9.optional(civilDateSchema),
|
|
793
|
+
startDate: Schema9.optional(civilDateSchema)
|
|
940
794
|
};
|
|
941
795
|
function validateDateRange(input) {
|
|
942
796
|
if (!(input.startDate || input.endDate)) {
|
|
@@ -976,216 +830,216 @@ function parseCivilDateUtcMs(date) {
|
|
|
976
830
|
}
|
|
977
831
|
return parsed.getTime();
|
|
978
832
|
}
|
|
979
|
-
var dailyCostInput =
|
|
833
|
+
var dailyCostInput = Schema9.Struct({
|
|
980
834
|
scope: scopeEnum,
|
|
981
835
|
stackBy: costStackByEnum,
|
|
982
836
|
...rangeFields,
|
|
983
837
|
timeZone: defaultTimeZoneSchema
|
|
984
|
-
}).check(
|
|
985
|
-
var dailyPointSchema =
|
|
838
|
+
}).check(Schema9.makeFilter(validateDateRange));
|
|
839
|
+
var dailyPointSchema = Schema9.Struct({
|
|
986
840
|
/** Local civil date in YYYY-MM-DD for the requested stats timezone. */
|
|
987
841
|
date: civilDateSchema,
|
|
988
842
|
/** null when stackBy is undefined. */
|
|
989
|
-
seriesName:
|
|
843
|
+
seriesName: Schema9.NullOr(Schema9.String),
|
|
990
844
|
/** USD; 0 or positive. */
|
|
991
|
-
usd:
|
|
845
|
+
usd: Schema9.Number
|
|
992
846
|
});
|
|
993
|
-
var dailyCostOutput =
|
|
994
|
-
var dailySessionsInput =
|
|
847
|
+
var dailyCostOutput = Schema9.Array(dailyPointSchema);
|
|
848
|
+
var dailySessionsInput = Schema9.Struct({
|
|
995
849
|
scope: scopeEnum,
|
|
996
850
|
stackBy: stackByEnum,
|
|
997
851
|
...rangeFields,
|
|
998
852
|
timeZone: defaultTimeZoneSchema
|
|
999
|
-
}).check(
|
|
1000
|
-
var dailySessionsPointSchema =
|
|
853
|
+
}).check(Schema9.makeFilter(validateDateRange));
|
|
854
|
+
var dailySessionsPointSchema = Schema9.Struct({
|
|
1001
855
|
date: civilDateSchema,
|
|
1002
|
-
seriesName:
|
|
856
|
+
seriesName: Schema9.NullOr(Schema9.String),
|
|
1003
857
|
sessions: nonNegativeSafeInteger
|
|
1004
858
|
});
|
|
1005
|
-
var dailySessionsOutput =
|
|
859
|
+
var dailySessionsOutput = Schema9.Array(dailySessionsPointSchema);
|
|
1006
860
|
var weekdaySessionsInput = dailySessionsInput;
|
|
1007
|
-
var weekdaySessionsPointSchema =
|
|
861
|
+
var weekdaySessionsPointSchema = Schema9.Struct({
|
|
1008
862
|
/** Local ISO weekday for the requested stats timezone: 1=Monday, 7=Sunday. */
|
|
1009
|
-
weekday: safeInteger.check(
|
|
1010
|
-
seriesName:
|
|
863
|
+
weekday: safeInteger.check(Schema9.isBetween({ minimum: MIN_ISO_WEEKDAY, maximum: MAX_ISO_WEEKDAY })),
|
|
864
|
+
seriesName: Schema9.NullOr(Schema9.String),
|
|
1011
865
|
sessions: nonNegativeSafeInteger
|
|
1012
866
|
});
|
|
1013
|
-
var weekdaySessionsOutput =
|
|
867
|
+
var weekdaySessionsOutput = Schema9.Array(weekdaySessionsPointSchema);
|
|
1014
868
|
var hourlySessionsInput = dailySessionsInput;
|
|
1015
|
-
var hourlySessionsPointSchema =
|
|
869
|
+
var hourlySessionsPointSchema = Schema9.Struct({
|
|
1016
870
|
/** Local hour of day for the requested stats timezone: 0-23. */
|
|
1017
|
-
hour: safeInteger.check(
|
|
1018
|
-
seriesName:
|
|
871
|
+
hour: safeInteger.check(Schema9.isBetween({ minimum: MIN_HOUR_OF_DAY, maximum: MAX_HOUR_OF_DAY })),
|
|
872
|
+
seriesName: Schema9.NullOr(Schema9.String),
|
|
1019
873
|
sessions: nonNegativeSafeInteger
|
|
1020
874
|
});
|
|
1021
|
-
var hourlySessionsOutput =
|
|
1022
|
-
var summaryInput =
|
|
875
|
+
var hourlySessionsOutput = Schema9.Array(hourlySessionsPointSchema);
|
|
876
|
+
var summaryInput = Schema9.Struct({
|
|
1023
877
|
...rangeFields,
|
|
1024
878
|
scope: scopeEnum,
|
|
1025
879
|
timeZone: defaultTimeZoneSchema
|
|
1026
|
-
}).check(
|
|
1027
|
-
var summaryOutput =
|
|
1028
|
-
rangeUsd:
|
|
880
|
+
}).check(Schema9.makeFilter(validateDateRange));
|
|
881
|
+
var summaryOutput = Schema9.Struct({
|
|
882
|
+
rangeUsd: Schema9.Number.check(Schema9.isGreaterThanOrEqualTo(0)),
|
|
1029
883
|
rangeSessions: nonNegativeSafeInteger,
|
|
1030
884
|
rangeSubjectCount: nonNegativeSafeInteger,
|
|
1031
|
-
totalUsd:
|
|
885
|
+
totalUsd: Schema9.Number.check(Schema9.isGreaterThanOrEqualTo(0)),
|
|
1032
886
|
totalSessions: nonNegativeSafeInteger
|
|
1033
887
|
});
|
|
1034
|
-
var deviceSummarySchema =
|
|
888
|
+
var deviceSummarySchema = Schema9.Struct({
|
|
1035
889
|
id: uuidSchema,
|
|
1036
|
-
name:
|
|
890
|
+
name: Schema9.String,
|
|
1037
891
|
/** ISO-8601 UTC. */
|
|
1038
892
|
lastSeen: isoUtcString
|
|
1039
893
|
});
|
|
1040
|
-
var devicesOutput =
|
|
1041
|
-
var participantSyncSchema =
|
|
894
|
+
var devicesOutput = Schema9.Array(deviceSummarySchema);
|
|
895
|
+
var participantSyncSchema = Schema9.Struct({
|
|
1042
896
|
userId: uuidSchema,
|
|
1043
|
-
userLabel:
|
|
897
|
+
userLabel: Schema9.String,
|
|
1044
898
|
/** ISO-8601 UTC, null when the participant has no synced devices yet. */
|
|
1045
|
-
latestSyncAt:
|
|
899
|
+
latestSyncAt: Schema9.NullOr(isoUtcString)
|
|
1046
900
|
});
|
|
1047
|
-
var participantsOutput =
|
|
1048
|
-
var leaderboardInput =
|
|
901
|
+
var participantsOutput = Schema9.Array(participantSyncSchema);
|
|
902
|
+
var leaderboardInput = Schema9.Struct({
|
|
1049
903
|
...rangeFields,
|
|
1050
904
|
limit: leaderboardLimitSchema,
|
|
1051
905
|
timeZone: defaultTimeZoneSchema
|
|
1052
|
-
}).check(
|
|
1053
|
-
var leaderboardRowSchema =
|
|
1054
|
-
rank: safeInteger.check(
|
|
906
|
+
}).check(Schema9.makeFilter(validateDateRange));
|
|
907
|
+
var leaderboardRowSchema = Schema9.Struct({
|
|
908
|
+
rank: safeInteger.check(Schema9.isGreaterThanOrEqualTo(1)),
|
|
1055
909
|
userId: uuidSchema,
|
|
1056
|
-
userLabel:
|
|
1057
|
-
usd:
|
|
910
|
+
userLabel: Schema9.String,
|
|
911
|
+
usd: Schema9.Number.check(Schema9.isGreaterThanOrEqualTo(0)),
|
|
1058
912
|
tokens: nonNegativeSafeInteger,
|
|
1059
913
|
sessions: nonNegativeSafeInteger
|
|
1060
914
|
});
|
|
1061
|
-
var leaderboardOutput =
|
|
915
|
+
var leaderboardOutput = Schema9.Array(leaderboardRowSchema);
|
|
1062
916
|
var topDimensionsInput = leaderboardInput;
|
|
1063
|
-
var topDimensionRowSchema =
|
|
1064
|
-
rank: safeInteger.check(
|
|
1065
|
-
name:
|
|
1066
|
-
usd:
|
|
917
|
+
var topDimensionRowSchema = Schema9.Struct({
|
|
918
|
+
rank: safeInteger.check(Schema9.isGreaterThanOrEqualTo(1)),
|
|
919
|
+
name: Schema9.String,
|
|
920
|
+
usd: Schema9.Number.check(Schema9.isGreaterThanOrEqualTo(0)),
|
|
1067
921
|
tokens: nonNegativeSafeInteger,
|
|
1068
|
-
sessions:
|
|
922
|
+
sessions: Schema9.NullOr(nonNegativeSafeInteger)
|
|
1069
923
|
});
|
|
1070
|
-
var topDimensionsOutput =
|
|
1071
|
-
harnesses:
|
|
1072
|
-
models:
|
|
924
|
+
var topDimensionsOutput = Schema9.Struct({
|
|
925
|
+
harnesses: Schema9.Array(topDimensionRowSchema),
|
|
926
|
+
models: Schema9.Array(topDimensionRowSchema)
|
|
1073
927
|
});
|
|
1074
|
-
var usageDetailsInput =
|
|
928
|
+
var usageDetailsInput = Schema9.Struct({
|
|
1075
929
|
...rangeFields,
|
|
1076
930
|
stackBy: usageDetailStackByEnum,
|
|
1077
931
|
timeZone: defaultTimeZoneSchema
|
|
1078
|
-
}).check(
|
|
1079
|
-
var usageDetailsRowSchema =
|
|
1080
|
-
id:
|
|
1081
|
-
name:
|
|
932
|
+
}).check(Schema9.makeFilter(validateDateRange));
|
|
933
|
+
var usageDetailsRowSchema = Schema9.Struct({
|
|
934
|
+
id: Schema9.String,
|
|
935
|
+
name: Schema9.String,
|
|
1082
936
|
tokens: nonNegativeSafeInteger,
|
|
1083
|
-
sessions:
|
|
1084
|
-
usd:
|
|
1085
|
-
totalUsd:
|
|
937
|
+
sessions: Schema9.NullOr(nonNegativeSafeInteger),
|
|
938
|
+
usd: Schema9.Number.check(Schema9.isGreaterThanOrEqualTo(0)),
|
|
939
|
+
totalUsd: Schema9.Number.check(Schema9.isGreaterThanOrEqualTo(0))
|
|
1086
940
|
});
|
|
1087
|
-
var usageDetailsOutput =
|
|
941
|
+
var usageDetailsOutput = Schema9.Array(usageDetailsRowSchema);
|
|
1088
942
|
|
|
1089
943
|
// ../../packages/api/src/rpc/stats.ts
|
|
1090
944
|
var StatsRpcs = RpcGroup3.make(
|
|
1091
945
|
Rpc3.make("stats.devices", {
|
|
1092
946
|
success: devicesOutput,
|
|
1093
|
-
error:
|
|
947
|
+
error: Schema10.Union([DatabaseError, UnauthorizedError])
|
|
1094
948
|
}),
|
|
1095
949
|
Rpc3.make("stats.participants", {
|
|
1096
950
|
success: participantsOutput,
|
|
1097
|
-
error:
|
|
951
|
+
error: Schema10.Union([DatabaseError, UnauthorizedError])
|
|
1098
952
|
}),
|
|
1099
953
|
Rpc3.make("stats.leaderboard", {
|
|
1100
954
|
payload: leaderboardInput,
|
|
1101
955
|
success: leaderboardOutput,
|
|
1102
|
-
error:
|
|
956
|
+
error: Schema10.Union([DatabaseError, UnauthorizedError])
|
|
1103
957
|
}),
|
|
1104
958
|
Rpc3.make("stats.topDimensions", {
|
|
1105
959
|
payload: topDimensionsInput,
|
|
1106
960
|
success: topDimensionsOutput,
|
|
1107
|
-
error:
|
|
961
|
+
error: Schema10.Union([DatabaseError, UnauthorizedError])
|
|
1108
962
|
}),
|
|
1109
963
|
Rpc3.make("stats.summary", {
|
|
1110
964
|
payload: summaryInput,
|
|
1111
965
|
success: summaryOutput,
|
|
1112
|
-
error:
|
|
966
|
+
error: Schema10.Union([DatabaseError, UnauthorizedError])
|
|
1113
967
|
}),
|
|
1114
968
|
Rpc3.make("stats.dailyCost", {
|
|
1115
969
|
payload: dailyCostInput,
|
|
1116
970
|
success: dailyCostOutput,
|
|
1117
|
-
error:
|
|
971
|
+
error: Schema10.Union([DatabaseError, UnauthorizedError])
|
|
1118
972
|
}),
|
|
1119
973
|
Rpc3.make("stats.dailySessions", {
|
|
1120
974
|
payload: dailySessionsInput,
|
|
1121
975
|
success: dailySessionsOutput,
|
|
1122
|
-
error:
|
|
976
|
+
error: Schema10.Union([DatabaseError, UnauthorizedError])
|
|
1123
977
|
}),
|
|
1124
978
|
Rpc3.make("stats.weekdaySessions", {
|
|
1125
979
|
payload: weekdaySessionsInput,
|
|
1126
980
|
success: weekdaySessionsOutput,
|
|
1127
|
-
error:
|
|
981
|
+
error: Schema10.Union([DatabaseError, UnauthorizedError])
|
|
1128
982
|
}),
|
|
1129
983
|
Rpc3.make("stats.hourlySessions", {
|
|
1130
984
|
payload: hourlySessionsInput,
|
|
1131
985
|
success: hourlySessionsOutput,
|
|
1132
|
-
error:
|
|
986
|
+
error: Schema10.Union([DatabaseError, UnauthorizedError])
|
|
1133
987
|
}),
|
|
1134
988
|
Rpc3.make("stats.usageDetails", {
|
|
1135
989
|
payload: usageDetailsInput,
|
|
1136
990
|
success: usageDetailsOutput,
|
|
1137
|
-
error:
|
|
991
|
+
error: Schema10.Union([DatabaseError, UnauthorizedError])
|
|
1138
992
|
})
|
|
1139
993
|
);
|
|
1140
994
|
|
|
1141
995
|
// ../../packages/api/src/rpc/tokens.ts
|
|
1142
|
-
import { Schema as
|
|
996
|
+
import { Schema as Schema12 } from "effect";
|
|
1143
997
|
import { Rpc as Rpc4, RpcGroup as RpcGroup4 } from "effect/unstable/rpc";
|
|
1144
998
|
|
|
1145
999
|
// ../../packages/api/src/schemas/tokens.ts
|
|
1146
|
-
import { Schema as
|
|
1000
|
+
import { Schema as Schema11 } from "effect";
|
|
1147
1001
|
var MIN_TOKEN_NAME_LENGTH = 1;
|
|
1148
1002
|
var MAX_TOKEN_NAME_LENGTH = 80;
|
|
1149
|
-
var createTokenInput =
|
|
1150
|
-
name:
|
|
1003
|
+
var createTokenInput = Schema11.Struct({
|
|
1004
|
+
name: Schema11.String.check(Schema11.isMinLength(MIN_TOKEN_NAME_LENGTH), Schema11.isMaxLength(MAX_TOKEN_NAME_LENGTH))
|
|
1151
1005
|
});
|
|
1152
|
-
var revokeTokenInput =
|
|
1006
|
+
var revokeTokenInput = Schema11.Struct({
|
|
1153
1007
|
id: uuidV7
|
|
1154
1008
|
});
|
|
1155
|
-
var tokenMetadata =
|
|
1009
|
+
var tokenMetadata = Schema11.Struct({
|
|
1156
1010
|
id: uuidV7,
|
|
1157
|
-
name:
|
|
1158
|
-
prefix:
|
|
1011
|
+
name: Schema11.String,
|
|
1012
|
+
prefix: Schema11.String,
|
|
1159
1013
|
createdAt: isoUtcString,
|
|
1160
|
-
lastUsedAt:
|
|
1161
|
-
revokedAt:
|
|
1014
|
+
lastUsedAt: Schema11.NullOr(isoUtcString),
|
|
1015
|
+
revokedAt: Schema11.NullOr(isoUtcString)
|
|
1162
1016
|
});
|
|
1163
|
-
var listTokensOutput =
|
|
1164
|
-
tokens:
|
|
1017
|
+
var listTokensOutput = Schema11.Struct({
|
|
1018
|
+
tokens: Schema11.Array(tokenMetadata)
|
|
1165
1019
|
});
|
|
1166
|
-
var createTokenOutput =
|
|
1167
|
-
token:
|
|
1020
|
+
var createTokenOutput = Schema11.Struct({
|
|
1021
|
+
token: Schema11.String,
|
|
1168
1022
|
metadata: tokenMetadata
|
|
1169
1023
|
});
|
|
1170
|
-
var revokeTokenOutput =
|
|
1171
|
-
ok:
|
|
1024
|
+
var revokeTokenOutput = Schema11.Struct({
|
|
1025
|
+
ok: Schema11.Boolean
|
|
1172
1026
|
});
|
|
1173
1027
|
|
|
1174
1028
|
// ../../packages/api/src/rpc/tokens.ts
|
|
1175
1029
|
var TokensRpcs = RpcGroup4.make(
|
|
1176
1030
|
Rpc4.make("tokens.list", {
|
|
1177
1031
|
success: listTokensOutput,
|
|
1178
|
-
error:
|
|
1032
|
+
error: Schema12.Union([DatabaseError, UnauthorizedError])
|
|
1179
1033
|
}),
|
|
1180
1034
|
Rpc4.make("tokens.create", {
|
|
1181
1035
|
payload: createTokenInput,
|
|
1182
1036
|
success: createTokenOutput,
|
|
1183
|
-
error:
|
|
1037
|
+
error: Schema12.Union([DatabaseError, UnauthorizedError])
|
|
1184
1038
|
}),
|
|
1185
1039
|
Rpc4.make("tokens.revoke", {
|
|
1186
1040
|
payload: revokeTokenInput,
|
|
1187
1041
|
success: revokeTokenOutput,
|
|
1188
|
-
error:
|
|
1042
|
+
error: Schema12.Union([DatabaseError, NotFoundError, UnauthorizedError])
|
|
1189
1043
|
})
|
|
1190
1044
|
);
|
|
1191
1045
|
|
|
@@ -1193,11 +1047,11 @@ var TokensRpcs = RpcGroup4.make(
|
|
|
1193
1047
|
var WebRpcs = AuthRpcs.merge(StatsRpcs).merge(TokensRpcs).merge(DevicesRpcs);
|
|
1194
1048
|
|
|
1195
1049
|
// ../../packages/api/src/rpc/sync.ts
|
|
1196
|
-
import { Schema as
|
|
1050
|
+
import { Schema as Schema14 } from "effect";
|
|
1197
1051
|
import { Rpc as Rpc5, RpcGroup as RpcGroup5 } from "effect/unstable/rpc";
|
|
1198
1052
|
|
|
1199
1053
|
// ../../packages/api/src/schemas/sync.ts
|
|
1200
|
-
import { Effect as
|
|
1054
|
+
import { Effect as Effect5, Schema as Schema13 } from "effect";
|
|
1201
1055
|
var MIN_DEVICE_NAME_LENGTH2 = 1;
|
|
1202
1056
|
var MAX_DEVICE_NAME_LENGTH2 = 80;
|
|
1203
1057
|
var MIN_MODEL_LENGTH = 1;
|
|
@@ -1208,28 +1062,31 @@ var MAX_BUCKETS_PER_SYNC = 5e3;
|
|
|
1208
1062
|
var MAX_SESSIONS_PER_SYNC = 5e3;
|
|
1209
1063
|
var MAX_BUCKET_SESSION_COUNT = 1e3;
|
|
1210
1064
|
var MAX_BUCKET_TOKEN_COUNT = null;
|
|
1065
|
+
var MAX_REPLACEMENT_BATCHES = 1e4;
|
|
1066
|
+
var MAX_REPLACEMENT_FACTS = 1e5;
|
|
1211
1067
|
var HARNESSES = ["codex", "claude_code", "pi", "hermes", "opencode"];
|
|
1212
|
-
var
|
|
1213
|
-
var
|
|
1214
|
-
|
|
1215
|
-
|
|
1068
|
+
var MAX_REPLACEMENT_HARNESSES = HARNESSES.length;
|
|
1069
|
+
var harnessEnum = Schema13.Literals(HARNESSES);
|
|
1070
|
+
var deviceNameSchema2 = Schema13.String.check(
|
|
1071
|
+
Schema13.isMinLength(MIN_DEVICE_NAME_LENGTH2),
|
|
1072
|
+
Schema13.isMaxLength(MAX_DEVICE_NAME_LENGTH2)
|
|
1216
1073
|
);
|
|
1217
|
-
var getWatermarkInput =
|
|
1074
|
+
var getWatermarkInput = Schema13.Struct({
|
|
1218
1075
|
device: deviceNameSchema2
|
|
1219
1076
|
});
|
|
1220
|
-
var getWatermarkOutput =
|
|
1221
|
-
watermarkHourUtc:
|
|
1222
|
-
sessionKeySalt: hex64.check(
|
|
1077
|
+
var getWatermarkOutput = Schema13.Struct({
|
|
1078
|
+
watermarkHourUtc: Schema13.NullOr(isoUtcString),
|
|
1079
|
+
sessionKeySalt: hex64.check(Schema13.isLengthBetween(SESSION_KEY_SALT_LENGTH, SESSION_KEY_SALT_LENGTH))
|
|
1223
1080
|
});
|
|
1224
1081
|
function boundedInteger(max) {
|
|
1225
1082
|
if (max === null) {
|
|
1226
1083
|
return nonNegativeSafeInteger;
|
|
1227
1084
|
}
|
|
1228
|
-
return nonNegativeSafeInteger.check(
|
|
1085
|
+
return nonNegativeSafeInteger.check(Schema13.isLessThanOrEqualTo(max));
|
|
1229
1086
|
}
|
|
1230
|
-
var bucketInput =
|
|
1087
|
+
var bucketInput = Schema13.Struct({
|
|
1231
1088
|
harness: harnessEnum,
|
|
1232
|
-
model:
|
|
1089
|
+
model: Schema13.String.check(Schema13.isMinLength(MIN_MODEL_LENGTH), Schema13.isMaxLength(MAX_MODEL_LENGTH)),
|
|
1233
1090
|
hourUtc: isoUtcString,
|
|
1234
1091
|
inputTokens: boundedInteger(MAX_BUCKET_TOKEN_COUNT),
|
|
1235
1092
|
outputTokens: boundedInteger(MAX_BUCKET_TOKEN_COUNT),
|
|
@@ -1237,149 +1094,458 @@ var bucketInput = Schema14.Struct({
|
|
|
1237
1094
|
cacheCreateTokens: boundedInteger(MAX_BUCKET_TOKEN_COUNT),
|
|
1238
1095
|
sessionCount: boundedInteger(MAX_BUCKET_SESSION_COUNT)
|
|
1239
1096
|
});
|
|
1240
|
-
var sessionInput =
|
|
1097
|
+
var sessionInput = Schema13.Struct({
|
|
1241
1098
|
harness: harnessEnum,
|
|
1242
|
-
sessionKey: hex64.check(
|
|
1099
|
+
sessionKey: hex64.check(Schema13.isLengthBetween(SESSION_KEY_LENGTH, SESSION_KEY_LENGTH)),
|
|
1243
1100
|
startedAt: isoUtcString,
|
|
1244
1101
|
lastSeenAt: isoUtcString
|
|
1245
1102
|
});
|
|
1246
|
-
var upsertBucketsInput =
|
|
1103
|
+
var upsertBucketsInput = Schema13.Struct({
|
|
1247
1104
|
device: deviceNameSchema2,
|
|
1248
|
-
buckets:
|
|
1249
|
-
sessions:
|
|
1105
|
+
buckets: Schema13.Array(bucketInput).check(Schema13.isMaxLength(MAX_BUCKETS_PER_SYNC)),
|
|
1106
|
+
sessions: Schema13.Array(sessionInput).check(Schema13.isMaxLength(MAX_SESSIONS_PER_SYNC)).pipe(Schema13.withDecodingDefaultTypeKey(Effect5.succeed([])))
|
|
1250
1107
|
});
|
|
1251
|
-
var upsertBucketsOutput =
|
|
1108
|
+
var upsertBucketsOutput = Schema13.Struct({
|
|
1252
1109
|
upserted: boundedInteger(null),
|
|
1253
1110
|
upsertedSessions: boundedInteger(null)
|
|
1254
1111
|
});
|
|
1112
|
+
var replacementId = uuidV7;
|
|
1113
|
+
var createUsageReplacementInput = Schema13.Struct({
|
|
1114
|
+
device: deviceNameSchema2,
|
|
1115
|
+
startHourUtc: isoUtcString,
|
|
1116
|
+
endHourUtc: isoUtcString,
|
|
1117
|
+
harnesses: Schema13.Array(harnessEnum).check(Schema13.isMinLength(1), Schema13.isMaxLength(MAX_REPLACEMENT_HARNESSES))
|
|
1118
|
+
});
|
|
1119
|
+
var createUsageReplacementOutput = Schema13.Struct({
|
|
1120
|
+
replacementId,
|
|
1121
|
+
startHourUtc: isoUtcString,
|
|
1122
|
+
endHourUtc: isoUtcString,
|
|
1123
|
+
harnesses: Schema13.Array(harnessEnum),
|
|
1124
|
+
expiresAt: isoUtcString
|
|
1125
|
+
});
|
|
1126
|
+
var stageUsageReplacementBatchInput = Schema13.Struct({
|
|
1127
|
+
device: deviceNameSchema2,
|
|
1128
|
+
replacementId,
|
|
1129
|
+
batchIndex: boundedInteger(MAX_REPLACEMENT_BATCHES),
|
|
1130
|
+
batchDigest: hex64,
|
|
1131
|
+
buckets: Schema13.Array(bucketInput).check(Schema13.isMaxLength(MAX_BUCKETS_PER_SYNC)),
|
|
1132
|
+
sessions: Schema13.Array(sessionInput).check(Schema13.isMaxLength(MAX_SESSIONS_PER_SYNC))
|
|
1133
|
+
});
|
|
1134
|
+
var stageUsageReplacementBatchOutput = Schema13.Struct({
|
|
1135
|
+
stagedBuckets: boundedInteger(MAX_BUCKETS_PER_SYNC),
|
|
1136
|
+
stagedSessions: boundedInteger(MAX_SESSIONS_PER_SYNC),
|
|
1137
|
+
alreadyStaged: Schema13.Boolean
|
|
1138
|
+
});
|
|
1139
|
+
var finalizeUsageReplacementInput = Schema13.Struct({
|
|
1140
|
+
device: deviceNameSchema2,
|
|
1141
|
+
replacementId,
|
|
1142
|
+
expectedBatchCount: boundedInteger(MAX_REPLACEMENT_BATCHES),
|
|
1143
|
+
expectedBucketCount: boundedInteger(MAX_REPLACEMENT_FACTS),
|
|
1144
|
+
expectedSessionCount: boundedInteger(MAX_REPLACEMENT_FACTS),
|
|
1145
|
+
digest: hex64
|
|
1146
|
+
});
|
|
1147
|
+
var finalizeUsageReplacementOutput = Schema13.Struct({
|
|
1148
|
+
replacementId,
|
|
1149
|
+
committedAt: isoUtcString,
|
|
1150
|
+
replacedBuckets: boundedInteger(MAX_REPLACEMENT_FACTS),
|
|
1151
|
+
replacedSessions: boundedInteger(MAX_REPLACEMENT_FACTS),
|
|
1152
|
+
digest: hex64
|
|
1153
|
+
});
|
|
1255
1154
|
|
|
1256
1155
|
// ../../packages/api/src/rpc/sync.ts
|
|
1156
|
+
var replacementErrors = Schema14.Union([
|
|
1157
|
+
ConflictError,
|
|
1158
|
+
DatabaseError,
|
|
1159
|
+
NotFoundError,
|
|
1160
|
+
SyncQuotaExceededError,
|
|
1161
|
+
SyncTimestampRejectedError,
|
|
1162
|
+
UnauthorizedError,
|
|
1163
|
+
ValidationError
|
|
1164
|
+
]);
|
|
1257
1165
|
var SyncRpcs = RpcGroup5.make(
|
|
1258
1166
|
Rpc5.make("sync.getWatermark", {
|
|
1259
1167
|
payload: getWatermarkInput,
|
|
1260
1168
|
success: getWatermarkOutput,
|
|
1261
|
-
error:
|
|
1169
|
+
error: Schema14.Union([DatabaseError, UnauthorizedError])
|
|
1262
1170
|
}),
|
|
1263
1171
|
Rpc5.make("sync.upsertBuckets", {
|
|
1264
1172
|
payload: upsertBucketsInput,
|
|
1265
1173
|
success: upsertBucketsOutput,
|
|
1266
|
-
error:
|
|
1174
|
+
error: Schema14.Union([DatabaseError, SyncQuotaExceededError, SyncTimestampRejectedError, UnauthorizedError])
|
|
1175
|
+
}),
|
|
1176
|
+
Rpc5.make("sync.createUsageReplacement", {
|
|
1177
|
+
payload: createUsageReplacementInput,
|
|
1178
|
+
success: createUsageReplacementOutput,
|
|
1179
|
+
error: replacementErrors
|
|
1180
|
+
}),
|
|
1181
|
+
Rpc5.make("sync.stageUsageReplacementBatch", {
|
|
1182
|
+
payload: stageUsageReplacementBatchInput,
|
|
1183
|
+
success: stageUsageReplacementBatchOutput,
|
|
1184
|
+
error: replacementErrors
|
|
1185
|
+
}),
|
|
1186
|
+
Rpc5.make("sync.finalizeUsageReplacement", {
|
|
1187
|
+
payload: finalizeUsageReplacementInput,
|
|
1188
|
+
success: finalizeUsageReplacementOutput,
|
|
1189
|
+
error: replacementErrors
|
|
1267
1190
|
})
|
|
1268
1191
|
);
|
|
1269
1192
|
|
|
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
|
-
)
|
|
1193
|
+
// ../../packages/api/src/sync-replacement-canonical.ts
|
|
1194
|
+
function normalizedIso(value) {
|
|
1195
|
+
return new Date(value).toISOString();
|
|
1196
|
+
}
|
|
1197
|
+
function canonicalReplacementLines(facts) {
|
|
1198
|
+
const lines = [
|
|
1199
|
+
...facts.buckets.map(
|
|
1200
|
+
(bucket) => JSON.stringify([
|
|
1201
|
+
"bucket",
|
|
1202
|
+
bucket.harness,
|
|
1203
|
+
bucket.model,
|
|
1204
|
+
normalizedIso(bucket.hourUtc),
|
|
1205
|
+
bucket.inputTokens,
|
|
1206
|
+
bucket.outputTokens,
|
|
1207
|
+
bucket.cacheReadTokens,
|
|
1208
|
+
bucket.cacheCreateTokens,
|
|
1209
|
+
bucket.sessionCount
|
|
1210
|
+
])
|
|
1211
|
+
),
|
|
1212
|
+
...facts.sessions.map(
|
|
1213
|
+
(session) => JSON.stringify([
|
|
1214
|
+
"session",
|
|
1215
|
+
session.harness,
|
|
1216
|
+
session.sessionKey,
|
|
1217
|
+
normalizedIso(session.startedAt),
|
|
1218
|
+
normalizedIso(session.lastSeenAt)
|
|
1219
|
+
])
|
|
1316
1220
|
)
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
clientLive = clientLive.pipe(Layer3.provide(Layer3.succeed(FetchHttpClient.Fetch, options.fetch)));
|
|
1320
|
-
}
|
|
1321
|
-
return clientLive;
|
|
1221
|
+
];
|
|
1222
|
+
return lines.sort();
|
|
1322
1223
|
}
|
|
1323
|
-
function
|
|
1324
|
-
return
|
|
1224
|
+
function canonicalReplacementPayload(facts) {
|
|
1225
|
+
return canonicalReplacementLines(facts).join("\n");
|
|
1325
1226
|
}
|
|
1326
1227
|
|
|
1327
|
-
// src/sync/
|
|
1328
|
-
import {
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
var
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
SUCCESS_EXIT_CODE,
|
|
1337
|
-
ZERO_BUCKETS_EXIT_CODE,
|
|
1338
|
-
UNAUTHORIZED_EXIT_CODE,
|
|
1339
|
-
NETWORK_ERROR_EXIT_CODE,
|
|
1340
|
-
INVALID_ARGUMENTS_EXIT_CODE,
|
|
1341
|
-
GENERAL_FAILURE_EXIT_CODE
|
|
1342
|
-
];
|
|
1343
|
-
var EXIT = {
|
|
1344
|
-
success: SUCCESS_EXIT_CODE,
|
|
1345
|
-
zeroBucketsButSourcesExist: ZERO_BUCKETS_EXIT_CODE,
|
|
1346
|
-
unauthorized: UNAUTHORIZED_EXIT_CODE,
|
|
1347
|
-
networkError: NETWORK_ERROR_EXIT_CODE,
|
|
1348
|
-
invalidArguments: INVALID_ARGUMENTS_EXIT_CODE,
|
|
1349
|
-
generalFailure: GENERAL_FAILURE_EXIT_CODE
|
|
1228
|
+
// src/sync/run.ts
|
|
1229
|
+
import { Effect as Effect9, Stream as Stream4 } from "effect";
|
|
1230
|
+
|
|
1231
|
+
// src/sync/aggregate.ts
|
|
1232
|
+
import { createHmac } from "crypto";
|
|
1233
|
+
import { Effect as Effect6, Schema as Schema15, Stream as Stream2 } from "effect";
|
|
1234
|
+
var UsageAggregationErrorFields = {
|
|
1235
|
+
message: Schema15.String,
|
|
1236
|
+
cause: Schema15.optionalKey(Schema15.Defect())
|
|
1350
1237
|
};
|
|
1351
|
-
var
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
}) {
|
|
1356
|
-
constructor(exitCode, message, options = {}) {
|
|
1357
|
-
super(
|
|
1358
|
-
options.cause === void 0 ? { message, exitCode } : { message, exitCode, cause: options.cause }
|
|
1359
|
-
);
|
|
1360
|
-
}
|
|
1238
|
+
var UsageAggregationError = class extends Schema15.TaggedErrorClass()(
|
|
1239
|
+
"UsageAggregationError",
|
|
1240
|
+
UsageAggregationErrorFields
|
|
1241
|
+
) {
|
|
1361
1242
|
};
|
|
1362
|
-
function
|
|
1363
|
-
|
|
1364
|
-
return true;
|
|
1365
|
-
}
|
|
1366
|
-
if (typeof err !== "object" || err === null) {
|
|
1367
|
-
return false;
|
|
1368
|
-
}
|
|
1369
|
-
const candidate = err;
|
|
1370
|
-
return candidate._tag === "UnauthorizedError" || candidate.data?.code === "UNAUTHORIZED";
|
|
1243
|
+
function toIso(hourUtc) {
|
|
1244
|
+
return hourUtc instanceof Date ? hourUtc.toISOString() : hourUtc;
|
|
1371
1245
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
function
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1246
|
+
function key(b) {
|
|
1247
|
+
return `${b.harness} ${b.model} ${toIso(b.hourUtc)}`;
|
|
1248
|
+
}
|
|
1249
|
+
function finalize(acc) {
|
|
1250
|
+
return {
|
|
1251
|
+
harness: acc.harness,
|
|
1252
|
+
model: acc.model,
|
|
1253
|
+
hourUtc: acc.hourUtc,
|
|
1254
|
+
inputTokens: acc.inputTokens,
|
|
1255
|
+
outputTokens: acc.outputTokens,
|
|
1256
|
+
cacheReadTokens: acc.cacheReadTokens,
|
|
1257
|
+
cacheCreateTokens: acc.cacheCreateTokens,
|
|
1258
|
+
sessionCount: acc.sessionIds.size
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
var SESSION_KEY_SALT_PATTERN = /^[a-f0-9]{64}$/u;
|
|
1262
|
+
function createSessionKey(harness, localSessionId, sessionKeySalt) {
|
|
1263
|
+
if (!SESSION_KEY_SALT_PATTERN.test(sessionKeySalt)) {
|
|
1264
|
+
throw new Error("invalid session key salt");
|
|
1265
|
+
}
|
|
1266
|
+
return createHmac("sha256", Buffer.from(sessionKeySalt, "hex")).update("oopst/session-key/v1").update("\0").update(harness).update("\0").update(localSessionId).digest("hex");
|
|
1267
|
+
}
|
|
1268
|
+
function sessionMapKey(harness, sessionKey) {
|
|
1269
|
+
return `${harness} ${sessionKey}`;
|
|
1270
|
+
}
|
|
1271
|
+
function finalizeSession(acc) {
|
|
1272
|
+
return {
|
|
1273
|
+
harness: acc.harness,
|
|
1274
|
+
sessionKey: acc.sessionKey,
|
|
1275
|
+
startedAt: acc.startedAt.toISOString(),
|
|
1276
|
+
lastSeenAt: acc.lastSeenAt.toISOString()
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
function addSessionAccumulator(sessionAccumulators, bucket, localSessionId, sessionKeySalt) {
|
|
1280
|
+
const sessionKey = createSessionKey(bucket.harness, localSessionId, sessionKeySalt);
|
|
1281
|
+
const sessionAccumulatorKey = sessionMapKey(bucket.harness, sessionKey);
|
|
1282
|
+
const existingSession = sessionAccumulators.get(sessionAccumulatorKey);
|
|
1283
|
+
if (existingSession) {
|
|
1284
|
+
if (bucket.sessionStartedAt.getTime() < existingSession.startedAt.getTime()) {
|
|
1285
|
+
existingSession.startedAt = bucket.sessionStartedAt;
|
|
1286
|
+
}
|
|
1287
|
+
if (bucket.sessionLastSeenAt.getTime() > existingSession.lastSeenAt.getTime()) {
|
|
1288
|
+
existingSession.lastSeenAt = bucket.sessionLastSeenAt;
|
|
1289
|
+
}
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
sessionAccumulators.set(sessionAccumulatorKey, {
|
|
1293
|
+
harness: bucket.harness,
|
|
1294
|
+
sessionKey,
|
|
1295
|
+
startedAt: bucket.sessionStartedAt,
|
|
1296
|
+
lastSeenAt: bucket.sessionLastSeenAt
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
function createUsageAggregationState() {
|
|
1300
|
+
return {
|
|
1301
|
+
accumulators: /* @__PURE__ */ new Map(),
|
|
1302
|
+
sessionAccumulators: /* @__PURE__ */ new Map()
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
function addBucketToAggregationState(state, bucket, sessionKeySalt, replacementRange) {
|
|
1306
|
+
const includeBucket = isReplacementBucketEligible(bucket, replacementRange);
|
|
1307
|
+
if (includeBucket) {
|
|
1308
|
+
const k = key(bucket);
|
|
1309
|
+
let acc = state.accumulators.get(k);
|
|
1310
|
+
if (!acc) {
|
|
1311
|
+
acc = {
|
|
1312
|
+
harness: bucket.harness,
|
|
1313
|
+
model: bucket.model,
|
|
1314
|
+
hourUtc: toIso(bucket.hourUtc),
|
|
1315
|
+
inputTokens: 0,
|
|
1316
|
+
outputTokens: 0,
|
|
1317
|
+
cacheReadTokens: 0,
|
|
1318
|
+
cacheCreateTokens: 0,
|
|
1319
|
+
sessionIds: /* @__PURE__ */ new Set()
|
|
1320
|
+
};
|
|
1321
|
+
state.accumulators.set(k, acc);
|
|
1322
|
+
}
|
|
1323
|
+
acc.inputTokens += bucket.inputTokens;
|
|
1324
|
+
acc.outputTokens += bucket.outputTokens;
|
|
1325
|
+
acc.cacheReadTokens += bucket.cacheReadTokens;
|
|
1326
|
+
acc.cacheCreateTokens += bucket.cacheCreateTokens;
|
|
1327
|
+
for (const id of bucket.sessionIds) {
|
|
1328
|
+
acc.sessionIds.add(id);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
if (isReplacementSessionEligible(bucket, replacementRange)) {
|
|
1332
|
+
for (const id of bucket.sessionIds) {
|
|
1333
|
+
addSessionAccumulator(state.sessionAccumulators, bucket, id, sessionKeySalt);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
return state;
|
|
1337
|
+
}
|
|
1338
|
+
function isReplacementBucketEligible(bucket, range) {
|
|
1339
|
+
if (!range) {
|
|
1340
|
+
return true;
|
|
1341
|
+
}
|
|
1342
|
+
const hourMs = bucket.hourUtc.getTime();
|
|
1343
|
+
return range.harnesses.has(bucket.harness) && hourMs >= range.startHourUtc.getTime() && hourMs < range.endHourUtc.getTime();
|
|
1344
|
+
}
|
|
1345
|
+
function isReplacementSessionEligible(bucket, range) {
|
|
1346
|
+
if (!range) {
|
|
1347
|
+
return true;
|
|
1348
|
+
}
|
|
1349
|
+
return range.harnesses.has(bucket.harness) && bucket.sessionStartedAt.getTime() < range.endHourUtc.getTime() && bucket.sessionLastSeenAt.getTime() >= range.startHourUtc.getTime();
|
|
1350
|
+
}
|
|
1351
|
+
function addBucketToAggregationStateEffect(state, bucket, sessionKeySalt, replacementRange) {
|
|
1352
|
+
return Effect6.try({
|
|
1353
|
+
try: () => addBucketToAggregationState(state, bucket, sessionKeySalt, replacementRange),
|
|
1354
|
+
catch: toUsageAggregationError
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
function finalizeUsageBatches(state, batchSize) {
|
|
1358
|
+
const finalized = [];
|
|
1359
|
+
for (const acc of state.accumulators.values()) {
|
|
1360
|
+
finalized.push(finalize(acc));
|
|
1361
|
+
}
|
|
1362
|
+
const finalizedSessions = [];
|
|
1363
|
+
for (const acc of state.sessionAccumulators.values()) {
|
|
1364
|
+
finalizedSessions.push(finalizeSession(acc));
|
|
1365
|
+
}
|
|
1366
|
+
const batches = [];
|
|
1367
|
+
const maxLength = Math.max(finalized.length, finalizedSessions.length);
|
|
1368
|
+
for (let i = 0; i < maxLength; i += batchSize) {
|
|
1369
|
+
batches.push({
|
|
1370
|
+
buckets: finalized.slice(i, i + batchSize),
|
|
1371
|
+
sessions: finalizedSessions.slice(i, i + batchSize)
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
return batches;
|
|
1375
|
+
}
|
|
1376
|
+
function toUsageAggregationError(cause) {
|
|
1377
|
+
return new UsageAggregationError({
|
|
1378
|
+
message: `usage aggregation failed: ${errorMessage(cause)}`,
|
|
1379
|
+
cause
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
function errorMessage(cause) {
|
|
1383
|
+
return cause instanceof Error ? cause.message : "unknown aggregation error";
|
|
1384
|
+
}
|
|
1385
|
+
function aggregateUsageStream(source, options) {
|
|
1386
|
+
return Stream2.fromIterableEffect(
|
|
1387
|
+
source.pipe(
|
|
1388
|
+
Stream2.runFoldEffect(
|
|
1389
|
+
() => createUsageAggregationState(),
|
|
1390
|
+
(state, bucket) => addBucketToAggregationStateEffect(state, bucket, options.sessionKeySalt, options.replacementRange)
|
|
1391
|
+
),
|
|
1392
|
+
Effect6.map((state) => finalizeUsageBatches(state, options.batchSize))
|
|
1393
|
+
)
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// src/sync/client.ts
|
|
1398
|
+
import { Context as Context4, Effect as Effect7, Layer as Layer3 } from "effect";
|
|
1399
|
+
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http";
|
|
1400
|
+
import { RpcClient, RpcSerialization } from "effect/unstable/rpc";
|
|
1401
|
+
var AUTHORIZATION_HEADER = "authorization";
|
|
1402
|
+
var SYNC_RPC_PATH = "/rpc/sync";
|
|
1403
|
+
var SyncRpcClient = class extends Context4.Service()("@oopst/cli/SyncRpcClient") {
|
|
1404
|
+
};
|
|
1405
|
+
function syncRpcClientFromPromiseClient(client) {
|
|
1406
|
+
const getWatermark = Effect7.fn("SyncRpcClient.fromPromise.getWatermark")(
|
|
1407
|
+
(input) => Effect7.tryPromise({
|
|
1408
|
+
try: () => client.sync.getWatermark.mutate(input),
|
|
1409
|
+
catch: (err) => err
|
|
1410
|
+
})
|
|
1411
|
+
);
|
|
1412
|
+
const upsertBuckets = Effect7.fn("SyncRpcClient.fromPromise.upsertBuckets")(
|
|
1413
|
+
(input) => Effect7.tryPromise({
|
|
1414
|
+
try: () => client.sync.upsertBuckets.mutate(input),
|
|
1415
|
+
catch: (err) => err
|
|
1416
|
+
})
|
|
1417
|
+
);
|
|
1418
|
+
const createUsageReplacement = Effect7.fn("SyncRpcClient.fromPromise.createUsageReplacement")(
|
|
1419
|
+
(input) => Effect7.tryPromise({
|
|
1420
|
+
try: () => client.sync.createUsageReplacement.mutate(input),
|
|
1421
|
+
catch: (err) => err
|
|
1422
|
+
})
|
|
1423
|
+
);
|
|
1424
|
+
const stageUsageReplacementBatch = Effect7.fn("SyncRpcClient.fromPromise.stageUsageReplacementBatch")(
|
|
1425
|
+
(input) => Effect7.tryPromise({
|
|
1426
|
+
try: () => client.sync.stageUsageReplacementBatch.mutate(input),
|
|
1427
|
+
catch: (err) => err
|
|
1428
|
+
})
|
|
1429
|
+
);
|
|
1430
|
+
const finalizeUsageReplacement = Effect7.fn("SyncRpcClient.fromPromise.finalizeUsageReplacement")(
|
|
1431
|
+
(input) => Effect7.tryPromise({
|
|
1432
|
+
try: () => client.sync.finalizeUsageReplacement.mutate(input),
|
|
1433
|
+
catch: (err) => err
|
|
1434
|
+
})
|
|
1435
|
+
);
|
|
1436
|
+
return SyncRpcClient.of({
|
|
1437
|
+
createUsageReplacement,
|
|
1438
|
+
finalizeUsageReplacement,
|
|
1439
|
+
getWatermark,
|
|
1440
|
+
stageUsageReplacementBatch,
|
|
1441
|
+
upsertBuckets
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
function makeSyncRpcClientLayer(options) {
|
|
1445
|
+
return Layer3.effect(
|
|
1446
|
+
SyncRpcClient,
|
|
1447
|
+
Effect7.gen(function* () {
|
|
1448
|
+
const client = yield* RpcClient.make(SyncRpcs);
|
|
1449
|
+
const getWatermark = Effect7.fn("SyncRpcClient.getWatermark")(
|
|
1450
|
+
(input) => client["sync.getWatermark"](input)
|
|
1451
|
+
);
|
|
1452
|
+
const upsertBuckets = Effect7.fn("SyncRpcClient.upsertBuckets")(
|
|
1453
|
+
(input) => client["sync.upsertBuckets"](input)
|
|
1454
|
+
);
|
|
1455
|
+
const createUsageReplacement = Effect7.fn("SyncRpcClient.createUsageReplacement")(
|
|
1456
|
+
(input) => client["sync.createUsageReplacement"](input)
|
|
1457
|
+
);
|
|
1458
|
+
const stageUsageReplacementBatch = Effect7.fn("SyncRpcClient.stageUsageReplacementBatch")(
|
|
1459
|
+
(input) => client["sync.stageUsageReplacementBatch"](input)
|
|
1460
|
+
);
|
|
1461
|
+
const finalizeUsageReplacement = Effect7.fn("SyncRpcClient.finalizeUsageReplacement")(
|
|
1462
|
+
(input) => client["sync.finalizeUsageReplacement"](input)
|
|
1463
|
+
);
|
|
1464
|
+
return SyncRpcClient.of({
|
|
1465
|
+
createUsageReplacement,
|
|
1466
|
+
finalizeUsageReplacement,
|
|
1467
|
+
getWatermark,
|
|
1468
|
+
stageUsageReplacementBatch,
|
|
1469
|
+
upsertBuckets
|
|
1470
|
+
});
|
|
1471
|
+
})
|
|
1472
|
+
).pipe(Layer3.provide(makeClientLive(options)));
|
|
1473
|
+
}
|
|
1474
|
+
function makeClientLive(options) {
|
|
1475
|
+
let clientLive = RpcClient.layerProtocolHttp({
|
|
1476
|
+
url: stripTrailingSlash(options.serverUrl),
|
|
1477
|
+
transformClient: HttpClient.mapRequest(
|
|
1478
|
+
(request) => request.pipe(
|
|
1479
|
+
HttpClientRequest.appendUrl(SYNC_RPC_PATH),
|
|
1480
|
+
HttpClientRequest.setHeader(AUTHORIZATION_HEADER, `Bearer ${options.token}`)
|
|
1481
|
+
)
|
|
1482
|
+
)
|
|
1483
|
+
}).pipe(Layer3.provide(RpcSerialization.layerJson), Layer3.provide(FetchHttpClient.layer));
|
|
1484
|
+
if (options.fetch) {
|
|
1485
|
+
clientLive = clientLive.pipe(Layer3.provide(Layer3.succeed(FetchHttpClient.Fetch, options.fetch)));
|
|
1486
|
+
}
|
|
1487
|
+
return clientLive;
|
|
1488
|
+
}
|
|
1489
|
+
function stripTrailingSlash(url) {
|
|
1490
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// src/sync/errors.ts
|
|
1494
|
+
import { Schema as Schema16 } from "effect";
|
|
1495
|
+
var SUCCESS_EXIT_CODE = 0;
|
|
1496
|
+
var ZERO_BUCKETS_EXIT_CODE = 1;
|
|
1497
|
+
var UNAUTHORIZED_EXIT_CODE = 2;
|
|
1498
|
+
var NETWORK_ERROR_EXIT_CODE = 3;
|
|
1499
|
+
var INVALID_ARGUMENTS_EXIT_CODE = 4;
|
|
1500
|
+
var GENERAL_FAILURE_EXIT_CODE = 5;
|
|
1501
|
+
var EXIT_CODES = [
|
|
1502
|
+
SUCCESS_EXIT_CODE,
|
|
1503
|
+
ZERO_BUCKETS_EXIT_CODE,
|
|
1504
|
+
UNAUTHORIZED_EXIT_CODE,
|
|
1505
|
+
NETWORK_ERROR_EXIT_CODE,
|
|
1506
|
+
INVALID_ARGUMENTS_EXIT_CODE,
|
|
1507
|
+
GENERAL_FAILURE_EXIT_CODE
|
|
1508
|
+
];
|
|
1509
|
+
var EXIT = {
|
|
1510
|
+
success: SUCCESS_EXIT_CODE,
|
|
1511
|
+
zeroBucketsButSourcesExist: ZERO_BUCKETS_EXIT_CODE,
|
|
1512
|
+
unauthorized: UNAUTHORIZED_EXIT_CODE,
|
|
1513
|
+
networkError: NETWORK_ERROR_EXIT_CODE,
|
|
1514
|
+
invalidArguments: INVALID_ARGUMENTS_EXIT_CODE,
|
|
1515
|
+
generalFailure: GENERAL_FAILURE_EXIT_CODE
|
|
1516
|
+
};
|
|
1517
|
+
var SyncError = class extends Schema16.TaggedErrorClass()("SyncError", {
|
|
1518
|
+
message: Schema16.String,
|
|
1519
|
+
exitCode: Schema16.Literals(EXIT_CODES),
|
|
1520
|
+
cause: Schema16.optionalKey(Schema16.Defect())
|
|
1521
|
+
}) {
|
|
1522
|
+
constructor(exitCode, message, options = {}) {
|
|
1523
|
+
super(
|
|
1524
|
+
options.cause === void 0 ? { message, exitCode } : { message, exitCode, cause: options.cause }
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
function isUnauthorizedError(err) {
|
|
1529
|
+
if (err instanceof UnauthorizedError) {
|
|
1530
|
+
return true;
|
|
1531
|
+
}
|
|
1532
|
+
if (typeof err !== "object" || err === null) {
|
|
1533
|
+
return false;
|
|
1534
|
+
}
|
|
1535
|
+
const candidate = err;
|
|
1536
|
+
return candidate._tag === "UnauthorizedError" || candidate.data?.code === "UNAUTHORIZED";
|
|
1537
|
+
}
|
|
1538
|
+
var NETWORK_ERROR_CODES = ["ECONNREFUSED", "ENOTFOUND", "ECONNRESET", "EAI_AGAIN", "ETIMEDOUT"];
|
|
1539
|
+
var NETWORK_ERROR_PATTERN = /fetch failed|network|ECONN|ENOTFOUND|ETIMEDOUT/iu;
|
|
1540
|
+
var NETWORK_HTTP_ERROR_KINDS = ["InvalidUrlError", "TransportError"];
|
|
1541
|
+
function isNetworkError(err) {
|
|
1542
|
+
if (typeof err !== "object" || err === null) {
|
|
1543
|
+
return false;
|
|
1544
|
+
}
|
|
1545
|
+
const rpcReason = err.reason;
|
|
1546
|
+
if (rpcReason?._tag === "HttpError" && rpcReason.kind && NETWORK_HTTP_ERROR_KINDS.includes(rpcReason.kind)) {
|
|
1547
|
+
return true;
|
|
1548
|
+
}
|
|
1383
1549
|
const code = err.cause?.code;
|
|
1384
1550
|
if (code && NETWORK_ERROR_CODES.includes(code)) {
|
|
1385
1551
|
return true;
|
|
@@ -1524,6 +1690,7 @@ function runSyncEffect(options) {
|
|
|
1524
1690
|
function runSyncProgram(options) {
|
|
1525
1691
|
return Effect9.gen(function* () {
|
|
1526
1692
|
yield* validateRunSyncOptions(options);
|
|
1693
|
+
const replacement = yield* validateUsageReplacement(options.replacement, options.harness);
|
|
1527
1694
|
const client = yield* SyncRpcClient;
|
|
1528
1695
|
const parserCollection = yield* ParserCollection;
|
|
1529
1696
|
const color = options.color === true;
|
|
@@ -1532,42 +1699,94 @@ function runSyncProgram(options) {
|
|
|
1532
1699
|
return watermarkResult.result;
|
|
1533
1700
|
}
|
|
1534
1701
|
const { sessionKeySalt, watermark } = watermarkResult.value;
|
|
1535
|
-
const cutoff = new Date((watermark?.getTime() ?? 0) - options.lookbackDays * MS_PER_DAY2);
|
|
1702
|
+
const cutoff = replacement ? /* @__PURE__ */ new Date(0) : new Date((watermark?.getTime() ?? 0) - options.lookbackDays * MS_PER_DAY2);
|
|
1536
1703
|
if (options.verbose) {
|
|
1537
1704
|
yield* logInfo2(options.logger, `watermark=${watermark?.toISOString() ?? "null"} cutoff=${cutoff.toISOString()}`);
|
|
1538
1705
|
}
|
|
1539
1706
|
const parsers = yield* Effect9.sync(() => options.parserFactory({ cutoff, device: options.device }));
|
|
1540
|
-
const sources = selectSources(options.harness, parsers);
|
|
1707
|
+
const sources = selectSources(options.harness, parsers, replacement?.harnesses);
|
|
1541
1708
|
const parserStream = yield* parserCollection.stream({
|
|
1542
1709
|
sources,
|
|
1543
1710
|
logger: options.logger,
|
|
1544
1711
|
color
|
|
1545
1712
|
});
|
|
1546
|
-
const usageBatches = usageBatchStream(parserStream, sessionKeySalt);
|
|
1713
|
+
const usageBatches = usageBatchStream(parserStream, sessionKeySalt, replacement?.aggregationRange);
|
|
1714
|
+
if (replacement) {
|
|
1715
|
+
return yield* runReplacementUsage({
|
|
1716
|
+
options,
|
|
1717
|
+
client,
|
|
1718
|
+
color,
|
|
1719
|
+
replacement,
|
|
1720
|
+
usageBatches
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
return yield* runOrdinaryUsage(options, client, color, usageBatches);
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
function runReplacementUsage(input) {
|
|
1727
|
+
return Effect9.gen(function* () {
|
|
1728
|
+
const { client, color, options, replacement, usageBatches } = input;
|
|
1729
|
+
const batchesResult = yield* recoverSyncFailure(usageBatches.pipe(Stream4.runCollect), options.logger);
|
|
1730
|
+
if (batchesResult._tag === "SyncFailure") {
|
|
1731
|
+
return batchesResult.result;
|
|
1732
|
+
}
|
|
1733
|
+
const batches = batchesResult.value;
|
|
1734
|
+
const plan = summarizeUsageBatchArray(batches);
|
|
1547
1735
|
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
1736
|
yield* logInfo2(
|
|
1558
1737
|
options.logger,
|
|
1559
|
-
|
|
1738
|
+
`${dryRunPrefix(color)} atomic replacement would stage ${usageRecordText(plan.bucketCount)} and ${localSessionText2(plan.sessionCount)} in ${uploadRequestText(plan.batchCount)}.`
|
|
1560
1739
|
);
|
|
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
1740
|
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
|
-
};
|
|
1741
|
+
return resultFromPlan(plan);
|
|
1570
1742
|
}
|
|
1743
|
+
const uploadResult = yield* recoverSyncFailure(
|
|
1744
|
+
uploadUsageReplacement({
|
|
1745
|
+
batches,
|
|
1746
|
+
client,
|
|
1747
|
+
color,
|
|
1748
|
+
device: options.device,
|
|
1749
|
+
logger: options.logger,
|
|
1750
|
+
replacement
|
|
1751
|
+
}),
|
|
1752
|
+
options.logger
|
|
1753
|
+
);
|
|
1754
|
+
if (uploadResult._tag === "SyncFailure") {
|
|
1755
|
+
return uploadResult.result;
|
|
1756
|
+
}
|
|
1757
|
+
yield* logInfo2(
|
|
1758
|
+
options.logger,
|
|
1759
|
+
`${successPrefix(color)} atomically replaced ${usageRecordText(plan.bucketCount)} and ${localSessionText2(plan.sessionCount)}.`
|
|
1760
|
+
);
|
|
1761
|
+
return uploadResult.value;
|
|
1762
|
+
});
|
|
1763
|
+
}
|
|
1764
|
+
function runOrdinaryUsage(options, client, color, usageBatches) {
|
|
1765
|
+
return options.dryRun ? runOrdinaryDryRun(options, color, usageBatches) : uploadOrdinaryUsage(options, client, color, usageBatches);
|
|
1766
|
+
}
|
|
1767
|
+
function runOrdinaryDryRun(options, color, usageBatches) {
|
|
1768
|
+
return Effect9.gen(function* () {
|
|
1769
|
+
const planResult = yield* recoverSyncFailure(summarizeUsageBatchStream(usageBatches), options.logger);
|
|
1770
|
+
if (planResult._tag === "SyncFailure") {
|
|
1771
|
+
return planResult.result;
|
|
1772
|
+
}
|
|
1773
|
+
const plan = planResult.value;
|
|
1774
|
+
const earlyResult = yield* finishDryRun(options, plan, color);
|
|
1775
|
+
if (earlyResult) {
|
|
1776
|
+
return earlyResult;
|
|
1777
|
+
}
|
|
1778
|
+
yield* logInfo2(
|
|
1779
|
+
options.logger,
|
|
1780
|
+
phase2(`Aggregated usage by hour and model: ${usageRecordText(plan.bucketCount)} ready.`, color)
|
|
1781
|
+
);
|
|
1782
|
+
const summary = `${dryRunPrefix(color)} ${usageRecordText(plan.bucketCount)} from ${localSessionText2(plan.sessionCount)} would be uploaded in ${uploadRequestText(plan.batchCount)}.`;
|
|
1783
|
+
yield* logInfo2(options.logger, summary);
|
|
1784
|
+
yield* logInfo2(options.logger, muted("No usage data was uploaded.", color));
|
|
1785
|
+
return resultFromPlan(plan);
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
function uploadOrdinaryUsage(options, client, color, usageBatches) {
|
|
1789
|
+
return Effect9.gen(function* () {
|
|
1571
1790
|
const uploadResult = yield* recoverSyncFailure(
|
|
1572
1791
|
uploadUsageBatchStream({
|
|
1573
1792
|
batches: usageBatches,
|
|
@@ -1597,8 +1816,10 @@ function runSyncProgram(options) {
|
|
|
1597
1816
|
return emptySuccessResult();
|
|
1598
1817
|
}
|
|
1599
1818
|
const verboseUploadSuffix = options.verbose ? ` in ${uploadRequestText(totals.batchesUploaded)}` : "";
|
|
1600
|
-
|
|
1601
|
-
|
|
1819
|
+
yield* logInfo2(
|
|
1820
|
+
options.logger,
|
|
1821
|
+
`${successPrefix(color)} uploaded ${usageRecordText(totals.bucketsUploaded)} from ${localSessionText2(totals.sessionsUploaded)}${verboseUploadSuffix}.`
|
|
1822
|
+
);
|
|
1602
1823
|
return {
|
|
1603
1824
|
exitCode: EXIT.success,
|
|
1604
1825
|
batchesUploaded: totals.batchesUploaded,
|
|
@@ -1607,6 +1828,14 @@ function runSyncProgram(options) {
|
|
|
1607
1828
|
};
|
|
1608
1829
|
});
|
|
1609
1830
|
}
|
|
1831
|
+
function resultFromPlan(plan) {
|
|
1832
|
+
return {
|
|
1833
|
+
exitCode: EXIT.success,
|
|
1834
|
+
batchesUploaded: plan.batchCount,
|
|
1835
|
+
bucketsUploaded: plan.bucketCount,
|
|
1836
|
+
sessionsUploaded: plan.sessionCount
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1610
1839
|
function validateRunSyncOptions(options) {
|
|
1611
1840
|
return Effect9.gen(function* () {
|
|
1612
1841
|
if (!options.token) {
|
|
@@ -1617,6 +1846,45 @@ function validateRunSyncOptions(options) {
|
|
|
1617
1846
|
}
|
|
1618
1847
|
});
|
|
1619
1848
|
}
|
|
1849
|
+
function validateUsageReplacement(replacement, harness) {
|
|
1850
|
+
if (!replacement) {
|
|
1851
|
+
return Effect9.succeed(void 0);
|
|
1852
|
+
}
|
|
1853
|
+
return Effect9.try({
|
|
1854
|
+
try: () => {
|
|
1855
|
+
if (harness !== void 0) {
|
|
1856
|
+
throw new SyncError(EXIT.invalidArguments, "--harness cannot be combined with replacement harnesses");
|
|
1857
|
+
}
|
|
1858
|
+
const start = parseUtcHour("replacement start", replacement.startHourUtc);
|
|
1859
|
+
const end = parseUtcHour("replacement end", replacement.endHourUtc);
|
|
1860
|
+
if (end.getTime() <= start.getTime()) {
|
|
1861
|
+
throw new SyncError(EXIT.invalidArguments, "replacement end must be later than replacement start");
|
|
1862
|
+
}
|
|
1863
|
+
const harnesses = [...new Set(replacement.harnesses)].sort();
|
|
1864
|
+
if (harnesses.length === 0 || harnesses.length !== replacement.harnesses.length) {
|
|
1865
|
+
throw new SyncError(EXIT.invalidArguments, "replacement harnesses must be a non-empty set without duplicates");
|
|
1866
|
+
}
|
|
1867
|
+
return {
|
|
1868
|
+
startHourUtc: start.toISOString(),
|
|
1869
|
+
endHourUtc: end.toISOString(),
|
|
1870
|
+
harnesses,
|
|
1871
|
+
aggregationRange: {
|
|
1872
|
+
startHourUtc: start,
|
|
1873
|
+
endHourUtc: end,
|
|
1874
|
+
harnesses: new Set(harnesses)
|
|
1875
|
+
}
|
|
1876
|
+
};
|
|
1877
|
+
},
|
|
1878
|
+
catch: (error) => error instanceof SyncError ? error : new SyncError(EXIT.invalidArguments, "replacement bounds must be exact UTC hours")
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
function parseUtcHour(field, value) {
|
|
1882
|
+
const parsed = new Date(value);
|
|
1883
|
+
if (Number.isNaN(parsed.getTime()) || parsed.toISOString() !== value || parsed.getUTCMinutes() !== 0 || parsed.getUTCSeconds() !== 0 || parsed.getUTCMilliseconds() !== 0) {
|
|
1884
|
+
throw new SyncError(EXIT.invalidArguments, `${field} must be an ISO UTC timestamp aligned to an hour`);
|
|
1885
|
+
}
|
|
1886
|
+
return parsed;
|
|
1887
|
+
}
|
|
1620
1888
|
function fetchWatermark(options, client, color) {
|
|
1621
1889
|
return Effect9.gen(function* () {
|
|
1622
1890
|
yield* logInfo2(options.logger, phase2("Checking server sync point...", color));
|
|
@@ -1627,29 +1895,24 @@ function fetchWatermark(options, client, color) {
|
|
|
1627
1895
|
};
|
|
1628
1896
|
});
|
|
1629
1897
|
}
|
|
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;
|
|
1898
|
+
function selectSources(harness, parsers, replacementHarnesses) {
|
|
1899
|
+
const replacementSet = replacementHarnesses ? new Set(replacementHarnesses) : void 0;
|
|
1900
|
+
const candidates = [
|
|
1901
|
+
{ harness: "codex", label: "Codex", source: parsers.codex },
|
|
1902
|
+
{ harness: "claude_code", label: "Claude Code", source: parsers.claudeCode },
|
|
1903
|
+
{ harness: "pi", label: "Pi", source: parsers.pi },
|
|
1904
|
+
{ harness: "hermes", label: "Hermes", source: parsers.hermes },
|
|
1905
|
+
{ harness: "opencode", label: "OpenCode", source: parsers.opencode }
|
|
1906
|
+
];
|
|
1907
|
+
return candidates.filter(
|
|
1908
|
+
(candidate) => replacementSet ? replacementSet.has(candidate.harness) : harness === void 0 || harness === candidate.harness
|
|
1909
|
+
).map(({ label, source }) => ({ label, source }));
|
|
1648
1910
|
}
|
|
1649
|
-
function usageBatchStream(source, sessionKeySalt) {
|
|
1911
|
+
function usageBatchStream(source, sessionKeySalt, replacementRange) {
|
|
1650
1912
|
return aggregateUsageStream(source, {
|
|
1651
1913
|
batchSize: SYNC_BATCH_SIZE,
|
|
1652
|
-
sessionKeySalt
|
|
1914
|
+
sessionKeySalt,
|
|
1915
|
+
replacementRange
|
|
1653
1916
|
}).pipe(Stream4.filter((batch) => batch.buckets.length > 0 || batch.sessions.length > 0));
|
|
1654
1917
|
}
|
|
1655
1918
|
var summarizeUsageBatchStream = Effect9.fn("RunSync.summarizeUsageBatchStream")(
|
|
@@ -1738,6 +2001,51 @@ var uploadUsageBatchStream = Effect9.fn("RunSync.uploadUsageBatchStream")(
|
|
|
1738
2001
|
return { plan, totals };
|
|
1739
2002
|
})
|
|
1740
2003
|
);
|
|
2004
|
+
var uploadUsageReplacement = Effect9.fn("RunSync.uploadUsageReplacement")(
|
|
2005
|
+
(options) => Effect9.gen(function* () {
|
|
2006
|
+
yield* logInfo2(options.logger, phase2("Creating atomic replacement scope...", options.color));
|
|
2007
|
+
const created = yield* options.client.createUsageReplacement({
|
|
2008
|
+
device: options.device,
|
|
2009
|
+
startHourUtc: options.replacement.startHourUtc,
|
|
2010
|
+
endHourUtc: options.replacement.endHourUtc,
|
|
2011
|
+
harnesses: options.replacement.harnesses
|
|
2012
|
+
});
|
|
2013
|
+
const buckets = options.batches.flatMap((batch) => batch.buckets);
|
|
2014
|
+
const sessions = options.batches.flatMap((batch) => batch.sessions);
|
|
2015
|
+
for (const [batchIndex, batch] of options.batches.entries()) {
|
|
2016
|
+
yield* logInfo2(
|
|
2017
|
+
options.logger,
|
|
2018
|
+
phase2(`Staging corrected usage... ${batchIndex + 1}/${options.batches.length}`, options.color)
|
|
2019
|
+
);
|
|
2020
|
+
yield* options.client.stageUsageReplacementBatch({
|
|
2021
|
+
device: options.device,
|
|
2022
|
+
replacementId: created.replacementId,
|
|
2023
|
+
batchIndex,
|
|
2024
|
+
batchDigest: usageReplacementDigest(batch),
|
|
2025
|
+
buckets: batch.buckets,
|
|
2026
|
+
sessions: batch.sessions
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
yield* logInfo2(options.logger, phase2("Finalizing corrected usage atomically...", options.color));
|
|
2030
|
+
yield* options.client.finalizeUsageReplacement({
|
|
2031
|
+
device: options.device,
|
|
2032
|
+
replacementId: created.replacementId,
|
|
2033
|
+
expectedBatchCount: options.batches.length,
|
|
2034
|
+
expectedBucketCount: buckets.length,
|
|
2035
|
+
expectedSessionCount: sessions.length,
|
|
2036
|
+
digest: usageReplacementDigest({ buckets, sessions })
|
|
2037
|
+
});
|
|
2038
|
+
return {
|
|
2039
|
+
exitCode: EXIT.success,
|
|
2040
|
+
batchesUploaded: options.batches.length,
|
|
2041
|
+
bucketsUploaded: buckets.length,
|
|
2042
|
+
sessionsUploaded: sessions.length
|
|
2043
|
+
};
|
|
2044
|
+
})
|
|
2045
|
+
);
|
|
2046
|
+
function usageReplacementDigest(facts) {
|
|
2047
|
+
return createHash("sha256").update(canonicalReplacementPayload(facts)).digest("hex");
|
|
2048
|
+
}
|
|
1741
2049
|
function recoverSyncFailure(effect, logger) {
|
|
1742
2050
|
return Effect9.gen(function* () {
|
|
1743
2051
|
return syncStepSuccess(yield* effect);
|
|
@@ -2270,7 +2578,7 @@ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join8, matchesG
|
|
|
2270
2578
|
|
|
2271
2579
|
// ../../packages/parsers/src/claude/index.ts
|
|
2272
2580
|
import { basename, dirname as dirname2 } from "path";
|
|
2273
|
-
import { Effect as
|
|
2581
|
+
import { Effect as Effect18, Stream as Stream11 } from "effect";
|
|
2274
2582
|
|
|
2275
2583
|
// ../../packages/parsers/src/config.ts
|
|
2276
2584
|
import { homedir as homedir2 } from "os";
|
|
@@ -2490,27 +2798,199 @@ var stderrLogger = createWarningLogger(stderr);
|
|
|
2490
2798
|
|
|
2491
2799
|
// ../../packages/parsers/src/claude/bucketize.ts
|
|
2492
2800
|
import { createReadStream as createReadStream2 } from "fs";
|
|
2801
|
+
import { normalize as normalize3 } from "path";
|
|
2493
2802
|
import { createInterface as createInterface3 } from "readline";
|
|
2494
|
-
import { Cause as Cause2, Effect as
|
|
2803
|
+
import { Cause as Cause2, Effect as Effect17, Queue as Queue2, Stream as Stream10 } from "effect";
|
|
2495
2804
|
|
|
2496
2805
|
// ../../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";
|
|
2806
|
+
import { Effect as Effect16, Stream as Stream9 } from "effect";
|
|
2500
2807
|
|
|
2501
|
-
// ../../packages/parsers/src/
|
|
2502
|
-
|
|
2503
|
-
var
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
}) {
|
|
2808
|
+
// ../../packages/parsers/src/codex/counters.ts
|
|
2809
|
+
var SEEN_RAW_TOTALS_LIMIT = 64;
|
|
2810
|
+
var ZERO_CODEX_USAGE = {
|
|
2811
|
+
inputTokens: 0,
|
|
2812
|
+
outputTokens: 0,
|
|
2813
|
+
cacheReadTokens: 0
|
|
2508
2814
|
};
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2815
|
+
function normalizeCodexUsage(usage) {
|
|
2816
|
+
return {
|
|
2817
|
+
inputTokens: Math.max(0, usage.input_tokens ?? 0),
|
|
2818
|
+
outputTokens: Math.max(0, usage.output_tokens ?? 0),
|
|
2819
|
+
cacheReadTokens: Math.max(0, usage.cached_input_tokens ?? usage.cache_read_input_tokens ?? 0)
|
|
2820
|
+
};
|
|
2821
|
+
}
|
|
2822
|
+
function sameCodexUsage(a, b) {
|
|
2823
|
+
return a.inputTokens === b.inputTokens && a.outputTokens === b.outputTokens && a.cacheReadTokens === b.cacheReadTokens;
|
|
2824
|
+
}
|
|
2825
|
+
function positiveCodexUsage(usage) {
|
|
2826
|
+
return usage.inputTokens > 0 || usage.outputTokens > 0 || usage.cacheReadTokens > 0;
|
|
2827
|
+
}
|
|
2828
|
+
function addCodexUsage(a, b) {
|
|
2829
|
+
return {
|
|
2830
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
2831
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
2832
|
+
cacheReadTokens: a.cacheReadTokens + b.cacheReadTokens
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
function minCodexUsage(a, b) {
|
|
2836
|
+
return {
|
|
2837
|
+
inputTokens: Math.min(a.inputTokens, b.inputTokens),
|
|
2838
|
+
outputTokens: Math.min(a.outputTokens, b.outputTokens),
|
|
2839
|
+
cacheReadTokens: Math.min(a.cacheReadTokens, b.cacheReadTokens)
|
|
2840
|
+
};
|
|
2841
|
+
}
|
|
2842
|
+
function maxCodexUsage(a, b) {
|
|
2843
|
+
if (a === void 0) {
|
|
2844
|
+
return b;
|
|
2845
|
+
}
|
|
2846
|
+
return {
|
|
2847
|
+
inputTokens: Math.max(a.inputTokens, b.inputTokens),
|
|
2848
|
+
outputTokens: Math.max(a.outputTokens, b.outputTokens),
|
|
2849
|
+
cacheReadTokens: Math.max(a.cacheReadTokens, b.cacheReadTokens)
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
function subtractCodexUsageFloorZero(current, baseline) {
|
|
2853
|
+
return {
|
|
2854
|
+
inputTokens: Math.max(0, current.inputTokens - baseline.inputTokens),
|
|
2855
|
+
outputTokens: Math.max(0, current.outputTokens - baseline.outputTokens),
|
|
2856
|
+
cacheReadTokens: Math.max(0, current.cacheReadTokens - baseline.cacheReadTokens)
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
function codexUsageContains(current, baseline) {
|
|
2860
|
+
return current.inputTokens >= baseline.inputTokens && current.outputTokens >= baseline.outputTokens && current.cacheReadTokens >= baseline.cacheReadTokens;
|
|
2861
|
+
}
|
|
2862
|
+
function codexUsageAtMost(current, ceiling) {
|
|
2863
|
+
return current.inputTokens <= ceiling.inputTokens && current.outputTokens <= ceiling.outputTokens && current.cacheReadTokens <= ceiling.cacheReadTokens;
|
|
2864
|
+
}
|
|
2865
|
+
function makeCodexContainmentState() {
|
|
2866
|
+
return {
|
|
2867
|
+
countedUsage: void 0,
|
|
2868
|
+
rawBaseline: void 0,
|
|
2869
|
+
watermark: void 0,
|
|
2870
|
+
seenRawTotals: [],
|
|
2871
|
+
sawDivergentTotals: false,
|
|
2872
|
+
sawInterleavedTotals: false,
|
|
2873
|
+
sawUnresolvedTotal: false
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
function containedCodexDelta(watermark, counted, current) {
|
|
2877
|
+
const water = watermark ?? ZERO_CODEX_USAGE;
|
|
2878
|
+
const accepted = counted ?? ZERO_CODEX_USAGE;
|
|
2879
|
+
const component = (highWatermark, countedUsage, currentUsage) => currentUsage >= highWatermark ? Math.max(0, currentUsage - Math.max(highWatermark, countedUsage)) : Math.max(0, currentUsage - countedUsage);
|
|
2880
|
+
return {
|
|
2881
|
+
inputTokens: component(water.inputTokens, accepted.inputTokens, current.inputTokens),
|
|
2882
|
+
outputTokens: component(water.outputTokens, accepted.outputTokens, current.outputTokens),
|
|
2883
|
+
cacheReadTokens: component(water.cacheReadTokens, accepted.cacheReadTokens, current.cacheReadTokens)
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
function divergentCodexDelta(rawBaseline, countedBaseline, current) {
|
|
2887
|
+
const raw = rawBaseline ?? ZERO_CODEX_USAGE;
|
|
2888
|
+
const counted = countedBaseline ?? ZERO_CODEX_USAGE;
|
|
2889
|
+
const component = (rawUsage, countedUsage, currentUsage) => currentUsage >= rawUsage ? Math.max(0, currentUsage - rawUsage) : Math.max(0, currentUsage - countedUsage);
|
|
2890
|
+
return {
|
|
2891
|
+
inputTokens: component(raw.inputTokens, counted.inputTokens, current.inputTokens),
|
|
2892
|
+
outputTokens: component(raw.outputTokens, counted.outputTokens, current.outputTokens),
|
|
2893
|
+
cacheReadTokens: component(raw.cacheReadTokens, counted.cacheReadTokens, current.cacheReadTokens)
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
function rememberCodexTotal(state, total) {
|
|
2897
|
+
state.watermark = maxCodexUsage(state.watermark, total);
|
|
2898
|
+
if (state.seenRawTotals.some((seen) => sameCodexUsage(seen, total))) {
|
|
2899
|
+
return;
|
|
2900
|
+
}
|
|
2901
|
+
state.seenRawTotals.push(total);
|
|
2902
|
+
if (state.seenRawTotals.length > SEEN_RAW_TOTALS_LIMIT) {
|
|
2903
|
+
state.seenRawTotals.splice(0, state.seenRawTotals.length - SEEN_RAW_TOTALS_LIMIT);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
function applyCodexSnapshotWithLast(state, last, total, watermarkBaseline) {
|
|
2907
|
+
const base = state.countedUsage ?? ZERO_CODEX_USAGE;
|
|
2908
|
+
if (total === void 0) {
|
|
2909
|
+
state.countedUsage = addCodexUsage(base, last);
|
|
2910
|
+
state.rawBaseline = state.countedUsage;
|
|
2911
|
+
state.watermark = maxCodexUsage(state.watermark, state.countedUsage);
|
|
2912
|
+
return last;
|
|
2913
|
+
}
|
|
2914
|
+
let delta = last;
|
|
2915
|
+
if (state.sawInterleavedTotals) {
|
|
2916
|
+
delta = minCodexUsage(last, containedCodexDelta(watermarkBaseline, state.countedUsage, total));
|
|
2917
|
+
} else {
|
|
2918
|
+
const totalDelta = subtractCodexUsageFloorZero(total, watermarkBaseline ?? ZERO_CODEX_USAGE);
|
|
2919
|
+
const useTotalDelta = !state.sawDivergentTotals && watermarkBaseline !== void 0 && codexUsageContains(total, watermarkBaseline) && codexUsageAtMost(totalDelta, last);
|
|
2920
|
+
if (useTotalDelta) {
|
|
2921
|
+
delta = totalDelta;
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
state.countedUsage = addCodexUsage(base, delta);
|
|
2925
|
+
state.rawBaseline = total;
|
|
2926
|
+
state.sawDivergentTotals ||= !sameCodexUsage(total, state.countedUsage);
|
|
2927
|
+
return delta;
|
|
2928
|
+
}
|
|
2929
|
+
function applyCodexTotalOnlySnapshot(state, total, watermarkBaseline) {
|
|
2930
|
+
let delta;
|
|
2931
|
+
if (state.sawInterleavedTotals) {
|
|
2932
|
+
delta = containedCodexDelta(watermarkBaseline, state.countedUsage, total);
|
|
2933
|
+
} else if (state.sawDivergentTotals) {
|
|
2934
|
+
delta = divergentCodexDelta(watermarkBaseline, state.countedUsage, total);
|
|
2935
|
+
} else {
|
|
2936
|
+
delta = subtractCodexUsageFloorZero(total, watermarkBaseline ?? ZERO_CODEX_USAGE);
|
|
2937
|
+
}
|
|
2938
|
+
state.countedUsage = addCodexUsage(state.countedUsage ?? ZERO_CODEX_USAGE, delta);
|
|
2939
|
+
state.rawBaseline = total;
|
|
2940
|
+
state.sawDivergentTotals ||= !sameCodexUsage(total, state.countedUsage);
|
|
2941
|
+
return delta;
|
|
2942
|
+
}
|
|
2943
|
+
function applyCodexContainedSnapshot(state, last, total, options = {}) {
|
|
2944
|
+
if (total !== void 0 && state.seenRawTotals.some((seen) => sameCodexUsage(seen, total))) {
|
|
2945
|
+
return void 0;
|
|
2946
|
+
}
|
|
2947
|
+
if (total !== void 0 && state.watermark !== void 0 && !codexUsageContains(total, state.watermark)) {
|
|
2948
|
+
state.sawInterleavedTotals = true;
|
|
2949
|
+
}
|
|
2950
|
+
const watermarkBaseline = state.watermark ?? state.rawBaseline;
|
|
2951
|
+
const effectiveLast = total !== void 0 && options.totalOnlyBeforeInterleaving === true && !state.sawInterleavedTotals ? void 0 : last;
|
|
2952
|
+
let delta;
|
|
2953
|
+
if (effectiveLast !== void 0) {
|
|
2954
|
+
delta = applyCodexSnapshotWithLast(state, effectiveLast, total, watermarkBaseline);
|
|
2955
|
+
} else if (total !== void 0) {
|
|
2956
|
+
delta = applyCodexTotalOnlySnapshot(state, total, watermarkBaseline);
|
|
2957
|
+
}
|
|
2958
|
+
if (total !== void 0) {
|
|
2959
|
+
rememberCodexTotal(state, total);
|
|
2960
|
+
}
|
|
2961
|
+
return delta !== void 0 && positiveCodexUsage(delta) ? delta : void 0;
|
|
2962
|
+
}
|
|
2963
|
+
function applyCodexUnresolvedSnapshot(state, last, total) {
|
|
2964
|
+
if (total === void 0) {
|
|
2965
|
+
return applyCodexContainedSnapshot(state, last, void 0);
|
|
2966
|
+
}
|
|
2967
|
+
if (state.seenRawTotals.some((seen) => sameCodexUsage(seen, total))) {
|
|
2968
|
+
return void 0;
|
|
2969
|
+
}
|
|
2970
|
+
if (state.watermark !== void 0 && !codexUsageContains(total, state.watermark)) {
|
|
2971
|
+
state.sawInterleavedTotals = true;
|
|
2972
|
+
}
|
|
2973
|
+
const watermarkBaseline = state.watermark ?? state.rawBaseline;
|
|
2974
|
+
let delta;
|
|
2975
|
+
if (state.sawUnresolvedTotal && last !== void 0) {
|
|
2976
|
+
delta = minCodexUsage(last, subtractCodexUsageFloorZero(total, watermarkBaseline ?? ZERO_CODEX_USAGE));
|
|
2977
|
+
state.countedUsage = addCodexUsage(state.countedUsage ?? ZERO_CODEX_USAGE, delta);
|
|
2978
|
+
state.rawBaseline = state.countedUsage;
|
|
2979
|
+
}
|
|
2980
|
+
state.sawUnresolvedTotal = true;
|
|
2981
|
+
rememberCodexTotal(state, total);
|
|
2982
|
+
return delta !== void 0 && positiveCodexUsage(delta) ? delta : void 0;
|
|
2983
|
+
}
|
|
2984
|
+
function cumulativeCodexDelta(current, previous) {
|
|
2985
|
+
if (previous === void 0) {
|
|
2986
|
+
return current;
|
|
2987
|
+
}
|
|
2988
|
+
return {
|
|
2989
|
+
inputTokens: current.inputTokens < previous.inputTokens ? current.inputTokens : current.inputTokens - previous.inputTokens,
|
|
2990
|
+
outputTokens: current.outputTokens < previous.outputTokens ? current.outputTokens : current.outputTokens - previous.outputTokens,
|
|
2991
|
+
cacheReadTokens: current.cacheReadTokens < previous.cacheReadTokens ? current.cacheReadTokens : current.cacheReadTokens - previous.cacheReadTokens
|
|
2992
|
+
};
|
|
2993
|
+
}
|
|
2514
2994
|
|
|
2515
2995
|
// ../../packages/parsers/src/codex/events.ts
|
|
2516
2996
|
function isObject(value) {
|
|
@@ -2519,7 +2999,13 @@ function isObject(value) {
|
|
|
2519
2999
|
function isFiniteNumber(value) {
|
|
2520
3000
|
return typeof value === "number" && Number.isFinite(value);
|
|
2521
3001
|
}
|
|
2522
|
-
|
|
3002
|
+
var MAX_CODEX_SOURCE_ID_LENGTH = 512;
|
|
3003
|
+
var MAX_CODEX_TIMESTAMP_LENGTH = 128;
|
|
3004
|
+
var MAX_CODEX_LOCAL_PATH_LENGTH = 4096;
|
|
3005
|
+
function isOptionalBoundedString(value, maxLength) {
|
|
3006
|
+
return value === void 0 || typeof value === "string" && value.length <= maxLength;
|
|
3007
|
+
}
|
|
3008
|
+
function isCodexUsageCounters(value) {
|
|
2523
3009
|
if (!isObject(value)) {
|
|
2524
3010
|
return false;
|
|
2525
3011
|
}
|
|
@@ -2549,6 +3035,11 @@ function isCodexTokenCountEvent(value) {
|
|
|
2549
3035
|
if (payload["type"] !== "token_count") {
|
|
2550
3036
|
return false;
|
|
2551
3037
|
}
|
|
3038
|
+
for (const key6 of ["id", "turn_id", "turnId"]) {
|
|
3039
|
+
if (!isOptionalBoundedString(payload[key6], MAX_CODEX_SOURCE_ID_LENGTH)) {
|
|
3040
|
+
return false;
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
2552
3043
|
const info = payload["info"];
|
|
2553
3044
|
if (info !== void 0) {
|
|
2554
3045
|
if (!isObject(info)) {
|
|
@@ -2557,20 +3048,47 @@ function isCodexTokenCountEvent(value) {
|
|
|
2557
3048
|
if (info["model"] !== void 0 && typeof info["model"] !== "string") {
|
|
2558
3049
|
return false;
|
|
2559
3050
|
}
|
|
2560
|
-
|
|
3051
|
+
for (const key6 of ["id", "turn_id", "turnId"]) {
|
|
3052
|
+
if (!isOptionalBoundedString(info[key6], MAX_CODEX_SOURCE_ID_LENGTH)) {
|
|
3053
|
+
return false;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
if (info["last_token_usage"] !== void 0 && !isCodexUsageCounters(info["last_token_usage"])) {
|
|
2561
3057
|
return false;
|
|
2562
3058
|
}
|
|
2563
|
-
if (info["total_token_usage"] !== void 0 && !
|
|
3059
|
+
if (info["total_token_usage"] !== void 0 && !isCodexUsageCounters(info["total_token_usage"])) {
|
|
3060
|
+
return false;
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
if (payload["model"] !== void 0 && typeof payload["model"] !== "string") {
|
|
3064
|
+
return false;
|
|
3065
|
+
}
|
|
3066
|
+
if (value["model"] !== void 0 && typeof value["model"] !== "string") {
|
|
3067
|
+
return false;
|
|
3068
|
+
}
|
|
3069
|
+
return true;
|
|
3070
|
+
}
|
|
3071
|
+
function isCodexTaskStartedEvent(value) {
|
|
3072
|
+
if (!isObject(value) || value["type"] !== "event_msg" || typeof value["timestamp"] !== "string") {
|
|
3073
|
+
return false;
|
|
3074
|
+
}
|
|
3075
|
+
const payload = value["payload"];
|
|
3076
|
+
if (!isObject(payload) || payload["type"] !== "task_started") {
|
|
3077
|
+
return false;
|
|
3078
|
+
}
|
|
3079
|
+
for (const key6 of ["id", "turn_id", "turnId"]) {
|
|
3080
|
+
if (!isOptionalBoundedString(payload[key6], MAX_CODEX_SOURCE_ID_LENGTH)) {
|
|
2564
3081
|
return false;
|
|
2565
3082
|
}
|
|
2566
3083
|
}
|
|
2567
|
-
|
|
2568
|
-
|
|
3084
|
+
const info = payload["info"];
|
|
3085
|
+
if (info === void 0) {
|
|
3086
|
+
return true;
|
|
2569
3087
|
}
|
|
2570
|
-
if (
|
|
3088
|
+
if (!isObject(info)) {
|
|
2571
3089
|
return false;
|
|
2572
3090
|
}
|
|
2573
|
-
return
|
|
3091
|
+
return ["id", "turn_id", "turnId"].every((key6) => isOptionalBoundedString(info[key6], MAX_CODEX_SOURCE_ID_LENGTH));
|
|
2574
3092
|
}
|
|
2575
3093
|
function isCodexTurnContextEvent(value) {
|
|
2576
3094
|
if (!isObject(value)) {
|
|
@@ -2610,46 +3128,105 @@ function isCodexSessionMetaEvent(value) {
|
|
|
2610
3128
|
if (value["type"] !== "session_meta") {
|
|
2611
3129
|
return false;
|
|
2612
3130
|
}
|
|
2613
|
-
if (value["timestamp"]
|
|
3131
|
+
if (!isOptionalBoundedString(value["timestamp"], MAX_CODEX_TIMESTAMP_LENGTH)) {
|
|
2614
3132
|
return false;
|
|
2615
3133
|
}
|
|
3134
|
+
for (const key6 of ["id", "session_id", "sessionId"]) {
|
|
3135
|
+
if (!isOptionalBoundedString(value[key6], MAX_CODEX_SOURCE_ID_LENGTH)) {
|
|
3136
|
+
return false;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
2616
3139
|
const payload = value["payload"];
|
|
2617
3140
|
if (!isObject(payload)) {
|
|
2618
3141
|
return false;
|
|
2619
3142
|
}
|
|
2620
|
-
if (payload["cwd"]
|
|
3143
|
+
if (!isOptionalBoundedString(payload["cwd"], MAX_CODEX_LOCAL_PATH_LENGTH)) {
|
|
3144
|
+
return false;
|
|
3145
|
+
}
|
|
3146
|
+
for (const key6 of [
|
|
3147
|
+
"id",
|
|
3148
|
+
"session_id",
|
|
3149
|
+
"sessionId",
|
|
3150
|
+
"forked_from_id",
|
|
3151
|
+
"forkedFromId",
|
|
3152
|
+
"parent_thread_id",
|
|
3153
|
+
"parent_session_id",
|
|
3154
|
+
"parentSessionId"
|
|
3155
|
+
]) {
|
|
3156
|
+
if (!isOptionalBoundedString(payload[key6], MAX_CODEX_SOURCE_ID_LENGTH)) {
|
|
3157
|
+
return false;
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
if (!isOptionalBoundedString(payload["timestamp"], MAX_CODEX_TIMESTAMP_LENGTH)) {
|
|
2621
3161
|
return false;
|
|
2622
3162
|
}
|
|
2623
3163
|
return true;
|
|
2624
3164
|
}
|
|
2625
|
-
function
|
|
2626
|
-
|
|
3165
|
+
function nonEmpty(value) {
|
|
3166
|
+
const trimmed = value?.trim();
|
|
3167
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
2627
3168
|
}
|
|
2628
|
-
function
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
3169
|
+
function extractCodexSourceMetadata(event) {
|
|
3170
|
+
const sourceSessionId = nonEmpty(
|
|
3171
|
+
event.payload.id ?? event.id ?? event.payload.session_id ?? event.payload.sessionId ?? event.session_id ?? event.sessionId
|
|
3172
|
+
);
|
|
3173
|
+
const canonicalSessionId = nonEmpty(
|
|
3174
|
+
event.payload.session_id ?? event.payload.sessionId ?? event.session_id ?? event.sessionId
|
|
3175
|
+
);
|
|
3176
|
+
return {
|
|
3177
|
+
sourceSessionId,
|
|
3178
|
+
canonicalSessionId,
|
|
3179
|
+
lineageSessionId: canonicalSessionId ?? sourceSessionId,
|
|
3180
|
+
parentSourceSessionId: nonEmpty(
|
|
3181
|
+
event.payload.forked_from_id ?? event.payload.forkedFromId ?? event.payload.parent_session_id ?? event.payload.parentSessionId
|
|
3182
|
+
),
|
|
3183
|
+
// Native agent files also carry parent_thread_id for structural thread
|
|
3184
|
+
// ownership. It is not evidence that token counters share a cumulative
|
|
3185
|
+
// scale, so retain it as bounded metadata without using it as a baseline.
|
|
3186
|
+
parentThreadId: nonEmpty(event.payload.parent_thread_id),
|
|
3187
|
+
forkedAt: nonEmpty(event.payload.timestamp ?? event.timestamp),
|
|
3188
|
+
cwd: event.payload.cwd
|
|
3189
|
+
};
|
|
2641
3190
|
}
|
|
2642
|
-
function
|
|
3191
|
+
function extractCodexUsageSnapshot(event) {
|
|
2643
3192
|
return {
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
excluded: false,
|
|
2647
|
-
lineNo: 0,
|
|
2648
|
-
sawTurnContext: false,
|
|
2649
|
-
sessionLastSeenAt: void 0,
|
|
2650
|
-
sessionStartedAt: void 0
|
|
3193
|
+
last: event.payload.info?.last_token_usage,
|
|
3194
|
+
total: event.payload.info?.total_token_usage
|
|
2651
3195
|
};
|
|
2652
3196
|
}
|
|
3197
|
+
function pickCodexEventModel(event) {
|
|
3198
|
+
return event.payload.info?.model ?? event.payload.model ?? event.model;
|
|
3199
|
+
}
|
|
3200
|
+
function pickCodexTurnId(event) {
|
|
3201
|
+
return nonEmpty(
|
|
3202
|
+
event.payload.turn_id ?? event.payload.turnId ?? event.payload.id ?? event.payload.info?.turn_id ?? event.payload.info?.turnId ?? event.payload.info?.id
|
|
3203
|
+
);
|
|
3204
|
+
}
|
|
3205
|
+
|
|
3206
|
+
// ../../packages/parsers/src/codex/lineage.ts
|
|
3207
|
+
import { createReadStream } from "fs";
|
|
3208
|
+
import { open, stat } from "fs/promises";
|
|
3209
|
+
import { normalize as normalize2 } from "path";
|
|
3210
|
+
import { createInterface as createInterface2 } from "readline";
|
|
3211
|
+
import { Cause, Effect as Effect15, Queue, Stream as Stream8 } from "effect";
|
|
3212
|
+
|
|
3213
|
+
// ../../packages/parsers/src/types.ts
|
|
3214
|
+
import { Schema as Schema21, Stream as Stream7 } from "effect";
|
|
3215
|
+
var ParserSourceReadError = class extends Schema21.TaggedErrorClass()("ParserSourceReadError", {
|
|
3216
|
+
message: Schema21.String,
|
|
3217
|
+
path: Schema21.String,
|
|
3218
|
+
cause: Schema21.optionalKey(Schema21.Defect())
|
|
3219
|
+
}) {
|
|
3220
|
+
};
|
|
3221
|
+
var makeParserSourceReadError = (path, error) => new ParserSourceReadError({
|
|
3222
|
+
message: `Parser source read failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3223
|
+
path,
|
|
3224
|
+
cause: error
|
|
3225
|
+
});
|
|
3226
|
+
|
|
3227
|
+
// ../../packages/parsers/src/codex/lineage.ts
|
|
3228
|
+
var CANDIDATE_TAIL_BYTES = 262144;
|
|
3229
|
+
var CANDIDATE_INSPECTION_CONCURRENCY = 8;
|
|
2653
3230
|
function lineStreamFromFile(path) {
|
|
2654
3231
|
return Stream8.callback(
|
|
2655
3232
|
(queue) => Effect15.acquireRelease(
|
|
@@ -2694,10 +3271,445 @@ function lineStreamFromFile(path) {
|
|
|
2694
3271
|
)
|
|
2695
3272
|
);
|
|
2696
3273
|
}
|
|
3274
|
+
async function inspectSourceMetadata(path) {
|
|
3275
|
+
const stream = createReadStream(path, { encoding: "utf8" });
|
|
3276
|
+
const lines = createInterface2({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
3277
|
+
let first;
|
|
3278
|
+
const sourceSessionIds = /* @__PURE__ */ new Set();
|
|
3279
|
+
try {
|
|
3280
|
+
for await (const line of lines) {
|
|
3281
|
+
if (!line.includes("session_meta")) {
|
|
3282
|
+
continue;
|
|
3283
|
+
}
|
|
3284
|
+
let parsed;
|
|
3285
|
+
try {
|
|
3286
|
+
parsed = JSON.parse(line);
|
|
3287
|
+
} catch {
|
|
3288
|
+
continue;
|
|
3289
|
+
}
|
|
3290
|
+
if (isCodexSessionMetaEvent(parsed)) {
|
|
3291
|
+
const metadata = extractCodexSourceMetadata(parsed);
|
|
3292
|
+
first ??= metadata;
|
|
3293
|
+
if (metadata.sourceSessionId !== void 0) {
|
|
3294
|
+
sourceSessionIds.add(metadata.sourceSessionId);
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
return { first, containsMultipleSessionIds: sourceSessionIds.size > 1 };
|
|
3299
|
+
} finally {
|
|
3300
|
+
lines.close();
|
|
3301
|
+
stream.destroy();
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
async function hasCompleteTail(path, size) {
|
|
3305
|
+
if (size <= 0) {
|
|
3306
|
+
return false;
|
|
3307
|
+
}
|
|
3308
|
+
const handle = await open(path, "r");
|
|
3309
|
+
try {
|
|
3310
|
+
const bytesToRead = Math.min(size, CANDIDATE_TAIL_BYTES);
|
|
3311
|
+
const tail = Buffer.allocUnsafe(bytesToRead);
|
|
3312
|
+
await handle.read(tail, 0, bytesToRead, size - bytesToRead);
|
|
3313
|
+
const lastLine = tail.toString("utf8").split("\n").findLast((line) => line.trim().length > 0)?.trim();
|
|
3314
|
+
if (!lastLine) {
|
|
3315
|
+
return false;
|
|
3316
|
+
}
|
|
3317
|
+
try {
|
|
3318
|
+
JSON.parse(lastLine);
|
|
3319
|
+
return true;
|
|
3320
|
+
} catch {
|
|
3321
|
+
return false;
|
|
3322
|
+
}
|
|
3323
|
+
} finally {
|
|
3324
|
+
await handle.close();
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
function inspectCodexSourceCandidate(path) {
|
|
3328
|
+
return Effect15.tryPromise({
|
|
3329
|
+
try: async () => {
|
|
3330
|
+
const info = await stat(path);
|
|
3331
|
+
const inspectedMetadata = await inspectSourceMetadata(path);
|
|
3332
|
+
return {
|
|
3333
|
+
path,
|
|
3334
|
+
normalizedPath: normalize2(path),
|
|
3335
|
+
metadata: inspectedMetadata.first,
|
|
3336
|
+
containsMultipleSessionIds: inspectedMetadata.containsMultipleSessionIds,
|
|
3337
|
+
complete: await hasCompleteTail(path, info.size),
|
|
3338
|
+
mtimeMs: info.mtimeMs,
|
|
3339
|
+
size: info.size
|
|
3340
|
+
};
|
|
3341
|
+
},
|
|
3342
|
+
catch: (error) => makeParserSourceReadError(path, error)
|
|
3343
|
+
});
|
|
3344
|
+
}
|
|
3345
|
+
function candidateComesFirst(a, b) {
|
|
3346
|
+
if (a.complete !== b.complete) {
|
|
3347
|
+
return a.complete ? -1 : 1;
|
|
3348
|
+
}
|
|
3349
|
+
if (a.size !== b.size) {
|
|
3350
|
+
return b.size - a.size;
|
|
3351
|
+
}
|
|
3352
|
+
if (a.mtimeMs !== b.mtimeMs) {
|
|
3353
|
+
return b.mtimeMs - a.mtimeMs;
|
|
3354
|
+
}
|
|
3355
|
+
return a.normalizedPath.localeCompare(b.normalizedPath);
|
|
3356
|
+
}
|
|
3357
|
+
function candidateConflictKind(candidates) {
|
|
3358
|
+
const parents = new Set(candidates.map((candidate) => candidate.metadata?.parentSourceSessionId ?? ""));
|
|
3359
|
+
if (parents.size > 1) {
|
|
3360
|
+
return "parent";
|
|
3361
|
+
}
|
|
3362
|
+
const forkBoundaries = new Set(candidates.map((candidate) => candidate.metadata?.forkedAt ?? ""));
|
|
3363
|
+
return forkBoundaries.size > 1 ? "fork_boundary" : void 0;
|
|
3364
|
+
}
|
|
3365
|
+
function cycleSessionIds(sources) {
|
|
3366
|
+
const parents = /* @__PURE__ */ new Map();
|
|
3367
|
+
const knownIds = new Set(
|
|
3368
|
+
sources.flatMap((source) => source.sourceSessionId === void 0 ? [] : [source.sourceSessionId])
|
|
3369
|
+
);
|
|
3370
|
+
for (const source of sources) {
|
|
3371
|
+
if (source.sourceSessionId !== void 0 && source.parentSourceSessionId !== void 0 && knownIds.has(source.parentSourceSessionId)) {
|
|
3372
|
+
parents.set(source.sourceSessionId, source.parentSourceSessionId);
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
const cycles = /* @__PURE__ */ new Set();
|
|
3376
|
+
for (const sourceId of [...knownIds].sort()) {
|
|
3377
|
+
const positions = /* @__PURE__ */ new Map();
|
|
3378
|
+
const chain = [];
|
|
3379
|
+
let current = sourceId;
|
|
3380
|
+
while (current !== void 0) {
|
|
3381
|
+
const cycleStart = positions.get(current);
|
|
3382
|
+
if (cycleStart !== void 0) {
|
|
3383
|
+
for (const member of chain.slice(cycleStart)) {
|
|
3384
|
+
cycles.add(member);
|
|
3385
|
+
}
|
|
3386
|
+
break;
|
|
3387
|
+
}
|
|
3388
|
+
positions.set(current, chain.length);
|
|
3389
|
+
chain.push(current);
|
|
3390
|
+
current = parents.get(current);
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
return cycles;
|
|
3394
|
+
}
|
|
3395
|
+
function groupCodexSourceCandidates(candidates) {
|
|
3396
|
+
const groups = /* @__PURE__ */ new Map();
|
|
3397
|
+
for (const candidate of candidates) {
|
|
3398
|
+
const sourceId = candidate.metadata?.sourceSessionId;
|
|
3399
|
+
const groupKey = sourceId === void 0 ? `path:${candidate.normalizedPath}` : `session:${sourceId}`;
|
|
3400
|
+
const group = groups.get(groupKey) ?? [];
|
|
3401
|
+
group.push(candidate);
|
|
3402
|
+
groups.set(groupKey, group);
|
|
3403
|
+
}
|
|
3404
|
+
return groups;
|
|
3405
|
+
}
|
|
3406
|
+
function indexedSourceFromCandidates(group, cutoffMs, logger) {
|
|
3407
|
+
group.sort(candidateComesFirst);
|
|
3408
|
+
const [canonical] = group;
|
|
3409
|
+
if (canonical === void 0) {
|
|
3410
|
+
return void 0;
|
|
3411
|
+
}
|
|
3412
|
+
const conflict = candidateConflictKind(group);
|
|
3413
|
+
if (conflict !== void 0) {
|
|
3414
|
+
logger.warn("conflicting codex source candidates", {
|
|
3415
|
+
candidateCount: group.length,
|
|
3416
|
+
conflict
|
|
3417
|
+
});
|
|
3418
|
+
}
|
|
3419
|
+
const sourceSessionId = canonical.metadata?.sourceSessionId;
|
|
3420
|
+
return {
|
|
3421
|
+
path: canonical.path,
|
|
3422
|
+
sourceIdentity: sourceSessionId ?? canonical.normalizedPath,
|
|
3423
|
+
sourceSessionId,
|
|
3424
|
+
lineageSessionId: canonical.metadata?.lineageSessionId,
|
|
3425
|
+
parentSourceSessionId: canonical.metadata?.parentSourceSessionId,
|
|
3426
|
+
forkedAt: canonical.metadata?.forkedAt,
|
|
3427
|
+
cwd: canonical.metadata?.cwd,
|
|
3428
|
+
containsMultipleSessionIds: canonical.containsMultipleSessionIds || canonical.metadata?.canonicalSessionId !== void 0,
|
|
3429
|
+
eligible: group.some((candidate) => candidate.mtimeMs >= cutoffMs)
|
|
3430
|
+
};
|
|
3431
|
+
}
|
|
3432
|
+
function indexCodexLineageSessions(sources) {
|
|
3433
|
+
const bySessionId = /* @__PURE__ */ new Map();
|
|
3434
|
+
for (const source of sources) {
|
|
3435
|
+
const { lineageSessionId } = source;
|
|
3436
|
+
if (lineageSessionId === void 0) {
|
|
3437
|
+
continue;
|
|
3438
|
+
}
|
|
3439
|
+
const existing = bySessionId.get(lineageSessionId);
|
|
3440
|
+
if (existing === void 0 || source.sourceSessionId === lineageSessionId && existing.sourceSessionId !== lineageSessionId) {
|
|
3441
|
+
bySessionId.set(lineageSessionId, source);
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
return bySessionId;
|
|
3445
|
+
}
|
|
3446
|
+
var buildCodexSourceIndex = Effect15.fn("CodexLineage.buildSourceIndex")(function* (paths, cutoff, logger) {
|
|
3447
|
+
const uniquePaths = [...new Set(paths.map(normalize2))].sort();
|
|
3448
|
+
const candidates = yield* Effect15.forEach(uniquePaths, inspectCodexSourceCandidate, {
|
|
3449
|
+
concurrency: CANDIDATE_INSPECTION_CONCURRENCY
|
|
3450
|
+
});
|
|
3451
|
+
const groups = groupCodexSourceCandidates(candidates);
|
|
3452
|
+
const sources = [];
|
|
3453
|
+
for (const groupKey of [...groups.keys()].sort()) {
|
|
3454
|
+
const group = groups.get(groupKey) ?? [];
|
|
3455
|
+
const source = indexedSourceFromCandidates(group, cutoff.getTime(), logger);
|
|
3456
|
+
if (source !== void 0) {
|
|
3457
|
+
sources.push(source);
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
sources.sort((a, b) => a.sourceIdentity.localeCompare(b.sourceIdentity));
|
|
3461
|
+
return {
|
|
3462
|
+
sources,
|
|
3463
|
+
bySessionId: indexCodexLineageSessions(sources),
|
|
3464
|
+
cycleSessionIds: cycleSessionIds(sources)
|
|
3465
|
+
};
|
|
3466
|
+
});
|
|
3467
|
+
function parseTimestamp(timestamp) {
|
|
3468
|
+
const parsed = Date.parse(timestamp);
|
|
3469
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
3470
|
+
}
|
|
3471
|
+
function parseSnapshotIncrement(state, last, total) {
|
|
3472
|
+
if (last !== void 0) {
|
|
3473
|
+
return normalizeCodexUsage(last);
|
|
3474
|
+
}
|
|
3475
|
+
return total === void 0 ? void 0 : cumulativeCodexDelta(total, state.previousTotal);
|
|
3476
|
+
}
|
|
3477
|
+
function accumulateCodexSnapshot(state, raw) {
|
|
3478
|
+
let parsed;
|
|
3479
|
+
try {
|
|
3480
|
+
parsed = JSON.parse(raw);
|
|
3481
|
+
} catch {
|
|
3482
|
+
return state;
|
|
3483
|
+
}
|
|
3484
|
+
if (!isCodexTokenCountEvent(parsed)) {
|
|
3485
|
+
return state;
|
|
3486
|
+
}
|
|
3487
|
+
const snapshot = extractCodexUsageSnapshot(parsed);
|
|
3488
|
+
const total = snapshot.total === void 0 ? void 0 : normalizeCodexUsage(snapshot.total);
|
|
3489
|
+
if (state.containment !== void 0) {
|
|
3490
|
+
const last = snapshot.last === void 0 ? void 0 : normalizeCodexUsage(snapshot.last);
|
|
3491
|
+
const increment2 = applyCodexContainedSnapshot(state.containment, last, total);
|
|
3492
|
+
if (increment2 === void 0) {
|
|
3493
|
+
return state;
|
|
3494
|
+
}
|
|
3495
|
+
state.counted = addCodexUsage(state.counted, increment2);
|
|
3496
|
+
state.snapshots.push({
|
|
3497
|
+
timestamp: parsed.timestamp,
|
|
3498
|
+
timestampMs: parseTimestamp(parsed.timestamp),
|
|
3499
|
+
usage: state.counted
|
|
3500
|
+
});
|
|
3501
|
+
return state;
|
|
3502
|
+
}
|
|
3503
|
+
if (total !== void 0 && state.previousTotal !== void 0 && sameCodexUsage(total, state.previousTotal)) {
|
|
3504
|
+
return state;
|
|
3505
|
+
}
|
|
3506
|
+
const increment = parseSnapshotIncrement(state, snapshot.last, total);
|
|
3507
|
+
if (total !== void 0) {
|
|
3508
|
+
state.previousTotal = total;
|
|
3509
|
+
}
|
|
3510
|
+
if (increment === void 0 || !positiveCodexUsage(increment)) {
|
|
3511
|
+
return state;
|
|
3512
|
+
}
|
|
3513
|
+
state.counted = addCodexUsage(state.counted, increment);
|
|
3514
|
+
state.snapshots.push({
|
|
3515
|
+
timestamp: parsed.timestamp,
|
|
3516
|
+
timestampMs: parseTimestamp(parsed.timestamp),
|
|
3517
|
+
usage: state.counted
|
|
3518
|
+
});
|
|
3519
|
+
return state;
|
|
3520
|
+
}
|
|
3521
|
+
function collectCodexSnapshots(path, containsMultipleSessionIds) {
|
|
3522
|
+
return lineStreamFromFile(path).pipe(
|
|
3523
|
+
Stream8.runFold(
|
|
3524
|
+
() => ({
|
|
3525
|
+
snapshots: [],
|
|
3526
|
+
containment: containsMultipleSessionIds ? makeCodexContainmentState() : void 0,
|
|
3527
|
+
counted: ZERO_CODEX_USAGE,
|
|
3528
|
+
previousTotal: void 0
|
|
3529
|
+
}),
|
|
3530
|
+
accumulateCodexSnapshot
|
|
3531
|
+
),
|
|
3532
|
+
Effect15.map((state) => state.snapshots)
|
|
3533
|
+
);
|
|
3534
|
+
}
|
|
3535
|
+
function selectInheritedUsage(snapshots, forkedAt, forkedAtMs) {
|
|
3536
|
+
let inherited = ZERO_CODEX_USAGE;
|
|
3537
|
+
for (const snapshot of snapshots) {
|
|
3538
|
+
const atOrBefore = snapshot.timestampMs === void 0 ? snapshot.timestamp <= forkedAt : snapshot.timestampMs <= forkedAtMs;
|
|
3539
|
+
if (atOrBefore) {
|
|
3540
|
+
inherited = snapshot.usage;
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
return inherited;
|
|
3544
|
+
}
|
|
3545
|
+
function makeCodexLineageResolver(index, logger) {
|
|
3546
|
+
const snapshotsBySessionId = /* @__PURE__ */ new Map();
|
|
3547
|
+
const warned = /* @__PURE__ */ new Set();
|
|
3548
|
+
const unresolved = (source, reason) => {
|
|
3549
|
+
const warningKey = `${source.sourceIdentity}:${reason}`;
|
|
3550
|
+
if (!warned.has(warningKey)) {
|
|
3551
|
+
warned.add(warningKey);
|
|
3552
|
+
logger.warn("codex fork baseline unresolved", { reason });
|
|
3553
|
+
}
|
|
3554
|
+
return { _tag: "Unresolved", reason };
|
|
3555
|
+
};
|
|
3556
|
+
const requestFor = (source) => {
|
|
3557
|
+
const parentId = source.parentSourceSessionId;
|
|
3558
|
+
if (parentId === void 0) {
|
|
3559
|
+
return { _tag: "Root" };
|
|
3560
|
+
}
|
|
3561
|
+
if (source.sourceSessionId !== void 0 && index.cycleSessionIds.has(source.sourceSessionId)) {
|
|
3562
|
+
return unresolved(source, "cycle");
|
|
3563
|
+
}
|
|
3564
|
+
const parent = index.bySessionId.get(parentId);
|
|
3565
|
+
if (parent === void 0) {
|
|
3566
|
+
return unresolved(source, "missing_parent");
|
|
3567
|
+
}
|
|
3568
|
+
const { forkedAt } = source;
|
|
3569
|
+
const forkedAtMs = forkedAt === void 0 ? void 0 : parseTimestamp(forkedAt);
|
|
3570
|
+
if (forkedAt === void 0 || forkedAtMs === void 0) {
|
|
3571
|
+
return unresolved(source, "invalid_fork_boundary");
|
|
3572
|
+
}
|
|
3573
|
+
return { _tag: "Ready", forkedAt, forkedAtMs, parent, parentId };
|
|
3574
|
+
};
|
|
3575
|
+
return {
|
|
3576
|
+
baselineFor: Effect15.fn("CodexLineage.baselineFor")(function* (source) {
|
|
3577
|
+
const request = requestFor(source);
|
|
3578
|
+
if (request._tag !== "Ready") {
|
|
3579
|
+
return request;
|
|
3580
|
+
}
|
|
3581
|
+
let snapshots = snapshotsBySessionId.get(request.parentId);
|
|
3582
|
+
if (snapshots === void 0) {
|
|
3583
|
+
snapshots = yield* collectCodexSnapshots(request.parent.path, request.parent.containsMultipleSessionIds);
|
|
3584
|
+
snapshotsBySessionId.set(request.parentId, snapshots);
|
|
3585
|
+
}
|
|
3586
|
+
return {
|
|
3587
|
+
_tag: "Resolved",
|
|
3588
|
+
usage: selectInheritedUsage(snapshots, request.forkedAt, request.forkedAtMs)
|
|
3589
|
+
};
|
|
3590
|
+
})
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
// ../../packages/parsers/src/codex/bucketize.ts
|
|
3595
|
+
function startOfUtcHour(d) {
|
|
3596
|
+
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), d.getUTCHours(), 0, 0, 0));
|
|
3597
|
+
}
|
|
3598
|
+
function key2(hourUtc, model) {
|
|
3599
|
+
return `${hourUtc.toISOString()}|${model}`;
|
|
3600
|
+
}
|
|
3601
|
+
function shouldExcludePath(path, excludePath) {
|
|
3602
|
+
return path !== void 0 && excludePath?.(path) === true;
|
|
3603
|
+
}
|
|
3604
|
+
function makeCodexFileAccumulator(options) {
|
|
3605
|
+
const lineageBaseline = options.lineageBaseline ?? { _tag: "Root" };
|
|
3606
|
+
return {
|
|
3607
|
+
buckets: /* @__PURE__ */ new Map(),
|
|
3608
|
+
candidates: [],
|
|
3609
|
+
currentModel: void 0,
|
|
3610
|
+
currentTurnId: void 0,
|
|
3611
|
+
containment: options.containInterleavedTotals === true ? makeCodexContainmentState() : void 0,
|
|
3612
|
+
excluded: false,
|
|
3613
|
+
lineNo: 0,
|
|
3614
|
+
nextEventIndex: 0,
|
|
3615
|
+
lineageKind: lineageBaseline._tag,
|
|
3616
|
+
inheritedBaselineUsage: lineageBaseline._tag === "Resolved" ? lineageBaseline.usage : void 0,
|
|
3617
|
+
sawTurnContext: false,
|
|
3618
|
+
previousTotalUsage: lineageBaseline._tag === "Resolved" ? lineageBaseline.usage : void 0,
|
|
3619
|
+
remainingInheritedUsage: lineageBaseline._tag === "Resolved" ? lineageBaseline.usage : void 0,
|
|
3620
|
+
pendingUsage: [],
|
|
3621
|
+
sessionLastSeenAt: void 0,
|
|
3622
|
+
sessionStartedAt: void 0
|
|
3623
|
+
};
|
|
3624
|
+
}
|
|
3625
|
+
function subtractRemainingInheritedUsage(acc, rawIncrement) {
|
|
3626
|
+
const remaining = acc.remainingInheritedUsage;
|
|
3627
|
+
if (remaining === void 0) {
|
|
3628
|
+
return rawIncrement;
|
|
3629
|
+
}
|
|
3630
|
+
acc.remainingInheritedUsage = subtractCodexUsageFloorZero(remaining, rawIncrement);
|
|
3631
|
+
return subtractCodexUsageFloorZero(rawIncrement, remaining);
|
|
3632
|
+
}
|
|
3633
|
+
function positiveIncrement(increment) {
|
|
3634
|
+
return positiveCodexUsage(increment) ? increment : void 0;
|
|
3635
|
+
}
|
|
3636
|
+
function unresolvedUsageIncrement(acc, total) {
|
|
3637
|
+
if (total === void 0) {
|
|
3638
|
+
return void 0;
|
|
3639
|
+
}
|
|
3640
|
+
const previous = acc.previousTotalUsage;
|
|
3641
|
+
acc.previousTotalUsage = total;
|
|
3642
|
+
if (previous === void 0 || sameCodexUsage(previous, total)) {
|
|
3643
|
+
return void 0;
|
|
3644
|
+
}
|
|
3645
|
+
return positiveIncrement(cumulativeCodexDelta(total, previous));
|
|
3646
|
+
}
|
|
3647
|
+
function resolvedUsageIncrement(acc, last, total) {
|
|
3648
|
+
if (total !== void 0) {
|
|
3649
|
+
const previous = acc.previousTotalUsage;
|
|
3650
|
+
if (previous !== void 0 && sameCodexUsage(previous, total)) {
|
|
3651
|
+
return void 0;
|
|
3652
|
+
}
|
|
3653
|
+
acc.previousTotalUsage = total;
|
|
3654
|
+
acc.remainingInheritedUsage = void 0;
|
|
3655
|
+
return positiveIncrement(cumulativeCodexDelta(total, previous));
|
|
3656
|
+
}
|
|
3657
|
+
if (last === void 0) {
|
|
3658
|
+
return void 0;
|
|
3659
|
+
}
|
|
3660
|
+
const rawIncrement = normalizeCodexUsage(last);
|
|
3661
|
+
return positiveIncrement(subtractRemainingInheritedUsage(acc, rawIncrement));
|
|
3662
|
+
}
|
|
3663
|
+
function containedUsageIncrement(acc, last, total) {
|
|
3664
|
+
const { containment } = acc;
|
|
3665
|
+
if (containment === void 0) {
|
|
3666
|
+
return void 0;
|
|
3667
|
+
}
|
|
3668
|
+
const normalizedTotal = total === void 0 ? void 0 : normalizeCodexUsage(total);
|
|
3669
|
+
const normalizedLast = last === void 0 ? void 0 : normalizeCodexUsage(last);
|
|
3670
|
+
if (acc.lineageKind === "Unresolved") {
|
|
3671
|
+
return applyCodexUnresolvedSnapshot(containment, normalizedLast, normalizedTotal);
|
|
3672
|
+
}
|
|
3673
|
+
if (acc.lineageKind === "Resolved") {
|
|
3674
|
+
const adjustedTotal = normalizedTotal === void 0 || acc.inheritedBaselineUsage === void 0 ? normalizedTotal : subtractCodexUsageFloorZero(normalizedTotal, acc.inheritedBaselineUsage);
|
|
3675
|
+
const adjustedLast = normalizedTotal === void 0 && normalizedLast !== void 0 ? subtractRemainingInheritedUsage(acc, normalizedLast) : normalizedLast;
|
|
3676
|
+
const increment = applyCodexContainedSnapshot(containment, adjustedLast, adjustedTotal, {
|
|
3677
|
+
totalOnlyBeforeInterleaving: true
|
|
3678
|
+
});
|
|
3679
|
+
if (normalizedTotal !== void 0) {
|
|
3680
|
+
acc.remainingInheritedUsage = void 0;
|
|
3681
|
+
}
|
|
3682
|
+
return increment;
|
|
3683
|
+
}
|
|
3684
|
+
return applyCodexContainedSnapshot(containment, normalizedLast, normalizedTotal);
|
|
3685
|
+
}
|
|
3686
|
+
function rootUsageIncrement(acc, last, total) {
|
|
3687
|
+
if (total !== void 0) {
|
|
3688
|
+
const previous = acc.previousTotalUsage;
|
|
3689
|
+
if (previous !== void 0 && sameCodexUsage(previous, total)) {
|
|
3690
|
+
return void 0;
|
|
3691
|
+
}
|
|
3692
|
+
const increment = last === void 0 ? cumulativeCodexDelta(total, previous) : normalizeCodexUsage(last);
|
|
3693
|
+
acc.previousTotalUsage = total;
|
|
3694
|
+
return positiveIncrement(increment);
|
|
3695
|
+
}
|
|
3696
|
+
return last === void 0 ? void 0 : positiveIncrement(normalizeCodexUsage(last));
|
|
3697
|
+
}
|
|
3698
|
+
function usageIncrement(acc, last, total) {
|
|
3699
|
+
if (acc.containment !== void 0) {
|
|
3700
|
+
return containedUsageIncrement(acc, last, total);
|
|
3701
|
+
}
|
|
3702
|
+
const normalizedTotal = total === void 0 ? void 0 : normalizeCodexUsage(total);
|
|
3703
|
+
if (acc.lineageKind === "Resolved") {
|
|
3704
|
+
return resolvedUsageIncrement(acc, last, normalizedTotal);
|
|
3705
|
+
}
|
|
3706
|
+
if (acc.lineageKind === "Unresolved") {
|
|
3707
|
+
return unresolvedUsageIncrement(acc, normalizedTotal);
|
|
3708
|
+
}
|
|
3709
|
+
return rootUsageIncrement(acc, last, normalizedTotal);
|
|
3710
|
+
}
|
|
2697
3711
|
function addToBucket(acc, hourUtc, model, delta) {
|
|
2698
|
-
const inputTokens
|
|
2699
|
-
const outputTokens = Math.max(0, delta.output_tokens ?? 0);
|
|
2700
|
-
const cacheReadTokens = Math.max(0, delta.cached_input_tokens ?? delta.cache_read_input_tokens ?? 0);
|
|
3712
|
+
const { inputTokens, outputTokens, cacheReadTokens } = delta;
|
|
2701
3713
|
if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0) {
|
|
2702
3714
|
return false;
|
|
2703
3715
|
}
|
|
@@ -2719,6 +3731,19 @@ function addToBucket(acc, hourUtc, model, delta) {
|
|
|
2719
3731
|
existing.cacheReadTokens += cacheReadTokens;
|
|
2720
3732
|
return true;
|
|
2721
3733
|
}
|
|
3734
|
+
function recordUsageCandidate(acc, usage, model) {
|
|
3735
|
+
if (!addToBucket(acc, startOfUtcHour(usage.eventAt), model, usage.usage)) {
|
|
3736
|
+
return;
|
|
3737
|
+
}
|
|
3738
|
+
acc.candidates.push({ ...usage, model });
|
|
3739
|
+
markSessionSeen(acc, usage.eventAt);
|
|
3740
|
+
}
|
|
3741
|
+
function flushPendingUsage(acc, model) {
|
|
3742
|
+
for (const pending of acc.pendingUsage) {
|
|
3743
|
+
recordUsageCandidate(acc, pending, model);
|
|
3744
|
+
}
|
|
3745
|
+
acc.pendingUsage = [];
|
|
3746
|
+
}
|
|
2722
3747
|
function markSessionSeen(acc, eventAt) {
|
|
2723
3748
|
if (acc.sessionStartedAt === void 0 || eventAt.getTime() < acc.sessionStartedAt.getTime()) {
|
|
2724
3749
|
acc.sessionStartedAt = eventAt;
|
|
@@ -2748,6 +3773,8 @@ function accumulateCodexLine(context, acc, raw) {
|
|
|
2748
3773
|
if (shouldExcludePath(parsed.payload.cwd, options.excludePath)) {
|
|
2749
3774
|
acc.excluded = true;
|
|
2750
3775
|
acc.buckets.clear();
|
|
3776
|
+
acc.candidates = [];
|
|
3777
|
+
acc.pendingUsage = [];
|
|
2751
3778
|
}
|
|
2752
3779
|
return acc;
|
|
2753
3780
|
}
|
|
@@ -2755,15 +3782,22 @@ function accumulateCodexLine(context, acc, raw) {
|
|
|
2755
3782
|
if (shouldExcludePath(parsed.payload.cwd, options.excludePath)) {
|
|
2756
3783
|
acc.excluded = true;
|
|
2757
3784
|
acc.buckets.clear();
|
|
3785
|
+
acc.candidates = [];
|
|
3786
|
+
acc.pendingUsage = [];
|
|
2758
3787
|
return acc;
|
|
2759
3788
|
}
|
|
2760
3789
|
const model2 = parsed.payload.model ?? parsed.payload.info?.model;
|
|
2761
3790
|
if (model2 !== void 0) {
|
|
2762
3791
|
acc.currentModel = model2;
|
|
2763
3792
|
acc.sawTurnContext = true;
|
|
3793
|
+
flushPendingUsage(acc, model2);
|
|
2764
3794
|
}
|
|
2765
3795
|
return acc;
|
|
2766
3796
|
}
|
|
3797
|
+
if (isCodexTaskStartedEvent(parsed)) {
|
|
3798
|
+
acc.currentTurnId = pickCodexTurnId(parsed);
|
|
3799
|
+
return acc;
|
|
3800
|
+
}
|
|
2767
3801
|
if (!isCodexTokenCountEvent(parsed)) {
|
|
2768
3802
|
return acc;
|
|
2769
3803
|
}
|
|
@@ -2771,28 +3805,31 @@ function accumulateCodexLine(context, acc, raw) {
|
|
|
2771
3805
|
if (!Number.isFinite(tsMs)) {
|
|
2772
3806
|
return acc;
|
|
2773
3807
|
}
|
|
2774
|
-
const
|
|
2775
|
-
const
|
|
2776
|
-
if (model === void 0) {
|
|
2777
|
-
return acc;
|
|
2778
|
-
}
|
|
2779
|
-
const delta = pickCodexDelta(parsed);
|
|
3808
|
+
const snapshot = extractCodexUsageSnapshot(parsed);
|
|
3809
|
+
const delta = usageIncrement(acc, snapshot.last, snapshot.total);
|
|
2780
3810
|
if (delta === void 0) {
|
|
2781
3811
|
return acc;
|
|
2782
3812
|
}
|
|
2783
3813
|
const eventAt = new Date(tsMs);
|
|
2784
|
-
|
|
2785
|
-
|
|
3814
|
+
const eventIndex = acc.nextEventIndex;
|
|
3815
|
+
acc.nextEventIndex += 1;
|
|
3816
|
+
const turnId = pickCodexTurnId(parsed) ?? acc.currentTurnId;
|
|
3817
|
+
const candidate = { eventAt, eventIndex, turnId, usage: delta };
|
|
3818
|
+
const eventModel = pickCodexEventModel(parsed);
|
|
3819
|
+
const model = acc.sawTurnContext ? acc.currentModel ?? eventModel : eventModel ?? acc.currentModel;
|
|
3820
|
+
if (model === void 0) {
|
|
3821
|
+
acc.pendingUsage.push(candidate);
|
|
3822
|
+
return acc;
|
|
2786
3823
|
}
|
|
3824
|
+
recordUsageCandidate(acc, candidate, model);
|
|
2787
3825
|
return acc;
|
|
2788
3826
|
}
|
|
2789
|
-
function bucketsFromAccumulator(path, acc) {
|
|
3827
|
+
function bucketsFromAccumulator(path, options, acc) {
|
|
2790
3828
|
if (acc.excluded || acc.sessionStartedAt === void 0 || acc.sessionLastSeenAt === void 0) {
|
|
2791
3829
|
return [];
|
|
2792
3830
|
}
|
|
2793
|
-
const sessionStartedAt = acc
|
|
2794
|
-
const
|
|
2795
|
-
const sessionIds = /* @__PURE__ */ new Set([path]);
|
|
3831
|
+
const { sessionLastSeenAt, sessionStartedAt } = acc;
|
|
3832
|
+
const sessionIds = /* @__PURE__ */ new Set([options.sessionIdentity ?? path]);
|
|
2796
3833
|
return Array.from(acc.buckets.values(), (bucket) => ({
|
|
2797
3834
|
harness: "codex",
|
|
2798
3835
|
model: bucket.model,
|
|
@@ -2806,13 +3843,37 @@ function bucketsFromAccumulator(path, acc) {
|
|
|
2806
3843
|
sessionLastSeenAt
|
|
2807
3844
|
}));
|
|
2808
3845
|
}
|
|
2809
|
-
function
|
|
3846
|
+
function candidatesFromAccumulator(path, options, acc) {
|
|
3847
|
+
if (acc.excluded || acc.sessionStartedAt === void 0 || acc.sessionLastSeenAt === void 0) {
|
|
3848
|
+
return [];
|
|
3849
|
+
}
|
|
3850
|
+
const sessionIdentity = options.sessionIdentity ?? path;
|
|
3851
|
+
const canonicalSessionIdentity = options.canonicalSessionIdentity ?? sessionIdentity;
|
|
3852
|
+
return acc.candidates.map((candidate) => ({
|
|
3853
|
+
cacheReadTokens: candidate.usage.cacheReadTokens,
|
|
3854
|
+
canonicalSessionIdentity,
|
|
3855
|
+
eventIndex: candidate.eventIndex,
|
|
3856
|
+
hourUtc: startOfUtcHour(candidate.eventAt),
|
|
3857
|
+
inputTokens: candidate.usage.inputTokens,
|
|
3858
|
+
model: candidate.model,
|
|
3859
|
+
outputTokens: candidate.usage.outputTokens,
|
|
3860
|
+
sessionIdentity,
|
|
3861
|
+
sessionLastSeenAt: acc.sessionLastSeenAt,
|
|
3862
|
+
sessionStartedAt: acc.sessionStartedAt,
|
|
3863
|
+
turnId: candidate.turnId
|
|
3864
|
+
}));
|
|
3865
|
+
}
|
|
3866
|
+
function codexFileUsageEffect(path, logger, options = {}) {
|
|
2810
3867
|
const context = { logger, options, path };
|
|
2811
|
-
return
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
)
|
|
3868
|
+
return lineStreamFromFile(path).pipe(
|
|
3869
|
+
Stream9.runFold(
|
|
3870
|
+
() => makeCodexFileAccumulator(options),
|
|
3871
|
+
(acc, raw) => accumulateCodexLine(context, acc, raw)
|
|
3872
|
+
),
|
|
3873
|
+
Effect16.map((acc) => ({
|
|
3874
|
+
buckets: bucketsFromAccumulator(path, options, acc),
|
|
3875
|
+
candidates: candidatesFromAccumulator(path, options, acc)
|
|
3876
|
+
}))
|
|
2816
3877
|
);
|
|
2817
3878
|
}
|
|
2818
3879
|
|
|
@@ -2823,6 +3884,12 @@ function isObject2(value) {
|
|
|
2823
3884
|
function isFiniteNumber2(value) {
|
|
2824
3885
|
return typeof value === "number" && Number.isFinite(value);
|
|
2825
3886
|
}
|
|
3887
|
+
var MAX_CLAUDE_SOURCE_ID_LENGTH = 512;
|
|
3888
|
+
var MAX_CLAUDE_TIMESTAMP_LENGTH = 128;
|
|
3889
|
+
var MAX_CLAUDE_LOCAL_PATH_LENGTH = 4096;
|
|
3890
|
+
function isOptionalBoundedString2(value, maxLength) {
|
|
3891
|
+
return value === void 0 || typeof value === "string" && value.length <= maxLength;
|
|
3892
|
+
}
|
|
2826
3893
|
function isClaudeUsage(value) {
|
|
2827
3894
|
if (!isObject2(value)) {
|
|
2828
3895
|
return false;
|
|
@@ -2843,20 +3910,23 @@ function isClaudeAssistantEvent(value) {
|
|
|
2843
3910
|
if (value["type"] !== "assistant") {
|
|
2844
3911
|
return false;
|
|
2845
3912
|
}
|
|
2846
|
-
if (typeof value["timestamp"] !== "string") {
|
|
3913
|
+
if (typeof value["timestamp"] !== "string" || value["timestamp"].length > MAX_CLAUDE_TIMESTAMP_LENGTH) {
|
|
2847
3914
|
return false;
|
|
2848
3915
|
}
|
|
2849
|
-
if (value["cwd"]
|
|
3916
|
+
if (!isOptionalBoundedString2(value["cwd"], MAX_CLAUDE_LOCAL_PATH_LENGTH)) {
|
|
3917
|
+
return false;
|
|
3918
|
+
}
|
|
3919
|
+
if (!isOptionalBoundedString2(value["requestId"], MAX_CLAUDE_SOURCE_ID_LENGTH)) {
|
|
2850
3920
|
return false;
|
|
2851
3921
|
}
|
|
2852
|
-
if (value["
|
|
3922
|
+
if (value["isSidechain"] !== void 0 && typeof value["isSidechain"] !== "boolean") {
|
|
2853
3923
|
return false;
|
|
2854
3924
|
}
|
|
2855
3925
|
const message = value["message"];
|
|
2856
3926
|
if (!isObject2(message)) {
|
|
2857
3927
|
return false;
|
|
2858
3928
|
}
|
|
2859
|
-
if (message["id"]
|
|
3929
|
+
if (!isOptionalBoundedString2(message["id"], MAX_CLAUDE_SOURCE_ID_LENGTH)) {
|
|
2860
3930
|
return false;
|
|
2861
3931
|
}
|
|
2862
3932
|
if (typeof message["model"] !== "string") {
|
|
@@ -2872,26 +3942,22 @@ function isClaudeAssistantEvent(value) {
|
|
|
2872
3942
|
function key3(hourUtc, model) {
|
|
2873
3943
|
return `${hourUtc.toISOString()}|${model}`;
|
|
2874
3944
|
}
|
|
2875
|
-
function shouldExcludePath2(path, excludePath) {
|
|
2876
|
-
return path !== void 0 && excludePath?.(path) === true;
|
|
2877
|
-
}
|
|
2878
|
-
function
|
|
2879
|
-
return {
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
sessionLastSeenAt: void 0,
|
|
2885
|
-
sessionStartedAt: void 0
|
|
2886
|
-
};
|
|
2887
|
-
}
|
|
3945
|
+
function shouldExcludePath2(path, excludePath) {
|
|
3946
|
+
return path !== void 0 && excludePath?.(path) === true;
|
|
3947
|
+
}
|
|
3948
|
+
function makeClaudeFileCandidateAccumulator() {
|
|
3949
|
+
return { excluded: false, identified: /* @__PURE__ */ new Map(), lineNo: 0, unidentified: [] };
|
|
3950
|
+
}
|
|
3951
|
+
function makeClaudeSessionAccumulator() {
|
|
3952
|
+
return { buckets: /* @__PURE__ */ new Map(), sessionLastSeenAt: void 0, sessionStartedAt: void 0 };
|
|
3953
|
+
}
|
|
2888
3954
|
function lineStreamFromFile2(path) {
|
|
2889
|
-
return
|
|
2890
|
-
(queue) =>
|
|
2891
|
-
|
|
3955
|
+
return Stream10.callback(
|
|
3956
|
+
(queue) => Effect17.acquireRelease(
|
|
3957
|
+
Effect17.sync(() => {
|
|
2892
3958
|
let done = false;
|
|
2893
3959
|
const stream = createReadStream2(path, { encoding: "utf8" });
|
|
2894
|
-
const
|
|
3960
|
+
const lines = createInterface3({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
2895
3961
|
const offer = (line) => {
|
|
2896
3962
|
if (!done) {
|
|
2897
3963
|
Queue2.offerUnsafe(queue, line);
|
|
@@ -2910,61 +3976,32 @@ function lineStreamFromFile2(path) {
|
|
|
2910
3976
|
}
|
|
2911
3977
|
};
|
|
2912
3978
|
stream.once("error", fail);
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
3979
|
+
lines.once("error", fail);
|
|
3980
|
+
lines.once("close", end);
|
|
3981
|
+
lines.on("line", offer);
|
|
2916
3982
|
return {
|
|
2917
3983
|
close: () => {
|
|
2918
3984
|
done = true;
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
3985
|
+
lines.off("close", end);
|
|
3986
|
+
lines.off("error", fail);
|
|
3987
|
+
lines.off("line", offer);
|
|
2922
3988
|
stream.off("error", fail);
|
|
2923
|
-
|
|
3989
|
+
lines.close();
|
|
2924
3990
|
stream.destroy();
|
|
2925
3991
|
}
|
|
2926
3992
|
};
|
|
2927
3993
|
}),
|
|
2928
|
-
(resource) =>
|
|
3994
|
+
(resource) => Effect17.sync(() => resource.close())
|
|
2929
3995
|
)
|
|
2930
3996
|
);
|
|
2931
3997
|
}
|
|
2932
|
-
function
|
|
2933
|
-
|
|
2934
|
-
const outputTokens = Math.max(0, usage.output_tokens ?? 0);
|
|
2935
|
-
const cacheReadTokens = Math.max(0, usage.cache_read_input_tokens ?? 0);
|
|
2936
|
-
const cacheCreateTokens = Math.max(0, usage.cache_creation_input_tokens ?? 0);
|
|
2937
|
-
if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheCreateTokens === 0) {
|
|
3998
|
+
function eventIsEligible(timestampMs3, options) {
|
|
3999
|
+
if (options.cutoff !== void 0 && timestampMs3 < options.cutoff.getTime()) {
|
|
2938
4000
|
return false;
|
|
2939
4001
|
}
|
|
2940
|
-
|
|
2941
|
-
const existing = acc.buckets.get(k);
|
|
2942
|
-
if (existing === void 0) {
|
|
2943
|
-
acc.buckets.set(k, {
|
|
2944
|
-
hourUtc,
|
|
2945
|
-
model,
|
|
2946
|
-
inputTokens,
|
|
2947
|
-
outputTokens,
|
|
2948
|
-
cacheReadTokens,
|
|
2949
|
-
cacheCreateTokens
|
|
2950
|
-
});
|
|
2951
|
-
return true;
|
|
2952
|
-
}
|
|
2953
|
-
existing.inputTokens += inputTokens;
|
|
2954
|
-
existing.outputTokens += outputTokens;
|
|
2955
|
-
existing.cacheReadTokens += cacheReadTokens;
|
|
2956
|
-
existing.cacheCreateTokens += cacheCreateTokens;
|
|
2957
|
-
return true;
|
|
2958
|
-
}
|
|
2959
|
-
function markSessionSeen2(acc, eventAt) {
|
|
2960
|
-
if (acc.sessionStartedAt === void 0 || eventAt.getTime() < acc.sessionStartedAt.getTime()) {
|
|
2961
|
-
acc.sessionStartedAt = eventAt;
|
|
2962
|
-
}
|
|
2963
|
-
if (acc.sessionLastSeenAt === void 0 || eventAt.getTime() > acc.sessionLastSeenAt.getTime()) {
|
|
2964
|
-
acc.sessionLastSeenAt = eventAt;
|
|
2965
|
-
}
|
|
4002
|
+
return options.endExclusive === void 0 || timestampMs3 < options.endExclusive.getTime();
|
|
2966
4003
|
}
|
|
2967
|
-
function
|
|
4004
|
+
function accumulateClaudeCandidate(context, acc, raw) {
|
|
2968
4005
|
const { logger, options, path } = context;
|
|
2969
4006
|
acc.lineNo += 1;
|
|
2970
4007
|
if (acc.excluded) {
|
|
@@ -2986,37 +4023,129 @@ function accumulateClaudeLine(context, acc, raw) {
|
|
|
2986
4023
|
}
|
|
2987
4024
|
if (shouldExcludePath2(parsed.cwd, options.excludePath)) {
|
|
2988
4025
|
acc.excluded = true;
|
|
2989
|
-
acc.
|
|
4026
|
+
acc.identified.clear();
|
|
4027
|
+
acc.unidentified = [];
|
|
2990
4028
|
return acc;
|
|
2991
4029
|
}
|
|
2992
|
-
const
|
|
2993
|
-
if (!Number.isFinite(
|
|
4030
|
+
const timestampMs3 = Date.parse(parsed.timestamp);
|
|
4031
|
+
if (!(Number.isFinite(timestampMs3) && eventIsEligible(timestampMs3, options))) {
|
|
2994
4032
|
return acc;
|
|
2995
4033
|
}
|
|
2996
|
-
|
|
2997
|
-
|
|
4034
|
+
const tupleKey = parsed.message.id === void 0 || parsed.requestId === void 0 ? void 0 : JSON.stringify([parsed.message.id, parsed.requestId]);
|
|
4035
|
+
const candidate = {
|
|
4036
|
+
eventAt: new Date(timestampMs3),
|
|
4037
|
+
eventOrder: acc.lineNo,
|
|
4038
|
+
isSidechain: parsed.isSidechain === true,
|
|
4039
|
+
model: parsed.message.model,
|
|
4040
|
+
pathRole: options.pathRole ?? "parent",
|
|
4041
|
+
sessionId: options.sessionId ?? path,
|
|
4042
|
+
sourceOrder: normalize3(path),
|
|
4043
|
+
tupleKey,
|
|
4044
|
+
usage: parsed.message.usage
|
|
4045
|
+
};
|
|
4046
|
+
if (tupleKey === void 0) {
|
|
4047
|
+
acc.unidentified.push(candidate);
|
|
4048
|
+
} else {
|
|
4049
|
+
acc.identified.set(tupleKey, candidate);
|
|
2998
4050
|
}
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
4051
|
+
return acc;
|
|
4052
|
+
}
|
|
4053
|
+
function candidatesFromAccumulator2(acc) {
|
|
4054
|
+
return acc.excluded ? [] : [...acc.identified.values(), ...acc.unidentified];
|
|
4055
|
+
}
|
|
4056
|
+
function collectClaudeFileCandidates(path, logger, options = {}) {
|
|
4057
|
+
const context = { logger, options, path };
|
|
4058
|
+
return lineStreamFromFile2(path).pipe(
|
|
4059
|
+
Stream10.runFold(makeClaudeFileCandidateAccumulator, (acc, raw) => accumulateClaudeCandidate(context, acc, raw)),
|
|
4060
|
+
Effect17.map(candidatesFromAccumulator2)
|
|
4061
|
+
);
|
|
4062
|
+
}
|
|
4063
|
+
function normalizedUsage(usage) {
|
|
4064
|
+
return {
|
|
4065
|
+
inputTokens: Math.max(0, usage.input_tokens ?? 0),
|
|
4066
|
+
outputTokens: Math.max(0, usage.output_tokens ?? 0),
|
|
4067
|
+
cacheReadTokens: Math.max(0, usage.cache_read_input_tokens ?? 0),
|
|
4068
|
+
cacheCreateTokens: Math.max(0, usage.cache_creation_input_tokens ?? 0)
|
|
4069
|
+
};
|
|
4070
|
+
}
|
|
4071
|
+
function sameUsage(left, right) {
|
|
4072
|
+
const a = normalizedUsage(left);
|
|
4073
|
+
const b = normalizedUsage(right);
|
|
4074
|
+
return a.inputTokens === b.inputTokens && a.outputTokens === b.outputTokens && a.cacheReadTokens === b.cacheReadTokens && a.cacheCreateTokens === b.cacheCreateTokens;
|
|
4075
|
+
}
|
|
4076
|
+
function candidateWins(next, current) {
|
|
4077
|
+
if (next.model === current.model && sameUsage(next.usage, current.usage)) {
|
|
4078
|
+
if (next.isSidechain !== current.isSidechain) {
|
|
4079
|
+
return !next.isSidechain;
|
|
4080
|
+
}
|
|
4081
|
+
if (next.pathRole !== current.pathRole) {
|
|
4082
|
+
return next.pathRole === "parent";
|
|
3004
4083
|
}
|
|
3005
|
-
acc.seenTuples.add(tuple);
|
|
3006
4084
|
}
|
|
3007
|
-
const
|
|
3008
|
-
if (
|
|
3009
|
-
|
|
4085
|
+
const timestampOrder = next.eventAt.getTime() - current.eventAt.getTime();
|
|
4086
|
+
if (timestampOrder !== 0) {
|
|
4087
|
+
return timestampOrder > 0;
|
|
3010
4088
|
}
|
|
3011
|
-
|
|
4089
|
+
if (next.eventOrder !== current.eventOrder) {
|
|
4090
|
+
return next.eventOrder > current.eventOrder;
|
|
4091
|
+
}
|
|
4092
|
+
return next.sourceOrder.localeCompare(current.sourceOrder) < 0;
|
|
3012
4093
|
}
|
|
3013
|
-
function
|
|
3014
|
-
|
|
4094
|
+
function reconcileClaudeCandidates(candidates) {
|
|
4095
|
+
const identified = /* @__PURE__ */ new Map();
|
|
4096
|
+
const unidentified = [];
|
|
4097
|
+
for (const candidate of candidates) {
|
|
4098
|
+
if (candidate.tupleKey === void 0) {
|
|
4099
|
+
unidentified.push(candidate);
|
|
4100
|
+
continue;
|
|
4101
|
+
}
|
|
4102
|
+
const current = identified.get(candidate.tupleKey);
|
|
4103
|
+
if (current === void 0 || candidateWins(candidate, current)) {
|
|
4104
|
+
identified.set(candidate.tupleKey, candidate);
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
return [...identified.values(), ...unidentified];
|
|
4108
|
+
}
|
|
4109
|
+
function addToBucket2(acc, candidate) {
|
|
4110
|
+
const usage = normalizedUsage(candidate.usage);
|
|
4111
|
+
const { cacheCreateTokens, cacheReadTokens, inputTokens, outputTokens } = usage;
|
|
4112
|
+
if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheCreateTokens === 0) {
|
|
4113
|
+
return false;
|
|
4114
|
+
}
|
|
4115
|
+
const hourUtc = startOfUtcHour(candidate.eventAt);
|
|
4116
|
+
const bucketKey = key3(hourUtc, candidate.model);
|
|
4117
|
+
const existing = acc.buckets.get(bucketKey);
|
|
4118
|
+
if (existing === void 0) {
|
|
4119
|
+
acc.buckets.set(bucketKey, {
|
|
4120
|
+
hourUtc,
|
|
4121
|
+
model: candidate.model,
|
|
4122
|
+
inputTokens,
|
|
4123
|
+
outputTokens,
|
|
4124
|
+
cacheReadTokens,
|
|
4125
|
+
cacheCreateTokens
|
|
4126
|
+
});
|
|
4127
|
+
return true;
|
|
4128
|
+
}
|
|
4129
|
+
existing.inputTokens += inputTokens;
|
|
4130
|
+
existing.outputTokens += outputTokens;
|
|
4131
|
+
existing.cacheReadTokens += cacheReadTokens;
|
|
4132
|
+
existing.cacheCreateTokens += cacheCreateTokens;
|
|
4133
|
+
return true;
|
|
4134
|
+
}
|
|
4135
|
+
function markSessionSeen2(acc, eventAt) {
|
|
4136
|
+
if (acc.sessionStartedAt === void 0 || eventAt.getTime() < acc.sessionStartedAt.getTime()) {
|
|
4137
|
+
acc.sessionStartedAt = eventAt;
|
|
4138
|
+
}
|
|
4139
|
+
if (acc.sessionLastSeenAt === void 0 || eventAt.getTime() > acc.sessionLastSeenAt.getTime()) {
|
|
4140
|
+
acc.sessionLastSeenAt = eventAt;
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
function bucketsForSession(sessionId, acc) {
|
|
4144
|
+
const { sessionLastSeenAt, sessionStartedAt } = acc;
|
|
4145
|
+
if (sessionStartedAt === void 0 || sessionLastSeenAt === void 0) {
|
|
3015
4146
|
return [];
|
|
3016
4147
|
}
|
|
3017
|
-
const
|
|
3018
|
-
const sessionLastSeenAt = acc.sessionLastSeenAt;
|
|
3019
|
-
const sessionIds = /* @__PURE__ */ new Set([options.sessionId ?? path]);
|
|
4148
|
+
const sessionIds = /* @__PURE__ */ new Set([sessionId]);
|
|
3020
4149
|
return Array.from(acc.buckets.values(), (bucket) => ({
|
|
3021
4150
|
harness: "claude_code",
|
|
3022
4151
|
model: bucket.model,
|
|
@@ -3030,70 +4159,80 @@ function bucketsFromAccumulator2(path, options, acc) {
|
|
|
3030
4159
|
sessionLastSeenAt
|
|
3031
4160
|
}));
|
|
3032
4161
|
}
|
|
4162
|
+
function bucketizeClaudeCandidates(candidates) {
|
|
4163
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
4164
|
+
for (const candidate of reconcileClaudeCandidates(candidates)) {
|
|
4165
|
+
const acc = sessions.get(candidate.sessionId) ?? makeClaudeSessionAccumulator();
|
|
4166
|
+
if (addToBucket2(acc, candidate)) {
|
|
4167
|
+
markSessionSeen2(acc, candidate.eventAt);
|
|
4168
|
+
}
|
|
4169
|
+
sessions.set(candidate.sessionId, acc);
|
|
4170
|
+
}
|
|
4171
|
+
return [...sessions.entries()].flatMap(([sessionId, acc]) => bucketsForSession(sessionId, acc));
|
|
4172
|
+
}
|
|
3033
4173
|
function bucketizeClaudeFileStream(path, logger, options = {}) {
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
Stream9.runFold(
|
|
3038
|
-
() => makeClaudeFileAccumulator(options),
|
|
3039
|
-
(acc, raw) => accumulateClaudeLine(context, acc, raw)
|
|
3040
|
-
),
|
|
3041
|
-
Effect16.map((acc) => Stream9.fromIterable(bucketsFromAccumulator2(path, options, acc)))
|
|
4174
|
+
return Stream10.unwrap(
|
|
4175
|
+
collectClaudeFileCandidates(path, logger, options).pipe(
|
|
4176
|
+
Effect17.map((candidates) => Stream10.fromIterable(bucketizeClaudeCandidates(candidates)))
|
|
3042
4177
|
)
|
|
3043
4178
|
);
|
|
3044
4179
|
}
|
|
3045
4180
|
|
|
3046
4181
|
// ../../packages/parsers/src/claude/index.ts
|
|
3047
|
-
function
|
|
4182
|
+
function sourceContext(path) {
|
|
3048
4183
|
const fileDir = dirname2(path);
|
|
3049
4184
|
if (basename(fileDir) !== "subagents") {
|
|
3050
|
-
return path;
|
|
4185
|
+
return { pathRole: "parent", sessionId: path };
|
|
3051
4186
|
}
|
|
3052
4187
|
const sessionDir = dirname2(fileDir);
|
|
3053
|
-
return `${sessionDir}.jsonl
|
|
4188
|
+
return { pathRole: "subagent", sessionId: `${sessionDir}.jsonl` };
|
|
3054
4189
|
}
|
|
3055
4190
|
function bucketizeClaudeSourcesStream(files, logger, options) {
|
|
3056
|
-
return
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
4191
|
+
return Stream11.unwrap(
|
|
4192
|
+
files.pipe(
|
|
4193
|
+
Stream11.runCollect,
|
|
4194
|
+
Effect18.map((paths) => [...paths].sort()),
|
|
4195
|
+
Effect18.flatMap(
|
|
4196
|
+
(paths) => Effect18.forEach(paths, (path) => {
|
|
4197
|
+
const source = sourceContext(path);
|
|
4198
|
+
return collectClaudeFileCandidates(path, logger, {
|
|
4199
|
+
...options,
|
|
4200
|
+
pathRole: source.pathRole,
|
|
4201
|
+
sessionId: source.sessionId
|
|
3067
4202
|
});
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
)
|
|
3071
|
-
)
|
|
3072
|
-
|
|
4203
|
+
})
|
|
4204
|
+
),
|
|
4205
|
+
Effect18.map((filesCandidates) => Stream11.fromIterable(bucketizeClaudeCandidates(filesCandidates.flat())))
|
|
4206
|
+
)
|
|
4207
|
+
);
|
|
3073
4208
|
}
|
|
3074
|
-
var parseClaudeEffect =
|
|
3075
|
-
(opts) =>
|
|
4209
|
+
var parseClaudeEffect = Effect18.fn("ClaudeParser.parseClaudeEffect")(
|
|
4210
|
+
(opts) => Stream11.runCollect(parseClaudeStream(opts))
|
|
3076
4211
|
);
|
|
3077
|
-
var parseClaudeStream = (opts) =>
|
|
3078
|
-
|
|
4212
|
+
var parseClaudeStream = (opts) => Stream11.unwrap(
|
|
4213
|
+
Effect18.gen(function* () {
|
|
3079
4214
|
const logger = opts.logger ?? silentLogger;
|
|
3080
4215
|
const root = opts.root ?? resolveClaudeRoot();
|
|
3081
4216
|
if ((yield* pathKindEffectStrict(root)) === "file") {
|
|
4217
|
+
const source = sourceContext(root);
|
|
3082
4218
|
return bucketizeClaudeFileStream(root, logger, {
|
|
3083
4219
|
cutoff: opts.cutoff,
|
|
4220
|
+
endExclusive: opts.endExclusive,
|
|
3084
4221
|
excludePath: opts.excludePath,
|
|
3085
|
-
|
|
4222
|
+
pathRole: source.pathRole,
|
|
4223
|
+
sessionId: source.sessionId
|
|
3086
4224
|
});
|
|
3087
4225
|
}
|
|
3088
4226
|
return bucketizeClaudeSourcesStream(walkStreamStrict(root, opts.cutoff), logger, {
|
|
3089
4227
|
cutoff: opts.cutoff,
|
|
4228
|
+
endExclusive: opts.endExclusive,
|
|
3090
4229
|
excludePath: opts.excludePath
|
|
3091
4230
|
});
|
|
3092
4231
|
})
|
|
3093
4232
|
);
|
|
3094
4233
|
|
|
3095
4234
|
// ../../packages/parsers/src/codex/index.ts
|
|
3096
|
-
import { Effect as
|
|
4235
|
+
import { Effect as Effect19, Stream as Stream12 } from "effect";
|
|
3097
4236
|
var codexRoots = (opts, root) => {
|
|
3098
4237
|
const roots = [root];
|
|
3099
4238
|
if (opts.root === void 0 && opts.archivedRoot === void 0) {
|
|
@@ -3103,52 +4242,134 @@ var codexRoots = (opts, root) => {
|
|
|
3103
4242
|
}
|
|
3104
4243
|
return roots;
|
|
3105
4244
|
};
|
|
3106
|
-
|
|
3107
|
-
|
|
4245
|
+
function candidateIdentity(candidate) {
|
|
4246
|
+
return [
|
|
4247
|
+
candidate.canonicalSessionIdentity,
|
|
4248
|
+
candidate.turnId ?? "",
|
|
4249
|
+
String(candidate.eventIndex),
|
|
4250
|
+
candidate.hourUtc.toISOString().slice(0, 10),
|
|
4251
|
+
candidate.model,
|
|
4252
|
+
String(candidate.inputTokens),
|
|
4253
|
+
String(candidate.cacheReadTokens),
|
|
4254
|
+
String(candidate.outputTokens)
|
|
4255
|
+
].join("");
|
|
4256
|
+
}
|
|
4257
|
+
function reconcileCodexCandidates(candidates) {
|
|
4258
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4259
|
+
const winners = [];
|
|
4260
|
+
for (const candidate of candidates) {
|
|
4261
|
+
const identity = candidateIdentity(candidate);
|
|
4262
|
+
if (seen.has(identity)) {
|
|
4263
|
+
continue;
|
|
4264
|
+
}
|
|
4265
|
+
seen.add(identity);
|
|
4266
|
+
winners.push(candidate);
|
|
4267
|
+
}
|
|
4268
|
+
return winners;
|
|
4269
|
+
}
|
|
4270
|
+
function bucketsFromCodexCandidates(candidates) {
|
|
4271
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
4272
|
+
for (const candidate of candidates) {
|
|
4273
|
+
const identity = `${candidate.sessionIdentity}${candidate.hourUtc.toISOString()}${candidate.model}`;
|
|
4274
|
+
const existing = buckets.get(identity);
|
|
4275
|
+
if (existing === void 0) {
|
|
4276
|
+
buckets.set(identity, {
|
|
4277
|
+
cacheReadTokens: candidate.cacheReadTokens,
|
|
4278
|
+
hourUtc: candidate.hourUtc,
|
|
4279
|
+
inputTokens: candidate.inputTokens,
|
|
4280
|
+
model: candidate.model,
|
|
4281
|
+
outputTokens: candidate.outputTokens,
|
|
4282
|
+
sessionIdentity: candidate.sessionIdentity,
|
|
4283
|
+
sessionLastSeenAt: candidate.sessionLastSeenAt,
|
|
4284
|
+
sessionStartedAt: candidate.sessionStartedAt
|
|
4285
|
+
});
|
|
4286
|
+
continue;
|
|
4287
|
+
}
|
|
4288
|
+
existing.inputTokens += candidate.inputTokens;
|
|
4289
|
+
existing.outputTokens += candidate.outputTokens;
|
|
4290
|
+
existing.cacheReadTokens += candidate.cacheReadTokens;
|
|
4291
|
+
}
|
|
4292
|
+
return [...buckets.values()].map((bucket) => ({
|
|
4293
|
+
harness: "codex",
|
|
4294
|
+
model: bucket.model,
|
|
4295
|
+
hourUtc: bucket.hourUtc,
|
|
4296
|
+
inputTokens: bucket.inputTokens,
|
|
4297
|
+
outputTokens: bucket.outputTokens,
|
|
4298
|
+
cacheReadTokens: bucket.cacheReadTokens,
|
|
4299
|
+
cacheCreateTokens: 0,
|
|
4300
|
+
sessionIds: /* @__PURE__ */ new Set([bucket.sessionIdentity]),
|
|
4301
|
+
sessionStartedAt: bucket.sessionStartedAt,
|
|
4302
|
+
sessionLastSeenAt: bucket.sessionLastSeenAt
|
|
4303
|
+
}));
|
|
4304
|
+
}
|
|
4305
|
+
var parseCodexEffect = Effect19.fn("CodexParser.parseCodexEffect")(
|
|
4306
|
+
(opts) => Stream12.runCollect(parseCodexStream(opts))
|
|
3108
4307
|
);
|
|
3109
|
-
var parseCodexStream = (opts) =>
|
|
3110
|
-
|
|
4308
|
+
var parseCodexStream = (opts) => Stream12.unwrap(
|
|
4309
|
+
Effect19.gen(function* () {
|
|
3111
4310
|
const logger = opts.logger ?? silentLogger;
|
|
3112
4311
|
const root = opts.root ?? resolveCodexSessionsRoot();
|
|
3113
4312
|
if ((yield* pathKindEffectStrict(root)) === "file") {
|
|
3114
|
-
return
|
|
4313
|
+
return bucketizeIndexedCodexFilesStream(Stream12.succeed(root), opts.cutoff, logger, {
|
|
4314
|
+
excludePath: opts.excludePath
|
|
4315
|
+
});
|
|
3115
4316
|
}
|
|
3116
|
-
return
|
|
3117
|
-
|
|
3118
|
-
|
|
4317
|
+
return bucketizeIndexedCodexFilesStream(
|
|
4318
|
+
walkManyStreamStrict(codexRoots(opts, root), /* @__PURE__ */ new Date(0)),
|
|
4319
|
+
opts.cutoff,
|
|
4320
|
+
logger,
|
|
4321
|
+
{ excludePath: opts.excludePath }
|
|
4322
|
+
);
|
|
3119
4323
|
})
|
|
3120
4324
|
);
|
|
3121
|
-
function
|
|
3122
|
-
return
|
|
4325
|
+
function bucketizeIndexedCodexFilesStream(files, cutoff, logger, options) {
|
|
4326
|
+
return Stream12.unwrap(
|
|
4327
|
+
files.pipe(
|
|
4328
|
+
Stream12.runCollect,
|
|
4329
|
+
Effect19.map((paths) => [...paths]),
|
|
4330
|
+
Effect19.flatMap((paths) => buildCodexSourceIndex(paths, cutoff, logger)),
|
|
4331
|
+
Effect19.flatMap(
|
|
4332
|
+
(index) => Effect19.gen(function* () {
|
|
4333
|
+
const resolver = makeCodexLineageResolver(index, logger);
|
|
4334
|
+
const usages = yield* Effect19.forEach(
|
|
4335
|
+
index.sources.filter((source) => source.eligible),
|
|
4336
|
+
(source) => resolver.baselineFor(source).pipe(
|
|
4337
|
+
Effect19.flatMap(
|
|
4338
|
+
(lineageBaseline) => codexFileUsageEffect(source.path, logger, {
|
|
4339
|
+
...options,
|
|
4340
|
+
canonicalSessionIdentity: source.lineageSessionId ?? source.sourceIdentity,
|
|
4341
|
+
containInterleavedTotals: source.containsMultipleSessionIds,
|
|
4342
|
+
lineageBaseline,
|
|
4343
|
+
sessionIdentity: source.sourceIdentity
|
|
4344
|
+
})
|
|
4345
|
+
)
|
|
4346
|
+
)
|
|
4347
|
+
);
|
|
4348
|
+
const candidates = usages.flatMap((usage) => usage.candidates);
|
|
4349
|
+
return Stream12.fromIterable(bucketsFromCodexCandidates(reconcileCodexCandidates(candidates)));
|
|
4350
|
+
})
|
|
4351
|
+
)
|
|
4352
|
+
)
|
|
4353
|
+
);
|
|
3123
4354
|
}
|
|
3124
4355
|
|
|
3125
4356
|
// ../../packages/parsers/src/effect/hermes-sqlite.ts
|
|
3126
|
-
import { Context as Context9, Effect as
|
|
4357
|
+
import { Context as Context9, Effect as Effect20, Layer as Layer8, Schema as Schema22 } from "effect";
|
|
3127
4358
|
var NODE_SQLITE_SPECIFIER = ["node", "sqlite"].join(":");
|
|
3128
4359
|
var MILLISECONDS_PER_SECOND = 1e3;
|
|
3129
|
-
var HERMES_SESSION_QUERY = `
|
|
3130
|
-
select
|
|
3131
|
-
id,
|
|
3132
|
-
model,
|
|
3133
|
-
billing_provider as "billingProvider",
|
|
3134
|
-
started_at as "startedAt",
|
|
3135
|
-
ended_at as "endedAt",
|
|
3136
|
-
input_tokens as "inputTokens",
|
|
3137
|
-
output_tokens as "outputTokens",
|
|
3138
|
-
cache_read_tokens as "cacheReadTokens",
|
|
3139
|
-
cache_write_tokens as "cacheWriteTokens",
|
|
3140
|
-
cwd
|
|
3141
|
-
from sessions
|
|
3142
|
-
where coalesce(ended_at, started_at) >= ?
|
|
3143
|
-
order by started_at asc, id asc
|
|
3144
|
-
`;
|
|
3145
4360
|
var HermesSqliteError = class extends Schema22.TaggedErrorClass()("HermesSqliteError", {
|
|
3146
4361
|
message: Schema22.String,
|
|
3147
|
-
operation: Schema22.Literals(["load", "open", "query", "
|
|
4362
|
+
operation: Schema22.Literals(["close", "load", "open", "query", "schema"]),
|
|
3148
4363
|
path: Schema22.optionalKey(Schema22.String),
|
|
3149
4364
|
cause: Schema22.optionalKey(Schema22.Defect())
|
|
3150
4365
|
}) {
|
|
3151
4366
|
};
|
|
4367
|
+
function unsupportedSchema(message) {
|
|
4368
|
+
return { _tag: "UnsupportedHermesSchema", message };
|
|
4369
|
+
}
|
|
4370
|
+
function isUnsupportedSchema(value) {
|
|
4371
|
+
return typeof value === "object" && value !== null && "_tag" in value && value._tag === "UnsupportedHermesSchema";
|
|
4372
|
+
}
|
|
3152
4373
|
var HermesSqlite = class extends Context9.Service()("@oopst/parsers/HermesSqlite") {
|
|
3153
4374
|
};
|
|
3154
4375
|
var toHermesSqliteError = (operation, path) => (error) => new HermesSqliteError({
|
|
@@ -3157,34 +4378,124 @@ var toHermesSqliteError = (operation, path) => (error) => new HermesSqliteError(
|
|
|
3157
4378
|
path,
|
|
3158
4379
|
cause: error
|
|
3159
4380
|
});
|
|
3160
|
-
var loadDatabaseSync =
|
|
3161
|
-
const sqliteModule = yield*
|
|
4381
|
+
var loadDatabaseSync = Effect20.fn("HermesSqlite.loadDatabaseSync")(function* () {
|
|
4382
|
+
const sqliteModule = yield* Effect20.tryPromise({
|
|
3162
4383
|
try: () => import(NODE_SQLITE_SPECIFIER),
|
|
3163
4384
|
catch: toHermesSqliteError("load")
|
|
3164
4385
|
});
|
|
3165
4386
|
return sqliteModule.DatabaseSync;
|
|
3166
4387
|
});
|
|
3167
|
-
var openDatabase = (path, DatabaseSync) =>
|
|
4388
|
+
var openDatabase = (path, DatabaseSync) => Effect20.try({
|
|
3168
4389
|
try: () => new DatabaseSync(path, { readOnly: true, timeout: 1e3 }),
|
|
3169
4390
|
catch: toHermesSqliteError("open", path)
|
|
3170
4391
|
});
|
|
3171
|
-
var closeDatabase = (path, db) =>
|
|
4392
|
+
var closeDatabase = (path, db) => Effect20.try({
|
|
3172
4393
|
try: () => {
|
|
3173
4394
|
db.close();
|
|
3174
4395
|
},
|
|
3175
4396
|
catch: toHermesSqliteError("close", path)
|
|
3176
4397
|
});
|
|
3177
|
-
|
|
4398
|
+
function tableNames(db) {
|
|
4399
|
+
const rows = db.prepare("select name from sqlite_master where type = 'table'").all();
|
|
4400
|
+
return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : []));
|
|
4401
|
+
}
|
|
4402
|
+
function tableColumns(db, table) {
|
|
4403
|
+
const rows = db.prepare(`pragma table_info(${table})`).all();
|
|
4404
|
+
return new Set(rows.flatMap((row) => typeof row.name === "string" ? [row.name] : []));
|
|
4405
|
+
}
|
|
4406
|
+
function requireColumns(table, columns, required) {
|
|
4407
|
+
const missing = required.filter((column) => !columns.has(column));
|
|
4408
|
+
if (missing.length > 0) {
|
|
4409
|
+
throw unsupportedSchema(`${table} is missing required accounting columns`);
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
function selectedColumn(columns, column, alias, qualifier) {
|
|
4413
|
+
const source = qualifier === void 0 ? column : `${qualifier}.${column}`;
|
|
4414
|
+
return columns.has(column) ? `${source} as "${alias}"` : `null as "${alias}"`;
|
|
4415
|
+
}
|
|
4416
|
+
function sessionRows(db, cutoffSeconds) {
|
|
4417
|
+
const columns = tableColumns(db, "sessions");
|
|
4418
|
+
requireColumns("sessions", columns, ["id", "model", "started_at", "ended_at", "input_tokens", "output_tokens"]);
|
|
4419
|
+
const query = `
|
|
4420
|
+
select
|
|
4421
|
+
id,
|
|
4422
|
+
model,
|
|
4423
|
+
${selectedColumn(columns, "billing_provider", "billingProvider")},
|
|
4424
|
+
started_at as "startedAt",
|
|
4425
|
+
ended_at as "endedAt",
|
|
4426
|
+
input_tokens as "inputTokens",
|
|
4427
|
+
output_tokens as "outputTokens",
|
|
4428
|
+
${selectedColumn(columns, "cache_read_tokens", "cacheReadTokens")},
|
|
4429
|
+
${selectedColumn(columns, "cache_write_tokens", "cacheWriteTokens")},
|
|
4430
|
+
${selectedColumn(columns, "reasoning_tokens", "reasoningTokens")},
|
|
4431
|
+
${selectedColumn(columns, "cwd", "cwd")}
|
|
4432
|
+
from sessions
|
|
4433
|
+
where ended_at is null or ended_at >= ?
|
|
4434
|
+
order by started_at asc, id asc
|
|
4435
|
+
`;
|
|
4436
|
+
return db.prepare(query).all(cutoffSeconds);
|
|
4437
|
+
}
|
|
4438
|
+
function modelUsageRows(db, cutoffSeconds) {
|
|
4439
|
+
const columns = tableColumns(db, "session_model_usage");
|
|
4440
|
+
requireColumns("session_model_usage", columns, ["session_id", "model", "input_tokens", "output_tokens"]);
|
|
4441
|
+
const query = `
|
|
4442
|
+
select
|
|
4443
|
+
usage.session_id as "sessionId",
|
|
4444
|
+
usage.model,
|
|
4445
|
+
${selectedColumn(columns, "billing_provider", "billingProvider", "usage")},
|
|
4446
|
+
${selectedColumn(columns, "first_seen", "firstSeen", "usage")},
|
|
4447
|
+
${selectedColumn(columns, "last_seen", "lastSeen", "usage")},
|
|
4448
|
+
usage.input_tokens as "inputTokens",
|
|
4449
|
+
usage.output_tokens as "outputTokens",
|
|
4450
|
+
${selectedColumn(columns, "cache_read_tokens", "cacheReadTokens", "usage")},
|
|
4451
|
+
${selectedColumn(columns, "cache_write_tokens", "cacheWriteTokens", "usage")},
|
|
4452
|
+
${selectedColumn(columns, "reasoning_tokens", "reasoningTokens", "usage")}
|
|
4453
|
+
from session_model_usage as usage
|
|
4454
|
+
inner join sessions on sessions.id = usage.session_id
|
|
4455
|
+
where sessions.ended_at is null or sessions.ended_at >= ?
|
|
4456
|
+
order by usage.session_id asc, usage.model asc
|
|
4457
|
+
`;
|
|
4458
|
+
return db.prepare(query).all(cutoffSeconds);
|
|
4459
|
+
}
|
|
4460
|
+
function attachModelUsageRows(sessions, usageRows) {
|
|
4461
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
4462
|
+
for (const row of usageRows) {
|
|
4463
|
+
if (typeof row.sessionId !== "string") {
|
|
4464
|
+
continue;
|
|
4465
|
+
}
|
|
4466
|
+
const rows = bySession.get(row.sessionId) ?? [];
|
|
4467
|
+
rows.push(row);
|
|
4468
|
+
bySession.set(row.sessionId, rows);
|
|
4469
|
+
}
|
|
4470
|
+
return sessions.map((row) => ({
|
|
4471
|
+
...row,
|
|
4472
|
+
modelUsageRows: typeof row.id === "string" ? bySession.get(row.id) ?? [] : []
|
|
4473
|
+
}));
|
|
4474
|
+
}
|
|
4475
|
+
var querySessionRows = (path, db, cutoff) => Effect20.try({
|
|
3178
4476
|
try: () => {
|
|
3179
|
-
const
|
|
3180
|
-
|
|
4477
|
+
const tables = tableNames(db);
|
|
4478
|
+
if (!tables.has("sessions")) {
|
|
4479
|
+
throw unsupportedSchema("sessions table is unavailable");
|
|
4480
|
+
}
|
|
4481
|
+
const cutoffSeconds = cutoff.getTime() / MILLISECONDS_PER_SECOND;
|
|
4482
|
+
const sessions = sessionRows(db, cutoffSeconds);
|
|
4483
|
+
if (!tables.has("session_model_usage")) {
|
|
4484
|
+
return sessions;
|
|
4485
|
+
}
|
|
4486
|
+
return attachModelUsageRows(sessions, modelUsageRows(db, cutoffSeconds));
|
|
3181
4487
|
},
|
|
3182
|
-
catch:
|
|
4488
|
+
catch: (error) => isUnsupportedSchema(error) ? new HermesSqliteError({
|
|
4489
|
+
message: "Hermes SQLite schema does not expose supported accounting aggregates",
|
|
4490
|
+
operation: "schema",
|
|
4491
|
+
path,
|
|
4492
|
+
cause: error
|
|
4493
|
+
}) : toHermesSqliteError("query", path)(error)
|
|
3183
4494
|
});
|
|
3184
4495
|
var makeHermesSqlite = () => ({
|
|
3185
|
-
readSessionRows:
|
|
4496
|
+
readSessionRows: Effect20.fn("HermesSqlite.readSessionRows")(function* (path, cutoff) {
|
|
3186
4497
|
const DatabaseSync = yield* loadDatabaseSync();
|
|
3187
|
-
return yield*
|
|
4498
|
+
return yield* Effect20.acquireUseRelease(
|
|
3188
4499
|
openDatabase(path, DatabaseSync),
|
|
3189
4500
|
(db) => querySessionRows(path, db, cutoff),
|
|
3190
4501
|
(db) => closeDatabase(path, db)
|
|
@@ -3193,13 +4504,13 @@ var makeHermesSqlite = () => ({
|
|
|
3193
4504
|
});
|
|
3194
4505
|
var HermesSqliteLive = Layer8.effect(
|
|
3195
4506
|
HermesSqlite,
|
|
3196
|
-
|
|
4507
|
+
Effect20.sync(() => HermesSqlite.of(makeHermesSqlite()))
|
|
3197
4508
|
);
|
|
3198
4509
|
|
|
3199
4510
|
// ../../packages/parsers/src/effect/opencode-io.ts
|
|
3200
4511
|
import { basename as basename2, join as join6 } from "path";
|
|
3201
4512
|
import { NodeFileSystem as NodeFileSystem5 } from "@effect/platform-node";
|
|
3202
|
-
import { Context as Context10, Effect as
|
|
4513
|
+
import { Context as Context10, Effect as Effect21, FileSystem as FileSystem6, Layer as Layer9, Option as Option3, Schema as Schema23, Stream as Stream13 } from "effect";
|
|
3203
4514
|
var NODE_SQLITE_SPECIFIER2 = ["node", "sqlite"].join(":");
|
|
3204
4515
|
var OPENCODE_DB_FILE_PATTERN2 = /^opencode(?:-[^/]+)?\.db$/u;
|
|
3205
4516
|
var SESSION_MESSAGE_QUERY = `
|
|
@@ -3231,13 +4542,13 @@ var toOpenCodeIoError = (operation, path) => (error) => new OpenCodeIoError({
|
|
|
3231
4542
|
path,
|
|
3232
4543
|
cause: error
|
|
3233
4544
|
});
|
|
3234
|
-
var readDir2 = (fs, path) => fs.readDirectory(path).pipe(
|
|
3235
|
-
var readDirBestEffort = (fs, path) => readDir2(fs, path).pipe(
|
|
3236
|
-
var readStat2 = (fs, path) => fs.stat(path).pipe(
|
|
3237
|
-
var readStatBestEffort = (fs, path) => readStat2(fs, path).pipe(
|
|
3238
|
-
var pathKind =
|
|
4545
|
+
var readDir2 = (fs, path) => fs.readDirectory(path).pipe(Effect21.mapError(toOpenCodeIoError("readdir", path)));
|
|
4546
|
+
var readDirBestEffort = (fs, path) => readDir2(fs, path).pipe(Effect21.catch(() => Effect21.succeed([])));
|
|
4547
|
+
var readStat2 = (fs, path) => fs.stat(path).pipe(Effect21.mapError(toOpenCodeIoError("stat", path)));
|
|
4548
|
+
var readStatBestEffort = (fs, path) => readStat2(fs, path).pipe(Effect21.catch(() => Effect21.succeed(void 0)));
|
|
4549
|
+
var pathKind = Effect21.fn("OpenCodeIo.pathKind")(function* (fs, path) {
|
|
3239
4550
|
return yield* readStat2(fs, path).pipe(
|
|
3240
|
-
|
|
4551
|
+
Effect21.map((info) => {
|
|
3241
4552
|
if (info.type === "File") {
|
|
3242
4553
|
return "file";
|
|
3243
4554
|
}
|
|
@@ -3246,10 +4557,10 @@ var pathKind = Effect20.fn("OpenCodeIo.pathKind")(function* (fs, path) {
|
|
|
3246
4557
|
}
|
|
3247
4558
|
return "missing";
|
|
3248
4559
|
}),
|
|
3249
|
-
|
|
4560
|
+
Effect21.catch(() => Effect21.succeed("missing"))
|
|
3250
4561
|
);
|
|
3251
4562
|
});
|
|
3252
|
-
var listOpenCodeDatabases =
|
|
4563
|
+
var listOpenCodeDatabases = Effect21.fn("OpenCodeIo.listOpenCodeDatabases")(function* (fs, root) {
|
|
3253
4564
|
const entries = yield* readDirBestEffort(fs, root);
|
|
3254
4565
|
const databasePaths = [];
|
|
3255
4566
|
for (const entry of entries) {
|
|
@@ -3263,7 +4574,7 @@ var listOpenCodeDatabases = Effect20.fn("OpenCodeIo.listOpenCodeDatabases")(func
|
|
|
3263
4574
|
}
|
|
3264
4575
|
return databasePaths;
|
|
3265
4576
|
});
|
|
3266
|
-
var listProjectLegacyRoots =
|
|
4577
|
+
var listProjectLegacyRoots = Effect21.fn("OpenCodeIo.listProjectLegacyRoots")(function* (fs, root) {
|
|
3267
4578
|
const projectRoot = join6(root, "project");
|
|
3268
4579
|
const entries = yield* readDirBestEffort(fs, projectRoot);
|
|
3269
4580
|
const roots = [];
|
|
@@ -3275,7 +4586,7 @@ var listProjectLegacyRoots = Effect20.fn("OpenCodeIo.listProjectLegacyRoots")(fu
|
|
|
3275
4586
|
}
|
|
3276
4587
|
return roots;
|
|
3277
4588
|
});
|
|
3278
|
-
var legacyRoots =
|
|
4589
|
+
var legacyRoots = Effect21.fn("OpenCodeIo.legacyRoots")(function* (fs, root) {
|
|
3279
4590
|
return [
|
|
3280
4591
|
join6(root, "storage", "message"),
|
|
3281
4592
|
join6(root, "storage", "session", "message"),
|
|
@@ -3286,26 +4597,26 @@ var isRecent = (info, cutoff) => Option3.match(info.mtime, {
|
|
|
3286
4597
|
onNone: () => false,
|
|
3287
4598
|
onSome: (mtime) => mtime.getTime() >= cutoff.getTime()
|
|
3288
4599
|
});
|
|
3289
|
-
var streamJsonFiles = (fs, root, cutoff) =>
|
|
3290
|
-
() =>
|
|
3291
|
-
|
|
4600
|
+
var streamJsonFiles = (fs, root, cutoff) => Stream13.suspend(
|
|
4601
|
+
() => Stream13.fromIterableEffect(readDirBestEffort(fs, root)).pipe(
|
|
4602
|
+
Stream13.flatMap((entry) => {
|
|
3292
4603
|
const full = join6(root, entry);
|
|
3293
|
-
return
|
|
3294
|
-
|
|
4604
|
+
return Stream13.fromEffect(readStatBestEffort(fs, full)).pipe(
|
|
4605
|
+
Stream13.flatMap((info) => {
|
|
3295
4606
|
if (info?.type === "Directory") {
|
|
3296
4607
|
return streamJsonFiles(fs, full, cutoff);
|
|
3297
4608
|
}
|
|
3298
4609
|
if (info?.type === "File" && entry.endsWith(".json") && isRecent(info, cutoff)) {
|
|
3299
|
-
return
|
|
4610
|
+
return Stream13.succeed(full);
|
|
3300
4611
|
}
|
|
3301
|
-
return
|
|
4612
|
+
return Stream13.empty;
|
|
3302
4613
|
})
|
|
3303
4614
|
);
|
|
3304
4615
|
})
|
|
3305
4616
|
)
|
|
3306
4617
|
);
|
|
3307
|
-
var streamManyJsonFiles = (fs, roots, cutoff) =>
|
|
3308
|
-
var discoverSources =
|
|
4618
|
+
var streamManyJsonFiles = (fs, roots, cutoff) => Stream13.fromIterable(roots).pipe(Stream13.flatMap((root) => streamJsonFiles(fs, root, cutoff)));
|
|
4619
|
+
var discoverSources = Effect21.fn("OpenCodeIo.discoverSources")(function* (fs, root, cutoff) {
|
|
3309
4620
|
const kind = yield* pathKind(fs, root);
|
|
3310
4621
|
if (kind === "missing") {
|
|
3311
4622
|
return { _tag: "Missing" };
|
|
@@ -3324,26 +4635,26 @@ var discoverSources = Effect20.fn("OpenCodeIo.discoverSources")(function* (fs, r
|
|
|
3324
4635
|
return { _tag: "LegacyJsonFiles", paths: streamManyJsonFiles(fs, roots, cutoff) };
|
|
3325
4636
|
});
|
|
3326
4637
|
var readJsonFile = (fs, path) => fs.readFileString(path, "utf8").pipe(
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
(text) =>
|
|
4638
|
+
Effect21.mapError(toOpenCodeIoError("readFile", path)),
|
|
4639
|
+
Effect21.flatMap(
|
|
4640
|
+
(text) => Effect21.try({
|
|
3330
4641
|
try: () => JSON.parse(text),
|
|
3331
4642
|
catch: toOpenCodeIoError("parseJson", path)
|
|
3332
4643
|
})
|
|
3333
4644
|
)
|
|
3334
4645
|
);
|
|
3335
|
-
var loadDatabaseSync2 =
|
|
3336
|
-
const sqliteModule = yield*
|
|
4646
|
+
var loadDatabaseSync2 = Effect21.fn("OpenCodeIo.loadDatabaseSync")(function* () {
|
|
4647
|
+
const sqliteModule = yield* Effect21.tryPromise({
|
|
3337
4648
|
try: () => import(NODE_SQLITE_SPECIFIER2),
|
|
3338
4649
|
catch: toOpenCodeIoError("load")
|
|
3339
4650
|
});
|
|
3340
4651
|
return sqliteModule.DatabaseSync;
|
|
3341
4652
|
});
|
|
3342
|
-
var openDatabase2 = (path, DatabaseSync) =>
|
|
4653
|
+
var openDatabase2 = (path, DatabaseSync) => Effect21.try({
|
|
3343
4654
|
try: () => new DatabaseSync(path, { readOnly: true, timeout: 1e3 }),
|
|
3344
4655
|
catch: toOpenCodeIoError("open", path)
|
|
3345
4656
|
});
|
|
3346
|
-
var closeDatabase2 = (path, db) =>
|
|
4657
|
+
var closeDatabase2 = (path, db) => Effect21.try({
|
|
3347
4658
|
try: () => {
|
|
3348
4659
|
db.close();
|
|
3349
4660
|
},
|
|
@@ -3368,7 +4679,7 @@ function readSessionPaths(db) {
|
|
|
3368
4679
|
}
|
|
3369
4680
|
return paths;
|
|
3370
4681
|
}
|
|
3371
|
-
var querySqliteRows = (path, db, cutoff) =>
|
|
4682
|
+
var querySqliteRows = (path, db, cutoff) => Effect21.try({
|
|
3372
4683
|
try: () => {
|
|
3373
4684
|
const statement = db.prepare(SESSION_MESSAGE_QUERY);
|
|
3374
4685
|
return {
|
|
@@ -3378,9 +4689,9 @@ var querySqliteRows = (path, db, cutoff) => Effect20.try({
|
|
|
3378
4689
|
},
|
|
3379
4690
|
catch: toOpenCodeIoError("query", path)
|
|
3380
4691
|
});
|
|
3381
|
-
var readSqlite =
|
|
4692
|
+
var readSqlite = Effect21.fn("OpenCodeIo.readSqlite")(function* (path, cutoff) {
|
|
3382
4693
|
const DatabaseSync = yield* loadDatabaseSync2();
|
|
3383
|
-
return yield*
|
|
4694
|
+
return yield* Effect21.acquireUseRelease(
|
|
3384
4695
|
openDatabase2(path, DatabaseSync),
|
|
3385
4696
|
(db) => querySqliteRows(path, db, cutoff),
|
|
3386
4697
|
(db) => closeDatabase2(path, db)
|
|
@@ -3393,17 +4704,17 @@ var makeOpenCodeIo = (fs) => ({
|
|
|
3393
4704
|
});
|
|
3394
4705
|
var OpenCodeIoLive = Layer9.effect(
|
|
3395
4706
|
OpenCodeIo,
|
|
3396
|
-
|
|
4707
|
+
Effect21.gen(function* () {
|
|
3397
4708
|
const fs = yield* FileSystem6.FileSystem;
|
|
3398
4709
|
return OpenCodeIo.of(makeOpenCodeIo(fs));
|
|
3399
4710
|
})
|
|
3400
4711
|
).pipe(Layer9.provide(NodeFileSystem5.layer));
|
|
3401
4712
|
|
|
3402
4713
|
// ../../packages/parsers/src/opencode/index.ts
|
|
3403
|
-
import { Effect as
|
|
4714
|
+
import { Effect as Effect23, Stream as Stream14 } from "effect";
|
|
3404
4715
|
|
|
3405
4716
|
// ../../packages/parsers/src/opencode/bucketize.ts
|
|
3406
|
-
import { Effect as
|
|
4717
|
+
import { Effect as Effect22 } from "effect";
|
|
3407
4718
|
|
|
3408
4719
|
// ../../packages/parsers/src/opencode/events.ts
|
|
3409
4720
|
function isObject3(value) {
|
|
@@ -3624,7 +4935,7 @@ function* finalizedBuckets(accumulators) {
|
|
|
3624
4935
|
function parseJson(value) {
|
|
3625
4936
|
return typeof value === "string" ? JSON.parse(value) : value;
|
|
3626
4937
|
}
|
|
3627
|
-
var bucketizeOpenCodeDatabaseEffect =
|
|
4938
|
+
var bucketizeOpenCodeDatabaseEffect = Effect22.fn("bucketizeOpenCodeDatabaseEffect")(
|
|
3628
4939
|
function* (path, cutoff, logger, excludePath) {
|
|
3629
4940
|
const io = yield* OpenCodeIo;
|
|
3630
4941
|
const rows = yield* io.readSqlite(path, cutoff);
|
|
@@ -3647,7 +4958,7 @@ var bucketizeOpenCodeDatabaseEffect = Effect21.fn("bucketizeOpenCodeDatabaseEffe
|
|
|
3647
4958
|
return [...finalizedBuckets(accumulators)];
|
|
3648
4959
|
}
|
|
3649
4960
|
);
|
|
3650
|
-
var bucketizeOpenCodeJsonFileEffect =
|
|
4961
|
+
var bucketizeOpenCodeJsonFileEffect = Effect22.fn("bucketizeOpenCodeJsonFileEffect")(
|
|
3651
4962
|
function* (path) {
|
|
3652
4963
|
const io = yield* OpenCodeIo;
|
|
3653
4964
|
const parsed = yield* io.readJsonFile(path);
|
|
@@ -3662,23 +4973,23 @@ var bucketizeOpenCodeJsonFileEffect = Effect21.fn("bucketizeOpenCodeJsonFileEffe
|
|
|
3662
4973
|
);
|
|
3663
4974
|
|
|
3664
4975
|
// ../../packages/parsers/src/opencode/index.ts
|
|
3665
|
-
var discoverOpenCodeSourcesEffect =
|
|
4976
|
+
var discoverOpenCodeSourcesEffect = Effect23.fn("discoverOpenCodeSourcesEffect")(function* (root, cutoff) {
|
|
3666
4977
|
const io = yield* OpenCodeIo;
|
|
3667
4978
|
return yield* io.discoverSources(root, cutoff);
|
|
3668
4979
|
});
|
|
3669
|
-
var parseOpenCodeEffect =
|
|
4980
|
+
var parseOpenCodeEffect = Effect23.fn(
|
|
3670
4981
|
"OpenCodeParser.parseOpenCodeEffect"
|
|
3671
|
-
)((opts) =>
|
|
3672
|
-
var parseOpenCodeStream = (opts) =>
|
|
3673
|
-
|
|
4982
|
+
)((opts) => Stream14.runCollect(parseOpenCodeStream(opts)));
|
|
4983
|
+
var parseOpenCodeStream = (opts) => Stream14.unwrap(
|
|
4984
|
+
Effect23.gen(function* () {
|
|
3674
4985
|
const logger = opts.logger ?? silentLogger;
|
|
3675
4986
|
const root = opts.root ?? resolveOpenCodeRoot();
|
|
3676
4987
|
const sources = yield* discoverOpenCodeSourcesEffect(root, opts.cutoff);
|
|
3677
4988
|
if (sources._tag === "Missing") {
|
|
3678
|
-
return
|
|
4989
|
+
return Stream14.empty;
|
|
3679
4990
|
}
|
|
3680
4991
|
if (sources._tag === "JsonFile") {
|
|
3681
|
-
return openCodeJsonFileStreams(
|
|
4992
|
+
return openCodeJsonFileStreams(Stream14.succeed(sources.path));
|
|
3682
4993
|
}
|
|
3683
4994
|
if (sources._tag === "DatabaseFiles") {
|
|
3684
4995
|
return openCodeDatabaseStreams(sources.paths, opts, logger);
|
|
@@ -3687,28 +4998,28 @@ var parseOpenCodeStream = (opts) => Stream13.unwrap(
|
|
|
3687
4998
|
})
|
|
3688
4999
|
);
|
|
3689
5000
|
function openCodeDatabaseStreams(paths, opts, logger) {
|
|
3690
|
-
let stream =
|
|
5001
|
+
let stream = Stream14.empty;
|
|
3691
5002
|
for (const dbPath of paths) {
|
|
3692
5003
|
stream = stream.pipe(
|
|
3693
|
-
|
|
3694
|
-
|
|
5004
|
+
Stream14.concat(
|
|
5005
|
+
Stream14.fromIterableEffect(bucketizeOpenCodeDatabaseEffect(dbPath, opts.cutoff, logger, opts.excludePath))
|
|
3695
5006
|
)
|
|
3696
5007
|
);
|
|
3697
5008
|
}
|
|
3698
5009
|
return stream;
|
|
3699
5010
|
}
|
|
3700
5011
|
function openCodeJsonFileStreams(paths) {
|
|
3701
|
-
return paths.pipe(
|
|
5012
|
+
return paths.pipe(Stream14.flatMap((file) => Stream14.fromIterableEffect(bucketizeOpenCodeJsonFileEffect(file))));
|
|
3702
5013
|
}
|
|
3703
5014
|
|
|
3704
5015
|
// ../../packages/parsers/src/pi/index.ts
|
|
3705
5016
|
import { basename as basename3, join as join7 } from "path";
|
|
3706
|
-
import { Effect as
|
|
5017
|
+
import { Effect as Effect25, Layer as Layer10, Stream as Stream16 } from "effect";
|
|
3707
5018
|
|
|
3708
5019
|
// ../../packages/parsers/src/pi/bucketize.ts
|
|
3709
5020
|
import { createReadStream as createReadStream3 } from "fs";
|
|
3710
5021
|
import { createInterface as createInterface4 } from "readline";
|
|
3711
|
-
import { Cause as Cause3, Effect as
|
|
5022
|
+
import { Cause as Cause3, Effect as Effect24, Queue as Queue3, Stream as Stream15 } from "effect";
|
|
3712
5023
|
|
|
3713
5024
|
// ../../packages/parsers/src/pi/events.ts
|
|
3714
5025
|
function isObject4(value) {
|
|
@@ -3787,9 +5098,9 @@ function key5(hourUtc, model) {
|
|
|
3787
5098
|
return `${hourUtc.toISOString()}|${model}`;
|
|
3788
5099
|
}
|
|
3789
5100
|
function lineStreamFromFile3(path) {
|
|
3790
|
-
return
|
|
3791
|
-
(queue) =>
|
|
3792
|
-
|
|
5101
|
+
return Stream15.callback(
|
|
5102
|
+
(queue) => Effect24.acquireRelease(
|
|
5103
|
+
Effect24.sync(() => {
|
|
3793
5104
|
let done = false;
|
|
3794
5105
|
const stream = createReadStream3(path, { encoding: "utf8" });
|
|
3795
5106
|
const rl = createInterface4({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
@@ -3826,7 +5137,7 @@ function lineStreamFromFile3(path) {
|
|
|
3826
5137
|
}
|
|
3827
5138
|
};
|
|
3828
5139
|
}),
|
|
3829
|
-
(resource) =>
|
|
5140
|
+
(resource) => Effect24.sync(() => resource.close())
|
|
3830
5141
|
)
|
|
3831
5142
|
);
|
|
3832
5143
|
}
|
|
@@ -3844,6 +5155,33 @@ function modelId2(provider, model) {
|
|
|
3844
5155
|
}
|
|
3845
5156
|
return `${trimmedProvider}/${trimmedModel}`;
|
|
3846
5157
|
}
|
|
5158
|
+
function hermesModelId(provider, model, fallbackProvider) {
|
|
5159
|
+
if (typeof model !== "string") {
|
|
5160
|
+
return void 0;
|
|
5161
|
+
}
|
|
5162
|
+
const trimmedModel = model.trim();
|
|
5163
|
+
if (trimmedModel.length === 0) {
|
|
5164
|
+
return void 0;
|
|
5165
|
+
}
|
|
5166
|
+
const directProvider = typeof provider === "string" ? provider.trim() : "";
|
|
5167
|
+
const fallback = typeof fallbackProvider === "string" ? fallbackProvider.trim() : "";
|
|
5168
|
+
const sourceProvider = directProvider.length > 0 ? directProvider : fallback;
|
|
5169
|
+
if (sourceProvider.length > 0) {
|
|
5170
|
+
const normalizedProvider = normalizeProvider(sourceProvider);
|
|
5171
|
+
if (trimmedModel.startsWith(`${sourceProvider}/`)) {
|
|
5172
|
+
return `${normalizedProvider}/${trimmedModel.slice(sourceProvider.length + 1)}`;
|
|
5173
|
+
}
|
|
5174
|
+
if (trimmedModel.startsWith(`${normalizedProvider}/`)) {
|
|
5175
|
+
return trimmedModel;
|
|
5176
|
+
}
|
|
5177
|
+
return `${normalizedProvider}/${trimmedModel}`;
|
|
5178
|
+
}
|
|
5179
|
+
const separator = trimmedModel.indexOf("/");
|
|
5180
|
+
if (separator <= 0) {
|
|
5181
|
+
return void 0;
|
|
5182
|
+
}
|
|
5183
|
+
return `${normalizeProvider(trimmedModel.slice(0, separator))}/${trimmedModel.slice(separator + 1)}`;
|
|
5184
|
+
}
|
|
3847
5185
|
function normalizeProvider(provider) {
|
|
3848
5186
|
return provider === "openai-codex" ? "openai" : provider;
|
|
3849
5187
|
}
|
|
@@ -3860,53 +5198,134 @@ function timestampMs2(value) {
|
|
|
3860
5198
|
}
|
|
3861
5199
|
return Math.abs(n) < UNIX_MILLISECONDS_THRESHOLD ? n * MILLISECONDS_PER_SECOND2 : n;
|
|
3862
5200
|
}
|
|
3863
|
-
function
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
5201
|
+
function normalizeHermesUsage(row) {
|
|
5202
|
+
return {
|
|
5203
|
+
inputTokens: positive2(row.inputTokens),
|
|
5204
|
+
outputTokens: positive2(row.outputTokens),
|
|
5205
|
+
cacheReadTokens: positive2(row.cacheReadTokens),
|
|
5206
|
+
cacheCreateTokens: positive2(row.cacheWriteTokens)
|
|
5207
|
+
};
|
|
5208
|
+
}
|
|
5209
|
+
function addHermesUsage(left, right) {
|
|
5210
|
+
return {
|
|
5211
|
+
inputTokens: left.inputTokens + right.inputTokens,
|
|
5212
|
+
outputTokens: left.outputTokens + right.outputTokens,
|
|
5213
|
+
cacheReadTokens: left.cacheReadTokens + right.cacheReadTokens,
|
|
5214
|
+
cacheCreateTokens: left.cacheCreateTokens + right.cacheCreateTokens
|
|
5215
|
+
};
|
|
5216
|
+
}
|
|
5217
|
+
function subtractHermesUsageFloorZero(current, baseline) {
|
|
5218
|
+
return {
|
|
5219
|
+
inputTokens: Math.max(0, current.inputTokens - baseline.inputTokens),
|
|
5220
|
+
outputTokens: Math.max(0, current.outputTokens - baseline.outputTokens),
|
|
5221
|
+
cacheReadTokens: Math.max(0, current.cacheReadTokens - baseline.cacheReadTokens),
|
|
5222
|
+
cacheCreateTokens: Math.max(0, current.cacheCreateTokens - baseline.cacheCreateTokens)
|
|
5223
|
+
};
|
|
5224
|
+
}
|
|
5225
|
+
function hasHermesUsage(usage) {
|
|
5226
|
+
return usage.inputTokens > 0 || usage.outputTokens > 0 || usage.cacheReadTokens > 0 || usage.cacheCreateTokens > 0;
|
|
5227
|
+
}
|
|
5228
|
+
function maxModelLastSeen(rows) {
|
|
5229
|
+
let latest;
|
|
5230
|
+
for (const row of rows) {
|
|
5231
|
+
const at = timestampMs2(row.lastSeen) ?? timestampMs2(row.firstSeen);
|
|
5232
|
+
if (at !== void 0 && (latest === void 0 || at > latest)) {
|
|
5233
|
+
latest = at;
|
|
5234
|
+
}
|
|
3878
5235
|
}
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheCreateTokens === 0) {
|
|
5236
|
+
return latest;
|
|
5237
|
+
}
|
|
5238
|
+
function makeHermesBucket(options) {
|
|
5239
|
+
if (!hasHermesUsage(options.usage)) {
|
|
3884
5240
|
return void 0;
|
|
3885
5241
|
}
|
|
3886
|
-
const startedAt = new Date(startedAtMs);
|
|
5242
|
+
const startedAt = new Date(options.startedAtMs);
|
|
3887
5243
|
return {
|
|
3888
5244
|
harness: "hermes",
|
|
3889
|
-
model,
|
|
3890
|
-
hourUtc: startOfUtcHour(
|
|
3891
|
-
inputTokens,
|
|
3892
|
-
outputTokens,
|
|
3893
|
-
cacheReadTokens,
|
|
3894
|
-
cacheCreateTokens,
|
|
3895
|
-
sessionIds: /* @__PURE__ */ new Set([`hermes:${sessionId}`]),
|
|
5245
|
+
model: options.model,
|
|
5246
|
+
hourUtc: startOfUtcHour(new Date(options.attributedAtMs)),
|
|
5247
|
+
inputTokens: options.usage.inputTokens,
|
|
5248
|
+
outputTokens: options.usage.outputTokens,
|
|
5249
|
+
cacheReadTokens: options.usage.cacheReadTokens,
|
|
5250
|
+
cacheCreateTokens: options.usage.cacheCreateTokens,
|
|
5251
|
+
sessionIds: /* @__PURE__ */ new Set([`hermes:${options.sessionId}`]),
|
|
3896
5252
|
sessionStartedAt: startedAt,
|
|
3897
|
-
sessionLastSeenAt: new Date(lastSeenAtMs)
|
|
5253
|
+
sessionLastSeenAt: new Date(options.lastSeenAtMs)
|
|
3898
5254
|
};
|
|
3899
5255
|
}
|
|
3900
|
-
function
|
|
5256
|
+
function bucketizeHermesModelUsage(options) {
|
|
3901
5257
|
const buckets = [];
|
|
3902
|
-
|
|
3903
|
-
|
|
5258
|
+
let attributedUsage = {
|
|
5259
|
+
cacheCreateTokens: 0,
|
|
5260
|
+
cacheReadTokens: 0,
|
|
5261
|
+
inputTokens: 0,
|
|
5262
|
+
outputTokens: 0
|
|
5263
|
+
};
|
|
5264
|
+
for (const usageRow of options.rows) {
|
|
5265
|
+
const model = hermesModelId(usageRow.billingProvider, usageRow.model, options.fallbackProvider);
|
|
5266
|
+
if (model === void 0) {
|
|
5267
|
+
continue;
|
|
5268
|
+
}
|
|
5269
|
+
const usage = normalizeHermesUsage(usageRow);
|
|
5270
|
+
const bucket = makeHermesBucket({
|
|
5271
|
+
attributedAtMs: timestampMs2(usageRow.firstSeen) ?? options.startedAtMs,
|
|
5272
|
+
lastSeenAtMs: options.lastSeenAtMs,
|
|
5273
|
+
model,
|
|
5274
|
+
sessionId: options.sessionId,
|
|
5275
|
+
startedAtMs: options.startedAtMs,
|
|
5276
|
+
usage
|
|
5277
|
+
});
|
|
3904
5278
|
if (bucket !== void 0) {
|
|
3905
5279
|
buckets.push(bucket);
|
|
5280
|
+
attributedUsage = addHermesUsage(attributedUsage, usage);
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
return { attributedUsage, buckets };
|
|
5284
|
+
}
|
|
5285
|
+
function hermesRowToBuckets(row, excludePath) {
|
|
5286
|
+
if (typeof row.cwd === "string" && excludePath?.(row.cwd) === true) {
|
|
5287
|
+
return [];
|
|
5288
|
+
}
|
|
5289
|
+
const sessionId = typeof row.id === "string" ? row.id : void 0;
|
|
5290
|
+
const startedAtMs = timestampMs2(row.startedAt);
|
|
5291
|
+
if (sessionId === void 0 || startedAtMs === void 0) {
|
|
5292
|
+
return [];
|
|
5293
|
+
}
|
|
5294
|
+
const modelUsageRows2 = row.modelUsageRows ?? [];
|
|
5295
|
+
const lastSeenAtMs = timestampMs2(row.endedAt) ?? maxModelLastSeen(modelUsageRows2) ?? startedAtMs;
|
|
5296
|
+
const modelUsage = bucketizeHermesModelUsage({
|
|
5297
|
+
fallbackProvider: row.billingProvider,
|
|
5298
|
+
lastSeenAtMs,
|
|
5299
|
+
rows: modelUsageRows2,
|
|
5300
|
+
sessionId,
|
|
5301
|
+
startedAtMs
|
|
5302
|
+
});
|
|
5303
|
+
const buckets = [...modelUsage.buckets];
|
|
5304
|
+
const aggregateUsage = normalizeHermesUsage(row);
|
|
5305
|
+
const residualUsage = subtractHermesUsageFloorZero(aggregateUsage, modelUsage.attributedUsage);
|
|
5306
|
+
const residualModel = hermesModelId(row.billingProvider, row.model);
|
|
5307
|
+
if (residualModel !== void 0) {
|
|
5308
|
+
const residual = makeHermesBucket({
|
|
5309
|
+
attributedAtMs: startedAtMs,
|
|
5310
|
+
lastSeenAtMs,
|
|
5311
|
+
model: residualModel,
|
|
5312
|
+
sessionId,
|
|
5313
|
+
startedAtMs,
|
|
5314
|
+
usage: residualUsage
|
|
5315
|
+
});
|
|
5316
|
+
if (residual !== void 0) {
|
|
5317
|
+
buckets.push(residual);
|
|
3906
5318
|
}
|
|
3907
5319
|
}
|
|
3908
5320
|
return buckets;
|
|
3909
5321
|
}
|
|
5322
|
+
function bucketizeHermesRows(rows, excludePath) {
|
|
5323
|
+
const buckets = [];
|
|
5324
|
+
for (const row of rows) {
|
|
5325
|
+
buckets.push(...hermesRowToBuckets(row, excludePath));
|
|
5326
|
+
}
|
|
5327
|
+
return buckets;
|
|
5328
|
+
}
|
|
3910
5329
|
function makePiStyleFileAccumulator(options) {
|
|
3911
5330
|
return {
|
|
3912
5331
|
buckets: /* @__PURE__ */ new Map(),
|
|
@@ -3999,12 +5418,11 @@ function accumulatePiStyleLine(context, acc, raw) {
|
|
|
3999
5418
|
existing.cacheCreateTokens += cacheCreateTokens;
|
|
4000
5419
|
return acc;
|
|
4001
5420
|
}
|
|
4002
|
-
function
|
|
5421
|
+
function bucketsFromAccumulator2(path, harness, acc) {
|
|
4003
5422
|
if (acc.excluded || acc.sessionStartedAt === void 0 || acc.sessionLastSeenAt === void 0) {
|
|
4004
5423
|
return [];
|
|
4005
5424
|
}
|
|
4006
|
-
const sessionStartedAt = acc
|
|
4007
|
-
const sessionLastSeenAt = acc.sessionLastSeenAt;
|
|
5425
|
+
const { sessionLastSeenAt, sessionStartedAt } = acc;
|
|
4008
5426
|
const sessionIds = /* @__PURE__ */ new Set([path]);
|
|
4009
5427
|
return Array.from(acc.buckets.values(), (bucket) => ({
|
|
4010
5428
|
harness,
|
|
@@ -4021,17 +5439,17 @@ function bucketsFromAccumulator3(path, harness, acc) {
|
|
|
4021
5439
|
}
|
|
4022
5440
|
function bucketizePiStyleFileStream(path, harness, logger, options = {}) {
|
|
4023
5441
|
const context = { logger, options, path };
|
|
4024
|
-
return
|
|
5442
|
+
return Stream15.unwrap(
|
|
4025
5443
|
lineStreamFromFile3(path).pipe(
|
|
4026
|
-
|
|
5444
|
+
Stream15.runFold(
|
|
4027
5445
|
() => makePiStyleFileAccumulator(options),
|
|
4028
5446
|
(acc, raw) => accumulatePiStyleLine(context, acc, raw)
|
|
4029
5447
|
),
|
|
4030
|
-
|
|
5448
|
+
Effect24.map((acc) => Stream15.fromIterable(bucketsFromAccumulator2(path, harness, acc)))
|
|
4031
5449
|
)
|
|
4032
5450
|
);
|
|
4033
5451
|
}
|
|
4034
|
-
var bucketizeHermesDatabaseEffect =
|
|
5452
|
+
var bucketizeHermesDatabaseEffect = Effect24.fn(
|
|
4035
5453
|
"bucketizeHermesDatabaseEffect"
|
|
4036
5454
|
)(function* (path, cutoff, excludePath) {
|
|
4037
5455
|
const sqlite = yield* HermesSqlite;
|
|
@@ -4040,14 +5458,14 @@ var bucketizeHermesDatabaseEffect = Effect23.fn(
|
|
|
4040
5458
|
});
|
|
4041
5459
|
|
|
4042
5460
|
// ../../packages/parsers/src/pi/index.ts
|
|
4043
|
-
var isFileBestEffortEffect = (path) => pathKindEffect(path).pipe(
|
|
5461
|
+
var isFileBestEffortEffect = (path) => pathKindEffect(path).pipe(Effect25.map((kind) => kind === "file"));
|
|
4044
5462
|
function bucketizePiStyleSourcesStream(files, harness, logger, options) {
|
|
4045
5463
|
return files.pipe(
|
|
4046
|
-
|
|
5464
|
+
Stream16.flatMap((file) => bucketizePiStyleFileStream(file, harness, logger, options), { concurrency: 1 })
|
|
4047
5465
|
);
|
|
4048
5466
|
}
|
|
4049
|
-
var parsePiStyleStream = (opts, harness, defaultRoot) =>
|
|
4050
|
-
|
|
5467
|
+
var parsePiStyleStream = (opts, harness, defaultRoot) => Stream16.unwrap(
|
|
5468
|
+
Effect25.gen(function* () {
|
|
4051
5469
|
const logger = opts.logger ?? silentLogger;
|
|
4052
5470
|
const root = opts.root ?? defaultRoot();
|
|
4053
5471
|
const kind = yield* pathKindEffectStrict(root);
|
|
@@ -4065,25 +5483,23 @@ var parsePiStyleStream = (opts, harness, defaultRoot) => Stream15.unwrap(
|
|
|
4065
5483
|
});
|
|
4066
5484
|
})
|
|
4067
5485
|
);
|
|
4068
|
-
var parsePiEffect =
|
|
5486
|
+
var parsePiEffect = Effect25.fn("PiParser.parsePiEffect")((opts) => Stream16.runCollect(parsePiStream(opts)));
|
|
4069
5487
|
var parsePiStream = (opts) => parsePiStyleStream(opts, "pi", resolvePiRoot);
|
|
4070
5488
|
var parserLive = Layer10.mergeAll(FileWalkerLive, HermesSqliteLive);
|
|
4071
|
-
var parseHermesEffect =
|
|
5489
|
+
var parseHermesEffect = Effect25.fn(
|
|
4072
5490
|
"HermesParser.parseHermesEffect"
|
|
4073
|
-
)((opts) =>
|
|
4074
|
-
var parseHermesStream = (opts) =>
|
|
4075
|
-
|
|
5491
|
+
)((opts) => Stream16.runCollect(parseHermesStream(opts)));
|
|
5492
|
+
var parseHermesStream = (opts) => Stream16.unwrap(
|
|
5493
|
+
Effect25.gen(function* () {
|
|
4076
5494
|
const logger = opts.logger ?? silentLogger;
|
|
4077
5495
|
const root = opts.root ?? resolveHermesRoot();
|
|
4078
5496
|
const kind = yield* pathKindEffectStrict(root);
|
|
4079
5497
|
if (kind === "missing") {
|
|
4080
|
-
return asHermesStream(
|
|
5498
|
+
return asHermesStream(Stream16.empty);
|
|
4081
5499
|
}
|
|
4082
5500
|
if (kind === "file") {
|
|
4083
5501
|
if (basename3(root).endsWith(".db")) {
|
|
4084
|
-
return asHermesStream(
|
|
4085
|
-
Stream15.fromIterableEffect(bucketizeHermesDatabaseEffect(root, opts.cutoff, opts.excludePath))
|
|
4086
|
-
);
|
|
5502
|
+
return asHermesStream(collectHermesDatabaseStream(root, logger, opts));
|
|
4087
5503
|
}
|
|
4088
5504
|
return asHermesStream(
|
|
4089
5505
|
bucketizePiStyleFileStream(root, "hermes", logger, { cutoff: opts.cutoff, excludePath: opts.excludePath })
|
|
@@ -4091,9 +5507,7 @@ var parseHermesStream = (opts) => Stream15.unwrap(
|
|
|
4091
5507
|
}
|
|
4092
5508
|
const stateDb = join7(root, "state.db");
|
|
4093
5509
|
if (yield* isFileBestEffortEffect(stateDb)) {
|
|
4094
|
-
return asHermesStream(
|
|
4095
|
-
Stream15.fromIterableEffect(bucketizeHermesDatabaseEffect(stateDb, opts.cutoff, opts.excludePath))
|
|
4096
|
-
);
|
|
5510
|
+
return asHermesStream(collectHermesDatabaseStream(stateDb, logger, opts));
|
|
4097
5511
|
}
|
|
4098
5512
|
const sessionsRoot2 = join7(root, "sessions");
|
|
4099
5513
|
if ((yield* pathKindEffect(sessionsRoot2)) === "directory") {
|
|
@@ -4109,8 +5523,20 @@ var parseHermesStream = (opts) => Stream15.unwrap(
|
|
|
4109
5523
|
function asHermesStream(stream) {
|
|
4110
5524
|
return stream;
|
|
4111
5525
|
}
|
|
4112
|
-
|
|
4113
|
-
|
|
5526
|
+
function collectHermesDatabaseStream(path, logger, opts) {
|
|
5527
|
+
return Stream16.fromIterableEffect(bucketizeHermesDatabaseEffect(path, opts.cutoff, opts.excludePath)).pipe(
|
|
5528
|
+
Stream16.catchTag("HermesSqliteError", (error) => {
|
|
5529
|
+
if (error.operation !== "schema") {
|
|
5530
|
+
return Stream16.fail(error);
|
|
5531
|
+
}
|
|
5532
|
+
return Stream16.fromEffectDrain(
|
|
5533
|
+
Effect25.sync(() => logger.warn("unsupported hermes sqlite schema", { reason: "unsupported_schema" }))
|
|
5534
|
+
);
|
|
5535
|
+
})
|
|
5536
|
+
);
|
|
5537
|
+
}
|
|
5538
|
+
var collectHermesPiStyleDirectoryStream = (root, logger, opts) => Stream16.unwrap(
|
|
5539
|
+
Effect25.sync(() => {
|
|
4114
5540
|
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
4115
5541
|
return bucketizePiStyleSourcesStream(walkStreamStrict(root, opts.cutoff), "hermes", logger, {
|
|
4116
5542
|
cutoff: opts.cutoff,
|
|
@@ -4121,7 +5547,7 @@ var collectHermesPiStyleDirectoryStream = (root, logger, opts) => Stream15.unwra
|
|
|
4121
5547
|
);
|
|
4122
5548
|
|
|
4123
5549
|
// src/sync/parser-factory.ts
|
|
4124
|
-
import { Effect as
|
|
5550
|
+
import { Effect as Effect26, Layer as Layer11, Stream as Stream17 } from "effect";
|
|
4125
5551
|
var GLOB_PATTERN = /[*?[\]{}]/u;
|
|
4126
5552
|
var HermesParserLive = Layer11.mergeAll(FileWalkerLive, HermesSqliteLive);
|
|
4127
5553
|
function sessionsRoot(agentDir, sessionDir, fallbackAgentDir) {
|
|
@@ -4186,7 +5612,7 @@ function createExcludeMatcher(options) {
|
|
|
4186
5612
|
};
|
|
4187
5613
|
}
|
|
4188
5614
|
function resolveParserSourcePlanEffect(options = {}) {
|
|
4189
|
-
return
|
|
5615
|
+
return Effect26.gen(function* () {
|
|
4190
5616
|
const sources = yield* ParserSources;
|
|
4191
5617
|
const home = yield* sources.homeDir;
|
|
4192
5618
|
const env = options.env ?? (yield* sources.cliEnv);
|
|
@@ -4225,49 +5651,43 @@ function resolveParserSourcePlanEffect(options = {}) {
|
|
|
4225
5651
|
});
|
|
4226
5652
|
}
|
|
4227
5653
|
function provideFileWalker(stream) {
|
|
4228
|
-
return stream.pipe(
|
|
4229
|
-
}
|
|
4230
|
-
function
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
5654
|
+
return stream.pipe(Stream17.provide(FileWalkerLive));
|
|
5655
|
+
}
|
|
5656
|
+
function codexStreamForPlan(plan, cutoff, excludePath) {
|
|
5657
|
+
if (plan.codexSessionsSourceExisted) {
|
|
5658
|
+
return provideFileWalker(
|
|
5659
|
+
parseCodexStream({
|
|
5660
|
+
archivedRoot: plan.codexArchivedSourceExisted ? plan.codexArchivedRoot : void 0,
|
|
5661
|
+
cutoff,
|
|
5662
|
+
excludePath,
|
|
5663
|
+
root: plan.codexSessionsRoot
|
|
5664
|
+
})
|
|
5665
|
+
);
|
|
4234
5666
|
}
|
|
4235
|
-
|
|
5667
|
+
if (plan.codexArchivedSourceExisted) {
|
|
5668
|
+
return provideFileWalker(
|
|
5669
|
+
parseCodexStream({ cutoff, excludePath, root: plan.codexArchivedRoot })
|
|
5670
|
+
);
|
|
5671
|
+
}
|
|
5672
|
+
return Stream17.empty;
|
|
4236
5673
|
}
|
|
4237
5674
|
function buildFactoryFromPlan(plan) {
|
|
4238
5675
|
const excludePath = createExcludeMatcher({ exclude: plan.exclude, home: plan.home });
|
|
4239
|
-
return (ctx) => {
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
}
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
parseCodexStream({ cutoff: ctx.cutoff, excludePath, root: plan.codexArchivedRoot })
|
|
4252
|
-
)
|
|
4253
|
-
);
|
|
4254
|
-
}
|
|
4255
|
-
return {
|
|
4256
|
-
anySourceExisted: plan.anySourceExisted,
|
|
4257
|
-
codex: concatParserStreams(codexStreams),
|
|
4258
|
-
claudeCode: plan.claudeSourceExisted ? provideFileWalker(parseClaudeStream({ cutoff: ctx.cutoff, excludePath, root: plan.claudeRoot })) : Stream16.empty,
|
|
4259
|
-
pi: plan.piSourceExisted ? provideFileWalker(parsePiStream({ cutoff: ctx.cutoff, excludePath, root: plan.piRoot })) : Stream16.empty,
|
|
4260
|
-
hermes: plan.hermesSourceExisted ? parseHermesStream({ cutoff: ctx.cutoff, excludePath, root: plan.hermesRoot }).pipe(
|
|
4261
|
-
Stream16.provide(HermesParserLive)
|
|
4262
|
-
) : Stream16.empty,
|
|
4263
|
-
opencode: plan.opencodeSourceExisted ? parseOpenCodeStream({ cutoff: ctx.cutoff, excludePath, root: plan.opencodeRoot }).pipe(
|
|
4264
|
-
Stream16.provide(OpenCodeIoLive)
|
|
4265
|
-
) : Stream16.empty
|
|
4266
|
-
};
|
|
4267
|
-
};
|
|
5676
|
+
return (ctx) => ({
|
|
5677
|
+
anySourceExisted: plan.anySourceExisted,
|
|
5678
|
+
codex: codexStreamForPlan(plan, ctx.cutoff, excludePath),
|
|
5679
|
+
claudeCode: plan.claudeSourceExisted ? provideFileWalker(parseClaudeStream({ cutoff: ctx.cutoff, excludePath, root: plan.claudeRoot })) : Stream17.empty,
|
|
5680
|
+
pi: plan.piSourceExisted ? provideFileWalker(parsePiStream({ cutoff: ctx.cutoff, excludePath, root: plan.piRoot })) : Stream17.empty,
|
|
5681
|
+
hermes: plan.hermesSourceExisted ? parseHermesStream({ cutoff: ctx.cutoff, excludePath, root: plan.hermesRoot }).pipe(
|
|
5682
|
+
Stream17.provide(HermesParserLive)
|
|
5683
|
+
) : Stream17.empty,
|
|
5684
|
+
opencode: plan.opencodeSourceExisted ? parseOpenCodeStream({ cutoff: ctx.cutoff, excludePath, root: plan.opencodeRoot }).pipe(
|
|
5685
|
+
Stream17.provide(OpenCodeIoLive)
|
|
5686
|
+
) : Stream17.empty
|
|
5687
|
+
});
|
|
4268
5688
|
}
|
|
4269
5689
|
function buildRealParserFactoryEffect(options = {}) {
|
|
4270
|
-
return
|
|
5690
|
+
return Effect26.map(resolveParserSourcePlanEffect(options), buildFactoryFromPlan);
|
|
4271
5691
|
}
|
|
4272
5692
|
|
|
4273
5693
|
// src/index.ts
|
|
@@ -4301,8 +5721,40 @@ function parseHarnessFilter(value) {
|
|
|
4301
5721
|
}
|
|
4302
5722
|
throw new SyncError(EXIT.invalidArguments, `error: --harness must be one of ${SUPPORTED_HARNESSES.join("|")}`);
|
|
4303
5723
|
}
|
|
5724
|
+
function parseReplacementFlags(raw) {
|
|
5725
|
+
const startHourUtc = optionalValue(raw.replaceStart);
|
|
5726
|
+
const endHourUtc = optionalValue(raw.replaceEnd);
|
|
5727
|
+
const harnessList = optionalValue(raw.replaceHarnesses);
|
|
5728
|
+
if (startHourUtc === void 0 && endHourUtc === void 0 && harnessList === void 0) {
|
|
5729
|
+
if (raw.confirmReplacement) {
|
|
5730
|
+
throw new SyncError(EXIT.invalidArguments, "error: --confirm-replacement requires an atomic replacement scope");
|
|
5731
|
+
}
|
|
5732
|
+
return void 0;
|
|
5733
|
+
}
|
|
5734
|
+
if (!(startHourUtc && endHourUtc && harnessList)) {
|
|
5735
|
+
throw new SyncError(
|
|
5736
|
+
EXIT.invalidArguments,
|
|
5737
|
+
"error: --replace-start, --replace-end, and --replace-harnesses must be provided together"
|
|
5738
|
+
);
|
|
5739
|
+
}
|
|
5740
|
+
const harnesses = harnessList.split(",").map((value) => parseHarnessFilter(value.trim()));
|
|
5741
|
+
if (harnesses.some((harness) => harness === void 0)) {
|
|
5742
|
+
throw new SyncError(EXIT.invalidArguments, "error: replacement harness list cannot be empty");
|
|
5743
|
+
}
|
|
5744
|
+
if (!(raw.dryRun || raw.confirmReplacement)) {
|
|
5745
|
+
throw new SyncError(
|
|
5746
|
+
EXIT.invalidArguments,
|
|
5747
|
+
"error: atomic replacement uploads require --confirm-replacement (use --dry-run to inspect first)"
|
|
5748
|
+
);
|
|
5749
|
+
}
|
|
5750
|
+
return {
|
|
5751
|
+
startHourUtc,
|
|
5752
|
+
endHourUtc,
|
|
5753
|
+
harnesses
|
|
5754
|
+
};
|
|
5755
|
+
}
|
|
4304
5756
|
function syncCommandErrorExitCode(err) {
|
|
4305
|
-
return
|
|
5757
|
+
return Effect27.gen(function* () {
|
|
4306
5758
|
const console = yield* CliConsole;
|
|
4307
5759
|
if (err instanceof CliConfigError) {
|
|
4308
5760
|
yield* console.writeStderrLine(err.message);
|
|
@@ -4317,7 +5769,7 @@ function syncCommandErrorExitCode(err) {
|
|
|
4317
5769
|
});
|
|
4318
5770
|
}
|
|
4319
5771
|
function setupCommandErrorExitCode(err) {
|
|
4320
|
-
return
|
|
5772
|
+
return Effect27.gen(function* () {
|
|
4321
5773
|
const console = yield* CliConsole;
|
|
4322
5774
|
if (err instanceof CliConfigError || err instanceof SetupError) {
|
|
4323
5775
|
yield* console.writeStderrLine(err.message);
|
|
@@ -4329,26 +5781,26 @@ function setupCommandErrorExitCode(err) {
|
|
|
4329
5781
|
}
|
|
4330
5782
|
function recoverCommandExitCode(effect) {
|
|
4331
5783
|
return effect.pipe(
|
|
4332
|
-
|
|
4333
|
-
|
|
5784
|
+
Effect27.catch((err) => syncCommandErrorExitCode(err)),
|
|
5785
|
+
Effect27.catchDefect((err) => syncCommandErrorExitCode(err))
|
|
4334
5786
|
);
|
|
4335
5787
|
}
|
|
4336
5788
|
function recoverSetupExitCode(effect) {
|
|
4337
5789
|
return effect.pipe(
|
|
4338
|
-
|
|
4339
|
-
|
|
5790
|
+
Effect27.catch((err) => setupCommandErrorExitCode(err)),
|
|
5791
|
+
Effect27.catchDefect((err) => setupCommandErrorExitCode(err))
|
|
4340
5792
|
);
|
|
4341
5793
|
}
|
|
4342
5794
|
function exitCodeToCommandEffect(exitCode) {
|
|
4343
|
-
return exitCode === EXIT.success ?
|
|
5795
|
+
return exitCode === EXIT.success ? Effect27.void : Effect27.fail(new CliExit(exitCode));
|
|
4344
5796
|
}
|
|
4345
5797
|
function runSyncCommand(raw) {
|
|
4346
5798
|
return recoverCommandExitCode(
|
|
4347
|
-
|
|
5799
|
+
Effect27.gen(function* () {
|
|
4348
5800
|
const configReader = yield* CliConfigReader;
|
|
4349
5801
|
const console = yield* CliConsole;
|
|
4350
5802
|
const env = yield* configReader.read();
|
|
4351
|
-
const config = yield*
|
|
5803
|
+
const config = yield* Effect27.try({
|
|
4352
5804
|
try: () => resolveSyncConfig(
|
|
4353
5805
|
{
|
|
4354
5806
|
api: optionalValue(raw.api),
|
|
@@ -4362,10 +5814,14 @@ function runSyncCommand(raw) {
|
|
|
4362
5814
|
catch: (err) => err
|
|
4363
5815
|
});
|
|
4364
5816
|
const { device, token } = config;
|
|
4365
|
-
const harness = yield*
|
|
5817
|
+
const harness = yield* Effect27.try({
|
|
4366
5818
|
try: () => parseHarnessFilter(optionalValue(raw.harness)),
|
|
4367
5819
|
catch: (err) => err
|
|
4368
5820
|
});
|
|
5821
|
+
const replacement = yield* Effect27.try({
|
|
5822
|
+
try: () => parseReplacementFlags(raw),
|
|
5823
|
+
catch: (err) => err
|
|
5824
|
+
});
|
|
4369
5825
|
if (!(token && device)) {
|
|
4370
5826
|
yield* console.writeStderrLine(
|
|
4371
5827
|
"error: --token and --device are required (or set ~/.oopst.yml / OOPST_TOKEN / OOPST_DEVICE)"
|
|
@@ -4373,7 +5829,7 @@ function runSyncCommand(raw) {
|
|
|
4373
5829
|
return EXIT.invalidArguments;
|
|
4374
5830
|
}
|
|
4375
5831
|
const factory = yield* buildRealParserFactoryEffect({ env, exclude: config.exclude }).pipe(
|
|
4376
|
-
|
|
5832
|
+
Effect27.provide(ParserSourcesLive)
|
|
4377
5833
|
);
|
|
4378
5834
|
let sourcesExistedHint = false;
|
|
4379
5835
|
const parserFactory = (ctx) => {
|
|
@@ -4387,6 +5843,7 @@ function runSyncCommand(raw) {
|
|
|
4387
5843
|
serverUrl: config.serverUrl,
|
|
4388
5844
|
lookbackDays: config.lookbackDays,
|
|
4389
5845
|
harness,
|
|
5846
|
+
replacement,
|
|
4390
5847
|
dryRun: raw.dryRun,
|
|
4391
5848
|
verbose: raw.verbose,
|
|
4392
5849
|
color: yield* console.stdoutIsTTY,
|
|
@@ -4398,16 +5855,16 @@ function runSyncCommand(raw) {
|
|
|
4398
5855
|
});
|
|
4399
5856
|
return result.exitCode;
|
|
4400
5857
|
})
|
|
4401
|
-
).pipe(
|
|
5858
|
+
).pipe(Effect27.flatMap(exitCodeToCommandEffect));
|
|
4402
5859
|
}
|
|
4403
5860
|
function runSetupCommand(raw) {
|
|
4404
5861
|
return recoverSetupExitCode(
|
|
4405
|
-
|
|
5862
|
+
Effect27.gen(function* () {
|
|
4406
5863
|
const console = yield* CliConsole;
|
|
4407
5864
|
const metadata = yield* CliPackageMetadata;
|
|
4408
5865
|
const cliProcess = yield* CliProcess;
|
|
4409
5866
|
const nodePath = yield* cliProcess.execPath;
|
|
4410
|
-
const result = yield*
|
|
5867
|
+
const result = yield* Effect27.tryPromise({
|
|
4411
5868
|
try: () => runSetup({
|
|
4412
5869
|
api: optionalValue(raw.api),
|
|
4413
5870
|
server: optionalValue(raw.server),
|
|
@@ -4420,7 +5877,7 @@ function runSetupCommand(raw) {
|
|
|
4420
5877
|
});
|
|
4421
5878
|
return result.exitCode;
|
|
4422
5879
|
})
|
|
4423
|
-
).pipe(
|
|
5880
|
+
).pipe(Effect27.flatMap(exitCodeToCommandEffect));
|
|
4424
5881
|
}
|
|
4425
5882
|
var apiFlag = Flag.string("api").pipe(
|
|
4426
5883
|
Flag.withDescription("API base URL (or server.api / OOPST_SERVER)"),
|
|
@@ -4443,6 +5900,22 @@ var harnessFlag = Flag.string("harness").pipe(
|
|
|
4443
5900
|
Flag.withDescription("Restrict to codex|claude_code|pi|hermes|opencode"),
|
|
4444
5901
|
Flag.optional
|
|
4445
5902
|
);
|
|
5903
|
+
var replaceStartFlag = Flag.string("replace-start").pipe(
|
|
5904
|
+
Flag.withDescription("Atomic replacement start as an inclusive UTC hour"),
|
|
5905
|
+
Flag.optional
|
|
5906
|
+
);
|
|
5907
|
+
var replaceEndFlag = Flag.string("replace-end").pipe(
|
|
5908
|
+
Flag.withDescription("Atomic replacement end as an exclusive UTC hour"),
|
|
5909
|
+
Flag.optional
|
|
5910
|
+
);
|
|
5911
|
+
var replaceHarnessesFlag = Flag.string("replace-harnesses").pipe(
|
|
5912
|
+
Flag.withDescription("Comma-separated harness set for an atomic replacement"),
|
|
5913
|
+
Flag.optional
|
|
5914
|
+
);
|
|
5915
|
+
var confirmReplacementFlag = Flag.boolean("confirm-replacement").pipe(
|
|
5916
|
+
Flag.withDescription("Confirm that the declared replacement scope may overwrite live facts"),
|
|
5917
|
+
Flag.withDefault(false)
|
|
5918
|
+
);
|
|
4446
5919
|
var dryRunFlag = Flag.boolean("dry-run").pipe(
|
|
4447
5920
|
Flag.withDescription("Parse and print without uploading"),
|
|
4448
5921
|
Flag.withDefault(false)
|
|
@@ -4458,6 +5931,10 @@ function buildCli() {
|
|
|
4458
5931
|
server: serverFlag,
|
|
4459
5932
|
lookbackDays: lookbackDaysFlag,
|
|
4460
5933
|
harness: harnessFlag,
|
|
5934
|
+
replaceStart: replaceStartFlag,
|
|
5935
|
+
replaceEnd: replaceEndFlag,
|
|
5936
|
+
replaceHarnesses: replaceHarnessesFlag,
|
|
5937
|
+
confirmReplacement: confirmReplacementFlag,
|
|
4461
5938
|
dryRun: dryRunFlag,
|
|
4462
5939
|
verbose: verboseFlag
|
|
4463
5940
|
},
|
|
@@ -4477,10 +5954,10 @@ function buildCli() {
|
|
|
4477
5954
|
);
|
|
4478
5955
|
}
|
|
4479
5956
|
function runCli() {
|
|
4480
|
-
return
|
|
5957
|
+
return Effect27.gen(function* () {
|
|
4481
5958
|
const metadata = yield* CliPackageMetadata;
|
|
4482
5959
|
yield* buildCli().pipe(Command.run({ version: metadata.version }));
|
|
4483
|
-
}).pipe(
|
|
5960
|
+
}).pipe(Effect27.provide(CliRuntimeLayer));
|
|
4484
5961
|
}
|
|
4485
5962
|
if (isEntrypoint()) {
|
|
4486
5963
|
runCli().pipe(NodeRuntime.runMain);
|