@cookielab.io/oopst 0.6.2 → 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 +2304 -882
- 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,6 +2999,12 @@ function isObject(value) {
|
|
|
2519
2999
|
function isFiniteNumber(value) {
|
|
2520
3000
|
return typeof value === "number" && Number.isFinite(value);
|
|
2521
3001
|
}
|
|
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
|
+
}
|
|
2522
3008
|
function isCodexUsageCounters(value) {
|
|
2523
3009
|
if (!isObject(value)) {
|
|
2524
3010
|
return false;
|
|
@@ -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,6 +3048,11 @@ function isCodexTokenCountEvent(value) {
|
|
|
2557
3048
|
if (info["model"] !== void 0 && typeof info["model"] !== "string") {
|
|
2558
3049
|
return false;
|
|
2559
3050
|
}
|
|
3051
|
+
for (const key6 of ["id", "turn_id", "turnId"]) {
|
|
3052
|
+
if (!isOptionalBoundedString(info[key6], MAX_CODEX_SOURCE_ID_LENGTH)) {
|
|
3053
|
+
return false;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
2560
3056
|
if (info["last_token_usage"] !== void 0 && !isCodexUsageCounters(info["last_token_usage"])) {
|
|
2561
3057
|
return false;
|
|
2562
3058
|
}
|
|
@@ -2572,6 +3068,28 @@ function isCodexTokenCountEvent(value) {
|
|
|
2572
3068
|
}
|
|
2573
3069
|
return true;
|
|
2574
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)) {
|
|
3081
|
+
return false;
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
const info = payload["info"];
|
|
3085
|
+
if (info === void 0) {
|
|
3086
|
+
return true;
|
|
3087
|
+
}
|
|
3088
|
+
if (!isObject(info)) {
|
|
3089
|
+
return false;
|
|
3090
|
+
}
|
|
3091
|
+
return ["id", "turn_id", "turnId"].every((key6) => isOptionalBoundedString(info[key6], MAX_CODEX_SOURCE_ID_LENGTH));
|
|
3092
|
+
}
|
|
2575
3093
|
function isCodexTurnContextEvent(value) {
|
|
2576
3094
|
if (!isObject(value)) {
|
|
2577
3095
|
return false;
|
|
@@ -2610,18 +3128,66 @@ 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
|
}
|
|
3165
|
+
function nonEmpty(value) {
|
|
3166
|
+
const trimmed = value?.trim();
|
|
3167
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
3168
|
+
}
|
|
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
|
+
};
|
|
3190
|
+
}
|
|
2625
3191
|
function extractCodexUsageSnapshot(event) {
|
|
2626
3192
|
return {
|
|
2627
3193
|
last: event.payload.info?.last_token_usage,
|
|
@@ -2631,30 +3197,36 @@ function extractCodexUsageSnapshot(event) {
|
|
|
2631
3197
|
function pickCodexEventModel(event) {
|
|
2632
3198
|
return event.payload.info?.model ?? event.payload.model ?? event.model;
|
|
2633
3199
|
}
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
}
|
|
2639
|
-
function key2(hourUtc, model) {
|
|
2640
|
-
return `${hourUtc.toISOString()}|${model}`;
|
|
2641
|
-
}
|
|
2642
|
-
function shouldExcludePath(path, excludePath) {
|
|
2643
|
-
return path !== void 0 && excludePath?.(path) === true;
|
|
2644
|
-
}
|
|
2645
|
-
function makeCodexFileAccumulator() {
|
|
2646
|
-
return {
|
|
2647
|
-
buckets: /* @__PURE__ */ new Map(),
|
|
2648
|
-
currentModel: void 0,
|
|
2649
|
-
excluded: false,
|
|
2650
|
-
lineNo: 0,
|
|
2651
|
-
sawTurnContext: false,
|
|
2652
|
-
previousTotalUsage: void 0,
|
|
2653
|
-
pendingUsage: [],
|
|
2654
|
-
sessionLastSeenAt: void 0,
|
|
2655
|
-
sessionStartedAt: void 0
|
|
2656
|
-
};
|
|
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
|
+
);
|
|
2657
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;
|
|
2658
3230
|
function lineStreamFromFile(path) {
|
|
2659
3231
|
return Stream8.callback(
|
|
2660
3232
|
(queue) => Effect15.acquireRelease(
|
|
@@ -2699,44 +3271,442 @@ function lineStreamFromFile(path) {
|
|
|
2699
3271
|
)
|
|
2700
3272
|
);
|
|
2701
3273
|
}
|
|
2702
|
-
function
|
|
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;
|
|
2703
3420
|
return {
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
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)
|
|
2707
3430
|
};
|
|
2708
3431
|
}
|
|
2709
|
-
function
|
|
2710
|
-
|
|
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;
|
|
2711
3445
|
}
|
|
2712
|
-
function
|
|
2713
|
-
|
|
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;
|
|
2714
3470
|
}
|
|
2715
|
-
function
|
|
2716
|
-
if (
|
|
2717
|
-
return
|
|
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
|
+
}
|
|
2718
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
|
+
};
|
|
2719
3575
|
return {
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
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
|
+
})
|
|
2723
3591
|
};
|
|
2724
3592
|
}
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
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)) {
|
|
2729
3651
|
return void 0;
|
|
2730
3652
|
}
|
|
2731
|
-
|
|
2732
|
-
acc.
|
|
2733
|
-
return
|
|
3653
|
+
acc.previousTotalUsage = total;
|
|
3654
|
+
acc.remainingInheritedUsage = void 0;
|
|
3655
|
+
return positiveIncrement(cumulativeCodexDelta(total, previous));
|
|
2734
3656
|
}
|
|
2735
3657
|
if (last === void 0) {
|
|
2736
3658
|
return void 0;
|
|
2737
3659
|
}
|
|
2738
|
-
const
|
|
2739
|
-
return
|
|
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);
|
|
2740
3710
|
}
|
|
2741
3711
|
function addToBucket(acc, hourUtc, model, delta) {
|
|
2742
3712
|
const { inputTokens, outputTokens, cacheReadTokens } = delta;
|
|
@@ -2761,11 +3731,16 @@ function addToBucket(acc, hourUtc, model, delta) {
|
|
|
2761
3731
|
existing.cacheReadTokens += cacheReadTokens;
|
|
2762
3732
|
return true;
|
|
2763
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
|
+
}
|
|
2764
3741
|
function flushPendingUsage(acc, model) {
|
|
2765
3742
|
for (const pending of acc.pendingUsage) {
|
|
2766
|
-
|
|
2767
|
-
markSessionSeen(acc, pending.eventAt);
|
|
2768
|
-
}
|
|
3743
|
+
recordUsageCandidate(acc, pending, model);
|
|
2769
3744
|
}
|
|
2770
3745
|
acc.pendingUsage = [];
|
|
2771
3746
|
}
|
|
@@ -2798,6 +3773,7 @@ function accumulateCodexLine(context, acc, raw) {
|
|
|
2798
3773
|
if (shouldExcludePath(parsed.payload.cwd, options.excludePath)) {
|
|
2799
3774
|
acc.excluded = true;
|
|
2800
3775
|
acc.buckets.clear();
|
|
3776
|
+
acc.candidates = [];
|
|
2801
3777
|
acc.pendingUsage = [];
|
|
2802
3778
|
}
|
|
2803
3779
|
return acc;
|
|
@@ -2806,6 +3782,7 @@ function accumulateCodexLine(context, acc, raw) {
|
|
|
2806
3782
|
if (shouldExcludePath(parsed.payload.cwd, options.excludePath)) {
|
|
2807
3783
|
acc.excluded = true;
|
|
2808
3784
|
acc.buckets.clear();
|
|
3785
|
+
acc.candidates = [];
|
|
2809
3786
|
acc.pendingUsage = [];
|
|
2810
3787
|
return acc;
|
|
2811
3788
|
}
|
|
@@ -2817,6 +3794,10 @@ function accumulateCodexLine(context, acc, raw) {
|
|
|
2817
3794
|
}
|
|
2818
3795
|
return acc;
|
|
2819
3796
|
}
|
|
3797
|
+
if (isCodexTaskStartedEvent(parsed)) {
|
|
3798
|
+
acc.currentTurnId = pickCodexTurnId(parsed);
|
|
3799
|
+
return acc;
|
|
3800
|
+
}
|
|
2820
3801
|
if (!isCodexTokenCountEvent(parsed)) {
|
|
2821
3802
|
return acc;
|
|
2822
3803
|
}
|
|
@@ -2830,24 +3811,25 @@ function accumulateCodexLine(context, acc, raw) {
|
|
|
2830
3811
|
return acc;
|
|
2831
3812
|
}
|
|
2832
3813
|
const eventAt = new Date(tsMs);
|
|
3814
|
+
const eventIndex = acc.nextEventIndex;
|
|
3815
|
+
acc.nextEventIndex += 1;
|
|
3816
|
+
const turnId = pickCodexTurnId(parsed) ?? acc.currentTurnId;
|
|
3817
|
+
const candidate = { eventAt, eventIndex, turnId, usage: delta };
|
|
2833
3818
|
const eventModel = pickCodexEventModel(parsed);
|
|
2834
3819
|
const model = acc.sawTurnContext ? acc.currentModel ?? eventModel : eventModel ?? acc.currentModel;
|
|
2835
3820
|
if (model === void 0) {
|
|
2836
|
-
acc.pendingUsage.push(
|
|
3821
|
+
acc.pendingUsage.push(candidate);
|
|
2837
3822
|
return acc;
|
|
2838
3823
|
}
|
|
2839
|
-
|
|
2840
|
-
markSessionSeen(acc, eventAt);
|
|
2841
|
-
}
|
|
3824
|
+
recordUsageCandidate(acc, candidate, model);
|
|
2842
3825
|
return acc;
|
|
2843
3826
|
}
|
|
2844
|
-
function bucketsFromAccumulator(path, acc) {
|
|
3827
|
+
function bucketsFromAccumulator(path, options, acc) {
|
|
2845
3828
|
if (acc.excluded || acc.sessionStartedAt === void 0 || acc.sessionLastSeenAt === void 0) {
|
|
2846
3829
|
return [];
|
|
2847
3830
|
}
|
|
2848
|
-
const sessionStartedAt = acc
|
|
2849
|
-
const
|
|
2850
|
-
const sessionIds = /* @__PURE__ */ new Set([path]);
|
|
3831
|
+
const { sessionLastSeenAt, sessionStartedAt } = acc;
|
|
3832
|
+
const sessionIds = /* @__PURE__ */ new Set([options.sessionIdentity ?? path]);
|
|
2851
3833
|
return Array.from(acc.buckets.values(), (bucket) => ({
|
|
2852
3834
|
harness: "codex",
|
|
2853
3835
|
model: bucket.model,
|
|
@@ -2861,13 +3843,37 @@ function bucketsFromAccumulator(path, acc) {
|
|
|
2861
3843
|
sessionLastSeenAt
|
|
2862
3844
|
}));
|
|
2863
3845
|
}
|
|
2864
|
-
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 = {}) {
|
|
2865
3867
|
const context = { logger, options, path };
|
|
2866
|
-
return
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
)
|
|
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
|
+
}))
|
|
2871
3877
|
);
|
|
2872
3878
|
}
|
|
2873
3879
|
|
|
@@ -2878,6 +3884,12 @@ function isObject2(value) {
|
|
|
2878
3884
|
function isFiniteNumber2(value) {
|
|
2879
3885
|
return typeof value === "number" && Number.isFinite(value);
|
|
2880
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
|
+
}
|
|
2881
3893
|
function isClaudeUsage(value) {
|
|
2882
3894
|
if (!isObject2(value)) {
|
|
2883
3895
|
return false;
|
|
@@ -2898,20 +3910,23 @@ function isClaudeAssistantEvent(value) {
|
|
|
2898
3910
|
if (value["type"] !== "assistant") {
|
|
2899
3911
|
return false;
|
|
2900
3912
|
}
|
|
2901
|
-
if (typeof value["timestamp"] !== "string") {
|
|
3913
|
+
if (typeof value["timestamp"] !== "string" || value["timestamp"].length > MAX_CLAUDE_TIMESTAMP_LENGTH) {
|
|
2902
3914
|
return false;
|
|
2903
3915
|
}
|
|
2904
|
-
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)) {
|
|
2905
3920
|
return false;
|
|
2906
3921
|
}
|
|
2907
|
-
if (value["
|
|
3922
|
+
if (value["isSidechain"] !== void 0 && typeof value["isSidechain"] !== "boolean") {
|
|
2908
3923
|
return false;
|
|
2909
3924
|
}
|
|
2910
3925
|
const message = value["message"];
|
|
2911
3926
|
if (!isObject2(message)) {
|
|
2912
3927
|
return false;
|
|
2913
3928
|
}
|
|
2914
|
-
if (message["id"]
|
|
3929
|
+
if (!isOptionalBoundedString2(message["id"], MAX_CLAUDE_SOURCE_ID_LENGTH)) {
|
|
2915
3930
|
return false;
|
|
2916
3931
|
}
|
|
2917
3932
|
if (typeof message["model"] !== "string") {
|
|
@@ -2928,25 +3943,21 @@ function key3(hourUtc, model) {
|
|
|
2928
3943
|
return `${hourUtc.toISOString()}|${model}`;
|
|
2929
3944
|
}
|
|
2930
3945
|
function shouldExcludePath2(path, excludePath) {
|
|
2931
|
-
return path !== void 0 && excludePath?.(path) === true;
|
|
2932
|
-
}
|
|
2933
|
-
function
|
|
2934
|
-
return {
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
seenTuples: options.seenTuples ?? /* @__PURE__ */ new Set(),
|
|
2939
|
-
sessionLastSeenAt: void 0,
|
|
2940
|
-
sessionStartedAt: void 0
|
|
2941
|
-
};
|
|
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 };
|
|
2942
3953
|
}
|
|
2943
3954
|
function lineStreamFromFile2(path) {
|
|
2944
|
-
return
|
|
2945
|
-
(queue) =>
|
|
2946
|
-
|
|
3955
|
+
return Stream10.callback(
|
|
3956
|
+
(queue) => Effect17.acquireRelease(
|
|
3957
|
+
Effect17.sync(() => {
|
|
2947
3958
|
let done = false;
|
|
2948
3959
|
const stream = createReadStream2(path, { encoding: "utf8" });
|
|
2949
|
-
const
|
|
3960
|
+
const lines = createInterface3({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
2950
3961
|
const offer = (line) => {
|
|
2951
3962
|
if (!done) {
|
|
2952
3963
|
Queue2.offerUnsafe(queue, line);
|
|
@@ -2965,61 +3976,32 @@ function lineStreamFromFile2(path) {
|
|
|
2965
3976
|
}
|
|
2966
3977
|
};
|
|
2967
3978
|
stream.once("error", fail);
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
3979
|
+
lines.once("error", fail);
|
|
3980
|
+
lines.once("close", end);
|
|
3981
|
+
lines.on("line", offer);
|
|
2971
3982
|
return {
|
|
2972
3983
|
close: () => {
|
|
2973
3984
|
done = true;
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3985
|
+
lines.off("close", end);
|
|
3986
|
+
lines.off("error", fail);
|
|
3987
|
+
lines.off("line", offer);
|
|
2977
3988
|
stream.off("error", fail);
|
|
2978
|
-
|
|
3989
|
+
lines.close();
|
|
2979
3990
|
stream.destroy();
|
|
2980
3991
|
}
|
|
2981
3992
|
};
|
|
2982
3993
|
}),
|
|
2983
|
-
(resource) =>
|
|
3994
|
+
(resource) => Effect17.sync(() => resource.close())
|
|
2984
3995
|
)
|
|
2985
3996
|
);
|
|
2986
3997
|
}
|
|
2987
|
-
function
|
|
2988
|
-
|
|
2989
|
-
const outputTokens = Math.max(0, usage.output_tokens ?? 0);
|
|
2990
|
-
const cacheReadTokens = Math.max(0, usage.cache_read_input_tokens ?? 0);
|
|
2991
|
-
const cacheCreateTokens = Math.max(0, usage.cache_creation_input_tokens ?? 0);
|
|
2992
|
-
if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheCreateTokens === 0) {
|
|
3998
|
+
function eventIsEligible(timestampMs3, options) {
|
|
3999
|
+
if (options.cutoff !== void 0 && timestampMs3 < options.cutoff.getTime()) {
|
|
2993
4000
|
return false;
|
|
2994
4001
|
}
|
|
2995
|
-
|
|
2996
|
-
const existing = acc.buckets.get(k);
|
|
2997
|
-
if (existing === void 0) {
|
|
2998
|
-
acc.buckets.set(k, {
|
|
2999
|
-
hourUtc,
|
|
3000
|
-
model,
|
|
3001
|
-
inputTokens,
|
|
3002
|
-
outputTokens,
|
|
3003
|
-
cacheReadTokens,
|
|
3004
|
-
cacheCreateTokens
|
|
3005
|
-
});
|
|
3006
|
-
return true;
|
|
3007
|
-
}
|
|
3008
|
-
existing.inputTokens += inputTokens;
|
|
3009
|
-
existing.outputTokens += outputTokens;
|
|
3010
|
-
existing.cacheReadTokens += cacheReadTokens;
|
|
3011
|
-
existing.cacheCreateTokens += cacheCreateTokens;
|
|
3012
|
-
return true;
|
|
3013
|
-
}
|
|
3014
|
-
function markSessionSeen2(acc, eventAt) {
|
|
3015
|
-
if (acc.sessionStartedAt === void 0 || eventAt.getTime() < acc.sessionStartedAt.getTime()) {
|
|
3016
|
-
acc.sessionStartedAt = eventAt;
|
|
3017
|
-
}
|
|
3018
|
-
if (acc.sessionLastSeenAt === void 0 || eventAt.getTime() > acc.sessionLastSeenAt.getTime()) {
|
|
3019
|
-
acc.sessionLastSeenAt = eventAt;
|
|
3020
|
-
}
|
|
4002
|
+
return options.endExclusive === void 0 || timestampMs3 < options.endExclusive.getTime();
|
|
3021
4003
|
}
|
|
3022
|
-
function
|
|
4004
|
+
function accumulateClaudeCandidate(context, acc, raw) {
|
|
3023
4005
|
const { logger, options, path } = context;
|
|
3024
4006
|
acc.lineNo += 1;
|
|
3025
4007
|
if (acc.excluded) {
|
|
@@ -3041,37 +4023,129 @@ function accumulateClaudeLine(context, acc, raw) {
|
|
|
3041
4023
|
}
|
|
3042
4024
|
if (shouldExcludePath2(parsed.cwd, options.excludePath)) {
|
|
3043
4025
|
acc.excluded = true;
|
|
3044
|
-
acc.
|
|
4026
|
+
acc.identified.clear();
|
|
4027
|
+
acc.unidentified = [];
|
|
3045
4028
|
return acc;
|
|
3046
4029
|
}
|
|
3047
|
-
const
|
|
3048
|
-
if (!Number.isFinite(
|
|
4030
|
+
const timestampMs3 = Date.parse(parsed.timestamp);
|
|
4031
|
+
if (!(Number.isFinite(timestampMs3) && eventIsEligible(timestampMs3, options))) {
|
|
3049
4032
|
return acc;
|
|
3050
4033
|
}
|
|
3051
|
-
|
|
3052
|
-
|
|
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);
|
|
3053
4050
|
}
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
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";
|
|
3059
4083
|
}
|
|
3060
|
-
acc.seenTuples.add(tuple);
|
|
3061
4084
|
}
|
|
3062
|
-
const
|
|
3063
|
-
if (
|
|
3064
|
-
|
|
4085
|
+
const timestampOrder = next.eventAt.getTime() - current.eventAt.getTime();
|
|
4086
|
+
if (timestampOrder !== 0) {
|
|
4087
|
+
return timestampOrder > 0;
|
|
3065
4088
|
}
|
|
3066
|
-
|
|
4089
|
+
if (next.eventOrder !== current.eventOrder) {
|
|
4090
|
+
return next.eventOrder > current.eventOrder;
|
|
4091
|
+
}
|
|
4092
|
+
return next.sourceOrder.localeCompare(current.sourceOrder) < 0;
|
|
3067
4093
|
}
|
|
3068
|
-
function
|
|
3069
|
-
|
|
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) {
|
|
3070
4146
|
return [];
|
|
3071
4147
|
}
|
|
3072
|
-
const
|
|
3073
|
-
const sessionLastSeenAt = acc.sessionLastSeenAt;
|
|
3074
|
-
const sessionIds = /* @__PURE__ */ new Set([options.sessionId ?? path]);
|
|
4148
|
+
const sessionIds = /* @__PURE__ */ new Set([sessionId]);
|
|
3075
4149
|
return Array.from(acc.buckets.values(), (bucket) => ({
|
|
3076
4150
|
harness: "claude_code",
|
|
3077
4151
|
model: bucket.model,
|
|
@@ -3085,70 +4159,80 @@ function bucketsFromAccumulator2(path, options, acc) {
|
|
|
3085
4159
|
sessionLastSeenAt
|
|
3086
4160
|
}));
|
|
3087
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
|
+
}
|
|
3088
4173
|
function bucketizeClaudeFileStream(path, logger, options = {}) {
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
Stream9.runFold(
|
|
3093
|
-
() => makeClaudeFileAccumulator(options),
|
|
3094
|
-
(acc, raw) => accumulateClaudeLine(context, acc, raw)
|
|
3095
|
-
),
|
|
3096
|
-
Effect16.map((acc) => Stream9.fromIterable(bucketsFromAccumulator2(path, options, acc)))
|
|
4174
|
+
return Stream10.unwrap(
|
|
4175
|
+
collectClaudeFileCandidates(path, logger, options).pipe(
|
|
4176
|
+
Effect17.map((candidates) => Stream10.fromIterable(bucketizeClaudeCandidates(candidates)))
|
|
3097
4177
|
)
|
|
3098
4178
|
);
|
|
3099
4179
|
}
|
|
3100
4180
|
|
|
3101
4181
|
// ../../packages/parsers/src/claude/index.ts
|
|
3102
|
-
function
|
|
4182
|
+
function sourceContext(path) {
|
|
3103
4183
|
const fileDir = dirname2(path);
|
|
3104
4184
|
if (basename(fileDir) !== "subagents") {
|
|
3105
|
-
return path;
|
|
4185
|
+
return { pathRole: "parent", sessionId: path };
|
|
3106
4186
|
}
|
|
3107
4187
|
const sessionDir = dirname2(fileDir);
|
|
3108
|
-
return `${sessionDir}.jsonl
|
|
4188
|
+
return { pathRole: "subagent", sessionId: `${sessionDir}.jsonl` };
|
|
3109
4189
|
}
|
|
3110
4190
|
function bucketizeClaudeSourcesStream(files, logger, options) {
|
|
3111
|
-
return
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
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
|
|
3122
4202
|
});
|
|
3123
|
-
}
|
|
3124
|
-
|
|
3125
|
-
)
|
|
3126
|
-
)
|
|
3127
|
-
|
|
4203
|
+
})
|
|
4204
|
+
),
|
|
4205
|
+
Effect18.map((filesCandidates) => Stream11.fromIterable(bucketizeClaudeCandidates(filesCandidates.flat())))
|
|
4206
|
+
)
|
|
4207
|
+
);
|
|
3128
4208
|
}
|
|
3129
|
-
var parseClaudeEffect =
|
|
3130
|
-
(opts) =>
|
|
4209
|
+
var parseClaudeEffect = Effect18.fn("ClaudeParser.parseClaudeEffect")(
|
|
4210
|
+
(opts) => Stream11.runCollect(parseClaudeStream(opts))
|
|
3131
4211
|
);
|
|
3132
|
-
var parseClaudeStream = (opts) =>
|
|
3133
|
-
|
|
4212
|
+
var parseClaudeStream = (opts) => Stream11.unwrap(
|
|
4213
|
+
Effect18.gen(function* () {
|
|
3134
4214
|
const logger = opts.logger ?? silentLogger;
|
|
3135
4215
|
const root = opts.root ?? resolveClaudeRoot();
|
|
3136
4216
|
if ((yield* pathKindEffectStrict(root)) === "file") {
|
|
4217
|
+
const source = sourceContext(root);
|
|
3137
4218
|
return bucketizeClaudeFileStream(root, logger, {
|
|
3138
4219
|
cutoff: opts.cutoff,
|
|
4220
|
+
endExclusive: opts.endExclusive,
|
|
3139
4221
|
excludePath: opts.excludePath,
|
|
3140
|
-
|
|
4222
|
+
pathRole: source.pathRole,
|
|
4223
|
+
sessionId: source.sessionId
|
|
3141
4224
|
});
|
|
3142
4225
|
}
|
|
3143
4226
|
return bucketizeClaudeSourcesStream(walkStreamStrict(root, opts.cutoff), logger, {
|
|
3144
4227
|
cutoff: opts.cutoff,
|
|
4228
|
+
endExclusive: opts.endExclusive,
|
|
3145
4229
|
excludePath: opts.excludePath
|
|
3146
4230
|
});
|
|
3147
4231
|
})
|
|
3148
4232
|
);
|
|
3149
4233
|
|
|
3150
4234
|
// ../../packages/parsers/src/codex/index.ts
|
|
3151
|
-
import { Effect as
|
|
4235
|
+
import { Effect as Effect19, Stream as Stream12 } from "effect";
|
|
3152
4236
|
var codexRoots = (opts, root) => {
|
|
3153
4237
|
const roots = [root];
|
|
3154
4238
|
if (opts.root === void 0 && opts.archivedRoot === void 0) {
|
|
@@ -3158,52 +4242,134 @@ var codexRoots = (opts, root) => {
|
|
|
3158
4242
|
}
|
|
3159
4243
|
return roots;
|
|
3160
4244
|
};
|
|
3161
|
-
|
|
3162
|
-
|
|
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))
|
|
3163
4307
|
);
|
|
3164
|
-
var parseCodexStream = (opts) =>
|
|
3165
|
-
|
|
4308
|
+
var parseCodexStream = (opts) => Stream12.unwrap(
|
|
4309
|
+
Effect19.gen(function* () {
|
|
3166
4310
|
const logger = opts.logger ?? silentLogger;
|
|
3167
4311
|
const root = opts.root ?? resolveCodexSessionsRoot();
|
|
3168
4312
|
if ((yield* pathKindEffectStrict(root)) === "file") {
|
|
3169
|
-
return
|
|
4313
|
+
return bucketizeIndexedCodexFilesStream(Stream12.succeed(root), opts.cutoff, logger, {
|
|
4314
|
+
excludePath: opts.excludePath
|
|
4315
|
+
});
|
|
3170
4316
|
}
|
|
3171
|
-
return
|
|
3172
|
-
|
|
3173
|
-
|
|
4317
|
+
return bucketizeIndexedCodexFilesStream(
|
|
4318
|
+
walkManyStreamStrict(codexRoots(opts, root), /* @__PURE__ */ new Date(0)),
|
|
4319
|
+
opts.cutoff,
|
|
4320
|
+
logger,
|
|
4321
|
+
{ excludePath: opts.excludePath }
|
|
4322
|
+
);
|
|
3174
4323
|
})
|
|
3175
4324
|
);
|
|
3176
|
-
function
|
|
3177
|
-
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
|
+
);
|
|
3178
4354
|
}
|
|
3179
4355
|
|
|
3180
4356
|
// ../../packages/parsers/src/effect/hermes-sqlite.ts
|
|
3181
|
-
import { Context as Context9, Effect as
|
|
4357
|
+
import { Context as Context9, Effect as Effect20, Layer as Layer8, Schema as Schema22 } from "effect";
|
|
3182
4358
|
var NODE_SQLITE_SPECIFIER = ["node", "sqlite"].join(":");
|
|
3183
4359
|
var MILLISECONDS_PER_SECOND = 1e3;
|
|
3184
|
-
var HERMES_SESSION_QUERY = `
|
|
3185
|
-
select
|
|
3186
|
-
id,
|
|
3187
|
-
model,
|
|
3188
|
-
billing_provider as "billingProvider",
|
|
3189
|
-
started_at as "startedAt",
|
|
3190
|
-
ended_at as "endedAt",
|
|
3191
|
-
input_tokens as "inputTokens",
|
|
3192
|
-
output_tokens as "outputTokens",
|
|
3193
|
-
cache_read_tokens as "cacheReadTokens",
|
|
3194
|
-
cache_write_tokens as "cacheWriteTokens",
|
|
3195
|
-
cwd
|
|
3196
|
-
from sessions
|
|
3197
|
-
where coalesce(ended_at, started_at) >= ?
|
|
3198
|
-
order by started_at asc, id asc
|
|
3199
|
-
`;
|
|
3200
4360
|
var HermesSqliteError = class extends Schema22.TaggedErrorClass()("HermesSqliteError", {
|
|
3201
4361
|
message: Schema22.String,
|
|
3202
|
-
operation: Schema22.Literals(["load", "open", "query", "
|
|
4362
|
+
operation: Schema22.Literals(["close", "load", "open", "query", "schema"]),
|
|
3203
4363
|
path: Schema22.optionalKey(Schema22.String),
|
|
3204
4364
|
cause: Schema22.optionalKey(Schema22.Defect())
|
|
3205
4365
|
}) {
|
|
3206
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
|
+
}
|
|
3207
4373
|
var HermesSqlite = class extends Context9.Service()("@oopst/parsers/HermesSqlite") {
|
|
3208
4374
|
};
|
|
3209
4375
|
var toHermesSqliteError = (operation, path) => (error) => new HermesSqliteError({
|
|
@@ -3212,34 +4378,124 @@ var toHermesSqliteError = (operation, path) => (error) => new HermesSqliteError(
|
|
|
3212
4378
|
path,
|
|
3213
4379
|
cause: error
|
|
3214
4380
|
});
|
|
3215
|
-
var loadDatabaseSync =
|
|
3216
|
-
const sqliteModule = yield*
|
|
4381
|
+
var loadDatabaseSync = Effect20.fn("HermesSqlite.loadDatabaseSync")(function* () {
|
|
4382
|
+
const sqliteModule = yield* Effect20.tryPromise({
|
|
3217
4383
|
try: () => import(NODE_SQLITE_SPECIFIER),
|
|
3218
4384
|
catch: toHermesSqliteError("load")
|
|
3219
4385
|
});
|
|
3220
4386
|
return sqliteModule.DatabaseSync;
|
|
3221
4387
|
});
|
|
3222
|
-
var openDatabase = (path, DatabaseSync) =>
|
|
4388
|
+
var openDatabase = (path, DatabaseSync) => Effect20.try({
|
|
3223
4389
|
try: () => new DatabaseSync(path, { readOnly: true, timeout: 1e3 }),
|
|
3224
4390
|
catch: toHermesSqliteError("open", path)
|
|
3225
4391
|
});
|
|
3226
|
-
var closeDatabase = (path, db) =>
|
|
4392
|
+
var closeDatabase = (path, db) => Effect20.try({
|
|
3227
4393
|
try: () => {
|
|
3228
4394
|
db.close();
|
|
3229
4395
|
},
|
|
3230
4396
|
catch: toHermesSqliteError("close", path)
|
|
3231
4397
|
});
|
|
3232
|
-
|
|
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({
|
|
3233
4476
|
try: () => {
|
|
3234
|
-
const
|
|
3235
|
-
|
|
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));
|
|
3236
4487
|
},
|
|
3237
|
-
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)
|
|
3238
4494
|
});
|
|
3239
4495
|
var makeHermesSqlite = () => ({
|
|
3240
|
-
readSessionRows:
|
|
4496
|
+
readSessionRows: Effect20.fn("HermesSqlite.readSessionRows")(function* (path, cutoff) {
|
|
3241
4497
|
const DatabaseSync = yield* loadDatabaseSync();
|
|
3242
|
-
return yield*
|
|
4498
|
+
return yield* Effect20.acquireUseRelease(
|
|
3243
4499
|
openDatabase(path, DatabaseSync),
|
|
3244
4500
|
(db) => querySessionRows(path, db, cutoff),
|
|
3245
4501
|
(db) => closeDatabase(path, db)
|
|
@@ -3248,13 +4504,13 @@ var makeHermesSqlite = () => ({
|
|
|
3248
4504
|
});
|
|
3249
4505
|
var HermesSqliteLive = Layer8.effect(
|
|
3250
4506
|
HermesSqlite,
|
|
3251
|
-
|
|
4507
|
+
Effect20.sync(() => HermesSqlite.of(makeHermesSqlite()))
|
|
3252
4508
|
);
|
|
3253
4509
|
|
|
3254
4510
|
// ../../packages/parsers/src/effect/opencode-io.ts
|
|
3255
4511
|
import { basename as basename2, join as join6 } from "path";
|
|
3256
4512
|
import { NodeFileSystem as NodeFileSystem5 } from "@effect/platform-node";
|
|
3257
|
-
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";
|
|
3258
4514
|
var NODE_SQLITE_SPECIFIER2 = ["node", "sqlite"].join(":");
|
|
3259
4515
|
var OPENCODE_DB_FILE_PATTERN2 = /^opencode(?:-[^/]+)?\.db$/u;
|
|
3260
4516
|
var SESSION_MESSAGE_QUERY = `
|
|
@@ -3286,13 +4542,13 @@ var toOpenCodeIoError = (operation, path) => (error) => new OpenCodeIoError({
|
|
|
3286
4542
|
path,
|
|
3287
4543
|
cause: error
|
|
3288
4544
|
});
|
|
3289
|
-
var readDir2 = (fs, path) => fs.readDirectory(path).pipe(
|
|
3290
|
-
var readDirBestEffort = (fs, path) => readDir2(fs, path).pipe(
|
|
3291
|
-
var readStat2 = (fs, path) => fs.stat(path).pipe(
|
|
3292
|
-
var readStatBestEffort = (fs, path) => readStat2(fs, path).pipe(
|
|
3293
|
-
var pathKind =
|
|
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) {
|
|
3294
4550
|
return yield* readStat2(fs, path).pipe(
|
|
3295
|
-
|
|
4551
|
+
Effect21.map((info) => {
|
|
3296
4552
|
if (info.type === "File") {
|
|
3297
4553
|
return "file";
|
|
3298
4554
|
}
|
|
@@ -3301,10 +4557,10 @@ var pathKind = Effect20.fn("OpenCodeIo.pathKind")(function* (fs, path) {
|
|
|
3301
4557
|
}
|
|
3302
4558
|
return "missing";
|
|
3303
4559
|
}),
|
|
3304
|
-
|
|
4560
|
+
Effect21.catch(() => Effect21.succeed("missing"))
|
|
3305
4561
|
);
|
|
3306
4562
|
});
|
|
3307
|
-
var listOpenCodeDatabases =
|
|
4563
|
+
var listOpenCodeDatabases = Effect21.fn("OpenCodeIo.listOpenCodeDatabases")(function* (fs, root) {
|
|
3308
4564
|
const entries = yield* readDirBestEffort(fs, root);
|
|
3309
4565
|
const databasePaths = [];
|
|
3310
4566
|
for (const entry of entries) {
|
|
@@ -3318,7 +4574,7 @@ var listOpenCodeDatabases = Effect20.fn("OpenCodeIo.listOpenCodeDatabases")(func
|
|
|
3318
4574
|
}
|
|
3319
4575
|
return databasePaths;
|
|
3320
4576
|
});
|
|
3321
|
-
var listProjectLegacyRoots =
|
|
4577
|
+
var listProjectLegacyRoots = Effect21.fn("OpenCodeIo.listProjectLegacyRoots")(function* (fs, root) {
|
|
3322
4578
|
const projectRoot = join6(root, "project");
|
|
3323
4579
|
const entries = yield* readDirBestEffort(fs, projectRoot);
|
|
3324
4580
|
const roots = [];
|
|
@@ -3330,7 +4586,7 @@ var listProjectLegacyRoots = Effect20.fn("OpenCodeIo.listProjectLegacyRoots")(fu
|
|
|
3330
4586
|
}
|
|
3331
4587
|
return roots;
|
|
3332
4588
|
});
|
|
3333
|
-
var legacyRoots =
|
|
4589
|
+
var legacyRoots = Effect21.fn("OpenCodeIo.legacyRoots")(function* (fs, root) {
|
|
3334
4590
|
return [
|
|
3335
4591
|
join6(root, "storage", "message"),
|
|
3336
4592
|
join6(root, "storage", "session", "message"),
|
|
@@ -3341,26 +4597,26 @@ var isRecent = (info, cutoff) => Option3.match(info.mtime, {
|
|
|
3341
4597
|
onNone: () => false,
|
|
3342
4598
|
onSome: (mtime) => mtime.getTime() >= cutoff.getTime()
|
|
3343
4599
|
});
|
|
3344
|
-
var streamJsonFiles = (fs, root, cutoff) =>
|
|
3345
|
-
() =>
|
|
3346
|
-
|
|
4600
|
+
var streamJsonFiles = (fs, root, cutoff) => Stream13.suspend(
|
|
4601
|
+
() => Stream13.fromIterableEffect(readDirBestEffort(fs, root)).pipe(
|
|
4602
|
+
Stream13.flatMap((entry) => {
|
|
3347
4603
|
const full = join6(root, entry);
|
|
3348
|
-
return
|
|
3349
|
-
|
|
4604
|
+
return Stream13.fromEffect(readStatBestEffort(fs, full)).pipe(
|
|
4605
|
+
Stream13.flatMap((info) => {
|
|
3350
4606
|
if (info?.type === "Directory") {
|
|
3351
4607
|
return streamJsonFiles(fs, full, cutoff);
|
|
3352
4608
|
}
|
|
3353
4609
|
if (info?.type === "File" && entry.endsWith(".json") && isRecent(info, cutoff)) {
|
|
3354
|
-
return
|
|
4610
|
+
return Stream13.succeed(full);
|
|
3355
4611
|
}
|
|
3356
|
-
return
|
|
4612
|
+
return Stream13.empty;
|
|
3357
4613
|
})
|
|
3358
4614
|
);
|
|
3359
4615
|
})
|
|
3360
4616
|
)
|
|
3361
4617
|
);
|
|
3362
|
-
var streamManyJsonFiles = (fs, roots, cutoff) =>
|
|
3363
|
-
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) {
|
|
3364
4620
|
const kind = yield* pathKind(fs, root);
|
|
3365
4621
|
if (kind === "missing") {
|
|
3366
4622
|
return { _tag: "Missing" };
|
|
@@ -3379,26 +4635,26 @@ var discoverSources = Effect20.fn("OpenCodeIo.discoverSources")(function* (fs, r
|
|
|
3379
4635
|
return { _tag: "LegacyJsonFiles", paths: streamManyJsonFiles(fs, roots, cutoff) };
|
|
3380
4636
|
});
|
|
3381
4637
|
var readJsonFile = (fs, path) => fs.readFileString(path, "utf8").pipe(
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
(text) =>
|
|
4638
|
+
Effect21.mapError(toOpenCodeIoError("readFile", path)),
|
|
4639
|
+
Effect21.flatMap(
|
|
4640
|
+
(text) => Effect21.try({
|
|
3385
4641
|
try: () => JSON.parse(text),
|
|
3386
4642
|
catch: toOpenCodeIoError("parseJson", path)
|
|
3387
4643
|
})
|
|
3388
4644
|
)
|
|
3389
4645
|
);
|
|
3390
|
-
var loadDatabaseSync2 =
|
|
3391
|
-
const sqliteModule = yield*
|
|
4646
|
+
var loadDatabaseSync2 = Effect21.fn("OpenCodeIo.loadDatabaseSync")(function* () {
|
|
4647
|
+
const sqliteModule = yield* Effect21.tryPromise({
|
|
3392
4648
|
try: () => import(NODE_SQLITE_SPECIFIER2),
|
|
3393
4649
|
catch: toOpenCodeIoError("load")
|
|
3394
4650
|
});
|
|
3395
4651
|
return sqliteModule.DatabaseSync;
|
|
3396
4652
|
});
|
|
3397
|
-
var openDatabase2 = (path, DatabaseSync) =>
|
|
4653
|
+
var openDatabase2 = (path, DatabaseSync) => Effect21.try({
|
|
3398
4654
|
try: () => new DatabaseSync(path, { readOnly: true, timeout: 1e3 }),
|
|
3399
4655
|
catch: toOpenCodeIoError("open", path)
|
|
3400
4656
|
});
|
|
3401
|
-
var closeDatabase2 = (path, db) =>
|
|
4657
|
+
var closeDatabase2 = (path, db) => Effect21.try({
|
|
3402
4658
|
try: () => {
|
|
3403
4659
|
db.close();
|
|
3404
4660
|
},
|
|
@@ -3423,7 +4679,7 @@ function readSessionPaths(db) {
|
|
|
3423
4679
|
}
|
|
3424
4680
|
return paths;
|
|
3425
4681
|
}
|
|
3426
|
-
var querySqliteRows = (path, db, cutoff) =>
|
|
4682
|
+
var querySqliteRows = (path, db, cutoff) => Effect21.try({
|
|
3427
4683
|
try: () => {
|
|
3428
4684
|
const statement = db.prepare(SESSION_MESSAGE_QUERY);
|
|
3429
4685
|
return {
|
|
@@ -3433,9 +4689,9 @@ var querySqliteRows = (path, db, cutoff) => Effect20.try({
|
|
|
3433
4689
|
},
|
|
3434
4690
|
catch: toOpenCodeIoError("query", path)
|
|
3435
4691
|
});
|
|
3436
|
-
var readSqlite =
|
|
4692
|
+
var readSqlite = Effect21.fn("OpenCodeIo.readSqlite")(function* (path, cutoff) {
|
|
3437
4693
|
const DatabaseSync = yield* loadDatabaseSync2();
|
|
3438
|
-
return yield*
|
|
4694
|
+
return yield* Effect21.acquireUseRelease(
|
|
3439
4695
|
openDatabase2(path, DatabaseSync),
|
|
3440
4696
|
(db) => querySqliteRows(path, db, cutoff),
|
|
3441
4697
|
(db) => closeDatabase2(path, db)
|
|
@@ -3448,17 +4704,17 @@ var makeOpenCodeIo = (fs) => ({
|
|
|
3448
4704
|
});
|
|
3449
4705
|
var OpenCodeIoLive = Layer9.effect(
|
|
3450
4706
|
OpenCodeIo,
|
|
3451
|
-
|
|
4707
|
+
Effect21.gen(function* () {
|
|
3452
4708
|
const fs = yield* FileSystem6.FileSystem;
|
|
3453
4709
|
return OpenCodeIo.of(makeOpenCodeIo(fs));
|
|
3454
4710
|
})
|
|
3455
4711
|
).pipe(Layer9.provide(NodeFileSystem5.layer));
|
|
3456
4712
|
|
|
3457
4713
|
// ../../packages/parsers/src/opencode/index.ts
|
|
3458
|
-
import { Effect as
|
|
4714
|
+
import { Effect as Effect23, Stream as Stream14 } from "effect";
|
|
3459
4715
|
|
|
3460
4716
|
// ../../packages/parsers/src/opencode/bucketize.ts
|
|
3461
|
-
import { Effect as
|
|
4717
|
+
import { Effect as Effect22 } from "effect";
|
|
3462
4718
|
|
|
3463
4719
|
// ../../packages/parsers/src/opencode/events.ts
|
|
3464
4720
|
function isObject3(value) {
|
|
@@ -3679,7 +4935,7 @@ function* finalizedBuckets(accumulators) {
|
|
|
3679
4935
|
function parseJson(value) {
|
|
3680
4936
|
return typeof value === "string" ? JSON.parse(value) : value;
|
|
3681
4937
|
}
|
|
3682
|
-
var bucketizeOpenCodeDatabaseEffect =
|
|
4938
|
+
var bucketizeOpenCodeDatabaseEffect = Effect22.fn("bucketizeOpenCodeDatabaseEffect")(
|
|
3683
4939
|
function* (path, cutoff, logger, excludePath) {
|
|
3684
4940
|
const io = yield* OpenCodeIo;
|
|
3685
4941
|
const rows = yield* io.readSqlite(path, cutoff);
|
|
@@ -3702,7 +4958,7 @@ var bucketizeOpenCodeDatabaseEffect = Effect21.fn("bucketizeOpenCodeDatabaseEffe
|
|
|
3702
4958
|
return [...finalizedBuckets(accumulators)];
|
|
3703
4959
|
}
|
|
3704
4960
|
);
|
|
3705
|
-
var bucketizeOpenCodeJsonFileEffect =
|
|
4961
|
+
var bucketizeOpenCodeJsonFileEffect = Effect22.fn("bucketizeOpenCodeJsonFileEffect")(
|
|
3706
4962
|
function* (path) {
|
|
3707
4963
|
const io = yield* OpenCodeIo;
|
|
3708
4964
|
const parsed = yield* io.readJsonFile(path);
|
|
@@ -3717,23 +4973,23 @@ var bucketizeOpenCodeJsonFileEffect = Effect21.fn("bucketizeOpenCodeJsonFileEffe
|
|
|
3717
4973
|
);
|
|
3718
4974
|
|
|
3719
4975
|
// ../../packages/parsers/src/opencode/index.ts
|
|
3720
|
-
var discoverOpenCodeSourcesEffect =
|
|
4976
|
+
var discoverOpenCodeSourcesEffect = Effect23.fn("discoverOpenCodeSourcesEffect")(function* (root, cutoff) {
|
|
3721
4977
|
const io = yield* OpenCodeIo;
|
|
3722
4978
|
return yield* io.discoverSources(root, cutoff);
|
|
3723
4979
|
});
|
|
3724
|
-
var parseOpenCodeEffect =
|
|
4980
|
+
var parseOpenCodeEffect = Effect23.fn(
|
|
3725
4981
|
"OpenCodeParser.parseOpenCodeEffect"
|
|
3726
|
-
)((opts) =>
|
|
3727
|
-
var parseOpenCodeStream = (opts) =>
|
|
3728
|
-
|
|
4982
|
+
)((opts) => Stream14.runCollect(parseOpenCodeStream(opts)));
|
|
4983
|
+
var parseOpenCodeStream = (opts) => Stream14.unwrap(
|
|
4984
|
+
Effect23.gen(function* () {
|
|
3729
4985
|
const logger = opts.logger ?? silentLogger;
|
|
3730
4986
|
const root = opts.root ?? resolveOpenCodeRoot();
|
|
3731
4987
|
const sources = yield* discoverOpenCodeSourcesEffect(root, opts.cutoff);
|
|
3732
4988
|
if (sources._tag === "Missing") {
|
|
3733
|
-
return
|
|
4989
|
+
return Stream14.empty;
|
|
3734
4990
|
}
|
|
3735
4991
|
if (sources._tag === "JsonFile") {
|
|
3736
|
-
return openCodeJsonFileStreams(
|
|
4992
|
+
return openCodeJsonFileStreams(Stream14.succeed(sources.path));
|
|
3737
4993
|
}
|
|
3738
4994
|
if (sources._tag === "DatabaseFiles") {
|
|
3739
4995
|
return openCodeDatabaseStreams(sources.paths, opts, logger);
|
|
@@ -3742,28 +4998,28 @@ var parseOpenCodeStream = (opts) => Stream13.unwrap(
|
|
|
3742
4998
|
})
|
|
3743
4999
|
);
|
|
3744
5000
|
function openCodeDatabaseStreams(paths, opts, logger) {
|
|
3745
|
-
let stream =
|
|
5001
|
+
let stream = Stream14.empty;
|
|
3746
5002
|
for (const dbPath of paths) {
|
|
3747
5003
|
stream = stream.pipe(
|
|
3748
|
-
|
|
3749
|
-
|
|
5004
|
+
Stream14.concat(
|
|
5005
|
+
Stream14.fromIterableEffect(bucketizeOpenCodeDatabaseEffect(dbPath, opts.cutoff, logger, opts.excludePath))
|
|
3750
5006
|
)
|
|
3751
5007
|
);
|
|
3752
5008
|
}
|
|
3753
5009
|
return stream;
|
|
3754
5010
|
}
|
|
3755
5011
|
function openCodeJsonFileStreams(paths) {
|
|
3756
|
-
return paths.pipe(
|
|
5012
|
+
return paths.pipe(Stream14.flatMap((file) => Stream14.fromIterableEffect(bucketizeOpenCodeJsonFileEffect(file))));
|
|
3757
5013
|
}
|
|
3758
5014
|
|
|
3759
5015
|
// ../../packages/parsers/src/pi/index.ts
|
|
3760
5016
|
import { basename as basename3, join as join7 } from "path";
|
|
3761
|
-
import { Effect as
|
|
5017
|
+
import { Effect as Effect25, Layer as Layer10, Stream as Stream16 } from "effect";
|
|
3762
5018
|
|
|
3763
5019
|
// ../../packages/parsers/src/pi/bucketize.ts
|
|
3764
5020
|
import { createReadStream as createReadStream3 } from "fs";
|
|
3765
5021
|
import { createInterface as createInterface4 } from "readline";
|
|
3766
|
-
import { Cause as Cause3, Effect as
|
|
5022
|
+
import { Cause as Cause3, Effect as Effect24, Queue as Queue3, Stream as Stream15 } from "effect";
|
|
3767
5023
|
|
|
3768
5024
|
// ../../packages/parsers/src/pi/events.ts
|
|
3769
5025
|
function isObject4(value) {
|
|
@@ -3842,9 +5098,9 @@ function key5(hourUtc, model) {
|
|
|
3842
5098
|
return `${hourUtc.toISOString()}|${model}`;
|
|
3843
5099
|
}
|
|
3844
5100
|
function lineStreamFromFile3(path) {
|
|
3845
|
-
return
|
|
3846
|
-
(queue) =>
|
|
3847
|
-
|
|
5101
|
+
return Stream15.callback(
|
|
5102
|
+
(queue) => Effect24.acquireRelease(
|
|
5103
|
+
Effect24.sync(() => {
|
|
3848
5104
|
let done = false;
|
|
3849
5105
|
const stream = createReadStream3(path, { encoding: "utf8" });
|
|
3850
5106
|
const rl = createInterface4({ input: stream, crlfDelay: Number.POSITIVE_INFINITY });
|
|
@@ -3881,7 +5137,7 @@ function lineStreamFromFile3(path) {
|
|
|
3881
5137
|
}
|
|
3882
5138
|
};
|
|
3883
5139
|
}),
|
|
3884
|
-
(resource) =>
|
|
5140
|
+
(resource) => Effect24.sync(() => resource.close())
|
|
3885
5141
|
)
|
|
3886
5142
|
);
|
|
3887
5143
|
}
|
|
@@ -3899,6 +5155,33 @@ function modelId2(provider, model) {
|
|
|
3899
5155
|
}
|
|
3900
5156
|
return `${trimmedProvider}/${trimmedModel}`;
|
|
3901
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
|
+
}
|
|
3902
5185
|
function normalizeProvider(provider) {
|
|
3903
5186
|
return provider === "openai-codex" ? "openai" : provider;
|
|
3904
5187
|
}
|
|
@@ -3915,53 +5198,134 @@ function timestampMs2(value) {
|
|
|
3915
5198
|
}
|
|
3916
5199
|
return Math.abs(n) < UNIX_MILLISECONDS_THRESHOLD ? n * MILLISECONDS_PER_SECOND2 : n;
|
|
3917
5200
|
}
|
|
3918
|
-
function
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
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
|
+
}
|
|
3933
5235
|
}
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === 0 && cacheCreateTokens === 0) {
|
|
5236
|
+
return latest;
|
|
5237
|
+
}
|
|
5238
|
+
function makeHermesBucket(options) {
|
|
5239
|
+
if (!hasHermesUsage(options.usage)) {
|
|
3939
5240
|
return void 0;
|
|
3940
5241
|
}
|
|
3941
|
-
const startedAt = new Date(startedAtMs);
|
|
5242
|
+
const startedAt = new Date(options.startedAtMs);
|
|
3942
5243
|
return {
|
|
3943
5244
|
harness: "hermes",
|
|
3944
|
-
model,
|
|
3945
|
-
hourUtc: startOfUtcHour(
|
|
3946
|
-
inputTokens,
|
|
3947
|
-
outputTokens,
|
|
3948
|
-
cacheReadTokens,
|
|
3949
|
-
cacheCreateTokens,
|
|
3950
|
-
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}`]),
|
|
3951
5252
|
sessionStartedAt: startedAt,
|
|
3952
|
-
sessionLastSeenAt: new Date(lastSeenAtMs)
|
|
5253
|
+
sessionLastSeenAt: new Date(options.lastSeenAtMs)
|
|
3953
5254
|
};
|
|
3954
5255
|
}
|
|
3955
|
-
function
|
|
5256
|
+
function bucketizeHermesModelUsage(options) {
|
|
3956
5257
|
const buckets = [];
|
|
3957
|
-
|
|
3958
|
-
|
|
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
|
+
});
|
|
3959
5278
|
if (bucket !== void 0) {
|
|
3960
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);
|
|
3961
5318
|
}
|
|
3962
5319
|
}
|
|
3963
5320
|
return buckets;
|
|
3964
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
|
+
}
|
|
3965
5329
|
function makePiStyleFileAccumulator(options) {
|
|
3966
5330
|
return {
|
|
3967
5331
|
buckets: /* @__PURE__ */ new Map(),
|
|
@@ -4054,12 +5418,11 @@ function accumulatePiStyleLine(context, acc, raw) {
|
|
|
4054
5418
|
existing.cacheCreateTokens += cacheCreateTokens;
|
|
4055
5419
|
return acc;
|
|
4056
5420
|
}
|
|
4057
|
-
function
|
|
5421
|
+
function bucketsFromAccumulator2(path, harness, acc) {
|
|
4058
5422
|
if (acc.excluded || acc.sessionStartedAt === void 0 || acc.sessionLastSeenAt === void 0) {
|
|
4059
5423
|
return [];
|
|
4060
5424
|
}
|
|
4061
|
-
const sessionStartedAt = acc
|
|
4062
|
-
const sessionLastSeenAt = acc.sessionLastSeenAt;
|
|
5425
|
+
const { sessionLastSeenAt, sessionStartedAt } = acc;
|
|
4063
5426
|
const sessionIds = /* @__PURE__ */ new Set([path]);
|
|
4064
5427
|
return Array.from(acc.buckets.values(), (bucket) => ({
|
|
4065
5428
|
harness,
|
|
@@ -4076,17 +5439,17 @@ function bucketsFromAccumulator3(path, harness, acc) {
|
|
|
4076
5439
|
}
|
|
4077
5440
|
function bucketizePiStyleFileStream(path, harness, logger, options = {}) {
|
|
4078
5441
|
const context = { logger, options, path };
|
|
4079
|
-
return
|
|
5442
|
+
return Stream15.unwrap(
|
|
4080
5443
|
lineStreamFromFile3(path).pipe(
|
|
4081
|
-
|
|
5444
|
+
Stream15.runFold(
|
|
4082
5445
|
() => makePiStyleFileAccumulator(options),
|
|
4083
5446
|
(acc, raw) => accumulatePiStyleLine(context, acc, raw)
|
|
4084
5447
|
),
|
|
4085
|
-
|
|
5448
|
+
Effect24.map((acc) => Stream15.fromIterable(bucketsFromAccumulator2(path, harness, acc)))
|
|
4086
5449
|
)
|
|
4087
5450
|
);
|
|
4088
5451
|
}
|
|
4089
|
-
var bucketizeHermesDatabaseEffect =
|
|
5452
|
+
var bucketizeHermesDatabaseEffect = Effect24.fn(
|
|
4090
5453
|
"bucketizeHermesDatabaseEffect"
|
|
4091
5454
|
)(function* (path, cutoff, excludePath) {
|
|
4092
5455
|
const sqlite = yield* HermesSqlite;
|
|
@@ -4095,14 +5458,14 @@ var bucketizeHermesDatabaseEffect = Effect23.fn(
|
|
|
4095
5458
|
});
|
|
4096
5459
|
|
|
4097
5460
|
// ../../packages/parsers/src/pi/index.ts
|
|
4098
|
-
var isFileBestEffortEffect = (path) => pathKindEffect(path).pipe(
|
|
5461
|
+
var isFileBestEffortEffect = (path) => pathKindEffect(path).pipe(Effect25.map((kind) => kind === "file"));
|
|
4099
5462
|
function bucketizePiStyleSourcesStream(files, harness, logger, options) {
|
|
4100
5463
|
return files.pipe(
|
|
4101
|
-
|
|
5464
|
+
Stream16.flatMap((file) => bucketizePiStyleFileStream(file, harness, logger, options), { concurrency: 1 })
|
|
4102
5465
|
);
|
|
4103
5466
|
}
|
|
4104
|
-
var parsePiStyleStream = (opts, harness, defaultRoot) =>
|
|
4105
|
-
|
|
5467
|
+
var parsePiStyleStream = (opts, harness, defaultRoot) => Stream16.unwrap(
|
|
5468
|
+
Effect25.gen(function* () {
|
|
4106
5469
|
const logger = opts.logger ?? silentLogger;
|
|
4107
5470
|
const root = opts.root ?? defaultRoot();
|
|
4108
5471
|
const kind = yield* pathKindEffectStrict(root);
|
|
@@ -4120,25 +5483,23 @@ var parsePiStyleStream = (opts, harness, defaultRoot) => Stream15.unwrap(
|
|
|
4120
5483
|
});
|
|
4121
5484
|
})
|
|
4122
5485
|
);
|
|
4123
|
-
var parsePiEffect =
|
|
5486
|
+
var parsePiEffect = Effect25.fn("PiParser.parsePiEffect")((opts) => Stream16.runCollect(parsePiStream(opts)));
|
|
4124
5487
|
var parsePiStream = (opts) => parsePiStyleStream(opts, "pi", resolvePiRoot);
|
|
4125
5488
|
var parserLive = Layer10.mergeAll(FileWalkerLive, HermesSqliteLive);
|
|
4126
|
-
var parseHermesEffect =
|
|
5489
|
+
var parseHermesEffect = Effect25.fn(
|
|
4127
5490
|
"HermesParser.parseHermesEffect"
|
|
4128
|
-
)((opts) =>
|
|
4129
|
-
var parseHermesStream = (opts) =>
|
|
4130
|
-
|
|
5491
|
+
)((opts) => Stream16.runCollect(parseHermesStream(opts)));
|
|
5492
|
+
var parseHermesStream = (opts) => Stream16.unwrap(
|
|
5493
|
+
Effect25.gen(function* () {
|
|
4131
5494
|
const logger = opts.logger ?? silentLogger;
|
|
4132
5495
|
const root = opts.root ?? resolveHermesRoot();
|
|
4133
5496
|
const kind = yield* pathKindEffectStrict(root);
|
|
4134
5497
|
if (kind === "missing") {
|
|
4135
|
-
return asHermesStream(
|
|
5498
|
+
return asHermesStream(Stream16.empty);
|
|
4136
5499
|
}
|
|
4137
5500
|
if (kind === "file") {
|
|
4138
5501
|
if (basename3(root).endsWith(".db")) {
|
|
4139
|
-
return asHermesStream(
|
|
4140
|
-
Stream15.fromIterableEffect(bucketizeHermesDatabaseEffect(root, opts.cutoff, opts.excludePath))
|
|
4141
|
-
);
|
|
5502
|
+
return asHermesStream(collectHermesDatabaseStream(root, logger, opts));
|
|
4142
5503
|
}
|
|
4143
5504
|
return asHermesStream(
|
|
4144
5505
|
bucketizePiStyleFileStream(root, "hermes", logger, { cutoff: opts.cutoff, excludePath: opts.excludePath })
|
|
@@ -4146,9 +5507,7 @@ var parseHermesStream = (opts) => Stream15.unwrap(
|
|
|
4146
5507
|
}
|
|
4147
5508
|
const stateDb = join7(root, "state.db");
|
|
4148
5509
|
if (yield* isFileBestEffortEffect(stateDb)) {
|
|
4149
|
-
return asHermesStream(
|
|
4150
|
-
Stream15.fromIterableEffect(bucketizeHermesDatabaseEffect(stateDb, opts.cutoff, opts.excludePath))
|
|
4151
|
-
);
|
|
5510
|
+
return asHermesStream(collectHermesDatabaseStream(stateDb, logger, opts));
|
|
4152
5511
|
}
|
|
4153
5512
|
const sessionsRoot2 = join7(root, "sessions");
|
|
4154
5513
|
if ((yield* pathKindEffect(sessionsRoot2)) === "directory") {
|
|
@@ -4164,8 +5523,20 @@ var parseHermesStream = (opts) => Stream15.unwrap(
|
|
|
4164
5523
|
function asHermesStream(stream) {
|
|
4165
5524
|
return stream;
|
|
4166
5525
|
}
|
|
4167
|
-
|
|
4168
|
-
|
|
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(() => {
|
|
4169
5540
|
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
4170
5541
|
return bucketizePiStyleSourcesStream(walkStreamStrict(root, opts.cutoff), "hermes", logger, {
|
|
4171
5542
|
cutoff: opts.cutoff,
|
|
@@ -4176,7 +5547,7 @@ var collectHermesPiStyleDirectoryStream = (root, logger, opts) => Stream15.unwra
|
|
|
4176
5547
|
);
|
|
4177
5548
|
|
|
4178
5549
|
// src/sync/parser-factory.ts
|
|
4179
|
-
import { Effect as
|
|
5550
|
+
import { Effect as Effect26, Layer as Layer11, Stream as Stream17 } from "effect";
|
|
4180
5551
|
var GLOB_PATTERN = /[*?[\]{}]/u;
|
|
4181
5552
|
var HermesParserLive = Layer11.mergeAll(FileWalkerLive, HermesSqliteLive);
|
|
4182
5553
|
function sessionsRoot(agentDir, sessionDir, fallbackAgentDir) {
|
|
@@ -4241,7 +5612,7 @@ function createExcludeMatcher(options) {
|
|
|
4241
5612
|
};
|
|
4242
5613
|
}
|
|
4243
5614
|
function resolveParserSourcePlanEffect(options = {}) {
|
|
4244
|
-
return
|
|
5615
|
+
return Effect26.gen(function* () {
|
|
4245
5616
|
const sources = yield* ParserSources;
|
|
4246
5617
|
const home = yield* sources.homeDir;
|
|
4247
5618
|
const env = options.env ?? (yield* sources.cliEnv);
|
|
@@ -4280,49 +5651,43 @@ function resolveParserSourcePlanEffect(options = {}) {
|
|
|
4280
5651
|
});
|
|
4281
5652
|
}
|
|
4282
5653
|
function provideFileWalker(stream) {
|
|
4283
|
-
return stream.pipe(
|
|
4284
|
-
}
|
|
4285
|
-
function
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
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
|
+
);
|
|
4289
5666
|
}
|
|
4290
|
-
|
|
5667
|
+
if (plan.codexArchivedSourceExisted) {
|
|
5668
|
+
return provideFileWalker(
|
|
5669
|
+
parseCodexStream({ cutoff, excludePath, root: plan.codexArchivedRoot })
|
|
5670
|
+
);
|
|
5671
|
+
}
|
|
5672
|
+
return Stream17.empty;
|
|
4291
5673
|
}
|
|
4292
5674
|
function buildFactoryFromPlan(plan) {
|
|
4293
5675
|
const excludePath = createExcludeMatcher({ exclude: plan.exclude, home: plan.home });
|
|
4294
|
-
return (ctx) => {
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
}
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
parseCodexStream({ cutoff: ctx.cutoff, excludePath, root: plan.codexArchivedRoot })
|
|
4307
|
-
)
|
|
4308
|
-
);
|
|
4309
|
-
}
|
|
4310
|
-
return {
|
|
4311
|
-
anySourceExisted: plan.anySourceExisted,
|
|
4312
|
-
codex: concatParserStreams(codexStreams),
|
|
4313
|
-
claudeCode: plan.claudeSourceExisted ? provideFileWalker(parseClaudeStream({ cutoff: ctx.cutoff, excludePath, root: plan.claudeRoot })) : Stream16.empty,
|
|
4314
|
-
pi: plan.piSourceExisted ? provideFileWalker(parsePiStream({ cutoff: ctx.cutoff, excludePath, root: plan.piRoot })) : Stream16.empty,
|
|
4315
|
-
hermes: plan.hermesSourceExisted ? parseHermesStream({ cutoff: ctx.cutoff, excludePath, root: plan.hermesRoot }).pipe(
|
|
4316
|
-
Stream16.provide(HermesParserLive)
|
|
4317
|
-
) : Stream16.empty,
|
|
4318
|
-
opencode: plan.opencodeSourceExisted ? parseOpenCodeStream({ cutoff: ctx.cutoff, excludePath, root: plan.opencodeRoot }).pipe(
|
|
4319
|
-
Stream16.provide(OpenCodeIoLive)
|
|
4320
|
-
) : Stream16.empty
|
|
4321
|
-
};
|
|
4322
|
-
};
|
|
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
|
+
});
|
|
4323
5688
|
}
|
|
4324
5689
|
function buildRealParserFactoryEffect(options = {}) {
|
|
4325
|
-
return
|
|
5690
|
+
return Effect26.map(resolveParserSourcePlanEffect(options), buildFactoryFromPlan);
|
|
4326
5691
|
}
|
|
4327
5692
|
|
|
4328
5693
|
// src/index.ts
|
|
@@ -4356,8 +5721,40 @@ function parseHarnessFilter(value) {
|
|
|
4356
5721
|
}
|
|
4357
5722
|
throw new SyncError(EXIT.invalidArguments, `error: --harness must be one of ${SUPPORTED_HARNESSES.join("|")}`);
|
|
4358
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
|
+
}
|
|
4359
5756
|
function syncCommandErrorExitCode(err) {
|
|
4360
|
-
return
|
|
5757
|
+
return Effect27.gen(function* () {
|
|
4361
5758
|
const console = yield* CliConsole;
|
|
4362
5759
|
if (err instanceof CliConfigError) {
|
|
4363
5760
|
yield* console.writeStderrLine(err.message);
|
|
@@ -4372,7 +5769,7 @@ function syncCommandErrorExitCode(err) {
|
|
|
4372
5769
|
});
|
|
4373
5770
|
}
|
|
4374
5771
|
function setupCommandErrorExitCode(err) {
|
|
4375
|
-
return
|
|
5772
|
+
return Effect27.gen(function* () {
|
|
4376
5773
|
const console = yield* CliConsole;
|
|
4377
5774
|
if (err instanceof CliConfigError || err instanceof SetupError) {
|
|
4378
5775
|
yield* console.writeStderrLine(err.message);
|
|
@@ -4384,26 +5781,26 @@ function setupCommandErrorExitCode(err) {
|
|
|
4384
5781
|
}
|
|
4385
5782
|
function recoverCommandExitCode(effect) {
|
|
4386
5783
|
return effect.pipe(
|
|
4387
|
-
|
|
4388
|
-
|
|
5784
|
+
Effect27.catch((err) => syncCommandErrorExitCode(err)),
|
|
5785
|
+
Effect27.catchDefect((err) => syncCommandErrorExitCode(err))
|
|
4389
5786
|
);
|
|
4390
5787
|
}
|
|
4391
5788
|
function recoverSetupExitCode(effect) {
|
|
4392
5789
|
return effect.pipe(
|
|
4393
|
-
|
|
4394
|
-
|
|
5790
|
+
Effect27.catch((err) => setupCommandErrorExitCode(err)),
|
|
5791
|
+
Effect27.catchDefect((err) => setupCommandErrorExitCode(err))
|
|
4395
5792
|
);
|
|
4396
5793
|
}
|
|
4397
5794
|
function exitCodeToCommandEffect(exitCode) {
|
|
4398
|
-
return exitCode === EXIT.success ?
|
|
5795
|
+
return exitCode === EXIT.success ? Effect27.void : Effect27.fail(new CliExit(exitCode));
|
|
4399
5796
|
}
|
|
4400
5797
|
function runSyncCommand(raw) {
|
|
4401
5798
|
return recoverCommandExitCode(
|
|
4402
|
-
|
|
5799
|
+
Effect27.gen(function* () {
|
|
4403
5800
|
const configReader = yield* CliConfigReader;
|
|
4404
5801
|
const console = yield* CliConsole;
|
|
4405
5802
|
const env = yield* configReader.read();
|
|
4406
|
-
const config = yield*
|
|
5803
|
+
const config = yield* Effect27.try({
|
|
4407
5804
|
try: () => resolveSyncConfig(
|
|
4408
5805
|
{
|
|
4409
5806
|
api: optionalValue(raw.api),
|
|
@@ -4417,10 +5814,14 @@ function runSyncCommand(raw) {
|
|
|
4417
5814
|
catch: (err) => err
|
|
4418
5815
|
});
|
|
4419
5816
|
const { device, token } = config;
|
|
4420
|
-
const harness = yield*
|
|
5817
|
+
const harness = yield* Effect27.try({
|
|
4421
5818
|
try: () => parseHarnessFilter(optionalValue(raw.harness)),
|
|
4422
5819
|
catch: (err) => err
|
|
4423
5820
|
});
|
|
5821
|
+
const replacement = yield* Effect27.try({
|
|
5822
|
+
try: () => parseReplacementFlags(raw),
|
|
5823
|
+
catch: (err) => err
|
|
5824
|
+
});
|
|
4424
5825
|
if (!(token && device)) {
|
|
4425
5826
|
yield* console.writeStderrLine(
|
|
4426
5827
|
"error: --token and --device are required (or set ~/.oopst.yml / OOPST_TOKEN / OOPST_DEVICE)"
|
|
@@ -4428,7 +5829,7 @@ function runSyncCommand(raw) {
|
|
|
4428
5829
|
return EXIT.invalidArguments;
|
|
4429
5830
|
}
|
|
4430
5831
|
const factory = yield* buildRealParserFactoryEffect({ env, exclude: config.exclude }).pipe(
|
|
4431
|
-
|
|
5832
|
+
Effect27.provide(ParserSourcesLive)
|
|
4432
5833
|
);
|
|
4433
5834
|
let sourcesExistedHint = false;
|
|
4434
5835
|
const parserFactory = (ctx) => {
|
|
@@ -4442,6 +5843,7 @@ function runSyncCommand(raw) {
|
|
|
4442
5843
|
serverUrl: config.serverUrl,
|
|
4443
5844
|
lookbackDays: config.lookbackDays,
|
|
4444
5845
|
harness,
|
|
5846
|
+
replacement,
|
|
4445
5847
|
dryRun: raw.dryRun,
|
|
4446
5848
|
verbose: raw.verbose,
|
|
4447
5849
|
color: yield* console.stdoutIsTTY,
|
|
@@ -4453,16 +5855,16 @@ function runSyncCommand(raw) {
|
|
|
4453
5855
|
});
|
|
4454
5856
|
return result.exitCode;
|
|
4455
5857
|
})
|
|
4456
|
-
).pipe(
|
|
5858
|
+
).pipe(Effect27.flatMap(exitCodeToCommandEffect));
|
|
4457
5859
|
}
|
|
4458
5860
|
function runSetupCommand(raw) {
|
|
4459
5861
|
return recoverSetupExitCode(
|
|
4460
|
-
|
|
5862
|
+
Effect27.gen(function* () {
|
|
4461
5863
|
const console = yield* CliConsole;
|
|
4462
5864
|
const metadata = yield* CliPackageMetadata;
|
|
4463
5865
|
const cliProcess = yield* CliProcess;
|
|
4464
5866
|
const nodePath = yield* cliProcess.execPath;
|
|
4465
|
-
const result = yield*
|
|
5867
|
+
const result = yield* Effect27.tryPromise({
|
|
4466
5868
|
try: () => runSetup({
|
|
4467
5869
|
api: optionalValue(raw.api),
|
|
4468
5870
|
server: optionalValue(raw.server),
|
|
@@ -4475,7 +5877,7 @@ function runSetupCommand(raw) {
|
|
|
4475
5877
|
});
|
|
4476
5878
|
return result.exitCode;
|
|
4477
5879
|
})
|
|
4478
|
-
).pipe(
|
|
5880
|
+
).pipe(Effect27.flatMap(exitCodeToCommandEffect));
|
|
4479
5881
|
}
|
|
4480
5882
|
var apiFlag = Flag.string("api").pipe(
|
|
4481
5883
|
Flag.withDescription("API base URL (or server.api / OOPST_SERVER)"),
|
|
@@ -4498,6 +5900,22 @@ var harnessFlag = Flag.string("harness").pipe(
|
|
|
4498
5900
|
Flag.withDescription("Restrict to codex|claude_code|pi|hermes|opencode"),
|
|
4499
5901
|
Flag.optional
|
|
4500
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
|
+
);
|
|
4501
5919
|
var dryRunFlag = Flag.boolean("dry-run").pipe(
|
|
4502
5920
|
Flag.withDescription("Parse and print without uploading"),
|
|
4503
5921
|
Flag.withDefault(false)
|
|
@@ -4513,6 +5931,10 @@ function buildCli() {
|
|
|
4513
5931
|
server: serverFlag,
|
|
4514
5932
|
lookbackDays: lookbackDaysFlag,
|
|
4515
5933
|
harness: harnessFlag,
|
|
5934
|
+
replaceStart: replaceStartFlag,
|
|
5935
|
+
replaceEnd: replaceEndFlag,
|
|
5936
|
+
replaceHarnesses: replaceHarnessesFlag,
|
|
5937
|
+
confirmReplacement: confirmReplacementFlag,
|
|
4516
5938
|
dryRun: dryRunFlag,
|
|
4517
5939
|
verbose: verboseFlag
|
|
4518
5940
|
},
|
|
@@ -4532,10 +5954,10 @@ function buildCli() {
|
|
|
4532
5954
|
);
|
|
4533
5955
|
}
|
|
4534
5956
|
function runCli() {
|
|
4535
|
-
return
|
|
5957
|
+
return Effect27.gen(function* () {
|
|
4536
5958
|
const metadata = yield* CliPackageMetadata;
|
|
4537
5959
|
yield* buildCli().pipe(Command.run({ version: metadata.version }));
|
|
4538
|
-
}).pipe(
|
|
5960
|
+
}).pipe(Effect27.provide(CliRuntimeLayer));
|
|
4539
5961
|
}
|
|
4540
5962
|
if (isEntrypoint()) {
|
|
4541
5963
|
runCli().pipe(NodeRuntime.runMain);
|