@effect-agent/platform-cloudflare 0.1.0-beta.13 → 0.1.0-beta.131
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/Alarm.d.mts +244 -0
- package/dist/Alarm.mjs +947 -0
- package/dist/Alarm.mjs.map +1 -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-DP6b-cpL.d.mts +125 -0
- package/dist/BrowserSession-DWoRC_iM.mjs +505 -0
- package/dist/BrowserSession-DWoRC_iM.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-DV6kya1H.mjs +495 -0
- package/dist/CloudflareBrowser-DV6kya1H.mjs.map +1 -0
- package/dist/CloudflareBrowser-Doe1M7R5.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-DKXGo8L3.d.mts +106 -0
- package/dist/CloudflareConfig.d.mts +2 -0
- package/dist/CloudflareConfig.mjs +128 -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 +530 -0
- package/dist/CloudflareSubscriptions.mjs.map +1 -0
- package/dist/CloudflareThreadClient.d.mts +1752 -0
- package/dist/CloudflareThreadClient.mjs +382 -0
- package/dist/CloudflareThreadClient.mjs.map +1 -0
- package/dist/InteractiveBrowser-BnqY_Y-F.d.mts +228 -0
- package/dist/InteractiveBrowser-CxFT-Nwm.mjs +1923 -0
- package/dist/InteractiveBrowser-CxFT-Nwm.mjs.map +1 -0
- package/dist/InteractiveBrowser.d.mts +2 -0
- package/dist/InteractiveBrowser.mjs +2 -0
- package/dist/ThreadObject-CVhZC-7l.mjs +849 -0
- package/dist/ThreadObject-CVhZC-7l.mjs.map +1 -0
- package/dist/ThreadObject-D8GzJxJB.d.mts +839 -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 -1550
- package/dist/index.mjs +15 -1716
- package/dist/prepared-admission-DU0_T-ev.mjs +73 -0
- package/dist/prepared-admission-DU0_T-ev.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -56
- package/src/Alarm.ts +1979 -0
- package/src/BrowserCredentials.ts +146 -0
- package/src/BrowserRestCapture.ts +477 -0
- package/src/BrowserRestCrawl.ts +540 -0
- package/src/BrowserSession.ts +624 -0
- package/src/CloudflareAiGateway.ts +170 -0
- package/src/CloudflareBindings.ts +199 -0
- package/src/CloudflareBrowser.ts +15 -0
- package/src/{code-mode-executor.ts → CloudflareCodeMode.ts} +308 -283
- package/src/{config.ts → CloudflareConfig.ts} +19 -12
- package/src/CloudflareMemory.ts +420 -0
- package/src/CloudflareScheduling.ts +747 -0
- package/src/CloudflareSubscriptions.ts +1041 -0
- package/src/CloudflareThreadClient.ts +809 -0
- package/src/InteractiveBrowser.ts +3246 -0
- package/src/ThreadObject.ts +1190 -0
- package/src/{wake-scheduler.ts → WakeScheduler.ts} +32 -26
- package/src/index.ts +14 -23
- 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/layers.ts +773 -0
- package/src/internal/message-delivery.ts +131 -0
- package/src/internal/prepared-admission.ts +116 -0
- package/src/internal/progress-wait.ts +121 -0
- package/src/internal/transport.ts +44 -0
- package/dist/index.mjs.map +0 -1
- package/src/alarm.ts +0 -334
- package/src/bindings.ts +0 -145
- package/src/client.ts +0 -646
- package/src/conversation-object.ts +0 -768
- package/src/layers.ts +0 -376
- package/src/transport.ts +0 -38
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Option, Redactable, Redacted, Schema } from "effect";
|
|
2
|
+
import {
|
|
3
|
+
FetchHttpClient,
|
|
4
|
+
Headers,
|
|
5
|
+
HttpClient,
|
|
6
|
+
HttpClientError,
|
|
7
|
+
HttpClientRequest,
|
|
8
|
+
} from "effect/unstable/http";
|
|
9
|
+
|
|
10
|
+
const Segment = Schema.NonEmptyString.check(
|
|
11
|
+
Schema.isMaxLength(256),
|
|
12
|
+
Schema.isPattern(/^[a-zA-Z0-9_-]+$/),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const decodeSegment = Schema.decodeUnknownSync(Segment);
|
|
16
|
+
|
|
17
|
+
/** Options understood by upstream Effect AI clients; no model or provider wrapper is created. */
|
|
18
|
+
export interface ClientOptions {
|
|
19
|
+
readonly apiUrl: string;
|
|
20
|
+
readonly transformClient: (client: HttpClient.HttpClient) => HttpClient.HttpClient;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ProviderOptions {
|
|
24
|
+
readonly accountId: string;
|
|
25
|
+
readonly gatewayId: string;
|
|
26
|
+
/** Cloudflare's provider path, such as openai, anthropic, google-ai-studio, or perplexity-ai. */
|
|
27
|
+
readonly provider: string;
|
|
28
|
+
/** Omit only for an unauthenticated gateway with a separately supplied provider key. */
|
|
29
|
+
readonly apiToken?: Redacted.Redacted<string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RestOptions {
|
|
33
|
+
readonly accountId: string;
|
|
34
|
+
readonly gatewayId: string;
|
|
35
|
+
/** Cloudflare API token with Workers AI Read permission. */
|
|
36
|
+
readonly apiToken: Redacted.Redacted<string>;
|
|
37
|
+
/** Matches the paths appended by the upstream Effect client. */
|
|
38
|
+
readonly protocol: "responses" | "chat-completions" | "messages";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Choose exactly one route: a native provider path or an account REST protocol. */
|
|
42
|
+
export type RouteOptions =
|
|
43
|
+
| (ProviderOptions & { readonly protocol?: never })
|
|
44
|
+
| (RestOptions & { readonly provider?: never });
|
|
45
|
+
|
|
46
|
+
const redactGatewayToken = (headers: Headers.Headers, tokenHeader: string): void => {
|
|
47
|
+
// HTTP tracing can copy headers after preprocessing. Attach the public redaction
|
|
48
|
+
// protocol to the final request as well, before a provider builds error details.
|
|
49
|
+
Object.defineProperty(headers, Redactable.symbolRedactable, {
|
|
50
|
+
configurable: true,
|
|
51
|
+
value: (context: Context.Context<never>) =>
|
|
52
|
+
Headers.redact(headers, [...Context.get(context, Headers.CurrentRedactedNames), tokenHeader]),
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const clientOptions = (
|
|
57
|
+
apiUrl: string,
|
|
58
|
+
tokenHeader: "authorization" | "cf-aig-authorization",
|
|
59
|
+
apiToken: Redacted.Redacted<string> | undefined,
|
|
60
|
+
gatewayId?: string,
|
|
61
|
+
): ClientOptions =>
|
|
62
|
+
Object.freeze({
|
|
63
|
+
apiUrl,
|
|
64
|
+
transformClient: (client: HttpClient.HttpClient) =>
|
|
65
|
+
client.pipe(
|
|
66
|
+
HttpClient.mapRequestEffect((request) => {
|
|
67
|
+
const url = URL.parse(request.url);
|
|
68
|
+
|
|
69
|
+
// Check the normalized URL too: dot segments must not escape this account or gateway.
|
|
70
|
+
if (
|
|
71
|
+
url === null ||
|
|
72
|
+
url.username !== "" ||
|
|
73
|
+
url.password !== "" ||
|
|
74
|
+
!(url.href === apiUrl || url.href.startsWith(`${apiUrl}/`))
|
|
75
|
+
) {
|
|
76
|
+
return Effect.fail(
|
|
77
|
+
new HttpClientError.HttpClientError({
|
|
78
|
+
reason: new HttpClientError.InvalidUrlError({
|
|
79
|
+
request,
|
|
80
|
+
description: "Request is outside the configured Cloudflare AI Gateway endpoint",
|
|
81
|
+
}),
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
let prepared = request;
|
|
86
|
+
|
|
87
|
+
if (apiToken !== undefined) {
|
|
88
|
+
prepared = HttpClientRequest.setHeader(
|
|
89
|
+
prepared,
|
|
90
|
+
tokenHeader,
|
|
91
|
+
`Bearer ${Redacted.value(apiToken)}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (gatewayId !== undefined) {
|
|
95
|
+
prepared = HttpClientRequest.setHeader(prepared, "cf-aig-gateway-id", gatewayId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return Effect.succeed(prepared);
|
|
99
|
+
}),
|
|
100
|
+
HttpClient.transformResponse((effect) =>
|
|
101
|
+
Effect.gen(function* () {
|
|
102
|
+
const defaults = yield* Effect.serviceOption(FetchHttpClient.RequestInit);
|
|
103
|
+
|
|
104
|
+
return yield* effect.pipe(
|
|
105
|
+
Effect.provideService(FetchHttpClient.RequestInit, {
|
|
106
|
+
...Option.getOrElse(defaults, () => ({})),
|
|
107
|
+
redirect: "error",
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
}),
|
|
111
|
+
),
|
|
112
|
+
HttpClient.transformResponse((effect) =>
|
|
113
|
+
effect.pipe(
|
|
114
|
+
Effect.tap((response) =>
|
|
115
|
+
Effect.sync(() => redactGatewayToken(response.request.headers, tokenHeader)),
|
|
116
|
+
),
|
|
117
|
+
Effect.tapError((error) =>
|
|
118
|
+
Effect.sync(() => redactGatewayToken(error.reason.request.headers, tokenHeader)),
|
|
119
|
+
),
|
|
120
|
+
Effect.updateService(Headers.CurrentRedactedNames, (names) => [...names, tokenHeader]),
|
|
121
|
+
),
|
|
122
|
+
),
|
|
123
|
+
),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Provider-native proxy for model calls, streaming, embeddings, and hosted tools supported
|
|
128
|
+
* by that provider. Pass the result to the upstream client's layer along with its apiKey
|
|
129
|
+
* for BYOK-in-request, or omit apiKey for Gateway stored keys / Unified Billing.
|
|
130
|
+
* Provider model names and request bodies pass through unchanged. This module is Node-safe.
|
|
131
|
+
* Custom transforms and redirect policies must retain this endpoint and credential boundary.
|
|
132
|
+
*/
|
|
133
|
+
export const provider = (options: ProviderOptions): ClientOptions =>
|
|
134
|
+
clientOptions(
|
|
135
|
+
`https://gateway.ai.cloudflare.com/v1/${decodeSegment(options.accountId)}/${decodeSegment(options.gatewayId)}/${decodeSegment(options.provider)}`,
|
|
136
|
+
"cf-aig-authorization",
|
|
137
|
+
options.apiToken,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Cloudflare's account REST API (not the deprecated /compat API). Use provider-qualified
|
|
142
|
+
* model names, e.g. openai/gpt-4.1 or anthropic/claude-haiku-4.5, and omit provider apiKey.
|
|
143
|
+
* OpenAI clients append /responses or /chat/completions; Anthropic appends /v1/messages.
|
|
144
|
+
* Compatibility remains the responsibility of the selected upstream client and model.
|
|
145
|
+
*/
|
|
146
|
+
export const rest = (options: RestOptions): ClientOptions =>
|
|
147
|
+
clientOptions(
|
|
148
|
+
`https://api.cloudflare.com/client/v4/accounts/${decodeSegment(options.accountId)}/ai${options.protocol === "messages" ? "" : "/v1"}`,
|
|
149
|
+
"authorization",
|
|
150
|
+
options.apiToken,
|
|
151
|
+
decodeSegment(options.gatewayId),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Provide a Gateway-configured upstream client directly in a Layer pipeline.
|
|
156
|
+
* Pass the client's `layer` factory, or a callback adding client-specific options.
|
|
157
|
+
* The factory's errors and remaining services (such as HttpClient) stay visible.
|
|
158
|
+
* Model selection and resource ownership remain with the supplied upstream Layers.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* AnthropicLanguageModel.model("claude-haiku-4-5").pipe(
|
|
163
|
+
* Gateway.provide(AnthropicClient.layer, { ...options, provider: "anthropic" }),
|
|
164
|
+
* )
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export const provide = <Client, E, R>(
|
|
168
|
+
clientLayer: (options: ClientOptions) => Layer.Layer<Client, E, R>,
|
|
169
|
+
options: RouteOptions,
|
|
170
|
+
) => Layer.provide(clientLayer(options.provider !== undefined ? provider(options) : rest(options)));
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Context, Effect, Layer, Predicate, Schema } from "effect";
|
|
2
|
+
import { type ThreadId } from "effect-agent/identifiers";
|
|
3
|
+
import { type ProducerId } from "effect-agent/records";
|
|
4
|
+
import { RpcTargets } from "effect-cf";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Cloudflare platform bindings as Effect services (DEPLOY-010: "Cloudflare platform bindings
|
|
8
|
+
* are supplied as Effect services/Layers"). Application code never reads `env` or touches a
|
|
9
|
+
* `DurableObjectState` directly — the Thread Object class constructs these Layers once
|
|
10
|
+
* per incarnation and everything downstream consumes the services.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** A Cloudflare platform binding was missing or carried the wrong shape (DEPLOY-003/010). */
|
|
14
|
+
export class CloudflareBindingError extends Schema.TaggedError<CloudflareBindingError>()(
|
|
15
|
+
"CloudflareBindingError",
|
|
16
|
+
{
|
|
17
|
+
binding: Schema.String,
|
|
18
|
+
message: Schema.String,
|
|
19
|
+
},
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The RPC surface one Thread Durable Object exposes to Workers and to sibling
|
|
24
|
+
* Thread Objects. `ThreadObject.make` implements it; the Worker-side client
|
|
25
|
+
* and the cross-Object transport call it through `DurableObjectNamespace` stubs. Every
|
|
26
|
+
* `encoded` value is a Schema-encoded envelope (`client.ts` wire schemas for host entry
|
|
27
|
+
* points, `@effect-agent/storage-cloudflare` port envelopes for `portCall`), so the RPC
|
|
28
|
+
* boundary carries only structured-cloneable JSON. The optional trailing trace context is
|
|
29
|
+
* transient native RPC metadata, stripped by an opted-in effect-cf receiver before decoding
|
|
30
|
+
* the host envelope. It never enters durable state.
|
|
31
|
+
*/
|
|
32
|
+
export interface ThreadObjectRpc extends Rpc.DurableObjectBranded {
|
|
33
|
+
/** Admission-limits gate + `DurableAgentRuntime.submit`; answers a `SubmitResponse`. */
|
|
34
|
+
submitEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
35
|
+
/** Wake-hinted, poll-guaranteed settlement wait; answers an `AwaitSettlementResponse`. */
|
|
36
|
+
submissionStatusEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
37
|
+
awaitSettlementEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
38
|
+
/** Event-driven durable progress wait; answers a `ProgressObserved` host response. */
|
|
39
|
+
awaitProgressEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
40
|
+
/** Best-effort cancellation for one in-flight progress wait. */
|
|
41
|
+
cancelProgressEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
42
|
+
/** One bounded page of canonical records; answers an `ObservePageResponse`. */
|
|
43
|
+
observePage(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
44
|
+
/** Durable abort intent; answers an `AbortResponse`. */
|
|
45
|
+
abortEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
46
|
+
/** Durable approval decision (plan §2.6); answers a `ResolveApprovalResponse`. */
|
|
47
|
+
resolveApprovalEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
48
|
+
/** Authorized DUR-017 Unknown-Outcome resolution; answers a `ResolveUnknownResponse`. */
|
|
49
|
+
resolveUnknownEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
50
|
+
/** Owner-side cross-Object port endpoint (WP2 envelopes, executed on LOCAL facets). */
|
|
51
|
+
portCall(encoded: unknown): Promise<unknown>;
|
|
52
|
+
/** Droppable liveness hint from another Object: arms an immediate alarm. */
|
|
53
|
+
wake(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A logical Thread endpoint may be bound to an application Object without forging its brand. */
|
|
57
|
+
export type ThreadObjectClient = Omit<ThreadObjectRpc, keyof Rpc.DurableObjectBranded>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Deterministic logical Thread placement. The native adapter uses `idFromName(threadId)`;
|
|
61
|
+
* a shared application owner can bind that logical identity into its RPC adapter instead.
|
|
62
|
+
* Lookup performs no I/O and grants no authority. Calls reuse a native target within
|
|
63
|
+
* one invocation; incoming requests and durable retries acquire their own targets.
|
|
64
|
+
*/
|
|
65
|
+
export class ThreadObjectNamespace extends Context.Service<
|
|
66
|
+
ThreadObjectNamespace,
|
|
67
|
+
{
|
|
68
|
+
readonly get: (threadId: ThreadId) => ThreadObjectClient;
|
|
69
|
+
/** Stable binding name for opted-in native RPC tracing; absent by default. */
|
|
70
|
+
readonly rpcTracing?: string;
|
|
71
|
+
}
|
|
72
|
+
>()("@effect-agent/platform-cloudflare/ThreadObjectNamespace") {
|
|
73
|
+
static layer(
|
|
74
|
+
namespace: DurableObjectNamespace<ThreadObjectRpc>,
|
|
75
|
+
options: { readonly rpcTracing?: string } = {},
|
|
76
|
+
): Layer.Layer<ThreadObjectNamespace> {
|
|
77
|
+
return Layer.succeed(ThreadObjectNamespace)({
|
|
78
|
+
get: (threadId) => namespace.get(namespace.idFromName(threadId)),
|
|
79
|
+
...(options.rpcTracing === undefined ? {} : { rpcTracing: options.rpcTracing }),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Invoke a placed Thread using the current invocation's native RPC channel. */
|
|
85
|
+
export const callThreadObject = Effect.fn("callThreadObject")(function* <A, E>(
|
|
86
|
+
threadId: ThreadId,
|
|
87
|
+
invoke: (target: ThreadObjectClient) => Promise<A>,
|
|
88
|
+
onError: (cause: unknown) => E,
|
|
89
|
+
): Effect.fn.Return<A, E, ThreadObjectNamespace> {
|
|
90
|
+
const namespace = yield* ThreadObjectNamespace;
|
|
91
|
+
|
|
92
|
+
const target = yield* RpcTargets.get(namespace, threadId, () => namespace.get(threadId)).pipe(
|
|
93
|
+
Effect.mapError(onError),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return yield* Effect.tryPromise({ try: () => invoke(target), catch: onError }).pipe(
|
|
97
|
+
Effect.tapCause(() => RpcTargets.invalidate(target)),
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Narrow one `env` member to a `DurableObjectNamespace`. `env` is an untyped platform value,
|
|
103
|
+
* and a namespace binding is a host object no Schema can decode, so this is the documented
|
|
104
|
+
* narrowest-boundary check (structural probe for the namespace surface the transport uses);
|
|
105
|
+
* a missing or misshaped binding fails typed before any Layer is built.
|
|
106
|
+
*/
|
|
107
|
+
export const threadNamespaceFromEnv = Effect.fn("threadNamespaceFromEnv")(function* (
|
|
108
|
+
env: unknown,
|
|
109
|
+
binding: string,
|
|
110
|
+
): Effect.fn.Return<DurableObjectNamespace<ThreadObjectRpc>, CloudflareBindingError> {
|
|
111
|
+
if (!Predicate.isObjectKeyword(env)) {
|
|
112
|
+
return yield* CloudflareBindingError.make({
|
|
113
|
+
binding,
|
|
114
|
+
message: "The Worker environment is not an object; no bindings are available.",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const candidate = yield* Effect.try({
|
|
119
|
+
try: () => {
|
|
120
|
+
const value: unknown = Reflect.get(env, binding);
|
|
121
|
+
|
|
122
|
+
if (!Predicate.isObjectKeyword(value)) return undefined;
|
|
123
|
+
const idFromName: unknown = Reflect.get(value, "idFromName");
|
|
124
|
+
const get: unknown = Reflect.get(value, "get");
|
|
125
|
+
|
|
126
|
+
return typeof idFromName === "function" && typeof get === "function" ? value : undefined;
|
|
127
|
+
},
|
|
128
|
+
catch: () =>
|
|
129
|
+
CloudflareBindingError.make({
|
|
130
|
+
binding,
|
|
131
|
+
message: `env.${binding} could not be inspected as a DurableObjectNamespace binding.`,
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (candidate !== undefined) {
|
|
136
|
+
// The structural probe above is the entire runtime contract this package relies on;
|
|
137
|
+
// the assertion records that `idFromName`/`get` name a DurableObjectNamespace.
|
|
138
|
+
return candidate as unknown as DurableObjectNamespace<ThreadObjectRpc>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return yield* CloudflareBindingError.make({
|
|
142
|
+
binding,
|
|
143
|
+
message:
|
|
144
|
+
`env.${binding} is not a DurableObjectNamespace binding; declare the Thread ` +
|
|
145
|
+
"Object class under this binding in the Worker configuration.",
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Build the namespace from Worker `env` (fails typed). Enable `rpcTracing` only when the
|
|
151
|
+
* receiver also opts into the effect-cf native RPC trace-context contract.
|
|
152
|
+
*/
|
|
153
|
+
export const threadNamespaceLayer = (
|
|
154
|
+
env: unknown,
|
|
155
|
+
binding: string,
|
|
156
|
+
options: { readonly rpcTracing?: boolean } = {},
|
|
157
|
+
): Layer.Layer<ThreadObjectNamespace, CloudflareBindingError> =>
|
|
158
|
+
Layer.effect(ThreadObjectNamespace)(
|
|
159
|
+
Effect.map(threadNamespaceFromEnv(env, binding), (namespace) => ({
|
|
160
|
+
get: (threadId) => namespace.get(namespace.idFromName(threadId)),
|
|
161
|
+
...(options.rpcTracing === true ? { rpcTracing: binding } : {}),
|
|
162
|
+
})),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The live Durable Object execution context of THIS incarnation. Only Layer construction and
|
|
167
|
+
* the alarm service consume it; important state never lives on it (`ctx.storage` is truth,
|
|
168
|
+
* everything in memory is a cache — deployment spec §11).
|
|
169
|
+
*/
|
|
170
|
+
export class DurableObjectContext extends Context.Service<
|
|
171
|
+
DurableObjectContext,
|
|
172
|
+
{
|
|
173
|
+
readonly ctx: DurableObjectState;
|
|
174
|
+
readonly env: unknown;
|
|
175
|
+
}
|
|
176
|
+
>()("@effect-agent/platform-cloudflare/DurableObjectContext") {
|
|
177
|
+
static layer(ctx: DurableObjectState, env: unknown): Layer.Layer<DurableObjectContext> {
|
|
178
|
+
return Layer.succeed(DurableObjectContext)({ ctx, env });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The Thread identity this Object serializes and the producer identity its Attempts
|
|
184
|
+
* write with (`{producerPrefix}:{threadId}`, plan §1.4). Derived once per incarnation
|
|
185
|
+
* from `ctx.id.name` — the Object identity rule guarantees the name IS the Thread ID.
|
|
186
|
+
*/
|
|
187
|
+
export class ThreadObjectIdentity extends Context.Service<
|
|
188
|
+
ThreadObjectIdentity,
|
|
189
|
+
{
|
|
190
|
+
readonly threadId: ThreadId;
|
|
191
|
+
readonly producerId: ProducerId;
|
|
192
|
+
}
|
|
193
|
+
>()("@effect-agent/platform-cloudflare/ThreadObjectIdentity") {}
|
|
194
|
+
|
|
195
|
+
/** Logical Threads whose canonical stores and admission ledger live in this physical Object. */
|
|
196
|
+
export class ThreadObjectPlacement extends Context.Service<
|
|
197
|
+
ThreadObjectPlacement,
|
|
198
|
+
{ readonly ownsThread: (threadId: ThreadId) => boolean }
|
|
199
|
+
>()("@effect-agent/platform-cloudflare/ThreadObjectPlacement") {}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Public CloudflareBrowser API. Implementation helpers remain private. */
|
|
2
|
+
export {
|
|
3
|
+
BrowserQuickActionBrowserBinding,
|
|
4
|
+
BrowserQuickActionRpcError,
|
|
5
|
+
BrowserQuickActionWorkersAi,
|
|
6
|
+
BrowserQuickActionWorkersAiPolicyError,
|
|
7
|
+
browserQuickActionCaptureLayer,
|
|
8
|
+
browserQuickActionWorkersAiCaptureLayer,
|
|
9
|
+
browserQuickActionScreenshotLayer,
|
|
10
|
+
CloudflareBrowser,
|
|
11
|
+
type BrowserQuickActionCaptureOptions,
|
|
12
|
+
type BrowserQuickActionClient,
|
|
13
|
+
type BrowserQuickActionWorkersAiPolicy,
|
|
14
|
+
type CloudflareBrowserOptions,
|
|
15
|
+
} from "./internal/browser-quick-action.ts";
|