@yielded/agent-platform-cloudflare 0.0.0-stage → 0.1.0-beta.166
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/Alarm-B0ODr1NE.mjs +1738 -0
- package/dist/Alarm-B0ODr1NE.mjs.map +1 -0
- package/dist/Alarm.d.mts +267 -0
- package/dist/Alarm.mjs +2 -0
- package/dist/BrowserCredentials.d.mts +91 -0
- package/dist/BrowserCredentials.mjs +120 -0
- package/dist/BrowserCredentials.mjs.map +1 -0
- package/dist/BrowserRestCapture.d.mts +34 -0
- package/dist/BrowserRestCapture.mjs +238 -0
- package/dist/BrowserRestCapture.mjs.map +1 -0
- package/dist/BrowserRestCrawl.d.mts +16 -0
- package/dist/BrowserRestCrawl.mjs +244 -0
- package/dist/BrowserRestCrawl.mjs.map +1 -0
- package/dist/BrowserSession-B969Xg-2.d.mts +132 -0
- package/dist/BrowserSession-RfIzxGCH.mjs +515 -0
- package/dist/BrowserSession-RfIzxGCH.mjs.map +1 -0
- package/dist/BrowserSession.d.mts +3 -0
- package/dist/BrowserSession.mjs +4 -0
- package/dist/CloudflareAiGateway.d.mts +64 -0
- package/dist/CloudflareAiGateway.mjs +70 -0
- package/dist/CloudflareAiGateway.mjs.map +1 -0
- package/dist/CloudflareBindings.d.mts +115 -0
- package/dist/CloudflareBindings.mjs +113 -0
- package/dist/CloudflareBindings.mjs.map +1 -0
- package/dist/CloudflareBrowser-5an6m-Qb.mjs +495 -0
- package/dist/CloudflareBrowser-5an6m-Qb.mjs.map +1 -0
- package/dist/CloudflareBrowser-gu7gma0Y.d.mts +80 -0
- package/dist/CloudflareBrowser.d.mts +2 -0
- package/dist/CloudflareBrowser.mjs +2 -0
- package/dist/CloudflareCodeMode.d.mts +44 -0
- package/dist/CloudflareCodeMode.mjs +616 -0
- package/dist/CloudflareCodeMode.mjs.map +1 -0
- package/dist/CloudflareConfig-qDrRLofL.d.mts +100 -0
- package/dist/CloudflareConfig.d.mts +2 -0
- package/dist/CloudflareConfig.mjs +122 -0
- package/dist/CloudflareConfig.mjs.map +1 -0
- package/dist/CloudflareMemory.d.mts +142 -0
- package/dist/CloudflareMemory.mjs +202 -0
- package/dist/CloudflareMemory.mjs.map +1 -0
- package/dist/CloudflareScheduling.d.mts +52 -0
- package/dist/CloudflareScheduling.mjs +396 -0
- package/dist/CloudflareScheduling.mjs.map +1 -0
- package/dist/CloudflareSubscriptions.d.mts +87 -0
- package/dist/CloudflareSubscriptions.mjs +529 -0
- package/dist/CloudflareSubscriptions.mjs.map +1 -0
- package/dist/CloudflareThreadClient.d.mts +1796 -0
- package/dist/CloudflareThreadClient.mjs +382 -0
- package/dist/CloudflareThreadClient.mjs.map +1 -0
- package/dist/InteractiveBrowser-BbG3kGA9.mjs +1934 -0
- package/dist/InteractiveBrowser-BbG3kGA9.mjs.map +1 -0
- package/dist/InteractiveBrowser-wV2Bti5e.d.mts +228 -0
- package/dist/InteractiveBrowser.d.mts +2 -0
- package/dist/InteractiveBrowser.mjs +2 -0
- package/dist/ThreadObject-BcfXbzSO.d.mts +867 -0
- package/dist/ThreadObject-DHjrnlX4.mjs +937 -0
- package/dist/ThreadObject-DHjrnlX4.mjs.map +1 -0
- package/dist/ThreadObject.d.mts +2 -0
- package/dist/ThreadObject.mjs +3 -0
- package/dist/WakeScheduler.d.mts +23 -0
- package/dist/WakeScheduler.mjs +57 -0
- package/dist/WakeScheduler.mjs.map +1 -0
- package/dist/boundary-BguazVkh.mjs +42 -0
- package/dist/boundary-BguazVkh.mjs.map +1 -0
- package/dist/index.d.mts +15 -0
- package/dist/index.mjs +15 -0
- package/dist/prepared-admission-DMX5p9dC.mjs +73 -0
- package/dist/prepared-admission-DMX5p9dC.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -6
- package/src/Alarm.ts +2961 -0
- package/src/BrowserCredentials.ts +146 -0
- package/src/BrowserRestCapture.ts +477 -0
- package/src/BrowserRestCrawl.ts +540 -0
- package/src/BrowserSession.ts +667 -0
- package/src/CloudflareAiGateway.ts +170 -0
- package/src/CloudflareBindings.ts +199 -0
- package/src/CloudflareBrowser.ts +15 -0
- package/src/CloudflareCodeMode.ts +878 -0
- package/src/CloudflareConfig.ts +138 -0
- package/src/CloudflareMemory.ts +420 -0
- package/src/CloudflareScheduling.ts +745 -0
- package/src/CloudflareSubscriptions.ts +1044 -0
- package/src/CloudflareThreadClient.ts +809 -0
- package/src/InteractiveBrowser.ts +3275 -0
- package/src/ThreadObject.ts +1198 -0
- package/src/WakeScheduler.ts +91 -0
- package/src/index.ts +14 -0
- package/src/internal/boundary.ts +55 -0
- package/src/internal/browser-binding.ts +322 -0
- package/src/internal/browser-credentials.ts +362 -0
- package/src/internal/browser-failure.ts +92 -0
- package/src/internal/browser-file-selection.ts +126 -0
- package/src/internal/browser-quick-action.ts +857 -0
- package/src/internal/browser-readonly-live-view.ts +111 -0
- package/src/internal/browser-session-lifecycle.ts +193 -0
- package/src/internal/due-queue.ts +493 -0
- package/src/internal/layers.ts +1068 -0
- package/src/internal/message-delivery.ts +120 -0
- package/src/internal/prepared-admission.ts +116 -0
- package/src/internal/progress-wait.ts +121 -0
- package/src/internal/transport.ts +56 -0
- package/README.md +0 -3
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { DEFAULT_MAX_STORED_VALUE_BYTES } from "@yielded/agent-storage-cloudflare/do-storage-config";
|
|
2
|
+
import { DeploymentId } from "@yielded/agent/records";
|
|
3
|
+
import { DEFAULT_OWNERSHIP_LEASE_DURATION } from "@yielded/agent/submission-ledger";
|
|
4
|
+
import { Context, Duration, Schema } from "effect";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Schema-validated configuration for the Cloudflare durable runtime (deployment spec §4:
|
|
8
|
+
* decoded once during Layer construction, exposed as a typed service; DEPLOY-003). Every
|
|
9
|
+
* cadence is in milliseconds; every bound is finite and checked before any resource opens.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const PositiveMillis = Schema.Int.check(Schema.isGreaterThan(0));
|
|
13
|
+
|
|
14
|
+
/** Maximum supported auxiliary wave equals the native message policy's five-minute ceiling. */
|
|
15
|
+
export const AuxiliaryDispatchMillis = PositiveMillis.check(Schema.isLessThanOrEqualTo(300_000));
|
|
16
|
+
const NonNegativeMillis = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
17
|
+
|
|
18
|
+
/** The supplied Cloudflare durable runtime configuration failed validation (DEPLOY-003). */
|
|
19
|
+
export class CloudflarePlatformConfigError extends Schema.TaggedError<CloudflarePlatformConfigError>()(
|
|
20
|
+
"CloudflarePlatformConfigError",
|
|
21
|
+
{
|
|
22
|
+
message: Schema.String,
|
|
23
|
+
cause: Schema.optionalKey(Schema.Defect()),
|
|
24
|
+
},
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* An admission was refused by a host resource limit BEFORE any ledger row existed
|
|
29
|
+
* (deployment spec §8, DEPLOY-007: "Admission has explicit bounded quota and overload
|
|
30
|
+
* behavior... a typed rejection"). This is the DC analogue of `NodeDurableHost`'s
|
|
31
|
+
* `AdmissionClosed` host gate: the port surface stays untouched, the refusal happens in the
|
|
32
|
+
* Thread Object's submit entry point before `DurableAgentRuntime.submit` runs, and
|
|
33
|
+
* nothing was admitted or written.
|
|
34
|
+
*/
|
|
35
|
+
export class AdmissionLimitExceeded extends Schema.TaggedError<AdmissionLimitExceeded>()(
|
|
36
|
+
"AdmissionLimitExceeded",
|
|
37
|
+
{
|
|
38
|
+
limit: Schema.Literals(["queue-depth", "input-bytes", "database-bytes"]),
|
|
39
|
+
actual: Schema.Int,
|
|
40
|
+
maximum: Schema.Int,
|
|
41
|
+
},
|
|
42
|
+
) {
|
|
43
|
+
override get message() {
|
|
44
|
+
return (
|
|
45
|
+
`Admission refused before any ledger row existed: ${this.limit} ${this.actual} exceeds ` +
|
|
46
|
+
`the configured maximum ${this.maximum}. Accepted work is unaffected; retry after the ` +
|
|
47
|
+
"lane drains or raise the limit (DEPLOY-007)."
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The platform's hard per-Object database cap (10 GB, developers.cloudflare.com limits). */
|
|
53
|
+
export const CLOUDFLARE_DATABASE_CAP_BYTES = 10_000_000_000;
|
|
54
|
+
|
|
55
|
+
/** Default database-size admission ceiling: a 1 GB safety margin under the platform cap. */
|
|
56
|
+
export const DEFAULT_MAX_DATABASE_BYTES = 9_000_000_000;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Explicit bounded admission quotas checked by the Thread Object BEFORE admission
|
|
60
|
+
* (exit gate "resource limits are checked before admission").
|
|
61
|
+
*/
|
|
62
|
+
export class CloudflareAdmissionLimitsValue extends Schema.Class<CloudflareAdmissionLimitsValue>(
|
|
63
|
+
"@effect-agent/platform-cloudflare/CloudflareAdmissionLimitsValue",
|
|
64
|
+
)({
|
|
65
|
+
/** Maximum nonterminal Submissions per Thread lane before new admissions refuse. */
|
|
66
|
+
maxQueueDepthPerLane: Schema.Int.check(
|
|
67
|
+
Schema.isGreaterThan(0),
|
|
68
|
+
Schema.isLessThanOrEqualTo(100_000),
|
|
69
|
+
),
|
|
70
|
+
/** Maximum encoded input bytes; never above the storage per-value bound. */
|
|
71
|
+
maxInputBytes: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(2_000_000)),
|
|
72
|
+
/** Maximum `ctx.storage.sql.databaseSize` at admission; stays under the 10 GB platform cap. */
|
|
73
|
+
maxDatabaseBytes: Schema.Int.check(
|
|
74
|
+
Schema.isGreaterThan(0),
|
|
75
|
+
Schema.isLessThanOrEqualTo(CLOUDFLARE_DATABASE_CAP_BYTES),
|
|
76
|
+
),
|
|
77
|
+
}) {}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Validated Cloudflare durable runtime configuration. The producer identity of one
|
|
81
|
+
* Thread Object is `{producerPrefix}:{threadId}` — stable across incarnations of
|
|
82
|
+
* the same deployment, distinct across deployments — and producer-epoch fencing (not the
|
|
83
|
+
* producer name) remains the correctness authority (DUR-006).
|
|
84
|
+
*/
|
|
85
|
+
export class CloudflareDurableRuntimeConfigValue extends Schema.Class<CloudflareDurableRuntimeConfigValue>(
|
|
86
|
+
"@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfigValue",
|
|
87
|
+
)({
|
|
88
|
+
deploymentId: DeploymentId,
|
|
89
|
+
/** Head of the minted producer identity `{producerPrefix}:{threadId}`. */
|
|
90
|
+
producerPrefix: Schema.NonEmptyString.check(Schema.isMaxLength(256)),
|
|
91
|
+
/** Submission ownership lease duration (D5); fences work across Object incarnations. */
|
|
92
|
+
ownershipLeaseDuration: PositiveMillis,
|
|
93
|
+
/** Base delay of exponential backoff when a pass fails or makes no progress. */
|
|
94
|
+
alarmBackoffBase: PositiveMillis,
|
|
95
|
+
/** Ceiling of exponential backoff after failed or no-progress maintenance passes. */
|
|
96
|
+
alarmBackoffCap: PositiveMillis,
|
|
97
|
+
/** `awaitSettlement` ledger re-check cadence when no wake arrives. */
|
|
98
|
+
settlementPollInterval: PositiveMillis,
|
|
99
|
+
/** Worker ownership-lease renewal cadence during an active Attempt. */
|
|
100
|
+
leaseRenewalInterval: PositiveMillis,
|
|
101
|
+
/** Active-Run abort-intent poll cadence. */
|
|
102
|
+
abortPollInterval: PositiveMillis,
|
|
103
|
+
/** Canonical observation poll cadence of the Durable Object store. */
|
|
104
|
+
observationPollInterval: NonNegativeMillis,
|
|
105
|
+
/** One disposable projection backfill wave; independent of required live applyCommitted. */
|
|
106
|
+
projectionDispatchTimeoutMillis: AuxiliaryDispatchMillis,
|
|
107
|
+
/** Per-value byte bound; must stay under the platform's 2 MB SQLite value limit. */
|
|
108
|
+
maxStoredValueBytes: Schema.Int.check(
|
|
109
|
+
Schema.isGreaterThan(0),
|
|
110
|
+
Schema.isLessThanOrEqualTo(2_000_000),
|
|
111
|
+
),
|
|
112
|
+
/** Opt-in full payload/digest-chain audit while opening the store. */
|
|
113
|
+
verifyOnOpen: Schema.Boolean,
|
|
114
|
+
limits: CloudflareAdmissionLimitsValue,
|
|
115
|
+
}) {}
|
|
116
|
+
|
|
117
|
+
/** Explicit configuration authority for the assembled Cloudflare durable runtime. */
|
|
118
|
+
export class CloudflareDurableRuntimeConfig extends Context.Service<
|
|
119
|
+
CloudflareDurableRuntimeConfig,
|
|
120
|
+
CloudflareDurableRuntimeConfigValue
|
|
121
|
+
>()("@effect-agent/platform-cloudflare/CloudflareDurableRuntimeConfig") {}
|
|
122
|
+
|
|
123
|
+
/** Documented production defaults applied by `ThreadObject.layer`. */
|
|
124
|
+
export const CLOUDFLARE_RUNTIME_DEFAULTS = {
|
|
125
|
+
ownershipLeaseDuration: Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION),
|
|
126
|
+
alarmBackoffBase: 100,
|
|
127
|
+
alarmBackoffCap: 5_000,
|
|
128
|
+
settlementPollInterval: 500,
|
|
129
|
+
leaseRenewalInterval: 10_000,
|
|
130
|
+
abortPollInterval: 500,
|
|
131
|
+
observationPollInterval: 25,
|
|
132
|
+
projectionDispatchTimeoutMillis: 30_000,
|
|
133
|
+
maxStoredValueBytes: DEFAULT_MAX_STORED_VALUE_BYTES,
|
|
134
|
+
verifyOnOpen: false,
|
|
135
|
+
maxQueueDepthPerLane: 256,
|
|
136
|
+
maxInputBytes: DEFAULT_MAX_STORED_VALUE_BYTES,
|
|
137
|
+
maxDatabaseBytes: DEFAULT_MAX_DATABASE_BYTES,
|
|
138
|
+
} as const;
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DoMemoryStorageLimits,
|
|
3
|
+
defaultDoMemoryStorageLimits,
|
|
4
|
+
doMemoryStoreLayerWithFailpoints,
|
|
5
|
+
} from "@yielded/agent-storage-cloudflare/do-memory-store";
|
|
6
|
+
import {
|
|
7
|
+
type MemoryOwnerAuthorizer,
|
|
8
|
+
decodeMemoryWire,
|
|
9
|
+
defaultMemoryRpcLimits,
|
|
10
|
+
encodeMemoryWire,
|
|
11
|
+
handleMemoryOwnerRequest,
|
|
12
|
+
MemoryOwnerIdentity,
|
|
13
|
+
MemoryOwnerRequest,
|
|
14
|
+
MemoryOwnerResponse,
|
|
15
|
+
MemoryRpcError,
|
|
16
|
+
MemoryRpcLimits,
|
|
17
|
+
type MemoryOwnerFailure,
|
|
18
|
+
} from "@yielded/agent-storage-cloudflare/memory-protocol";
|
|
19
|
+
import * as Memory from "@yielded/agent/memory";
|
|
20
|
+
import * as MemoryNamespace from "@yielded/agent/memory-namespace";
|
|
21
|
+
import { MemoryNamespaceAddress } from "@yielded/agent/memory-namespace";
|
|
22
|
+
import { type MemoryLookup, MemoryRecallLimits } from "@yielded/agent/memory-reference";
|
|
23
|
+
import { MemoryAccess } from "@yielded/agent/memory-revalidation";
|
|
24
|
+
import {
|
|
25
|
+
type MemoryReader,
|
|
26
|
+
type MemoryWrite,
|
|
27
|
+
MemoryKey,
|
|
28
|
+
MemoryDocument,
|
|
29
|
+
MemoryMutationFailpoint,
|
|
30
|
+
MemoryStorageError,
|
|
31
|
+
MemoryWriter,
|
|
32
|
+
} from "@yielded/agent/memory-store";
|
|
33
|
+
import {
|
|
34
|
+
type MemoryIndexSearch,
|
|
35
|
+
type SemanticMemoryProfile,
|
|
36
|
+
} from "@yielded/agent/semantic-memory-index";
|
|
37
|
+
import { type SemanticCandidateLimits } from "@yielded/agent/semantic-memory-revalidation";
|
|
38
|
+
import { Principal } from "@yielded/agent/submission-ledger";
|
|
39
|
+
import { Clock, Context, Effect, Layer, Schema } from "effect";
|
|
40
|
+
import {
|
|
41
|
+
DurableObject as EffectCfDurableObject,
|
|
42
|
+
DurableObjectState,
|
|
43
|
+
RpcTargets,
|
|
44
|
+
type WorkerEnvironment,
|
|
45
|
+
} from "effect-cf";
|
|
46
|
+
|
|
47
|
+
export interface MemoryObjectRpc extends Rpc.DurableObjectBranded {
|
|
48
|
+
memory(encoded: string): Promise<string>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class MemoryObjectNamespace extends Context.Service<
|
|
52
|
+
MemoryObjectNamespace,
|
|
53
|
+
{
|
|
54
|
+
readonly namespace: DurableObjectNamespace<MemoryObjectRpc>;
|
|
55
|
+
}
|
|
56
|
+
>()("@effect-agent/platform-cloudflare/MemoryObjectNamespace") {}
|
|
57
|
+
|
|
58
|
+
/** Namespace version and identity are already canonicalized by MemoryNamespace. */
|
|
59
|
+
export const memoryObjectName = (namespace: MemoryNamespace.Any): string => namespace.address;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Effect-native, host-bound memory client. Recall revalidates the entire admitted lookup
|
|
63
|
+
* in one RPC and renders it locally. No retries or per-source splitting occur here.
|
|
64
|
+
* Interrupted callers stop waiting; the owner has its own deadline. A timed-out write
|
|
65
|
+
* may have committed: reconcile by sending the identical operation ID and command.
|
|
66
|
+
*/
|
|
67
|
+
const makeMemoryClient = Effect.fn("CloudflareMemoryClient.make")(function* <
|
|
68
|
+
Namespace extends MemoryNamespace.Any,
|
|
69
|
+
>(
|
|
70
|
+
access: MemoryAccess<Namespace>,
|
|
71
|
+
principal: Principal,
|
|
72
|
+
rpcLimits: MemoryRpcLimits = defaultMemoryRpcLimits,
|
|
73
|
+
) {
|
|
74
|
+
const validated = yield* Schema.decodeEffect(MemoryRpcLimits)(rpcLimits).pipe(
|
|
75
|
+
Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const bound = yield* Schema.decodeUnknownEffect(MemoryAccess.Wire)(access).pipe(
|
|
79
|
+
Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
principal = yield* Schema.decodeEffect(Principal)(principal).pipe(
|
|
83
|
+
Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
|
|
84
|
+
);
|
|
85
|
+
const { namespace } = yield* MemoryObjectNamespace;
|
|
86
|
+
|
|
87
|
+
const call = Effect.fn("CloudflareMemoryClient.call")(function* (request: MemoryOwnerRequest) {
|
|
88
|
+
const decoded = yield* Schema.decodeEffect(MemoryOwnerRequest)(request).pipe(
|
|
89
|
+
Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const encoded = yield* encodeMemoryWire(MemoryOwnerRequest, decoded, validated.maxRequestBytes);
|
|
93
|
+
|
|
94
|
+
const unavailable = (cause: unknown) => {
|
|
95
|
+
const error = MemoryRpcError.make({ reason: "unavailable" });
|
|
96
|
+
|
|
97
|
+
error.cause = cause;
|
|
98
|
+
|
|
99
|
+
return error;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const address = memoryObjectName(bound.namespace);
|
|
103
|
+
|
|
104
|
+
const target = yield* RpcTargets.get(namespace, address, () =>
|
|
105
|
+
namespace.get(namespace.idFromName(address)),
|
|
106
|
+
).pipe(Effect.mapError(unavailable));
|
|
107
|
+
|
|
108
|
+
const raw = yield* Effect.tryPromise({
|
|
109
|
+
try: () => target.memory(encoded),
|
|
110
|
+
catch: unavailable,
|
|
111
|
+
}).pipe(Effect.tapCause(() => RpcTargets.invalidate(target)));
|
|
112
|
+
|
|
113
|
+
const response = yield* decodeMemoryWire(MemoryOwnerResponse, raw, validated.maxResponseBytes);
|
|
114
|
+
|
|
115
|
+
if (response._tag === "Failed") return yield* response.failure;
|
|
116
|
+
if (
|
|
117
|
+
!MemoryNamespace.equals(response.access.namespace, bound.namespace) ||
|
|
118
|
+
response.access.scope !== bound.scope
|
|
119
|
+
)
|
|
120
|
+
return yield* MemoryRpcError.make({ reason: "protocol" });
|
|
121
|
+
|
|
122
|
+
return response;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const withinDeadline = <A, E, R>(effect: Effect.Effect<A, E, R>, timeoutMillis: number) =>
|
|
126
|
+
effect.pipe(
|
|
127
|
+
Effect.timeoutOrElse({
|
|
128
|
+
duration: timeoutMillis,
|
|
129
|
+
orElse: () => Effect.fail(MemoryRpcError.make({ reason: "timeout" })),
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const revalidate = Effect.fn("CloudflareMemoryClient.revalidate")(function* (
|
|
134
|
+
lookup: MemoryLookup,
|
|
135
|
+
limits: MemoryRecallLimits,
|
|
136
|
+
) {
|
|
137
|
+
limits = yield* Schema.decodeEffect(MemoryRecallLimits)(limits).pipe(
|
|
138
|
+
Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
|
|
139
|
+
);
|
|
140
|
+
const timeoutMillis = Math.min(validated.timeoutMillis, limits.timeoutMillis);
|
|
141
|
+
|
|
142
|
+
return yield* Effect.gen(function* () {
|
|
143
|
+
const response = yield* call({
|
|
144
|
+
_tag: "Revalidate",
|
|
145
|
+
version: 1,
|
|
146
|
+
access: bound,
|
|
147
|
+
principal,
|
|
148
|
+
lookup,
|
|
149
|
+
limits,
|
|
150
|
+
deadlineMillis: (yield* Clock.currentTimeMillis) + timeoutMillis,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (response._tag !== "Lookup") return yield* MemoryRpcError.make({ reason: "protocol" });
|
|
154
|
+
|
|
155
|
+
return response.lookup;
|
|
156
|
+
}).pipe((effect) => withinDeadline(effect, timeoutMillis));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const change = Effect.fn("CloudflareMemoryClient.change")(function* (
|
|
160
|
+
write: MemoryWrite<Namespace>,
|
|
161
|
+
) {
|
|
162
|
+
if (!MemoryNamespace.equals(write.key.namespace, bound.namespace))
|
|
163
|
+
return yield* MemoryRpcError.make({ reason: "denied" });
|
|
164
|
+
|
|
165
|
+
return yield* Effect.gen(function* () {
|
|
166
|
+
const response = yield* call({
|
|
167
|
+
_tag: "Change",
|
|
168
|
+
version: 1,
|
|
169
|
+
access: bound,
|
|
170
|
+
principal,
|
|
171
|
+
write,
|
|
172
|
+
deadlineMillis: (yield* Clock.currentTimeMillis) + validated.timeoutMillis,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (response._tag !== "Changed" || response.document.key.id !== write.key.id)
|
|
176
|
+
return yield* MemoryRpcError.make({ reason: "protocol" });
|
|
177
|
+
|
|
178
|
+
return yield* MemoryDocument.restore(access.namespace, response.document);
|
|
179
|
+
}).pipe((effect) => withinDeadline(effect, validated.timeoutMillis));
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Read one exact current document in one owner RPC. Null means absent; withdrawals return
|
|
184
|
+
* tombstones. Denial, unavailable storage and deadlines fail typed, never become absence.
|
|
185
|
+
* Reads begun after an acknowledged write observe it or a later revision. The owner checks
|
|
186
|
+
* exact-key authority and active document scopes; source-dependent provenance policy remains
|
|
187
|
+
* application-owned. No extraction, job draining, embedding, discovery or rendering occurs.
|
|
188
|
+
*/
|
|
189
|
+
const get = Effect.fn("CloudflareMemoryClient.get")(function* (key: MemoryKey<Namespace>) {
|
|
190
|
+
const decodedKey = yield* Schema.decodeUnknownEffect(MemoryKey.Wire)(key).pipe(
|
|
191
|
+
Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (!MemoryNamespace.equals(decodedKey.namespace, bound.namespace))
|
|
195
|
+
return yield* MemoryRpcError.make({ reason: "denied" });
|
|
196
|
+
|
|
197
|
+
return yield* Effect.gen(function* () {
|
|
198
|
+
const response = yield* call({
|
|
199
|
+
_tag: "Get",
|
|
200
|
+
version: 1,
|
|
201
|
+
access: bound,
|
|
202
|
+
principal,
|
|
203
|
+
key: decodedKey,
|
|
204
|
+
deadlineMillis: (yield* Clock.currentTimeMillis) + validated.timeoutMillis,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
if (
|
|
208
|
+
response._tag !== "Document" ||
|
|
209
|
+
!MemoryNamespace.equals(response.key.namespace, bound.namespace) ||
|
|
210
|
+
response.key.id !== decodedKey.id
|
|
211
|
+
)
|
|
212
|
+
return yield* MemoryRpcError.make({ reason: "protocol" });
|
|
213
|
+
if (response.document === null) return null;
|
|
214
|
+
if (
|
|
215
|
+
response.document.key.id !== decodedKey.id ||
|
|
216
|
+
response.document.source.id !== decodedKey.id ||
|
|
217
|
+
(response.document._tag === "ActiveMemoryDocument" &&
|
|
218
|
+
!response.document.scopes.includes(bound.scope))
|
|
219
|
+
)
|
|
220
|
+
return yield* MemoryRpcError.make({ reason: "protocol" });
|
|
221
|
+
|
|
222
|
+
yield* encodeMemoryWire(MemoryDocument.Wire, response.document, validated.maxSourceBytes);
|
|
223
|
+
|
|
224
|
+
return yield* MemoryDocument.restore(access.namespace, response.document);
|
|
225
|
+
}).pipe((effect) => withinDeadline(effect, validated.timeoutMillis));
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const revalidateSemantic = Effect.fn("CloudflareMemoryClient.revalidateSemantic")(function* (
|
|
229
|
+
found: MemoryIndexSearch<Namespace>,
|
|
230
|
+
profile: SemanticMemoryProfile,
|
|
231
|
+
limits: SemanticCandidateLimits,
|
|
232
|
+
) {
|
|
233
|
+
return yield* Effect.gen(function* () {
|
|
234
|
+
const response = yield* call({
|
|
235
|
+
_tag: "RevalidateSemantic",
|
|
236
|
+
version: 1,
|
|
237
|
+
access: bound,
|
|
238
|
+
principal,
|
|
239
|
+
found,
|
|
240
|
+
profile,
|
|
241
|
+
limits,
|
|
242
|
+
deadlineMillis: (yield* Clock.currentTimeMillis) + validated.timeoutMillis,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (response._tag !== "Semantic") return yield* MemoryRpcError.make({ reason: "protocol" });
|
|
246
|
+
|
|
247
|
+
return response.result;
|
|
248
|
+
}).pipe((effect) => withinDeadline(effect, validated.timeoutMillis));
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Revalidate in one owner RPC, then render whole passages within the caller's budget.
|
|
253
|
+
* The bound source is essential: unavailable/stale results and matches that cannot fit
|
|
254
|
+
* fail instead of silently producing empty context. No-match remains successful.
|
|
255
|
+
* The single outcome has sourceId "memory". No embedding or candidate search is performed.
|
|
256
|
+
* Use revalidate with Memory.recall for multiple readers sharing one output budget.
|
|
257
|
+
*/
|
|
258
|
+
const recall = Effect.fn("CloudflareMemoryClient.recall")(function* (
|
|
259
|
+
lookup: MemoryLookup,
|
|
260
|
+
limits: MemoryRecallLimits,
|
|
261
|
+
estimateTokens?: (text: string) => number,
|
|
262
|
+
) {
|
|
263
|
+
return yield* Memory.recall(
|
|
264
|
+
[{ id: "memory", essential: true, read: revalidate(lookup, limits) }],
|
|
265
|
+
limits,
|
|
266
|
+
estimateTokens,
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
return { get, recall, revalidate, revalidateSemantic, change };
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
export const CloudflareMemoryClient = {
|
|
274
|
+
/** Bind access and principal using the MemoryObjectNamespace supplied by the application. */
|
|
275
|
+
make: makeMemoryClient,
|
|
276
|
+
/** Use a resolved Worker or Durable Object binding without manual service provisioning. */
|
|
277
|
+
fromBinding: Effect.fn("CloudflareMemoryClient.fromBinding")(function* <
|
|
278
|
+
Namespace extends MemoryNamespace.Any,
|
|
279
|
+
>(
|
|
280
|
+
binding: DurableObjectNamespace<MemoryObjectRpc>,
|
|
281
|
+
options: {
|
|
282
|
+
readonly access: MemoryAccess<Namespace>;
|
|
283
|
+
readonly principal: Principal;
|
|
284
|
+
readonly rpcLimits?: MemoryRpcLimits;
|
|
285
|
+
},
|
|
286
|
+
) {
|
|
287
|
+
return yield* makeMemoryClient(options.access, options.principal, options.rpcLimits).pipe(
|
|
288
|
+
Effect.provideService(MemoryObjectNamespace, { namespace: binding }),
|
|
289
|
+
);
|
|
290
|
+
}),
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Optional activity-processor destination. Keeps domain write errors intact; transport,
|
|
295
|
+
* authorization and deadline failures become the existing MemoryStorageError contract.
|
|
296
|
+
* Receipts remain authoritative, including after caller interruption or lost replies.
|
|
297
|
+
*/
|
|
298
|
+
export const cloudflareMemoryWriterLayer = (
|
|
299
|
+
access: MemoryAccess,
|
|
300
|
+
principal: Principal,
|
|
301
|
+
limits: MemoryRpcLimits = defaultMemoryRpcLimits,
|
|
302
|
+
) =>
|
|
303
|
+
Layer.effect(
|
|
304
|
+
MemoryWriter,
|
|
305
|
+
Effect.gen(function* () {
|
|
306
|
+
const client = yield* CloudflareMemoryClient.make(access, principal, limits);
|
|
307
|
+
|
|
308
|
+
return MemoryWriter.fromAdapter({
|
|
309
|
+
change: (write) =>
|
|
310
|
+
client.change(write).pipe(
|
|
311
|
+
Effect.catchTag("MemoryRpcError", (error) =>
|
|
312
|
+
Effect.fail(
|
|
313
|
+
MemoryStorageError.make({
|
|
314
|
+
operation: `memory RPC ${error.reason}`,
|
|
315
|
+
reason:
|
|
316
|
+
error.reason === "unavailable" || error.reason === "timeout"
|
|
317
|
+
? "unavailable"
|
|
318
|
+
: "invalid-input",
|
|
319
|
+
}),
|
|
320
|
+
),
|
|
321
|
+
),
|
|
322
|
+
Effect.catchTag(["MemoryRecallError", "MemoryIndexError", "SemanticMemoryError"], () =>
|
|
323
|
+
Effect.fail(
|
|
324
|
+
MemoryStorageError.make({ operation: "memory RPC response", reason: "corrupt" }),
|
|
325
|
+
),
|
|
326
|
+
),
|
|
327
|
+
),
|
|
328
|
+
});
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
type OwnerServices = MemoryReader | MemoryWriter | MemoryOwnerAuthorizer | MemoryOwnerIdentity;
|
|
333
|
+
|
|
334
|
+
export interface MemoryObjectInstance extends InstanceType<
|
|
335
|
+
EffectCfDurableObject.DurableObjectClass<Record<never, never>, OwnerServices>
|
|
336
|
+
> {
|
|
337
|
+
memory(encoded: string): Promise<string>;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export interface MemoryObjectClass {
|
|
341
|
+
new (ctx: globalThis.DurableObjectState, env: Cloudflare.Env): MemoryObjectInstance;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Dedicated SQLite owner, independent of Thread lifetimes. The host binds authorization
|
|
346
|
+
* after restoring its namespace definition from MemoryOwnerIdentity. Do not retain
|
|
347
|
+
* cleanup-scoped resources in the host Layer; it lives for the DO incarnation.
|
|
348
|
+
*/
|
|
349
|
+
const makeMemoryObject = <E>(
|
|
350
|
+
host: Layer.Layer<
|
|
351
|
+
MemoryOwnerAuthorizer,
|
|
352
|
+
E,
|
|
353
|
+
MemoryOwnerIdentity | DurableObjectState.DurableObjectState | WorkerEnvironment
|
|
354
|
+
>,
|
|
355
|
+
options: {
|
|
356
|
+
readonly storageLimits?: DoMemoryStorageLimits;
|
|
357
|
+
readonly rpcLimits?: MemoryRpcLimits;
|
|
358
|
+
readonly failpoints?: Layer.Layer<
|
|
359
|
+
MemoryMutationFailpoint,
|
|
360
|
+
never,
|
|
361
|
+
DurableObjectState.DurableObjectState
|
|
362
|
+
>;
|
|
363
|
+
} = {},
|
|
364
|
+
): MemoryObjectClass => {
|
|
365
|
+
const identity = Layer.effect(
|
|
366
|
+
MemoryOwnerIdentity,
|
|
367
|
+
Effect.gen(function* () {
|
|
368
|
+
const state = yield* DurableObjectState.DurableObjectState;
|
|
369
|
+
|
|
370
|
+
const address = yield* Schema.decodeUnknownEffect(MemoryNamespaceAddress)(
|
|
371
|
+
state.raw.id.name,
|
|
372
|
+
).pipe(Effect.mapError(() => MemoryRpcError.make({ reason: "denied" })));
|
|
373
|
+
|
|
374
|
+
return { namespace: MemoryNamespace.Any.make({ address }) };
|
|
375
|
+
}),
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
const store = Layer.unwrap(
|
|
379
|
+
Effect.map(DurableObjectState.DurableObjectState, (state) =>
|
|
380
|
+
doMemoryStoreLayerWithFailpoints(
|
|
381
|
+
state.raw.storage,
|
|
382
|
+
options.storageLimits ?? defaultDoMemoryStorageLimits,
|
|
383
|
+
),
|
|
384
|
+
),
|
|
385
|
+
).pipe(Layer.provide(options.failpoints ?? MemoryMutationFailpoint.layer));
|
|
386
|
+
|
|
387
|
+
const application = Layer.merge(store, host).pipe(Layer.provideMerge(identity));
|
|
388
|
+
|
|
389
|
+
const runtime: Layer.Layer<
|
|
390
|
+
OwnerServices,
|
|
391
|
+
E | MemoryOwnerFailure,
|
|
392
|
+
DurableObjectState.DurableObjectState | WorkerEnvironment
|
|
393
|
+
> = Layer.effectContext(
|
|
394
|
+
Effect.gen(function* () {
|
|
395
|
+
const state = yield* DurableObjectState.DurableObjectState;
|
|
396
|
+
const scope = yield* Effect.scope;
|
|
397
|
+
|
|
398
|
+
yield* Schema.decodeEffect(MemoryRpcLimits)(options.rpcLimits ?? defaultMemoryRpcLimits).pipe(
|
|
399
|
+
Effect.mapError(() => MemoryRpcError.make({ reason: "protocol" })),
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
return yield* state.blockConcurrencyWhile(Layer.buildWithScope(application, scope));
|
|
403
|
+
}),
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
const rpc = { memory: (encoded: string) => handleMemoryOwnerRequest(encoded, options.rpcLimits) };
|
|
407
|
+
|
|
408
|
+
return EffectCfDurableObject.make<
|
|
409
|
+
OwnerServices,
|
|
410
|
+
E | MemoryOwnerFailure,
|
|
411
|
+
never,
|
|
412
|
+
never,
|
|
413
|
+
typeof rpc
|
|
414
|
+
>(runtime, { rpc });
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
export const MemoryObject = {
|
|
418
|
+
/** Build the SQLite Durable Object class with the application's owner authorization Layer. */
|
|
419
|
+
make: makeMemoryObject,
|
|
420
|
+
};
|