@effect-agent/storage-sqlite 0.1.0-beta.24 → 0.1.0-beta.25
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.mjs +33 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/sqlite-conversation-store.ts +20 -15
- package/src/sqlite-ledger.ts +62 -31
- package/src/sqlite-storage-failpoint.ts +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1062,6 +1062,9 @@ const OffsetText = Schema.String.check(Schema.isMaxLength(4 * 1024));
|
|
|
1062
1062
|
const SQLITE_OFFSET_PREFIX = "effect-agent-sqlite@1:";
|
|
1063
1063
|
const ZERO_CANONICAL_SEQUENCE = Schema.decodeSync(CanonicalSequence)(0);
|
|
1064
1064
|
const isDigest = Schema.is(Digest);
|
|
1065
|
+
const isSqliteFenceRejected = Schema.is(SqliteFenceRejected);
|
|
1066
|
+
const isSqliteAppendConflict = Schema.is(SqliteAppendConflict);
|
|
1067
|
+
const isSqliteCheckpointConflict = Schema.is(SqliteCheckpointConflict);
|
|
1065
1068
|
const storeError = (operation, error) => ConversationStoreError.make({
|
|
1066
1069
|
cause: error,
|
|
1067
1070
|
operation,
|
|
@@ -1290,8 +1293,8 @@ const makeServices$1 = Effect.fn("SqliteConversationStore.makeServices")(functio
|
|
|
1290
1293
|
}).pipe(Effect.mapError((error) => schemaStoreError("encode canonical append", error)));
|
|
1291
1294
|
yield* hitFailpoint("append:before");
|
|
1292
1295
|
const result = yield* journal.append(rawRequest).pipe(Effect.mapError((error) => {
|
|
1293
|
-
if (error
|
|
1294
|
-
if (error
|
|
1296
|
+
if (isSqliteFenceRejected(error)) return mapFence(validated.conversationId, error);
|
|
1297
|
+
if (isSqliteAppendConflict(error)) return error.actualTailSequence !== void 0 && isDigest(error.actualTailDigest) ? AppendConflict.make({
|
|
1295
1298
|
conversationId: validated.conversationId,
|
|
1296
1299
|
batchId: validated.batch.batchId,
|
|
1297
1300
|
reason: error.reason,
|
|
@@ -1392,7 +1395,7 @@ const makeServices$1 = Effect.fn("SqliteConversationStore.makeServices")(functio
|
|
|
1392
1395
|
checkpointJson
|
|
1393
1396
|
});
|
|
1394
1397
|
yield* hitFailpoint("save-checkpoint:before");
|
|
1395
|
-
yield* journal.saveCheckpoint(raw).pipe(Effect.mapError((error) => error
|
|
1398
|
+
yield* journal.saveCheckpoint(raw).pipe(Effect.mapError((error) => isSqliteCheckpointConflict(error) ? CheckpointRejected.make({
|
|
1396
1399
|
conversationId: validated.checkpoint.conversationId,
|
|
1397
1400
|
reason: "digest-mismatch"
|
|
1398
1401
|
}) : storeError("save checkpoint", error)));
|
|
@@ -1452,10 +1455,7 @@ const storageFailpointLayer = (options) => options.failpoint === void 0 ? Sqlite
|
|
|
1452
1455
|
* A composition-root convenience Layer for canonical Conversations. Durable accepted work is
|
|
1453
1456
|
* served by the separate SubmissionLedger port.
|
|
1454
1457
|
*/
|
|
1455
|
-
const layer = (options) => {
|
|
1456
|
-
const sqlLayer = SqliteClient.layer({ filename: options.filename });
|
|
1457
|
-
return conversationStoreLayer.pipe(Layer.provide(Layer.mergeAll(storageConfigLayer(options), storageFailpointLayer(options), sqlLayer, NodeCrypto.layer)));
|
|
1458
|
-
};
|
|
1458
|
+
const layer = (options) => Layer.unwrap(Effect.map(SqliteStorageConfig, (config) => conversationStoreLayer.pipe(Layer.provide(Layer.mergeAll(Layer.succeed(SqliteStorageConfig)(config), storageFailpointLayer(options), SqliteClient.layer({ filename: options.filename }), NodeCrypto.layer))))).pipe(Layer.provide(storageConfigLayer(options)));
|
|
1459
1459
|
/** Create an adapter-owned resumable observation offset for a known canonical sequence. */
|
|
1460
1460
|
const observationOffsetAt = makeOffset;
|
|
1461
1461
|
//#endregion
|
|
@@ -1465,6 +1465,11 @@ const BoundedIdentifier = Schema.NonEmptyString.check(Schema.isMaxLength(1024));
|
|
|
1465
1465
|
const BoundedTimestamp = Schema.NonEmptyString.check(Schema.isMaxLength(128));
|
|
1466
1466
|
const SCAN_PAGE_SIZE = 256;
|
|
1467
1467
|
const EPOCH_ZERO = Schema.decodeSync(ProducerEpoch)(0);
|
|
1468
|
+
const RESUME_IMMEDIATELY = "resume-immediately";
|
|
1469
|
+
const SUSPENDED = "suspended";
|
|
1470
|
+
const NOT_WAITING = "not-waiting";
|
|
1471
|
+
const STILL_WAITING = "still-waiting";
|
|
1472
|
+
const WOKEN = "woken";
|
|
1468
1473
|
var SubmissionRow = class extends Schema.Class("SubmissionRow")({
|
|
1469
1474
|
submission_id: BoundedIdentifier,
|
|
1470
1475
|
conversation_id: BoundedIdentifier,
|
|
@@ -1617,6 +1622,9 @@ const decodeUnknownResolutionIntent = Schema.decodeUnknownEffect(UnknownResoluti
|
|
|
1617
1622
|
const decodeParentLinkage = Schema.decodeUnknownEffect(ParentLinkage);
|
|
1618
1623
|
const decodeChildReservationSnapshotUnknown = Schema.decodeUnknownEffect(ChildBudgetReservationSnapshot);
|
|
1619
1624
|
const decodeChildAttachmentSnapshot = Schema.decodeUnknownEffect(ChildAttachmentSnapshot);
|
|
1625
|
+
const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
|
|
1626
|
+
const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
|
|
1627
|
+
const isSqliteTransactionFailure = Schema.is(Schema.Union([SqliteStorageError, SqliteWriteContention]));
|
|
1620
1628
|
/** Wrap an adapter-internal failure into the port's LedgerError without erasing its tag. */
|
|
1621
1629
|
const internalFailure = (operation) => (error) => LedgerError.make({
|
|
1622
1630
|
operation,
|
|
@@ -1654,7 +1662,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
|
|
|
1654
1662
|
* acquisition failures surface as LedgerError carrying the typed retryable
|
|
1655
1663
|
* SqliteWriteContention (or SqliteStorageError) as cause.
|
|
1656
1664
|
*/
|
|
1657
|
-
const inWriteTransaction = (operation, effect) => journal.withWriteTransaction(operation)(effect).pipe(Effect.mapError((error) => error
|
|
1665
|
+
const inWriteTransaction = (operation, effect) => journal.withWriteTransaction(operation)(effect).pipe(Effect.mapError((error) => isSqliteTransactionFailure(error) ? internalFailure(operation)(error) : error));
|
|
1658
1666
|
const mintIdentifier = (prefix, operation) => crypto.randomUUIDv7.pipe(Effect.map((uuid) => `${prefix}-${uuid}`), Effect.mapError((error) => internalFailure(operation)(error)));
|
|
1659
1667
|
const currentInstant = Effect.map(Clock.currentTimeMillis, (millis) => ({
|
|
1660
1668
|
millis,
|
|
@@ -2506,7 +2514,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
|
|
|
2506
2514
|
if (validated.reason._tag === "ApprovalPending") {
|
|
2507
2515
|
const decisions = yield* readApprovalDecisions(operation, validated.submissionId);
|
|
2508
2516
|
const decided = new Set(decisions.map((row) => row.tool_call_id));
|
|
2509
|
-
if (validated.reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) return
|
|
2517
|
+
if (validated.reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) return RESUME_IMMEDIATELY;
|
|
2510
2518
|
} else {
|
|
2511
2519
|
let allSettled = true;
|
|
2512
2520
|
for (const child of validated.reason.children) {
|
|
@@ -2516,7 +2524,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
|
|
|
2516
2524
|
break;
|
|
2517
2525
|
}
|
|
2518
2526
|
}
|
|
2519
|
-
if (allSettled) return
|
|
2527
|
+
if (allSettled) return RESUME_IMMEDIATELY;
|
|
2520
2528
|
}
|
|
2521
2529
|
const now = yield* currentInstant;
|
|
2522
2530
|
yield* sql`
|
|
@@ -2531,7 +2539,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
|
|
|
2531
2539
|
DELETE FROM effect_agent_submission_ownership
|
|
2532
2540
|
WHERE submission_id = ${validated.submissionId}
|
|
2533
2541
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2534
|
-
return
|
|
2542
|
+
return SUSPENDED;
|
|
2535
2543
|
}));
|
|
2536
2544
|
yield* hitFailpoint("ledger:suspend:after", operation);
|
|
2537
2545
|
return outcome;
|
|
@@ -2659,12 +2667,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
|
|
|
2659
2667
|
});
|
|
2660
2668
|
}
|
|
2661
2669
|
const existing = (yield* readUnknownResolutions(operation, validated.submissionId)).find((row) => row.tool_call_id === validated.toolCallId);
|
|
2662
|
-
|
|
2670
|
+
const existingIntent = existing === void 0 ? void 0 : yield* unknownResolutionIntentFromRow(operation, existing);
|
|
2671
|
+
if (existingIntent !== void 0 && !equivalentUnknownResolution(existingIntent.resolution, validated.resolution)) return yield* UnknownResolutionConflict.make({
|
|
2663
2672
|
submissionId: validated.submissionId,
|
|
2664
2673
|
toolCallId: validated.toolCallId
|
|
2665
2674
|
});
|
|
2666
2675
|
let resolved;
|
|
2667
|
-
if (
|
|
2676
|
+
if (existingIntent !== void 0) resolved = existingIntent;
|
|
2668
2677
|
else {
|
|
2669
2678
|
const now = yield* currentInstant;
|
|
2670
2679
|
yield* sql`
|
|
@@ -2724,14 +2733,14 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
|
|
|
2724
2733
|
operation,
|
|
2725
2734
|
message: `Child submission ${validated.childSubmissionId} has no recorded settlement.`
|
|
2726
2735
|
});
|
|
2727
|
-
if (parent.state !== "suspended" || parent.suspended_reason_json === null) return
|
|
2736
|
+
if (parent.state !== "suspended" || parent.suspended_reason_json === null) return NOT_WAITING;
|
|
2728
2737
|
const reason = yield* Schema.decodeEffect(Schema.fromJsonString(SuspensionReason))(parent.suspended_reason_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", parent.submission_id, error.message)));
|
|
2729
|
-
if (reason._tag !== "WaitingForChild") return
|
|
2730
|
-
if (!reason.children.some((entry) => entry.childSubmissionId === validated.childSubmissionId)) return
|
|
2738
|
+
if (reason._tag !== "WaitingForChild") return NOT_WAITING;
|
|
2739
|
+
if (!reason.children.some((entry) => entry.childSubmissionId === validated.childSubmissionId)) return NOT_WAITING;
|
|
2731
2740
|
for (const entry of reason.children) {
|
|
2732
2741
|
const listed = yield* readSubmission(operation, entry.childSubmissionId);
|
|
2733
2742
|
const reservation = yield* readReservation(operation, entry.childSubmissionId);
|
|
2734
|
-
if (!(Option.isSome(listed) && (listed.value.state === "settled" || listed.value.state === "terminalizing" && Option.isSome(reservation)))) return
|
|
2743
|
+
if (!(Option.isSome(listed) && (listed.value.state === "settled" || listed.value.state === "terminalizing" && Option.isSome(reservation)))) return STILL_WAITING;
|
|
2735
2744
|
}
|
|
2736
2745
|
yield* sql`
|
|
2737
2746
|
UPDATE effect_agent_submissions
|
|
@@ -2741,7 +2750,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
|
|
|
2741
2750
|
suspended_at = NULL
|
|
2742
2751
|
WHERE submission_id = ${validated.parentSubmissionId}
|
|
2743
2752
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2744
|
-
return
|
|
2753
|
+
return WOKEN;
|
|
2745
2754
|
}));
|
|
2746
2755
|
yield* hitFailpoint("ledger:child-settled:after", operation);
|
|
2747
2756
|
return outcome;
|
|
@@ -2754,13 +2763,14 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
|
|
|
2754
2763
|
const reserved = yield* inWriteTransaction(operation, Effect.gen(function* () {
|
|
2755
2764
|
const existing = yield* readChildReservation(operation, validated.reservationId);
|
|
2756
2765
|
if (Option.isSome(existing)) {
|
|
2757
|
-
|
|
2766
|
+
const existingSnapshot = yield* childReservationSnapshotFromRow(operation, existing.value);
|
|
2767
|
+
if (!(existing.value.parent_submission_id === validated.parentSubmissionId && existing.value.parent_tool_call_id === validated.parentToolCallId && existing.value.allocation_digest === validated.allocationDigest && equivalentPersistedJson(existingSnapshot.allocation, validated.allocation))) return yield* ChildReservationConflict.make({
|
|
2758
2768
|
reservationId: validated.reservationId,
|
|
2759
2769
|
status: existing.value.status,
|
|
2760
2770
|
message: "A reservation with this identity exists with a different parent Tool Call or allocation."
|
|
2761
2771
|
});
|
|
2762
2772
|
return ReservedChildBudget.make({
|
|
2763
|
-
reservation:
|
|
2773
|
+
reservation: existingSnapshot,
|
|
2764
2774
|
replayed: true
|
|
2765
2775
|
});
|
|
2766
2776
|
}
|
|
@@ -2856,7 +2866,8 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
|
|
|
2856
2866
|
message: `Unknown child reservation ${validated.reservationId}.`
|
|
2857
2867
|
});
|
|
2858
2868
|
if (existing.value.status !== "reserved") {
|
|
2859
|
-
|
|
2869
|
+
const existingSnapshot = yield* childReservationSnapshotFromRow(operation, existing.value);
|
|
2870
|
+
if (existingSnapshot.accounting !== void 0 && equivalentPersistedJson(existingSnapshot.accounting, validated.accounting)) return existingSnapshot;
|
|
2860
2871
|
return yield* ChildReservationConflict.make({
|
|
2861
2872
|
reservationId: validated.reservationId,
|
|
2862
2873
|
status: existing.value.status,
|
|
@@ -3081,7 +3092,7 @@ const submissionLedgerLayer = Layer.effectContext(makeServices());
|
|
|
3081
3092
|
* A composition-root convenience Layer for the durable Submission Ledger. Point it at the
|
|
3082
3093
|
* same database file as the ConversationStore so claims fence the same producer epochs.
|
|
3083
3094
|
*/
|
|
3084
|
-
const ledgerLayer = (options) => submissionLedgerLayer.pipe(Layer.provide(Layer.mergeAll(
|
|
3095
|
+
const ledgerLayer = (options) => Layer.unwrap(Effect.map(SqliteStorageConfig, (config) => submissionLedgerLayer.pipe(Layer.provide(Layer.mergeAll(Layer.succeed(SqliteStorageConfig)(config), storageFailpointLayer(options), SqliteClient.layer({ filename: options.filename }), NodeCrypto.layer))))).pipe(Layer.provide(storageConfigLayer(options)));
|
|
3085
3096
|
//#endregion
|
|
3086
3097
|
export { CurrentSqliteStorageVersion, SqliteAppendConflict, SqliteCheckpointConflict, SqliteFenceRejected, SqliteLedgerError, SqliteStorageCompatibilityError, SqliteStorageConfig, SqliteStorageConfigValue, SqliteStorageCorruptionError, SqliteStorageError, SqliteStorageFailpoint, SqliteStorageFailpointError, SqliteStorageFailpointLocation, SqliteStorageFailpointTestControl, SqliteWriteContention, conversationStoreLayer, layer, ledgerLayer, observationOffsetAt, sqliteMigrations, storageConfigLayer, storageFailpointLayer, submissionLedgerLayer };
|
|
3087
3098
|
|