@effect-agent/platform-cloudflare 0.0.1-beta.5 → 0.0.1-beta.6
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.d.mts +120 -35
- package/dist/index.mjs +888 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -7
- package/src/code-mode-executor.ts +710 -0
- package/src/config.ts +3 -0
- package/src/conversation-object.ts +159 -28
- package/src/index.ts +2 -0
- package/src/layers.ts +14 -5
- package/src/telemetry-internal.ts +432 -0
- package/src/telemetry.ts +37 -0
package/src/config.ts
CHANGED
|
@@ -103,6 +103,8 @@ export class CloudflareDurableRuntimeConfigValue extends Schema.Class<Cloudflare
|
|
|
103
103
|
abortPollInterval: PositiveMillis,
|
|
104
104
|
/** Canonical observation poll cadence of the Durable Object store. */
|
|
105
105
|
observationPollInterval: NonNegativeMillis,
|
|
106
|
+
/** Cooperative background-export budget; native Object delivery never awaits the flush. */
|
|
107
|
+
telemetryFlushTimeout: PositiveMillis,
|
|
106
108
|
/** Per-value byte bound; must stay under the platform's 2 MB SQLite value limit. */
|
|
107
109
|
maxStoredValueBytes: Schema.Int.check(
|
|
108
110
|
Schema.isGreaterThan(0),
|
|
@@ -129,6 +131,7 @@ export const CLOUDFLARE_RUNTIME_DEFAULTS = {
|
|
|
129
131
|
leaseRenewalInterval: 10_000,
|
|
130
132
|
abortPollInterval: 500,
|
|
131
133
|
observationPollInterval: 25,
|
|
134
|
+
telemetryFlushTimeout: 2_000,
|
|
132
135
|
maxStoredValueBytes: DEFAULT_MAX_STORED_VALUE_BYTES,
|
|
133
136
|
verifyOnOpen: false,
|
|
134
137
|
maxQueueDepthPerLane: 256,
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
DurableObjectContext,
|
|
39
39
|
conversationNamespaceLayer,
|
|
40
40
|
type CloudflareBindingError,
|
|
41
|
+
type ConversationObjectNamespace,
|
|
41
42
|
} from "./bindings.ts";
|
|
42
43
|
import {
|
|
43
44
|
AbortRecorded,
|
|
@@ -67,9 +68,18 @@ import {
|
|
|
67
68
|
type CloudflareDurableRuntimeOptions,
|
|
68
69
|
type CloudflareDurableRuntimeServices,
|
|
69
70
|
} from "./layers.ts";
|
|
71
|
+
import {
|
|
72
|
+
flushCloudflareRuntimeTelemetry,
|
|
73
|
+
logCloudflareWaitUntilRegistrationFailure,
|
|
74
|
+
makeCloudflareTelemetryFlushCoordinator,
|
|
75
|
+
registerCloudflareTelemetryAfterNativeSettlement,
|
|
76
|
+
withCloudflareNativeSpanFailure,
|
|
77
|
+
type CloudflareTelemetryFlushCoordinator,
|
|
78
|
+
} from "./telemetry-internal.ts";
|
|
79
|
+
import { CloudflareRuntimeTelemetry } from "./telemetry.ts";
|
|
70
80
|
|
|
71
81
|
/**
|
|
72
|
-
* `makeConversationObjectClass(options)` — the Conversation Durable Object (plan §1.4,
|
|
82
|
+
* `makeConversationObjectClass(options, telemetry?)` — the Conversation Durable Object (plan §1.4,
|
|
73
83
|
* D-P6-1): a factory returning a class that applications export from their Worker entry.
|
|
74
84
|
* One SQLite-backed Object per Conversation is the serialized owner (durability §6); the
|
|
75
85
|
* Object never runs `runResolvedWorker`'s infinite loop — each ingress event or alarm runs
|
|
@@ -95,9 +105,15 @@ export interface ConversationObjectOptions extends CloudflareDurableRuntimeOptio
|
|
|
95
105
|
readonly namespaceBinding: string;
|
|
96
106
|
}
|
|
97
107
|
|
|
98
|
-
type ConversationObjectError =
|
|
108
|
+
type ConversationObjectError<TelemetryError> =
|
|
109
|
+
| CloudflareDurableRuntimeInitializationError
|
|
110
|
+
| CloudflareBindingError
|
|
111
|
+
| TelemetryError;
|
|
99
112
|
|
|
100
|
-
type EndpointServices =
|
|
113
|
+
type EndpointServices =
|
|
114
|
+
| CloudflareDurableRuntimeServices
|
|
115
|
+
| DurableObjectContext
|
|
116
|
+
| CloudflareRuntimeTelemetry;
|
|
101
117
|
|
|
102
118
|
/** Port envelope tags whose owner-side execution durably mutates this Object's lane. */
|
|
103
119
|
const MUTATING_PORT_TAGS: ReadonlySet<string> = new Set([
|
|
@@ -608,6 +624,54 @@ const gateEndpoint: Effect.Effect<void, unknown, EndpointServices> = Effect.gen(
|
|
|
608
624
|
yield* maintenance.ensureAlarm;
|
|
609
625
|
});
|
|
610
626
|
|
|
627
|
+
const NATIVE_ENTRYPOINTS = [
|
|
628
|
+
"submit",
|
|
629
|
+
"await_settlement",
|
|
630
|
+
"observe",
|
|
631
|
+
"abort",
|
|
632
|
+
"resolve_approval",
|
|
633
|
+
"resolve_unknown",
|
|
634
|
+
"explain",
|
|
635
|
+
"verify",
|
|
636
|
+
"retry",
|
|
637
|
+
"obligations",
|
|
638
|
+
"port_call",
|
|
639
|
+
"wake",
|
|
640
|
+
"alarm",
|
|
641
|
+
] as const;
|
|
642
|
+
|
|
643
|
+
type NativeEntrypoint = (typeof NATIVE_ENTRYPOINTS)[number];
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Owner-side delivery measurement. The exact endpoint Cause is hidden behind a bounded typed
|
|
647
|
+
* marker while the span closes, then restored together with any newly composed real reasons.
|
|
648
|
+
*/
|
|
649
|
+
const observeNativeEntrypoint = <A, E>(
|
|
650
|
+
entrypoint: NativeEntrypoint,
|
|
651
|
+
effect: Effect.Effect<A, E, EndpointServices>,
|
|
652
|
+
): Effect.Effect<A, E, EndpointServices> =>
|
|
653
|
+
Effect.gen(function* () {
|
|
654
|
+
const identity = yield* ConversationObjectIdentity;
|
|
655
|
+
const config = yield* CloudflareDurableRuntimeConfig;
|
|
656
|
+
return yield* withCloudflareNativeSpanFailure(effect, (masked) =>
|
|
657
|
+
masked.pipe(
|
|
658
|
+
Effect.withSpan(`effect_agent.cloudflare.conversation_object.${entrypoint}`, {
|
|
659
|
+
kind: "server",
|
|
660
|
+
attributes: {
|
|
661
|
+
"effect_agent.cloudflare.entrypoint": entrypoint,
|
|
662
|
+
"effect_agent.deployment.id": config.deploymentId,
|
|
663
|
+
conversationId: identity.conversationId,
|
|
664
|
+
producerId: identity.producerId,
|
|
665
|
+
},
|
|
666
|
+
}),
|
|
667
|
+
),
|
|
668
|
+
);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
const flushNativeEntrypointTelemetry = Effect.flatMap(CloudflareDurableRuntimeConfig, (config) =>
|
|
672
|
+
flushCloudflareRuntimeTelemetry(config.telemetryFlushTimeout),
|
|
673
|
+
);
|
|
674
|
+
|
|
611
675
|
/** The public endpoint surface of one Conversation Object instance. */
|
|
612
676
|
export interface ConversationObjectInstance extends DurableObject {
|
|
613
677
|
submitEncoded(encoded: unknown): Promise<unknown>;
|
|
@@ -631,84 +695,151 @@ export interface ConversationObjectClass {
|
|
|
631
695
|
}
|
|
632
696
|
|
|
633
697
|
/**
|
|
634
|
-
* Build the application's Conversation Object class (export it from the
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
*
|
|
698
|
+
* Build the application's Conversation Object class (export it from the Worker entry).
|
|
699
|
+
* `telemetry` is the host composition boundary: it may require the Object context/namespace and
|
|
700
|
+
* retain a typed acquisition error, while any additional output services remain available to the
|
|
701
|
+
* cached runtime. `TelemetryError` stays the first explicit generic for source compatibility;
|
|
702
|
+
* additional outputs infer from the Layer. Omitting it installs the content-free no-op service.
|
|
703
|
+
* The explicit return type is what makes declaration emit possible: the class body carries a
|
|
704
|
+
* private runtime field, and TS4094 rejects inferring an exported anonymous class type around it.
|
|
638
705
|
*/
|
|
639
|
-
export const makeConversationObjectClass =
|
|
706
|
+
export const makeConversationObjectClass = <
|
|
707
|
+
TelemetryError = never,
|
|
708
|
+
AdditionalTelemetryOutputs = never,
|
|
709
|
+
>(
|
|
640
710
|
options: ConversationObjectOptions,
|
|
711
|
+
telemetry?: Layer.Layer<
|
|
712
|
+
CloudflareRuntimeTelemetry | AdditionalTelemetryOutputs,
|
|
713
|
+
TelemetryError,
|
|
714
|
+
DurableObjectContext | ConversationObjectNamespace
|
|
715
|
+
>,
|
|
641
716
|
): ConversationObjectClass => {
|
|
642
717
|
class ConversationObject extends DurableObject {
|
|
643
|
-
readonly #runtime: ManagedRuntime.ManagedRuntime<
|
|
718
|
+
readonly #runtime: ManagedRuntime.ManagedRuntime<
|
|
719
|
+
EndpointServices,
|
|
720
|
+
ConversationObjectError<TelemetryError>
|
|
721
|
+
>;
|
|
722
|
+
readonly #ctx: DurableObjectState;
|
|
723
|
+
readonly #telemetryFlush: CloudflareTelemetryFlushCoordinator;
|
|
644
724
|
|
|
645
725
|
constructor(ctx: DurableObjectState, env: Cloudflare.Env) {
|
|
646
726
|
super(ctx, env);
|
|
727
|
+
this.#ctx = ctx;
|
|
728
|
+
const platform = Layer.mergeAll(
|
|
729
|
+
DurableObjectContext.layer(ctx, env),
|
|
730
|
+
conversationNamespaceLayer(env, options.namespaceBinding),
|
|
731
|
+
);
|
|
732
|
+
// Host observability is composed at the Worker edge, not hidden in runtime options. Its
|
|
733
|
+
// typed acquisition failure remains in the ManagedRuntime error, while the Object context
|
|
734
|
+
// and namespace are available to Layers that derive exporter configuration from `env`.
|
|
735
|
+
const hostTelemetry = (telemetry ?? CloudflareRuntimeTelemetry.layerNoop).pipe(
|
|
736
|
+
Layer.provideMerge(platform),
|
|
737
|
+
);
|
|
647
738
|
this.#runtime = ManagedRuntime.make(
|
|
648
739
|
CloudflareDurableRuntime.layer(options).pipe(
|
|
649
|
-
Layer
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
conversationNamespaceLayer(env, options.namespaceBinding),
|
|
653
|
-
),
|
|
654
|
-
),
|
|
740
|
+
// Supplying the host Layer outermost makes its Logger/Tracer/Metric runtime
|
|
741
|
+
// configuration observe application Layer acquisition and every native endpoint.
|
|
742
|
+
Layer.provideMerge(hostTelemetry),
|
|
655
743
|
),
|
|
656
744
|
);
|
|
745
|
+
this.#telemetryFlush = makeCloudflareTelemetryFlushCoordinator(
|
|
746
|
+
() => this.#runtime.runPromise(flushNativeEntrypointTelemetry),
|
|
747
|
+
{
|
|
748
|
+
onReservationDropped: () => {
|
|
749
|
+
void this.#runtime.runSyncExit(
|
|
750
|
+
Effect.logWarning(
|
|
751
|
+
"Cloudflare telemetry delivery coalesced at the batch reservation limit",
|
|
752
|
+
).pipe(
|
|
753
|
+
Effect.annotateLogs({
|
|
754
|
+
"effect_agent.cloudflare.telemetry.failure_kind": "reservation_limit",
|
|
755
|
+
}),
|
|
756
|
+
),
|
|
757
|
+
);
|
|
758
|
+
},
|
|
759
|
+
},
|
|
760
|
+
);
|
|
657
761
|
// The constructor gate: local-only checks; never the recovery pass (deadlock argument,
|
|
658
762
|
// plan §1.4). A failure here fails every delivery with the typed construction error.
|
|
659
763
|
ctx.blockConcurrencyWhile(() => this.#runtime.runPromise(gateEndpoint));
|
|
660
764
|
}
|
|
661
765
|
|
|
766
|
+
#runNative<A, E>(
|
|
767
|
+
entrypoint: NativeEntrypoint,
|
|
768
|
+
effect: Effect.Effect<A, E, EndpointServices>,
|
|
769
|
+
): Promise<A> {
|
|
770
|
+
const delivery = this.#runtime.runPromise(observeNativeEntrypoint(entrypoint, effect));
|
|
771
|
+
// Reserve synchronously with the current native delivery, but do not await export from the
|
|
772
|
+
// RPC/alarm Promise. Each pending/trailing/queued batch waits for its assigned deliveries to
|
|
773
|
+
// settle and only its first owner registers the shared background Promise. Even an
|
|
774
|
+
// uninterruptible exporter therefore cannot delay a caller, suppress alarm retry, accumulate
|
|
775
|
+
// concurrent exporter work, an unbounded waitUntil registration queue, or unbounded retained
|
|
776
|
+
// delivery Promises. Excess arrivals are diagnosed and lossy-coalesced at the batch cap.
|
|
777
|
+
// Registration failure and every asynchronous flush failure remain isolated from delivery.
|
|
778
|
+
return registerCloudflareTelemetryAfterNativeSettlement(
|
|
779
|
+
(background) => this.#ctx.waitUntil(background),
|
|
780
|
+
delivery,
|
|
781
|
+
this.#telemetryFlush.reserve,
|
|
782
|
+
(cause) => {
|
|
783
|
+
// Native entrypoints run only after blockConcurrencyWhile has built the cached runtime.
|
|
784
|
+
// Effect Logger invocation is synchronous; runSyncExit captures a broken logger without
|
|
785
|
+
// starting an unscoped fiber or changing the delivery Promise. The exact platform value
|
|
786
|
+
// stays confined to this registration callback; the automatic Logger diagnostic is
|
|
787
|
+
// deliberately content-free.
|
|
788
|
+
void this.#runtime.runSyncExit(logCloudflareWaitUntilRegistrationFailure(cause));
|
|
789
|
+
},
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
|
|
662
793
|
async submitEncoded(encoded: unknown): Promise<unknown> {
|
|
663
|
-
return this.#
|
|
794
|
+
return this.#runNative("submit", submitEndpoint(encoded));
|
|
664
795
|
}
|
|
665
796
|
|
|
666
797
|
async awaitSettlementEncoded(encoded: unknown): Promise<unknown> {
|
|
667
|
-
return this.#
|
|
798
|
+
return this.#runNative("await_settlement", awaitSettlementEndpoint(encoded));
|
|
668
799
|
}
|
|
669
800
|
|
|
670
801
|
async observePage(encoded: unknown): Promise<unknown> {
|
|
671
|
-
return this.#
|
|
802
|
+
return this.#runNative("observe", observePageEndpoint(encoded));
|
|
672
803
|
}
|
|
673
804
|
|
|
674
805
|
async abortEncoded(encoded: unknown): Promise<unknown> {
|
|
675
|
-
return this.#
|
|
806
|
+
return this.#runNative("abort", abortEndpoint(encoded));
|
|
676
807
|
}
|
|
677
808
|
|
|
678
809
|
async resolveApprovalEncoded(encoded: unknown): Promise<unknown> {
|
|
679
|
-
return this.#
|
|
810
|
+
return this.#runNative("resolve_approval", resolveApprovalEndpoint(encoded));
|
|
680
811
|
}
|
|
681
812
|
|
|
682
813
|
async resolveUnknownEncoded(encoded: unknown): Promise<unknown> {
|
|
683
|
-
return this.#
|
|
814
|
+
return this.#runNative("resolve_unknown", resolveUnknownEndpoint(encoded));
|
|
684
815
|
}
|
|
685
816
|
|
|
686
817
|
async explainEncoded(encoded: unknown): Promise<unknown> {
|
|
687
|
-
return this.#
|
|
818
|
+
return this.#runNative("explain", explainEndpoint(encoded));
|
|
688
819
|
}
|
|
689
820
|
|
|
690
821
|
async verifyEncoded(encoded: unknown): Promise<unknown> {
|
|
691
|
-
return this.#
|
|
822
|
+
return this.#runNative("verify", verifyEndpoint(encoded));
|
|
692
823
|
}
|
|
693
824
|
|
|
694
825
|
async retryEncoded(encoded: unknown): Promise<unknown> {
|
|
695
|
-
return this.#
|
|
826
|
+
return this.#runNative("retry", retryEndpoint(encoded));
|
|
696
827
|
}
|
|
697
828
|
|
|
698
829
|
async obligationsEncoded(encoded: unknown): Promise<unknown> {
|
|
699
|
-
return this.#
|
|
830
|
+
return this.#runNative("obligations", obligationsEndpoint(encoded));
|
|
700
831
|
}
|
|
701
832
|
|
|
702
833
|
async portCall(encoded: unknown): Promise<unknown> {
|
|
703
|
-
return this.#
|
|
834
|
+
return this.#runNative("port_call", portCallEndpoint(encoded));
|
|
704
835
|
}
|
|
705
836
|
|
|
706
837
|
async wake(): Promise<void> {
|
|
707
|
-
await this.#
|
|
838
|
+
await this.#runNative("wake", wakeEndpoint);
|
|
708
839
|
}
|
|
709
840
|
|
|
710
841
|
override async alarm(): Promise<void> {
|
|
711
|
-
await this.#
|
|
842
|
+
await this.#runNative("alarm", alarmEndpoint);
|
|
712
843
|
}
|
|
713
844
|
}
|
|
714
845
|
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,8 @@ export * from "./config.ts";
|
|
|
17
17
|
export * from "./alarm.ts";
|
|
18
18
|
export * from "./wake-scheduler.ts";
|
|
19
19
|
export * from "./transport.ts";
|
|
20
|
+
export * from "./telemetry.ts";
|
|
20
21
|
export * from "./layers.ts";
|
|
21
22
|
export * from "./conversation-object.ts";
|
|
22
23
|
export * from "./client.ts";
|
|
24
|
+
export * from "./code-mode-executor.ts";
|
package/src/layers.ts
CHANGED
|
@@ -31,8 +31,8 @@ import { Context, Duration, Effect, Layer, Schema } from "effect";
|
|
|
31
31
|
import { ConversationMaintenance, DurableAlarmService } from "./alarm.ts";
|
|
32
32
|
import {
|
|
33
33
|
ConversationObjectIdentity,
|
|
34
|
-
ConversationObjectNamespace,
|
|
35
34
|
DurableObjectContext,
|
|
35
|
+
type ConversationObjectNamespace,
|
|
36
36
|
} from "./bindings.ts";
|
|
37
37
|
import {
|
|
38
38
|
CLOUDFLARE_RUNTIME_DEFAULTS,
|
|
@@ -69,6 +69,11 @@ export interface CloudflareDurableRuntimeOptions {
|
|
|
69
69
|
readonly abortPollInterval?: number | undefined;
|
|
70
70
|
/** Milliseconds; default 25. */
|
|
71
71
|
readonly observationPollInterval?: number | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Milliseconds; default 2000. Cooperative budget for the background exporter flush registered
|
|
74
|
+
* after each native RPC, wake, or alarm span. Delivery never awaits the flush.
|
|
75
|
+
*/
|
|
76
|
+
readonly telemetryFlushTimeout?: number | undefined;
|
|
72
77
|
/** Bytes; default just under the 2 MB platform value limit. */
|
|
73
78
|
readonly maxStoredValueBytes?: number | undefined;
|
|
74
79
|
/** Default false. */
|
|
@@ -180,6 +185,8 @@ const configFromOptions = (
|
|
|
180
185
|
abortPollInterval: options.abortPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.abortPollInterval,
|
|
181
186
|
observationPollInterval:
|
|
182
187
|
options.observationPollInterval ?? CLOUDFLARE_RUNTIME_DEFAULTS.observationPollInterval,
|
|
188
|
+
telemetryFlushTimeout:
|
|
189
|
+
options.telemetryFlushTimeout ?? CLOUDFLARE_RUNTIME_DEFAULTS.telemetryFlushTimeout,
|
|
183
190
|
maxStoredValueBytes:
|
|
184
191
|
options.maxStoredValueBytes ?? CLOUDFLARE_RUNTIME_DEFAULTS.maxStoredValueBytes,
|
|
185
192
|
verifyOnOpen: options.verifyOnOpen ?? CLOUDFLARE_RUNTIME_DEFAULTS.verifyOnOpen,
|
|
@@ -254,9 +261,9 @@ const resolveBindings = (
|
|
|
254
261
|
* Storage compatibility is verified during construction: an incompatible database fails the
|
|
255
262
|
* Layer typed (`DoStorageCompatibilityError`) before anything is mutated (DEPLOY-008).
|
|
256
263
|
*
|
|
257
|
-
* Requires
|
|
258
|
-
*
|
|
259
|
-
* (DEPLOY-010).
|
|
264
|
+
* Requires the two binding services (`DurableObjectContext`, `ConversationObjectNamespace`).
|
|
265
|
+
* Host observability belongs to the Worker composition edge because native entrypoint lifecycle
|
|
266
|
+
* instrumentation, rather than this durable runtime assembly, consumes it (DEPLOY-010).
|
|
260
267
|
*/
|
|
261
268
|
export class CloudflareDurableRuntime {
|
|
262
269
|
static layer(
|
|
@@ -365,11 +372,13 @@ export class CloudflareDurableRuntime {
|
|
|
365
372
|
Layer.provideMerge(base),
|
|
366
373
|
);
|
|
367
374
|
|
|
368
|
-
|
|
375
|
+
const application = Layer.mergeAll(
|
|
369
376
|
runtimeStack,
|
|
370
377
|
ConversationMaintenance.layer.pipe(Layer.provide(runtimeStack)),
|
|
371
378
|
portsEndpointLayer,
|
|
372
379
|
);
|
|
380
|
+
|
|
381
|
+
return application;
|
|
373
382
|
}),
|
|
374
383
|
);
|
|
375
384
|
}
|